drop dead solver and pdf code, fix reputation and truncation
This commit is contained in:
@@ -122,7 +122,7 @@ pub fn to_pdf(puzzle: &Puzzle, title: &str, variant: &str) -> Vec<u8> {
|
||||
|
||||
c.push_str(&format!("BT /F2 6 Tf 0.6 0.55 0.5 rg {} {} Td (cruciverb) Tj ET\n", margin, margin - 10.0));
|
||||
|
||||
build_pdf_with_fonts(&c, pw, ph, bold_font.as_deref(), regular_font.as_deref())
|
||||
build_pdf_clean(&c, pw, ph, regular_font.as_deref(), bold_font.as_deref())
|
||||
}
|
||||
|
||||
fn esc(s: &str) -> String {
|
||||
@@ -130,100 +130,16 @@ fn esc(s: &str) -> String {
|
||||
}
|
||||
|
||||
fn truncate(s: &str, max: usize) -> String {
|
||||
if s.len() <= max { s.to_string() } else { format!("{}...", &s[..max - 3]) }
|
||||
}
|
||||
|
||||
fn build_pdf_with_fonts(content: &str, pw: f64, ph: f64, bold_ttf: Option<&[u8]>, regular_ttf: Option<&[u8]>) -> Vec<u8> {
|
||||
let mut pdf = Vec::new();
|
||||
let mut offsets = Vec::new();
|
||||
let mut obj_count = 0;
|
||||
|
||||
pdf.extend_from_slice(b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n");
|
||||
|
||||
macro_rules! obj {
|
||||
($data:expr) => {{
|
||||
obj_count += 1;
|
||||
offsets.push(pdf.len());
|
||||
pdf.extend_from_slice(format!("{} 0 obj\n", obj_count).as_bytes());
|
||||
pdf.extend_from_slice($data);
|
||||
pdf.extend_from_slice(b"\nendobj\n");
|
||||
obj_count
|
||||
}};
|
||||
}
|
||||
|
||||
// obj 1: catalog
|
||||
obj!(b"<< /Type /Catalog /Pages 2 0 R >>");
|
||||
|
||||
// obj 2: pages
|
||||
obj!(b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>");
|
||||
|
||||
// build font objects
|
||||
let (f1_ref, f2_ref);
|
||||
|
||||
if let Some(reg_data) = regular_ttf {
|
||||
// embedded TrueType font for F1 (regular/body)
|
||||
let stream_id = {
|
||||
obj_count += 1;
|
||||
offsets.push(pdf.len());
|
||||
pdf.extend_from_slice(format!("{} 0 obj\n", obj_count).as_bytes());
|
||||
pdf.extend_from_slice(format!("<< /Length {} /Length1 {} >>\nstream\n", reg_data.len(), reg_data.len()).as_bytes());
|
||||
pdf.extend_from_slice(reg_data);
|
||||
pdf.extend_from_slice(b"\nendstream\nendobj\n");
|
||||
obj_count
|
||||
};
|
||||
let desc_id = obj!(format!("<< /Type /FontDescriptor /FontName /DMSans /Flags 32 /FontFile2 {} 0 R /ItalicAngle 0 /Ascent 1000 /Descent -200 /CapHeight 700 /StemV 80 >>", stream_id).as_bytes());
|
||||
f1_ref = obj!(format!("<< /Type /Font /Subtype /TrueType /BaseFont /DMSans /FirstChar 32 /LastChar 255 /Encoding /WinAnsiEncoding /FontDescriptor {} 0 R >>", desc_id).as_bytes());
|
||||
if s.chars().count() <= max {
|
||||
s.to_string()
|
||||
} else {
|
||||
f1_ref = obj!(b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>");
|
||||
let keep = max.saturating_sub(3);
|
||||
let cut: String = s.chars().take(keep).collect();
|
||||
format!("{}...", cut)
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bold_data) = bold_ttf {
|
||||
let stream_id = {
|
||||
obj_count += 1;
|
||||
offsets.push(pdf.len());
|
||||
pdf.extend_from_slice(format!("{} 0 obj\n", obj_count).as_bytes());
|
||||
pdf.extend_from_slice(format!("<< /Length {} /Length1 {} >>\nstream\n", bold_data.len(), bold_data.len()).as_bytes());
|
||||
pdf.extend_from_slice(bold_data);
|
||||
pdf.extend_from_slice(b"\nendstream\nendobj\n");
|
||||
obj_count
|
||||
};
|
||||
let desc_id = obj!(format!("<< /Type /FontDescriptor /FontName /PlayfairDisplay-Bold /Flags 32 /FontFile2 {} 0 R /ItalicAngle 0 /Ascent 1000 /Descent -200 /CapHeight 700 /StemV 120 >>", stream_id).as_bytes());
|
||||
f2_ref = obj!(format!("<< /Type /Font /Subtype /TrueType /BaseFont /PlayfairDisplay-Bold /FirstChar 32 /LastChar 255 /Encoding /WinAnsiEncoding /FontDescriptor {} 0 R >>", desc_id).as_bytes());
|
||||
} else {
|
||||
f2_ref = obj!(b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>");
|
||||
}
|
||||
|
||||
// obj: content stream
|
||||
let content_id = {
|
||||
obj_count += 1;
|
||||
offsets.push(pdf.len());
|
||||
pdf.extend_from_slice(format!("{} 0 obj\n", obj_count).as_bytes());
|
||||
pdf.extend_from_slice(format!("<< /Length {} >>\nstream\n", content.len()).as_bytes());
|
||||
pdf.extend_from_slice(content.as_bytes());
|
||||
pdf.extend_from_slice(b"\nendstream\nendobj\n");
|
||||
obj_count
|
||||
};
|
||||
|
||||
// obj 3: page (deferred - write now)
|
||||
let page_data = format!(
|
||||
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {} {}] /Contents {} 0 R /Resources << /Font << /F1 {} 0 R /F2 {} 0 R >> >> >>",
|
||||
pw, ph, content_id, f1_ref, f2_ref
|
||||
);
|
||||
// we need to insert page as obj 3, but we already wrote objects 1,2 then font objects
|
||||
// so obj 3 wasn't written yet. let's write it now
|
||||
obj_count += 1;
|
||||
offsets.push(pdf.len());
|
||||
pdf.extend_from_slice(format!("{} 0 obj\n{}\nendobj\n", obj_count, page_data).as_bytes());
|
||||
let _page_obj = obj_count;
|
||||
|
||||
// fix pages to reference our page object
|
||||
// this is hacky but works - we'll just rebuild the whole thing properly
|
||||
|
||||
// actually let's just rebuild cleanly
|
||||
drop(pdf);
|
||||
drop(offsets);
|
||||
build_pdf_clean(content, pw, ph, regular_ttf, bold_ttf)
|
||||
}
|
||||
|
||||
fn build_pdf_clean(content: &str, pw: f64, ph: f64, regular_ttf: Option<&[u8]>, bold_ttf: Option<&[u8]>) -> Vec<u8> {
|
||||
// collect all objects first, then serialize
|
||||
|
||||
@@ -1,49 +1,3 @@
|
||||
use crate::dict::Dictionary;
|
||||
|
||||
pub fn score_candidate(
|
||||
word_idx: usize,
|
||||
dict: &Dictionary,
|
||||
used_words: &[bool],
|
||||
placed_texts: &[String],
|
||||
) -> f64 {
|
||||
if used_words[word_idx] {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
let word = dict.get(word_idx);
|
||||
let text = &word.text;
|
||||
let mut score = word.frequency;
|
||||
|
||||
// reject words that are too similar to already-placed words
|
||||
for placed in placed_texts {
|
||||
if is_related(text, placed) {
|
||||
return -1.0;
|
||||
}
|
||||
}
|
||||
|
||||
// bonus for letter variety
|
||||
let bytes = text.as_bytes();
|
||||
let mut seen = [false; 26];
|
||||
let mut unique = 0;
|
||||
for &b in bytes {
|
||||
let idx = (b - b'A') as usize;
|
||||
if !seen[idx] {
|
||||
seen[idx] = true;
|
||||
unique += 1;
|
||||
}
|
||||
}
|
||||
let variety = unique as f64 / bytes.len() as f64;
|
||||
score += variety * 0.1;
|
||||
|
||||
// bonus for common letters
|
||||
let common = b"ETAOINSHRDLU";
|
||||
let common_count = bytes.iter().filter(|b| common.contains(b)).count();
|
||||
let common_ratio = common_count as f64 / bytes.len() as f64;
|
||||
score += common_ratio * 0.05;
|
||||
|
||||
score
|
||||
}
|
||||
|
||||
pub fn is_related(a: &str, b: &str) -> bool {
|
||||
// one is a prefix of the other (SEAL / SEALS, RUN / RUNNING)
|
||||
if a.len() >= 3 && b.len() >= 3 {
|
||||
|
||||
@@ -890,7 +890,7 @@ async fn handle_leaderboard(state: &Arc<AppState>, activity: &serde_json::Value,
|
||||
|
||||
// check instance reputation
|
||||
let reputation: f64 = dbi.db.query_row(
|
||||
"SELECT reputation FROM ap_instance_reputation WHERE domain = ?1",
|
||||
"SELECT score FROM ap_instance_reputation WHERE domain = ?1",
|
||||
[origin], |row| row.get(0),
|
||||
).unwrap_or(100.0);
|
||||
if reputation < 30.0 {
|
||||
|
||||
Reference in New Issue
Block a user