diff --git a/crates/eps/src/import/cid.rs b/crates/eps/src/import/cid.rs
index 4c00486de9..8fb5137362 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 last byte of its code.
+ pub last: u8,
}
impl CMap {
@@ -136,6 +138,7 @@ impl CMap {
};
let n = n.clamp(1, rest.len());
let c = code(rest.get(..n).unwrap_or_default());
+ let last = rest.get(..n).and_then(<[u8]>::last).copied().unwrap_or(0);
let find = |maps: &[(usize, u32, u32, u32, usize)], work: &mut u64| {
let at = maps.iter().position(|m| m.0 == n && (m.1..=m.2).contains(&c));
*work += at.map_or(maps.len(), |k| k + 1) as u64;
@@ -146,12 +149,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, last },
+ (true, None, Some((font, base))) => CidChar { font, cid: 0, notdef: None, base: Some(base), last },
// 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, last },
+ _ => CidChar { font: 0, cid: 0, notdef: None, base: None, last },
},
});
i += n;
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 8350cd514b..3e5c846737 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:?}");
+ }
+}
+
/// `usecmap` brings in another CMap's ranges and mappings; the CMap's own come after them.
#[test]
fn usecmap_takes_the_parents_mappings() {
@@ -375,6 +400,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 e682ce10ec..d7eaa358cc 100644
--- a/crates/eps/src/import/tests_composite.rs
+++ b/crates/eps/src/import/tests_composite.rs
@@ -119,6 +119,25 @@ 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)
+ ));
+}
+
/// 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..83a27a8035 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)))?;
@@ -989,6 +998,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).
@@ -999,6 +1009,8 @@ struct Mapped {
cid: Option<(u32, Option)>,
outer: Affine,
vertical: bool,
+ /// The last byte of the character's code in the shown string (what `cshow` gives its procedure).
+ last: u8,
}
/// Type in a CIDFont only named, over a predefined CMap ([`Interp::named_cid_type`]): its codes
@@ -1011,13 +1023,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.
@@ -1064,6 +1078,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