Merge pull request #7458 from dotCooCoo/fix/eps-cshow-composite-codes

EPS import: cshow runs once for each character of a composite font
This commit is contained in:
BFlat
2026-11-15 04:22:01 +07:00
committed by GitHub
4 changed files with 184 additions and 9 deletions
+1 -1
View File
@@ -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<DictRef>,
/// 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<super::text::CShowFont>,
pub g: GState,
+65
View File
@@ -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
+39
View File
@@ -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`).
+79 -8
View File
@@ -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<NamedCid>,
mapped: Option<Mapped>,
}
/// 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<Option<Vec<Mapped>>> {
// 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<NamedCid> {
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<DictRef>) -> 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<Vec2> {