slint/examples/gallery/index.html
Tasuku Suzuki 224a06af57
Gallery Example: Add i18n and dynamic font loading for WASM (#9762)
- Bundle translations for WASM/Android only (not for native builds)
- Load Noto Sans CJK fonts dynamically from GitHub at runtime
- Implement register_font_from_memory() with Result<FontHandle, RegisterFontError>
- Make API automatically available with std feature (no explicit feature flag needed)
- Export load_font_from_bytes() for WASM font loading from JavaScript
- Change from wasm_bindgen(start) to explicit main() call for better control

Fonts are loaded before app initialization to ensure proper CJK text
rendering across all major browsers without bundling font files.

Native builds load translations from lang/ directory at runtime.
WASM/Android builds bundle translations at compile time and detect
browser/system language automatically.

API is now available when std feature is enabled, since we always
have fontique with std. No need for experimental-register-font feature.
2025-11-17 14:00:39 +01:00

169 lines
6 KiB
HTML

<!DOCTYPE html>
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> -->
<!-- SPDX-License-Identifier: MIT -->
<html>
<!--
This is a static html file used to display the wasm build.
In order to generate the build
- uncomment the #wasm# lines in Cargo.toml
- Run in this directory:
SLINT_STYLE=fluent wasm-pack build --release --out-dir pkg/fluent --target web
SLINT_STYLE=material wasm-pack build --release --out-dir pkg/material --target web
SLINT_STYLE=cupertino wasm-pack build --release --out-dir pkg/cupertino --target web
SLINT_STYLE=cosmic wasm-pack build --release --out-dir pkg/cosmic --target web
-->
<head>
<meta charset="UTF-8">
<title>Slint Widget Gallery Demo (Web Assembly version)</title>
<link rel="stylesheet" href="https://slint.dev/css/demos-v1.css">
</head>
<body>
<h1>Slint Gallery</h1>
<p>This is the <a href="https://slint.dev">Slint</a> UI Widget Gallery Demo compiled to WebAssembly. It
demonstrates
different re-usable graphical
elements.</p>
<div id="spinner" style="position: relative;">
<div class="spinner">Loading...</div>
</div>
<p>Select style
<select id="style-selection">
<option value="fluent">Fluent</option>
<option value="material">Material</option>
<option value="cupertino">Cupertino</option>
<option value="cosmic">Cosmic</option>
</select>
</p>
<div id="canvas-parent"></div>
<p class="links">
<a href="https://github.com/slint-ui/slint/blob/master/examples/gallery/gallery.slint">
View Source Code on GitHub</a> -
<a href="https://slint.dev/editor?load_demo=examples/gallery/gallery.slint">
Open in SlintPad
</a>
</p>
<script type="module">
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;
if (currentGallery !== undefined) {
let currentGalleryCanvas = document.getElementById("canvas");
// remove old canvas and unload window
if (currentGalleryCanvas != undefined) {
document.getElementById("canvas-parent").removeChild(currentGalleryCanvas);
}
}
if (galleries[gallery] !== undefined) {
document.getElementById("canvas-parent").appendChild(galleries[gallery]);
document.getElementById("spinner").hidden = true;
} else {
import(gallery).then(async module => {
let canvas = document.createElement("canvas");
canvas.id = "canvas";
canvas.dataset.slintAutoResizeToPreferred = "true";
currentGallery = gallery;
galleries[gallery] = canvas;
document.getElementById("canvas-parent").appendChild(canvas);
// 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;
});
}
}
var styleSelection = document.getElementById("style-selection");
function loadGallery() {
var selectedStyle = ".\/pkg\/" + styleSelection[styleSelection.selectedIndex].value + "\/gallery.js";
initGallery(selectedStyle);
}
styleSelection.onchange = loadGallery;
window.addEventListener('load', () => {
const urlParams = new URLSearchParams(window.location.search);
const style = urlParams.get('style');
if (style) {
document.getElementById('style-selection').value = style.split(',')[0];
} else {
const userAgent = window.navigator.userAgent.toLowerCase();
let defaultStyle = '';
if (userAgent.indexOf('mac') !== -1 || userAgent.indexOf('iphone') !== -1 || userAgent.indexOf('ipad') !== -1) {
defaultStyle = 'cupertino';
} else if (userAgent.indexOf('windows') !== -1) {
defaultStyle = 'fluent';
} else if (userAgent.indexOf('android') !== -1) {
defaultStyle = 'material';
}
if (defaultStyle) {
document.getElementById('style-selection').value = defaultStyle;
}
}
loadGallery();
}, false);
</script>
</body>
</html>