use std::sync::OnceLock;
use crate::glyphs::{Charmap, GlyphSource, SynthGlyph};
use crate::ids::{CharCode, FontFlags, FontId, Gid};
use crate::subst::{self, CodePage, FontRequest, SubstFont, SubstitutionOptions};
use pdfrum_common::Diagnostics;
use pdfrum_common::kurbo::BezPath;
#[derive(Debug)]
pub struct GlyphFallback {
pub(crate) glyphs: GlyphSource,
pub(crate) subst: SubstFont,
pub(crate) id: FontId,
}
impl GlyphFallback {
#[must_use]
pub fn id(&self) -> FontId {
self.id
}
#[must_use]
pub fn gid(&self, unicode: &[char], code: CharCode) -> Option<Gid> {
let u = unicode.first().copied().map_or(code.0, u32::from);
let gid = self.glyphs.char_index(Charmap::Unicode, u);
(gid != 0).then_some(Gid(gid))
}
#[must_use]
pub fn hinted_path(&self, gid: Gid) -> Option<BezPath> {
self.glyphs.hinted_outline(gid)
}
#[must_use]
pub fn advance(&self, gid: Gid) -> i32 {
self.glyphs
.advance(gid, crate::glyphs::GlyphParams::default())
}
#[must_use]
pub fn render_synth(&self, xx: i32, xy: i32, vertical: bool) -> Option<SynthGlyph> {
let level = self.subst.embolden_level_for_render(false, xx, xy)?;
Some(SynthGlyph {
skew: self.subst.effective_skew(false),
vertical,
embolden: f64::from(level) / 64.0,
})
}
}
#[must_use]
pub(crate) fn should_use_own_glyph(
embedded: bool,
is_truetype: bool,
has_to_unicode: bool,
gid: Option<Gid>,
) -> bool {
let Some(gid) = gid else {
return false;
};
if embedded {
return true;
}
if !is_truetype {
return true;
}
gid != Gid(0) || has_to_unicode
}
pub(crate) fn ensure(
slot: &OnceLock<Option<GlyphFallback>>,
host_id: FontId,
is_truetype: bool,
flags: FontFlags,
stem_v: i32,
italic_angle: i32,
vertical: bool,
) -> Option<&GlyphFallback> {
slot.get_or_init(|| {
let weight = i32::try_from(i64::from(stem_v).saturating_mul(5)).unwrap_or(400);
let request = FontRequest {
name: b"Arial".to_vec(),
is_truetype,
flags,
weight,
italic_angle,
code_page: CodePage::DefAnsi,
vertical,
};
let resolved = subst::resolve_with_options(
&request,
&SubstitutionOptions::default(),
&mut Diagnostics::with_limit(0),
);
if !resolved.glyphs.is_some() {
return None;
}
Some(GlyphFallback {
glyphs: resolved.glyphs,
subst: resolved.subst,
id: FontId(host_id.0 | (1 << 63)),
})
})
.as_ref()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn missing_glyph_always_falls_back() {
assert!(!should_use_own_glyph(true, true, true, None));
assert!(!should_use_own_glyph(false, false, false, None));
}
#[test]
fn embedded_notdef_is_kept() {
assert!(should_use_own_glyph(true, true, false, Some(Gid(0))));
}
#[test]
fn non_embedded_truetype_notdef_without_tounicode_falls_back() {
assert!(!should_use_own_glyph(false, true, false, Some(Gid(0))));
}
#[test]
fn tounicode_keeps_a_truetype_notdef() {
assert!(should_use_own_glyph(false, true, true, Some(Gid(0))));
}
#[test]
fn type1_notdef_is_kept() {
assert!(should_use_own_glyph(false, false, false, Some(Gid(0))));
}
#[test]
fn a_real_glyph_is_kept() {
assert!(should_use_own_glyph(false, true, false, Some(Gid(1))));
}
}