use valo_text::{FaceSet, Font, FontAttrs};
fn asset(name: &str) -> Vec<u8> {
let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/fonts");
std::fs::read(format!("{dir}/{name}")).unwrap()
}
#[test]
fn from_bytes_reads_embedded_family() {
let font = Font::from_bytes(asset("fira_sans.ttf")).unwrap();
assert_eq!(font.family(), "Fira Sans");
assert_eq!(font.attrs(), FontAttrs::default());
}
#[test]
fn alias_matches_alongside_embedded_name() {
let mut font = Font::from_bytes(asset("fira_sans.ttf")).unwrap();
font.add_alias("Picker Family Name");
font.add_alias("Fira Sans"); let mut c = FaceSet::default();
let id = c.add(font);
assert_eq!(c.family("Fira Sans"), Some(id));
assert_eq!(c.family("Picker Family Name"), Some(id));
assert_eq!(
c.family_variant("Picker Family Name", FontAttrs::default()),
Some(id)
);
assert_eq!(c.family("Unknown"), None);
}
#[test]
fn resolve_searches_every_face_of_a_family() {
let mut c = FaceSet::default();
let latin = c.register("Chunked", asset("fira_sans.ttf")).unwrap();
let arabic = c
.register("Chunked", asset("noto_sans_arabic.ttf"))
.unwrap();
let families = vec!["Chunked".to_owned()];
assert_eq!(c.resolve(&families, FontAttrs::default(), 'A'), latin);
assert_eq!(c.resolve(&families, FontAttrs::default(), 'ع'), arabic);
}
#[test]
fn fallback_chain_expands_to_every_face_of_a_family() {
let mut c = FaceSet::default();
let latin = c.register("Chunked", asset("fira_sans.ttf")).unwrap();
let arabic = c
.register("Chunked", asset("noto_sans_arabic.ttf"))
.unwrap();
assert_eq!(c.faces("Chunked").collect::<Vec<_>>(), vec![latin, arabic]);
let c = c.with_fallbacks(c.faces("Chunked").collect());
let families = vec!["Missing".to_owned()];
assert_eq!(
c.resolve_covered(&families, FontAttrs::default(), 'A'),
(latin, true)
);
assert_eq!(
c.resolve_covered(&families, FontAttrs::default(), 'ع'),
(arabic, true)
);
}
#[test]
fn resolve_prefers_families_over_fallbacks_and_tofus_in_the_first() {
let mut c = FaceSet::default();
let latin = c.register("Fira Sans", asset("fira_sans.ttf")).unwrap();
let hebrew = c
.register("Noto Sans Hebrew", asset("noto_sans_hebrew.ttf"))
.unwrap();
c.add_fallback(hebrew);
let families = vec!["Fira Sans".to_owned()];
assert_eq!(c.resolve(&families, FontAttrs::default(), 'A'), latin);
assert_eq!(c.resolve(&families, FontAttrs::default(), 'א'), hebrew);
assert_eq!(c.resolve(&families, FontAttrs::default(), 'ع'), latin);
}
#[test]
fn appended_faces_are_visible_by_length() {
let font = Font::from_bytes(asset("fira_sans.ttf")).unwrap();
let a = FaceSet::default();
let (b, id) = a.with_font(font);
assert_eq!((a.len(), b.len()), (0, 1));
assert_eq!(b.get(id).family(), "Fira Sans");
}
#[test]
fn paragraphs_report_font_demand() {
use valo_text::{ParagraphBuilder, TextStyle};
let mut fonts = valo_text::FontCollection::new();
let latin = fonts.register("Fira Sans", asset("fira_sans.ttf")).unwrap();
fonts.add_fallback(latin);
let mut b = ParagraphBuilder::new(&mut fonts);
b.add_text(
"hello",
&TextStyle::new("Fira Sans", 16.0, valo_geometry::Color::BLACK),
);
assert!(b.build().demand().is_empty());
let mut b = ParagraphBuilder::new(&mut fonts);
b.add_text(
"hi 🍶 你好",
&TextStyle::new("Noto Sans", 16.0, valo_geometry::Color::BLACK),
);
let p = b.build();
let d = p.demand();
let regular = FontAttrs::default();
assert_eq!(d.families, vec!["Noto Sans".to_owned()]);
assert_eq!(
d.codepoints,
vec![('🍶', regular), ('你', regular), ('好', regular)]
);
assert!(!d.codepoints.iter().any(|&(ch, _)| ch == ' '));
assert!(!d.codepoints.iter().any(|&(ch, _)| ch == 'h'));
}
#[test]
fn shared_bytes_parse_like_owned_bytes() {
struct Mapped(Vec<u8>);
impl AsRef<[u8]> for Mapped {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
let bytes = asset("fira_sans.ttf");
let owned = Font::from_bytes(bytes.clone()).unwrap();
let shared = Font::from_data(std::sync::Arc::new(Mapped(bytes)), 0).unwrap();
assert_eq!(shared.family(), owned.family());
assert_eq!(shared.attrs(), owned.attrs());
assert_eq!(shared.face_index(), 0);
assert_eq!(shared.data(), owned.data());
}
#[test]
fn family_names_match_case_insensitively() {
let mut c = FaceSet::default();
let id = c.add(Font::from_bytes(asset("fira_sans.ttf")).unwrap());
assert_eq!(c.family("fira sans"), Some(id));
assert_eq!(c.family("FIRA SANS"), Some(id));
}
#[test]
fn fallback_chain_picks_nearest_attrs_among_covering() {
let bold_attrs = FontAttrs {
weight: 700,
italic: false,
..Default::default()
};
let mut c = FaceSet::default();
let regular = c
.register_with("Chain", FontAttrs::default(), asset("fira_sans.ttf"))
.unwrap();
let bold = c
.register_with("Chain", bold_attrs, asset("fira_sans.ttf"))
.unwrap();
c.add_fallback(regular);
c.add_fallback(bold);
let families = vec!["Absent".to_owned()];
assert_eq!(c.resolve(&families, bold_attrs, 'A'), bold);
assert_eq!(c.resolve(&families, FontAttrs::default(), 'A'), regular);
}
struct ScriptedSource {
family_bytes: Option<Vec<u8>>,
fallback_bytes: Option<Vec<u8>>,
asked_codepoints: Vec<(char, FontAttrs)>,
}
impl valo_text::FontSource for ScriptedSource {
fn family(&mut self, _name: &str) -> Vec<Font> {
self.family_bytes
.iter()
.filter_map(|bytes| Font::from_bytes(bytes.clone()))
.collect()
}
fn face_for_codepoint(&mut self, codepoint: char, attrs: FontAttrs) -> Option<Font> {
self.asked_codepoints.push((codepoint, attrs));
let font = Font::from_bytes(self.fallback_bytes.clone()?)?;
font.covers(codepoint).then_some(font)
}
}
#[test]
fn grown_by_teaches_the_requested_name_and_answers_once() {
let mut source = ScriptedSource {
family_bytes: Some(asset("fira_sans.ttf")),
fallback_bytes: None,
asked_codepoints: Vec::new(),
};
let base = FaceSet::default();
let demand = valo_text::FontDemand {
families: vec!["my display font".to_owned()],
codepoints: Vec::new(),
};
let grown = base.grown_by(&mut source, &demand).unwrap();
assert!(grown.family("Fira Sans").is_some());
assert!(grown.family("my display font").is_some());
assert!(grown.grown_by(&mut source, &demand).is_none());
}
#[test]
fn grown_by_answers_codepoints_with_the_demanding_attrs() {
let bold_attrs = FontAttrs {
weight: 700,
italic: false,
..Default::default()
};
let mut source = ScriptedSource {
family_bytes: None,
fallback_bytes: Some(asset("noto_sans_arabic.ttf")),
asked_codepoints: Vec::new(),
};
let mut base = FaceSet::default();
base.register("Fira Sans", asset("fira_sans.ttf")).unwrap();
let demand = valo_text::FontDemand {
families: Vec::new(),
codepoints: vec![('ع', bold_attrs)],
};
let grown = base.grown_by(&mut source, &demand).unwrap();
assert_eq!(source.asked_codepoints, vec![('ع', bold_attrs)]);
assert!(grown.resolve_covered(&[], FontAttrs::default(), 'ع').1);
assert!(grown.grown_by(&mut source, &demand).is_none());
}
#[test]
fn grown_by_reports_nothing_when_the_source_cannot_answer() {
let mut source = ScriptedSource {
family_bytes: None,
fallback_bytes: None,
asked_codepoints: Vec::new(),
};
let mut base = FaceSet::default();
base.register("Fira Sans", asset("fira_sans.ttf")).unwrap();
let demand = valo_text::FontDemand {
families: vec!["Ghost".to_owned()],
codepoints: vec![('中', FontAttrs::default())],
};
assert!(base.grown_by(&mut source, &demand).is_none());
}
#[test]
fn grown_by_never_answers_private_use_codepoints() {
let mut source = ScriptedSource {
family_bytes: None,
fallback_bytes: Some(asset("fira_sans.ttf")),
asked_codepoints: Vec::new(),
};
let mut base = FaceSet::default();
base.register("Fira Sans", asset("fira_sans.ttf")).unwrap();
let demand = valo_text::FontDemand {
families: Vec::new(),
codepoints: vec![('\u{E58A}', FontAttrs::default())],
};
assert!(base.grown_by(&mut source, &demand).is_none());
assert!(
source.asked_codepoints.is_empty(),
"the source is never even consulted for PUA"
);
}
#[test]
fn static_fonts_offer_exactly_one_instance() {
let instances = Font::instances_from_data(std::sync::Arc::new(asset("fira_sans.ttf")), 0);
assert_eq!(instances.len(), 1);
assert_eq!(instances[0].attrs(), FontAttrs::default());
assert!(instances[0].variation_coordinates().is_empty());
}