diff --git a/api/rs/slint/Cargo.toml b/api/rs/slint/Cargo.toml index 0d25d2363a1..d5925d09d51 100644 --- a/api/rs/slint/Cargo.toml +++ b/api/rs/slint/Cargo.toml @@ -56,6 +56,9 @@ serde = ["i-slint-core/serde"] ## This feature enables the software renderer to pick up fonts from the operating system for text rendering. software-renderer-systemfonts = ["renderer-software", "i-slint-core/software-renderer-systemfonts"] +## Internal feature: Enable shared fontique font handling +shared-fontique = ["i-slint-core/shared-fontique"] + ## Slint uses internally some `thread_local` state. ## ## When the `std` feature is enabled, Slint can use [`std::thread_local!`], but when in a `#![no_std]` diff --git a/api/rs/slint/lib.rs b/api/rs/slint/lib.rs index 2ec865ba08d..b26e3a5eb6a 100644 --- a/api/rs/slint/lib.rs +++ b/api/rs/slint/lib.rs @@ -233,6 +233,14 @@ pub use i_slint_core::{ string::{SharedString, ToSharedString}, }; +/// Register a custom font from byte data at runtime. +/// +/// Returns the number of font families that were registered from the provided data. +#[cfg(feature = "shared-fontique")] +pub fn register_font_from_memory(font_data: Vec) -> usize { + i_slint_core::register_font_from_memory(font_data) +} + pub mod private_unstable_api; /// Enters the main event loop. This is necessary in order to receive diff --git a/examples/gallery/Cargo.toml b/examples/gallery/Cargo.toml index 05d703d50a2..e735b064be1 100644 --- a/examples/gallery/Cargo.toml +++ b/examples/gallery/Cargo.toml @@ -31,7 +31,7 @@ slint-build = { path = "../../api/rs/build" } #wasm# #wasm# [target.'cfg(target_arch = "wasm32")'.dependencies] #wasm# wasm-bindgen = { version = "0.2" } -#wasm# web-sys = { version = "0.3", features=["console"] } +#wasm# web-sys = { version = "0.3", features=["console", "Window", "Navigator"] } #wasm# console_error_panic_hook = "0.1.5" [package.metadata.bundle] diff --git a/examples/gallery/build.rs b/examples/gallery/build.rs index d627d293ec6..83f9682a000 100644 --- a/examples/gallery/build.rs +++ b/examples/gallery/build.rs @@ -2,5 +2,10 @@ // SPDX-License-Identifier: MIT fn main() { - slint_build::compile("gallery.slint").unwrap(); + slint_build::compile_with_config( + "gallery.slint", + slint_build::CompilerConfiguration::new() + .with_bundled_translations(concat!(env!("CARGO_MANIFEST_DIR"), "/lang/")), + ) + .unwrap(); } diff --git a/examples/gallery/index.html b/examples/gallery/index.html index a56c500b433..451f5c9ded1 100644 --- a/examples/gallery/index.html +++ b/examples/gallery/index.html @@ -51,6 +51,44 @@

Slint Gallery

var galleries = []; var currentGallery = undefined; + // Get Noto CJK font URL from GitHub for the detected language + function getNotoFontUrl(lang) { + const langCode = lang.split('-')[0].toLowerCase(); + + // Direct URLs to OTF files from Noto CJK GitHub repository (using raw.githubusercontent.com for CORS) + const fontMap = { + 'ja': 'https://raw.githubusercontent.com/notofonts/noto-cjk/main/Sans/OTF/Japanese/NotoSansCJKjp-Regular.otf', + // 'zh': 'https://raw.githubusercontent.com/notofonts/noto-cjk/main/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf', + // 'ko': 'https://raw.githubusercontent.com/notofonts/noto-cjk/main/Sans/OTF/Korean/NotoSansCJKkr-Regular.otf', + }; + + return fontMap[langCode]; + } + + // Fetch font from GitHub + async function fetchFont(fontUrl) { + const fontResponse = await fetch(fontUrl); + if (!fontResponse.ok) { + throw new Error(`HTTP ${fontResponse.status}: ${fontResponse.statusText}`); + } + return await fontResponse.arrayBuffer(); + } + + // Load font for the detected language + async function loadFontForLanguage(module, lang) { + const fontUrl = getNotoFontUrl(lang); + + if (fontUrl) { + try { + const fontData = await fetchFont(fontUrl); + const uint8Array = new Uint8Array(fontData); + const result = await module.load_font_from_bytes(uint8Array); + } catch (error) { + console.error(`Failed to load font for language ${lang}:`, error); + } + } + } + function initGallery(gallery) { document.getElementById("spinner").hidden = false; @@ -67,7 +105,7 @@

Slint Gallery

document.getElementById("canvas-parent").appendChild(galleries[gallery]); document.getElementById("spinner").hidden = true; } else { - import(gallery).then(module => { + import(gallery).then(async module => { let canvas = document.createElement("canvas"); canvas.id = "canvas"; canvas.dataset.slintAutoResizeToPreferred = "true"; @@ -75,11 +113,23 @@

Slint Gallery

galleries[gallery] = canvas; document.getElementById("canvas-parent").appendChild(canvas); - module.default().finally(() => { - document.getElementById("canvas").hidden = false; - document.getElementById("spinner").hidden = true; - }); - }) + + // Initialize WASM module first + await module.default(); + + // Detect browser language and load appropriate font + const browserLang = (navigator.languages && navigator.languages[0]) || navigator.language || navigator.userLanguage || 'en'; + await loadFontForLanguage(module, browserLang); + + // Start the application + module.main(); + + document.getElementById("canvas").hidden = false; + document.getElementById("spinner").hidden = true; + }).catch(error => { + console.error('Failed to initialize gallery:', error); + document.getElementById("spinner").hidden = true; + }); } } diff --git a/examples/gallery/main.rs b/examples/gallery/main.rs index 5b16cf0bb9a..4b06fef995d 100644 --- a/examples/gallery/main.rs +++ b/examples/gallery/main.rs @@ -8,21 +8,39 @@ use wasm_bindgen::prelude::*; slint::include_modules!(); +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub fn load_font_from_bytes(font_data: &[u8]) -> Result<(), JsValue> { + slint::register_font_from_memory(font_data.to_vec()); + Ok(()) +} + use std::rc::Rc; use slint::{Model, ModelExt, ModelRc, SharedString, StandardListViewItem, VecModel}; -#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen)] pub fn main() { // This provides better error messages in debug mode. // It's disabled in release mode so it doesn't bloat up the file size. #[cfg(all(debug_assertions, target_arch = "wasm32"))] console_error_panic_hook::set_once(); + // For native builds, initialize gettext translations + #[cfg(not(target_arch = "wasm32"))] slint::init_translations!(concat!(env!("CARGO_MANIFEST_DIR"), "/lang/")); let app = App::new().unwrap(); + // For WASM builds, select translation after App::new() + #[cfg(target_arch = "wasm32")] + if let Some(window) = web_sys::window() { + if let Some(lang) = window.navigator().language() { + let lang_code = lang.split('-').next().unwrap_or("en"); + let _ = slint::select_bundled_translation(lang_code); + } + } + let row_data: Rc>> = Rc::new(VecModel::default()); for r in 1..101 { diff --git a/internal/common/sharedfontique.rs b/internal/common/sharedfontique.rs index 39977870579..13770c342cd 100644 --- a/internal/common/sharedfontique.rs +++ b/internal/common/sharedfontique.rs @@ -131,6 +131,26 @@ impl std::ops::DerefMut for Collection { } } +/// Register a font from byte data dynamically. +pub fn register_font_from_memory(font_data: Vec) -> usize { + let blob = fontique::Blob::new(Arc::new(font_data)); + + let mut collection = get_collection(); + let fonts = collection.register_fonts(blob, None); + + let family_count = fonts.len(); + + // Set up fallbacks for all scripts + for script in fontique::Script::all_samples().iter().map(|(script, _)| *script) { + collection.append_fallbacks( + fontique::FallbackKey::new(script, None), + fonts.iter().map(|(family_id, _)| *family_id), + ); + } + + family_count +} + /// Font metrics in design space. Scale with desired pixel size and divided by units_per_em /// to obtain pixel metrics. #[derive(Clone)] diff --git a/internal/core/lib.rs b/internal/core/lib.rs index bc36541cc9a..850fb16b9dc 100644 --- a/internal/core/lib.rs +++ b/internal/core/lib.rs @@ -62,6 +62,12 @@ pub mod window; #[doc(inline)] pub use string::SharedString; +/// Register a font from memory. +#[cfg(feature = "shared-fontique")] +pub fn register_font_from_memory(font_data: alloc::vec::Vec) -> usize { + i_slint_common::sharedfontique::register_font_from_memory(font_data) +} + #[doc(inline)] pub use sharedvector::SharedVector;