effectcraft/crates/pdf/src/cff.rs

1035 lines
31 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Compact Font Format programs (`FontFile3` `/Type1C` and `/CIDFontType0C`, and the `CFF `
//! table of OpenType fonts), from Adobe Technical Note #5176 (The Compact Font Format
//! Specification) and #5177 (The Type 2 Charstring Format): INDEX and DICT structures,
//! charsets, encodings, CID-keyed FDArray / FDSelect, and a Type 2 charstring interpreter that
//! produces glyph outlines in glyph space.
use kurbo::BezPath;
/// The standard strings (SIDs 0–228: the ISOAdobe character set); SIDs 229–390 (the expert
/// set) are not named here and match no glyph name.
const STD: &[&str] = &[
".notdef",
"space",
"exclam",
"quotedbl",
"numbersign",
"dollar",
"percent",
"ampersand",
"quoteright",
"parenleft",
"parenright",
"asterisk",
"plus",
"comma",
"hyphen",
"period",
"slash",
"zero",
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
"colon",
"semicolon",
"less",
"equal",
"greater",
"question",
"at",
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"bracketleft",
"backslash",
"bracketright",
"asciicircum",
"underscore",
"quoteleft",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
"braceleft",
"bar",
"braceright",
"asciitilde",
"exclamdown",
"cent",
"sterling",
"fraction",
"yen",
"florin",
"section",
"currency",
"quotesingle",
"quotedblleft",
"guillemotleft",
"guilsinglleft",
"guilsinglright",
"fi",
"fl",
"endash",
"dagger",
"daggerdbl",
"periodcentered",
"paragraph",
"bullet",
"quotesinglbase",
"quotedblbase",
"quotedblright",
"guillemotright",
"ellipsis",
"perthousand",
"questiondown",
"grave",
"acute",
"circumflex",
"tilde",
"macron",
"breve",
"dotaccent",
"dieresis",
"ring",
"cedilla",
"hungarumlaut",
"ogonek",
"caron",
"emdash",
"AE",
"ordfeminine",
"Lslash",
"Oslash",
"OE",
"ordmasculine",
"ae",
"dotlessi",
"lslash",
"oslash",
"oe",
"germandbls",
"onesuperior",
"logicalnot",
"mu",
"trademark",
"Eth",
"onehalf",
"plusminus",
"Thorn",
"onequarter",
"divide",
"brokenbar",
"degree",
"thorn",
"threequarters",
"twosuperior",
"registered",
"minus",
"eth",
"multiply",
"threesuperior",
"copyright",
"Aacute",
"Acircumflex",
"Adieresis",
"Agrave",
"Aring",
"Atilde",
"Ccedilla",
"Eacute",
"Ecircumflex",
"Edieresis",
"Egrave",
"Iacute",
"Icircumflex",
"Idieresis",
"Igrave",
"Ntilde",
"Oacute",
"Ocircumflex",
"Odieresis",
"Ograve",
"Otilde",
"Scaron",
"Uacute",
"Ucircumflex",
"Udieresis",
"Ugrave",
"Yacute",
"Ydieresis",
"Zcaron",
"aacute",
"acircumflex",
"adieresis",
"agrave",
"aring",
"atilde",
"ccedilla",
"eacute",
"ecircumflex",
"edieresis",
"egrave",
"iacute",
"icircumflex",
"idieresis",
"igrave",
"ntilde",
"oacute",
"ocircumflex",
"odieresis",
"ograve",
"otilde",
"scaron",
"uacute",
"ucircumflex",
"udieresis",
"ugrave",
"yacute",
"ydieresis",
"zcaron",
];
const N_STD: usize = 391;
/// An INDEX: the byte ranges of its objects.
fn index(d: &[u8], pos: usize) -> Option<(Vec<std::ops::Range<usize>>, usize)> {
let count = u16::from_be_bytes([*d.get(pos)?, *d.get(pos + 1)?]) as usize;
if count == 0 {
return Some((vec![], pos + 2));
}
let os = *d.get(pos + 2)? as usize;
if !(1..=4).contains(&os) {
return None;
}
let off = |i: usize| -> Option<usize> {
let p = pos + 3 + i * os;
let mut v = 0usize;
for k in 0..os {
v = (v << 8) | *d.get(p + k)? as usize;
}
Some(v)
};
let base = pos + 3 + (count + 1) * os - 1;
let mut out = Vec::with_capacity(count);
for i in 0..count {
let (a, b) = (off(i)?, off(i + 1)?);
if b < a || base + b > d.len() {
return None;
}
out.push(base + a..base + b);
}
let end = base + off(count)?;
Some((out, end))
}
/// A DICT: (operator, operands) pairs; two-byte operators are `1200 + b1`.
fn dict(d: &[u8]) -> Vec<(u16, Vec<f64>)> {
let mut out = vec![];
let mut ops: Vec<f64> = vec![];
let mut i = 0;
while i < d.len() {
let b0 = d[i];
match b0 {
0..=21 => {
let op = if b0 == 12 {
i += 1;
1200 + *d.get(i).unwrap_or(&0) as u16
} else {
b0 as u16
};
out.push((op, std::mem::take(&mut ops)));
i += 1;
}
28 => {
ops.push(i16::from_be_bytes([*d.get(i + 1).unwrap_or(&0), *d.get(i + 2).unwrap_or(&0)]) as f64);
i += 3;
}
29 => {
let b = |k: usize| *d.get(i + k).unwrap_or(&0);
ops.push(i32::from_be_bytes([b(1), b(2), b(3), b(4)]) as f64);
i += 5;
}
30 => {
// Real number: BCD nibbles.
let mut s = String::new();
i += 1;
'n: while i < d.len() {
for nib in [d[i] >> 4, d[i] & 15] {
match nib {
0..=9 => s.push((b'0' + nib) as char),
0xA => s.push('.'),
0xB => s.push('E'),
0xC => s.push_str("E-"),
0xE => s.push('-'),
0xF => {
i += 1;
break 'n;
}
_ => {}
}
}
i += 1;
}
ops.push(s.parse().unwrap_or(0.0));
}
32..=246 => {
ops.push(b0 as f64 - 139.0);
i += 1;
}
247..=250 => {
ops.push((b0 as f64 - 247.0) * 256.0 + *d.get(i + 1).unwrap_or(&0) as f64 + 108.0);
i += 2;
}
251..=254 => {
ops.push(-(b0 as f64 - 251.0) * 256.0 - *d.get(i + 1).unwrap_or(&0) as f64 - 108.0);
i += 2;
}
_ => i += 1,
}
}
out
}
fn get(d: &[(u16, Vec<f64>)], op: u16) -> Option<&[f64]> {
d.iter().find(|(o, _)| *o == op).map(|(_, v)| v.as_slice())
}
/// One font of a CFF program.
#[derive(Clone, Debug)]
pub struct Cff {
data: Vec<u8>,
charstrings: Vec<std::ops::Range<usize>>,
gsubrs: Vec<std::ops::Range<usize>>,
/// Local subroutines of each Font DICT (one for name-keyed fonts).
lsubrs: Vec<Vec<std::ops::Range<usize>>>,
/// Nominal / default widths per Font DICT.
widths: Vec<(f64, f64)>,
/// Glyph → Font DICT (CID-keyed fonts).
fdselect: Vec<u8>,
/// Glyph → SID (name-keyed) or CID.
charset: Vec<u16>,
strings: Vec<std::ops::Range<usize>>,
pub cid: bool,
pub font_matrix: [f64; 6],
/// The built-in encoding: code → glyph.
pub encoding: [u16; 256],
}
impl Cff {
pub fn parse(data: &[u8]) -> Option<Cff> {
let d = data;
let hdr = *d.get(2)? as usize;
let (_names, p) = index(d, hdr)?;
let (tops, p) = index(d, p)?;
let (strings, p) = index(d, p)?;
let (gsubrs, _) = index(d, p)?;
let top = dict(&d[tops.first()?.clone()]);
let cs_off = get(&top, 17)?.first().copied()? as usize;
let (charstrings, _) = index(d, cs_off)?;
let n = charstrings.len();
let cid = get(&top, 1230).is_some();
let font_matrix = match get(&top, 1207) {
Some(m) if m.len() == 6 => [m[0], m[1], m[2], m[3], m[4], m[5]],
_ => [0.001, 0.0, 0.0, 0.001, 0.0, 0.0],
};
let private = |pd: &[(u16, Vec<f64>)]| -> (Vec<std::ops::Range<usize>>, (f64, f64)) {
let Some(pv) = get(pd, 18).filter(|v| v.len() == 2) else { return (vec![], (0.0, 0.0)) };
let (size, off) = (pv[0] as usize, pv[1] as usize);
let Some(bytes) = d.get(off..off + size) else { return (vec![], (0.0, 0.0)) };
let pr = dict(bytes);
let nominal = get(&pr, 21).and_then(|v| v.first().copied()).unwrap_or(0.0);
let default = get(&pr, 20).and_then(|v| v.first().copied()).unwrap_or(0.0);
let subrs = get(&pr, 19).and_then(|v| v.first().copied()).and_then(|s| index(d, off + s as usize)).map(|x| x.0).unwrap_or_default();
(subrs, (default, nominal))
};
let mut lsubrs = vec![];
let mut widths = vec![];
let mut fdselect = vec![];
if cid {
if let Some(fa) = get(&top, 1236).and_then(|v| v.first().copied())
&& let Some((fds, _)) = index(d, fa as usize)
{
for r in fds {
let (s, w) = private(&dict(&d[r]));
lsubrs.push(s);
widths.push(w);
}
}
if let Some(fs) = get(&top, 1237).and_then(|v| v.first().copied()) {
fdselect = read_fdselect(d, fs as usize, n).unwrap_or_default();
}
} else {
let (s, w) = private(&top);
lsubrs.push(s);
widths.push(w);
}
if lsubrs.is_empty() {
lsubrs.push(vec![]);
widths.push((0.0, 0.0));
}
let charset = match get(&top, 15).and_then(|v| v.first().copied()).unwrap_or(0.0) as usize {
0..=2 => (0..n as u16).collect(),
off => read_charset(d, off, n).unwrap_or_else(|| (0..n as u16).collect()),
};
let mut cff = Cff { data: data.to_vec(), charstrings, gsubrs, lsubrs, widths, fdselect, charset, strings, cid, font_matrix, encoding: [0; 256] };
let enc_off = get(&top, 16).and_then(|v| v.first().copied()).unwrap_or(0.0) as usize;
cff.encoding = cff.read_encoding(enc_off);
Some(cff)
}
fn read_encoding(&self, off: usize) -> [u16; 256] {
let mut enc = [0u16; 256];
if self.cid {
return enc;
}
if off <= 1 {
// Standard encoding by glyph name.
for code in 0..=255u8 {
if let Some(name) = crate::encoding::Base::Standard.name(code)
&& let Some(g) = self.gid_by_name(&name)
{
enc[code as usize] = g;
}
}
return enc;
}
let d = &self.data;
let Some(&fmt) = d.get(off) else { return enc };
let mut p = off + 1;
match fmt & 0x7F {
0 => {
let n = *d.get(p).unwrap_or(&0) as usize;
p += 1;
for i in 0..n {
if let Some(&c) = d.get(p + i) {
enc[c as usize] = (i + 1) as u16;
}
}
p += n;
}
1 => {
let n = *d.get(p).unwrap_or(&0) as usize;
p += 1;
let mut g = 1u16;
for _ in 0..n {
let (Some(&first), Some(&left)) = (d.get(p), d.get(p + 1)) else { break };
for k in 0..=left as usize {
if let Some(e) = enc.get_mut(first as usize + k) {
*e = g;
}
g += 1;
}
p += 2;
}
}
_ => {}
}
if fmt & 0x80 != 0 {
let n = *d.get(p).unwrap_or(&0) as usize;
p += 1;
for _ in 0..n {
let (Some(&c), Some(&a), Some(&b)) = (d.get(p), d.get(p + 1), d.get(p + 2)) else { break };
let sid = u16::from_be_bytes([a, b]);
if let Some(g) = self.charset.iter().position(|s| *s == sid) {
enc[c as usize] = g as u16;
}
p += 3;
}
}
enc
}
#[cfg(test)]
pub fn glyph_count(&self) -> usize {
self.charstrings.len()
}
fn sid_name(&self, sid: u16) -> Option<&str> {
let sid = sid as usize;
if sid < STD.len() {
return Some(STD[sid]);
}
if sid < N_STD {
return None;
}
let r = self.strings.get(sid - N_STD)?.clone();
std::str::from_utf8(&self.data[r]).ok()
}
/// A glyph by name (name-keyed fonts).
pub fn gid_by_name(&self, name: &str) -> Option<u16> {
if self.cid {
return None;
}
self.charset.iter().position(|&sid| self.sid_name(sid) == Some(name)).map(|g| g as u16)
}
/// A glyph by CID (CID-keyed fonts; name-keyed fonts take the CID as the glyph index).
pub fn gid_by_cid(&self, cid: u16) -> Option<u16> {
if !self.cid {
return ((cid as usize) < self.charstrings.len()).then_some(cid);
}
self.charset.iter().position(|&c| c == cid).map(|g| g as u16)
}
/// A glyph's outline in glyph space (`font_matrix` maps it to text space) and its advance.
pub fn outline(&self, gid: u16) -> Option<(BezPath, f64)> {
let r = self.charstrings.get(gid as usize)?.clone();
let fd = self.fdselect.get(gid as usize).copied().unwrap_or(0) as usize;
let fd = fd.min(self.lsubrs.len() - 1);
let mut st =
T2 { cff: self, fd, path: BezPath::new(), x: 0.0, y: 0.0, stack: vec![], stems: 0, width: None, open: false, done: false, depth: 0, seac: None };
st.run(&self.data[r]);
if st.open {
st.path.close_path();
}
let width = match st.width {
Some(w) if !w.is_nan() => w + self.widths[fd].1,
_ => self.widths[fd].0,
};
let mut path = st.path;
// endchar seac: base and accent glyphs by standard code.
if let Some((adx, ady, b, a)) = st.seac {
let find = |code: u8| crate::encoding::Base::Standard.name(code).and_then(|n| self.gid_by_name(&n));
if let Some((bp, _)) = find(b).and_then(|g| self.outline(g)) {
path.extend(bp);
}
if let Some((ap, _)) = find(a).and_then(|g| self.outline(g)) {
path.extend(kurbo::Affine::translate((adx, ady)) * ap);
}
}
Some((path, width))
}
}
fn read_charset(d: &[u8], off: usize, n: usize) -> Option<Vec<u16>> {
let fmt = *d.get(off)?;
let mut out = vec![0u16];
let mut p = off + 1;
let u16at = |p: usize| -> Option<u16> { Some(u16::from_be_bytes([*d.get(p)?, *d.get(p + 1)?])) };
while out.len() < n {
match fmt {
0 => {
out.push(u16at(p)?);
p += 2;
}
1 | 2 => {
let first = u16at(p)?;
let left = if fmt == 1 { *d.get(p + 2)? as u16 } else { u16at(p + 2)? };
p += if fmt == 1 { 3 } else { 4 };
for k in 0..=left {
if out.len() >= n {
break;
}
out.push(first.wrapping_add(k));
}
}
_ => return None,
}
}
Some(out)
}
fn read_fdselect(d: &[u8], off: usize, n: usize) -> Option<Vec<u8>> {
match *d.get(off)? {
0 => d.get(off + 1..off + 1 + n).map(<[u8]>::to_vec),
3 => {
let nr = u16::from_be_bytes([*d.get(off + 1)?, *d.get(off + 2)?]) as usize;
let mut out = vec![0u8; n];
for i in 0..nr {
let p = off + 3 + i * 3;
let first = u16::from_be_bytes([*d.get(p)?, *d.get(p + 1)?]) as usize;
let fd = *d.get(p + 2)?;
let next = u16::from_be_bytes([*d.get(p + 3)?, *d.get(p + 4)?]) as usize;
for g in first..next.min(n) {
out[g] = fd;
}
}
Some(out)
}
_ => None,
}
}
fn bias(n: usize) -> i64 {
if n < 1240 {
107
} else if n < 33900 {
1131
} else {
32768
}
}
/// Type 2 charstring interpreter state.
struct T2<'a> {
cff: &'a Cff,
fd: usize,
path: BezPath,
x: f64,
y: f64,
stack: Vec<f64>,
stems: usize,
width: Option<f64>,
open: bool,
done: bool,
depth: usize,
seac: Option<(f64, f64, u8, u8)>,
}
impl T2<'_> {
fn move_to(&mut self, dx: f64, dy: f64) {
if self.open {
self.path.close_path();
}
self.x += dx;
self.y += dy;
self.path.move_to((self.x, self.y));
self.open = true;
}
fn line_to(&mut self, dx: f64, dy: f64) {
self.x += dx;
self.y += dy;
self.path.line_to((self.x, self.y));
}
fn curve(&mut self, d: [f64; 6]) {
let (x1, y1) = (self.x + d[0], self.y + d[1]);
let (x2, y2) = (x1 + d[2], y1 + d[3]);
self.x = x2 + d[4];
self.y = y2 + d[5];
self.path.curve_to((x1, y1), (x2, y2), (self.x, self.y));
}
/// The width argument before the first stack-clearing operator.
fn take_width(&mut self, expected_even: bool) {
if self.width.is_none() {
let odd = self.stack.len() % 2 == 1;
if expected_even == odd && !self.stack.is_empty() {
self.width = Some(self.stack.remove(0));
} else {
self.width = Some(f64::NAN);
}
}
}
fn run(&mut self, cs: &[u8]) {
if self.depth > 10 {
return;
}
let mut i = 0;
while i < cs.len() && !self.done {
let b0 = cs[i];
i += 1;
match b0 {
32..=246 => self.stack.push(b0 as f64 - 139.0),
247..=250 => {
self.stack.push((b0 as f64 - 247.0) * 256.0 + *cs.get(i).unwrap_or(&0) as f64 + 108.0);
i += 1;
}
251..=254 => {
self.stack.push(-(b0 as f64 - 251.0) * 256.0 - *cs.get(i).unwrap_or(&0) as f64 - 108.0);
i += 1;
}
28 => {
self.stack.push(i16::from_be_bytes([*cs.get(i).unwrap_or(&0), *cs.get(i + 1).unwrap_or(&0)]) as f64);
i += 2;
}
255 => {
let b = |k: usize| *cs.get(i + k).unwrap_or(&0);
self.stack.push(i32::from_be_bytes([b(0), b(1), b(2), b(3)]) as f64 / 65536.0);
i += 4;
}
1 | 3 | 18 | 23 => {
self.take_width(true);
self.stems += self.stack.len() / 2;
self.stack.clear();
}
19 | 20 => {
self.take_width(true);
self.stems += self.stack.len() / 2;
self.stack.clear();
i += self.stems.div_ceil(8);
}
21 => {
self.take_width(true);
let s = self.args();
let n = s.len();
if n >= 2 {
self.move_to(s[n - 2], s[n - 1]);
}
}
22 | 4 => {
self.take_width(false);
let s = self.args();
let v = s.last().copied().unwrap_or(0.0);
if b0 == 22 { self.move_to(v, 0.0) } else { self.move_to(0.0, v) }
}
5 => {
let s = self.args();
for c in s.chunks_exact(2) {
self.line_to(c[0], c[1]);
}
}
6 | 7 => {
let s = self.args();
let mut horiz = b0 == 6;
for v in s {
if horiz {
self.line_to(v, 0.0)
} else {
self.line_to(0.0, v)
}
horiz = !horiz;
}
}
8 => {
let s = self.args();
for c in s.chunks_exact(6) {
self.curve([c[0], c[1], c[2], c[3], c[4], c[5]]);
}
}
24 => {
// rcurveline
let s = self.args();
let nc = s.len().saturating_sub(2) / 6;
for c in s[..nc * 6].chunks_exact(6) {
self.curve([c[0], c[1], c[2], c[3], c[4], c[5]]);
}
if s.len() >= nc * 6 + 2 {
self.line_to(s[nc * 6], s[nc * 6 + 1]);
}
}
25 => {
// rlinecurve
let s = self.args();
if s.len() >= 6 {
let nl = (s.len() - 6) / 2;
for c in s[..nl * 2].chunks_exact(2) {
self.line_to(c[0], c[1]);
}
let c = &s[nl * 2..];
self.curve([c[0], c[1], c[2], c[3], c[4], c[5]]);
}
}
26 => {
// vvcurveto
let mut s = self.args();
let mut dx1 = 0.0;
if s.len() % 4 == 1 {
dx1 = s.remove(0);
}
for c in s.chunks_exact(4) {
self.curve([dx1, c[0], c[1], c[2], 0.0, c[3]]);
dx1 = 0.0;
}
}
27 => {
// hhcurveto
let mut s = self.args();
let mut dy1 = 0.0;
if s.len() % 4 == 1 {
dy1 = s.remove(0);
}
for c in s.chunks_exact(4) {
self.curve([c[0], dy1, c[1], c[2], c[3], 0.0]);
dy1 = 0.0;
}
}
30 | 31 => {
// vhcurveto / hvcurveto: alternating tangents, optional final delta.
let s = self.args();
let mut horiz = b0 == 31;
let mut k = 0;
while k + 4 <= s.len() {
let last = k + 4 == s.len() - 1;
let extra = if last { s[k + 4] } else { 0.0 };
if horiz {
self.curve([s[k], 0.0, s[k + 1], s[k + 2], extra, s[k + 3]]);
} else {
self.curve([0.0, s[k], s[k + 1], s[k + 2], s[k + 3], extra]);
}
horiz = !horiz;
k += 4;
if last {
break;
}
}
}
10 | 29 => {
let Some(idx) = self.stack.pop() else { continue };
let subrs = if b0 == 10 { &self.cff.lsubrs[self.fd] } else { &self.cff.gsubrs };
let k = idx as i64 + bias(subrs.len());
if let Some(r) = usize::try_from(k).ok().and_then(|k| subrs.get(k)).cloned() {
self.depth += 1;
let data = &self.cff.data[r];
self.run(data);
self.depth -= 1;
}
}
11 => return,
14 => {
self.take_width(false);
if self.stack.len() >= 4 {
let n = self.stack.len();
let s = &self.stack[n - 4..];
self.seac = Some((s[0], s[1], s[2] as u8, s[3] as u8));
}
self.stack.clear();
self.done = true;
}
12 => {
let b1 = *cs.get(i).unwrap_or(&0);
i += 1;
self.escape(b1);
}
_ => self.stack.clear(),
}
}
}
fn args(&mut self) -> Vec<f64> {
std::mem::take(&mut self.stack)
}
fn escape(&mut self, b1: u8) {
let pop = |s: &mut Vec<f64>| s.pop().unwrap_or(0.0);
match b1 {
35 => {
// flex: two curves.
let s = self.args();
if s.len() >= 12 {
self.curve([s[0], s[1], s[2], s[3], s[4], s[5]]);
self.curve([s[6], s[7], s[8], s[9], s[10], s[11]]);
}
}
34 => {
// hflex: dx1 dx2 dy2 dx3 dx4 dx5 dx6.
let s = self.args();
if s.len() >= 7 {
self.curve([s[0], 0.0, s[1], s[2], s[3], 0.0]);
self.curve([s[4], 0.0, s[5], -s[2], s[6], 0.0]);
}
}
36 => {
// hflex1: dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6.
let s = self.args();
if s.len() >= 9 {
self.curve([s[0], s[1], s[2], s[3], s[4], 0.0]);
self.curve([s[5], 0.0, s[6], s[7], s[8], -(s[1] + s[3] + s[7])]);
}
}
37 => {
// flex1
let s = self.args();
if s.len() >= 11 {
let (x0, y0) = (self.x, self.y);
let dx: f64 = s[0] + s[2] + s[4] + s[6] + s[8];
let dy: f64 = s[1] + s[3] + s[5] + s[7] + s[9];
self.curve([s[0], s[1], s[2], s[3], s[4], s[5]]);
let (lx, ly) = if dx.abs() > dy.abs() { (s[10], y0 - (self.y + s[7] + s[9])) } else { (x0 - (self.x + s[6] + s[8]), s[10]) };
self.curve([s[6], s[7], s[8], s[9], lx, ly]);
}
}
9 => {
let a = pop(&mut self.stack);
self.stack.push(a.abs());
}
10 => {
let (b, a) = (pop(&mut self.stack), pop(&mut self.stack));
self.stack.push(a + b);
}
11 => {
let (b, a) = (pop(&mut self.stack), pop(&mut self.stack));
self.stack.push(a - b);
}
12 => {
let (b, a) = (pop(&mut self.stack), pop(&mut self.stack));
self.stack.push(if b != 0.0 { a / b } else { 0.0 });
}
14 => {
let a = pop(&mut self.stack);
self.stack.push(-a);
}
18 => {
self.stack.pop();
}
24 => {
let (b, a) = (pop(&mut self.stack), pop(&mut self.stack));
self.stack.push(a * b);
}
27 => {
let a = pop(&mut self.stack);
self.stack.push(a);
self.stack.push(a);
}
28 => {
let (b, a) = (pop(&mut self.stack), pop(&mut self.stack));
self.stack.push(b);
self.stack.push(a);
}
_ => self.stack.clear(),
}
}
}
/// A minimal CFF writer for tests: one name-keyed font whose glyphs are Type 2 charstrings,
/// named by custom strings.
#[cfg(test)]
pub(crate) fn write_test_cff(glyphs: &[(&str, Vec<u8>)]) -> Vec<u8> {
fn idx(items: &[Vec<u8>]) -> Vec<u8> {
let mut out = (items.len() as u16).to_be_bytes().to_vec();
if items.is_empty() {
return out;
}
out.push(4);
let mut off = 1u32;
out.extend_from_slice(&off.to_be_bytes());
for it in items {
off += it.len() as u32;
out.extend_from_slice(&off.to_be_bytes());
}
for it in items {
out.extend_from_slice(it);
}
out
}
let int = |v: i32| {
let mut o = vec![29];
o.extend_from_slice(&v.to_be_bytes());
o
};
let names = idx(&[b"Test".to_vec()]);
let strings: Vec<Vec<u8>> = glyphs.iter().skip(1).map(|(n, _)| n.as_bytes().to_vec()).collect();
let strings_idx = idx(&strings);
let gsubrs = idx(&[]);
let cs = idx(&glyphs.iter().map(|(_, c)| c.clone()).collect::<Vec<_>>());
// charset format 0: SIDs of glyphs 1.. (custom strings from 391).
let mut charset = vec![0u8];
for i in 0..glyphs.len() - 1 {
charset.extend_from_slice(&((N_STD + i) as u16).to_be_bytes());
}
// Top DICT with fixed-size operands so offsets can be computed first.
let top_len = 4 * 5 + 3;
let header = [1u8, 0, 4, 4];
let top_index_len = 2 + 1 + 8 + top_len;
let base = header.len() + names.len() + top_index_len + strings_idx.len() + gsubrs.len();
let charset_off = base;
let cs_off = charset_off + charset.len();
let priv_off = cs_off + cs.len();
let mut top = vec![];
top.extend(int(charset_off as i32));
top.push(15);
top.extend(int(cs_off as i32));
top.push(17);
top.extend(int(0));
top.extend(int(priv_off as i32));
top.push(18);
assert_eq!(top.len(), top_len);
let mut out = header.to_vec();
out.extend(names);
out.extend(idx(&[top]));
out.extend(strings_idx);
out.extend(gsubrs);
out.extend(charset);
out.extend(cs);
out
}
#[cfg(test)]
mod tests {
use super::*;
use kurbo::Shape;
#[test]
fn standard_strings_cover_isoadobe() {
assert_eq!(STD.len(), 229);
assert_eq!(STD[34], "A");
assert_eq!(STD[66], "a");
assert_eq!(STD[149], "germandbls");
assert_eq!(STD[228], "zcaron");
}
#[test]
fn type2_charstrings_draw_outlines() {
// A 500-unit square with a width: `w dx dy rmoveto dx hlineto dy vlineto dx hlineto endchar`.
let enc = |v: i32| -> Vec<u8> {
if (-107..=107).contains(&v) {
vec![(v + 139) as u8]
} else {
let mut o = vec![28];
o.extend_from_slice(&(v as i16).to_be_bytes());
o
}
};
let mut square = vec![];
for v in [600, 50, 0] {
square.extend(enc(v));
}
square.push(21);
for v in [500, 500, -500] {
square.extend(enc(v));
}
square.push(6);
square.push(14);
let font = write_test_cff(&[(".notdef", vec![14]), ("square", square)]);
let cff = Cff::parse(&font).unwrap();
assert_eq!(cff.glyph_count(), 2);
let g = cff.gid_by_name("square").unwrap();
assert_eq!(g, 1);
let (p, w) = cff.outline(g).unwrap();
assert_eq!(w, 600.0);
assert!((p.area().abs() - 250_000.0).abs() < 1e-6, "{}", p.area());
assert_eq!(p.bounding_box(), kurbo::Rect::new(50.0, 0.0, 550.0, 500.0));
}
}