Another project
1use parley::FontWeight as ParleyFontWeight;
2use serde::{Deserialize, Serialize};
3use swash::FontRef;
4
5pub(crate) const SANS_DATA: &[u8] = include_bytes!("../../../assets/DejaVuSans.ttf");
6pub(crate) const MONO_DATA: &[u8] = include_bytes!("../../../assets/DejaVuSansMono.ttf");
7
8pub(crate) const SANS_FAMILY: &str = "DejaVu Sans";
9pub(crate) const MONO_FAMILY: &str = "DejaVu Sans Mono";
10
11#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub enum FontFace {
13 Sans,
14 Mono,
15}
16
17#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub enum FontWeight {
19 Regular,
20 Medium,
21 Semibold,
22 Bold,
23}
24
25#[must_use]
26pub fn load_font(face: FontFace) -> FontRef<'static> {
27 let bytes = data_for(face);
28 let label = family_for(face);
29 let Some(font) = FontRef::from_index(bytes, 0) else {
30 panic!("bundled font {label} failed to parse; binary asset is broken");
31 };
32 font
33}
34
35pub(crate) const fn data_for(face: FontFace) -> &'static [u8] {
36 match face {
37 FontFace::Sans => SANS_DATA,
38 FontFace::Mono => MONO_DATA,
39 }
40}
41
42pub(crate) const fn family_for(face: FontFace) -> &'static str {
43 match face {
44 FontFace::Sans => SANS_FAMILY,
45 FontFace::Mono => MONO_FAMILY,
46 }
47}
48
49pub(crate) const fn parley_weight(weight: FontWeight) -> ParleyFontWeight {
50 match weight {
51 FontWeight::Regular => ParleyFontWeight::NORMAL,
52 FontWeight::Medium => ParleyFontWeight::MEDIUM,
53 FontWeight::Semibold => ParleyFontWeight::SEMI_BOLD,
54 FontWeight::Bold => ParleyFontWeight::BOLD,
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::{
61 FontFace, FontWeight, MONO_DATA, MONO_FAMILY, SANS_DATA, SANS_FAMILY, data_for, family_for,
62 load_font, parley_weight,
63 };
64 use parley::FontWeight as ParleyFontWeight;
65
66 #[test]
67 fn sans_and_mono_data_are_distinct_bundled_assets() {
68 assert!(!SANS_DATA.is_empty());
69 assert!(!MONO_DATA.is_empty());
70 assert_ne!(SANS_DATA, MONO_DATA);
71 }
72
73 #[test]
74 fn data_for_resolves_face_to_its_asset() {
75 assert_eq!(data_for(FontFace::Sans), SANS_DATA);
76 assert_eq!(data_for(FontFace::Mono), MONO_DATA);
77 }
78
79 #[test]
80 fn family_for_resolves_face_to_its_family_name() {
81 assert_eq!(family_for(FontFace::Sans), SANS_FAMILY);
82 assert_eq!(family_for(FontFace::Mono), MONO_FAMILY);
83 }
84
85 #[test]
86 fn load_font_returns_valid_fontref_for_each_face() {
87 let sans = load_font(FontFace::Sans);
88 let mono = load_font(FontFace::Mono);
89 assert!(sans.charmap().map(u32::from('A')) > 0);
90 assert!(mono.charmap().map(u32::from('A')) > 0);
91 }
92
93 #[test]
94 fn bundled_fonts_cover_arabic_baseline_for_complex_script_dim_labels() {
95 let sans = load_font(FontFace::Sans);
96 let mono = load_font(FontFace::Mono);
97 ['\u{0627}', '\u{0644}', '\u{0637}', '\u{0648}', '\u{0645}']
98 .into_iter()
99 .for_each(|ch| {
100 let cp = u32::from(ch);
101 assert!(
102 sans.charmap().map(cp) > 0,
103 "sans must cover arabic codepoint U+{cp:04X}",
104 );
105 assert!(
106 mono.charmap().map(cp) > 0,
107 "mono must cover arabic codepoint U+{cp:04X}",
108 );
109 });
110 }
111
112 #[test]
113 fn parley_weight_round_trips_each_step() {
114 assert_eq!(parley_weight(FontWeight::Regular), ParleyFontWeight::NORMAL);
115 assert_eq!(parley_weight(FontWeight::Medium), ParleyFontWeight::MEDIUM);
116 assert_eq!(
117 parley_weight(FontWeight::Semibold),
118 ParleyFontWeight::SEMI_BOLD
119 );
120 assert_eq!(parley_weight(FontWeight::Bold), ParleyFontWeight::BOLD);
121 }
122}