Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/rs/slint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this feature is meant to be internal , it should not be documented and have a name that shows it.
Example: https://github.com/slint-ui/slint/blob/55a45a6c339223a19bcd79fc191e79193f495a79/api/rs/build/Cargo.toml#L28C1-L28C14

Maybe something like experimental-register-font

But actually, i'd rather not have experimental API like this enabled from the slint crate, but directly on the i-slint-core crate, and have app that want to use internal API to use the i-slint-core directly.


## 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]`
Expand Down
8 changes: 8 additions & 0 deletions api/rs/slint/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>) -> usize {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just wondering what's the most efficient and future proof API.
Also in the light of #4250

I'm thinking we could have something like
fn register_font_from_memory(font_data : impl AsRef[u8] + Sync + Send /* + 'static ? */) -> Result<???, RegisterFontError>

That way we could also pass a &'static str embedded in the binary or in mapped memory.

I see that you are returning the count, any reason to do that?
Maybe we could return some kind of FontHandle? Not sure what the use-case is to get access to that.
However, I think this could error for several reason (invalid format, unsupported by the backend or renderer, and stuff like that.)

We can also bike shed the name of this function.

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
Expand Down
2 changes: 1 addition & 1 deletion examples/gallery/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
7 changes: 6 additions & 1 deletion examples/gallery/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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/")),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the printerdemo only does it for wasm and android.

if target.contains("android") || target.contains("wasm32") {

I think we should do the same.

)
.unwrap();
}
62 changes: 56 additions & 6 deletions examples/gallery/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,44 @@ <h1>Slint Gallery</h1>
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;

Expand All @@ -67,19 +105,31 @@ <h1>Slint Gallery</h1>
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";
currentGallery = 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;
});
}
}

Expand Down
20 changes: 19 additions & 1 deletion examples/gallery/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably something we should do in i_slint_core instead.

fn index_for_locale(languages: &[&'static str]) -> Option<usize> {

Actually, we're already using the sys_locale crate and it should support wasm. But we probably need to enable extra feature in it for it to work?

#[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<VecModel<slint::ModelRc<StandardListViewItem>>> = Rc::new(VecModel::default());

for r in 1..101 {
Expand Down
20 changes: 20 additions & 0 deletions internal/common/sharedfontique.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>) -> usize {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just wondering what's the most efficient and future proof API.
Also in the light of #4250

I'm thinking we could have something like
fn register_font_from_memory(font_data : impl AsRef[u8] + Sync + Send /* + 'static ? */) -> Result<???>

That way we could also pass a &'static str embedded in the binary or in mapped memory.

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)]
Expand Down
6 changes: 6 additions & 0 deletions internal/core/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>) -> usize {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a re-export, it can be done with pub use.

i_slint_common::sharedfontique::register_font_from_memory(font_data)
}

#[doc(inline)]
pub use sharedvector::SharedVector;

Expand Down
Loading