diff --git a/crates/eps/src/import/cid.rs b/crates/eps/src/import/cid.rs index 4c00486de9..18498352f7 100644 --- a/crates/eps/src/import/cid.rs +++ b/crates/eps/src/import/cid.rs @@ -70,6 +70,8 @@ pub(super) struct CidChar { /// The base-font code or glyph name a `bfchar` or `bfrange` mapping gives it, instead of a /// CID. pub base: Option, + /// The value of its code (`<8001>` is 32769). + pub value: u32, } impl CMap { @@ -146,12 +148,12 @@ impl CMap { work += at.map_or(self.bases.len(), |k| k + 1) as u64; let base = at.and_then(|k| self.bases.get(k)).and_then(|m| Some((m.4, base_of(&m.3, c - m.1)?))); out.push(match (matched.is_some(), find(&self.cids, &mut work), base) { - (true, Some((font, cid, _)), _) => CidChar { font, cid, notdef, base: None }, - (true, None, Some((font, base))) => CidChar { font, cid: 0, notdef: None, base: Some(base) }, + (true, Some((font, cid, _)), _) => CidChar { font, cid, notdef, base: None, value: c }, + (true, None, Some((font, base))) => CidChar { font, cid: 0, notdef: None, base: Some(base), value: c }, // No character mapping: the notdef mapping's CID, else font 0's CID 0. _ => match find(&self.notdefs, &mut work) { - Some((font, _, base)) if matched.is_some() => CidChar { font, cid: base, notdef: None, base: None }, - _ => CidChar { font: 0, cid: 0, notdef: None, base: None }, + Some((font, _, base)) if matched.is_some() => CidChar { font, cid: base, notdef: None, base: None, value: c }, + _ => CidChar { font: 0, cid: 0, notdef: None, base: None, value: c }, }, }); i += n; diff --git a/crates/eps/src/import/tests_cid.rs b/crates/eps/src/import/tests_cid.rs index f63a77e09b..43b088c66f 100644 --- a/crates/eps/src/import/tests_cid.rs +++ b/crates/eps/src/import/tests_cid.rs @@ -136,6 +136,16 @@ fn codes_map_by_their_codespace_with_notdef_fallbacks() { width("<01>", 30.0); } +/// `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] +fn kshow_runs_between_each_two_cmap_codes() { + let fonts = String::from_utf8(cid_font(true)).unwrap() + CMAP; + let check = |cond: &str| check(&format!("{fonts} /TestFont 50 selectfont {cond}")); + check("gsave nulldevice 10 20 moveto [ {} <418001> kshow ] grestore aload length 2 eq exch 16#8001 eq and exch 65 eq and"); + check("gsave nulldevice 10 20 moveto {pop pop 100 0 rmoveto} <418001> kshow currentpoint grestore pop 165 sub abs 0.01 lt"); +} + /// `usecmap` brings in another CMap's ranges and mappings; the CMap's own come after them. #[test] fn usecmap_takes_the_parents_mappings() { diff --git a/crates/eps/src/import/tests_composite.rs b/crates/eps/src/import/tests_composite.rs index e682ce10ec..2f4074af97 100644 --- a/crates/eps/src/import/tests_composite.rs +++ b/crates/eps/src/import/tests_composite.rs @@ -119,6 +119,35 @@ 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)); } +/// `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`). +#[test] +fn kshow_runs_between_each_two_composite_characters() { + let (_, wh) = glyph('H'); + let (_, wi) = glyph('I'); + let fonts = fonts().replace("/Encoding [0] def", "/Encoding [0 0] def"); + let check = |cond: String| super::tests::check(&format!("{fonts} /Comp findfont 50 scalefont setfont {cond}")); + check("gsave nulldevice 10 20 moveto [ {} (\\000\\001\\001\\002\\000\\001) kshow ] grestore aload length 4 eq exch 1 eq and exch 258 eq and exch 258 eq and exch 1 eq and".into()); + check(format!( + "gsave nulldevice 10 20 moveto {{pop pop 100 0 rmoveto}} (\\000\\001\\000\\002) kshow currentpoint grestore pop {} sub abs 0.01 lt", + 10.0 + 50.0 * (wh + wi) + 100.0 + )); + // Drawn, the I starts where the procedure moved the point. + let r = read(&format!("{fonts}/Comp findfont 50 scalefont setfont 10 20 moveto {{pop pop 100 0 rmoveto}} (\\000\\001\\000\\002) kshow")); + let paths: Vec<_> = all(&r.document).iter().filter(|n| matches!(n.kind, NodeKind::Path { .. })).map(|n| bounds(n)).collect(); + assert_eq!(paths.len(), 2, "{paths:?}"); + assert!(paths[1].x0 > 10.0 + 50.0 * wh + 100.0 - 1.0, "{paths:?}"); + // A procedure that draws: its square goes after the glyphs' group, outside it. + let r = read(&format!( + "{fonts}/Comp findfont 50 scalefont setfont 10 20 moveto {{pop pop currentpoint 2 2 rectfill}} (\\000\\001\\000\\002\\000\\001) kshow" + )); + let top = super::tests::objects(&r.document); + assert_eq!(top.len(), 3, "{top:?}"); + assert_eq!(top[0].children().map(Vec::len), Some(3)); + assert!(top[1..].iter().all(|n| matches!(n.kind, NodeKind::Path { .. }) && (bounds(n).width() - 2.0).abs() < 0.01), "{top:?}"); +} + /// Measuring a composite font's Type 42 glyphs (`stringwidth` on a long string in a `loop`) /// counts against the operation budget, as drawing them does. #[test] diff --git a/crates/eps/src/import/text.rs b/crates/eps/src/import/text.rs index 515e0b12ec..15b9ca34c6 100644 --- a/crates/eps/src/import/text.rs +++ b/crates/eps/src/import/text.rs @@ -999,6 +999,8 @@ struct Mapped { cid: Option<(u32, Option)>, outer: Affine, vertical: bool, + /// The value of the character's code in the shown string (what `kshow` gives its procedure). + value: u32, } /// Type in a CIDFont only named, over a predefined CMap ([`Interp::named_cid_type`]): its codes @@ -1097,7 +1099,8 @@ impl Interp<'_> { if let Some(Obj::Dict(d)) = index.and_then(|i| fonts.get(i as usize)) && font_type(&d.borrow()) != Some(0.0) { - out.push(Mapped { font: d.clone(), pick, code, name: None, cid: None, outer, vertical: false }); + let value = u32::from(pick) * 256 + u32::from(code); + out.push(Mapped { font: d.clone(), pick, code, name: None, cid: None, outer, vertical: false, value }); } } Ok(Some(out)) @@ -1124,21 +1127,28 @@ impl Interp<'_> { .filter_map(|c| match (fonts.get(c.font), c.base) { // A base font's code, or its glyph name (at the code its encoding gives it, if any). (Some(Obj::Dict(d)), Some(Base::Code(code))) => { - Some(Mapped { font: d.clone(), pick: 0, code, name: None, cid: None, outer, vertical }) + Some(Mapped { font: d.clone(), pick: 0, code, name: None, cid: None, outer, vertical, value: c.value }) } (Some(Obj::Dict(d)), Some(Base::Name(name))) => { let code = encoded(&d.borrow(), &name); let name = code.is_none().then_some(name); - Some(Mapped { font: d.clone(), pick: 0, code: code.unwrap_or(0), name, cid: None, outer, vertical }) + Some(Mapped { font: d.clone(), pick: 0, code: code.unwrap_or(0), name, cid: None, outer, vertical, value: c.value }) } // A code without a mapping in a base font (not a CIDFont): its code 0, the // `.notdef` glyph CID 0 stands for in a CIDFont. (Some(Obj::Dict(d)), None) if !d.borrow().contains_key(&Key::name("CIDFontType")) => { - Some(Mapped { font: d.clone(), pick: 0, code: 0, name: None, cid: None, outer, vertical }) - } - (Some(Obj::Dict(d)), None) => { - Some(Mapped { font: d.clone(), pick: 0, code: (c.cid & 0xff) as u8, name: None, cid: Some((c.cid, c.notdef)), outer, vertical }) + Some(Mapped { font: d.clone(), pick: 0, code: 0, name: None, cid: None, outer, vertical, value: c.value }) } + (Some(Obj::Dict(d)), None) => Some(Mapped { + font: d.clone(), + pick: 0, + code: (c.cid & 0xff) as u8, + name: None, + cid: Some((c.cid, c.notdef)), + outer, + vertical, + value: c.value, + }), _ => None, }) .collect()) @@ -1346,6 +1356,12 @@ impl Interp<'_> { fn show_composite(&mut self, glyphs: &[Mapped], spacing: &Spacing) -> Res { let start = self.out.drawn.len(); let mut text = String::new(); + let kern: Vec = match spacing { + Spacing::Kern(_) => glyphs.iter().map(|g| g.value).collect(), + _ => Vec::new(), + }; + // Where in the objects drawn each run of `kshow`'s procedure put its art. + let mut kerned = Vec::new(); for (i, g) in glyphs.iter().enumerate() { let at = self.user_point()?; let w = self.composite_glyph(g, at, Draw::Fill)?; @@ -1353,6 +1369,12 @@ impl Interp<'_> { // times 256 plus its code (PLRM 3rd ed., `widthshow` in chapter 8). let d = spacing.advance(i, i64::from(g.pick) * 256 + i64::from(g.code), w); self.g.cur = Some(self.xf() * (at + d)); + // `kshow`'s procedure runs between each two characters with the values of their codes. + let before = self.out.drawn.len(); + self.kern(spacing, &kern, i, at + d)?; + if self.out.drawn.len() > before { + kerned.push(before..self.out.drawn.len()); + } // A CID stands for no character (its code is only the CID's low byte). let c = if g.cid.is_some() { '\u{2022}' } else { g.name.as_deref().map_or_else(|| code_char(&g.font.borrow(), g.code), glyph_char) }; if c != '\u{2022}' { @@ -1364,7 +1386,13 @@ impl Interp<'_> { { text = name.to_string(); } + // What `kshow`'s procedure draws goes after the type, outside its group. + let mut art = Vec::new(); + for run in kerned.into_iter().rev() { + art.splice(0..0, self.out.drawn.drain(run)); + } self.group_glyphs(start, &text); + self.out.drawn.extend(art); Ok(()) }