mod blob;
mod cache;
mod format;
mod instance;
mod source;
mod tables;
pub use blob::FontBlob;
pub use format::FontFormat;
pub use instance::{
FontFeatureVariations, FontInstance, FontInstanceBuilder, FontVariation, NormalizedCoord,
};
pub use source::FontSource;
pub use tables::{FontTableFunction, FontTables};
#[doc(hidden)]
#[rust_analyzer::completions(hidden_from_completion)]
pub mod interop;
use super::metrics::{empty_glyph_metrics, GlobalMetrics, GlyphMetrics, RawGlyphMetrics};
use super::once::Once;
use crate::tables::{glyf::Glyf, gvar::Gvar, hvar::Hvar, loca::Loca};
use crate::{
ps::{cff::CffFontRef, type1::Type1Font},
ReadError,
};
use alloc::{boxed::Box, sync::Arc};
use cache::{GlyfLoca, GvarTable, HvarTable, TableCache};
use core::any::Any;
#[derive(Clone)]
pub struct Font(Arc<FontRepr>);
impl Font {
pub fn new(source: impl Into<FontSource>, index: u32) -> Result<Self, ReadError> {
let source = source.into();
let kind = if let Ok(tables) = FontTables::new(source.clone(), index) {
Some(FontKindRepr::Sfnt(Arc::new(tables), index))
} else if let FontSource::Blob(blob) = &source {
match FontFormat::new(blob) {
Some(FontFormat::Type1) => Type1Font::new(blob)
.ok()
.map(|font| FontKindRepr::Type1(Box::new(font))),
_ => None,
}
} else {
None
};
let kind = kind.ok_or(ReadError::MalformedData("Data isn't a font"))?;
let repr = FontRepr {
source,
kind,
shaping_data: Once::new(),
global_metrics: Once::new(),
h_metrics: Once::new(),
glyf_loca: Once::new(),
hvar: Once::new(),
gvar: Once::new(),
};
Ok(Self(Arc::new(repr)))
}
pub fn source(&self) -> &FontSource {
&self.0.source
}
pub fn kind(&self) -> FontKind<'_> {
match &self.0.kind {
FontKindRepr::Sfnt(tables, index) => FontKind::Sfnt(tables, *index),
FontKindRepr::Type1(font) => FontKind::Type1(font),
}
}
#[inline]
pub fn global_metrics(&self) -> &GlobalMetrics {
self.0.global_metrics.get_or_init(|| match self.kind() {
FontKind::Type1(font) => GlobalMetrics::from_type1(font),
_ => GlobalMetrics::from_sfnt(&self.tables(), &[]),
})
}
#[inline]
pub fn num_glyphs(&self) -> u32 {
self.global_metrics().num_glyphs
}
#[inline]
pub fn units_per_em(&self) -> u16 {
self.global_metrics().units_per_em
}
#[inline]
pub fn glyph_metrics(&self) -> GlyphMetrics<'_> {
GlyphMetrics::new(self, &[])
}
pub(crate) fn tables_arc(&self) -> Option<&Arc<FontTables>> {
match &self.0.kind {
FontKindRepr::Sfnt(tables, _) => Some(tables),
_ => None,
}
}
#[inline]
pub(crate) fn h_metrics(&self) -> &RawGlyphMetrics<'_> {
let Some(tables) = self.tables_arc() else {
return empty_glyph_metrics();
};
self.0
.h_metrics
.get_or_init(|| {
TableCache::read(tables.clone(), |tables| RawGlyphMetrics::from_hmtx(&tables))
})
.get()
}
#[inline]
pub(crate) fn glyf_loca(&self) -> Option<&(Glyf<'_>, Loca<'_>)> {
let tables = self.tables_arc()?;
self.0
.glyf_loca
.get_or_init(|| TableCache::read(tables.clone(), |tables| GlyfLoca::read(&tables)))
.get()
.0
.as_ref()
}
#[inline]
pub(crate) fn hvar(&self) -> Option<&Hvar<'_>> {
let tables = self.tables_arc()?;
self.0
.hvar
.get_or_init(|| TableCache::read(tables.clone(), |tables| HvarTable::read(&tables)))
.get()
.0
.as_ref()
}
#[inline]
pub(crate) fn gvar(&self) -> Option<&Gvar<'_>> {
let tables = self.tables_arc()?;
self.0
.gvar
.get_or_init(|| TableCache::read(tables.clone(), |tables| GvarTable::read(&tables)))
.get()
.0
.as_ref()
}
#[inline]
pub fn default_instance(&self) -> FontInstance {
FontInstance::from(self)
}
pub fn tables(&self) -> &FontTables {
if let FontKindRepr::Sfnt(tables, _) = &self.0.kind {
tables
} else {
&tables::EMPTY_FONT_TABLES
}
}
}
struct FontRepr {
source: FontSource,
kind: FontKindRepr,
shaping_data: Once<Box<dyn Any + Send + Sync>>,
global_metrics: Once<GlobalMetrics>,
h_metrics: Once<TableCache<RawGlyphMetrics<'static>>>,
glyf_loca: Once<TableCache<GlyfLoca<'static>>>,
hvar: Once<TableCache<HvarTable<'static>>>,
gvar: Once<TableCache<GvarTable<'static>>>,
}
#[derive(Clone)]
pub enum FontKind<'a> {
Sfnt(&'a FontTables, u32),
Type1(&'a Type1Font),
Cff(CffFontRef<'a>, u32),
}
enum FontKindRepr {
Sfnt(Arc<FontTables>, u32),
Type1(Box<Type1Font>),
}