1use std::collections::{BTreeMap, BTreeSet};
20use std::sync::Arc;
21
22use eframe::egui::{FontData, FontDefinitions, FontFamily};
23use odox_core::{Element, Ns};
24
25pub use odox_fonts::Variant;
26
27pub fn family_of(family: &str, variant: Variant) -> FontFamily {
33 let suffix = match (variant.bold, variant.italic) {
34 (false, false) => "",
35 (true, false) => ":bold",
36 (false, true) => ":italic",
37 (true, true) => ":bolditalic",
38 };
39 FontFamily::Name(format!("{family}{suffix}").into())
40}
41
42pub fn for_document(parts: &[&Element]) -> FontDefinitions {
45 definitions(&families_used(parts), &characters_used(parts))
46}
47
48pub fn definitions(families: &BTreeSet<String>, characters: &BTreeSet<char>) -> FontDefinitions {
58 let mut definitions = FontDefinitions::default();
59 let fallback = definitions
60 .families
61 .get(&FontFamily::Proportional)
62 .cloned()
63 .unwrap_or_default();
64
65 let database = odox_fonts::database();
66
67 for family in families {
68 for variant in Variant::ALL {
69 let key = match family_of(family, variant) {
70 FontFamily::Name(name) => name.to_string(),
71 other => format!("{other:?}"),
74 };
75 let mut chain = Vec::new();
76 if let Some(face) =
77 odox_fonts::find(&database, family, variant).and_then(|id| load(&database, id))
78 {
79 definitions.font_data.insert(key.clone(), Arc::new(face));
80 chain.push(key.clone());
81 }
82 chain.extend(fallback.iter().cloned());
83 definitions
84 .families
85 .insert(FontFamily::Name(key.into()), chain);
86 }
87 }
88
89 let mut added = Vec::new();
90 for &character in characters {
91 if character.is_whitespace() || character.is_control() {
92 continue;
93 }
94 let drawn = definitions
95 .font_data
96 .values()
97 .any(|face| odox_fonts::covers(&face.font, face.index, character));
98 if drawn {
99 continue;
100 }
101 let Some(face) = odox_fonts::faces_with(&database, character, Variant::default())
102 .next()
103 .and_then(|id| load(&database, id))
104 else {
105 continue;
106 };
107 let key = format!("machine fallback {}", added.len());
108 definitions.font_data.insert(key.clone(), Arc::new(face));
109 added.push(key);
110 }
111 for chain in definitions.families.values_mut() {
112 chain.extend(added.iter().cloned());
113 }
114 definitions
115}
116
117fn load(database: &odox_fonts::fontdb::Database, id: odox_fonts::fontdb::ID) -> Option<FontData> {
119 let index = database.face(id)?.index;
120 database.with_face_data(id, |data, face_index| FontData {
121 font: data.to_vec().into(),
122 index: face_index.max(index),
126 tweak: eframe::egui::FontTweak::default(),
127 })
128}
129
130pub fn characters_used(parts: &[&Element]) -> BTreeSet<char> {
132 parts
133 .iter()
134 .flat_map(|part| part.plain_text().chars().collect::<Vec<_>>())
135 .collect()
136}
137
138pub fn families_used(parts: &[&Element]) -> BTreeSet<String> {
144 let mut families = BTreeSet::new();
145 let mut faces: BTreeMap<String, String> = BTreeMap::new();
146 for root in parts {
147 collect(root, &mut families, &mut faces);
148 }
149 let resolved: BTreeSet<String> = families
153 .iter()
154 .map(|name| faces.get(name).cloned().unwrap_or_else(|| name.clone()))
155 .collect();
156 resolved
157}
158
159fn collect(
160 element: &Element,
161 families: &mut BTreeSet<String>,
162 faces: &mut BTreeMap<String, String>,
163) {
164 if element.is(&Ns::Style, "font-face")
165 && let Some(name) = element.attr(&Ns::Style, "name")
166 {
167 let family = element
168 .attr(&Ns::Svg, "font-family")
169 .unwrap_or(name)
170 .trim_matches('\'')
171 .to_owned();
172 faces.insert(name.to_owned(), family);
173 }
174 for (ns, local) in [(Ns::Style, "font-name"), (Ns::Fo, "font-family")] {
175 if let Some(name) = element.attr(&ns, local) {
176 let name = name.trim_matches('\'');
177 if !name.is_empty() {
178 families.insert(name.to_owned());
179 }
180 }
181 }
182 for child in element.elements() {
183 collect(child, families, faces);
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn family_of_names_a_variant_with_a_suffix() {
193 let plain = Variant {
194 bold: false,
195 italic: false,
196 };
197 let bold = Variant {
198 bold: true,
199 italic: false,
200 };
201 let italic = Variant {
202 bold: false,
203 italic: true,
204 };
205 let both = Variant {
206 bold: true,
207 italic: true,
208 };
209 assert_eq!(family_of("Arial", plain), FontFamily::Name("Arial".into()));
210 assert_eq!(
211 family_of("Arial", bold),
212 FontFamily::Name("Arial:bold".into())
213 );
214 assert_eq!(
215 family_of("Arial", italic),
216 FontFamily::Name("Arial:italic".into())
217 );
218 assert_eq!(
219 family_of("Arial", both),
220 FontFamily::Name("Arial:bolditalic".into())
221 );
222 }
223
224 #[test]
225 fn a_character_no_loaded_face_has_is_drawn_from_one_of_the_machines() {
226 let database = odox_fonts::database();
227 if odox_fonts::faces_with(&database, '日', Variant::default())
228 .next()
229 .is_none()
230 {
231 eprintln!("no face on this machine has 日: nothing to check");
232 return;
233 }
234 let families = BTreeSet::from(["Liberation Serif".to_owned()]);
235 let defs = definitions(&families, &BTreeSet::from(['日', 'a']));
236 let chain = &defs.families[&family_of("Liberation Serif", Variant::default())];
237 let added: Vec<&String> = chain
238 .iter()
239 .filter(|key| key.starts_with("machine fallback"))
240 .collect();
241 assert_eq!(added.len(), 1, "one face for the one character missing");
242 assert_eq!(chain.last(), Some(added[0]), "it comes last");
243 let face = &defs.font_data[added[0]];
244 assert!(odox_fonts::covers(&face.font, face.index, '日'));
245 }
246}