use std::collections::HashMap;
use std::sync::Arc;
use skrifa::metrics::Metrics;
use skrifa::prelude::Size;
use skrifa::MetadataProvider;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct FontId(pub u32);
pub type FontData = Arc<dyn AsRef<[u8]> + Send + Sync>;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FontAttrs {
pub weight: u16,
pub italic: bool,
pub stretch: f32,
}
pub const NORMAL_STRETCH: f32 = 100.0;
impl Default for FontAttrs {
fn default() -> Self {
Self {
weight: 400,
italic: false,
stretch: NORMAL_STRETCH,
}
}
}
struct SharedFace {
data: FontData,
face_index: u32,
charmap: HashMap<u32, u32>,
shaper_data: harfrust::ShaperData,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct FontUid(pub u64);
impl FontUid {
fn next() -> FontUid {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(1);
FontUid(COUNTER.fetch_add(1, Ordering::Relaxed))
}
}
pub struct Font {
uid: FontUid,
shared: Arc<SharedFace>,
variation_coordinates: Vec<([u8; 4], f32)>,
variation_location: skrifa::instance::Location,
shaper_instance: Option<harfrust::ShaperInstance>,
family: String,
aliases: Vec<String>,
attrs: FontAttrs,
units_per_em: f32,
ascent: f32,
descent: f32,
line_gap: f32,
bounds: Option<(f32, f32, f32, f32)>,
underline: Option<(f32, f32)>,
strikeout: Option<(f32, f32)>,
}
impl std::fmt::Debug for Font {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Font")
.field("uid", &self.uid)
.field("family", &self.family)
.field("attrs", &self.attrs)
.finish_non_exhaustive()
}
}
impl Font {
fn parse(
family: &str,
attrs: FontAttrs,
data: FontData,
face_index: u32,
variation_coordinates: Vec<([u8; 4], f32)>,
) -> Option<Self> {
let shared = SharedFace::parse(data, face_index)?;
Self::at_coordinates(shared, family, attrs, variation_coordinates)
}
fn at_coordinates(
shared: Arc<SharedFace>,
family: &str,
attrs: FontAttrs,
variation_coordinates: Vec<([u8; 4], f32)>,
) -> Option<Self> {
let bytes: &[u8] = (*shared.data).as_ref();
let font = skrifa::FontRef::from_index(bytes, shared.face_index).ok()?;
let variation_location = font.axes().location(
variation_coordinates
.iter()
.map(|(tag, value)| (skrifa::Tag::new(tag), *value)),
);
let metrics = Metrics::new(&font, Size::unscaled(), &variation_location);
let shaper_instance = (!variation_coordinates.is_empty()).then(|| {
let harf = harfrust::FontRef::from_index(bytes, shared.face_index).ok();
harf.map(|harf| {
harfrust::ShaperInstance::from_variations(
&harf,
variation_coordinates
.iter()
.map(|(tag, value)| harfrust::Variation {
tag: harfrust::Tag::new(tag),
value: *value,
}),
)
})
});
Some(Self {
uid: FontUid::next(),
family: family.to_owned(),
aliases: Vec::new(),
attrs,
units_per_em: metrics.units_per_em as f32,
ascent: metrics.ascent,
descent: metrics.descent,
line_gap: metrics.leading,
bounds: metrics.bounds.map(|b| (b.x_min, b.y_min, b.x_max, b.y_max)),
underline: metrics.underline.map(|d| (d.offset, d.thickness)),
strikeout: metrics.strikeout.map(|d| (d.offset, d.thickness)),
shared,
variation_coordinates,
variation_location,
shaper_instance: shaper_instance.flatten(),
})
}
pub fn data(&self) -> &[u8] {
(*self.shared.data).as_ref()
}
pub fn face_index(&self) -> u32 {
self.shared.face_index
}
pub fn variation_coordinates(&self) -> &[([u8; 4], f32)] {
&self.variation_coordinates
}
pub(crate) fn variation_location(&self) -> &skrifa::instance::Location {
&self.variation_location
}
pub(crate) fn shaper_instance(&self) -> Option<&harfrust::ShaperInstance> {
self.shaper_instance.as_ref()
}
pub fn uid(&self) -> FontUid {
self.uid
}
pub fn family(&self) -> &str {
&self.family
}
pub fn aliases(&self) -> &[String] {
&self.aliases
}
pub fn matches(&self, name: &str) -> bool {
self.family.eq_ignore_ascii_case(name)
|| self.aliases.iter().any(|a| a.eq_ignore_ascii_case(name))
}
pub fn add_alias(&mut self, name: &str) {
if !self.matches(name) {
self.aliases.push(name.to_owned());
}
}
pub fn attrs(&self) -> FontAttrs {
self.attrs
}
pub fn ascent_px(&self, size: f32) -> f32 {
self.ascent * size / self.units_per_em
}
pub fn descent_px(&self, size: f32) -> f32 {
-self.descent * size / self.units_per_em
}
pub fn line_height_px(&self, size: f32) -> f32 {
(self.ascent - self.descent + self.line_gap) * size / self.units_per_em
}
pub fn units_per_em(&self) -> f32 {
self.units_per_em
}
pub fn ink_box_px(&self, size: f32) -> Option<(f32, f32, f32, f32)> {
let k = size / self.units_per_em;
self.bounds
.map(|(x0, y0, x1, y1)| (x0 * k, y0 * k, x1 * k, y1 * k))
}
pub fn covers(&self, ch: char) -> bool {
self.shared.charmap.contains_key(&(ch as u32))
}
pub fn glyph_for(&self, ch: char) -> Option<u32> {
self.shared.charmap.get(&(ch as u32)).copied()
}
pub(crate) fn shaper_data(&self) -> &harfrust::ShaperData {
&self.shared.shaper_data
}
pub fn underline_px(&self, size: f32) -> (f32, f32) {
self.decoration_px(self.underline, size, -0.1, 0.05)
}
pub fn strikeout_px(&self, size: f32) -> (f32, f32) {
self.decoration_px(self.strikeout, size, 0.3, 0.05)
}
fn decoration_px(
&self,
metric: Option<(f32, f32)>,
size: f32,
default_offset: f32,
default_thickness: f32,
) -> (f32, f32) {
match metric {
Some((offset, thickness)) => (
offset * size / self.units_per_em,
(thickness * size / self.units_per_em).max(0.5),
),
None => (size * default_offset, (size * default_thickness).max(0.5)),
}
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct FontDemand {
pub families: Vec<String>,
pub codepoints: Vec<(char, FontAttrs)>,
}
impl FontDemand {
pub fn is_empty(&self) -> bool {
self.families.is_empty() && self.codepoints.is_empty()
}
pub(crate) fn add_family(&mut self, name: &str) {
if !self.families.iter().any(|f| f == name) {
self.families.push(name.to_owned());
}
}
pub(crate) fn add_codepoint(&mut self, ch: char, attrs: FontAttrs) {
if !self.codepoints.contains(&(ch, attrs)) {
self.codepoints.push((ch, attrs));
}
}
}
pub trait FontSource {
fn family(&mut self, name: &str) -> Vec<Font>;
fn face_for_codepoint(&mut self, codepoint: char, attrs: FontAttrs) -> Option<Font>;
}
#[derive(Default, Clone)]
pub struct FaceSet {
fonts: Vec<Arc<Font>>,
fallbacks: Vec<FontId>,
}
impl FaceSet {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, family: &str, bytes: Vec<u8>) -> Option<FontId> {
self.register_with(family, FontAttrs::default(), bytes)
}
pub fn register_with(
&mut self,
family: &str,
attrs: FontAttrs,
bytes: Vec<u8>,
) -> Option<FontId> {
let data = unwrapped(Arc::new(bytes))?;
let font = Font::parse(family, attrs, data, 0, Vec::new())?;
Some(self.add(font))
}
pub fn add(&mut self, font: Font) -> FontId {
self.fonts.push(Arc::new(font));
FontId(self.fonts.len() as u32 - 1)
}
pub fn with_font(&self, font: Font) -> (FaceSet, FontId) {
let mut next = self.clone();
let id = next.add(font);
(next, id)
}
pub fn with_fallbacks(&self, fallbacks: Vec<FontId>) -> FaceSet {
let mut next = self.clone();
next.fallbacks = fallbacks;
next
}
pub fn add_fallback(&mut self, id: FontId) {
self.fallbacks.push(id);
}
pub fn grown_by(&self, source: &mut dyn FontSource, demand: &FontDemand) -> Option<FaceSet> {
let mut next = self.clone();
let mut grew = false;
for name in &demand.families {
if next.family(name).is_some() {
continue;
}
grew |= next.register_answers(source.family(name), name);
}
for &(codepoint, attrs) in &demand.codepoints {
grew |= next.register_fallback_answer(source, codepoint, attrs);
}
grew.then_some(next)
}
fn register_answers(&mut self, faces: Vec<Font>, requested_name: &str) -> bool {
let mut added = false;
for mut font in faces {
font.add_alias(requested_name);
self.add(font);
added = true;
}
added
}
fn register_fallback_answer(
&mut self,
source: &mut dyn FontSource,
codepoint: char,
attrs: FontAttrs,
) -> bool {
if is_private_use(codepoint) {
return false;
}
if self.covers_anywhere(codepoint) {
return false;
}
let Some(font) = source.face_for_codepoint(codepoint, attrs) else {
return false;
};
let id = self.add(font);
self.add_fallback(id);
true
}
fn covers_anywhere(&self, codepoint: char) -> bool {
self.fonts.iter().any(|font| font.covers(codepoint))
}
pub fn is_empty(&self) -> bool {
self.fonts.is_empty()
}
pub fn len(&self) -> usize {
self.fonts.len()
}
pub fn get_arc(&self, id: FontId) -> Arc<Font> {
self.fonts[id.0 as usize].clone()
}
pub fn get(&self, id: FontId) -> &Font {
&self.fonts[id.0 as usize]
}
pub fn family(&self, name: &str) -> Option<FontId> {
let at = self.fonts.iter().position(|f| f.matches(name))?;
Some(FontId(at as u32))
}
pub fn faces<'a>(&'a self, name: &'a str) -> impl Iterator<Item = FontId> + 'a {
self.variants(name).map(|(id, _)| id)
}
pub fn family_variant(&self, name: &str, attrs: FontAttrs) -> Option<FontId> {
self.nearest(self.variants(name), attrs)
}
pub fn resolve(&self, families: &[String], attrs: FontAttrs, ch: char) -> FontId {
self.resolve_covered(families, attrs, ch).0
}
pub fn resolve_covered(
&self,
families: &[String],
attrs: FontAttrs,
ch: char,
) -> (FontId, bool) {
for name in families {
let covering = self.variants(name).filter(|(_, f)| f.covers(ch));
if let Some(id) = self.nearest(covering, attrs) {
return (id, true);
}
}
let covering_fallbacks = self
.fallbacks
.iter()
.map(|id| (*id, self.get(*id)))
.filter(|(_, f)| f.covers(ch));
if let Some(id) = self.nearest(covering_fallbacks, attrs) {
return (id, true);
}
(self.tofu_face(families, attrs), false)
}
fn variants<'a>(&'a self, name: &'a str) -> impl Iterator<Item = (FontId, &'a Font)> {
self.fonts
.iter()
.enumerate()
.filter(move |(_, f)| f.matches(name))
.map(|(at, f)| (FontId(at as u32), f.as_ref()))
}
fn nearest<'a>(
&self,
faces: impl Iterator<Item = (FontId, &'a Font)>,
attrs: FontAttrs,
) -> Option<FontId> {
faces
.min_by_key(|(_, f)| {
(
stretch_distance(f.attrs.stretch, attrs.stretch),
f.attrs.italic != attrs.italic,
f.attrs.weight.abs_diff(attrs.weight),
)
})
.map(|(id, _)| id)
}
fn tofu_face(&self, families: &[String], attrs: FontAttrs) -> FontId {
self.tofu_face_opt(families, attrs).unwrap_or_else(|| {
panic!(
"FontCollection has no fonts registered — register() one before building paragraphs"
);
})
}
pub(crate) fn tofu_face_opt(&self, families: &[String], attrs: FontAttrs) -> Option<FontId> {
families
.iter()
.find_map(|name| self.family_variant(name, attrs))
.or_else(|| self.fallbacks.first().copied())
.or_else(|| (!self.fonts.is_empty()).then_some(FontId(0)))
}
pub(crate) fn resolve_covered_opt(
&self,
families: &[String],
attrs: FontAttrs,
ch: char,
) -> Option<(FontId, bool)> {
if self.fonts.is_empty() {
return None;
}
Some(self.resolve_covered(families, attrs, ch))
}
}
impl Font {
pub fn from_bytes(bytes: Vec<u8>) -> Option<Font> {
Self::from_data(Arc::new(bytes), 0)
}
pub fn from_data(data: FontData, face_index: u32) -> Option<Font> {
let data = unwrapped(data)?;
Self::instance(data, face_index, Vec::new())
}
pub fn instances_from_data(data: FontData, face_index: u32) -> Vec<Font> {
let Some(data) = unwrapped(data) else {
return Vec::new();
};
let instances = named_instance_coordinates((*data).as_ref(), face_index);
if instances.is_empty() {
return Self::instance(data, face_index, Vec::new())
.into_iter()
.collect();
}
let Some(shared) = SharedFace::parse(data, face_index) else {
return Vec::new();
};
instances
.into_iter()
.filter_map(|coordinates| Self::shared_instance(shared.clone(), coordinates))
.collect()
}
fn instance(data: FontData, face_index: u32, coordinates: Vec<([u8; 4], f32)>) -> Option<Font> {
let shared = SharedFace::parse(data, face_index)?;
Self::shared_instance(shared, coordinates)
}
fn shared_instance(shared: Arc<SharedFace>, coordinates: Vec<([u8; 4], f32)>) -> Option<Font> {
let bytes: &[u8] = (*shared.data).as_ref();
let (family, aliases) = embedded_names(bytes, shared.face_index)?;
let attrs = instance_attrs(embedded_attrs(bytes, shared.face_index), &coordinates);
let mut font = Font::at_coordinates(shared, &family, attrs, coordinates)?;
for name in &aliases {
font.add_alias(name);
}
Some(font)
}
}
impl SharedFace {
fn parse(data: FontData, face_index: u32) -> Option<Arc<Self>> {
let bytes: &[u8] = (*data).as_ref();
let font = skrifa::FontRef::from_index(bytes, face_index).ok()?;
let charmap = font
.charmap()
.mappings()
.map(|(code, glyph)| (code, glyph.to_u32()))
.collect();
let harf = harfrust::FontRef::from_index(bytes, face_index).ok()?;
let shaper_data = harfrust::ShaperData::new(&harf);
Some(Arc::new(Self {
data,
face_index,
charmap,
shaper_data,
}))
}
}
fn named_instance_coordinates(bytes: &[u8], face_index: u32) -> Vec<Vec<([u8; 4], f32)>> {
let Ok(font) = skrifa::FontRef::from_index(bytes, face_index) else {
return Vec::new();
};
let axis_tags: Vec<[u8; 4]> = font
.axes()
.iter()
.map(|axis| axis.tag().to_be_bytes())
.collect();
font.named_instances()
.iter()
.map(|instance| {
axis_tags
.iter()
.copied()
.zip(instance.user_coords())
.collect()
})
.collect()
}
fn instance_attrs(base: FontAttrs, coordinates: &[([u8; 4], f32)]) -> FontAttrs {
let mut attrs = base;
for (tag, value) in coordinates {
match tag {
b"wght" => attrs.weight = value.clamp(1.0, 1000.0) as u16,
b"ital" => attrs.italic = *value >= 0.5,
b"slnt" => attrs.italic = attrs.italic || *value < 0.0,
b"wdth" => attrs.stretch = value.clamp(1.0, 1000.0),
_ => {}
}
}
attrs
}
fn stretch_distance(candidate: f32, wanted: f32) -> u32 {
((candidate - wanted).abs() * 16.0) as u32
}
#[cfg(feature = "woff2")]
fn unwrapped(data: FontData) -> Option<FontData> {
let bytes: &[u8] = (*data).as_ref();
if !woff2_patched::decode::is_woff2(bytes) {
return Some(data);
}
let unpacked = woff2_patched::decode::convert_woff2_to_ttf(&mut &bytes[..]).ok()?;
Some(Arc::new(unpacked))
}
#[cfg(not(feature = "woff2"))]
fn unwrapped(data: FontData) -> Option<FontData> {
Some(data)
}
fn embedded_names(data: &[u8], face_index: u32) -> Option<(String, Vec<String>)> {
use swash::StringId;
let font = swash::FontRef::from_index(data, face_index as usize)?;
let strings = font.localized_strings();
let pick = |id: StringId| {
strings
.find_by_id(id, Some("en"))
.or_else(|| strings.find_by_id(id, None))
.map(|s| s.to_string())
};
let primary = pick(StringId::TypographicFamily).or_else(|| pick(StringId::Family))?;
let aliases = strings
.filter(|s| matches!(s.id(), StringId::Family | StringId::TypographicFamily))
.map(|s| s.to_string())
.filter(|name| *name != primary)
.collect();
Some((primary, aliases))
}
fn embedded_attrs(data: &[u8], face_index: u32) -> FontAttrs {
let Some(font) = swash::FontRef::from_index(data, face_index as usize) else {
return FontAttrs::default();
};
let attrs = font.attributes();
FontAttrs {
weight: attrs.weight().0,
italic: attrs.style() != swash::Style::Normal,
stretch: attrs.stretch().to_percentage(),
}
}
fn is_private_use(codepoint: char) -> bool {
matches!(
codepoint,
'\u{E000}'..='\u{F8FF}' | '\u{F0000}'..='\u{FFFFD}' | '\u{100000}'..='\u{10FFFD}'
)
}
#[derive(Default)]
pub struct FontCollection {
faces: FaceSet,
sources: Vec<Box<dyn FontSource>>,
unanswered: FontDemand,
}
impl FontCollection {
pub fn new() -> FontCollection {
FontCollection::default()
}
pub fn faces(&self) -> &FaceSet {
&self.faces
}
pub fn register(&mut self, family: &str, bytes: Vec<u8>) -> Option<FontId> {
self.faces.register(family, bytes)
}
pub fn add(&mut self, font: Font) -> FontId {
self.faces.add(font)
}
pub fn add_fallback(&mut self, id: FontId) {
self.faces.add_fallback(id);
}
pub fn get(&self, id: FontId) -> &Font {
self.faces.get(id)
}
pub fn len(&self) -> usize {
self.faces.len()
}
pub fn family(&self, name: &str) -> Option<FontId> {
self.faces.family(name)
}
pub fn add_source(&mut self, source: impl FontSource + 'static) {
self.sources.push(Box::new(source));
}
pub fn add_boxed_source(&mut self, source: Box<dyn FontSource>) {
self.sources.push(source);
}
pub fn is_empty(&self) -> bool {
self.faces.is_empty()
}
pub fn adopt_faces(&mut self, faces: FaceSet) {
self.faces = faces;
}
pub fn take_unanswered(&mut self) -> FontDemand {
std::mem::take(&mut self.unanswered)
}
pub(crate) fn require_family(&mut self, name: &str) -> bool {
if self.faces.family(name).is_some() {
return true;
}
for source in &mut self.sources {
let faces = source.family(name);
if self.faces.register_answers(faces, name) {
return true;
}
}
self.unanswered.add_family(name);
false
}
pub(crate) fn require_codepoint(&mut self, codepoint: char, attrs: FontAttrs) -> bool {
if self.faces.covers_anywhere(codepoint) {
return true;
}
for index in 0..self.sources.len() {
let (head, tail) = self.sources.split_at_mut(index);
let _ = head;
let source = &mut tail[0];
if self
.faces
.register_fallback_answer(source.as_mut(), codepoint, attrs)
{
return true;
}
}
self.unanswered.add_codepoint(codepoint, attrs);
false
}
}