use pdfrum_common::kurbo::{BezPath, Rect};
use pdfrum_common::{Diagnostics, Limits};
use pdfrum_object::{Dict, ObjRef, Resolve};
use smallvec::SmallVec;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use crate::cid::{self, CidTransform, Type0Font};
use crate::encoding;
use crate::glyphs::{self, GlyphSource};
use crate::ids::{CharCode, Cid, FontId, Gid};
use crate::names;
use crate::simple::{self, SimpleFont};
use crate::subst::{self, StandardFont, SubstFont, SubstitutionOptions};
use crate::type3::{self, Type3Font};
#[derive(Debug)]
pub enum Font {
Simple(Box<SimpleFont>),
Type0(Box<Type0Font>),
Type3(Box<Type3Font>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct CharItem {
pub code: CharCode,
pub cid: Option<Cid>,
pub gid: Option<Gid>,
pub unicode: SmallVec<[char; 2]>,
pub width: f32,
pub vertical_glyph: bool,
}
impl Font {
pub fn decode<'a>(&'a self, s: &'a [u8]) -> impl Iterator<Item = CharItem> + 'a {
Decoder {
font: self,
bytes: s,
offset: 0,
}
}
#[must_use]
pub fn glyph_path(&self, gid: Gid) -> Option<BezPath> {
self.glyphs().outline(gid, glyphs::GlyphParams::default())
}
#[must_use]
pub fn hinted_glyph_path(&self, gid: Gid) -> Option<BezPath> {
self.glyphs().hinted_outline(gid)
}
#[must_use]
pub fn is_vertical(&self) -> bool {
match self {
Self::Type0(f) => f.cmap.is_vertical(),
Self::Simple(_) | Self::Type3(_) => false,
}
}
#[must_use]
pub fn is_embedded(&self) -> bool {
match self {
Self::Simple(f) => f.embedded,
Self::Type0(f) => f.embedded,
Self::Type3(_) => false,
}
}
#[must_use]
pub fn is_unicode_compatible(&self) -> bool {
match self {
Self::Simple(f) => {
f.to_unicode.is_some() || f.encoding_kind != encoding::FontEncoding::Builtin
}
Self::Type0(f) => f.is_unicode_compatible(),
Self::Type3(f) => f.to_unicode.is_some(),
}
}
#[must_use]
pub fn font_bbox(&self) -> Rect {
match self {
Self::Simple(f) => f.descriptor.font_bbox,
Self::Type0(f) => f.descriptor.font_bbox,
Self::Type3(f) => f.font_bbox,
}
}
#[must_use]
pub fn ascent(&self) -> f32 {
match self {
Self::Simple(f) => f.descriptor.ascent,
Self::Type0(f) => f.descriptor.ascent,
Self::Type3(_) => 0.0,
}
}
#[must_use]
pub fn descent(&self) -> f32 {
match self {
Self::Simple(f) => f.descriptor.descent,
Self::Type0(f) => f.descriptor.descent,
Self::Type3(_) => 0.0,
}
}
#[must_use]
pub fn type3(&self) -> Option<&Type3Font> {
match self {
Self::Type3(f) => Some(f),
Self::Simple(_) | Self::Type0(_) => None,
}
}
#[must_use]
pub fn base_font_name(&self) -> &[u8] {
match self {
Self::Simple(f) => &f.base_font_name,
Self::Type0(f) => &f.base_font_name,
Self::Type3(_) => b"",
}
}
#[must_use]
pub fn id(&self) -> FontId {
match self {
Self::Simple(f) => f.id,
Self::Type0(f) => f.id,
Self::Type3(f) => f.id,
}
}
#[must_use]
pub fn subst(&self) -> Option<&SubstFont> {
match self {
Self::Simple(f) => f.subst.as_ref(),
Self::Type0(f) => f.subst.as_ref(),
Self::Type3(_) => None,
}
}
#[must_use]
pub fn char_width(&self, code: CharCode) -> f32 {
match self {
Self::Simple(f) => f.char_width(code),
Self::Type0(f) => f.char_width(code),
Self::Type3(f) => f.char_width(code),
}
}
#[must_use]
pub(crate) fn has_declared_widths(&self) -> bool {
match self {
Self::Simple(f) => f.has_font_widths(),
Self::Type0(_) | Self::Type3(_) => true,
}
}
#[must_use]
pub fn applies_glyph_spacing(&self) -> bool {
subst::applies_glyph_spacing(&subst::GlyphSpacingGate {
vertical: self.is_vertical(),
embedded: self.is_embedded(),
declared_widths: self.has_declared_widths(),
base_font_name: self.base_font_name(),
subst: self.subst(),
})
}
#[must_use]
pub fn glyph_advance(&self, gid: Gid) -> i32 {
self.glyphs().advance(gid, glyphs::GlyphParams::default())
}
#[must_use]
pub fn char_bbox(&self, code: CharCode) -> Rect {
match self {
Self::Simple(f) => f.char_bbox(code),
Self::Type0(f) => f.char_bbox(code),
Self::Type3(_) => Rect::ZERO,
}
}
#[must_use]
pub fn japan1_transform(&self, code: CharCode) -> Option<CidTransform> {
match self {
Self::Type0(f) => f.japan1_transform(code),
Self::Simple(_) | Self::Type3(_) => None,
}
}
#[must_use]
pub fn string_width(&self, bytes: &[u8]) -> f32 {
self.decode(bytes)
.map(|item| self.char_width(item.code))
.sum()
}
#[must_use]
pub fn type_ascent(&self) -> i32 {
truncate(self.ascent())
}
#[must_use]
pub fn type_descent(&self) -> i32 {
truncate(self.descent())
}
#[must_use]
pub fn cid_from_charcode(&self, code: CharCode) -> Option<Cid> {
match self {
Self::Type0(f) => Some(f.cid_from_charcode(code)),
Self::Simple(_) | Self::Type3(_) => None,
}
}
#[must_use]
pub fn vert_origin(&self, code: CharCode) -> Option<(f32, f32)> {
match self {
Self::Type0(f) => Some(f.vert_origin(code)),
Self::Simple(_) | Self::Type3(_) => None,
}
}
#[must_use]
pub fn vert_width(&self, code: CharCode) -> Option<f32> {
match self {
Self::Type0(f) => Some(f.vert_width(code)),
Self::Simple(_) | Self::Type3(_) => None,
}
}
#[must_use]
pub fn unicode_from_charcode(&self, code: CharCode) -> SmallVec<[char; 2]> {
match self {
Self::Simple(f) => f.unicode_from_charcode(code),
Self::Type0(f) => f.unicode_from_charcode(code),
Self::Type3(f) => f.unicode_from_charcode(code),
}
}
#[must_use]
pub fn char_code_from_unicode(&self, unicode: char) -> Option<CharCode> {
match self {
Self::Simple(f) => f.char_code_from_unicode(unicode),
Self::Type0(f) => {
let code = f.charcode_from_unicode(unicode);
(code.0 != 0).then_some(code)
}
Self::Type3(f) => f.char_code_from_unicode(unicode),
}
}
pub fn append_char(&self, out: &mut Vec<u8>, code: CharCode) {
match self {
Self::Type0(f) => f.cmap.append_char(out, code),
Self::Simple(_) | Self::Type3(_) => out.push((code.0 & 0xff) as u8),
}
}
#[must_use]
pub fn load_standard(which: StandardFont, cache: &FontCache) -> Self {
let dict = Dict::from_pairs([
(
names::TYPE.clone(),
pdfrum_object::Object::Name(names::FONT.clone()),
),
(
names::SUBTYPE.clone(),
pdfrum_object::Object::Name(pdfrum_object::Name::from("Type1")),
),
(
names::BASE_FONT.clone(),
pdfrum_object::Object::Name(pdfrum_object::Name::from(subst::canonical_font_name(
which,
))),
),
(
names::ENCODING.clone(),
pdfrum_object::Object::Name(names::WIN_ANSI_ENCODING.clone()),
),
]);
Self::Simple(Box::new(simple::load(
&dict,
&pdfrum_object::NoResolve,
cache,
&SubstitutionOptions::default(),
&Limits::default(),
&mut Diagnostics::with_limit(0),
false,
)))
}
pub(crate) fn glyphs(&self) -> &GlyphSource {
match self {
Self::Simple(f) => &f.glyphs,
Self::Type0(f) => &f.glyphs,
Self::Type3(_) => &GlyphSource::None,
}
}
}
struct Decoder<'a> {
font: &'a Font,
bytes: &'a [u8],
offset: usize,
}
impl Iterator for Decoder<'_> {
type Item = CharItem;
fn next(&mut self) -> Option<CharItem> {
if self.offset >= self.bytes.len() {
return None;
}
Some(match self.font {
Font::Simple(f) => {
let byte = *self.bytes.get(self.offset)?;
self.offset += 1;
f.char_item(CharCode(u32::from(byte)))
}
Font::Type3(f) => {
let byte = *self.bytes.get(self.offset)?;
self.offset += 1;
f.char_item(CharCode(u32::from(byte)))
}
Font::Type0(f) => {
let before = self.offset;
let code = f.cmap.next_char(self.bytes, &mut self.offset);
if self.offset <= before {
return None;
}
f.char_item(code)
}
})
}
}
fn truncate(value: f32) -> i32 {
#[expect(
clippy::cast_possible_truncation,
reason = "the saturating cast is the point: a metric outside i32 is nonsense"
)]
let truncated = value.trunc() as i32;
truncated
}
#[derive(Debug, Default)]
pub struct FontCache {
next_id: AtomicU64,
loaded: RwLock<HashMap<ObjRef, Option<Arc<Font>>>>,
}
impl FontCache {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn get_or_load<F>(&self, reference: ObjRef, load: F) -> Option<Arc<Font>>
where
F: FnOnce() -> Option<Font>,
{
if let Ok(map) = self.loaded.read()
&& let Some(hit) = map.get(&reference)
{
return hit.clone();
}
let font = load().map(Arc::new);
match self.loaded.write() {
Ok(mut map) => map.entry(reference).or_insert(font).clone(),
Err(_) => font,
}
}
pub(crate) fn next_id(&self) -> FontId {
FontId(self.next_id.fetch_add(1, Ordering::Relaxed))
}
}
#[must_use]
pub fn load(
dict: &Dict,
r: &impl Resolve,
cache: &FontCache,
limits: &Limits,
diags: &mut Diagnostics,
) -> Option<Font> {
load_with_options(
dict,
r,
cache,
&SubstitutionOptions::default(),
limits,
diags,
)
}
#[must_use]
pub fn load_with_options(
dict: &Dict,
r: &impl Resolve,
cache: &FontCache,
opts: &SubstitutionOptions,
limits: &Limits,
diags: &mut Diagnostics,
) -> Option<Font> {
let subtype = dict.name(names::SUBTYPE).map(|n| n.as_bytes().to_vec());
match subtype.as_deref() {
Some(b"Type3") => Some(Font::Type3(Box::new(type3::load(
dict, r, cache, limits, diags,
)))),
Some(b"Type0") => cid::load(dict, r, cache, opts, limits, diags)
.ok()
.map(|f| Font::Type0(Box::new(f))),
Some(b"TrueType") if wants_chinese_cid_rescue(dict, r) => {
match cid::load_gb2312(dict, r, cache, opts, limits, diags) {
Ok(f) => Some(Font::Type0(Box::new(f))),
Err(_) => Some(Font::Simple(Box::new(simple::load(
dict, r, cache, opts, limits, diags, true,
)))),
}
}
Some(b"TrueType") => Some(Font::Simple(Box::new(simple::load(
dict, r, cache, opts, limits, diags, true,
)))),
_ => Some(Font::Simple(Box::new(simple::load(
dict, r, cache, opts, limits, diags, false,
)))),
}
}
const CHINESE_FONT_NAMES: [[u8; 4]; 5] = [
[0xcb, 0xce, 0xcc, 0xe5],
[0xbf, 0xac, 0xcc, 0xe5],
[0xba, 0xda, 0xcc, 0xe5],
[0xb7, 0xc2, 0xcb, 0xce],
[0xd0, 0xc2, 0xcb, 0xce],
];
pub(crate) fn wants_chinese_cid_rescue(dict: &Dict, r: &impl Resolve) -> bool {
let Some(base) = dict.name(names::BASE_FONT) else {
return false;
};
let Some(prefix) = base.as_bytes().get(..4) else {
return false;
};
if !CHINESE_FONT_NAMES.iter().any(|n| n == prefix) {
return false;
}
match dict.dict(names::FONT_DESCRIPTOR, r) {
None => true,
Some(desc) => desc.raw(names::FONT_FILE2).is_none(),
}
}