use std::any::Any;
use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
mod android;
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod coretext;
mod files;
#[cfg(not(target_arch = "wasm32"))]
mod scan;
mod sfnt;
#[cfg(not(target_arch = "wasm32"))]
pub use android::Android;
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub use coretext::CoreText;
#[cfg(not(target_arch = "wasm32"))]
pub use scan::Scan;
pub use sfnt::assemble;
pub use skrifa::raw::types::Tag;
pub type FontData = Arc<dyn AsRef<[u8]> + Send + Sync>;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Slant {
#[default]
Upright,
Italic,
Oblique,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Style {
pub weight: u16,
pub width: f32,
pub slant: Slant,
}
pub const NORMAL_WIDTH: f32 = 100.0;
impl Default for Style {
fn default() -> Self {
Style {
weight: 400,
width: NORMAL_WIDTH,
slant: Slant::Upright,
}
}
}
impl Style {
fn distance(self, requested: Style) -> (u32, u8, u16) {
let width = ((self.width - requested.width).abs() * 10.0) as u32;
let slant = match (requested.slant, self.slant) {
(a, b) if a == b => 0,
(Slant::Italic, Slant::Oblique) | (Slant::Oblique, Slant::Italic) => 1,
_ => 2,
};
(width, slant, self.weight.abs_diff(requested.weight))
}
}
#[derive(Clone)]
pub struct Typeface {
data: FontData,
index: u32,
family: String,
style: Style,
}
impl Typeface {
pub fn new(data: FontData, index: u32, family: impl Into<String>, style: Style) -> Typeface {
Typeface {
data,
index,
family: family.into(),
style,
}
}
pub fn data(&self) -> &[u8] {
(*self.data).as_ref()
}
pub fn shared_data(&self) -> FontData {
Arc::clone(&self.data)
}
pub fn index(&self) -> u32 {
self.index
}
pub fn family(&self) -> &str {
&self.family
}
pub fn style(&self) -> Style {
self.style
}
pub fn covers(&self, character: char) -> bool {
files::covers(self.data(), self.index, character)
}
pub fn tables(&self) -> Vec<(Tag, &[u8])> {
sfnt::served_tables(self.data(), self.index).unwrap_or_default()
}
}
impl std::fmt::Debug for Typeface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Typeface")
.field("family", &self.family)
.field("index", &self.index)
.field("style", &self.style)
.finish_non_exhaustive()
}
}
pub trait FontManager: Send {
fn family(&mut self, family: &str) -> Vec<Typeface>;
fn match_family_style(&mut self, family: &str, style: Style) -> Option<Typeface> {
nearest(self.family(family), style)
}
fn match_character(
&mut self,
family: Option<&str>,
style: Style,
locales: &[&str],
character: char,
) -> Option<Typeface>;
fn system_font(&mut self, size: f32, style: Style) -> Option<Typeface>;
fn families(&self) -> Vec<String>;
fn face_count(&self) -> usize;
fn watch(&mut self, on_change: Box<dyn Fn() + Send + Sync>) -> Option<Watch>;
}
impl<M: FontManager + ?Sized> FontManager for Box<M> {
fn family(&mut self, family: &str) -> Vec<Typeface> {
(**self).family(family)
}
fn match_family_style(&mut self, family: &str, style: Style) -> Option<Typeface> {
(**self).match_family_style(family, style)
}
fn match_character(
&mut self,
family: Option<&str>,
style: Style,
locales: &[&str],
character: char,
) -> Option<Typeface> {
(**self).match_character(family, style, locales, character)
}
fn system_font(&mut self, size: f32, style: Style) -> Option<Typeface> {
(**self).system_font(size, style)
}
fn families(&self) -> Vec<String> {
(**self).families()
}
fn face_count(&self) -> usize {
(**self).face_count()
}
fn watch(&mut self, on_change: Box<dyn Fn() + Send + Sync>) -> Option<Watch> {
(**self).watch(on_change)
}
}
pub struct Watch {
_subscription: Box<dyn Any + Send>,
}
impl Watch {
pub fn new(subscription: impl Any + Send) -> Watch {
Watch {
_subscription: Box::new(subscription),
}
}
}
pub fn nearest(faces: Vec<Typeface>, style: Style) -> Option<Typeface> {
faces
.into_iter()
.min_by_key(|face| face.style().distance(style))
}
#[cfg(not(target_arch = "wasm32"))]
pub fn platform() -> Box<dyn FontManager> {
#[cfg(any(target_os = "macos", target_os = "ios"))]
{
Box::new(CoreText::new())
}
#[cfg(target_os = "android")]
{
Box::new(Android::new())
}
#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "android")))]
{
Box::new(Scan::load())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn face(family: &str, weight: u16, width: f32, slant: Slant) -> Typeface {
Typeface::new(
Arc::new(Vec::new()),
0,
family,
Style {
weight,
width,
slant,
},
)
}
#[test]
fn nearest_prefers_width_then_slant_then_weight() {
let faces = vec![
face("a", 400, 100.0, Slant::Italic),
face("b", 700, 100.0, Slant::Upright),
face("c", 400, 75.0, Slant::Upright),
];
let regular = Style::default();
assert_eq!(nearest(faces.clone(), regular).unwrap().family(), "b");
let italic = Style {
slant: Slant::Italic,
..regular
};
assert_eq!(nearest(faces.clone(), italic).unwrap().family(), "a");
let condensed = Style {
width: 75.0,
..regular
};
assert_eq!(nearest(faces, condensed).unwrap().family(), "c");
}
#[test]
fn oblique_stands_in_for_italic_before_upright() {
let faces = vec![
face("upright", 400, 100.0, Slant::Upright),
face("oblique", 400, 100.0, Slant::Oblique),
];
let italic = Style {
slant: Slant::Italic,
..Style::default()
};
assert_eq!(nearest(faces, italic).unwrap().family(), "oblique");
}
}