mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 21:44:57 +00:00
EPS import: kshow runs its procedure between the characters of a composite font
In a composite font the file carries (FMapType 2, or a CMap), kshow ran its procedure zero times. It now runs between each two characters with the values of their codes (font number times 256 plus code, or the CMap code), and the next character follows where the procedure moves the current point (PLRM 3rd ed., kshow). What the procedure draws goes after the type, outside the glyphs' group, as for live type.
This commit is contained in:
@@ -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<Base>,
|
||||
/// 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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -999,6 +999,8 @@ struct Mapped {
|
||||
cid: Option<(u32, Option<u32>)>,
|
||||
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<u32> = 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(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user