diff --git a/examples/printerdemo/ui/fonts/NotoSans-Italic.ttf b/examples/printerdemo/ui/fonts/NotoSans-Italic.ttf new file mode 100644 index 00000000000..06d7e921b35 Binary files /dev/null and b/examples/printerdemo/ui/fonts/NotoSans-Italic.ttf differ diff --git a/examples/printerdemo/ui/fonts/NotoSans-Italic.ttf.license b/examples/printerdemo/ui/fonts/NotoSans-Italic.ttf.license new file mode 100644 index 00000000000..be4d6f97cd8 --- /dev/null +++ b/examples/printerdemo/ui/fonts/NotoSans-Italic.ttf.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: Google Inc. + +SPDX-License-Identifier: OFL-1.1-RFN diff --git a/examples/printerdemo/ui/fonts/convert.sh b/examples/printerdemo/ui/fonts/convert.sh index 1bf7e453e57..de2675318dc 100755 --- a/examples/printerdemo/ui/fonts/convert.sh +++ b/examples/printerdemo/ui/fonts/convert.sh @@ -13,3 +13,4 @@ cp NotoSans-unhinted/LICENSE_OFL.txt . for weight in Light Regular Bold; do pyftsubset NotoSans-unhinted/NotoSans-$weight.ttf --unicodes="U+0020-007F,U+2026" --output-file=NotoSans-$weight.ttf done +pyftsubset NotoSans-unhinted/NotoSans-Italic.ttf --unicodes="U+0020-007F,U+2026" --output-file=NotoSans-Italic.ttf diff --git a/internal/common/sharedfontdb.rs b/internal/common/sharedfontdb.rs index 2000191d91e..dafa15c5b18 100644 --- a/internal/common/sharedfontdb.rs +++ b/internal/common/sharedfontdb.rs @@ -18,7 +18,9 @@ pub struct FontDatabase { )))] pub fontconfig_fallback_families: Vec, // Default font famiilies to use instead of SansSerif when SLINT_DEFAULT_FONT env var is set. - default_font_families: Vec, + pub default_font_family_ids: Vec, + // Same as default_font_families but reduced to unique family names + default_font_family_names: Vec, } impl FontDatabase { @@ -33,12 +35,12 @@ impl FontDatabase { query.families = &single_family; self.db.query(&query) } else { - if self.default_font_families.is_empty() { + if self.default_font_family_ids.is_empty() { query.families = &[fontdb::Family::SansSerif]; self.db.query(&query) } else { let family_storage = self - .default_font_families + .default_font_family_names .iter() .map(|name| fontdb::Family::Name(name)) .collect::>(); @@ -65,29 +67,46 @@ fn init_fontdb() -> FontDatabase { let mut font_db = fontdb::Database::new(); #[cfg(not(target_arch = "wasm32"))] - let default_font_families = std::env::var_os("SLINT_DEFAULT_FONT").and_then(|maybe_font_path| { - let path = std::path::Path::new(&maybe_font_path); - if path.extension().is_some() { - match font_db.load_font_file(path) { - Ok(()) => { - Some(font_db.faces().flat_map(|face_info| face_info.families.first().map(|(name, _)| name.clone())).collect()) - }, - Err(err) => { - eprintln!( - "Could not load the font set via `SLINT_DEFAULT_FONT`: {}: {}", path.display(), err, - ); - None - }, - } - } else { - eprintln!( - "The environment variable `SLINT_DEFAULT_FONT` is set, but its value is not referring to a file", - ); - None - } - }).unwrap_or_default(); + let (default_font_family_ids, default_font_family_names) = + std::env::var_os("SLINT_DEFAULT_FONT") + .and_then(|maybe_font_path| { + let path = std::path::Path::new(&maybe_font_path); + match if path.extension().is_some() { + font_db.load_font_file(path) + } else { + font_db.load_fonts_dir(path); + Ok(()) + } { + Ok(_) => { + let mut family_ids = Vec::new(); + let mut family_names = Vec::new(); + + for face_info in font_db.faces() { + family_ids.push(face_info.id); + + let family_name = &face_info.families[0].0; + if let Err(insert_pos) = family_names.binary_search(family_name) { + family_names.insert(insert_pos, family_name.clone()); + } + } + + Some((family_ids, family_names)) + } + Err(err) => { + eprintln!( + "Could not load the font set via `SLINT_DEFAULT_FONT`: {}: {}", + path.display(), + err, + ); + None + } + } + }) + .unwrap_or_default(); + #[cfg(target_arch = "wasm32")] - let default_font_families = Vec::default(); + let (default_font_family_ids, default_font_family_names) = + (Default::default(), Default::default()); #[cfg(not(any( target_family = "windows", @@ -136,7 +155,8 @@ fn init_fontdb() -> FontDatabase { target_arch = "wasm32" )))] fontconfig_fallback_families, - default_font_families, + default_font_family_ids, + default_font_family_names, } } diff --git a/internal/compiler/embedded_resources.rs b/internal/compiler/embedded_resources.rs index 9e493341445..eaa73fc2f99 100644 --- a/internal/compiler/embedded_resources.rs +++ b/internal/compiler/embedded_resources.rs @@ -79,6 +79,8 @@ pub struct BitmapFont { pub ascent: f32, pub descent: f32, pub glyphs: Vec, + pub weight: u16, + pub italic: bool, } #[derive(Debug, Clone)] diff --git a/internal/compiler/generator/rust.rs b/internal/compiler/generator/rust.rs index 66255cd58f8..59fa2a7adaa 100644 --- a/internal/compiler/generator/rust.rs +++ b/internal/compiler/generator/rust.rs @@ -228,7 +228,7 @@ pub fn generate(doc: &Document) -> TokenStream { ) }, #[cfg(feature = "software-renderer")] - crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(crate::embedded_resources::BitmapFont { family_name, character_map, units_per_em, ascent, descent, glyphs }) => { + crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(crate::embedded_resources::BitmapFont { family_name, character_map, units_per_em, ascent, descent, glyphs, weight, italic }) => { let character_map_size = character_map.len(); @@ -285,7 +285,9 @@ pub fn generate(doc: &Document) -> TokenStream { #link_section static GLYPHS : [slint::private_unstable_api::re_exports::BitmapGlyphs; #glyphs_size] = [#(#glyphs),*]; &GLYPHS - }) + }), + weight: #weight, + italic: #italic, }; ) }, diff --git a/internal/compiler/passes/embed_glyphs.rs b/internal/compiler/passes/embed_glyphs.rs index 0407dda4c02..436347e1ca3 100644 --- a/internal/compiler/passes/embed_glyphs.rs +++ b/internal/compiler/passes/embed_glyphs.rs @@ -13,6 +13,13 @@ use std::rc::Rc; use i_slint_common::sharedfontdb::{self, fontdb}; +#[derive(Clone, derive_more::Deref)] +struct Font { + id: fontdb::ID, + #[deref] + fontdue_font: fontdue::Font, +} + #[cfg(target_arch = "wasm32")] pub fn embed_glyphs<'a>( _component: &Rc, @@ -96,33 +103,7 @@ fn embed_glyphs_with_fontdb<'a>( diag: &mut BuildDiagnostics, generic_diag_location: Option, ) { - let maybe_override_default_font_id = - std::env::var_os("SLINT_DEFAULT_FONT").and_then(|maybe_font_path| { - let path = std::path::Path::new(&maybe_font_path); - if path.extension().is_some() { - let face_count = fontdb.len(); - match fontdb.load_font_file(path) { - Ok(()) => { - fontdb.faces().nth(face_count).map(|face_info| face_info.id) - }, - Err(err) => { - diag.push_error( - format!("Could not load the font set via `SLINT_DEFAULT_FONT`: {}: {}", path.display(), err), - &generic_diag_location, - ); - None - }, - } - } else { - diag.push_error( - "The environment variable `SLINT_DEFAULT_FONT` is set, but its value is not referring to a file".into(), - &generic_diag_location, - ); - None - } - }); - - let (fallback_fonts, fallback_font) = get_fallback_fonts(fontdb); + let fallback_fonts = get_fallback_fonts(fontdb); let mut custom_fonts = Vec::new(); @@ -138,8 +119,9 @@ fn embed_glyphs_with_fontdb<'a>( } } - let (default_font_face_id, default_font_path) = if let Some(result) = maybe_override_default_font_id.map_or_else(||{ - // TODO: improve heuristics in choice of which fonts to embed. use default-font-family, etc. + let default_font_ids = if !fontdb.default_font_family_ids.is_empty() { + fontdb.default_font_family_ids.clone() + } else { let (family, source_location) = component .root_element .borrow() @@ -153,53 +135,36 @@ fn embed_glyphs_with_fontdb<'a>( }) .unwrap_or_default(); - let query = fontdb::Query { - families: &[family - .as_ref() - .map_or(fontdb::Family::SansSerif, |name| fontdb::Family::Name(name))], - ..Default::default() - }; - let face_id = fontdb.query(&query).unwrap_or_else(|| { - if let Some(source_location) = source_location { - diag.push_warning_with_span(format!("could not find font that provides specified family, falling back to Sans-Serif"), source_location); + match fontdb.query_with_family(Default::default(), family.as_deref()) { + Some(id) => vec![id], + None => { + if let Some(source_location) = source_location { + diag.push_error_with_span(format!("could not find font that provides specified family, falling back to Sans-Serif"), source_location); + } else { + diag.push_error(format!("internal error: fontdb could not determine a default font for sans-serif"), &generic_diag_location); + }; + return; } - fallback_font - }); - - let face_info = if let Some(face_info) = fontdb - .face(face_id) { - face_info - } else { - diag.push_error("internal error: fontdb query returned a font that does not exist".to_string(), &generic_diag_location); - return None; - }; - Some(( - face_id, - match &face_info.source { - fontdb::Source::File(path) => path.to_string_lossy().to_string(), - _ => { - diag.push_error("internal error: memory fonts are not supported in the compiler".to_string(), &generic_diag_location); - return None; - } - }, - )) - }, |override_default_font_id| { - let path = match &fontdb.face(override_default_font_id).unwrap().source { - fontdb::Source::Binary(_) => unreachable!(), - fontdb::Source::File(path_buf) => path_buf.clone(), - fontdb::Source::SharedFile(path_buf, _) => path_buf.clone(), - }; - - Some((override_default_font_id, path.to_string_lossy().into())) - }) { - result - } else { - return; + } }; + let default_font_paths = default_font_ids + .iter() + .map(|id| { + let (source, _) = + fontdb.face_source(*id).expect("internal error: fontdb provided ids are not valid"); + match source { + fontdb::Source::Binary(_) => unreachable!(), + fontdb::Source::File(path_buf) => path_buf, + fontdb::Source::SharedFile(path_buf, _) => path_buf, + } + .clone() + }) + .collect::>(); + // Map from path to family name - let mut fonts = std::collections::BTreeMap::::new(); - fonts.insert(default_font_path.clone(), default_font_face_id); + let mut fonts = std::collections::BTreeMap::::new(); + fonts.extend(default_font_paths.iter().cloned().zip(default_font_ids.iter().cloned())); // add custom fonts let mut custom_face_error = false; @@ -207,7 +172,7 @@ fn embed_glyphs_with_fontdb<'a>( fontdb.face(*face_id).and_then(|face_info| { Some(( match &face_info.source { - fontdb::Source::File(path) => path.to_string_lossy().to_string(), + fontdb::Source::File(path) => path.clone(), _ => { diag.push_error( "internal error: memory fonts are not supported in the compiler" @@ -227,7 +192,7 @@ fn embed_glyphs_with_fontdb<'a>( return; } - let mut embed_font_by_path_and_face_id = |path, face_id| { + let mut embed_font_by_path_and_face_id = |path: &std::path::Path, face_id| { let maybe_font = if let Some(maybe_font) = fontdb.with_face_data(face_id, |font_data, face_index| { let fontdue_font = match fontdue::Font::from_bytes( @@ -238,7 +203,7 @@ fn embed_glyphs_with_fontdb<'a>( Err(fontdue_msg) => { diag.push_error( format!( - "internal error: fontdue can't parse font {path}: {fontdue_msg}" + "internal error: fontdue can't parse font {}: {fontdue_msg}", path.display() ), &generic_diag_location, ); @@ -256,15 +221,16 @@ fn embed_glyphs_with_fontdb<'a>( family_name } else { diag.push_error( - format!("internal error: TrueType font without english family name encountered: {path}"), + format!("internal error: TrueType font without english family name encountered: {}", path.display()), &generic_diag_location, ); return None; }; embed_font( + fontdb, family_name, - fontdue_font, + Font{ id: face_id, fontdue_font }, &pixel_sizes, characters_seen.iter().cloned(), &fallback_fonts, @@ -274,7 +240,7 @@ fn embed_glyphs_with_fontdb<'a>( maybe_font } else { diag.push_error( - format!("internal error: face_id of selected font {path} is unknown to fontdb"), + format!("internal error: face_id of selected font {} is unknown to fontdb", path.display()), &generic_diag_location, ); return; @@ -289,7 +255,7 @@ fn embed_glyphs_with_fontdb<'a>( let resource_id = component.embedded_file_resources.borrow().len(); component.embedded_file_resources.borrow_mut().insert( - path, + path.to_string_lossy().to_string(), crate::embedded_resources::EmbeddedResources { id: resource_id, kind: crate::embedded_resources::EmbeddedResourcesKind::BitmapFontData(font), @@ -307,18 +273,17 @@ fn embed_glyphs_with_fontdb<'a>( }; // Make sure to embed the default font first, because that becomes the default at run-time. - embed_font_by_path_and_face_id( - default_font_path.clone(), - fonts.remove(&default_font_path).unwrap(), - ); + for path in default_font_paths { + embed_font_by_path_and_face_id(&path, fonts.remove(&path).unwrap()); + } - for (path, face_id) in fonts { - embed_font_by_path_and_face_id(path, face_id); + for (path, face_id) in &fonts { + embed_font_by_path_and_face_id(path, *face_id); } } #[inline(never)] // workaround https://github.com/rust-lang/rust/issues/104099 -fn get_fallback_fonts(fontdb: &sharedfontdb::FontDatabase) -> (Vec, fontdb::ID) { +fn get_fallback_fonts(fontdb: &sharedfontdb::FontDatabase) -> Vec { #[allow(unused)] let mut fallback_families: Vec = Vec::new(); @@ -361,16 +326,14 @@ fn get_fallback_fonts(fontdb: &sharedfontdb::FontDatabase) -> (Vec>(); - let fallback_font = fontdb - .query(&fontdb::Query { families: &[fontdb::Family::SansSerif], ..Default::default() }) - .expect("internal error: Failed to locate default system font"); - (fallback_fonts, fallback_font) + fallback_fonts } #[cfg(not(target_arch = "wasm32"))] fn embed_font( + fontdb: &fontdb::Database, family_name: String, - font: fontdue::Font, + font: Font, pixel_sizes: &[i16], character_coverage: impl Iterator, fallback_fonts: &[fontdue::Font], @@ -392,7 +355,7 @@ fn embed_font( glyph_data.resize(character_map.len(), Default::default()); for CharacterMapEntry { code_point, glyph_index } in &character_map { - let (metrics, bitmap) = core::iter::once(&font) + let (metrics, bitmap) = core::iter::once(&font.fontdue_font) .chain(fallback_fonts.iter()) .find_map(|font| { font.chars() @@ -422,6 +385,8 @@ fn embed_font( .horizontal_line_metrics(font.units_per_em()) .expect("encountered font without hmtx table"); + let face_info = fontdb.face(font.id).unwrap(); + BitmapFont { family_name, character_map, @@ -429,6 +394,8 @@ fn embed_font( ascent: metrics.ascent, descent: metrics.descent, glyphs, + weight: face_info.weight.0, + italic: if face_info.style == fontdb::Style::Normal { false } else { true }, } } diff --git a/internal/core/graphics/bitmapfont.rs b/internal/core/graphics/bitmapfont.rs index 772b5f72070..219c595b23c 100644 --- a/internal/core/graphics/bitmapfont.rs +++ b/internal/core/graphics/bitmapfont.rs @@ -59,4 +59,8 @@ pub struct BitmapFont { /// A vector of pre-rendered glyph sets. Each glyph set must have the same number of glyphs, /// which must be at least as big as the largest glyph index in the character map. pub glyphs: Slice<'static, BitmapGlyphs>, + /// The weight of the font in CSS units (400 is normal). + pub weight: u16, + /// Whether the type-face is rendered italic. + pub italic: bool, } diff --git a/internal/core/software_renderer/fonts.rs b/internal/core/software_renderer/fonts.rs index 65aa5e94d36..5306d5137df 100644 --- a/internal/core/software_renderer/fonts.rs +++ b/internal/core/software_renderer/fonts.rs @@ -70,6 +70,7 @@ pub fn match_font(request: &FontRequest, scale_factor: ScaleFactor) -> Font { core::str::from_utf8(bitmap_font.family_name.as_slice()).unwrap() == requested_family.as_str() }) + .filter(|bitmap_font| bitmap_font.italic == request.italic) .copied() }) }); @@ -81,9 +82,15 @@ pub fn match_font(request: &FontRequest, scale_factor: ScaleFactor) -> Font { if let Some(vectorfont) = systemfonts::match_font(request, scale_factor) { return vectorfont.into(); } - if let Some(fallback_bitmap_font) = - BITMAP_FONTS.with(|fonts| fonts.borrow().first().cloned()) - { + if let Some(fallback_bitmap_font) = BITMAP_FONTS.with(|fonts| { + let fonts = fonts.borrow(); + fonts + .iter() + .cloned() + .filter(|bitmap_font| bitmap_font.italic == request.italic) + .next() + .or_else(|| fonts.first().cloned()) + }) { fallback_bitmap_font } else { #[cfg(feature = "software-renderer-systemfonts")] diff --git a/tests/screenshots/build.rs b/tests/screenshots/build.rs index b05bf2e9080..327834382c3 100644 --- a/tests/screenshots/build.rs +++ b/tests/screenshots/build.rs @@ -37,18 +37,10 @@ pub fn collect_test_cases() -> std::io::Result> { } fn main() -> std::io::Result<()> { - let default_font_path: std::path::PathBuf = [ - env!("CARGO_MANIFEST_DIR"), - "..", - "..", - "examples", - "printerdemo", - "ui", - "fonts", - "NotoSans-Regular.ttf", - ] - .iter() - .collect(); + let default_font_path: std::path::PathBuf = + [env!("CARGO_MANIFEST_DIR"), "..", "..", "examples", "printerdemo", "ui", "fonts"] + .iter() + .collect(); std::env::set_var("SLINT_DEFAULT_FONT", default_font_path.clone()); println!("cargo:rustc-env=SLINT_DEFAULT_FONT={}", default_font_path.display()); diff --git a/tests/screenshots/cases/software/basic/text.slint b/tests/screenshots/cases/software/basic/text.slint index 005fe87b663..26aefca5c5d 100644 --- a/tests/screenshots/cases/software/basic/text.slint +++ b/tests/screenshots/cases/software/basic/text.slint @@ -23,6 +23,7 @@ TestCase := Window { font-size: 10px; width: 100%; horizontal-alignment: right; + font-italic: true; } } } diff --git a/tests/screenshots/references/software/basic/text.png b/tests/screenshots/references/software/basic/text.png index cad19632e32..cdce07e9328 100644 Binary files a/tests/screenshots/references/software/basic/text.png and b/tests/screenshots/references/software/basic/text.png differ