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