pub mod cmap;
pub mod dotsection;
pub mod encodings;
pub mod fallback;
pub mod type1;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
use lopdf::{Dictionary, Document, Object, ObjectId};
use ttf_parser::GlyphId;
use super::geom::Mat;
use super::objects::{
as_dict, as_stream, deref, get, get_dict, get_int, get_name, get_num, name, num, nums,
};
use cmap::CMap;
pub struct GlyphPath {
pub path: Option<tiny_skia::Path>,
pub advance: f64,
}
enum Program {
Sfnt {
data: Vec<u8>,
matrix: Mat,
cid_to_gid: Option<HashMap<u16, u16>>,
has_cmap: bool,
has_glyf: bool,
names: HashMap<String, u16>,
},
Cff {
data: Vec<u8>,
matrix: Mat,
cid_to_gid: Option<HashMap<u16, u16>>,
names: HashMap<String, u16>,
},
Type1 {
font: type1::Type1Font,
matrix: Mat,
},
Fallback {
face: Arc<fallback::FallbackFace>,
matrix: Mat,
},
None,
}
pub struct Type3 {
pub font_matrix: Mat,
pub char_procs: HashMap<String, ObjectId>,
pub resources: Option<Dictionary>,
}
pub struct LoadedFont {
pub composite: bool,
pub type3: Option<Type3>,
program: Program,
encoding_names: HashMap<u8, String>,
has_base_encoding: bool,
symbolic: bool,
cmap: Option<CMap>,
cid_to_gid: CidToGid,
widths: HashMap<u32, f64>,
default_width: Option<f64>,
to_unicode: HashMap<u32, String>,
std14: Option<crate::std14::Std14Widths>,
cache: RefCell<HashMap<u32, Option<Rc<tiny_skia::Path>>>>,
advance_cache: RefCell<HashMap<u32, f64>>,
pub vertical: bool,
is_dingbats: bool,
is_symbol_face: bool,
}
enum CidToGid {
Identity,
Map(Vec<u8>),
}
fn descriptor<'a>(
doc: &'a Document,
fdict: &'a Dictionary,
composite: bool,
) -> Option<&'a Dictionary> {
let owner = if composite {
descendant(doc, fdict)?
} else {
fdict
};
get_dict(doc, owner, b"FontDescriptor")
}
fn descendant<'a>(doc: &'a Document, fdict: &'a Dictionary) -> Option<&'a Dictionary> {
match get(doc, fdict, b"DescendantFonts")? {
Object::Array(a) => a.first().and_then(|o| as_dict(doc, o)),
Object::Dictionary(d) => Some(d),
_ => None,
}
}
fn base_font_name(doc: &Document, fdict: &Dictionary) -> String {
let raw = get_name(doc, fdict, b"BaseFont")
.map(|n| String::from_utf8_lossy(n).into_owned())
.unwrap_or_default();
match raw.split_once('+') {
Some((pre, rest)) if pre.len() == 6 && pre.bytes().all(|b| b.is_ascii_uppercase()) => {
rest.to_string()
}
_ => raw,
}
}
impl LoadedFont {
pub fn load(doc: &Document, fdict: &Dictionary) -> LoadedFont {
let subtype = get_name(doc, fdict, b"Subtype").unwrap_or(b"");
let composite = subtype == b"Type0";
let base_font = base_font_name(doc, fdict);
let desc = descriptor(doc, fdict, composite);
let flags = desc.and_then(|d| get_int(doc, d, b"Flags"));
let symbolic_flag = flags.is_some_and(|f| f & 4 != 0 && f & 32 == 0);
let mut program = Program::None;
if let Some(d) = desc {
if let Some(s) = d.get(b"FontFile2").ok().and_then(|o| as_stream(doc, o)) {
if let Ok(data) = s.decompressed_content() {
program = sfnt_program(data).unwrap_or(Program::None);
}
}
if matches!(program, Program::None) {
if let Some(s) = d.get(b"FontFile3").ok().and_then(|o| as_stream(doc, o)) {
if let Ok(data) = s.decompressed_content() {
let st = get_name(doc, &s.dict, b"Subtype").unwrap_or(b"");
program = if st == b"OpenType"
|| data.starts_with(b"OTTO")
|| data.starts_with(&[0, 1, 0, 0])
|| data.starts_with(b"true")
{
sfnt_program(data.clone())
.or_else(|| cff_program(data))
.unwrap_or(Program::None)
} else {
cff_program(data.clone())
.or_else(|| sfnt_program(data))
.unwrap_or(Program::None)
};
}
}
}
if matches!(program, Program::None) {
if let Some(s) = d.get(b"FontFile").ok().and_then(|o| as_stream(doc, o)) {
if let Ok(data) = s.decompressed_content() {
if let Some(f) = type1::Type1Font::parse(&data) {
let m =
Mat::from_slice(&f.font_matrix).unwrap_or(Mat::scale(0.001, 0.001));
program = Program::Type1 { font: f, matrix: m };
} else if let Some(p) =
cff_program(data.clone()).or_else(|| sfnt_program(data))
{
program = p;
}
}
}
}
}
let type3 = if subtype == b"Type3" {
let fm = get(doc, fdict, b"FontMatrix")
.and_then(|o| nums(doc, o))
.and_then(|v| Mat::from_slice(&v))
.unwrap_or(Mat::scale(0.001, 0.001));
let mut char_procs = HashMap::new();
if let Some(cp) = get_dict(doc, fdict, b"CharProcs") {
for (k, v) in cp.iter() {
if let Object::Reference(id) = v {
char_procs.insert(String::from_utf8_lossy(k).into_owned(), *id);
}
}
}
let resources = get_dict(doc, fdict, b"Resources").cloned();
Some(Type3 {
font_matrix: fm,
char_procs,
resources,
})
} else {
None
};
let lower = base_font.to_ascii_lowercase();
let is_symbol_face = lower.starts_with("symbol");
let is_dingbats = lower.contains("dingbat");
let mut encoding_names: HashMap<u8, String> = HashMap::new();
let mut has_base_encoding = false;
let enc = get(doc, fdict, b"Encoding");
let mut cmap = None;
if composite {
cmap = Some(match enc {
Some(Object::Name(n)) => CMap::predefined(n),
Some(Object::Stream(s)) => {
let mut c = s
.decompressed_content()
.ok()
.map(|d| CMap::parse(&d))
.unwrap_or_else(CMap::identity_h);
if let Some(Object::Name(n)) = get(doc, &s.dict, b"UseCMap") {
if n.starts_with(b"Identity") && !c.identity {
}
}
if get_int(doc, &s.dict, b"WMode") == Some(1) {
c.vertical = true;
}
c
}
_ => CMap::identity_h(),
});
} else {
let base_table: Option<&'static [(u8, &'static str)]> = match enc {
Some(Object::Name(n)) => {
has_base_encoding = true;
encodings::by_name(n)
}
Some(Object::Dictionary(d)) => match get_name(doc, d, b"BaseEncoding") {
Some(n) => {
has_base_encoding = true;
encodings::by_name(n)
}
None => None,
},
_ => None,
};
let implicit: Option<&'static [(u8, &'static str)]> = if is_symbol_face
&& matches!(program, Program::None)
{
Some(encodings::SYMBOL)
} else if is_dingbats && matches!(program, Program::None) {
Some(encodings::ZAPF_DINGBATS)
} else if !symbolic_flag || matches!(program, Program::None | Program::Fallback { .. })
{
Some(encodings::STANDARD)
} else {
None
};
if let Some(t) = base_table.or(implicit) {
for &(c, n) in t {
encoding_names.insert(c, n.to_string());
}
}
if let Some(Object::Dictionary(d)) = enc {
if let Some(Object::Array(diffs)) = get(doc, d, b"Differences") {
let mut code: i64 = 0;
for el in diffs {
match deref(doc, el) {
Object::Integer(i) => code = *i,
Object::Real(r) => code = *r as i64,
Object::Name(n) => {
if (0..=255).contains(&code) {
encoding_names.insert(
code as u8,
String::from_utf8_lossy(n).into_owned(),
);
}
code += 1;
}
_ => {}
}
}
}
}
}
let mut widths = HashMap::new();
let mut default_width = None;
if composite {
if let Some(dd) = descendant(doc, fdict) {
default_width = Some(get_num(doc, dd, b"DW").unwrap_or(1000.0));
if let Some(Object::Array(w)) = get(doc, dd, b"W") {
let mut i = 0;
while i < w.len() {
let c = w.get(i).map(|o| deref(doc, o)).and_then(num);
match (c, w.get(i + 1).map(|o| deref(doc, o))) {
(Some(c), Some(Object::Array(list))) => {
for (k, wv) in list.iter().enumerate() {
if let Some(wv) = num(deref(doc, wv)) {
widths.insert(c as u32 + k as u32, wv);
}
}
i += 2;
}
(Some(c1), Some(o2)) => {
if let (Some(c2), Some(wv)) =
(num(o2), w.get(i + 2).map(|o| deref(doc, o)).and_then(num))
{
let (c1, c2) = (c1.max(0.0) as u32, c2.max(0.0) as u32);
if c2 >= c1 && c2 - c1 < 65536 {
for cid in c1..=c2 {
widths.insert(cid, wv);
}
}
}
i += 3;
}
_ => break,
}
}
}
}
} else {
let first = get_int(doc, fdict, b"FirstChar").unwrap_or(0);
if let Some(Object::Array(w)) = get(doc, fdict, b"Widths") {
for (k, wv) in w.iter().enumerate() {
if let Some(wv) = num(deref(doc, wv)) {
let code = first + k as i64;
if (0..=255).contains(&code) {
widths.insert(code as u32, wv);
}
}
}
if !w.is_empty() {
default_width = Some(
desc.and_then(|d| get_num(doc, d, b"MissingWidth"))
.unwrap_or(0.0),
);
}
}
if widths.is_empty() {
if let Some(mw) = desc.and_then(|d| get_num(doc, d, b"MissingWidth")) {
if mw > 0.0 {
default_width = Some(mw);
}
}
}
}
let std14 = if widths.is_empty() && !composite && type3.is_none() {
crate::std14::widths_for(base_font.as_bytes())
} else {
None
};
let mut cid_to_gid = CidToGid::Identity;
if composite {
if let Some(dd) = descendant(doc, fdict) {
if let Some(Object::Stream(s)) = get(doc, dd, b"CIDToGIDMap") {
if let Ok(data) = s.decompressed_content() {
cid_to_gid = CidToGid::Map(data);
}
}
}
}
if matches!(program, Program::None) && type3.is_none() {
let ordering_cjk = composite
&& descendant(doc, fdict)
.and_then(|dd| get_dict(doc, dd, b"CIDSystemInfo"))
.and_then(|si| get(doc, si, b"Ordering"))
.and_then(|o| match o {
Object::String(s, _) => Some(s.clone()),
_ => None,
})
.is_some_and(|o| {
matches!(
o.as_slice(),
b"Japan1" | b"GB1" | b"CNS1" | b"Korea1" | b"KR"
)
});
let mut style = fallback::style_for(&base_font, flags, None);
if ordering_cjk {
style.family = fallback::Family::Cjk;
}
if let Some(face) = fallback::face(style) {
let upem = ttf_parser::Face::parse(&face.data, 0)
.ok()
.map(|f| f64::from(f.units_per_em()))
.filter(|u| *u > 0.0)
.unwrap_or(1000.0);
program = Program::Fallback {
face,
matrix: Mat::scale(1.0 / upem, 1.0 / upem),
};
}
}
let to_unicode = get(doc, fdict, b"ToUnicode")
.and_then(|o| match o {
Object::Stream(s) => s.decompressed_content().ok(),
_ => None,
})
.map(|d| crate::textparse::parse_tounicode(&d))
.unwrap_or_default();
let vertical = cmap.as_ref().is_some_and(|c| c.vertical);
LoadedFont {
composite,
type3,
program,
encoding_names,
has_base_encoding,
symbolic: symbolic_flag && enc.is_none(),
cmap,
cid_to_gid,
widths,
default_width,
to_unicode,
std14,
cache: RefCell::new(HashMap::new()),
advance_cache: RefCell::new(HashMap::new()),
vertical,
is_dingbats,
is_symbol_face,
}
}
pub fn decode(&self, bytes: &[u8]) -> Vec<(u32, u32, bool)> {
match &self.cmap {
Some(c) => c
.split(bytes)
.into_iter()
.map(|(code, n)| (code, c.cid(code), n == 1))
.collect(),
None => bytes
.iter()
.map(|&b| (u32::from(b), u32::from(b), true))
.collect(),
}
}
pub fn advance(&self, code: u32, cid: u32) -> f64 {
let key = if self.composite { cid } else { code };
if let Some(a) = self.advance_cache.borrow().get(&key) {
return *a;
}
let a = self.advance_uncached(code, cid);
self.advance_cache.borrow_mut().insert(key, a);
a
}
fn advance_uncached(&self, code: u32, cid: u32) -> f64 {
let key = if self.composite { cid } else { code };
if let Some(t3) = &self.type3 {
let w = self.widths.get(&key).copied().unwrap_or(0.0);
let (ax, _) = t3.font_matrix.apply_vec(w, 0.0);
return ax;
}
if let Some(w) = self.widths.get(&key) {
return w / 1000.0;
}
if let Some(std) = &self.std14 {
if let Some(ch) = self.unicode_of(code, cid) {
if let Some(w) = std.width(ch) {
return w / 1000.0;
}
}
}
if let Some(dw) = self.default_width {
if dw > 0.0 || !self.widths.is_empty() {
return dw / 1000.0;
}
}
if let Some(adv) = self.program_advance(code, cid) {
return adv;
}
if self.default_width.is_some() {
return self.default_width.unwrap_or(0.0) / 1000.0;
}
0.5
}
fn unicode_of(&self, code: u32, cid: u32) -> Option<char> {
let key = if self.composite { cid } else { code };
if let Some(s) = self
.to_unicode
.get(&key)
.or_else(|| self.to_unicode.get(&code))
{
return s.chars().next();
}
if !self.composite {
if let Some(n) = self.encoding_names.get(&(code as u8)) {
return crate::textparse::glyph_name_to_char(n.as_bytes());
}
return char::from_u32(code).filter(|c| c.is_ascii_graphic() || *c == ' ');
}
if self.cmap.as_ref().is_some_and(|c| c.unicode_codes) {
return char::from_u32(code);
}
None
}
pub fn type3_glyph_name(&self, code: u32) -> Option<String> {
self.encoding_names.get(&(code as u8)).cloned()
}
fn glyph_name(&self, code: u32) -> Option<&str> {
self.encoding_names.get(&(code as u8)).map(String::as_str)
}
pub fn glyph(&self, code: u32, cid: u32) -> Option<Rc<tiny_skia::Path>> {
let key = if self.composite { cid } else { code };
if let Some(p) = self.cache.borrow().get(&key) {
return p.clone();
}
let built = self.build_glyph(code, cid).and_then(|g| {
let m = self.program_matrix();
g.path.and_then(|p| p.transform(m.to_ts())).map(Rc::new)
});
self.cache.borrow_mut().insert(key, built.clone());
built
}
fn program_matrix(&self) -> Mat {
match &self.program {
Program::Sfnt { matrix, .. }
| Program::Cff { matrix, .. }
| Program::Type1 { matrix, .. }
| Program::Fallback { matrix, .. } => *matrix,
Program::None => Mat::scale(0.001, 0.001),
}
}
pub fn has_program(&self) -> bool {
!matches!(self.program, Program::None)
}
fn program_advance(&self, code: u32, cid: u32) -> Option<f64> {
let g = self.build_glyph(code, cid)?;
let (ax, _) = self.program_matrix().apply_vec(g.advance, 0.0);
Some(ax)
}
fn build_glyph(&self, code: u32, cid: u32) -> Option<GlyphPath> {
match &self.program {
Program::None => None,
Program::Type1 { font, .. } => {
let name = self.type1_glyph_name(font, code)?;
font.glyph(&name)
}
Program::Cff {
data,
cid_to_gid,
names,
..
} => {
let table = ttf_parser::cff::Table::parse(data)?;
let gid = if self.composite {
let gid = match &self.cid_to_gid {
CidToGid::Map(m) => map_cid(m, cid),
CidToGid::Identity => cid,
};
match cid_to_gid {
Some(map) => GlyphId(*map.get(&(gid as u16))?),
None => GlyphId(gid as u16),
}
} else {
self.cff_simple_gid(&table, names, code)?
};
outline_cff(&table, gid)
}
Program::Sfnt {
data,
cid_to_gid,
has_cmap,
has_glyf,
names,
..
} => {
let face = ttf_parser::Face::parse(data, 0).ok()?;
let gid = if self.composite {
let gid = match &self.cid_to_gid {
CidToGid::Map(m) => map_cid(m, cid),
CidToGid::Identity => cid,
};
match cid_to_gid {
Some(map) => GlyphId(*map.get(&(gid as u16))?),
None => GlyphId(gid as u16),
}
} else {
self.sfnt_simple_gid(&face, names, code, *has_cmap, *has_glyf)?
};
outline_face(&face, gid)
}
Program::Fallback { face, .. } => {
let f = ttf_parser::Face::parse(&face.data, 0).ok()?;
let gid = self.fallback_gid(&f, code, cid)?;
outline_face(&f, gid)
}
}
}
fn type1_glyph_name(&self, font: &type1::Type1Font, code: u32) -> Option<String> {
let c = code as u8;
let pdf_name = self.glyph_name(code);
let builtin = font.encoding.get(&c).cloned();
let candidates: Vec<String> = if self.symbolic && !self.has_base_encoding {
[pdf_name.map(str::to_string), builtin.clone()]
.into_iter()
.flatten()
.collect()
} else {
[pdf_name.map(str::to_string), builtin.clone()]
.into_iter()
.flatten()
.collect()
};
for cand in &candidates {
if font.has_glyph(cand) {
return Some(cand.clone());
}
if let Some(ch) = crate::textparse::glyph_name_to_char(cand.as_bytes()) {
if let Some(n) = self.type1_name_for_char(font, ch) {
return Some(n);
}
}
}
if let Some(n) = encodings::lookup(encodings::STANDARD, c) {
if font.has_glyph(n) {
return Some(n.to_string());
}
}
if let Some(b) = builtin {
return Some(b);
}
None
}
fn type1_name_for_char(&self, font: &type1::Type1Font, ch: char) -> Option<String> {
font.glyph_names()
.find(|n| crate::textparse::glyph_name_to_char(n.as_bytes()) == Some(ch))
.cloned()
}
fn cff_simple_gid(
&self,
table: &ttf_parser::cff::Table,
names: &HashMap<String, u16>,
code: u32,
) -> Option<GlyphId> {
let c = code as u8;
let by_name = |n: &str| -> Option<GlyphId> {
names
.get(n)
.map(|g| GlyphId(*g))
.or_else(|| table.glyph_index_by_name(n))
};
if let Some(name) = self.glyph_name(code) {
if let Some(g) = by_name(name) {
return Some(g);
}
if let Some(ch) = crate::textparse::glyph_name_to_char(name.as_bytes()) {
if let Some(std_name) = agl_name(ch) {
if let Some(g) = by_name(std_name) {
return Some(g);
}
}
let uni = format!("uni{:04X}", ch as u32);
if let Some(g) = by_name(&uni) {
return Some(g);
}
if let Some(g) = names
.iter()
.find(|(n, _)| crate::textparse::glyph_name_to_char(n.as_bytes()) == Some(ch))
.map(|(_, g)| GlyphId(*g))
{
return Some(g);
}
}
}
if let Some(g) = table.glyph_index(c) {
if g.0 != 0 {
return Some(g);
}
}
if let Some(n) = encodings::lookup(encodings::STANDARD, c) {
if let Some(g) = by_name(n) {
return Some(g);
}
}
None
}
fn sfnt_simple_gid(
&self,
face: &ttf_parser::Face,
names: &HashMap<String, u16>,
code: u32,
has_cmap: bool,
has_glyf: bool,
) -> Option<GlyphId> {
let c = code as u8;
let cmap = face.tables().cmap.as_ref();
let symbol_lookup = |code: u32| -> Option<GlyphId> {
let cm = cmap?;
for sub in cm.subtables {
if sub.platform_id == ttf_parser::PlatformId::Windows && sub.encoding_id == 0 {
for probe in [code, 0xF000 + code, 0xF100 + code, 0xF200 + code] {
if let Some(g) = sub.glyph_index(probe) {
if g.0 != 0 {
return Some(g);
}
}
}
}
}
None
};
let mac_lookup = |code: u32| -> Option<GlyphId> {
let cm = cmap?;
for sub in cm.subtables {
if sub.platform_id == ttf_parser::PlatformId::Macintosh {
if let Some(g) = sub.glyph_index(code) {
if g.0 != 0 {
return Some(g);
}
}
}
}
None
};
let unicode_lookup = |ch: char| -> Option<GlyphId> {
let cm = cmap?;
for sub in cm.subtables {
if sub.is_unicode() {
if let Some(g) = sub.glyph_index(ch as u32) {
if g.0 != 0 {
return Some(g);
}
}
}
}
None
};
let name = self.glyph_name(code);
let symbolic_path = self.symbolic || !self.has_base_encoding && name.is_none();
if symbolic_path {
if let Some(g) = symbol_lookup(code) {
return Some(g);
}
if let Some(g) = mac_lookup(code) {
return Some(g);
}
}
if let Some(n) = name {
if let Some(ch) = crate::textparse::glyph_name_to_char(n.as_bytes()) {
if let Some(g) = unicode_lookup(ch) {
return Some(g);
}
if let Some(g) = symbol_lookup(ch as u32) {
return Some(g);
}
}
if let Some(g) = names
.get(n)
.map(|g| GlyphId(*g))
.or_else(|| face.glyph_index_by_name(n))
{
if g.0 != 0 {
return Some(g);
}
}
if let Some(g) = gid_from_name(n) {
return Some(GlyphId(g));
}
}
if !symbolic_path {
if let Some(g) = symbol_lookup(code) {
return Some(g);
}
if let Some(g) = mac_lookup(code) {
return Some(g);
}
}
if let Some(std) = encodings::lookup(encodings::STANDARD, c) {
if let Some(ch) = crate::textparse::glyph_name_to_char(std.as_bytes()) {
if let Some(g) = unicode_lookup(ch) {
return Some(g);
}
}
}
if !has_cmap || face.tables().cmap.is_none() {
let _ = has_glyf;
return Some(GlyphId(code as u16));
}
None
}
fn fallback_gid(&self, face: &ttf_parser::Face, code: u32, cid: u32) -> Option<GlyphId> {
if !self.composite && (self.is_symbol_face || self.is_dingbats) {
if let Some(n) = self.glyph_name(code) {
if let Some(g) = face.glyph_index_by_name(n) {
return Some(g);
}
}
}
if !self.composite {
if let Some(n) = self.glyph_name(code) {
if let Some(ch) = crate::textparse::glyph_name_to_char(n.as_bytes()) {
if let Some(g) = face.glyph_index(ch) {
return Some(g);
}
}
if let Some(g) = face.glyph_index_by_name(n) {
return Some(g);
}
}
}
let ch = self.unicode_of(code, cid)?;
face.glyph_index(ch)
}
}
fn map_cid(map: &[u8], cid: u32) -> u32 {
let i = cid as usize * 2;
match (map.get(i), map.get(i + 1)) {
(Some(hi), Some(lo)) => (u32::from(*hi) << 8) | u32::from(*lo),
_ => 0,
}
}
fn gid_from_name(n: &str) -> Option<u16> {
for prefix in ["glyph", "index", "cid", "g", "G"] {
if let Some(rest) = n.strip_prefix(prefix) {
if !rest.is_empty() && rest.bytes().all(|b| b.is_ascii_digit()) {
return rest.parse().ok();
}
}
}
None
}
fn agl_name(ch: char) -> Option<&'static str> {
encodings::STANDARD
.iter()
.chain(encodings::WIN_ANSI.iter())
.find(|(_, n)| crate::textparse::glyph_name_to_char(n.as_bytes()) == Some(ch))
.map(|(_, n)| *n)
}
struct Builder(tiny_skia::PathBuilder);
impl ttf_parser::OutlineBuilder for Builder {
fn move_to(&mut self, x: f32, y: f32) {
self.0.move_to(x, y);
}
fn line_to(&mut self, x: f32, y: f32) {
self.0.line_to(x, y);
}
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
self.0.quad_to(x1, y1, x, y);
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
self.0.cubic_to(x1, y1, x2, y2, x, y);
}
fn close(&mut self) {
self.0.close();
}
}
fn outline_face(face: &ttf_parser::Face, gid: GlyphId) -> Option<GlyphPath> {
let mut b = Builder(tiny_skia::PathBuilder::new());
let bbox = face.outline_glyph(gid, &mut b);
let advance = face
.glyph_hor_advance(gid)
.map(f64::from)
.or_else(|| {
face.tables()
.cff
.as_ref()
.and_then(|c| c.glyph_width(gid))
.map(f64::from)
})
.unwrap_or(0.0);
if bbox.is_none() && gid.0 >= face.number_of_glyphs() {
return None;
}
Some(GlyphPath {
path: b.0.finish(),
advance,
})
}
fn outline_cff(table: &ttf_parser::cff::Table, gid: GlyphId) -> Option<GlyphPath> {
let mut b = Builder(tiny_skia::PathBuilder::new());
let ok = table.outline(gid, &mut b).is_ok();
let advance = table.glyph_width(gid).map(f64::from).unwrap_or(0.0);
if !ok && gid.0 >= table.number_of_glyphs() {
return None;
}
Some(GlyphPath {
path: b.0.finish(),
advance,
})
}
fn cff_matrix(m: ttf_parser::cff::Matrix) -> Mat {
let mat = Mat::new(
f64::from(m.sx),
f64::from(m.ky),
f64::from(m.kx),
f64::from(m.sy),
f64::from(m.tx),
f64::from(m.ty),
);
if mat.is_finite() && mat.det().abs() > 1e-12 {
mat
} else {
Mat::scale(0.001, 0.001)
}
}
fn sfnt_program(mut data: Vec<u8>) -> Option<Program> {
dotsection::strip_sfnt(&mut data);
let face = ttf_parser::Face::parse(&data, 0).ok()?;
let tables = face.tables();
let has_glyf = tables.glyf.is_some();
let has_cmap = tables.cmap.is_some();
let (matrix, cid_to_gid, names) = match &tables.cff {
Some(cff) if !has_glyf => (cff_matrix(cff.matrix()), cid_map(cff), cff_names(cff)),
_ => {
let upem = f64::from(face.units_per_em()).max(1.0);
(Mat::scale(1.0 / upem, 1.0 / upem), None, HashMap::new())
}
};
Some(Program::Sfnt {
data,
matrix,
cid_to_gid,
has_cmap,
has_glyf,
names,
})
}
fn cff_program(mut data: Vec<u8>) -> Option<Program> {
dotsection::strip(&mut data);
let table = ttf_parser::cff::Table::parse(&data)?;
let matrix = cff_matrix(table.matrix());
let cid_to_gid = cid_map(&table);
let names = cff_names(&table);
Some(Program::Cff {
data,
matrix,
cid_to_gid,
names,
})
}
fn cff_names(table: &ttf_parser::cff::Table) -> HashMap<String, u16> {
let mut map = HashMap::new();
if table.glyph_cid(GlyphId(0)).is_some() {
return map;
}
for g in 0..table.number_of_glyphs() {
if let Some(n) = table.glyph_name(GlyphId(g)) {
map.entry(n.to_string()).or_insert(g);
}
}
map
}
fn cid_map(table: &ttf_parser::cff::Table) -> Option<HashMap<u16, u16>> {
let n = table.number_of_glyphs();
table.glyph_cid(GlyphId(0))?;
let mut map = HashMap::with_capacity(n as usize);
for g in 0..n {
if let Some(cid) = table.glyph_cid(GlyphId(g)) {
map.entry(cid).or_insert(g);
}
}
Some(map)
}
#[derive(Default)]
pub struct FontCache {
by_id: HashMap<ObjectId, Rc<LoadedFont>>,
by_repr: HashMap<String, Rc<LoadedFont>>,
}
impl FontCache {
pub fn get(&mut self, doc: &Document, obj: &Object) -> Option<Rc<LoadedFont>> {
match obj {
Object::Reference(id) => {
if let Some(f) = self.by_id.get(id) {
return Some(f.clone());
}
let d = as_dict(doc, obj)?;
let f = Rc::new(LoadedFont::load(doc, d));
self.by_id.insert(*id, f.clone());
Some(f)
}
Object::Dictionary(d) => {
let key = format!("{d:?}");
if let Some(f) = self.by_repr.get(&key) {
return Some(f.clone());
}
let f = Rc::new(LoadedFont::load(doc, d));
self.by_repr.insert(key, f.clone());
Some(f)
}
_ => None,
}
}
}
#[allow(dead_code)]
fn _unused(_: &Dictionary) -> Option<&[u8]> {
None.and_then(name)
}