1use crate::CompilerConfiguration;
6use crate::diagnostics::BuildDiagnostics;
7#[cfg(not(target_arch = "wasm32"))]
8use crate::embedded_resources::{BitmapFont, BitmapGlyph, BitmapGlyphs, CharacterMapEntry};
9#[cfg(not(target_arch = "wasm32"))]
10use crate::expression_tree::BuiltinFunction;
11use crate::expression_tree::{Expression, Unit};
12use crate::object_tree::*;
13use std::collections::HashMap;
14use std::collections::HashSet;
15use std::rc::Rc;
16
17use i_slint_common::sharedfontique::{self, fontique, skrifa};
18#[cfg(not(target_arch = "wasm32"))]
19use skrifa::MetadataProvider;
20
21#[derive(Clone)]
22struct Font {
23 font: fontique::QueryFont,
24}
25
26#[cfg(feature = "software-renderer")]
30pub struct FontCollection {
31 pub collection: sharedfontique::Collection,
32 pub custom_font_paths: HashMap<fontique::FamilyId, std::path::PathBuf>,
33 pub custom_fonts: HashMap<std::path::PathBuf, fontique::QueryFont>,
34}
35
36#[cfg(feature = "software-renderer")]
40pub type SharedFontCollection = std::sync::Arc<
41 std::sync::LazyLock<
42 std::sync::Mutex<FontCollection>,
43 Box<dyn FnOnce() -> std::sync::Mutex<FontCollection> + Send + Sync>,
44 >,
45>;
46
47#[cfg(feature = "software-renderer")]
50pub fn read_custom_fonts<'a>(
51 all_docs: impl Iterator<Item = &'a Document>,
52 diag: &mut BuildDiagnostics,
53) -> Vec<(std::path::PathBuf, Vec<u8>)> {
54 let mut fonts = Vec::new();
55 for doc in all_docs {
56 for (font_path, import_token) in doc.custom_fonts.iter() {
57 match std::fs::read(font_path.as_str()) {
58 Err(e) => diag.push_error(format!("Error loading font: {e}"), import_token),
59 Ok(bytes) => fonts.push((font_path.as_str().into(), bytes)),
60 }
61 }
62 }
63 fonts
64}
65
66#[cfg(feature = "software-renderer")]
68pub fn shared_font_collection(
69 custom_fonts: Vec<(std::path::PathBuf, Vec<u8>)>,
70) -> SharedFontCollection {
71 let init: Box<dyn FnOnce() -> std::sync::Mutex<FontCollection> + Send + Sync> =
72 Box::new(move || {
73 let mut collection = sharedfontique::create_collection(true);
74 let mut custom_font_paths = HashMap::new();
75 let mut custom_font_map = HashMap::new();
76 for (path, bytes) in custom_fonts {
77 if let Some(font) = collection
78 .register_fonts(bytes.into(), None)
79 .first()
80 .and_then(|(id, infos)| collection.get_font_for_info(*id, infos.first()?))
81 {
82 custom_font_paths.insert(font.family.0, path.clone());
83 custom_font_map.insert(path, font);
84 }
85 }
86 std::sync::Mutex::new(FontCollection {
87 collection,
88 custom_font_paths,
89 custom_fonts: custom_font_map,
90 })
91 });
92 std::sync::Arc::new(std::sync::LazyLock::new(init))
93}
94
95fn swash_font_ref(font: &Font) -> swash::FontRef<'_> {
96 swash::FontRef::from_index(font.font.blob.data(), font.font.index as usize).unwrap()
97}
98
99#[cfg(target_arch = "wasm32")]
100pub fn embed_glyphs<'a>(
101 _component: &Document,
102 _compiler_config: &CompilerConfiguration,
103 _scale_factor: f64,
104 _pixel_sizes: Vec<i16>,
105 _font_weights: Vec<u16>,
106 _characters_seen: HashSet<char>,
107 _all_docs: impl Iterator<Item = &'a crate::object_tree::Document> + 'a,
108 _diag: &mut BuildDiagnostics,
109) -> bool {
110 false
111}
112
113#[cfg(not(target_arch = "wasm32"))]
114pub fn embed_glyphs(
115 doc: &Document,
116 compiler_config: &CompilerConfiguration,
117 mut pixel_sizes: Vec<i16>,
118 font_weights: Vec<u16>,
119 mut characters_seen: HashSet<char>,
120 font_collection: &SharedFontCollection,
121 diag: &mut BuildDiagnostics,
122) {
123 use crate::diagnostics::Spanned;
124
125 let generic_diag_location = doc.node.as_ref().map(|n| n.to_source_location());
126 let scale_factor = compiler_config.const_scale_factor.unwrap_or(1.);
127
128 characters_seen.extend(
129 ('a'..='z')
130 .chain('A'..='Z')
131 .chain('0'..='9')
132 .chain(" '!\"#$%&()*+,-./:;<=>?@\\[]{}^_|~".chars())
133 .chain(std::iter::once('●'))
134 .chain(std::iter::once('…')),
135 );
136
137 if let Ok(sizes_str) = std::env::var("SLINT_FONT_SIZES") {
138 for custom_size_str in sizes_str.split(',') {
139 let custom_size = if let Ok(custom_size) = custom_size_str
140 .parse::<f32>()
141 .map(|size_as_float| (size_as_float * scale_factor) as i16)
142 {
143 custom_size
144 } else {
145 diag.push_error(
146 format!(
147 "Invalid font size '{custom_size_str}' specified in `SLINT_FONT_SIZES`"
148 ),
149 &generic_diag_location,
150 );
151 return;
152 };
153
154 if let Err(pos) = pixel_sizes.binary_search(&custom_size) {
155 pixel_sizes.insert(pos, custom_size)
156 }
157 }
158 }
159
160 let fallback_fonts = get_fallback_fonts();
161
162 let mut shared = font_collection.lock().unwrap();
165 let FontCollection { collection, custom_font_paths: font_paths, custom_fonts } = &mut *shared;
166
167 let mut custom_face_error = false;
168
169 let default_fonts: Vec<(std::path::PathBuf, fontique::QueryFont)> = if !collection
170 .default_fonts
171 .is_empty()
172 {
173 collection.default_fonts.as_ref().clone()
174 } else {
175 let mut default_fonts: Vec<(std::path::PathBuf, fontique::QueryFont)> = Vec::new();
176
177 for c in doc.exported_roots() {
178 let (family, source_location) = c
179 .root_element
180 .borrow()
181 .bindings
182 .get("default-font-family")
183 .and_then(|binding| match &binding.borrow().expression {
184 Expression::StringLiteral(family) => {
185 Some((Some(family.clone()), binding.borrow().span.clone()))
186 }
187 _ => None,
188 })
189 .unwrap_or_default();
190
191 let font = {
192 let mut query = collection.query();
193
194 query.set_families(
195 family
196 .as_ref()
197 .map(|family| fontique::QueryFamily::from(family.as_str()))
198 .into_iter()
199 .chain(
200 sharedfontique::FALLBACK_FAMILIES
201 .into_iter()
202 .map(fontique::QueryFamily::Generic),
203 ),
204 );
205
206 let mut font = None;
207
208 query.matches_with(|queried_font| {
209 font = Some(queried_font.clone());
210 fontique::QueryStatus::Stop
211 });
212 font
213 };
214
215 match font {
216 None => {
217 if let Some(source_location) = source_location {
218 diag.push_error_with_span("could not find font that provides specified family, falling back to Sans-Serif".to_string(), source_location);
219 } else {
220 diag.push_error(
221 "internal error: could not determine a default font for sans-serif"
222 .to_string(),
223 &generic_diag_location,
224 );
225 };
226 }
227 Some(query_font) => {
228 if let Some(font_info) = collection
229 .family(query_font.family.0)
230 .and_then(|family_info| family_info.fonts().first().cloned())
231 {
232 let path = if let Some(path) = font_paths.get(&query_font.family.0) {
233 path.clone()
234 } else {
235 match &font_info.source().kind {
236 fontique::SourceKind::Path(path) => path.to_path_buf(),
237 fontique::SourceKind::Memory(_) => {
238 diag.push_error(
239 "internal error: memory fonts are not supported in the compiler"
240 .to_string(),
241 &generic_diag_location,
242 );
243 custom_face_error = true;
244 continue;
245 }
246 }
247 };
248 font_paths.insert(query_font.family.0, path.clone());
249 default_fonts.push((path.clone(), query_font));
250 }
251 }
252 }
253 }
254
255 default_fonts
256 };
257
258 if custom_face_error {
259 return;
260 }
261
262 let register_embedded_font = |path: &std::path::Path, embedded_bitmap_font: BitmapFont| {
263 let resource_id = doc.embedded_file_resources.borrow_mut().push_and_get_key(
264 crate::embedded_resources::EmbeddedResources {
265 path: Some(path.to_string_lossy().as_ref().into()),
266 kind: crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(
267 embedded_bitmap_font,
268 ),
269 },
270 );
271
272 for c in doc.exported_roots() {
273 c.init_code.borrow_mut().font_registration_code.push(Expression::FunctionCall {
274 function: BuiltinFunction::RegisterBitmapFont.into(),
275 arguments: vec![Expression::NumberLiteral(resource_id.0 as _, Unit::None)],
276 source_location: None,
277 });
278 }
279 };
280
281 let mut embed_font_by_path = |path: &std::path::Path, font: &fontique::QueryFont| {
282 let Some(family_name) = collection.family_name(font.family.0).to_owned() else {
283 diag.push_error(
284 format!(
285 "internal error: TrueType font without family name encountered: {}",
286 path.display()
287 ),
288 &generic_diag_location,
289 );
290 return;
291 };
292
293 let Some(font_ref) = skrifa::FontRef::from_index(font.blob.data(), font.index).ok() else {
294 diag.push_error(
295 format!("internal error: failed to parse font: {}", path.display()),
296 &generic_diag_location,
297 );
298 return;
299 };
300 let axes = font_ref.axes();
301 let wght_axis = axes.iter().find(|axis| axis.tag() == skrifa::Tag::new(b"wght"));
302
303 if let Some(wght_axis) = wght_axis {
304 let weights = if font_weights.is_empty() {
306 vec![fontique::FontWeight::NORMAL.value() as u16]
307 } else {
308 font_weights.clone()
309 };
310 for &weight in &weights {
311 let clamped = (weight as f32).clamp(wght_axis.min_value(), wght_axis.max_value());
312 let location = axes.location([("wght", clamped)]);
313 let variations = vec![(skrifa::Tag::new(b"wght"), clamped)];
314
315 let embedded = embed_font(
316 family_name.to_owned(),
317 Font { font: font.clone() },
318 &pixel_sizes,
319 characters_seen.iter().cloned(),
320 &fallback_fonts,
321 compiler_config,
322 location.coords(),
323 &variations,
324 Some(weight),
325 );
326 register_embedded_font(path, embedded);
327 }
328 } else {
329 let embedded = embed_font(
331 family_name.to_owned(),
332 Font { font: font.clone() },
333 &pixel_sizes,
334 characters_seen.iter().cloned(),
335 &fallback_fonts,
336 compiler_config,
337 &[],
338 &[],
339 None,
340 );
341 register_embedded_font(path, embedded);
342 }
343 };
344
345 for (path, font) in default_fonts.iter() {
348 custom_fonts.remove(path);
349 embed_font_by_path(path, font);
350 }
351
352 for (path, font) in custom_fonts.iter() {
353 embed_font_by_path(path, font);
354 }
355}
356
357#[inline(never)] fn get_fallback_fonts() -> Vec<Font> {
359 let mut fallback_fonts = Vec::new();
360
361 let mut collection = sharedfontique::create_collection(false);
362 let mut query = collection.query();
363 query.set_families(
364 sharedfontique::FALLBACK_FAMILIES.into_iter().map(fontique::QueryFamily::Generic).chain(
365 core::iter::once(fontique::QueryFamily::Generic(fontique::GenericFamily::Emoji)),
366 ),
367 );
368
369 query.matches_with(|query_font| {
370 fallback_fonts.push(Font { font: query_font.clone() });
371 fontique::QueryStatus::Continue
372 });
373
374 fallback_fonts
375}
376
377#[cfg(not(target_arch = "wasm32"))]
378fn embed_font(
379 family_name: String,
380 font: Font,
381 pixel_sizes: &[i16],
382 character_coverage: impl Iterator<Item = char>,
383 fallback_fonts: &[Font],
384 _compiler_config: &CompilerConfiguration,
385 normalized_coords: &[skrifa::instance::NormalizedCoord],
386 _variations: &[(skrifa::Tag, f32)],
387 override_weight: Option<u16>,
388) -> BitmapFont {
389 let coords_i16: Vec<i16> = normalized_coords.iter().map(|c| c.to_bits()).collect();
390
391 let mut character_map: Vec<CharacterMapEntry> = character_coverage
392 .filter(|code_point| {
393 core::iter::once(&font)
394 .chain(fallback_fonts.iter())
395 .any(|font| swash_font_ref(font).charmap().map(*code_point) != 0)
396 })
397 .enumerate()
398 .map(|(glyph_index, code_point)| CharacterMapEntry {
399 code_point,
400 glyph_index: u16::try_from(glyph_index)
401 .expect("more than 65535 glyphs are not supported"),
402 })
403 .collect();
404
405 #[cfg(feature = "sdf-fonts")]
406 let glyphs = if _compiler_config.use_sdf_fonts {
407 embed_sdf_glyphs(pixel_sizes, &character_map, &font, fallback_fonts, _variations)
408 } else {
409 embed_alpha_map_glyphs(pixel_sizes, &character_map, &font, fallback_fonts, &coords_i16)
410 };
411 #[cfg(not(feature = "sdf-fonts"))]
412 let glyphs =
413 embed_alpha_map_glyphs(pixel_sizes, &character_map, &font, fallback_fonts, &coords_i16);
414
415 character_map.sort_by_key(|entry| entry.code_point);
416
417 let font_ref = skrifa::FontRef::from_index(font.font.blob.data(), font.font.index).unwrap();
418 let location = skrifa::instance::LocationRef::new(normalized_coords);
419 let metrics =
420 skrifa::metrics::Metrics::new(&font_ref, skrifa::instance::Size::unscaled(), location);
421 let attrs = skrifa::attribute::Attributes::new(&font_ref);
422
423 BitmapFont {
424 family_name,
425 character_map,
426 units_per_em: metrics.units_per_em as f32,
427 ascent: metrics.ascent,
428 descent: metrics.descent,
429 x_height: metrics.x_height.unwrap_or_default(),
430 cap_height: metrics.cap_height.unwrap_or_default(),
431 glyphs,
432 weight: override_weight.unwrap_or(attrs.weight.value() as u16),
433 italic: attrs.style != skrifa::attribute::Style::Normal,
434 #[cfg(feature = "sdf-fonts")]
435 sdf: _compiler_config.use_sdf_fonts,
436 #[cfg(not(feature = "sdf-fonts"))]
437 sdf: false,
438 }
439}
440
441#[cfg(not(target_arch = "wasm32"))]
442fn embed_alpha_map_glyphs(
443 pixel_sizes: &[i16],
444 character_map: &Vec<CharacterMapEntry>,
445 font: &Font,
446 fallback_fonts: &[Font],
447 normalized_coords: &[i16],
448) -> Vec<BitmapGlyphs> {
449 use rayon::prelude::*;
450 use std::cell::RefCell;
451
452 thread_local! {
453 static SCALE_CONTEXT: RefCell<swash::scale::ScaleContext> =
454 RefCell::new(swash::scale::ScaleContext::new());
455 }
456
457 pixel_sizes
458 .par_iter()
459 .map(|pixel_size| {
460 let glyph_data = character_map
461 .par_iter()
462 .map(|CharacterMapEntry { code_point, .. }| {
463 let font_to_use = core::iter::once(font)
464 .chain(fallback_fonts.iter())
465 .find(|f| swash_font_ref(f).charmap().map(*code_point) != 0)
466 .unwrap_or(font);
467
468 let font_ref = swash_font_ref(font_to_use);
469 let glyph_id = font_ref.charmap().map(*code_point);
470 let gm = font_ref.glyph_metrics(normalized_coords);
471 let fm = font_ref.metrics(normalized_coords);
472 let scale = *pixel_size as f32 / fm.units_per_em as f32;
473 let advance_width = gm.advance_width(glyph_id) * scale;
474
475 SCALE_CONTEXT.with(|ctx| {
476 let font_ref = swash_font_ref(font_to_use);
477 let mut ctx = ctx.borrow_mut();
478 let mut scaler = ctx
479 .builder(font_ref)
480 .size(*pixel_size as f32)
481 .normalized_coords(normalized_coords)
482 .build();
483 let image = swash::scale::Render::new(&[swash::scale::Source::Outline])
484 .format(swash::zeno::Format::Alpha)
485 .render(&mut scaler, glyph_id);
486
487 match image {
488 Some(image) => {
489 let p = image.placement;
490 BitmapGlyph {
491 x: i16::try_from(p.left * 64)
492 .expect("large glyph x coordinate"),
493 y: i16::try_from((p.top - p.height as i32) * 64)
494 .expect("large glyph y coordinate"),
495 width: i16::try_from(p.width).expect("large width"),
496 height: i16::try_from(p.height).expect("large height"),
497 x_advance: i16::try_from((advance_width * 64.) as i64)
498 .expect("large advance width"),
499 data: image.data,
500 }
501 }
502 None => BitmapGlyph {
503 x: 0,
504 y: 0,
505 width: 0,
506 height: 0,
507 x_advance: i16::try_from((advance_width * 64.) as i64)
508 .expect("large advance width"),
509 data: vec![],
510 },
511 }
512 })
513 })
514 .collect();
515
516 BitmapGlyphs { pixel_size: *pixel_size, glyph_data }
517 })
518 .collect()
519}
520
521#[cfg(all(not(target_arch = "wasm32"), feature = "sdf-fonts"))]
522fn embed_sdf_glyphs(
523 pixel_sizes: &[i16],
524 character_map: &Vec<CharacterMapEntry>,
525 font: &Font,
526 fallback_fonts: &[Font],
527 variations: &[(skrifa::Tag, f32)],
528) -> Vec<BitmapGlyphs> {
529 use rayon::prelude::*;
530
531 const RANGE: f64 = 6.;
532
533 let Some(max_size) = pixel_sizes.iter().max() else {
534 return Vec::new();
535 };
536 let min_size = pixel_sizes.iter().min().expect("we have a 'max' so the vector is not empty");
537 let target_pixel_size = (max_size * 2 / 3).max(16).min(RANGE as i16 * min_size);
538
539 let glyph_data = character_map
540 .par_iter()
541 .map(|CharacterMapEntry { code_point, .. }| {
542 core::iter::once(font)
543 .chain(fallback_fonts.iter())
544 .find_map(|font| {
545 (swash_font_ref(font).charmap().map(*code_point) != 0).then(|| {
546 generate_sdf_for_glyph(
547 font,
548 *code_point,
549 target_pixel_size,
550 RANGE,
551 variations,
552 )
553 })
554 })
555 .unwrap_or_else(|| {
556 generate_sdf_for_glyph(font, *code_point, target_pixel_size, RANGE, variations)
557 })
558 .unwrap_or_default()
559 })
560 .collect::<Vec<_>>();
561
562 vec![BitmapGlyphs { pixel_size: target_pixel_size, glyph_data }]
563}
564
565#[cfg(all(not(target_arch = "wasm32"), feature = "sdf-fonts"))]
566fn generate_sdf_for_glyph(
567 font: &Font,
568 code_point: char,
569 target_pixel_size: i16,
570 range: f64,
571 variations: &[(skrifa::Tag, f32)],
572) -> Option<BitmapGlyph> {
573 use fdsm::transform::Transform;
574 use nalgebra::{Affine2, Similarity2, Vector2};
575
576 let mut face =
577 fdsm_ttf_parser::ttf_parser::Face::parse(font.font.blob.data(), font.font.index).unwrap();
578 for &(tag, value) in variations {
579 face.set_variation(
580 fdsm_ttf_parser::ttf_parser::Tag(u32::from_be_bytes(tag.to_be_bytes())),
581 value,
582 );
583 }
584 let glyph_id = face.glyph_index(code_point).unwrap_or_default();
585
586 let font_ref = skrifa::FontRef::from_index(font.font.blob.data(), font.font.index).unwrap();
587 let variation_settings: Vec<_> =
588 variations.iter().map(|&(tag, value)| (tag, value)).collect::<Vec<_>>();
589 let location = font_ref.axes().location(variation_settings);
590 let metrics = skrifa::metrics::Metrics::new(
591 &font_ref,
592 skrifa::instance::Size::unscaled(),
593 skrifa::instance::LocationRef::from(&location),
594 );
595 let target_pixel_size = target_pixel_size as f64;
596 let scale = target_pixel_size / metrics.units_per_em as f64;
597
598 let Some(bbox) = face.glyph_bounding_box(glyph_id) else {
600 return Some(BitmapGlyph {
602 x_advance: (face.glyph_hor_advance(glyph_id).unwrap_or(0) as f64 * scale * 64.) as i16,
603 ..Default::default()
604 });
605 };
606
607 let mut shape = fdsm_ttf_parser::load_shape_from_face(&face, glyph_id)?;
608
609 let width = ((bbox.x_max as f64 - bbox.x_min as f64) * scale + 2.).ceil() as u32;
610 let height = ((bbox.y_max as f64 - bbox.y_min as f64) * scale + 2.).ceil() as u32;
611 let transformation = nalgebra::convert::<_, Affine2<f64>>(Similarity2::new(
612 Vector2::new(1. - bbox.x_min as f64 * scale, 1. - bbox.y_min as f64 * scale),
613 0.,
614 scale,
615 ));
616
617 shape.transform(&transformation);
623
624 let prepared_shape = shape.prepare();
625
626 let mut sdf = image::GrayImage::new(width, height);
629 fdsm::generate::generate_sdf(&prepared_shape, range, &mut sdf);
630 fdsm::render::correct_sign_sdf(
631 &mut sdf,
632 &prepared_shape,
633 fdsm::bezier::scanline::FillRule::Nonzero,
634 );
635
636 let mut glyph_data = sdf.into_raw();
637
638 for x in &mut glyph_data {
640 *x = x.wrapping_sub(128);
641 }
642
643 let (w, h) = (width as usize, height as usize);
645 for idx in 0..glyph_data.len() / 2 {
646 glyph_data.swap(idx, (h - idx / w - 1) * w + idx % w);
647 }
648
649 glyph_data.push(0);
652
653 let bg = BitmapGlyph {
654 x: i16::try_from((-(1. - bbox.x_min as f64 * scale) * 64.).ceil() as i32)
655 .expect("large glyph x coordinate"),
656 y: i16::try_from((-(1. - bbox.y_min as f64 * scale) * 64.).ceil() as i32)
657 .expect("large glyph y coordinate"),
658 width: i16::try_from(width).expect("large width"),
659 height: i16::try_from(height).expect("large height"),
660 x_advance: i16::try_from(
661 (face.glyph_hor_advance(glyph_id).unwrap() as f64 * scale * 64.).round() as i32,
662 )
663 .expect("large advance width"),
664 data: glyph_data,
665 };
666
667 Some(bg)
668}
669
670fn try_extract_literal_from_element(
671 elem: &ElementRc,
672 property_name: &str,
673 unit: Unit,
674) -> Option<f64> {
675 elem.borrow().bindings.get(property_name).and_then(|expression| {
676 match &expression.borrow().expression {
677 Expression::NumberLiteral(value, u) if *u == unit => Some(*value),
678 Expression::Cast { from, .. } => match from.as_ref() {
679 Expression::NumberLiteral(value, u) if *u == unit => Some(*value),
680 _ => None,
681 },
682 _ => None,
683 }
684 })
685}
686
687pub fn collect_font_sizes_used(
688 component: &Rc<Component>,
689 scale_factor: f64,
690 sizes_seen: &mut Vec<i16>,
691) {
692 let mut add_font_size = |logical_size: f64| {
693 let pixel_size = (logical_size * scale_factor) as i16;
694 match sizes_seen.binary_search(&pixel_size) {
695 Ok(_) => {}
696 Err(pos) => sizes_seen.insert(pos, pixel_size),
697 }
698 };
699
700 recurse_elem_including_sub_components(component, &(), &mut |elem, _| match elem
701 .borrow()
702 .base_type
703 .to_string()
704 .as_str()
705 {
706 "TextInput" | "Text" | "SimpleText" | "ComplexText" | "StyledTextItem" => {
707 if let Some(font_size) = try_extract_literal_from_element(elem, "font-size", Unit::Px) {
708 add_font_size(font_size)
709 }
710 }
711 "Dialog" | "Window" | "WindowItem" => {
712 if let Some(font_size) =
713 try_extract_literal_from_element(elem, "default-font-size", Unit::Px)
714 {
715 add_font_size(font_size)
716 }
717 }
718 _ => {}
719 });
720}
721
722pub fn collect_font_weights_used(component: &Rc<Component>, weights_seen: &mut Vec<u16>) {
723 let mut add_weight = |weight: f64| {
724 let weight = weight as u16;
725 if let Err(pos) = weights_seen.binary_search(&weight) {
726 weights_seen.insert(pos, weight);
727 }
728 };
729
730 recurse_elem_including_sub_components(component, &(), &mut |elem, _| match elem
731 .borrow()
732 .base_type
733 .to_string()
734 .as_str()
735 {
736 "TextInput" | "Text" | "SimpleText" | "ComplexText" | "StyledTextItem" => {
737 if let Some(weight) = try_extract_literal_from_element(elem, "font-weight", Unit::None)
738 {
739 add_weight(weight)
740 }
741 }
742 "Dialog" | "Window" | "WindowItem" => {
743 if let Some(weight) =
744 try_extract_literal_from_element(elem, "default-font-weight", Unit::None)
745 {
746 add_weight(weight)
747 }
748 }
749 _ => {}
750 });
751}
752
753pub fn scan_string_literals(component: &Rc<Component>, characters_seen: &mut HashSet<char>) {
754 visit_all_expressions(component, |expr, _| {
755 expr.visit_recursive(&mut |expr| {
756 if let Expression::StringLiteral(string) = expr {
757 characters_seen.extend(string.chars());
758 }
759 })
760 })
761}