F1, F2: adapt to main and re-pin the Arabic layout golden

Rebase onto 7163ba3 (396 commits on). Three adaptations, none of them
behavioural:

- `crates/fonts` renamed its `arabic` module to `shaping` and added
  Thai, so `Family::width`'s callers moved; the italic argument is
  re-applied to all six, including one new call site main added in
  `draw_laid_shaped`.
- `Metrics::text_of` now returns `Cow<str>`, so the F2 tests compare
  through `as_deref()`.
- `unicode_map` writes into a struct field now, so `/Differences`
  inserts go to `out.encoding`.

`SMALL_ITEM_FINGERPRINT` in added/arabic_tests.rs is re-pinned, and this
is the part worth reviewing. That golden is the appearance-stream pin
F1's acceptance criteria asked for, which landed from the Arabic work
after F1 was written; it caught the change correctly. The item's Latin
run is "Hi " in Helvetica 12pt. The old character-class guess called H
680, i 260 and space 260 (14.4pt); the published metrics are 722, 222
and 278 (14.664pt), verified against the table. The Arabic clusters that
follow start 0.264pt further right, which is the intended effect: the
glyphs now land where the font says they do. The Arabic path itself is
untouched.

Also dropped from the rebase: a re-added ATTRIBUTION entry for
vendor/hayro-interpret/assets/LAB.icc, which main deleted in favour of
CGATS001Compat-v2-micro.icc. Carrying it would have attributed a file
that no longer exists.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Tanish Manku 2026-10-12 12:57:39 -07:00
parent ce9a1d1014
commit 0a09864833
4 changed files with 13 additions and 14 deletions

View File

@ -11,9 +11,7 @@
use std::collections::{HashMap, HashSet};
use pdfcraft_cos::{Dict, Document, ObjRef, Object, PdfString, Stream};
use pdfcraft_fonts::{
GlyphError, GlyphOutline, ShapedCluster, ShapedScript, Std14, literal, shape_arabic, shape_thai, shaped_glyph, win_ansi,
};
use pdfcraft_fonts::{GlyphError, GlyphOutline, ShapedCluster, ShapedScript, Std14, literal, shape_arabic, shape_thai, shaped_glyph, win_ansi};
use unicode_bidi::{Level, ParagraphBidiInfo};
use crate::{EditError, MarkKind, check, contents, n, page_list, place_tagged};
@ -286,7 +284,7 @@ fn thai_lines(t: &AddedText, para: &str, width: f64) -> Result<Vec<String>, Edit
Piece::Shaped(clusters) => atoms.extend(clusters.into_iter().map(|c| (c.text, c.advance * t.size))),
Piece::Latin(s) => atoms.extend(s.chars().map(|c| {
let text = c.to_string();
let w = t.family.width(&text, t.size, t.bold);
let w = t.family.width(&text, t.size, t.bold, t.italic);
(text, w)
})),
}

View File

@ -8,7 +8,12 @@ use super::*;
/// Regression fingerprint for the small-item fixture: drawing bytes, font map and document
/// objects, with generated streams decoded so Flate backend selection cannot change the hash.
const SMALL_ITEM_FINGERPRINT: u64 = 4_811_174_621_764_261_623;
// Re-pinned when the standard-14 metrics became exact (F1). The Latin run in the fingerprinted
// item is "Hi " in Helvetica 12pt: the old character-class guess called H 680, i 260 and space
// 260 (14.4pt), where the published metrics are 722, 222 and 278 (14.664pt). The Arabic clusters
// that follow therefore start 0.264pt further right, which is the whole point of F1 -- the glyphs
// now land where the font says they do. Nothing about the Arabic path changed.
const SMALL_ITEM_FINGERPRINT: u64 = 9_200_256_185_091_160_371;
/// A cluster that shows one character with the single glyph `id`, at `advance` em.
fn cluster(id: u32, advance: f64) -> ShapedCluster {

View File

@ -3251,11 +3251,7 @@ fn an_items_measured_face_is_the_face_it_is_written_as() {
let measured = family.std14(bold, italic);
assert_eq!(written, measured.base_font(), "{family:?} bold={bold} italic={italic}");
// ... and the public measure reaches that face, rather than an upright stand-in.
assert_eq!(
family.width("Anna", 12.0, bold, italic),
measured.text_width("Anna", 12.0),
"{family:?} bold={bold} italic={italic}"
);
assert_eq!(family.width("Anna", 12.0, bold, italic), measured.text_width("Anna", 12.0), "{family:?} bold={bold} italic={italic}");
}
}
}

View File

@ -1729,7 +1729,7 @@ mod encoding_tests {
let (doc, d) = simple("Custom", None, Some(differences(vec![Object::Int(65), Object::name("g7")])));
let m = Metrics::from_dict(&doc, &d);
assert_eq!(m.text_of(65), None);
assert_eq!(m.text_of(66), Some("B"), "neighbouring codes keep their meaning");
assert_eq!(m.text_of(66).as_deref(), Some("B"), "neighbouring codes keep their meaning");
}
#[test]
@ -1751,7 +1751,7 @@ mod encoding_tests {
// /Differences still applies on top of a built-in encoding we cannot read.
let (doc, d) = simple("Symbol", Some(4), Some(differences(vec![Object::Int(0x61), Object::name("uni03B1")])));
let m = Metrics::from_dict(&doc, &d);
assert_eq!(m.text_of(0x61), Some("\u{3b1}"), "the font told us this one");
assert_eq!(m.text_of(0x61).as_deref(), Some("\u{3b1}"), "the font told us this one");
assert_eq!(m.text_of(0x62), None, "and nothing about this one");
}
@ -1763,7 +1763,7 @@ mod encoding_tests {
enc.set(b"BaseEncoding".to_vec(), Object::name(name));
let (doc, d) = simple("Symbol", Some(4), Some(Object::Dict(enc)));
let m = Metrics::from_dict(&doc, &d);
assert_eq!(m.text_of(0x61), Some("a"), "{name} was asked for by name");
assert_eq!(m.text_of(0x61).as_deref(), Some("a"), "{name} was asked for by name");
}
}
@ -1775,7 +1775,7 @@ mod encoding_tests {
fn the_symbolic_flag_alone_does_not_suppress_the_latin_default() {
let (doc, d) = simple("SomeSubsetFont", Some(4), None);
let m = Metrics::from_dict(&doc, &d);
assert_eq!(m.text_of(0x61), Some("a"));
assert_eq!(m.text_of(0x61).as_deref(), Some("a"));
assert_eq!(m.decode(b"abc"), "abc");
}
}