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 8350cd514b..876d3df412 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(())
}