mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-09 03:51:47 +00:00
craft-fonts-build gains Embed::serve (served faces for web builds) and the coverage module that craft-ui's fonts use.
104 lines
4.8 KiB
Rust
104 lines
4.8 KiB
Rust
//! Served faces in a browser: a character no installed face has fetches the one served face that
|
|
//! has it, a character no served face has fetches nothing, and a failed face is fetched again
|
|
//! later.
|
|
//!
|
|
//! Start `tests/web/serve_fonts.py` (127.0.0.1:8137; another address through
|
|
//! `CRAFT_UI_FONT_SERVER` when building), then run
|
|
//!
|
|
//! ```text
|
|
//! cargo test -p craft-ui --target wasm32-unknown-unknown --test web_fonts
|
|
//! ```
|
|
//!
|
|
//! with `wasm-bindgen-test-runner` as the target's runner: headless through a WebDriver, or with
|
|
//! `NO_HEADLESS=1`, which serves the test page for any browser to open.
|
|
#![cfg(target_arch = "wasm32")]
|
|
|
|
use craft_ui::fonts::{I18nScripts, ServedFace, SystemFonts, UiFonts};
|
|
use egui::{FontDefinitions, FontFamily, FontId};
|
|
use wasm_bindgen::JsCast as _;
|
|
use wasm_bindgen_futures::JsFuture;
|
|
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
|
|
|
|
wasm_bindgen_test_configure!(run_in_browser);
|
|
|
|
fn url(path: &str) -> &'static str {
|
|
let server = option_env!("CRAFT_UI_FONT_SERVER").unwrap_or("http://127.0.0.1:8137");
|
|
Box::leak(format!("{server}{path}").into_boxed_str())
|
|
}
|
|
|
|
fn face(family: &'static str, scripts: &'static [&'static str], path: &str, coverage: Vec<u8>) -> ServedFace {
|
|
ServedFace { family, style: "Regular", scripts, index: 0, url: url(path), size: 0, integrity: "", coverage: coverage.leak() }
|
|
}
|
|
|
|
fn coverage_of(font: &[u8]) -> Vec<u8> {
|
|
craft_fonts_build::coverage::of_face(font, 0).expect("a font")
|
|
}
|
|
|
|
async fn sleep(millis: i32) {
|
|
let promise = js_sys::Promise::new(&mut |resolve, _| {
|
|
let window = web_sys::window().expect("a browser window");
|
|
window.set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, millis).expect("setTimeout");
|
|
});
|
|
JsFuture::from(promise).await.expect("a timer");
|
|
}
|
|
|
|
/// Run passes until the context draws `c`, for up to `seconds`.
|
|
async fn draws_within(ctx: &egui::Context, c: char, seconds: f64) -> bool {
|
|
let start = js_sys::Date::now();
|
|
loop {
|
|
ctx.run_ui(egui::RawInput::default(), |_| {}).textures_delta.clear();
|
|
if ctx.fonts_mut(|f| f.has_glyph(&FontId::proportional(14.0), c)) {
|
|
return true;
|
|
}
|
|
if js_sys::Date::now() - start > seconds * 1000.0 {
|
|
return false;
|
|
}
|
|
sleep(50).await;
|
|
}
|
|
}
|
|
|
|
/// The paths the font server was asked for, in order.
|
|
async fn requested() -> Vec<String> {
|
|
let window = web_sys::window().expect("a browser window");
|
|
let response: web_sys::Response = JsFuture::from(window.fetch_with_str(url("/log"))).await.expect("the font server").unchecked_into();
|
|
let paths = JsFuture::from(response.json().expect("JSON")).await.expect("JSON");
|
|
js_sys::Array::from(&paths).iter().filter_map(|path| path.as_string()).collect()
|
|
}
|
|
|
|
#[wasm_bindgen_test]
|
|
async fn a_missing_character_fetches_the_face_that_has_it_and_a_failed_one_is_retried() {
|
|
let encode = craft_fonts_build::coverage::encode;
|
|
let served = [
|
|
face("Noto Sans", &["Latn", "Cyrl", "Grek", "Deva"], "/fonts/NotoSans-test.ttf", coverage_of(include_bytes!("fonts/NotoSans-test.ttf"))),
|
|
face("Noto Color Emoji", &["Zsye"], "/missing/NotoColorEmoji.ttf", encode(&[(0x1F300, 0x1FAFF)])),
|
|
// ⌃ ⎋, and ⌥ ⌫ ⏎: the keyboard keys are split between the two symbol faces.
|
|
face("Noto Sans Symbols", &["Zsym"], "/fonts/NotoSansSymbols-test.ttf", coverage_of(include_bytes!("fonts/NotoSansSymbols-test.ttf"))),
|
|
// Fails the first time.
|
|
face("Noto Sans Symbols 2", &["Zsym"], "/flaky/NotoSansSymbols2-test.ttf", coverage_of(include_bytes!("fonts/NotoSansSymbols2-test.ttf"))),
|
|
face("Noto Sans Math", &["Zmth", "Zsym"], "/missing/NotoSansMath-Regular.ttf", encode(&[(0x2200, 0x22FF)])),
|
|
];
|
|
let defaults = FontDefinitions::default();
|
|
let ubuntu = (**defaults.font_data.get("Ubuntu-Light").expect("egui default_fonts")).clone();
|
|
let ctx = egui::Context::default();
|
|
UiFonts::new(&I18nScripts)
|
|
.app_face("ui", ubuntu, &[FontFamily::Proportional])
|
|
.served_faces(&served)
|
|
.ui_language("en")
|
|
.system_fonts(SystemFonts::Off)
|
|
.stack()
|
|
.install(&ctx);
|
|
|
|
assert!(draws_within(&ctx, 'ổ', 5.0).await, "Vietnamese from Noto Sans");
|
|
assert!(draws_within(&ctx, '⌃', 5.0).await, "a key from Noto Sans Symbols");
|
|
// Only Symbols 2 has ⌥: it fails, and is fetched again after two seconds.
|
|
assert!(draws_within(&ctx, '⌥', 10.0).await, "a key from Noto Sans Symbols 2 after a failure");
|
|
assert!(draws_within(&ctx, '⌫', 1.0).await);
|
|
// No face has ẫ.
|
|
assert!(!draws_within(&ctx, 'ẫ', 1.0).await);
|
|
assert_eq!(
|
|
requested().await,
|
|
["/fonts/NotoSans-test.ttf", "/fonts/NotoSansSymbols-test.ttf", "/flaky/NotoSansSymbols2-test.ttf", "/flaky/NotoSansSymbols2-test.ttf"],
|
|
"each character fetches only the face that has it",
|
|
);
|
|
}
|