diff --git a/cruciverb-core/src/export/pdf.rs b/cruciverb-core/src/export/pdf.rs index bc6b6ec..0c81b5e 100644 --- a/cruciverb-core/src/export/pdf.rs +++ b/cruciverb-core/src/export/pdf.rs @@ -122,7 +122,7 @@ pub fn to_pdf(puzzle: &Puzzle, title: &str, variant: &str) -> Vec { 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]) } + if s.chars().count() <= max { + s.to_string() + } else { + let keep = max.saturating_sub(3); + let cut: String = s.chars().take(keep).collect(); + format!("{}...", cut) + } } -fn build_pdf_with_fonts(content: &str, pw: f64, ph: f64, bold_ttf: Option<&[u8]>, regular_ttf: Option<&[u8]>) -> Vec { - 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()); - } else { - f1_ref = obj!(b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>"); - } - - 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 { // collect all objects first, then serialize diff --git a/cruciverb-core/src/solver/candidate.rs b/cruciverb-core/src/solver/candidate.rs index f5e6c67..f35a32b 100644 --- a/cruciverb-core/src/solver/candidate.rs +++ b/cruciverb-core/src/solver/candidate.rs @@ -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 { diff --git a/cruciverb-server/src/activitypub/inbox.rs b/cruciverb-server/src/activitypub/inbox.rs index 31054fc..093e201 100644 --- a/cruciverb-server/src/activitypub/inbox.rs +++ b/cruciverb-server/src/activitypub/inbox.rs @@ -890,7 +890,7 @@ async fn handle_leaderboard(state: &Arc, 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 {