use ttf_parser::{Face, GlyphId};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FaceKind {
SansRegular,
SansBold,
SansOblique,
SansBoldOblique,
Mono,
}
impl FaceKind {
pub fn index(self) -> usize {
match self {
FaceKind::SansRegular => 0,
FaceKind::SansBold => 1,
FaceKind::SansOblique => 2,
FaceKind::SansBoldOblique => 3,
FaceKind::Mono => 4,
}
}
pub fn ps_name(self) -> &'static str {
match self {
FaceKind::SansRegular => "DejaVuSans",
FaceKind::SansBold => "DejaVuSans-Bold",
FaceKind::SansOblique => "DejaVuSans-Oblique",
FaceKind::SansBoldOblique => "DejaVuSans-BoldOblique",
FaceKind::Mono => "DejaVuSansMono",
}
}
}
pub const FACE_COUNT: usize = 5;
pub const CJK_FACE: usize = FACE_COUNT;
pub static FONTS: [&[u8]; FACE_COUNT] = [
include_bytes!("../assets/fonts/DejaVuSans.ttf"),
include_bytes!("../assets/fonts/DejaVuSans-Bold.ttf"),
include_bytes!("../assets/fonts/DejaVuSans-Oblique.ttf"),
include_bytes!("../assets/fonts/DejaVuSans-BoldOblique.ttf"),
include_bytes!("../assets/fonts/DejaVuSansMono.ttf"),
];
pub struct PdfFonts {
pub metrics: Vec<FaceMetrics>,
pub bytes: Vec<Vec<u8>>,
pub names: Vec<String>,
}
impl PdfFonts {
pub fn load(cjk_path: Option<&std::path::Path>) -> anyhow::Result<Self> {
let mut bytes: Vec<Vec<u8>> = FONTS.iter().map(|b| b.to_vec()).collect();
let mut names: Vec<String> = (0..FACE_COUNT)
.map(|i| {
let kind = match i {
0 => FaceKind::SansRegular,
1 => FaceKind::SansBold,
2 => FaceKind::SansOblique,
3 => FaceKind::SansBoldOblique,
_ => FaceKind::Mono,
};
kind.ps_name().to_string()
})
.collect();
if let Some(path) = cjk_path {
let raw = std::fs::read(path)
.map_err(|e| anyhow::anyhow!("read CJK font {}: {}", path.display(), e))?;
bytes.push(raw);
names.push("CJKFallback".to_string());
}
let mut metrics = Vec::with_capacity(bytes.len());
for b in &bytes {
metrics.push(FaceMetrics::parse(b)?);
}
Ok(PdfFonts {
metrics,
bytes,
names,
})
}
pub fn has_cjk(&self) -> bool {
self.metrics.len() > FACE_COUNT
}
pub fn face_count(&self) -> usize {
self.metrics.len()
}
}
pub struct FaceMetrics {
pub units_per_em: u16,
pub ascent: i16,
pub descent: i16,
pub bbox: (i16, i16, i16, i16),
pub italic_angle: f32,
pub cap_height: i16,
pub num_glyphs: u16,
pub widths: Vec<u16>,
cmap_cache: std::sync::Mutex<std::collections::HashMap<u32, u16>>,
}
impl FaceMetrics {
pub fn parse(ttf: &[u8]) -> anyhow::Result<Self> {
let face = Face::parse(ttf, 0).map_err(|e| anyhow::anyhow!("parse TTF: {:?}", e))?;
let units_per_em = face.units_per_em();
let ascent = face.ascender();
let descent = face.descender();
let bbox = face.global_bounding_box();
let bbox = (bbox.x_min, bbox.y_min, bbox.x_max, bbox.y_max);
let italic_angle = face.italic_angle().unwrap_or(0.0);
let cap_height = face.capital_height().unwrap_or(ascent);
let num_glyphs = face.number_of_glyphs();
let mut widths = vec![0u16; num_glyphs as usize];
for gid in 0..num_glyphs {
widths[gid as usize] = face.glyph_hor_advance(GlyphId(gid)).unwrap_or(0);
}
Ok(FaceMetrics {
units_per_em,
ascent,
descent,
bbox,
italic_angle,
cap_height,
num_glyphs,
widths,
cmap_cache: std::sync::Mutex::new(std::collections::HashMap::new()),
})
}
pub fn glyph_for_char(&self, ttf: &[u8], c: char) -> Option<u16> {
let cp = c as u32;
let mut cache = self.cmap_cache.lock().unwrap();
if let Some(&gid) = cache.get(&cp) {
return if gid == u16::MAX { None } else { Some(gid) };
}
let face = Face::parse(ttf, 0).ok()?;
let gid = face.glyph_index(c).map(|g| g.0);
cache.insert(cp, gid.unwrap_or(u16::MAX));
gid
}
pub fn glyph_width(&self, gid: u16) -> u16 {
*self.widths.get(gid as usize).unwrap_or(&0)
}
pub fn cid_to_unicode(&self, ttf: &[u8]) -> Vec<(u16, char)> {
let Some(face) = Face::parse(ttf, 0).ok() else {
return Vec::new();
};
let mut by_gid: std::collections::BTreeMap<u16, char> = std::collections::BTreeMap::new();
for cp in (0x21u32..=0xFFFFu32).chain(0x1D400u32..=0x1D7FFu32) {
let Some(c) = char::from_u32(cp) else {
continue;
};
if let Some(gid) = face.glyph_index(c) {
if gid.0 == 0 {
continue;
}
by_gid.entry(gid.0).or_insert(c);
}
}
by_gid.into_iter().collect()
}
pub fn glyph_width_pt(&self, gid: u16, size_pt: f32) -> f32 {
(self.glyph_width(gid) as f32 / self.units_per_em as f32) * size_pt
}
}