gpui_wgpu: Respect buffer_font_fallbacks setting (#54878)

wires user configured `FontFallbacks` into the cosmic text path. the
chain is resolved at font load time and stored on each `LoadedFont`.
`layout_line` splits each `FontRun` into spans by codepoint coverage and
emits one `Attrs` per slot so cosmic text shapes each span with the
correct face. inheriting codepoints (marks, zwj, zwnj, variation
selectors) stick to the current span so emoji zwj sequences and
combining marks are not torn across faces.

Closes #17254

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] Performance impact has been considered and is acceptable
- [x] Tests cover the new/changed behavior
- [] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)

Release Notes:

- added support for `buffer_font_fallbacks` on linux
This commit is contained in:
Albab Hasan 2026-05-15 21:34:18 +06:00 committed by GitHub
parent 3eeda10ede
commit 1c16e13a2b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1154 additions and 654 deletions

2
Cargo.lock generated
View file

@ -7930,6 +7930,7 @@ dependencies = [
"bytemuck",
"collections",
"cosmic-text",
"criterion",
"etagere",
"gpui",
"gpui_util",
@ -7942,6 +7943,7 @@ dependencies = [
"raw-window-handle",
"smallvec",
"swash",
"unicode-segmentation",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",

View file

@ -29,6 +29,7 @@ profiling.workspace = true
raw-window-handle = "0.6"
smallvec.workspace = true
swash = "0.2.6"
unicode-segmentation.workspace = true
gpui_util.workspace = true
wgpu.workspace = true
@ -43,4 +44,11 @@ pollster.workspace = true
wasm-bindgen.workspace = true
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = ["HtmlCanvasElement"] }
js-sys = "0.3"
js-sys = "0.3"
[dev-dependencies]
criterion.workspace = true
[[bench]]
name = "layout_line"
harness = false

View file

@ -0,0 +1,82 @@
use criterion::{Criterion, criterion_group, criterion_main};
use gpui::{FontFallbacks, FontRun, PlatformTextSystem, font, px};
use gpui_wgpu::CosmicTextSystem;
use std::borrow::Cow;
const LILEX: &[u8] = include_bytes!("../../../assets/fonts/lilex/Lilex-Regular.ttf");
const IBM_PLEX: &[u8] =
include_bytes!("../../../assets/fonts/ibm-plex-sans/IBMPlexSans-Regular.ttf");
// ~4 000 chars of typical ASCII code text.
fn code_text() -> String {
concat!(
" fn compute_run_spans(\n",
" text: &str,\n",
" run_offset: usize,\n",
" run_len: usize,\n",
" primary: FontId,\n",
" fallback_chain: &[(FontId, SharedString)],\n",
" covers: &impl Fn(FontId, char) -> bool,\n",
" ) -> SmallVec<[RunSpan; 4]> {\n",
" let mut spans = SmallVec::new();\n",
" let run_end = run_offset + run_len;\n",
" if run_end <= run_offset { return spans; }\n",
" let run_text = &text[run_offset..run_end];\n",
" let mut span_start = run_offset;\n",
" let mut span_slot: Option<usize> = None;\n",
" for (ch_idx, ch) in run_text.char_indices() {\n",
" let abs = run_offset + ch_idx;\n",
" let next = pick_covering_slot(ch, span_slot, primary, fallback_chain, covers);\n",
" if next == span_slot { continue; }\n",
" if abs > span_start {\n",
" spans.push(RunSpan { start: span_start, end: abs, slot: span_slot });\n",
" }\n",
" span_start = abs;\n",
" span_slot = next;\n",
" }\n",
" spans\n",
" }\n",
)
.repeat(8) // ~3 800 chars
}
fn bench_layout_line(c: &mut Criterion) {
let system = CosmicTextSystem::new_without_system_fonts("Lilex");
system
.add_fonts(vec![Cow::Borrowed(LILEX), Cow::Borrowed(IBM_PLEX)])
.unwrap();
let font_id_no_fallback = system.font_id(&font("Lilex")).unwrap();
let font_id_with_fallback = {
let mut f = font("Lilex");
f.fallbacks = Some(FontFallbacks::from_fonts(vec!["IBM Plex Sans".to_string()]));
system.font_id(&f).unwrap()
};
let text = code_text();
let runs_no_fallback = vec![FontRun {
len: text.len(),
font_id: font_id_no_fallback,
}];
let runs_with_fallback = vec![FontRun {
len: text.len(),
font_id: font_id_with_fallback,
}];
let mut group = c.benchmark_group("layout_line");
group.bench_function("no_fallback", |b| {
b.iter(|| system.layout_line(&text, px(14.0), &runs_no_fallback))
});
group.bench_function("with_fallback_ascii", |b| {
b.iter(|| system.layout_line(&text, px(14.0), &runs_with_fallback))
});
group.finish();
}
criterion_group!(benches, bench_layout_line);
criterion_main!(benches);

File diff suppressed because it is too large Load diff