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;
15use std::sync::Arc;
16
17use i_slint_common::sharedfontique::{self, fontique, ttf_parser};
18
19#[derive(Clone, derive_more::Deref)]
20struct Font {
21 font: fontique::QueryFont,
22 #[deref]
23 fontdue_font: Arc<fontdue::Font>,
24}
25
26#[cfg(target_arch = "wasm32")]
27pub fn embed_glyphs<'a>(
28 _component: &Document,
29 _compiler_config: &CompilerConfiguration,
30 _scale_factor: f64,
31 _pixel_sizes: Vec<i16>,
32 _characters_seen: HashSet<char>,
33 _all_docs: impl Iterator<Item = &'a crate::object_tree::Document> + 'a,
34 _diag: &mut BuildDiagnostics,
35) -> bool {
36 false
37}
38
39#[cfg(not(target_arch = "wasm32"))]
40pub fn embed_glyphs<'a>(
41 doc: &Document,
42 compiler_config: &CompilerConfiguration,
43 mut pixel_sizes: Vec<i16>,
44 mut characters_seen: HashSet<char>,
45 all_docs: impl Iterator<Item = &'a crate::object_tree::Document> + 'a,
46 diag: &mut BuildDiagnostics,
47) {
48 use crate::diagnostics::Spanned;
49
50 let generic_diag_location = doc.node.as_ref().map(|n| n.to_source_location());
51 let scale_factor = compiler_config.const_scale_factor.unwrap_or(1.);
52
53 characters_seen.extend(
54 ('a'..='z')
55 .chain('A'..='Z')
56 .chain('0'..='9')
57 .chain(" '!\"#$%&()*+,-./:;<=>?@\\[]{}^_|~".chars())
58 .chain(std::iter::once('●'))
59 .chain(std::iter::once('…')),
60 );
61
62 if let Ok(sizes_str) = std::env::var("SLINT_FONT_SIZES") {
63 for custom_size_str in sizes_str.split(',') {
64 let custom_size = if let Ok(custom_size) = custom_size_str
65 .parse::<f32>()
66 .map(|size_as_float| (size_as_float * scale_factor) as i16)
67 {
68 custom_size
69 } else {
70 diag.push_error(
71 format!(
72 "Invalid font size '{custom_size_str}' specified in `SLINT_FONT_SIZES`"
73 ),
74 &generic_diag_location,
75 );
76 return;
77 };
78
79 if let Err(pos) = pixel_sizes.binary_search(&custom_size) {
80 pixel_sizes.insert(pos, custom_size)
81 }
82 }
83 }
84
85 let fallback_fonts = get_fallback_fonts(compiler_config);
86
87 let mut custom_fonts: HashMap<std::path::PathBuf, fontique::QueryFont> = Default::default();
88 let mut font_paths: HashMap<fontique::FamilyId, std::path::PathBuf> = Default::default();
89
90 let mut collection = sharedfontique::get_collection();
91
92 for doc in all_docs {
93 for (font_path, import_token) in doc.custom_fonts.iter() {
94 match std::fs::read(&font_path) {
95 Err(e) => {
96 diag.push_error(format!("Error loading font: {e}"), import_token);
97 }
98 Ok(bytes) => {
99 if let Some(font) = collection
100 .register_fonts(bytes.into(), None)
101 .first()
102 .and_then(|(id, infos)| {
103 let info = infos.first()?;
104 collection.get_font_for_info(*id, info)
105 })
106 {
107 font_paths.insert(font.family.0, font_path.into());
108 custom_fonts.insert(font_path.into(), font);
109 }
110 }
111 }
112 }
113 }
114
115 let mut custom_face_error = false;
116
117 let default_fonts = if !collection.default_fonts.is_empty() {
118 collection.default_fonts.as_ref().clone()
119 } else {
120 let mut default_fonts: HashMap<std::path::PathBuf, fontique::QueryFont> =
121 Default::default();
122
123 for c in doc.exported_roots() {
124 let (family, source_location) = c
125 .root_element
126 .borrow()
127 .bindings
128 .get("default-font-family")
129 .and_then(|binding| match &binding.borrow().expression {
130 Expression::StringLiteral(family) => {
131 Some((Some(family.clone()), binding.borrow().span.clone()))
132 }
133 _ => None,
134 })
135 .unwrap_or_default();
136
137 let font = {
138 let mut query = collection.query();
139
140 query.set_families(
141 family
142 .as_ref()
143 .map(|family| fontique::QueryFamily::from(family.as_str()))
144 .into_iter()
145 .chain(
146 sharedfontique::FALLBACK_FAMILIES
147 .into_iter()
148 .map(fontique::QueryFamily::Generic),
149 ),
150 );
151
152 let mut font = None;
153
154 query.matches_with(|queried_font| {
155 font = Some(queried_font.clone());
156 fontique::QueryStatus::Stop
157 });
158 font
159 };
160
161 match font {
162 None => {
163 if let Some(source_location) = source_location {
164 diag.push_error_with_span("could not find font that provides specified family, falling back to Sans-Serif".to_string(), source_location);
165 } else {
166 diag.push_error(
167 "internal error: could not determine a default font for sans-serif"
168 .to_string(),
169 &generic_diag_location,
170 );
171 };
172 }
173 Some(query_font) => {
174 if let Some(font_info) = collection
175 .family(query_font.family.0)
176 .and_then(|family_info| family_info.fonts().first().cloned())
177 {
178 let path = if let Some(path) = font_paths.get(&query_font.family.0) {
179 path.clone()
180 } else {
181 match &font_info.source().kind {
182 fontique::SourceKind::Path(path) => path.to_path_buf(),
183 fontique::SourceKind::Memory(_) => {
184 diag.push_error(
185 "internal error: memory fonts are not supported in the compiler"
186 .to_string(),
187 &generic_diag_location,
188 );
189 custom_face_error = true;
190 continue;
191 }
192 }
193 };
194 font_paths.insert(query_font.family.0, path.clone());
195 default_fonts.insert(path.clone(), query_font);
196 }
197 }
198 }
199 }
200
201 default_fonts
202 };
203
204 if custom_face_error {
205 return;
206 }
207
208 let mut embed_font_by_path = |path: &std::path::Path, font: &fontique::QueryFont| {
209 let fontdue_font = match compiler_config.load_font_by_id(font) {
210 Ok(font) => font,
211 Err(msg) => {
212 diag.push_error(
213 format!("error loading font for embedding {}: {msg}", path.display()),
214 &generic_diag_location,
215 );
216 return;
217 }
218 };
219
220 let Some(family_name) = collection.family_name(font.family.0).to_owned() else {
221 diag.push_error(
222 format!(
223 "internal error: TrueType font without family name encountered: {}",
224 path.display()
225 ),
226 &generic_diag_location,
227 );
228 return;
229 };
230
231 let embedded_bitmap_font = embed_font(
232 family_name.to_owned(),
233 Font { font: font.clone(), fontdue_font },
234 &pixel_sizes,
235 characters_seen.iter().cloned(),
236 &fallback_fonts,
237 compiler_config,
238 );
239
240 let resource_id = doc.embedded_file_resources.borrow().len();
241 doc.embedded_file_resources.borrow_mut().insert(
242 path.to_string_lossy().into(),
243 crate::embedded_resources::EmbeddedResources {
244 id: resource_id,
245 kind: crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(
246 embedded_bitmap_font,
247 ),
248 },
249 );
250
251 for c in doc.exported_roots() {
252 c.init_code.borrow_mut().font_registration_code.push(Expression::FunctionCall {
253 function: BuiltinFunction::RegisterBitmapFont.into(),
254 arguments: vec![Expression::NumberLiteral(resource_id as _, Unit::None)],
255 source_location: None,
256 });
257 }
258 };
259
260 for (path, font) in default_fonts.iter() {
261 custom_fonts.remove(&path.to_owned());
262 embed_font_by_path(&path, &font);
263 }
264
265 for (path, font) in custom_fonts {
266 embed_font_by_path(&path, &font);
267 }
268}
269
270#[inline(never)] fn get_fallback_fonts(compiler_config: &CompilerConfiguration) -> Vec<Font> {
272 let mut fallback_fonts = Vec::new();
273
274 let mut collection = sharedfontique::get_collection();
275 let mut query = collection.query();
276 query.set_families(
277 sharedfontique::FALLBACK_FAMILIES.into_iter().map(fontique::QueryFamily::Generic).chain(
278 core::iter::once(fontique::QueryFamily::Generic(fontique::GenericFamily::Emoji)),
279 ),
280 );
281
282 query.matches_with(|query_font| {
283 if let Some(font) = compiler_config
284 .load_font_by_id(&query_font)
285 .ok()
286 .map(|fontdue_font| Font { font: query_font.clone(), fontdue_font })
287 {
288 fallback_fonts.push(font);
289 }
290
291 fontique::QueryStatus::Continue
292 });
293
294 fallback_fonts
295}
296
297#[cfg(not(target_arch = "wasm32"))]
298fn embed_font(
299 family_name: String,
300 font: Font,
301 pixel_sizes: &[i16],
302 character_coverage: impl Iterator<Item = char>,
303 fallback_fonts: &[Font],
304 _compiler_config: &CompilerConfiguration,
305) -> BitmapFont {
306 let mut character_map: Vec<CharacterMapEntry> = character_coverage
307 .filter(|code_point| {
308 core::iter::once(&font)
309 .chain(fallback_fonts.iter())
310 .any(|font| font.fontdue_font.lookup_glyph_index(*code_point) != 0)
311 })
312 .enumerate()
313 .map(|(glyph_index, code_point)| CharacterMapEntry {
314 code_point,
315 glyph_index: u16::try_from(glyph_index)
316 .expect("more than 65535 glyphs are not supported"),
317 })
318 .collect();
319
320 #[cfg(feature = "sdf-fonts")]
321 let glyphs = if _compiler_config.use_sdf_fonts {
322 embed_sdf_glyphs(pixel_sizes, &character_map, &font, fallback_fonts)
323 } else {
324 embed_alpha_map_glyphs(pixel_sizes, &character_map, &font, fallback_fonts)
325 };
326 #[cfg(not(feature = "sdf-fonts"))]
327 let glyphs = embed_alpha_map_glyphs(pixel_sizes, &character_map, &font, fallback_fonts);
328
329 character_map.sort_by_key(|entry| entry.code_point);
330
331 let face_info = ttf_parser::Face::parse(font.font.blob.data(), font.font.index).unwrap();
332
333 let metrics = sharedfontique::DesignFontMetrics::new_from_face(&face_info);
334
335 BitmapFont {
336 family_name,
337 character_map,
338 units_per_em: metrics.units_per_em,
339 ascent: metrics.ascent,
340 descent: metrics.descent,
341 x_height: metrics.x_height,
342 cap_height: metrics.cap_height,
343 glyphs,
344 weight: face_info.weight().to_number(),
345 italic: face_info.style() != ttf_parser::Style::Normal,
346 #[cfg(feature = "sdf-fonts")]
347 sdf: _compiler_config.use_sdf_fonts,
348 #[cfg(not(feature = "sdf-fonts"))]
349 sdf: false,
350 }
351}
352
353#[cfg(all(not(target_arch = "wasm32")))]
354fn embed_alpha_map_glyphs(
355 pixel_sizes: &[i16],
356 character_map: &Vec<CharacterMapEntry>,
357 font: &Font,
358 fallback_fonts: &[Font],
359) -> Vec<BitmapGlyphs> {
360 use rayon::prelude::*;
361
362 let glyphs = pixel_sizes
363 .par_iter()
364 .map(|pixel_size| {
365 let glyph_data = character_map
366 .par_iter()
367 .map(|CharacterMapEntry { code_point, .. }| {
368 let (metrics, bitmap) = core::iter::once(font)
369 .chain(fallback_fonts.iter())
370 .find_map(|font| {
371 font.chars()
372 .contains_key(code_point)
373 .then(|| font.rasterize(*code_point, *pixel_size as _))
374 })
375 .unwrap_or_else(|| font.rasterize(*code_point, *pixel_size as _));
376
377 BitmapGlyph {
378 x: i16::try_from(metrics.xmin * 64).expect("large glyph x coordinate"),
379 y: i16::try_from(metrics.ymin * 64).expect("large glyph y coordinate"),
380 width: i16::try_from(metrics.width).expect("large width"),
381 height: i16::try_from(metrics.height).expect("large height"),
382 x_advance: i16::try_from((metrics.advance_width * 64.) as i64)
383 .expect("large advance width"),
384 data: bitmap,
385 }
386 })
387 .collect();
388
389 BitmapGlyphs { pixel_size: *pixel_size, glyph_data }
390 })
391 .collect();
392 glyphs
393}
394
395#[cfg(all(not(target_arch = "wasm32"), feature = "sdf-fonts"))]
396fn embed_sdf_glyphs(
397 pixel_sizes: &[i16],
398 character_map: &Vec<CharacterMapEntry>,
399 font: &Font,
400 fallback_fonts: &[Font],
401) -> Vec<BitmapGlyphs> {
402 use rayon::prelude::*;
403
404 const RANGE: f64 = 6.;
405
406 let Some(max_size) = pixel_sizes.iter().max() else {
407 return Vec::new();
408 };
409 let min_size = pixel_sizes.iter().min().expect("we have a 'max' so the vector is not empty");
410 let target_pixel_size = (max_size * 2 / 3).max(16).min(RANGE as i16 * min_size);
411
412 let glyph_data = character_map
413 .par_iter()
414 .map(|CharacterMapEntry { code_point, .. }| {
415 core::iter::once(font)
416 .chain(fallback_fonts.iter())
417 .find_map(|font| {
418 (font.lookup_glyph_index(*code_point) != 0).then(|| {
419 generate_sdf_for_glyph(font, *code_point, target_pixel_size, RANGE)
420 })
421 })
422 .unwrap_or_else(|| {
423 generate_sdf_for_glyph(font, *code_point, target_pixel_size, RANGE)
424 })
425 .unwrap_or_default()
426 })
427 .collect::<Vec<_>>();
428
429 vec![BitmapGlyphs { pixel_size: target_pixel_size, glyph_data }]
430}
431
432#[cfg(all(not(target_arch = "wasm32"), feature = "sdf-fonts"))]
433fn generate_sdf_for_glyph(
434 font: &Font,
435 code_point: char,
436 target_pixel_size: i16,
437 range: f64,
438) -> Option<BitmapGlyph> {
439 use fdsm::transform::Transform;
440 use nalgebra::{Affine2, Similarity2, Vector2};
441
442 let face =
443 fdsm_ttf_parser::ttf_parser::Face::parse(font.font.blob.data(), font.font.index).unwrap();
444 let glyph_id = face.glyph_index(code_point).unwrap_or_default();
445 let mut shape = fdsm_ttf_parser::load_shape_from_face(&face, glyph_id)?;
446
447 let metrics = sharedfontique::DesignFontMetrics::new(&font.font);
448 let target_pixel_size = target_pixel_size as f64;
449 let scale = target_pixel_size / metrics.units_per_em as f64;
450
451 let Some(bbox) = face.glyph_bounding_box(glyph_id) else {
453 return Some(BitmapGlyph {
455 x_advance: (face.glyph_hor_advance(glyph_id).unwrap_or(0) as f64 * scale * 64.) as i16,
456 ..Default::default()
457 });
458 };
459
460 let width = ((bbox.x_max as f64 - bbox.x_min as f64) * scale + 2.).ceil() as u32;
461 let height = ((bbox.y_max as f64 - bbox.y_min as f64) * scale + 2.).ceil() as u32;
462 let transformation = nalgebra::convert::<_, Affine2<f64>>(Similarity2::new(
463 Vector2::new(1. - bbox.x_min as f64 * scale, 1. - bbox.y_min as f64 * scale),
464 0.,
465 scale,
466 ));
467
468 shape.transform(&transformation);
474
475 let prepared_shape = shape.prepare();
476
477 let mut sdf = image::GrayImage::new(width, height);
480 fdsm::generate::generate_sdf(&prepared_shape, range, &mut sdf);
481 fdsm::render::correct_sign_sdf(
482 &mut sdf,
483 &prepared_shape,
484 fdsm::bezier::scanline::FillRule::Nonzero,
485 );
486
487 let mut glyph_data = sdf.into_raw();
488
489 for x in &mut glyph_data {
491 *x = x.wrapping_sub(128);
492 }
493
494 let (w, h) = (width as usize, height as usize);
496 for idx in 0..glyph_data.len() / 2 {
497 glyph_data.swap(idx, (h - idx / w - 1) * w + idx % w);
498 }
499
500 glyph_data.push(0);
503
504 let bg = BitmapGlyph {
505 x: i16::try_from((-(1. - bbox.x_min as f64 * scale) * 64.).ceil() as i32)
506 .expect("large glyph x coordinate"),
507 y: i16::try_from((-(1. - bbox.y_min as f64 * scale) * 64.).ceil() as i32)
508 .expect("large glyph y coordinate"),
509 width: i16::try_from(width).expect("large width"),
510 height: i16::try_from(height).expect("large height"),
511 x_advance: i16::try_from(
512 (face.glyph_hor_advance(glyph_id).unwrap() as f64 * scale * 64.).round() as i32,
513 )
514 .expect("large advance width"),
515 data: glyph_data,
516 };
517
518 Some(bg)
519}
520
521fn try_extract_font_size_from_element(elem: &ElementRc, property_name: &str) -> Option<f64> {
522 elem.borrow().bindings.get(property_name).and_then(|expression| {
523 match &expression.borrow().expression {
524 Expression::NumberLiteral(value, Unit::Px) => Some(*value),
525 _ => None,
526 }
527 })
528}
529
530pub fn collect_font_sizes_used(
531 component: &Rc<Component>,
532 scale_factor: f64,
533 sizes_seen: &mut Vec<i16>,
534) {
535 let mut add_font_size = |logical_size: f64| {
536 let pixel_size = (logical_size * scale_factor) as i16;
537 match sizes_seen.binary_search(&pixel_size) {
538 Ok(_) => {}
539 Err(pos) => sizes_seen.insert(pos, pixel_size),
540 }
541 };
542
543 recurse_elem_including_sub_components(component, &(), &mut |elem, _| match elem
544 .borrow()
545 .base_type
546 .to_string()
547 .as_str()
548 {
549 "TextInput" | "Text" | "SimpleText" | "ComplexText" | "StyledTextItem" => {
550 if let Some(font_size) = try_extract_font_size_from_element(elem, "font-size") {
551 add_font_size(font_size)
552 }
553 }
554 "Dialog" | "Window" | "WindowItem" => {
555 if let Some(font_size) = try_extract_font_size_from_element(elem, "default-font-size") {
556 add_font_size(font_size)
557 }
558 }
559 _ => {}
560 });
561}
562
563pub fn scan_string_literals(component: &Rc<Component>, characters_seen: &mut HashSet<char>) {
564 visit_all_expressions(component, |expr, _| {
565 expr.visit_recursive(&mut |expr| {
566 if let Expression::StringLiteral(string) = expr {
567 characters_seen.extend(string.chars());
568 }
569 })
570 })
571}