//! PDF fonts (ISO 32000-1 §9.5–9.10): simple fonts (Type 1, TrueType, Type 3) and composite //! (Type 0) fonts with their encodings, widths and embedded programs, turned into glyph //! outlines in text space. //! //! Embedded programs: TrueType (`FontFile2`, through skrifa), CFF (`FontFile3` `/Type1C`, //! `/CIDFontType0C` and OpenType, [`crate::cff`]), Type 1 (`FontFile`, [`crate::type1`]) and //! Type 3 glyph procedures (drawn by the content interpreter). Fonts that are not embedded //! (the standard 14 and others) are drawn with the bundled OFL fonts: Inter for sans serif, //! Noto Serif for serif and JetBrains Mono for monospaced families. use std::cell::RefCell; use std::collections::HashMap; use std::sync::Arc; use kurbo::{Affine, BezPath}; use skrifa::instance::{LocationRef, Size}; use skrifa::outline::{DrawSettings, OutlinePen}; use skrifa::raw::TableProvider; use skrifa::raw::tables::cmap::PlatformId; use skrifa::{FontRef, GlyphId, MetadataProvider}; use crate::cff::Cff; use crate::encoding::{Base, name_to_unicode}; use crate::object::{Dict, File, Lexer, Obj, decode_stream}; use crate::type1::Type1; pub(crate) enum Program { TrueType(Arc>), Cff(Box), Type1(Box), /// A bundled stand-in (not embedded). Fallback(&'static [u8]), /// Glyph procedures: (name → content stream, resources). Type3 { procs: HashMap>, resources: Option, }, None, } pub(crate) struct Font { /// Two-byte (or CMap-coded) codes and CIDs. pub composite: bool, pub program: Program, /// Glyph space → text space. pub matrix: Affine, first_char: u32, widths: Vec, cid_widths: HashMap, default_width: f64, /// Simple fonts: code → glyph name. names: Vec>, symbolic: bool, to_unicode: HashMap, cid_to_gid: Option>, /// Composite fonts: code space ranges (bytes, lo, hi) and code → CID ranges. codespace: Vec<(usize, u32, u32)>, cid_ranges: Vec<(u32, u32, u32)>, pub vertical: bool, /// Vertical metrics (`/W2`, in text space): CID → (w1y, vx, vy). cid_vmetrics: HashMap, /// `/DW2`: default (vy, w1y). dw2: (f64, f64), cache: RefCell>>>, /// Program glyph advances (font units → text space) for fonts without `/Widths`. has_widths: bool, pub base_name: String, } struct Pen(BezPath); impl OutlinePen for Pen { fn move_to(&mut self, x: f32, y: f32) { self.0.move_to((x as f64, y as f64)); } fn line_to(&mut self, x: f32, y: f32) { self.0.line_to((x as f64, y as f64)); } fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) { self.0.quad_to((cx0 as f64, cy0 as f64), (x as f64, y as f64)); } fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) { self.0.curve_to((cx0 as f64, cy0 as f64), (cx1 as f64, cy1 as f64), (x as f64, y as f64)); } fn close(&mut self) { self.0.close_path(); } } /// A bundled stand-in for a font that is not embedded, by its PostScript name. fn fallback(base: &str) -> &'static [u8] { use effectcraft_text::fonts::*; let b = base.to_ascii_lowercase(); if b.contains("courier") || b.contains("mono") || b.contains("consol") { return JETBRAINS_MONO_REGULAR; } if b.contains("times") || b.contains("serif") && !b.contains("sans") || b.contains("georgia") || b.contains("garamond") || b.contains("minion") || b.contains("roman") { return NOTO_SERIF_REGULAR; } if b.contains("bold") || b.contains("black") || b.contains("heavy") { return INTER_BOLD; } if b.contains("semibold") || b.contains("demi") { return INTER_SEMIBOLD; } if b.contains("italic") || b.contains("oblique") { return INTER_ITALIC; } INTER_REGULAR } fn sfnt_upem(data: &[u8]) -> f64 { FontRef::new(data).ok().and_then(|f| f.head().ok().map(|h| h.units_per_em() as f64)).filter(|u| *u > 0.0).unwrap_or(1000.0) } /// A TrueType / OpenType glyph's outline in font units. fn sfnt_outline(data: &[u8], gid: u16) -> Option { let font = FontRef::new(data).ok()?; let g = font.outline_glyphs().get(GlyphId::new(gid as u32))?; let mut pen = Pen(BezPath::new()); g.draw(DrawSettings::unhinted(Size::unscaled(), LocationRef::default()), &mut pen).ok()?; Some(pen.0) } fn sfnt_advance(data: &[u8], gid: u16) -> Option { let font = FontRef::new(data).ok()?; font.glyph_metrics(Size::unscaled(), LocationRef::default()).advance_width(GlyphId::new(gid as u32)).map(|a| a as f64) } /// A glyph of a TrueType font for a simple-font code (§9.6.6.4): the (3,1) subtable through /// the glyph's Unicode value, the (3,0) symbol subtable at `0xF000 + code`, the (1,0) Macintosh /// subtable by code, then `post` glyph names. fn sfnt_glyph(data: &[u8], code: u8, name: Option<&str>, symbolic: bool) -> Option { let font = FontRef::new(data).ok()?; let cmap = font.cmap().ok(); let sub = |pid: PlatformId, eid: u16| { let cmap = cmap.as_ref()?; cmap.encoding_records().iter().find(|r| r.platform_id() == pid && r.encoding_id() == eid).and_then(|r| r.subtable(cmap.offset_data()).ok()) }; let uni = name.and_then(name_to_unicode); let ok = |g: Option| g.map(|g| g.to_u32() as u16).filter(|g| *g != 0); if !symbolic && let Some(u) = uni && let Some(g) = ok(sub(PlatformId::Windows, 1).and_then(|s| s.map_codepoint(u as u32))).or_else(|| ok(font.charmap().map(u))) { return Some(g); } if let Some(s) = sub(PlatformId::Windows, 0) { for c in [0xF000 + code as u32, 0xF100 + code as u32, 0xF200 + code as u32, code as u32] { if let Some(g) = ok(s.map_codepoint(c)) { return Some(g); } } } if let Some(g) = ok(sub(PlatformId::Macintosh, 0).and_then(|s| s.map_codepoint(code as u32))) { return Some(g); } if let Some(n) = name && let Some((g, _)) = font.glyph_names().iter().find(|(_, gn)| gn.as_str() == n) { return Some(g.to_u32() as u16); } if let Some(u) = uni { return ok(font.charmap().map(u)); } // Symbolic fonts without a usable cmap: the code as the glyph index. (symbolic && code != 0).then_some(code as u16) } /// `/Differences [code /name /name … code /name …]`. fn differences(file: &File, enc: &Dict, names: &mut [Option]) { let Some(diffs) = file.get(enc, "Differences").and_then(Obj::array) else { return }; let mut code = 0usize; for d in diffs { match file.resolve(d) { Obj::Num(n) => code = *n as usize, Obj::Name(n) => { if let Some(slot) = names.get_mut(code) { *slot = Some(n.clone()); } code += 1; } _ => {} } } } /// A ToUnicode or encoding CMap: `bfchar` / `bfrange` → strings, `cidchar` / `cidrange` → /// CIDs, `codespacerange` → code lengths. #[derive(Default)] struct CMap { codespace: Vec<(usize, u32, u32)>, unicode: HashMap, cids: Vec<(u32, u32, u32)>, /// `/WMode 1`: vertical writing. wmode: bool, } fn be(b: &[u8]) -> u32 { b.iter().fold(0u32, |a, &c| (a << 8) | c as u32) } fn utf16(b: &[u8]) -> String { let u: Vec = b.chunks(2).map(|c| u16::from_be_bytes([c[0], *c.get(1).unwrap_or(&0)])).collect(); String::from_utf16_lossy(&u) } fn parse_cmap(data: &[u8]) -> CMap { let mut m = CMap::default(); let mut lx = Lexer::new(data, 0); let mut args: Vec = vec![]; let mut mode = ""; while let Some(o) = lx.next() { let Obj::Op(op) = o else { args.push(o); continue; }; match op.as_str() { "begincodespacerange" => mode = "cs", "beginbfchar" => mode = "bfchar", "beginbfrange" => mode = "bfrange", "begincidchar" => mode = "cidchar", "begincidrange" => mode = "cidrange", "endcodespacerange" | "endbfchar" | "endbfrange" | "endcidchar" | "endcidrange" => { let a = std::mem::take(&mut args); match mode { "cs" => { for p in a.as_chunks::<2>().0 { if let (Obj::Str(lo), Obj::Str(hi)) = (&p[0], &p[1]) { m.codespace.push((lo.len().max(1), be(lo), be(hi))); } } } "bfchar" => { for p in a.as_chunks::<2>().0 { if let (Obj::Str(src), Obj::Str(dst)) = (&p[0], &p[1]) { m.unicode.insert(be(src), utf16(dst)); } } } "bfrange" => { for p in a.as_chunks::<3>().0 { let (Obj::Str(lo), Obj::Str(hi)) = (&p[0], &p[1]) else { continue }; let (lo, hi) = (be(lo), be(hi)); if hi < lo || hi - lo > 65535 { continue; } match &p[2] { Obj::Str(dst) => { let mut d = dst.clone(); for c in lo..=hi { m.unicode.insert(c, utf16(&d)); if let Some(last) = d.last_mut() { *last = last.wrapping_add(1); } } } Obj::Array(list) => { for (k, s) in list.iter().enumerate() { if let Obj::Str(s) = s { m.unicode.insert(lo + k as u32, utf16(s)); } } } _ => {} } } } "cidchar" => { for p in a.as_chunks::<2>().0 { if let (Obj::Str(src), Some(cid)) = (&p[0], p[1].num()) { let c = be(src); m.cids.push((c, c, cid as u32)); } } } "cidrange" => { for p in a.as_chunks::<3>().0 { if let (Obj::Str(lo), Obj::Str(hi), Some(cid)) = (&p[0], &p[1], p[2].num()) { m.cids.push((be(lo), be(hi), cid as u32)); } } } _ => {} } mode = ""; } "def" if mode.is_empty() => { if let [.., Obj::Name(k), Obj::Num(v)] = args.as_slice() && k == "WMode" { m.wmode = *v == 1.0; } args.clear(); } _ => { if mode.is_empty() { args.clear(); } } } } m } impl Font { /// Read a font dictionary. `skipped` collects what could not be read. fn blank(base_name: &str, composite: bool) -> Font { Font { composite, program: Program::None, matrix: Affine::scale(0.001), first_char: 0, widths: vec![], cid_widths: HashMap::new(), default_width: 0.0, names: vec![None; 256], symbolic: false, to_unicode: HashMap::new(), cid_to_gid: None, codespace: vec![], cid_ranges: vec![], vertical: false, cid_vmetrics: HashMap::new(), dw2: (0.88, -1.0), cache: RefCell::new(HashMap::new()), has_widths: false, base_name: base_name.to_string(), } } /// A simple font outside a PDF (EPS text): `program` is an embedded font program (CFF when /// the flag is set, else Type 1), otherwise the bundled stand-in for `name` is used; /// `names` is the encoding (code → glyph name), else the program's built-in encoding or /// StandardEncoding. Advances are the program's. pub fn standalone(name: &str, program: Option<(&[u8], bool)>, names: Option>>) -> Font { let mut f = Font::blank(name, false); f.program = match program { Some((data, true)) => Cff::parse(data).map(|c| Program::Cff(Box::new(c))), Some((data, false)) => Type1::parse(data).map(|t| Program::Type1(Box::new(t))), None => None, } .unwrap_or_else(|| Program::Fallback(fallback(name))); match &f.program { Program::Cff(c) => f.matrix = Affine::new(c.font_matrix), Program::Type1(t) => f.matrix = t.font_matrix.map(Affine::new).unwrap_or(f.matrix), _ => {} } let symbol = matches!(f.program, Program::Fallback(_)) && (name.contains("Symbol") || name.contains("Dingbats")); f.names = match names { // Unencoded codes draw .notdef (not the built-in encoding). Some(n) => (0..256).map(|c| Some(n.get(c).cloned().flatten().unwrap_or_else(|| ".notdef".into()))).collect(), None => match &f.program { Program::Type1(t) if !t.encoding.is_empty() => (0..256).map(|c| t.encoding.get(&(c as u8)).cloned()).collect(), _ if symbol => vec![None; 256], _ => (0..256).map(|c| Base::Standard.name(c as u8)).collect(), }, }; f } pub fn load(file: &File, d: &Dict, skipped: &mut Vec) -> Font { let subtype = file.get(d, "Subtype").and_then(Obj::name).unwrap_or("Type1").to_string(); let base_name = file.get(d, "BaseFont").and_then(Obj::name).unwrap_or("").to_string(); let mut f = Font::blank(&base_name, subtype == "Type0"); if let Some(Obj::Stream(sd, raw)) = file.get(d, "ToUnicode") && let Some(data) = decode_stream(file, sd, raw) { f.to_unicode = parse_cmap(&data).unicode; } let (desc_dict, fd) = if f.composite { let desc = file .get(d, "DescendantFonts") .and_then(Obj::array) .and_then(|a| a.first()) .map(|x| file.resolve(x)) .and_then(Obj::dict) .cloned() .unwrap_or_default(); let fd = file.get_dict(&desc, "FontDescriptor").cloned(); (desc, fd) } else { (d.clone(), file.get_dict(d, "FontDescriptor").cloned()) }; if let Some(fd) = &fd { f.symbolic = file.get_num(fd, "Flags").is_some_and(|fl| (fl as u32) & 4 != 0); } // Widths. if f.composite { f.default_width = file.get_num(&desc_dict, "DW").unwrap_or(1000.0) / 1000.0; if let Some(w) = file.get(&desc_dict, "W").and_then(Obj::array) { let mut i = 0; while i < w.len() { let first = file.resolve(&w[i]).num().unwrap_or(0.0) as u32; match w.get(i + 1).map(|x| file.resolve(x)) { Some(Obj::Array(list)) => { for (k, v) in list.iter().enumerate() { f.cid_widths.insert(first + k as u32, file.resolve(v).num().unwrap_or(0.0) / 1000.0); } i += 2; } Some(Obj::Num(last)) => { let wv = w.get(i + 2).and_then(|x| file.resolve(x).num()).unwrap_or(0.0) / 1000.0; for c in first..=(*last as u32).min(first + 65535) { f.cid_widths.insert(c, wv); } i += 3; } _ => break, } } } // Vertical metrics (§9.7.4.3): /DW2 [vy w1y] and /W2 entries // `c [w1y vx vy …]` or `cfirst clast w1y vx vy`. let dw2 = file.get(&desc_dict, "DW2").map(|x| file.nums(x)).unwrap_or_default(); if dw2.len() == 2 { f.dw2 = (dw2[0] / 1000.0, dw2[1] / 1000.0); } if let Some(w) = file.get(&desc_dict, "W2").and_then(Obj::array) { let num = |i: usize| w.get(i).and_then(|x| file.resolve(x).num()); let mut i = 0; while i < w.len() { let Some(first) = num(i) else { break }; let first = first.max(0.0) as u32; match w.get(i + 1).map(|x| file.resolve(x)) { Some(Obj::Array(list)) => { let v: Vec = list.iter().filter_map(|x| file.resolve(x).num()).collect(); for (k, t) in v.as_chunks::<3>().0.iter().enumerate() { f.cid_vmetrics.insert(first + k as u32, (t[0] / 1000.0, t[1] / 1000.0, t[2] / 1000.0)); } i += 2; } Some(Obj::Num(last)) => { let (Some(w1), Some(vx), Some(vy)) = (num(i + 2), num(i + 3), num(i + 4)) else { break }; for c in first..=(last.max(0.0) as u32).min(first + 65535) { f.cid_vmetrics.insert(c, (w1 / 1000.0, vx / 1000.0, vy / 1000.0)); } i += 5; } _ => break, } } } f.has_widths = true; // Encoding CMap. match file.get(d, "Encoding") { Some(Obj::Name(n)) => { f.vertical = n.ends_with("-V"); if !n.starts_with("Identity") { skipped.push(format!("CMap {n}")); } } Some(Obj::Stream(sd, raw)) => { if let Some(data) = decode_stream(file, sd, raw) { let cm = parse_cmap(&data); f.vertical = cm.wmode || file.get_num(sd, "WMode") == Some(1.0); f.codespace = cm.codespace; f.cid_ranges = cm.cids; } } _ => {} } match file.get(&desc_dict, "CIDToGIDMap") { Some(Obj::Stream(sd, raw)) => { f.cid_to_gid = decode_stream(file, sd, raw).map(|b| b.as_chunks::<2>().0.iter().map(|c| u16::from_be_bytes([c[0], c[1]])).collect()); } _ => f.cid_to_gid = None, } } else { f.first_char = file.get_num(d, "FirstChar").unwrap_or(0.0) as u32; if let Some(w) = file.get(d, "Widths") { f.widths = file.nums(w); f.has_widths = !f.widths.is_empty(); } if let Some(fd) = &fd { f.default_width = file.get_num(fd, "MissingWidth").unwrap_or(0.0); } } // The program. let font_file = |key: &str| -> Option<(Dict, Vec)> { let fd = fd.as_ref()?; match file.get(fd, key) { Some(Obj::Stream(sd, raw)) => Some((sd.clone(), decode_stream(file, sd, raw)?)), _ => None, } }; if subtype == "Type3" { f.matrix = file .get(d, "FontMatrix") .map(|m| file.nums(m)) .filter(|m| m.len() == 6) .map(|m| Affine::new([m[0], m[1], m[2], m[3], m[4], m[5]])) .unwrap_or(Affine::scale(0.001)); let mut procs = HashMap::new(); if let Some(cp) = file.get_dict(d, "CharProcs") { for (k, v) in cp { if let Obj::Stream(sd, raw) = file.resolve(v) && let Some(data) = decode_stream(file, sd, raw) { procs.insert(k.clone(), data); } } } f.program = Program::Type3 { procs, resources: file.get_dict(d, "Resources").cloned() }; // Type 3 widths are in glyph space. let m = f.matrix.as_coeffs()[0]; for w in &mut f.widths { *w *= m * 1000.0; } } else if let Some((_, data)) = font_file("FontFile2") { f.program = Program::TrueType(Arc::new(data)); } else if let Some((sd, data)) = font_file("FontFile3") { match file.get(&sd, "Subtype").and_then(Obj::name) { Some("OpenType") => { // An OpenType wrapper: TrueType outlines through skrifa, CFF through ours. let cff = FontRef::new(&data).ok().and_then(|fr| fr.table_data(skrifa::Tag::new(b"CFF ")).map(|t| t.as_bytes().to_vec())); match cff.and_then(|c| Cff::parse(&c)) { Some(c) => f.program = Program::Cff(Box::new(c)), None => f.program = Program::TrueType(Arc::new(data)), } } _ => match Cff::parse(&data) { Some(c) => f.program = Program::Cff(Box::new(c)), None => skipped.push("font program (CFF)".into()), }, } } else if let Some((_, data)) = font_file("FontFile") { match Type1::parse(&data) { Some(t) => f.program = Program::Type1(Box::new(t)), None => skipped.push("font program (Type 1)".into()), } } else { f.program = Program::Fallback(fallback(&base_name)); } if let Program::Cff(c) = &f.program { let m = c.font_matrix; f.matrix = Affine::new(m); } if let Program::Type1(t) = &f.program && let Some(m) = t.font_matrix { f.matrix = Affine::new(m); } // Simple-font encoding: base encoding, built-in encoding, Differences. if !f.composite { let enc = file.get(d, "Encoding").map(|e| file.resolve(e)); let base = match enc { Some(Obj::Name(n)) => Base::from_name(n), Some(Obj::Dict(e)) => file.get(e, "BaseEncoding").and_then(Obj::name).and_then(Base::from_name), _ => None, }; let builtin: Option>> = match &f.program { Program::Type1(t) if !t.encoding.is_empty() => Some((0..256).map(|c| t.encoding.get(&(c as u8)).cloned()).collect()), _ => None, }; let fallback_base = if f.symbolic { None } else { Some(Base::Standard) }; for c in 0..256usize { f.names[c] = match (base, &builtin) { (Some(b), _) => b.name(c as u8), (None, Some(bi)) => bi[c].clone(), (None, None) => match &f.program { // TrueType symbolic fonts map codes directly; others use Standard. Program::TrueType(_) if f.symbolic => None, Program::Fallback(_) if base_name.contains("Symbol") || base_name.contains("Dingbats") => None, _ => fallback_base.or(Some(Base::Standard)).and_then(|b| b.name(c as u8)), }, }; } if let Some(Obj::Dict(e)) = enc { differences(file, e, &mut f.names); } } f } /// Split a string into (code, byte length) pairs. pub fn codes(&self, s: &[u8]) -> Vec<(u32, usize)> { if !self.composite { return s.iter().map(|&b| (b as u32, 1)).collect(); } let mut out = vec![]; let mut i = 0; while i < s.len() { let mut len = 2; if !self.codespace.is_empty() { len = (1..=4) .find(|&n| { i + n <= s.len() && { let v = be(&s[i..i + n]); self.codespace.iter().any(|(l, lo, hi)| *l == n && v >= *lo && v <= *hi) } }) .unwrap_or(1); } let end = (i + len).min(s.len()); out.push((be(&s[i..end]), end - i)); i = end; } out } fn cid(&self, code: u32) -> u32 { if self.cid_ranges.is_empty() { return code; } self.cid_ranges.iter().find(|(lo, hi, _)| code >= *lo && code <= *hi).map(|(lo, _, c)| c + (code - lo)).unwrap_or(0) } /// Vertical metrics of a code in text space (before the font size): the vertical /// displacement `w1y` (negative: downwards) and the position vector `(vx, vy)` from the /// horizontal origin to the vertical origin (§9.7.4.3; defaults from `/DW2`, `vx` half the /// horizontal advance). pub fn vmetrics(&self, code: u32) -> (f64, f64, f64) { match self.cid_vmetrics.get(&self.cid(code)) { Some(m) => *m, None => (self.dw2.1, self.width(code) / 2.0, self.dw2.0), } } /// The advance of a code in text space (before the font size). pub fn width(&self, code: u32) -> f64 { if self.composite { return self.cid_widths.get(&self.cid(code)).copied().unwrap_or(self.default_width); } if self.has_widths { return match code.checked_sub(self.first_char).and_then(|i| self.widths.get(i as usize)) { Some(w) => w / 1000.0, None => self.default_width / 1000.0, }; } // No /Widths (standard 14 fonts): the program's own advance. match &self.program { Program::Fallback(data) => { self.sfnt_gid(data, code).and_then(|g| sfnt_advance(data, g)).map(|a| a / sfnt_upem(data)).unwrap_or(if code == 32 { 0.25 } else { 0.5 }) } Program::TrueType(data) => self.sfnt_gid(data, code).and_then(|g| sfnt_advance(data, g)).map(|a| a / sfnt_upem(data)).unwrap_or(0.5), Program::Type1(t) => self.name(code).and_then(|n| t.outline(n)).map(|(_, w)| self.matrix.as_coeffs()[0] * w).unwrap_or(0.5), Program::Cff(c) => { self.name(code).and_then(|n| c.gid_by_name(n)).and_then(|g| c.outline(g)).map(|(_, w)| self.matrix.as_coeffs()[0] * w).unwrap_or(0.5) } _ => 0.5, } } fn name(&self, code: u32) -> Option<&str> { self.names.get(code as usize).and_then(|n| n.as_deref()) } fn sfnt_gid(&self, data: &[u8], code: u32) -> Option { if self.composite { let cid = self.cid(code); return Some(match &self.cid_to_gid { Some(map) => *map.get(cid as usize)?, None => cid as u16, }); } // Stand-ins are looked up by Unicode (ToUnicode first). if let Program::Fallback(_) = self.program && let Some(u) = self.to_unicode.get(&code).and_then(|s| s.chars().next()) { let font = FontRef::new(data).ok()?; return font.charmap().map(u).map(|g| g.to_u32() as u16); } sfnt_glyph(data, code as u8, self.name(code), self.symbolic && !matches!(self.program, Program::Fallback(_))) } /// The Type 3 glyph procedure of a code. pub fn type3_proc(&self, code: u32) -> Option<(&[u8], Option<&Dict>)> { let Program::Type3 { procs, resources } = &self.program else { return None }; let n = self.name(code)?; procs.get(n).map(|p| (p.as_slice(), resources.as_ref())) } /// A code's outline in text space (font size 1), cached. pub fn glyph(&self, code: u32) -> Option> { if let Some(v) = self.cache.borrow().get(&code) { return v.clone(); } let path = self.glyph_uncached(code).map(Arc::new); self.cache.borrow_mut().insert(code, path.clone()); path } fn glyph_uncached(&self, code: u32) -> Option { match &self.program { Program::TrueType(data) => self.sfnt_path(data, code), Program::Fallback(data) => self.sfnt_path(data, code), Program::Cff(c) => { let g = if self.composite { c.gid_by_cid(self.cid(code) as u16)? } else { match self.name(code).and_then(|n| c.gid_by_name(n)) { Some(g) => g, None => Some(c.encoding[(code & 255) as usize]).filter(|g| *g != 0)?, } }; c.outline(g).map(|(p, _)| self.matrix * p) } Program::Type1(t) => { let name = self.name(code).filter(|n| t.has_glyph(n)).or_else(|| t.encoding.get(&(code as u8)).map(String::as_str))?; t.outline(name).map(|(p, _)| self.matrix * p) } Program::Type3 { .. } | Program::None => None, } } fn sfnt_path(&self, data: &[u8], code: u32) -> Option { let g = self.sfnt_gid(data, code)?; let p = sfnt_outline(data, g)?; Some(Affine::scale(1.0 / sfnt_upem(data)) * p) } /// The text of a code (ToUnicode, else the glyph name), for naming text shapes. pub fn unicode(&self, code: u32) -> String { if let Some(s) = self.to_unicode.get(&code) { return s.clone(); } if self.composite { return String::new(); } self.name(code).and_then(name_to_unicode).map(String::from).unwrap_or_default() } }