diff --git a/crates/eps/src/import/interp.rs b/crates/eps/src/import/interp.rs index 0ea7017c11..43346988ff 100644 --- a/crates/eps/src/import/interp.rs +++ b/crates/eps/src/import/interp.rs @@ -98,7 +98,7 @@ pub(crate) struct Interp<'a> { /// The font a `kshow` procedure sees as current while type in a CIDFont only named is set in /// a font of its own (see `text`). pub kshow_font: Option, - /// The `cshow` procedure running over type in a CIDFont only named, with the font it sees as + /// The `cshow` procedure running over a composite font's type, with the font it sees as /// current (see `text`). pub cshow: Option, pub g: GState, diff --git a/crates/eps/src/import/tests_cid.rs b/crates/eps/src/import/tests_cid.rs index d7ef0623d9..b44a5410ee 100644 --- a/crates/eps/src/import/tests_cid.rs +++ b/crates/eps/src/import/tests_cid.rs @@ -136,6 +136,31 @@ fn codes_map_by_their_codespace_with_notdef_fallbacks() { width("<01>", 30.0); } +/// `cshow` over a CMap runs its procedure once for each code, whatever its length, with the +/// code's last byte and its width (PLRM 3rd ed., `cshow`). +#[test] +fn cshow_runs_once_for_each_cmap_code() { + let fonts = String::from_utf8(cid_font(true)).unwrap() + CMAP; + let check = |cond: &str| check(&format!("{fonts} /TestFont 50 selectfont {cond}")); + check("[ {pop pop} <418001> cshow ] dup length 2 eq exch aload pop 1 eq exch 65 eq and and"); + check("0 {pop exch pop add} <418001> cshow 55 sub abs 0.01 lt"); + check( + "{pop pop pop currentfont /CIDFontName get /seen exch def rootfont /FontName get /root exch def} <41> cshow seen /TestCID eq root /TestFont eq and", + ); + // With the CIDFont current, the byte the procedure was given shows the glyph of its code's + // CID: <8001>'s last byte draws CID 2, not code 01's CID 1. Another string is an error. + let shown = "{pop pop ( ) dup 0 4 -1 roll put show} <418001> cshow"; + check(&format!("gsave nulldevice 10 20 moveto {shown} currentpoint grestore pop 65 sub abs 0.01 lt")); + check("gsave nulldevice 10 20 moveto {{pop pop pop (B) show} <41> cshow} stopped grestore"); + let r = import(&program(true, &format!("/TestFont 50 selectfont 10 20 moveto {shown}"))).unwrap(); + let want = import(&program(true, "/TestFont 50 selectfont 10 20 moveto <418001> show")).unwrap(); + let (p, q) = (paths(&r), paths(&want)); + assert_eq!(p.len(), 2, "{p:?}"); + for (a, b) in p.iter().zip(&q) { + assert!((a.x0 - b.x0).abs() < 0.01 && (a.width() - b.width()).abs() < 0.01 && (a.height() - b.height()).abs() < 0.01, "{p:?} {q:?}"); + } +} + /// `kshow` over a CMap runs its procedure between each two codes with their values, whatever /// their length, and the next character follows where it moves the point (PLRM 3rd ed., `kshow`). #[test] @@ -188,6 +213,22 @@ fn type2_cidfonts_draw_their_truetype_glyphs_through_cidmap() { } } +/// A `cshow` procedure over a Type 2 CIDFont without a `FontMatrix` sees the CIDFont current at +/// the scale its glyphs are drawn at: an em of the composite font's size, as with the identity. +#[test] +fn cshow_over_a_type2_cidfont_without_a_matrix_sees_it_at_its_drawn_scale() { + let (h, i) = (glyph('H').0, glyph('I').0); + let with = cid2_font(&format!("<0000{h:04x}{i:04x}>")); + let without = with.replace("/FontMatrix [1 0 0 1 0 0] def", ""); + assert_ne!(with, without); + for fonts in [with, without] { + check(&format!( + "{fonts}/T2 /Identity-H [/TestCID2] composefont 50 scalefont setfont \ + {{pop pop pop currentfont /FontMatrix get 0 get /m exch def}} <0001> cshow m 50 sub abs 0.01 lt" + )); + } +} + /// `Identity-V`: each glyph an em below the one before (writing mode 1). #[test] fn identity_v_sets_glyphs_downwards() { @@ -385,6 +426,30 @@ fn cmaps_map_codes_to_base_fonts_codes_and_glyph_names() { }) .collect(); assert_eq!(text, "ABC"); + // A `cshow` procedure that shows the byte it was given, in the base font current while it + // runs, shows the character the code maps to, as `show` does. + let body = format!( + "{plain}{BF_CMAP}/BF /TestBF-H [/Plain42 /Helvetica] composefont 20 scalefont setfont 10 20 moveto \ + {{pop pop ( ) dup 0 4 -1 roll put show}} <001000110012> cshow" + ); + let r = super::tests::read(&body); + let text: String = all(&r.document) + .iter() + .filter_map(|n| match &n.kind { + NodeKind::Text(t) => Some(t.plain_text()), + _ => None, + }) + .collect(); + assert_eq!(text, "ABC"); + let body = format!( + "{plain}{BF_CMAP}/BF /TestBF-H [/Plain42 /Helvetica] composefont 50 scalefont setfont 10 20 moveto \ + {{pop pop ( ) dup 0 4 -1 roll put show}} <000100020003> cshow" + ); + let q = paths(&super::tests::read(&body)); + assert_eq!(q.len(), 3, "{q:?}"); + for (a, b) in p.iter().zip(&q) { + assert!((a.x0 - b.x0).abs() < 0.01 && (a.width() - b.width()).abs() < 0.01, "{p:?} {q:?}"); + } } /// A CID-keyed font over a predefined Unicode CMap the file doesn't define, its CIDFont only diff --git a/crates/eps/src/import/tests_composite.rs b/crates/eps/src/import/tests_composite.rs index 2f4074af97..ff3b3382d3 100644 --- a/crates/eps/src/import/tests_composite.rs +++ b/crates/eps/src/import/tests_composite.rs @@ -119,6 +119,45 @@ fn widthshow_matches_a_composite_character_by_font_number_and_code() { check(format!("{} {} sub abs 0.01 lt", end(2), 10.0 + 100.0 * wh)); } +/// `cshow` runs its procedure once for each character of a composite font, not for each byte, +/// with the last byte of its code and its width, and with the descendant that draws it as the +/// current font while `rootfont` gives the composite font (PLRM 3rd ed., `cshow` and `rootfont`). +#[test] +fn cshow_runs_once_for_each_composite_character_in_its_descendant() { + let (_, wh) = glyph('H'); + let (_, wi) = glyph('I'); + let check = |cond: String| super::tests::check(&format!("{} /Comp findfont 50 scalefont setfont {cond}", fonts())); + check("[ {pop pop} (\\000\\001\\000\\002) cshow ] dup length 2 eq exch aload pop 2 eq exch 1 eq and and".into()); + check(format!("0 {{pop exch pop add}} (\\000\\001\\000\\002) cshow {} sub abs 0.01 lt", 50.0 * (wh + wi))); + check("{pop pop pop currentfont /FontName get /seen exch def rootfont /FontName get /root exch def} (\\000\\001) cshow seen /T42 eq root /Comp eq and".into()); + check("{pop pop pop} (\\000\\001) cshow currentfont /FontName get /Comp eq".into()); + // A character the procedure shows in the descendant is as wide as the composite font sets it. + check(format!( + "gsave nulldevice 10 20 moveto {{pop pop ( ) dup 0 4 -1 roll put show}} (\\000\\001\\000\\002) cshow currentpoint grestore pop {} sub abs 0.01 lt", + 10.0 + 50.0 * (wh + wi) + )); + // A character in font number 1 still gives only the last byte of its code, not 258. + let two = fonts().replace("/Encoding [0] def", "/Encoding [0 0] def"); + super::tests::check(&format!("{two} /Comp findfont 50 scalefont setfont [ {{pop pop}} (\\001\\002) cshow ] dup length 1 eq exch 0 get 2 eq and")); +} + +/// A `cshow` procedure over a Type 42 descendant without a `FontMatrix` sees it current at the +/// scale its characters are drawn at: another character it measures there is as wide as the +/// composite font sets it, as with the identity. +#[test] +fn cshow_over_a_type42_descendant_without_a_matrix_sees_it_at_its_drawn_scale() { + let (_, wi) = glyph('I'); + let with = fonts(); + let without = with.replacen("/FontType 42 def /FontMatrix [1 0 0 1 0 0] def", "/FontType 42 def", 1); + assert_ne!(with, without); + for fonts in [with, without] { + super::tests::check(&format!( + "{fonts} /Comp findfont 50 scalefont setfont {{pop pop pop (\\002) stringwidth pop /w exch def}} (\\000\\001) cshow w {} sub abs 0.01 lt", + 50.0 * wi + )); + } +} + /// `kshow` runs its procedure between each two characters of a composite font, with the values of /// their codes (font number times 256 plus code), and the next character follows where it moves /// the current point (PLRM 3rd ed., `kshow`). diff --git a/crates/eps/src/import/text.rs b/crates/eps/src/import/text.rs index 257fdc2247..3de16d7bbf 100644 --- a/crates/eps/src/import/text.rs +++ b/crates/eps/src/import/text.rs @@ -251,6 +251,15 @@ impl Interp<'_> { self.g.font = root; return r; } + // In a composite font the file carries, once for each character it maps. + if let Some(glyphs) = self.composite_glyphs(&s)? { + let root = self.g.font.clone(); + let before = self.cshow.take(); + let r = self.composite_cshow(&glyphs, &proc, root.clone()); + self.cshow = before; + self.g.font = root; + return r; + } for b in s { let v = self.width_of(&[b])?; self.push(Obj::Int(i64::from(b)))?; @@ -992,6 +1001,7 @@ impl Spacing { /// A character of a composite font: the descendant font that draws it, its code there (its last /// byte for a CID), its CID and the notdef mapping's (`FMapType` 9), the composite font's matrix /// (over the descendant's own) and whether it is set vertically (`WMode` 1). +#[derive(Clone)] struct Mapped { font: DictRef, /// The byte that picked the descendant (the font number). @@ -1006,6 +1016,13 @@ struct Mapped { value: u32, } +impl Mapped { + /// The last byte of the character's code in the shown string (what `cshow` gives its procedure). + fn last(&self) -> u8 { + (self.value & 0xff) as u8 + } +} + /// Type in a CIDFont only named, over a predefined CMap ([`Interp::named_cid_type`]): its codes /// with their characters, the font it is set in, whether it runs down a column, and whether each /// code moves the point one em (down a column, or in a CMap VectorCraft can't read). @@ -1016,13 +1033,15 @@ struct NamedCid { ems: bool, } -/// A `cshow` procedure running over type in a CIDFont only named ([`Interp::named_cshow`]): the -/// CIDFont it sees as current, the composite font `rootfont` gives while that CIDFont is current, -/// and the type of the code it was given. +/// A `cshow` procedure running over a composite font's type ([`Interp::named_cshow`], +/// [`Interp::composite_cshow`]): the descendant it sees as current, the composite font `rootfont` +/// gives while that descendant is current, and the code it was given: over a CIDFont only named, +/// its type; over a composite font the file carries, the character it maps to. pub(super) struct CShowFont { selected: DictRef, root: DictRef, - code: NamedCid, + code: Option, + mapped: Option, } /// What drawing a glyph does with it. @@ -1069,6 +1088,22 @@ impl Interp<'_> { /// codes mapped to a descendant and a CID, ยง5.11.5); `None` for other fonts and mappings, /// which are set as type in the font of the composite font's name. fn composite_glyphs(&mut self, s: &[u8]) -> Res>> { + // Inside a `cshow` procedure over a composite font the file carries, with the descendant + // it selected current, the one byte the procedure was given stands for the character its + // code maps to: a CMap's CID, base-font code or glyph name. In a CIDFont any other string + // is a `rangecheck` error (PLRM 3rd ed., `cshow`: the glyph of the selected CID); in a + // base font it is shown as it is. + if let Some(c) = self.cshow.as_ref() + && let Some(mapped) = c.mapped.as_ref() + && self.g.font.as_ref().is_some_and(|f| f.same(&c.selected)) + { + if s == [mapped.last()] { + return Ok(Some(vec![mapped.clone()])); + } + if mapped.cid.is_some() { + return ps_err("rangecheck", "a string shown in cshow"); + } + } let Some(font) = self.g.font.clone() else { return Ok(None) }; let map = { let f = font.borrow(); @@ -1220,17 +1255,18 @@ impl Interp<'_> { /// that code when `code` and with none otherwise, while the CIDFont it selected is current. fn cshow_code(&self, code: bool) -> Option { let c = self.cshow.as_ref()?; + let given = c.code.as_ref()?; if !self.g.font.as_ref().is_some_and(|f| f.same(&c.selected)) { return None; } let mut codes = Decoded::default(); if code { - let mut chars = c.code.codes.text.chars(); - for &(value, n) in &c.code.codes.codes { + let mut chars = given.codes.text.chars(); + for &(value, n) in &given.codes.codes { codes.push(value, chars.by_ref().take(n)); } } - Some(NamedCid { codes, font: c.code.font.clone(), vertical: c.code.vertical, ems: c.code.ems }) + Some(NamedCid { codes, font: given.font.clone(), vertical: given.vertical, ems: given.ems }) } /// The CMap's name, the composite font, the CIDFont's name and the writing mode of @@ -1318,7 +1354,7 @@ impl Interp<'_> { self.push(Obj::Int(i64::from(code & 0xff)))?; self.push_num(v.x)?; self.push_num(v.y)?; - self.cshow = Some(CShowFont { selected: selected.clone(), root: composite.clone(), code: one }); + self.cshow = Some(CShowFont { selected: selected.clone(), root: composite.clone(), code: Some(one), mapped: None }); self.g.font = Some(selected.clone()); let go = self.body(proc); self.g.font = root.clone(); @@ -1329,6 +1365,41 @@ impl Interp<'_> { Ok(()) } + /// `cshow`'s procedure `proc` run for each character `glyphs` of composite font `root` maps, + /// with the last byte of its code and its width (PLRM 3rd ed., `cshow`). While it runs the + /// descendant that draws the character, at the size the composite font sets it, is the current + /// font and `rootfont` gives `root`. The current font is restored when the procedure completes. + fn composite_cshow(&mut self, glyphs: &[Mapped], proc: &Obj, root: Option) -> Res { + let Some(composite) = root.clone() else { return Ok(()) }; + for g in glyphs { + let v = self.composite_glyph(g, Point::ZERO, Draw::No)?; + self.push(Obj::Int(i64::from(g.last())))?; + self.push_num(v.x)?; + self.push_num(v.y)?; + // Scaling the descendant copies its dictionary for every character: charged by its + // size, so a huge font dictionary in a loop runs out of the budget. + let entries = g.font.borrow().len(); + self.spend(entries as u64)?; + // Without a matrix, a Type 42 font or a Type 2 CIDFont is in ems, as `composite_glyph` + // and `cid_glyph_at` draw it. + let selected = { + let f = g.font.borrow(); + let default = if units(&f) == 1.0 { 1.0 } else { 0.001 }; + let mut d = f.clone(); + d.insert(Key::name("FontMatrix"), matrix_obj(g.outer * font_matrix(&f, default))); + DictRef::new(d) + }; + self.cshow = Some(CShowFont { selected: selected.clone(), root: composite.clone(), code: None, mapped: Some(g.clone()) }); + self.g.font = Some(selected); + let go = self.body(proc); + self.g.font = root.clone(); + if !go? { + break; + } + } + Ok(()) + } + /// How far codes `codes` of type `named` move the current point (user space) when /// [`Self::show_named_cid`] shows them with no extra spacing. fn named_cid_width(&mut self, named: &NamedCid, codes: &Decoded) -> Res {