mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-02 14:38:13 -06:00
Fonts: measure a remapped standard-14 code by the glyph it draws
A standard-14 font with no /Widths was measured by code in the WinAnsi-indexed table, so a /Differences remap took the width of the glyph WinAnsi gives that code: Helvetica with [65 /W 87 /A] laid out "AAAA" four W's wide. The code is now looked up through the font's encoding first; /Widths still go by code, and Symbol, ZapfDingbats and glyphs WinAnsi has no code for keep the code itself.
This commit is contained in:
+43
-1
@@ -766,7 +766,7 @@ impl Metrics {
|
||||
match self.std14 {
|
||||
// A font with no /Widths is one of the standard 14; use its published metrics.
|
||||
// Codes the font's encoding leaves undefined fall back to /MissingWidth.
|
||||
Some(f) => match u8::try_from(code).map(|c| f.width(c)).unwrap_or(0.0) {
|
||||
Some(f) => match u8::try_from(self.std14_code(f, code)).map(|c| f.width(c)).unwrap_or(0.0) {
|
||||
0.0 => self.default * self.scale,
|
||||
w => w / 1000.0,
|
||||
},
|
||||
@@ -774,6 +774,17 @@ impl Metrics {
|
||||
}
|
||||
}
|
||||
|
||||
/// Where the standard-14 table, indexed by WinAnsi, holds the glyph `code` draws in this font:
|
||||
/// its encoding (base and `/Differences`) names the glyph, so a remapped code is measured as
|
||||
/// the glyph it now shows (#2894). Symbol and ZapfDingbats are indexed by their own encodings,
|
||||
/// and a glyph WinAnsi has no code for keeps the code itself.
|
||||
fn std14_code(&self, face: crate::Std14, code: u32) -> u32 {
|
||||
if matches!(face, crate::Std14::Symbol | crate::Std14::ZapfDingbats) {
|
||||
return code;
|
||||
}
|
||||
self.unicode.encoding.get(&code).and_then(|t| only_char(t)).and_then(crate::win_ansi_byte).map_or(code, u32::from)
|
||||
}
|
||||
|
||||
/// Word spacing applies to the single-byte code 32 (§9.3.3).
|
||||
pub fn is_space(&self, code: u32, len: usize) -> bool {
|
||||
code == 32 && len == 1
|
||||
@@ -1325,6 +1336,37 @@ mod tests {
|
||||
assert_eq!(m.encode("Ω").as_deref(), Some(&b"B"[..]));
|
||||
}
|
||||
|
||||
/// A standard-14 font with no `/Widths` is measured by the glyph each code draws, not by the
|
||||
/// glyph WinAnsi gives that code (#2894); `/Widths`, when present, still go by code.
|
||||
#[test]
|
||||
fn standard_fonts_without_widths_measure_the_glyph_their_differences_name() {
|
||||
let mut doc = Document::new_empty();
|
||||
let mut enc = Dict::new();
|
||||
enc.set(b"BaseEncoding".to_vec(), Object::name("WinAnsiEncoding"));
|
||||
let diffs =
|
||||
vec![Object::Int(65), Object::name("W"), Object::Int(87), Object::name("A"), Object::name("uni03A9"), Object::name("not.a.glyph")];
|
||||
enc.set(b"Differences".to_vec(), Object::Array(diffs));
|
||||
let f = font(
|
||||
&mut doc,
|
||||
vec![("Subtype", Object::name("Type1")), ("BaseFont", Object::name("Helvetica")), ("Encoding", Object::Dict(enc.clone()))],
|
||||
);
|
||||
let m = Metrics::from_dict(&doc, &f);
|
||||
assert_eq!((m.width(65), m.width(87)), (0.944, 0.667), "A and W swap their widths");
|
||||
// Omega has no WinAnsi code and the last name reads as nothing: both keep their code's width.
|
||||
assert_eq!((m.width(88), m.width(89)), (0.667, 0.667));
|
||||
let f = font(
|
||||
&mut doc,
|
||||
vec![
|
||||
("Subtype", Object::name("Type1")),
|
||||
("BaseFont", Object::name("Helvetica")),
|
||||
("FirstChar", Object::Int(87)),
|
||||
("Widths", Object::Array(vec![Object::Int(500)])),
|
||||
("Encoding", Object::Dict(enc)),
|
||||
],
|
||||
);
|
||||
assert_eq!(Metrics::from_dict(&doc, &f).width(87), 0.5, "/Widths go by code");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subset_fonts_only_encode_the_glyphs_they_have() {
|
||||
let mut doc = Document::new_empty();
|
||||
|
||||
@@ -405,6 +405,41 @@ fn reused_standard_fonts_wrap_and_align_with_their_own_widths() {
|
||||
assert!((start("initials") - 186.88).abs() < 1e-3, "Times: {}", start("initials"));
|
||||
}
|
||||
|
||||
/// A standard-14 font whose `/Differences` swap A and W draws `AAAA` with code 87, so it is
|
||||
/// measured as four A's (0.667 em), not as the W that code has in WinAnsi. The same remap with
|
||||
/// `/Widths` takes the width the font gives code 87, here 0.5 em.
|
||||
#[test]
|
||||
fn remapped_standard_fonts_are_measured_by_the_glyph_each_code_draws() {
|
||||
let objs: Vec<String> = vec![
|
||||
"<< /Type /Catalog /Pages 2 0 R /AcroForm 4 0 R >>".into(),
|
||||
"<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 612 792] >>".into(),
|
||||
"<< /Type /Page /Parent 2 0 R /Annots [7 0 R 8 0 R] >>".into(),
|
||||
"<< /Fields [7 0 R 8 0 R] /DA (/Dif 10 Tf 0 g) /DR << /Font << /Dif 5 0 R /DifW 6 0 R >> >> >>".into(),
|
||||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding << /BaseEncoding /WinAnsiEncoding /Differences [65 /W 87 /A] >> >>"
|
||||
.into(),
|
||||
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /FirstChar 87 /LastChar 87 /Widths [500] /Encoding << /BaseEncoding /WinAnsiEncoding /Differences [65 /W 87 /A] >> >>"
|
||||
.into(),
|
||||
"<< /Type /Annot /Subtype /Widget /FT /Tx /T (std) /Q 2 /Rect [21 500 221 520] /DA (/Dif 10 Tf 0 g) /P 3 0 R >>".into(),
|
||||
"<< /Type /Annot /Subtype /Widget /FT /Tx /T (widths) /Q 2 /Rect [21 470 221 490] /DA (/DifW 10 Tf 0 g) /P 3 0 R >>".into(),
|
||||
];
|
||||
let mut doc = document(&objs);
|
||||
set_value(&mut doc, "std", &FieldValue::Text("AAAA".into())).unwrap();
|
||||
set_value(&mut doc, "widths", &FieldValue::Text("AAAA".into())).unwrap();
|
||||
let doc = reopen(&doc);
|
||||
let all = fields(&doc);
|
||||
assert_eq!(appearance_text(&doc, &field(&all, "std").widgets[0], b"Dif").1, vec![b"WWWW".to_vec()], "A is drawn by code 87");
|
||||
let start = |name: &str| {
|
||||
let w = &field(&all, name).widgets[0];
|
||||
let n = doc.get(w.obj).as_dict().unwrap().get(b"AP").unwrap().as_dict().unwrap().reference(b"N").unwrap();
|
||||
let Object::Stream(s) = &*doc.get(n) else { panic!() };
|
||||
let tm = pdfcraft_content::parse(&s.decoded().unwrap()).ops.into_iter().find(|op| op.is("Tm")).unwrap();
|
||||
tm.operands[4].as_f64().unwrap()
|
||||
};
|
||||
// The text ends at 198 (200 pt box, 2 pt padding): 26.68 pt of A's, or 20 pt by /Widths.
|
||||
assert!((start("std") - 171.32).abs() < 1e-3, "no /Widths: {}", start("std"));
|
||||
assert!((start("widths") - 178.0).abs() < 1e-3, "/Widths: {}", start("widths"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn japanese_choices_use_the_unicode_cid_font() {
|
||||
// A choice field whose /DA names a non-embedded CID font with a predefined Unicode CMap
|
||||
|
||||
Reference in New Issue
Block a user