Skip to content

Commit 4b9375c

Browse files
committed
Gallery Example: Add i18n and dynamic font loading for WASM
- Bundle translations and detect browser language automatically - Load Noto Sans CJK fonts dynamically from GitHub at runtime - Add register_font_from_memory() API for runtime font registration - Export main() with #[wasm_bindgen] for explicit app startup control Fonts are loaded before app initialization to ensure proper CJK text rendering across all major browsers without bundling font files.
1 parent 55a45a6 commit 4b9375c

File tree

7 files changed

+114
-9
lines changed

7 files changed

+114
-9
lines changed

api/rs/slint/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,13 @@ pub use i_slint_core::{
233233
string::{SharedString, ToSharedString},
234234
};
235235

236+
/// Register a custom font from byte data at runtime.
237+
///
238+
/// Returns the number of font families that were registered from the provided data.
239+
pub fn register_font_from_memory(font_data: alloc::vec::Vec<u8>) -> usize {
240+
i_slint_core::register_font_from_memory(font_data)
241+
}
242+
236243
pub mod private_unstable_api;
237244

238245
/// Enters the main event loop. This is necessary in order to receive

examples/gallery/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ slint-build = { path = "../../api/rs/build" }
3131
#wasm#
3232
#wasm# [target.'cfg(target_arch = "wasm32")'.dependencies]
3333
#wasm# wasm-bindgen = { version = "0.2" }
34-
#wasm# web-sys = { version = "0.3", features=["console"] }
34+
#wasm# web-sys = { version = "0.3", features=["console", "Window", "Navigator"] }
3535
#wasm# console_error_panic_hook = "0.1.5"
3636

3737
[package.metadata.bundle]

examples/gallery/build.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,10 @@
22
// SPDX-License-Identifier: MIT
33

44
fn main() {
5-
slint_build::compile("gallery.slint").unwrap();
5+
slint_build::compile_with_config(
6+
"gallery.slint",
7+
slint_build::CompilerConfiguration::new()
8+
.with_bundled_translations(concat!(env!("CARGO_MANIFEST_DIR"), "/lang/")),
9+
)
10+
.unwrap();
611
}

examples/gallery/index.html

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,44 @@ <h1>Slint Gallery</h1>
5151
var galleries = [];
5252
var currentGallery = undefined;
5353

54+
// Get Noto CJK font URL from GitHub for the detected language
55+
function getNotoFontUrl(lang) {
56+
const langCode = lang.split('-')[0].toLowerCase();
57+
58+
// Direct URLs to OTF files from Noto CJK GitHub repository (using raw.githubusercontent.com for CORS)
59+
const fontMap = {
60+
'ja': 'https://raw.githubusercontent.com/notofonts/noto-cjk/main/Sans/OTF/Japanese/NotoSansCJKjp-Regular.otf',
61+
// 'zh': 'https://raw.githubusercontent.com/notofonts/noto-cjk/main/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf',
62+
// 'ko': 'https://raw.githubusercontent.com/notofonts/noto-cjk/main/Sans/OTF/Korean/NotoSansCJKkr-Regular.otf',
63+
};
64+
65+
return fontMap[langCode];
66+
}
67+
68+
// Fetch font from GitHub
69+
async function fetchFont(fontUrl) {
70+
const fontResponse = await fetch(fontUrl);
71+
if (!fontResponse.ok) {
72+
throw new Error(`HTTP ${fontResponse.status}: ${fontResponse.statusText}`);
73+
}
74+
return await fontResponse.arrayBuffer();
75+
}
76+
77+
// Load font for the detected language
78+
async function loadFontForLanguage(module, lang) {
79+
const fontUrl = getNotoFontUrl(lang);
80+
81+
if (fontUrl) {
82+
try {
83+
const fontData = await fetchFont(fontUrl);
84+
const uint8Array = new Uint8Array(fontData);
85+
const result = await module.load_font_from_bytes(uint8Array);
86+
} catch (error) {
87+
console.error(`Failed to load font for language ${lang}:`, error);
88+
}
89+
}
90+
}
91+
5492
function initGallery(gallery) {
5593
document.getElementById("spinner").hidden = false;
5694

@@ -67,19 +105,31 @@ <h1>Slint Gallery</h1>
67105
document.getElementById("canvas-parent").appendChild(galleries[gallery]);
68106
document.getElementById("spinner").hidden = true;
69107
} else {
70-
import(gallery).then(module => {
108+
import(gallery).then(async module => {
71109
let canvas = document.createElement("canvas");
72110
canvas.id = "canvas";
73111
canvas.dataset.slintAutoResizeToPreferred = "true";
74112
currentGallery = gallery;
75113
galleries[gallery] = canvas;
76114

77115
document.getElementById("canvas-parent").appendChild(canvas);
78-
module.default().finally(() => {
79-
document.getElementById("canvas").hidden = false;
80-
document.getElementById("spinner").hidden = true;
81-
});
82-
})
116+
117+
// Initialize WASM module first
118+
await module.default();
119+
120+
// Detect browser language and load appropriate font
121+
const browserLang = (navigator.languages && navigator.languages[0]) || navigator.language || navigator.userLanguage || 'en';
122+
await loadFontForLanguage(module, browserLang);
123+
124+
// Start the application
125+
module.main();
126+
127+
document.getElementById("canvas").hidden = false;
128+
document.getElementById("spinner").hidden = true;
129+
}).catch(error => {
130+
console.error('Failed to initialize gallery:', error);
131+
document.getElementById("spinner").hidden = true;
132+
});
83133
}
84134
}
85135

examples/gallery/main.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,39 @@ use wasm_bindgen::prelude::*;
88

99
slint::include_modules!();
1010

11+
#[cfg(target_arch = "wasm32")]
12+
#[wasm_bindgen]
13+
pub fn load_font_from_bytes(font_data: &[u8]) -> Result<(), JsValue> {
14+
slint::register_font_from_memory(font_data.to_vec());
15+
Ok(())
16+
}
17+
1118
use std::rc::Rc;
1219

1320
use slint::{Model, ModelExt, ModelRc, SharedString, StandardListViewItem, VecModel};
1421

15-
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
22+
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
1623
pub fn main() {
1724
// This provides better error messages in debug mode.
1825
// It's disabled in release mode so it doesn't bloat up the file size.
1926
#[cfg(all(debug_assertions, target_arch = "wasm32"))]
2027
console_error_panic_hook::set_once();
2128

29+
// For native builds, initialize gettext translations
30+
#[cfg(not(target_arch = "wasm32"))]
2231
slint::init_translations!(concat!(env!("CARGO_MANIFEST_DIR"), "/lang/"));
2332

2433
let app = App::new().unwrap();
2534

35+
// For WASM builds, select translation after App::new()
36+
#[cfg(target_arch = "wasm32")]
37+
if let Some(window) = web_sys::window() {
38+
if let Some(lang) = window.navigator().language() {
39+
let lang_code = lang.split('-').next().unwrap_or("en");
40+
let _ = slint::select_bundled_translation(lang_code);
41+
}
42+
}
43+
2644
let row_data: Rc<VecModel<slint::ModelRc<StandardListViewItem>>> = Rc::new(VecModel::default());
2745

2846
for r in 1..101 {

internal/common/sharedfontique.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,26 @@ impl std::ops::DerefMut for Collection {
131131
}
132132
}
133133

134+
/// Register a font from byte data dynamically.
135+
pub fn register_font_from_memory(font_data: Vec<u8>) -> usize {
136+
let blob = fontique::Blob::new(Arc::new(font_data));
137+
138+
let mut collection = get_collection();
139+
let fonts = collection.register_fonts(blob, None);
140+
141+
let family_count = fonts.len();
142+
143+
// Set up fallbacks for all scripts
144+
for script in fontique::Script::all_samples().iter().map(|(script, _)| *script) {
145+
collection.append_fallbacks(
146+
fontique::FallbackKey::new(script, None),
147+
fonts.iter().map(|(family_id, _)| *family_id),
148+
);
149+
}
150+
151+
family_count
152+
}
153+
134154
/// Font metrics in design space. Scale with desired pixel size and divided by units_per_em
135155
/// to obtain pixel metrics.
136156
#[derive(Clone)]

internal/core/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ pub mod window;
6262
#[doc(inline)]
6363
pub use string::SharedString;
6464

65+
/// Register a font from memory.
66+
pub fn register_font_from_memory(font_data: alloc::vec::Vec<u8>) -> usize {
67+
i_slint_common::sharedfontique::register_font_from_memory(font_data)
68+
}
69+
6570
#[doc(inline)]
6671
pub use sharedvector::SharedVector;
6772

0 commit comments

Comments
 (0)