1use std::collections::HashMap;
9use std::sync::Arc;
10
11use harfrust::{
12 BufferClusterLevel, Feature, FontRef as HarfFontRef, Language, ShaperData, ShaperInstance, Tag,
13 UnicodeBuffer, script,
14};
15use serde::{Deserialize, Serialize};
16use skrifa::MetadataProvider;
17use skrifa::attribute::Style as SlopeStyle;
18use skrifa::instance::{Location, LocationRef, Size};
19use skrifa::metrics::{GlyphMetrics, Metrics};
20use skrifa::prelude::GlyphId;
21use skrifa::raw::TableProvider;
22use skrifa::string::StringId;
23
24#[derive(Debug, Clone)]
30pub struct FontSource {
31 pub bytes: Vec<u8>,
34 pub family: String,
36 pub name: String,
38 pub style: String,
40 pub declared: Option<FaceAttributes>,
46}
47
48impl FontSource {
49 pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, FontError> {
52 let font = skrifa::FontRef::new(&bytes).map_err(|_| FontError::Parse)?;
53 let family = name_string(&font, StringId::TYPOGRAPHIC_FAMILY_NAME)
54 .or_else(|| name_string(&font, StringId::FAMILY_NAME))
55 .ok_or(FontError::MissingName)?;
56 let name = name_string(&font, StringId::FULL_NAME).unwrap_or_else(|| family.clone());
57 let style =
58 name_string(&font, StringId::SUBFAMILY_NAME).unwrap_or_else(|| "Regular".into());
59 Ok(FontSource {
60 bytes,
61 family: family.to_lowercase(),
62 name,
63 style,
64 declared: None,
65 })
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
72pub struct FaceAttributes {
73 pub italic: bool,
76 pub weight: u16,
78}
79
80impl FaceAttributes {
81 pub const REGULAR: FaceAttributes = FaceAttributes {
83 italic: false,
84 weight: 400,
85 };
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
95pub struct AxisSetting {
96 #[serde(with = "tag")]
98 pub tag: [u8; 4],
99 pub value: f32,
101}
102
103mod tag {
107 use serde::de::{Error, Unexpected};
108 use serde::{Deserialize, Deserializer, Serializer};
109
110 pub fn serialize<S: Serializer>(tag: &[u8; 4], serializer: S) -> Result<S::Ok, S::Error> {
111 serializer.serialize_str(&String::from_utf8_lossy(tag))
112 }
113
114 pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<[u8; 4], D::Error> {
115 let text = String::deserialize(deserializer)?;
116 text.as_bytes()
117 .try_into()
118 .map_err(|_| D::Error::invalid_value(Unexpected::Str(&text), &"four bytes"))
119 }
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub struct FaceMatch {
129 pub id: u16,
131 pub attributes: FaceAttributes,
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
137#[serde(rename_all = "snake_case")]
138pub enum GenericFamily {
139 Serif,
141 SansSerif,
143 Monospace,
145}
146
147impl GenericFamily {
148 pub fn keyword(self) -> &'static str {
150 match self {
151 GenericFamily::Serif => "serif",
152 GenericFamily::SansSerif => "sans-serif",
153 GenericFamily::Monospace => "monospace",
154 }
155 }
156
157 pub fn parse(keyword: &str) -> Option<Self> {
160 match keyword.to_ascii_lowercase().as_str() {
161 "serif" => Some(GenericFamily::Serif),
162 "sans-serif" | "sans" => Some(GenericFamily::SansSerif),
163 "monospace" | "mono" => Some(GenericFamily::Monospace),
164 _ => None,
165 }
166 }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173pub struct ShapedGlyph {
174 pub id: u32,
176 pub x_advance: u32,
178 pub cluster: u32,
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
187pub struct FeatureSetting {
188 #[serde(with = "tag")]
190 pub tag: [u8; 4],
191 pub value: u32,
193}
194
195impl FeatureSetting {
196 pub const fn new(tag: [u8; 4], value: u32) -> FeatureSetting {
198 FeatureSetting { tag, value }
199 }
200}
201
202#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
205pub struct FeatureSet {
206 pub small_caps: bool,
209 pub settings: Vec<FeatureSetting>,
213}
214
215#[derive(Debug, Clone, Default, PartialEq, Eq)]
227pub struct Features(Option<Arc<FeatureSet>>);
228
229impl Serialize for Features {
230 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
231 self.0.as_deref().serialize(serializer)
232 }
233}
234
235impl<'de> Deserialize<'de> for Features {
236 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Features, D::Error> {
237 let set = Option::<FeatureSet>::deserialize(deserializer)?;
238 Ok(Features(set.map(Arc::new)))
239 }
240}
241
242impl Features {
243 pub const NONE: Features = Features(None);
245
246 pub fn new(small_caps: bool, settings: Vec<FeatureSetting>) -> Features {
249 match (small_caps, settings.is_empty()) {
250 (false, true) => Features::NONE,
251 (true, true) => Features(Some(small_caps_only())),
252 _ => Features(Some(Arc::new(FeatureSet {
253 small_caps,
254 settings,
255 }))),
256 }
257 }
258
259 pub fn small_caps(&self) -> bool {
261 self.0.as_ref().is_some_and(|set| set.small_caps)
262 }
263
264 pub fn settings(&self) -> &[FeatureSetting] {
266 self.0.as_ref().map_or(&[], |set| &set.settings)
267 }
268}
269
270fn small_caps_only() -> Arc<FeatureSet> {
274 static SET: std::sync::OnceLock<Arc<FeatureSet>> = std::sync::OnceLock::new();
275 SET.get_or_init(|| {
276 Arc::new(FeatureSet {
277 small_caps: true,
278 settings: Vec::new(),
279 })
280 })
281 .clone()
282}
283
284#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
286pub struct FontRefEntry {
287 pub family: String,
289 pub name: String,
291 pub style: String,
293 pub attributes: FaceAttributes,
295 pub variations: Vec<AxisSetting>,
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
309pub struct FontMetricsTable {
310 pub units_per_em: u16,
312 pub ascender: i16,
314 pub descender: i16,
316 pub line_gap: i16,
318 pub cap_height: i16,
321 pub underline: Option<Rule>,
324 pub strikeout: Option<Rule>,
327}
328
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
331pub struct Rule {
332 pub offset: i16,
335 pub thickness: i16,
337}
338
339struct Face {
345 bytes: Arc<Vec<u8>>,
346 identity: FontRefEntry,
347 metrics: FontMetricsTable,
348 shaper_data: Arc<ShaperData>,
350 location: Location,
354 instance: Option<ShaperInstance>,
357 small_caps: bool,
359 features: Vec<[u8; 4]>,
363}
364
365#[derive(Default)]
370pub struct FontRegistry {
371 faces: Vec<Face>,
372 by_family: HashMap<String, Vec<u16>>,
373 generics: HashMap<GenericFamily, u16>,
374}
375
376impl FontRegistry {
377 pub fn new() -> Self {
379 Self::default()
380 }
381
382 pub fn add(&mut self, mut source: FontSource) -> Result<Vec<u16>, FontError> {
393 let bytes = Arc::new(std::mem::take(&mut source.bytes));
394 let font = skrifa::FontRef::new(&bytes).map_err(|_| FontError::Parse)?;
395 let shaper_data = Arc::new(ShaperData::new(&font));
396 let harf = HarfFontRef::new(&bytes).map_err(|_| FontError::Parse)?;
397
398 let mut ids = Vec::new();
399 for cut in cuts(&font, &source) {
400 let id = self.faces.len() as u16;
401 let instance = (!cut.variations.is_empty())
402 .then(|| ShaperInstance::from_coords(&harf, cut.location.coords().iter().copied()));
403 self.by_family
404 .entry(source.family.clone())
405 .or_default()
406 .push(id);
407 self.faces.push(Face {
408 bytes: bytes.clone(),
409 identity: FontRefEntry {
410 family: source.family.clone(),
411 name: cut.name,
412 style: cut.style,
413 attributes: cut.attributes,
414 variations: cut.variations,
415 },
416 metrics: read_metrics(&font, &cut.location),
417 small_caps: draws_small_caps(&harf, &shaper_data, instance.as_ref()),
418 features: feature_tags(&font),
419 shaper_data: shaper_data.clone(),
420 location: cut.location,
421 instance,
422 });
423 ids.push(id);
424 }
425 Ok(ids)
426 }
427
428 pub(crate) fn truncate(&mut self, len: usize) {
431 self.faces.truncate(len);
432 for ids in self.by_family.values_mut() {
433 ids.retain(|id| usize::from(*id) < len);
434 }
435 self.by_family.retain(|_, ids| !ids.is_empty());
436 self.generics.retain(|_, id| usize::from(*id) < len);
437 }
438
439 pub fn len(&self) -> usize {
441 self.faces.len()
442 }
443
444 pub fn is_empty(&self) -> bool {
446 self.faces.is_empty()
447 }
448
449 pub fn map_generic(&mut self, generic: GenericFamily, family: &str) -> Option<u16> {
452 let id = self.by_family.get(family)?.first().copied()?;
453 if self.generics.insert(generic, id).is_some() {
454 panic!("generic family {generic:?} already mapped");
455 }
456 Some(id)
457 }
458
459 pub fn generic(&self, generic: GenericFamily) -> Option<u16> {
461 self.generics.get(&generic).copied()
462 }
463
464 pub fn by_family(&self, family: &str) -> Option<u16> {
467 self.by_family
468 .get(&family.to_lowercase())
469 .and_then(|ids| ids.first().copied())
470 }
471
472 pub fn select(&self, family: &str, want: FaceAttributes) -> Option<FaceMatch> {
482 let ids = self.by_family.get(&family.to_lowercase())?;
483 let attributes = |id: &u16| self.faces[*id as usize].identity.attributes;
484 let has_slope = ids.iter().any(|id| attributes(id).italic == want.italic);
487 let id = ids
488 .iter()
489 .filter(|id| !has_slope || attributes(id).italic == want.italic)
490 .min_by_key(|id| (weight_rank(attributes(id).weight, want.weight), **id))
491 .copied()?;
492 Some(FaceMatch {
493 id,
494 attributes: attributes(&id),
495 })
496 }
497
498 pub fn font_ref(&self, id: u16) -> Option<&FontRefEntry> {
500 self.faces.get(id as usize).map(|face| &face.identity)
501 }
502
503 pub fn metrics(&self, id: u16) -> Option<FontMetricsTable> {
505 self.faces.get(id as usize).map(|face| face.metrics)
506 }
507
508 pub fn has_small_caps(&self, id: u16) -> bool {
511 self.faces
512 .get(id as usize)
513 .is_some_and(|face| face.small_caps)
514 }
515
516 pub fn has_feature(&self, id: u16, tag: [u8; 4]) -> bool {
519 self.faces
520 .get(id as usize)
521 .is_some_and(|face| face.features.binary_search(&tag).is_ok())
522 }
523
524 pub fn bytes(&self, id: u16) -> Option<Arc<Vec<u8>>> {
529 self.faces.get(id as usize).map(|face| face.bytes.clone())
530 }
531
532 pub fn variations(&self, id: u16) -> Option<&[AxisSetting]> {
536 self.faces
537 .get(id as usize)
538 .map(|face| face.identity.variations.as_slice())
539 }
540
541 pub fn advance_width(&self, id: u16, glyph: u32) -> Option<u16> {
543 let face = self.faces.get(id as usize)?;
544 let font = HarfFontRef::new(&face.bytes).ok()?;
545 let glyph_metrics =
546 GlyphMetrics::new(&font, Size::unscaled(), LocationRef::from(&face.location));
547 glyph_metrics
548 .advance_width(GlyphId::new(glyph))
549 .map(|w| w.round() as u16)
550 }
551
552 pub fn advance_widths(&self, id: u16, glyphs: &[u32]) -> Option<Vec<u16>> {
557 let face = self.faces.get(id as usize)?;
558 let font = HarfFontRef::new(&face.bytes).ok()?;
559 let glyph_metrics =
560 GlyphMetrics::new(&font, Size::unscaled(), LocationRef::from(&face.location));
561 Some(
562 glyphs
563 .iter()
564 .map(|g| {
565 glyph_metrics
566 .advance_width(GlyphId::new(*g))
567 .map(|w| w.round() as u16)
568 .unwrap_or(0)
569 })
570 .collect(),
571 )
572 }
573
574 pub fn char_glyph(&self, id: u16, ch: char) -> Option<u32> {
576 let face = self.faces.get(id as usize)?;
577 let font = HarfFontRef::new(&face.bytes).ok()?;
578 let charmap = font.charmap();
579 charmap.map(ch).map(|g| g.to_u32())
580 }
581
582 pub fn shape(&self, id: u16, text: &str) -> Option<Vec<ShapedGlyph>> {
589 self.shape_with(id, text, &Features::NONE)
590 }
591
592 pub fn shape_with(&self, id: u16, text: &str, features: &Features) -> Option<Vec<ShapedGlyph>> {
594 let face = self.faces.get(id as usize)?;
595 let font = HarfFontRef::new(&face.bytes).ok()?;
596 Some(shape_text(
597 &font,
598 &face.shaper_data,
599 face.instance.as_ref(),
600 text,
601 features,
602 ))
603 }
604}
605
606fn shape_text(
609 font: &HarfFontRef<'_>,
610 data: &ShaperData,
611 instance: Option<&ShaperInstance>,
612 text: &str,
613 features: &Features,
614) -> Vec<ShapedGlyph> {
615 let shaper = data.shaper(font).instance(instance).build();
616 let mut buffer = UnicodeBuffer::new();
617 buffer.push_str(text);
618 buffer.set_cluster_level(BufferClusterLevel::MonotoneCharacters);
619 buffer.set_script(script::LATIN);
620 buffer.set_direction(harfrust::Direction::LeftToRight);
621 buffer.set_language(Language::new("en").unwrap());
622 let mut wanted = Vec::new();
623 if features.small_caps() {
624 wanted.push(Feature::new(Tag::new(b"smcp"), 1, ..));
625 }
626 for setting in features.settings() {
627 wanted.push(Feature::new(Tag::new(&setting.tag), setting.value, ..));
628 }
629 let shaped = shaper.shape(
630 buffer,
631 harfrust::ShapeOptions::new().features(&wanted),
633 );
634 let positions = shaped.glyph_positions();
635 shaped
636 .glyph_infos()
637 .iter()
638 .zip(positions)
639 .map(|(info, pos)| ShapedGlyph {
640 id: info.glyph_id,
641 x_advance: pos.x_advance as u32,
642 cluster: info.cluster,
643 })
644 .collect()
645}
646
647fn feature_tags(font: &skrifa::FontRef) -> Vec<[u8; 4]> {
650 let mut tags = Vec::new();
651 if let Ok(gsub) = font.gsub() {
652 collect_features(gsub.feature_list(), &mut tags);
653 }
654 if let Ok(gpos) = font.gpos() {
655 collect_features(gpos.feature_list(), &mut tags);
656 }
657 tags.sort_unstable();
658 tags.dedup();
659 tags
660}
661
662fn collect_features(
664 list: Result<skrifa::raw::tables::layout::FeatureList<'_>, skrifa::raw::ReadError>,
665 tags: &mut Vec<[u8; 4]>,
666) {
667 let Ok(list) = list else {
668 return;
669 };
670 tags.extend(
671 list.feature_records()
672 .iter()
673 .map(|record| record.feature_tag().into_bytes()),
674 );
675}
676
677fn draws_small_caps(
682 font: &HarfFontRef<'_>,
683 data: &ShaperData,
684 instance: Option<&ShaperInstance>,
685) -> bool {
686 const PROBE: &str = "a";
687 let small_caps = Features::new(true, Vec::new());
688 let plain = shape_text(font, data, instance, PROBE, &Features::NONE);
689 let small = shape_text(font, data, instance, PROBE, &small_caps);
690 plain.iter().map(|g| g.id).ne(small.iter().map(|g| g.id))
691}
692
693struct Cut {
696 name: String,
697 style: String,
698 attributes: FaceAttributes,
699 location: Location,
700 variations: Vec<AxisSetting>,
701}
702
703fn cuts(font: &skrifa::FontRef, source: &FontSource) -> Vec<Cut> {
710 let attributes = font.attributes();
711 let default = FaceAttributes {
712 italic: attributes.style != SlopeStyle::Normal,
713 weight: attributes.weight.value().round().clamp(1.0, 1000.0) as u16,
714 };
715 let whole_file = |attributes| {
716 vec![Cut {
717 name: source.name.clone(),
718 style: source.style.clone(),
719 attributes,
720 location: Location::default(),
721 variations: Vec::new(),
722 }]
723 };
724 if let Some(declared) = source.declared {
725 return whole_file(declared);
726 }
727 let axes: Vec<_> = font.axes().iter().collect();
728 let family = name_string(font, StringId::TYPOGRAPHIC_FAMILY_NAME)
729 .or_else(|| name_string(font, StringId::FAMILY_NAME))
730 .unwrap_or_else(|| source.name.clone());
731 let instances: Vec<Cut> = font
732 .named_instances()
733 .iter()
734 .filter_map(|instance| {
735 let style = name_string(font, instance.subfamily_name_id())?;
736 let settings: Vec<AxisSetting> = axes
737 .iter()
738 .zip(instance.user_coords())
739 .map(|(axis, value)| AxisSetting {
740 tag: axis.tag().into_bytes(),
741 value,
742 })
743 .collect();
744 let location = instance.location();
745 let at_default = location.coords().iter().all(|coord| coord.to_f32() == 0.0);
746 Some(Cut {
747 name: format!("{family} {style}"),
748 attributes: FaceAttributes {
749 italic: default.italic || slanted(&settings),
750 weight: setting(&settings, b"wght")
751 .map(|weight| weight.round().clamp(1.0, 1000.0) as u16)
752 .unwrap_or(default.weight),
753 },
754 style,
755 variations: if at_default { Vec::new() } else { settings },
756 location,
757 })
758 })
759 .collect();
760 if instances.is_empty() {
761 whole_file(default)
762 } else {
763 instances
764 }
765}
766
767fn setting(settings: &[AxisSetting], tag: &[u8; 4]) -> Option<f32> {
769 settings
770 .iter()
771 .find(|setting| setting.tag == *tag)
772 .map(|setting| setting.value)
773}
774
775fn slanted(settings: &[AxisSetting]) -> bool {
778 setting(settings, b"ital").is_some_and(|value| value >= 0.5)
779 || setting(settings, b"slnt").is_some_and(|value| value != 0.0)
780}
781
782fn weight_rank(candidate: u16, desired: u16) -> (u8, u16) {
789 if (400..=500).contains(&desired) {
790 if candidate >= desired && candidate <= 500 {
791 (0, candidate - desired)
792 } else if candidate < desired {
793 (1, desired - candidate)
794 } else {
795 (2, candidate - 500)
796 }
797 } else if desired < 400 {
798 if candidate <= desired {
799 (0, desired - candidate)
800 } else {
801 (1, candidate - desired)
802 }
803 } else if candidate >= desired {
804 (0, candidate - desired)
805 } else {
806 (1, desired - candidate)
807 }
808}
809
810fn name_string<'a>(font: &skrifa::FontRef<'a>, id: StringId) -> Option<String> {
811 font.localized_strings(id)
812 .english_or_first()
813 .map(|s| s.to_string())
814}
815
816fn read_metrics(font: &skrifa::FontRef, location: &Location) -> FontMetricsTable {
817 let metrics = Metrics::new(font, Size::unscaled(), LocationRef::from(location));
818 FontMetricsTable {
819 units_per_em: metrics.units_per_em,
820 ascender: metrics.ascent as i16,
821 descender: metrics.descent as i16,
822 line_gap: metrics.leading as i16,
823 cap_height: metrics.cap_height.unwrap_or_default() as i16,
824 underline: metrics.underline.map(rule),
825 strikeout: metrics.strikeout.map(rule),
826 }
827}
828
829fn rule(decoration: skrifa::metrics::Decoration) -> Rule {
831 Rule {
832 offset: decoration.offset as i16,
833 thickness: decoration.thickness as i16,
834 }
835}
836
837#[derive(Debug, thiserror::Error)]
839pub enum FontError {
840 #[error("font data could not be parsed")]
842 Parse,
843 #[error("font has no family name")]
845 MissingName,
846}
847
848pub const BUNDLED_FONT: &[u8] = include_bytes!("../fonts/EBGaramond-VF.ttf");
850
851pub const BUNDLED_ITALIC: &[u8] = include_bytes!("../fonts/EBGaramond-Italic-VF.ttf");
854
855pub fn bundled_registry() -> Result<FontRegistry, FontError> {
858 let mut registry = FontRegistry::new();
859 for bytes in [BUNDLED_FONT, BUNDLED_ITALIC] {
860 registry.add(FontSource::from_bytes(bytes.to_vec())?)?;
861 }
862 for generic in [
863 GenericFamily::Serif,
864 GenericFamily::SansSerif,
865 GenericFamily::Monospace,
866 ] {
867 registry
868 .map_generic(generic, "eb garamond")
869 .expect("bundled face is registered");
870 }
871 Ok(registry)
872}
873
874#[cfg(test)]
879pub(crate) fn registry_without_substitutions() -> FontRegistry {
880 let mut bytes = BUNDLED_FONT.to_vec();
881 let tables = u16::from_be_bytes([bytes[4], bytes[5]]) as usize;
882 for table in 0..tables {
883 let at = 12 + 16 * table;
884 if &bytes[at..at + 4] == b"GSUB" {
885 bytes[at..at + 4].copy_from_slice(b"xsub");
886 }
887 }
888 let mut registry = FontRegistry::new();
889 let mut source = FontSource::from_bytes(bytes).expect("the face parses");
890 source.declared = Some(FaceAttributes::REGULAR);
891 registry.add(source).expect("the face registers");
892 for generic in [
893 GenericFamily::Serif,
894 GenericFamily::SansSerif,
895 GenericFamily::Monospace,
896 ] {
897 registry
898 .map_generic(generic, "eb garamond")
899 .expect("the face is registered");
900 }
901 registry
902}
903
904#[cfg(test)]
905mod tests {
906 use super::*;
907
908 fn registry() -> FontRegistry {
909 bundled_registry().expect("bundled font parses")
910 }
911
912 #[test]
915 fn registration_assigns_sequential_ids() {
916 let mut registry = FontRegistry::new();
917 assert!(registry.is_empty());
918 let mut source = FontSource::from_bytes(BUNDLED_FONT.to_vec()).unwrap();
919 source.declared = Some(FaceAttributes::REGULAR);
920 let id = registry.add(source.clone()).unwrap();
921 assert_eq!(id, vec![0]);
922 let second = registry.add(source).unwrap();
923 assert_eq!(second, vec![1]);
924 assert_eq!(registry.len(), 2);
925 }
926
927 #[test]
932 fn variable_files_register_their_named_instances() {
933 let registry = registry();
934 let cuts: Vec<(&str, bool, u16)> = (0..registry.len() as u16)
935 .map(|id| {
936 let entry = registry.font_ref(id).unwrap();
937 (
938 entry.style.as_str(),
939 entry.attributes.italic,
940 entry.attributes.weight,
941 )
942 })
943 .collect();
944 assert_eq!(
945 cuts,
946 vec![
947 ("Regular", false, 400),
948 ("Medium", false, 500),
949 ("SemiBold", false, 600),
950 ("Bold", false, 700),
951 ("ExtraBold", false, 800),
952 ("Italic", true, 400),
953 ("Medium Italic", true, 500),
954 ("SemiBold Italic", true, 600),
955 ("Bold Italic", true, 700),
956 ("ExtraBold Italic", true, 800),
957 ],
958 );
959 assert_eq!(registry.variations(0).unwrap(), &[]);
960 assert_eq!(
961 registry.variations(3).unwrap(),
962 &[AxisSetting {
963 tag: *b"wght",
964 value: 700.0,
965 }],
966 );
967 let regular: u32 = registry
970 .shape(0, "quantities")
971 .unwrap()
972 .iter()
973 .map(|g| g.x_advance)
974 .sum();
975 let bold: u32 = registry
976 .shape(3, "quantities")
977 .unwrap()
978 .iter()
979 .map(|g| g.x_advance)
980 .sum();
981 assert!(
982 bold > regular,
983 "bold ({bold}) is no wider than regular ({regular})"
984 );
985 }
986
987 #[test]
991 fn faces_match_by_slope_then_weight() {
992 let registry = registry();
993 let select = |italic, weight| {
994 registry
995 .select("EB Garamond", FaceAttributes { italic, weight })
996 .unwrap()
997 };
998 assert_eq!(select(false, 400).id, 0);
999 assert_eq!(select(true, 400).id, 5);
1000 assert_eq!(select(false, 700).id, 3);
1001 assert_eq!(select(true, 700).id, 8, "no bold italic cut");
1002 assert_eq!(select(false, 450).attributes.weight, 500);
1005 assert_eq!(select(false, 100).attributes.weight, 400);
1006 assert_eq!(select(false, 1000).attributes.weight, 800);
1007 assert_eq!(registry.select("nowhere", FaceAttributes::REGULAR), None);
1008 }
1009
1010 #[test]
1013 fn a_match_reports_the_face_it_settled_for() {
1014 let mut registry = FontRegistry::new();
1015 registry
1016 .add(FontSource::from_bytes(BUNDLED_FONT.to_vec()).unwrap())
1017 .unwrap();
1018 let found = registry
1019 .select(
1020 "eb garamond",
1021 FaceAttributes {
1022 italic: true,
1023 weight: 400,
1024 },
1025 )
1026 .unwrap();
1027 assert_eq!(found.id, 0);
1028 assert!(!found.attributes.italic, "there is no italic cut to find");
1029 }
1030
1031 #[test]
1035 fn a_declared_face_registers_as_the_one_cut_it_was_called() {
1036 let mut registry = FontRegistry::new();
1037 let mut source = FontSource::from_bytes(BUNDLED_FONT.to_vec()).unwrap();
1038 source.family = "house".into();
1039 source.declared = Some(FaceAttributes {
1040 italic: true,
1041 weight: 700,
1042 });
1043 registry.add(source).unwrap();
1044 assert_eq!(registry.len(), 1);
1045 assert_eq!(
1046 registry.font_ref(0).unwrap().attributes,
1047 FaceAttributes {
1048 italic: true,
1049 weight: 700,
1050 },
1051 );
1052 }
1053
1054 #[test]
1057 fn metrics_parse_in_font_units() {
1058 let registry = registry();
1059 let metrics = registry.metrics(0).unwrap();
1060 assert_eq!(metrics.units_per_em, 1000);
1061 assert_eq!(metrics.ascender, 1007);
1062 assert_eq!(metrics.descender, -298);
1063 assert_eq!(metrics.line_gap, 0);
1064 }
1065
1066 #[test]
1070 fn metrics_carry_the_underline_and_the_strikeout() {
1071 let registry = registry();
1072 let metrics = registry.metrics(0).unwrap();
1073 let underline = metrics.underline.expect("the face declares an underline");
1074 let strikeout = metrics.strikeout.expect("the face declares a strikeout");
1075 assert!(
1076 underline.offset < 0,
1077 "the underline sits above the baseline: {underline:?}",
1078 );
1079 assert!(underline.thickness > 0, "{underline:?}");
1080 assert!(
1081 strikeout.offset > 0,
1082 "the strikeout sits below the baseline: {strikeout:?}",
1083 );
1084 assert!(strikeout.thickness > 0, "{strikeout:?}");
1085 }
1086
1087 #[test]
1089 fn advance_widths_match_hmtx() {
1090 let registry = registry();
1091 let widths = registry.advance_widths(0, &[490, 991, 2426]).unwrap();
1094 assert_eq!(widths, vec![495, 776, 200]);
1095 assert_eq!(registry.advance_width(0, 490).unwrap(), 495);
1096 }
1097
1098 #[test]
1100 fn charmap_maps_to_nominal_glyphs() {
1101 let registry = registry();
1102 assert_eq!(registry.char_glyph(0, 'o'), Some(490));
1103 assert_eq!(registry.char_glyph(0, 'A'), Some(1));
1104 assert_eq!(registry.char_glyph(0, '\u{10FFF0}'), None);
1105 }
1106
1107 #[test]
1110 fn identity_comes_from_the_name_table() {
1111 let registry = registry();
1112 let entry = registry.font_ref(0).unwrap();
1113 assert_eq!(entry.family, "eb garamond");
1114 assert_eq!(entry.name, "EB Garamond Regular");
1115 assert_eq!(entry.style, "Regular");
1116 }
1117
1118 #[test]
1120 fn generic_families_map_to_bundled_defaults() {
1121 let registry = registry();
1122 for generic in [
1123 GenericFamily::Serif,
1124 GenericFamily::SansSerif,
1125 GenericFamily::Monospace,
1126 ] {
1127 assert_eq!(registry.generic(generic), Some(0));
1128 }
1129 assert_eq!(GenericFamily::parse("SERIF"), Some(GenericFamily::Serif));
1130 assert_eq!(GenericFamily::parse("fancy"), None);
1131 }
1132
1133 #[test]
1135 fn families_resolve_case_insensitively() {
1136 let registry = registry();
1137 assert_eq!(registry.by_family("EB Garamond"), Some(0));
1138 assert_eq!(registry.by_family("nope"), None);
1139 }
1140
1141 #[test]
1144 fn garbage_bytes_fail_at_registration() {
1145 let err = FontSource::from_bytes(vec![0; 64]).unwrap_err();
1146 assert!(matches!(err, FontError::Parse));
1147 }
1148
1149 #[test]
1152 fn shaping_matches_hb_shape_reference() {
1153 let registry = registry();
1154 let shaped = registry.shape(0, "AVAToffice quantities").unwrap();
1155 let expected: &[(u32, u32)] = &[
1156 (1, 552), (113, 542), (1, 597), (98, 565), (490, 495), (991, 776), (430, 387), (440, 390), (2426, 200),
1165 (505, 522),
1166 (519, 527),
1167 (415, 399),
1168 (484, 528),
1169 (516, 314),
1170 (462, 245),
1171 (516, 314),
1172 (462, 245),
1173 (440, 390),
1174 (509, 323),
1175 ];
1176 assert_eq!(
1177 shaped
1178 .iter()
1179 .map(|g| (g.id, g.x_advance))
1180 .collect::<Vec<_>>(),
1181 expected,
1182 "harfrust disagrees with hb-shape on the reference string"
1183 );
1184 }
1185
1186 #[test]
1189 fn a_face_answers_for_the_features_it_carries() {
1190 let registry = registry();
1191 for tag in [b"onum", b"ss01", b"sups", b"liga", b"smcp"] {
1192 assert!(registry.has_feature(0, *tag), "the face carries {tag:?}");
1193 }
1194 assert!(!registry.has_feature(0, *b"zero"));
1195 assert!(!registry.has_feature(99, *b"onum"), "no such face");
1196 }
1197
1198 #[test]
1201 fn a_requested_feature_draws_the_glyphs_of_that_feature() {
1202 let registry = registry();
1203 let plain = registry.shape(0, "1805").unwrap();
1204 let old_style = Features::new(false, vec![FeatureSetting::new(*b"onum", 1)]);
1205 let shaped = registry.shape_with(0, "1805", &old_style).unwrap();
1206 assert_ne!(
1207 shaped.iter().map(|g| g.id).collect::<Vec<_>>(),
1208 plain.iter().map(|g| g.id).collect::<Vec<_>>(),
1209 "old-style figures draw other glyphs than the lining ones"
1210 );
1211 }
1212
1213 #[test]
1216 fn a_setting_of_zero_turns_off_a_default_feature() {
1217 let registry = registry();
1218 let ligatures_off = Features::new(false, vec![FeatureSetting::new(*b"liga", 0)]);
1219 let shaped = registry.shape_with(0, "office", &ligatures_off).unwrap();
1220 assert_eq!(shaped.len(), 6, "every letter draws its own glyph");
1221 assert!(
1222 registry.shape(0, "office").unwrap().len() < shaped.len(),
1223 "the ffi ligature forms when nothing turns it off"
1224 );
1225 }
1226
1227 #[test]
1230 fn clusters_index_the_input_text() {
1231 let registry = registry();
1232 let shaped = registry.shape(0, "AVAToffice quantities").unwrap();
1233 let clusters: Vec<u32> = shaped.iter().map(|g| g.cluster).collect();
1234 assert_eq!(
1235 clusters,
1236 vec![
1237 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
1238 ],
1239 "f_f_i (cluster 5) absorbs 'f','f','i' (6, 7) into one glyph"
1240 );
1241 }
1242}