mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 12:17:14 -06:00
The panels and dialogs draw in their own egui families (`ui`, `ui-semibold`, `mono`), and egui panics drawing text in a family no font is bound to. Only `theme::install_fonts` binds them, and only `VectorcraftApp::logic` calls it, on its first frame. An embedder that draws the panels or dialogs itself (`dialogs::show`, a panel's `ui`) on its own context, without `logic`, panics on the first semibold title it draws, an effect dialog's for one. Calling `install_fonts` avoids that, but it embeds the interface's five faces (about 2 MB) whatever the embedder wants: today such an embedder keeps them out of its binary only by never calling it, so that the linker drops them. vectorcraft-ui-egui gets an `interface-fonts` feature, on by default, that embeds those faces. Without it `install_fonts` binds the same three families to egui's built-in fonts, so an embedder can always call it. Those lack some symbols the panels show (→ ▾ ● ✓), which the docs say. With the feature, `install_fonts` installs exactly what it did before: the same font data and the same family stacks. `tests/install_fonts.rs` draws an effect dialog on a context only `install_fonts` prepared, and checks the face at the head of each family in both configurations. `cargo xtask ci`'s `no-bundled-fonts` step runs it without default features and lints the crate that way. Without the `install_fonts` call, or without the semibold binding, both tests fail with egui's "FontFamily::Name(\"ui-semibold\") is not bound to any fonts". The two lib tests about the interface faces are adjusted: the graphics-loss test checks a family install_fonts binds, and the catalogs' glyph coverage test needs `interface-fonts`. docs/development.md says when an embedder calls `install_fonts`, and that it replaces the context's font definitions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
78 lines
3.8 KiB
Rust
78 lines
3.8 KiB
Rust
//! `theme::install_fonts` on a context the app never ran on, as an embedder that draws the panels
|
||
//! or dialogs itself makes it: every family they draw in is bound, so an effect dialog draws. With
|
||
//! the `interface-fonts` feature the interface's own faces head them; without it
|
||
//! (`cargo test -p vectorcraft-ui-egui --no-default-features --test install_fonts`) they are
|
||
//! egui's.
|
||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||
|
||
use egui::{FontFamily, FontId};
|
||
use serde_json::json;
|
||
use vectorcraft_engine::Session;
|
||
use vectorcraft_ui_egui::{VectorcraftApp, dialogs, theme};
|
||
|
||
/// The faces `install_fonts` adds with the `interface-fonts` feature.
|
||
const INTERFACE_FACES: [&str; 5] = ["Inter", "Inter-SemiBold", "SourceSans3", "SourceSans3-Semibold", "JetBrainsMono"];
|
||
|
||
/// A context with the interface's fonts installed and nothing else done to it.
|
||
fn context() -> egui::Context {
|
||
let ctx = egui::Context::default();
|
||
theme::install_fonts(&ctx);
|
||
ctx
|
||
}
|
||
|
||
/// One headless frame running `draw`.
|
||
fn frame(ctx: &egui::Context, draw: impl FnMut(&mut egui::Ui)) {
|
||
let mut out = ctx.run_ui(egui::RawInput::default(), draw);
|
||
out.textures_delta.clear();
|
||
}
|
||
|
||
/// Text in each family the panels draw in lays out, and the faces at the head of those families are
|
||
/// the interface's with `interface-fonts` and egui's without it.
|
||
#[test]
|
||
fn every_family_the_panels_draw_in_is_bound() {
|
||
let ctx = context();
|
||
frame(&ctx, |ui| {
|
||
for font in [theme::semibold(12.0), theme::mono(12.0), FontId::new(12.0, FontFamily::Name(theme::FONT_UI.into()))] {
|
||
ui.label(egui::RichText::new("Gaussian Blur").font(font));
|
||
}
|
||
});
|
||
let definitions = ctx.fonts(|fonts| fonts.definitions().clone());
|
||
for name in [theme::FONT_UI, theme::FONT_UI_SEMIBOLD, theme::FONT_MONO] {
|
||
let family = &definitions.families[&FontFamily::Name(name.into())];
|
||
assert!(!family.is_empty(), "{name} has no font");
|
||
}
|
||
let installed: Vec<&str> = INTERFACE_FACES.into_iter().filter(|face| definitions.font_data.contains_key(*face)).collect();
|
||
let family = |family: FontFamily| definitions.families[&family].clone();
|
||
let named = |name: &str| family(FontFamily::Name(name.into()));
|
||
if cfg!(feature = "interface-fonts") {
|
||
assert_eq!(installed, INTERFACE_FACES);
|
||
assert_eq!(named(theme::FONT_UI)[0], "Inter");
|
||
assert_eq!(named(theme::FONT_UI_SEMIBOLD)[0], "Inter-SemiBold");
|
||
assert_eq!(named(theme::FONT_MONO)[0], "JetBrainsMono");
|
||
assert_eq!(family(FontFamily::Proportional)[0], "Inter");
|
||
} else {
|
||
assert!(installed.is_empty(), "{installed:?}");
|
||
let egui = egui::FontDefinitions::default();
|
||
let proportional = &egui.families[&FontFamily::Proportional];
|
||
assert!(named(theme::FONT_UI).starts_with(proportional));
|
||
assert!(named(theme::FONT_UI_SEMIBOLD).starts_with(proportional));
|
||
assert!(named(theme::FONT_MONO).starts_with(&egui.families[&FontFamily::Monospace]));
|
||
assert!(family(FontFamily::Proportional).starts_with(proportional));
|
||
}
|
||
}
|
||
|
||
/// An effect dialog, whose titles are semibold, draws without panicking on a context only
|
||
/// `install_fonts` prepared.
|
||
#[test]
|
||
fn an_effect_dialog_draws_on_a_context_the_app_never_ran_on() {
|
||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||
app.run("file.new", json!({"width": 200, "height": 200})).unwrap();
|
||
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 50})).unwrap();
|
||
app.run("select.all", json!({})).unwrap();
|
||
app.run("effect.dialog", json!({"effect": "blur.gaussian"})).unwrap();
|
||
assert!(app.ui.dialog.is_some());
|
||
let ctx = context();
|
||
frame(&ctx, |ui| dialogs::show(&mut app, ui.ctx()));
|
||
}
|