//! 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>, 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 { 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)> { let mut out = vec![]; let mut ops: Vec = 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)], 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, charstrings: Vec>, gsubrs: Vec>, /// Local subroutines of each Font DICT (one for name-keyed fonts). lsubrs: Vec>>, /// Nominal / default widths per Font DICT. widths: Vec<(f64, f64)>, /// Glyph → Font DICT (CID-keyed fonts). fdselect: Vec, /// Glyph → SID (name-keyed) or CID. charset: Vec, strings: Vec>, pub cid: bool, pub font_matrix: [f64; 6], /// The built-in encoding: code → glyph. pub encoding: [u16; 256], } /// The first font name in a CFF program's Name INDEX. pub fn font_name(data: &[u8]) -> Option { let hdr = *data.get(2)? as usize; let (names, _) = index(data, hdr)?; let r = names.first()?.clone(); Some(String::from_utf8_lossy(data.get(r)?).into_owned()) } impl Cff { pub fn parse(data: &[u8]) -> Option { 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)]| -> (Vec>, (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 { 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 { 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> { let fmt = *d.get(off)?; let mut out = vec![0u16]; let mut p = off + 1; let u16at = |p: usize| -> Option { 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> { 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, stems: usize, width: Option, 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; } /// A segment before the first moveto starts at the current point. fn ensure_open(&mut self) { if !self.open { self.path.move_to((self.x, self.y)); self.open = true; } } fn line_to(&mut self, dx: f64, dy: f64) { self.ensure_open(); self.x += dx; self.y += dy; self.path.line_to((self.x, self.y)); } fn curve(&mut self, d: [f64; 6]) { self.ensure_open(); 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 { std::mem::take(&mut self.stack) } fn escape(&mut self, b1: u8) { let pop = |s: &mut Vec| 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)]) -> Vec { fn idx(items: &[Vec]) -> Vec { 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> = 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::>()); // 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 { 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)); } }