1use std::collections::HashMap;
2use std::sync::Arc;
3
4use skrifa::metrics::Metrics;
5use skrifa::prelude::Size;
6use skrifa::MetadataProvider;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10pub struct FontId(
11 pub u32,
13);
14
15pub type FontData = Arc<dyn AsRef<[u8]> + Send + Sync>;
20
21#[derive(Clone, Copy, Debug, PartialEq)]
23pub struct FontAttrs {
24 pub weight: u16,
26 pub italic: bool,
28 pub stretch: f32,
30}
31
32pub const NORMAL_STRETCH: f32 = 100.0;
34
35impl Default for FontAttrs {
36 fn default() -> Self {
37 Self {
38 weight: 400,
39 italic: false,
40 stretch: NORMAL_STRETCH,
41 }
42 }
43}
44
45struct SharedFace {
50 data: FontData,
51 face_index: u32,
54 charmap: HashMap<u32, u32>,
57 shaper_data: harfrust::ShaperData,
61}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
68pub struct FontUid(
69 pub u64,
71);
72
73impl FontUid {
74 fn next() -> FontUid {
75 use std::sync::atomic::{AtomicU64, Ordering};
76 static COUNTER: AtomicU64 = AtomicU64::new(1);
77 FontUid(COUNTER.fetch_add(1, Ordering::Relaxed))
78 }
79}
80
81pub struct Font {
86 uid: FontUid,
87 shared: Arc<SharedFace>,
88 variation_coordinates: Vec<([u8; 4], f32)>,
92 variation_location: skrifa::instance::Location,
95 shaper_instance: Option<harfrust::ShaperInstance>,
97 family: String,
98 aliases: Vec<String>,
102 attrs: FontAttrs,
103 units_per_em: f32,
104 ascent: f32,
106 descent: f32,
107 line_gap: f32,
108 bounds: Option<(f32, f32, f32, f32)>,
110 underline: Option<(f32, f32)>,
112 strikeout: Option<(f32, f32)>,
113}
114
115impl std::fmt::Debug for Font {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 f.debug_struct("Font")
118 .field("uid", &self.uid)
119 .field("family", &self.family)
120 .field("attrs", &self.attrs)
121 .finish_non_exhaustive()
122 }
123}
124
125impl PartialEq for Font {
128 fn eq(&self, other: &Font) -> bool {
129 self.uid == other.uid
130 }
131}
132
133impl Font {
134 fn parse(
135 family: &str,
136 attrs: FontAttrs,
137 data: FontData,
138 face_index: u32,
139 variation_coordinates: Vec<([u8; 4], f32)>,
140 ) -> Option<Self> {
141 let shared = SharedFace::parse(data, face_index)?;
142 Self::at_coordinates(shared, family, attrs, variation_coordinates)
143 }
144
145 fn at_coordinates(
148 shared: Arc<SharedFace>,
149 family: &str,
150 attrs: FontAttrs,
151 variation_coordinates: Vec<([u8; 4], f32)>,
152 ) -> Option<Self> {
153 let bytes: &[u8] = (*shared.data).as_ref();
154 let font = skrifa::FontRef::from_index(bytes, shared.face_index).ok()?;
155 let variation_location = font.axes().location(
156 variation_coordinates
157 .iter()
158 .map(|(tag, value)| (skrifa::Tag::new(tag), *value)),
159 );
160 let metrics = Metrics::new(&font, Size::unscaled(), &variation_location);
161 let shaper_instance = (!variation_coordinates.is_empty()).then(|| {
162 let harf = harfrust::FontRef::from_index(bytes, shared.face_index).ok();
163 harf.map(|harf| {
164 harfrust::ShaperInstance::from_variations(
165 &harf,
166 variation_coordinates
167 .iter()
168 .map(|(tag, value)| harfrust::Variation {
169 tag: harfrust::Tag::new(tag),
170 value: *value,
171 }),
172 )
173 })
174 });
175 Some(Self {
176 uid: FontUid::next(),
177 family: family.to_owned(),
178 aliases: Vec::new(),
179 attrs,
180 units_per_em: metrics.units_per_em as f32,
181 ascent: metrics.ascent,
182 descent: metrics.descent,
183 line_gap: metrics.leading,
184 bounds: metrics.bounds.map(|b| (b.x_min, b.y_min, b.x_max, b.y_max)),
185 underline: metrics.underline.map(|d| (d.offset, d.thickness)),
186 strikeout: metrics.strikeout.map(|d| (d.offset, d.thickness)),
187 shared,
188 variation_coordinates,
189 variation_location,
190 shaper_instance: shaper_instance.flatten(),
191 })
192 }
193
194 pub fn data(&self) -> &[u8] {
198 (*self.shared.data).as_ref()
199 }
200
201 pub fn face_index(&self) -> u32 {
203 self.shared.face_index
204 }
205
206 pub fn variation_coordinates(&self) -> &[([u8; 4], f32)] {
210 &self.variation_coordinates
211 }
212
213 pub(crate) fn variation_location(&self) -> &skrifa::instance::Location {
214 &self.variation_location
215 }
216
217 pub(crate) fn shaper_instance(&self) -> Option<&harfrust::ShaperInstance> {
218 self.shaper_instance.as_ref()
219 }
220
221 pub fn uid(&self) -> FontUid {
223 self.uid
224 }
225
226 pub fn family(&self) -> &str {
228 &self.family
229 }
230
231 pub fn aliases(&self) -> &[String] {
233 &self.aliases
234 }
235
236 pub fn matches(&self, name: &str) -> bool {
240 self.family.eq_ignore_ascii_case(name)
241 || self.aliases.iter().any(|a| a.eq_ignore_ascii_case(name))
242 }
243
244 pub fn add_alias(&mut self, name: &str) {
246 if !self.matches(name) {
247 self.aliases.push(name.to_owned());
248 }
249 }
250
251 pub fn attrs(&self) -> FontAttrs {
253 self.attrs
254 }
255
256 pub fn ascent_px(&self, size: f32) -> f32 {
258 self.ascent * size / self.units_per_em
259 }
260
261 pub fn descent_px(&self, size: f32) -> f32 {
263 -self.descent * size / self.units_per_em
264 }
265
266 pub fn line_height_px(&self, size: f32) -> f32 {
268 (self.ascent - self.descent + self.line_gap) * size / self.units_per_em
269 }
270
271 pub fn units_per_em(&self) -> f32 {
273 self.units_per_em
274 }
275
276 pub fn ink_box_px(&self, size: f32) -> Option<(f32, f32, f32, f32)> {
281 let k = size / self.units_per_em;
282 self.bounds
283 .map(|(x0, y0, x1, y1)| (x0 * k, y0 * k, x1 * k, y1 * k))
284 }
285
286 pub fn covers(&self, ch: char) -> bool {
288 self.shared.charmap.contains_key(&(ch as u32))
289 }
290
291 pub fn glyph_for(&self, ch: char) -> Option<u32> {
293 self.shared.charmap.get(&(ch as u32)).copied()
294 }
295
296 pub(crate) fn shaper_data(&self) -> &harfrust::ShaperData {
297 &self.shared.shaper_data
298 }
299
300 pub fn underline_px(&self, size: f32) -> (f32, f32) {
305 self.decoration_px(self.underline, size, -0.1, 0.05)
306 }
307
308 pub fn strikeout_px(&self, size: f32) -> (f32, f32) {
313 self.decoration_px(self.strikeout, size, 0.3, 0.05)
314 }
315
316 fn decoration_px(
317 &self,
318 metric: Option<(f32, f32)>,
319 size: f32,
320 default_offset: f32,
321 default_thickness: f32,
322 ) -> (f32, f32) {
323 match metric {
324 Some((offset, thickness)) => (
326 offset * size / self.units_per_em,
327 (thickness * size / self.units_per_em).max(0.5),
328 ),
329 None => (size * default_offset, (size * default_thickness).max(0.5)),
330 }
331 }
332}
333
334#[derive(Clone, Debug, Default, PartialEq)]
338pub struct FontDemand {
339 pub families: Vec<String>,
341 pub codepoints: Vec<(char, FontAttrs)>,
343}
344
345impl FontDemand {
346 pub fn is_empty(&self) -> bool {
348 self.families.is_empty() && self.codepoints.is_empty()
349 }
350
351 pub(crate) fn add_family(&mut self, name: &str) {
352 if !self.families.iter().any(|f| f == name) {
353 self.families.push(name.to_owned());
354 }
355 }
356
357 pub(crate) fn add_codepoint(&mut self, ch: char, attrs: FontAttrs) {
358 if !self.codepoints.contains(&(ch, attrs)) {
359 self.codepoints.push((ch, attrs));
360 }
361 }
362}
363
364pub trait FontSource {
370 fn family(&mut self, name: &str) -> Vec<Font>;
372
373 fn face_for_codepoint(&mut self, codepoint: char, attrs: FontAttrs) -> Option<Font>;
375}
376
377#[derive(Default, Clone)]
382pub struct FaceSet {
383 fonts: Vec<Arc<Font>>,
386 fallbacks: Vec<FontId>,
387}
388
389impl FaceSet {
390 pub fn new() -> Self {
392 Self::default()
393 }
394
395 pub fn register(&mut self, family: &str, bytes: Vec<u8>) -> Option<FontId> {
399 self.register_with(family, FontAttrs::default(), bytes)
400 }
401
402 pub fn register_with(
406 &mut self,
407 family: &str,
408 attrs: FontAttrs,
409 bytes: Vec<u8>,
410 ) -> Option<FontId> {
411 let (data, face_index) = unwrapped(Arc::new(bytes), 0)?;
412 let font = Font::parse(family, attrs, data, face_index, Vec::new())?;
413 Some(self.add(font))
414 }
415
416 pub fn add(&mut self, font: Font) -> FontId {
418 self.fonts.push(Arc::new(font));
419 FontId(self.fonts.len() as u32 - 1)
420 }
421
422 pub fn with_font(&self, font: Font) -> (FaceSet, FontId) {
426 let mut next = self.clone();
427 let id = next.add(font);
428 (next, id)
429 }
430
431 pub fn with_fallbacks(&self, fallbacks: Vec<FontId>) -> FaceSet {
435 let mut next = self.clone();
436 next.fallbacks = fallbacks;
437 next
438 }
439
440 pub fn add_fallback(&mut self, id: FontId) {
445 self.fallbacks.push(id);
446 }
447
448 pub fn grown_by(&self, source: &mut dyn FontSource, demand: &FontDemand) -> Option<FaceSet> {
454 let mut next = self.clone();
455 let mut grew = false;
456 for name in &demand.families {
457 if next.family(name).is_some() {
458 continue;
461 }
462 grew |= next.register_answers(source.family(name), name);
463 }
464 for &(codepoint, attrs) in &demand.codepoints {
465 grew |= next.register_fallback_answer(source, codepoint, attrs);
466 }
467 grew.then_some(next)
468 }
469
470 fn register_answers(&mut self, faces: Vec<Font>, requested_name: &str) -> bool {
471 let mut added = false;
472 for mut font in faces {
473 font.add_alias(requested_name);
474 self.add(font);
475 added = true;
476 }
477 added
478 }
479
480 fn register_fallback_answer(
481 &mut self,
482 source: &mut dyn FontSource,
483 codepoint: char,
484 attrs: FontAttrs,
485 ) -> bool {
486 if is_private_use(codepoint) {
487 return false;
491 }
492 if self.fallbacks_cover(codepoint) {
493 return false;
496 }
497 let Some(font) = source.face_for_codepoint(codepoint, attrs) else {
498 return false;
499 };
500 let id = self.add(font);
501 self.add_fallback(id);
502 true
503 }
504
505 fn fallbacks_cover(&self, codepoint: char) -> bool {
510 self.fallbacks
511 .iter()
512 .any(|id| self.get(*id).covers(codepoint))
513 }
514
515 pub(crate) fn covered_by_resolution(
523 &self,
524 families: &[String],
525 attrs: FontAttrs,
526 ch: char,
527 ) -> bool {
528 matches!(
529 self.resolve_covered_opt(families, attrs, ch),
530 Some((_, true))
531 )
532 }
533
534 pub fn is_empty(&self) -> bool {
536 self.fonts.is_empty()
537 }
538
539 pub fn len(&self) -> usize {
541 self.fonts.len()
542 }
543
544 pub fn get_arc(&self, id: FontId) -> Arc<Font> {
550 self.fonts[id.0 as usize].clone()
551 }
552
553 pub fn get(&self, id: FontId) -> &Font {
559 &self.fonts[id.0 as usize]
560 }
561
562 pub fn family(&self, name: &str) -> Option<FontId> {
564 let at = self.fonts.iter().position(|f| f.matches(name))?;
565 Some(FontId(at as u32))
566 }
567
568 pub fn faces<'a>(&'a self, name: &'a str) -> impl Iterator<Item = FontId> + 'a {
572 self.variants(name).map(|(id, _)| id)
573 }
574
575 pub fn family_variant(&self, name: &str, attrs: FontAttrs) -> Option<FontId> {
580 self.nearest(self.variants(name), attrs)
581 }
582
583 pub fn resolve(&self, families: &[String], attrs: FontAttrs, ch: char) -> FontId {
593 self.resolve_covered(families, attrs, ch).0
594 }
595
596 pub fn resolve_covered(
604 &self,
605 families: &[String],
606 attrs: FontAttrs,
607 ch: char,
608 ) -> (FontId, bool) {
609 for name in families {
610 let covering = self.variants(name).filter(|(_, f)| f.covers(ch));
611 if let Some(id) = self.nearest(covering, attrs) {
612 return (id, true);
613 }
614 }
615 let covering_fallbacks = self
616 .fallbacks
617 .iter()
618 .map(|id| (*id, self.get(*id)))
619 .filter(|(_, f)| f.covers(ch));
620 if let Some(id) = self.nearest(covering_fallbacks, attrs) {
621 return (id, true);
622 }
623 (self.tofu_face(families, attrs), false)
624 }
625
626 fn variants<'a>(&'a self, name: &'a str) -> impl Iterator<Item = (FontId, &'a Font)> {
628 self.fonts
629 .iter()
630 .enumerate()
631 .filter(move |(_, f)| f.matches(name))
632 .map(|(at, f)| (FontId(at as u32), f.as_ref()))
633 }
634
635 fn nearest<'a>(
640 &self,
641 faces: impl Iterator<Item = (FontId, &'a Font)>,
642 attrs: FontAttrs,
643 ) -> Option<FontId> {
644 faces
645 .min_by_key(|(_, f)| {
646 (
647 stretch_distance(f.attrs.stretch, attrs.stretch),
648 f.attrs.italic != attrs.italic,
649 f.attrs.weight.abs_diff(attrs.weight),
650 )
651 })
652 .map(|(id, _)| id)
653 }
654
655 fn tofu_face(&self, families: &[String], attrs: FontAttrs) -> FontId {
658 self.tofu_face_opt(families, attrs).unwrap_or_else(|| {
659 panic!(
660 "FontCollection has no fonts registered — register() one before building paragraphs"
661 );
662 })
663 }
664
665 pub(crate) fn tofu_face_opt(&self, families: &[String], attrs: FontAttrs) -> Option<FontId> {
670 families
671 .iter()
672 .find_map(|name| self.family_variant(name, attrs))
673 .or_else(|| self.fallbacks.first().copied())
674 .or_else(|| (!self.fonts.is_empty()).then_some(FontId(0)))
675 }
676
677 pub(crate) fn resolve_covered_opt(
680 &self,
681 families: &[String],
682 attrs: FontAttrs,
683 ch: char,
684 ) -> Option<(FontId, bool)> {
685 if self.fonts.is_empty() {
686 return None;
687 }
688 Some(self.resolve_covered(families, attrs, ch))
689 }
690}
691
692impl Font {
693 pub fn from_bytes(bytes: Vec<u8>) -> Option<Font> {
698 Self::from_data(Arc::new(bytes), 0)
699 }
700
701 pub fn from_data(data: FontData, face_index: u32) -> Option<Font> {
706 let (data, face_index) = unwrapped(data, face_index)?;
707 Self::instance(data, face_index, Vec::new())
708 }
709
710 pub fn instances_from_data(data: FontData, face_index: u32) -> Vec<Font> {
715 let Some((data, face_index)) = unwrapped(data, face_index) else {
716 return Vec::new();
717 };
718 let instances = named_instance_coordinates((*data).as_ref(), face_index);
719 if instances.is_empty() {
720 return Self::instance(data, face_index, Vec::new())
721 .into_iter()
722 .collect();
723 }
724 let Some(shared) = SharedFace::parse(data, face_index) else {
727 return Vec::new();
728 };
729 instances
730 .into_iter()
731 .filter_map(|coordinates| Self::shared_instance(shared.clone(), coordinates))
732 .collect()
733 }
734
735 fn instance(data: FontData, face_index: u32, coordinates: Vec<([u8; 4], f32)>) -> Option<Font> {
738 let shared = SharedFace::parse(data, face_index)?;
739 Self::shared_instance(shared, coordinates)
740 }
741
742 fn shared_instance(shared: Arc<SharedFace>, coordinates: Vec<([u8; 4], f32)>) -> Option<Font> {
744 let bytes: &[u8] = (*shared.data).as_ref();
745 let (family, aliases) = embedded_names(bytes, shared.face_index)?;
746 let attrs = instance_attrs(embedded_attrs(bytes, shared.face_index), &coordinates);
747 let mut font = Font::at_coordinates(shared, &family, attrs, coordinates)?;
748 for name in &aliases {
749 font.add_alias(name);
750 }
751 Some(font)
752 }
753}
754
755impl SharedFace {
756 fn parse(data: FontData, face_index: u32) -> Option<Arc<Self>> {
757 let bytes: &[u8] = (*data).as_ref();
758 let font = skrifa::FontRef::from_index(bytes, face_index).ok()?;
759 let charmap = font
760 .charmap()
761 .mappings()
762 .map(|(code, glyph)| (code, glyph.to_u32()))
763 .collect();
764 let harf = harfrust::FontRef::from_index(bytes, face_index).ok()?;
765 let shaper_data = harfrust::ShaperData::new(&harf);
766 Some(Arc::new(Self {
767 data,
768 face_index,
769 charmap,
770 shaper_data,
771 }))
772 }
773}
774
775fn named_instance_coordinates(bytes: &[u8], face_index: u32) -> Vec<Vec<([u8; 4], f32)>> {
777 let Ok(font) = skrifa::FontRef::from_index(bytes, face_index) else {
778 return Vec::new();
779 };
780 let axis_tags: Vec<[u8; 4]> = font
781 .axes()
782 .iter()
783 .map(|axis| axis.tag().to_be_bytes())
784 .collect();
785 font.named_instances()
786 .iter()
787 .map(|instance| {
788 axis_tags
789 .iter()
790 .copied()
791 .zip(instance.user_coords())
792 .collect()
793 })
794 .collect()
795}
796
797fn instance_attrs(base: FontAttrs, coordinates: &[([u8; 4], f32)]) -> FontAttrs {
800 let mut attrs = base;
801 for (tag, value) in coordinates {
802 match tag {
803 b"wght" => attrs.weight = value.clamp(1.0, 1000.0) as u16,
804 b"ital" => attrs.italic = *value >= 0.5,
805 b"slnt" => attrs.italic = attrs.italic || *value < 0.0,
806 b"wdth" => attrs.stretch = value.clamp(1.0, 1000.0),
808 _ => {}
809 }
810 }
811 attrs
812}
813
814fn stretch_distance(candidate: f32, wanted: f32) -> u32 {
818 ((candidate - wanted).abs() * 16.0) as u32
819}
820
821#[cfg(feature = "woff2")]
827fn unwrapped(data: FontData, face_index: u32) -> Option<(FontData, u32)> {
828 let bytes: &[u8] = (*data).as_ref();
829 if !bytes.starts_with(b"wOF2") {
830 return Some((data, face_index));
831 }
832 let unpacked = crate::woff2::to_sfnt(bytes, face_index)?;
833 Some((Arc::new(unpacked), 0))
834}
835
836#[cfg(not(feature = "woff2"))]
837fn unwrapped(data: FontData, face_index: u32) -> Option<(FontData, u32)> {
838 Some((data, face_index))
839}
840
841fn embedded_names(data: &[u8], face_index: u32) -> Option<(String, Vec<String>)> {
845 use swash::StringId;
846 let font = swash::FontRef::from_index(data, face_index as usize)?;
847 let strings = font.localized_strings();
848 let pick = |id: StringId| {
849 strings
850 .find_by_id(id, Some("en"))
851 .or_else(|| strings.find_by_id(id, None))
852 .map(|s| s.to_string())
853 };
854 let primary = pick(StringId::TypographicFamily).or_else(|| pick(StringId::Family))?;
855 let aliases = strings
856 .filter(|s| matches!(s.id(), StringId::Family | StringId::TypographicFamily))
857 .map(|s| s.to_string())
858 .filter(|name| *name != primary)
859 .collect();
860 Some((primary, aliases))
861}
862
863fn embedded_attrs(data: &[u8], face_index: u32) -> FontAttrs {
865 let Some(font) = swash::FontRef::from_index(data, face_index as usize) else {
866 return FontAttrs::default();
867 };
868 let attrs = font.attributes();
869 FontAttrs {
870 weight: attrs.weight().0,
871 italic: attrs.style() != swash::Style::Normal,
872 stretch: attrs.stretch().to_percentage(),
873 }
874}
875
876fn is_private_use(codepoint: char) -> bool {
879 matches!(
880 codepoint,
881 '\u{E000}'..='\u{F8FF}' | '\u{F0000}'..='\u{FFFFD}' | '\u{100000}'..='\u{10FFFD}'
882 )
883}
884
885#[derive(Default)]
890pub struct FontCollection {
891 faces: FaceSet,
892 sources: Vec<Box<dyn FontSource>>,
895 unanswered: FontDemand,
897}
898
899impl FontCollection {
900 pub fn new() -> FontCollection {
902 FontCollection::default()
903 }
904
905 pub fn faces(&self) -> &FaceSet {
909 &self.faces
910 }
911
912 pub fn register(&mut self, family: &str, bytes: Vec<u8>) -> Option<FontId> {
916 self.faces.register(family, bytes)
917 }
918
919 pub fn add(&mut self, font: Font) -> FontId {
921 self.faces.add(font)
922 }
923
924 pub fn add_fallback(&mut self, id: FontId) {
928 self.faces.add_fallback(id);
929 }
930
931 pub fn get(&self, id: FontId) -> &Font {
937 self.faces.get(id)
938 }
939
940 pub fn len(&self) -> usize {
942 self.faces.len()
943 }
944
945 pub fn family(&self, name: &str) -> Option<FontId> {
947 self.faces.family(name)
948 }
949
950 pub fn add_source(&mut self, source: impl FontSource + 'static) {
952 self.sources.push(Box::new(source));
953 }
954
955 pub fn add_boxed_source(&mut self, source: Box<dyn FontSource>) {
957 self.sources.push(source);
958 }
959
960 pub fn is_empty(&self) -> bool {
962 self.faces.is_empty()
963 }
964
965 pub fn adopt_faces(&mut self, faces: FaceSet) {
969 self.faces = faces;
970 }
971
972 pub fn take_unanswered(&mut self) -> FontDemand {
977 std::mem::take(&mut self.unanswered)
978 }
979
980 pub(crate) fn require_family(&mut self, name: &str) -> bool {
984 if self.faces.family(name).is_some() {
985 return true;
986 }
987 for source in &mut self.sources {
988 let faces = source.family(name);
989 if self.faces.register_answers(faces, name) {
990 return true;
991 }
992 }
993 self.unanswered.add_family(name);
994 false
995 }
996
997 pub(crate) fn require_codepoint(
999 &mut self,
1000 families: &[String],
1001 codepoint: char,
1002 attrs: FontAttrs,
1003 ) -> bool {
1004 if self.faces.covered_by_resolution(families, attrs, codepoint) {
1005 return true;
1006 }
1007 for index in 0..self.sources.len() {
1008 let (head, tail) = self.sources.split_at_mut(index);
1009 let _ = head;
1010 let source = &mut tail[0];
1011 if self
1012 .faces
1013 .register_fallback_answer(source.as_mut(), codepoint, attrs)
1014 {
1015 return true;
1016 }
1017 }
1018 self.unanswered.add_codepoint(codepoint, attrs);
1019 false
1020 }
1021}