Browser applications cannot rely on operating-system font discovery, but embedding comprehensive emoji and CJK fonts makes the initial WebAssembly payload prohibitively large. GPUI previously offered no structured way to learn that fallback was exhausted, and adding a font at runtime left descriptor and line-layout caches shaped against the old font database. This change lets WGPU text systems report unresolved grapheme clusters through `App::on_missing_glyphs`. GPUI invokes one replaceable application callback after shaping, carrying each complete grapheme and whether its source run is proportional or monospace. Reports are deduplicated and delivered through a bounded, nonblocking channel; reporting remains disabled until the callback is installed, and queued reports from an earlier font generation are discarded. Successful font installation now advances a font generation. GPUI clears descriptor and platform family resolution, reuses stable font IDs for unchanged faces while refreshing their fallback chains, and invalidates each window's current and previous line-layout caches when it observes the new generation. This supplies the GPUI half of an on-demand web font loader; fetching and selecting font assets remains application-owned. Release Notes: - N/A --------- Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
65 lines
1.9 KiB
Rust
65 lines
1.9 KiB
Rust
use gpui::{FallbackFontClass, FontRun, MissingGlyph, MissingGlyphSink, font, px};
|
|
use std::{
|
|
borrow::Cow,
|
|
sync::{Arc, Mutex},
|
|
};
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
fn main() {}
|
|
|
|
#[derive(Default)]
|
|
struct Reports(Mutex<Vec<MissingGlyph>>);
|
|
|
|
impl MissingGlyphSink for Reports {
|
|
fn report(&self, missing: Vec<MissingGlyph>) {
|
|
self.0.lock().expect("report lock").extend(missing);
|
|
}
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn test_missing_glyph_notifications() -> Result<(), JsValue> {
|
|
let platform = gpui_platform::current_platform(false);
|
|
let text_system = platform.text_system();
|
|
text_system
|
|
.add_fonts(vec![Cow::Borrowed(include_bytes!(
|
|
"../../../../assets/fonts/lilex/Lilex-Regular.ttf"
|
|
))])
|
|
.map_err(|error| JsValue::from_str(&error.to_string()))?;
|
|
let font_id = text_system
|
|
.font_id(&font("Lilex"))
|
|
.map_err(|error| JsValue::from_str(&error.to_string()))?;
|
|
let reports = Arc::new(Reports::default());
|
|
text_system.set_missing_glyph_sink(Some(reports.clone()));
|
|
let text = "A界😀👨👩👧👦か\u{3099}";
|
|
let runs = [FontRun {
|
|
len: text.len(),
|
|
font_id,
|
|
}];
|
|
let layout = text_system.layout_line(text, px(24.), &runs);
|
|
assert!(
|
|
layout
|
|
.runs
|
|
.iter()
|
|
.any(|run| run.glyphs.iter().any(|glyph| glyph.is_emoji))
|
|
);
|
|
let missing = reports.0.lock().expect("report lock").clone();
|
|
assert_eq!(
|
|
missing
|
|
.iter()
|
|
.map(|report| report.grapheme())
|
|
.collect::<Vec<_>>(),
|
|
["界", "か\u{3099}"],
|
|
);
|
|
assert!(
|
|
missing
|
|
.iter()
|
|
.all(|report| report.font_class() == FallbackFontClass::Monospace)
|
|
);
|
|
|
|
reports.0.lock().expect("report lock").clear();
|
|
text_system.set_missing_glyph_sink(None);
|
|
text_system.layout_line(text, px(24.), &runs);
|
|
assert!(reports.0.lock().expect("report lock").is_empty());
|
|
Ok(())
|
|
}
|