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 = "renderer-software")]
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 = "renderer-software")]
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 = "renderer-software")]
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 = "renderer-software")]
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 .binding("default-font-family")
182 .and_then(|binding| match binding.value_expression() {
183 Expression::StringLiteral(family) => {
184 Some((Some(family.clone()), binding.span.clone()))
185 }
186 _ => None,
187 })
188 .unwrap_or_default();
189
190 let font = {
191 let mut query = collection.query();
192
193 query.set_families(
194 family
195 .as_ref()
196 .map(|family| fontique::QueryFamily::from(family.as_str()))
197 .into_iter()
198 .chain(
199 sharedfontique::FALLBACK_FAMILIES
200 .into_iter()
201 .map(fontique::QueryFamily::Generic),
202 ),
203 );
204
205 let mut font = None;
206
207 query.matches_with(|queried_font| {
208 font = Some(queried_font.clone());
209 fontique::QueryStatus::Stop
210 });
211 font
212 };
213
214 match font {
215 None => {
216 if let Some(source_location) = source_location {
217 diag.push_error_with_span("could not find font that provides specified family, falling back to Sans-Serif".to_string(), source_location);
218 } else {
219 diag.push_error(
220 "internal error: could not determine a default font for sans-serif"
221 .to_string(),
222 &generic_diag_location,
223 );
224 };
225 }
226 Some(query_font) => {
227 if let Some(font_info) = collection
228 .family(query_font.family.0)
229 .and_then(|family_info| family_info.fonts().first().cloned())
230 {
231 let path = if let Some(path) = font_paths.get(&query_font.family.0) {
232 path.clone()
233 } else {
234 match &font_info.source().kind {
235 fontique::SourceKind::Path(path) => path.to_path_buf(),
236 fontique::SourceKind::Memory(_) => {
237 diag.push_error(
238 "internal error: memory fonts are not supported in the compiler"
239 .to_string(),
240 &generic_diag_location,
241 );
242 custom_face_error = true;
243 continue;
244 }
245 }
246 };
247 font_paths.insert(query_font.family.0, path.clone());
248 default_fonts.push((path.clone(), query_font));
249 }
250 }
251 }
252 }
253
254 default_fonts
255 };
256
257 if custom_face_error {
258 return;
259 }
260
261 let register_embedded_font = |path: &std::path::Path, embedded_bitmap_font: BitmapFont| {
262 let resource_id = doc.embedded_file_resources.borrow_mut().push_and_get_key(
263 crate::embedded_resources::EmbeddedResources {
264 path: Some(path.to_string_lossy().as_ref().into()),
265 kind: crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(
266 embedded_bitmap_font,
267 ),
268 },
269 );
270
271 for c in doc.exported_roots() {
272 c.init_code.borrow_mut().font_registration_code.push(Expression::FunctionCall {
273 function: BuiltinFunction::RegisterBitmapFont.into(),
274 arguments: vec![Expression::NumberLiteral(resource_id.0 as _, Unit::None)],
275 source_location: None,
276 });
277 }
278 };
279
280 let mut embed_font_by_path = |path: &std::path::Path, font: &fontique::QueryFont| {
281 let Some(family_name) = collection.family_name(font.family.0).to_owned() else {
282 diag.push_error(
283 format!(
284 "internal error: TrueType font without family name encountered: {}",
285 path.display()
286 ),
287 &generic_diag_location,
288 );
289 return;
290 };
291
292 let Some(font_ref) = skrifa::FontRef::from_index(font.blob.data(), font.index).ok() else {
293 diag.push_error(
294 format!("internal error: failed to parse font: {}", path.display()),
295 &generic_diag_location,
296 );
297 return;
298 };
299 let axes = font_ref.axes();
300 let wght_axis = axes.iter().find(|axis| axis.tag() == skrifa::Tag::new(b"wght"));
301
302 if let Some(wght_axis) = wght_axis {
303 let weights = if font_weights.is_empty() {
305 vec![fontique::FontWeight::NORMAL.value() as u16]
306 } else {
307 font_weights.clone()
308 };
309 for &weight in &weights {
310 let clamped = (weight as f32).clamp(wght_axis.min_value(), wght_axis.max_value());
311 let location = axes.location([("wght", clamped)]);
312 let variations = vec![(skrifa::Tag::new(b"wght"), clamped)];
313
314 let embedded = embed_font(
315 family_name.to_owned(),
316 Font { font: font.clone() },
317 &pixel_sizes,
318 characters_seen.iter().cloned(),
319 &fallback_fonts,
320 compiler_config,
321 location.coords(),
322 &variations,
323 Some(weight),
324 );
325 register_embedded_font(path, embedded);
326 }
327 } else {
328 let embedded = embed_font(
330 family_name.to_owned(),
331 Font { font: font.clone() },
332 &pixel_sizes,
333 characters_seen.iter().cloned(),
334 &fallback_fonts,
335 compiler_config,
336 &[],
337 &[],
338 None,
339 );
340 register_embedded_font(path, embedded);
341 }
342 };
343
344 for (path, font) in default_fonts.iter() {
347 custom_fonts.remove(path);
348 embed_font_by_path(path, font);
349 }
350
351 for (path, font) in custom_fonts.iter() {
352 embed_font_by_path(path, font);
353 }
354}
355
356#[inline(never)] fn get_fallback_fonts() -> Vec<Font> {
358 let mut fallback_fonts = Vec::new();
359
360 let mut collection = sharedfontique::create_collection(false);
361 let mut query = collection.query();
362 query.set_families(
363 sharedfontique::FALLBACK_FAMILIES.into_iter().map(fontique::QueryFamily::Generic).chain(
364 core::iter::once(fontique::QueryFamily::Generic(fontique::GenericFamily::Emoji)),
365 ),
366 );
367
368 query.matches_with(|query_font| {
369 fallback_fonts.push(Font { font: query_font.clone() });
370 fontique::QueryStatus::Continue
371 });
372
373 fallback_fonts
374}
375
376#[cfg(not(target_arch = "wasm32"))]
377fn embed_font(
378 family_name: String,
379 font: Font,
380 pixel_sizes: &[i16],
381 character_coverage: impl Iterator<Item = char>,
382 fallback_fonts: &[Font],
383 _compiler_config: &CompilerConfiguration,
384 normalized_coords: &[skrifa::instance::NormalizedCoord],
385 _variations: &[(skrifa::Tag, f32)],
386 override_weight: Option<u16>,
387) -> BitmapFont {
388 let coords_i16: Vec<i16> = normalized_coords.iter().map(|c| c.to_bits()).collect();
389
390 let mut character_map: Vec<CharacterMapEntry> = character_coverage
391 .filter(|code_point| {
392 core::iter::once(&font)
393 .chain(fallback_fonts.iter())
394 .any(|font| swash_font_ref(font).charmap().map(*code_point) != 0)
395 })
396 .enumerate()
397 .map(|(glyph_index, code_point)| CharacterMapEntry {
398 code_point,
399 glyph_index: u16::try_from(glyph_index)
400 .expect("more than 65535 glyphs are not supported"),
401 })
402 .collect();
403
404 #[cfg(feature = "sdf-fonts")]
405 let glyphs = if _compiler_config.use_sdf_fonts {
406 embed_sdf_glyphs(pixel_sizes, &character_map, &font, fallback_fonts, _variations)
407 } else {
408 embed_alpha_map_glyphs(pixel_sizes, &character_map, &font, fallback_fonts, &coords_i16)
409 };
410 #[cfg(not(feature = "sdf-fonts"))]
411 let glyphs =
412 embed_alpha_map_glyphs(pixel_sizes, &character_map, &font, fallback_fonts, &coords_i16);
413
414 character_map.sort_by_key(|entry| entry.code_point);
415
416 let font_ref = skrifa::FontRef::from_index(font.font.blob.data(), font.font.index).unwrap();
417 let location = skrifa::instance::LocationRef::new(normalized_coords);
418 let metrics =
419 skrifa::metrics::Metrics::new(&font_ref, skrifa::instance::Size::unscaled(), location);
420 let attrs = skrifa::attribute::Attributes::new(&font_ref);
421
422 BitmapFont {
423 family_name,
424 character_map,
425 units_per_em: metrics.units_per_em as f32,
426 ascent: metrics.ascent,
427 descent: metrics.descent,
428 x_height: metrics.x_height.unwrap_or_default(),
429 cap_height: metrics.cap_height.unwrap_or_default(),
430 glyphs,
431 weight: override_weight.unwrap_or(attrs.weight.value() as u16),
432 italic: attrs.style != skrifa::attribute::Style::Normal,
433 #[cfg(feature = "sdf-fonts")]
434 sdf: _compiler_config.use_sdf_fonts,
435 #[cfg(not(feature = "sdf-fonts"))]
436 sdf: false,
437 }
438}
439
440#[cfg(not(target_arch = "wasm32"))]
441fn embed_alpha_map_glyphs(
442 pixel_sizes: &[i16],
443 character_map: &Vec<CharacterMapEntry>,
444 font: &Font,
445 fallback_fonts: &[Font],
446 normalized_coords: &[i16],
447) -> Vec<BitmapGlyphs> {
448 use rayon::prelude::*;
449 use std::cell::RefCell;
450
451 thread_local! {
452 static SCALE_CONTEXT: RefCell<swash::scale::ScaleContext> =
453 RefCell::new(swash::scale::ScaleContext::new());
454 }
455
456 pixel_sizes
457 .par_iter()
458 .map(|pixel_size| {
459 let glyph_data = character_map
460 .par_iter()
461 .map(|CharacterMapEntry { code_point, .. }| {
462 let font_to_use = core::iter::once(font)
463 .chain(fallback_fonts.iter())
464 .find(|f| swash_font_ref(f).charmap().map(*code_point) != 0)
465 .unwrap_or(font);
466
467 let font_ref = swash_font_ref(font_to_use);
468 let glyph_id = font_ref.charmap().map(*code_point);
469 let gm = font_ref.glyph_metrics(normalized_coords);
470 let fm = font_ref.metrics(normalized_coords);
471 let scale = *pixel_size as f32 / fm.units_per_em as f32;
472 let advance_width = gm.advance_width(glyph_id) * scale;
473
474 SCALE_CONTEXT.with(|ctx| {
475 let font_ref = swash_font_ref(font_to_use);
476 let mut ctx = ctx.borrow_mut();
477 let mut scaler = ctx
478 .builder(font_ref)
479 .size(*pixel_size as f32)
480 .normalized_coords(normalized_coords)
481 .build();
482 let image = swash::scale::Render::new(&[swash::scale::Source::Outline])
483 .format(swash::zeno::Format::Alpha)
484 .render(&mut scaler, glyph_id);
485
486 match image {
487 Some(image) => {
488 let p = image.placement;
489 BitmapGlyph {
490 x: i16::try_from(p.left * 64)
491 .expect("large glyph x coordinate"),
492 y: i16::try_from((p.top - p.height as i32) * 64)
493 .expect("large glyph y coordinate"),
494 width: i16::try_from(p.width).expect("large width"),
495 height: i16::try_from(p.height).expect("large height"),
496 x_advance: i16::try_from((advance_width * 64.) as i64)
497 .expect("large advance width"),
498 data: image.data,
499 }
500 }
501 None => BitmapGlyph {
502 x: 0,
503 y: 0,
504 width: 0,
505 height: 0,
506 x_advance: i16::try_from((advance_width * 64.) as i64)
507 .expect("large advance width"),
508 data: vec![],
509 },
510 }
511 })
512 })
513 .collect();
514
515 BitmapGlyphs { pixel_size: *pixel_size, glyph_data }
516 })
517 .collect()
518}
519
520#[cfg(all(not(target_arch = "wasm32"), feature = "sdf-fonts"))]
521fn embed_sdf_glyphs(
522 pixel_sizes: &[i16],
523 character_map: &Vec<CharacterMapEntry>,
524 font: &Font,
525 fallback_fonts: &[Font],
526 variations: &[(skrifa::Tag, f32)],
527) -> Vec<BitmapGlyphs> {
528 use rayon::prelude::*;
529
530 const RANGE: f64 = 6.;
531
532 let Some(max_size) = pixel_sizes.iter().max() else {
533 return Vec::new();
534 };
535 let min_size = pixel_sizes.iter().min().expect("we have a 'max' so the vector is not empty");
536 let target_pixel_size = (max_size * 2 / 3).max(16).min(RANGE as i16 * min_size);
537
538 let glyph_data = character_map
539 .par_iter()
540 .map(|CharacterMapEntry { code_point, .. }| {
541 core::iter::once(font)
542 .chain(fallback_fonts.iter())
543 .find_map(|font| {
544 (swash_font_ref(font).charmap().map(*code_point) != 0).then(|| {
545 generate_sdf_for_glyph(
546 font,
547 *code_point,
548 target_pixel_size,
549 RANGE,
550 variations,
551 )
552 })
553 })
554 .unwrap_or_else(|| {
555 generate_sdf_for_glyph(font, *code_point, target_pixel_size, RANGE, variations)
556 })
557 .unwrap_or_default()
558 })
559 .collect::<Vec<_>>();
560
561 vec![BitmapGlyphs { pixel_size: target_pixel_size, glyph_data }]
562}
563
564#[cfg(all(not(target_arch = "wasm32"), feature = "sdf-fonts"))]
565fn generate_sdf_for_glyph(
566 font: &Font,
567 code_point: char,
568 target_pixel_size: i16,
569 range: f64,
570 variations: &[(skrifa::Tag, f32)],
571) -> Option<BitmapGlyph> {
572 use fdsm::transform::Transform;
573 use nalgebra::{Affine2, Similarity2, Vector2};
574
575 let mut face =
576 fdsm_ttf_parser::ttf_parser::Face::parse(font.font.blob.data(), font.font.index).unwrap();
577 for &(tag, value) in variations {
578 face.set_variation(
579 fdsm_ttf_parser::ttf_parser::Tag(u32::from_be_bytes(tag.to_be_bytes())),
580 value,
581 );
582 }
583 let glyph_id = face.glyph_index(code_point).unwrap_or_default();
584
585 let font_ref = skrifa::FontRef::from_index(font.font.blob.data(), font.font.index).unwrap();
586 let variation_settings: Vec<_> =
587 variations.iter().map(|&(tag, value)| (tag, value)).collect::<Vec<_>>();
588 let location = font_ref.axes().location(variation_settings);
589 let metrics = skrifa::metrics::Metrics::new(
590 &font_ref,
591 skrifa::instance::Size::unscaled(),
592 skrifa::instance::LocationRef::from(&location),
593 );
594 let target_pixel_size = target_pixel_size as f64;
595 let scale = target_pixel_size / metrics.units_per_em as f64;
596
597 let Some(bbox) = face.glyph_bounding_box(glyph_id) else {
599 return Some(BitmapGlyph {
601 x_advance: (face.glyph_hor_advance(glyph_id).unwrap_or(0) as f64 * scale * 64.) as i16,
602 ..Default::default()
603 });
604 };
605
606 let mut shape = fdsm_ttf_parser::load_shape_from_face(&face, glyph_id)?;
607
608 let width = ((bbox.x_max as f64 - bbox.x_min as f64) * scale + 2.).ceil() as u32;
609 let height = ((bbox.y_max as f64 - bbox.y_min as f64) * scale + 2.).ceil() as u32;
610 let transformation = nalgebra::convert::<_, Affine2<f64>>(Similarity2::new(
611 Vector2::new(1. - bbox.x_min as f64 * scale, 1. - bbox.y_min as f64 * scale),
612 0.,
613 scale,
614 ));
615
616 shape.transform(&transformation);
622
623 let prepared_shape = shape.prepare();
624
625 let mut sdf = image::GrayImage::new(width, height);
628 fdsm::generate::generate_sdf(&prepared_shape, range, &mut sdf);
629 fdsm::render::correct_sign_sdf(
630 &mut sdf,
631 &prepared_shape,
632 fdsm::bezier::scanline::FillRule::Nonzero,
633 );
634
635 let mut glyph_data = sdf.into_raw();
636
637 for x in &mut glyph_data {
639 *x = x.wrapping_sub(128);
640 }
641
642 let (w, h) = (width as usize, height as usize);
644 for idx in 0..glyph_data.len() / 2 {
645 glyph_data.swap(idx, (h - idx / w - 1) * w + idx % w);
646 }
647
648 glyph_data.push(0);
651
652 let bg = BitmapGlyph {
653 x: i16::try_from((-(1. - bbox.x_min as f64 * scale) * 64.).ceil() as i32)
654 .expect("large glyph x coordinate"),
655 y: i16::try_from((-(1. - bbox.y_min as f64 * scale) * 64.).ceil() as i32)
656 .expect("large glyph y coordinate"),
657 width: i16::try_from(width).expect("large width"),
658 height: i16::try_from(height).expect("large height"),
659 x_advance: i16::try_from(
660 (face.glyph_hor_advance(glyph_id).unwrap() as f64 * scale * 64.).round() as i32,
661 )
662 .expect("large advance width"),
663 data: glyph_data,
664 };
665
666 Some(bg)
667}
668
669fn try_extract_literal_from_element(
670 elem: &ElementRc,
671 property_name: &str,
672 unit: Unit,
673) -> Option<f64> {
674 elem.borrow().binding(property_name).and_then(|binding| match binding.value_expression() {
675 Expression::NumberLiteral(value, u) if *u == unit => Some(*value),
676 Expression::Cast { from, .. } => match from.as_ref() {
677 Expression::NumberLiteral(value, u) if *u == unit => Some(*value),
678 _ => None,
679 },
680 _ => None,
681 })
682}
683
684pub fn collect_font_sizes_used(
685 component: &Rc<Component>,
686 scale_factor: f64,
687 sizes_seen: &mut Vec<i16>,
688) {
689 let mut add_font_size = |logical_size: f64| {
690 let pixel_size = (logical_size * scale_factor) as i16;
691 match sizes_seen.binary_search(&pixel_size) {
692 Ok(_) => {}
693 Err(pos) => sizes_seen.insert(pos, pixel_size),
694 }
695 };
696
697 recurse_elem_including_sub_components(component, &(), &mut |elem, _| match elem
698 .borrow()
699 .base_type
700 .to_string()
701 .as_str()
702 {
703 "TextInput" | "Text" | "SimpleText" | "ComplexText" | "StyledTextItem" => {
704 if let Some(font_size) = try_extract_literal_from_element(elem, "font-size", Unit::Px) {
705 add_font_size(font_size)
706 }
707 }
708 "Dialog" | "Window" | "WindowItem" => {
709 if let Some(font_size) =
710 try_extract_literal_from_element(elem, "default-font-size", Unit::Px)
711 {
712 add_font_size(font_size)
713 }
714 }
715 _ => {}
716 });
717}
718
719pub fn collect_font_weights_used(component: &Rc<Component>, weights_seen: &mut Vec<u16>) {
720 let mut add_weight = |weight: f64| {
721 let weight = weight as u16;
722 if let Err(pos) = weights_seen.binary_search(&weight) {
723 weights_seen.insert(pos, weight);
724 }
725 };
726
727 recurse_elem_including_sub_components(component, &(), &mut |elem, _| match elem
728 .borrow()
729 .base_type
730 .to_string()
731 .as_str()
732 {
733 "TextInput" | "Text" | "SimpleText" | "ComplexText" | "StyledTextItem" => {
734 if let Some(weight) = try_extract_literal_from_element(elem, "font-weight", Unit::None)
735 {
736 add_weight(weight)
737 }
738 }
739 "Dialog" | "Window" | "WindowItem" => {
740 if let Some(weight) =
741 try_extract_literal_from_element(elem, "default-font-weight", Unit::None)
742 {
743 add_weight(weight)
744 }
745 }
746 _ => {}
747 });
748}
749
750pub fn scan_string_literals(component: &Rc<Component>, characters_seen: &mut HashSet<char>) {
751 visit_all_expressions(component, |expr, _| {
752 expr.visit_recursive(&mut |expr| {
753 if let Expression::StringLiteral(string) = expr {
754 characters_seen.extend(string.chars());
755 }
756 })
757 })
758}