fix(engine): bundle fonts only and drop system-font fallback
Code-review feedback: the SVG rasteriser mixed three font-resolution layers (load_system_fonts + bundled FiraMono + lenient resolver appending CSS generics), which made card text rendering depend on which fonts the host machine happened to have. The Bevy UI face loaded separately at runtime via AssetServer. Picking option (a) from the review and applying it consistently: bundle FiraMono via include_bytes!() in BOTH layers, no system fallback anywhere. solitaire_engine/src/font_plugin.rs now embeds main.ttf at compile time and registers it with Assets<Font>. A parse failure aborts with "bundled FiraMono failed to parse — binary is corrupt"; the MinimalPlugins early-return stays as a "this plugin doesn't apply in headless tests" check (consumers query Option<Res<FontResource>> and degrade cleanly), not a production fallback. solitaire_engine/src/assets/svg_loader.rs drops load_system_fonts entirely, drops the lenient_font_resolver, and drops the five set_*_family pins. The new bundled_font_resolver ignores the SVG's font-family request and always returns the single bundled face — the bundled card SVGs reference Arial / Bitstream Vera Sans by name and we deliberately don't ship those, so routing every query to FiraMono keeps rasterisation deterministic. shared_fontdb asserts the embedded bytes parsed. The two layers now embed the same path (assets/fonts/main.ttf) independently, so they can't drift. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,23 @@
|
||||
// Register FontPlugin in solitaire_engine/src/lib.rs before use.
|
||||
|
||||
//! Loads FiraMono-Medium via the Bevy `AssetServer` and exposes it via [`FontResource`].
|
||||
//! Embeds FiraMono-Medium into the binary and exposes it via [`FontResource`].
|
||||
//!
|
||||
//! Bundling rather than runtime-loading guarantees the canonical UI face is
|
||||
//! always available regardless of install or platform. The bytes are
|
||||
//! validated at startup; a parse failure aborts the program with a clear
|
||||
//! error because it means the binary is corrupt.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Holds the project-wide [`Handle<Font>`] loaded at startup.
|
||||
/// FiraMono-Medium bytes embedded at compile time. Single source of truth for
|
||||
/// the project's UI face — `solitaire_engine::assets::svg_loader` embeds the
|
||||
/// same path independently for SVG rasterisation so the two layers can't
|
||||
/// drift.
|
||||
const BUNDLED_FONT_BYTES: &[u8] = include_bytes!("../../assets/fonts/main.ttf");
|
||||
|
||||
/// Holds the project-wide [`Handle<Font>`] registered at startup.
|
||||
#[derive(Resource)]
|
||||
pub struct FontResource(pub Handle<Font>);
|
||||
|
||||
/// Loads FiraMono-Medium at startup and inserts [`FontResource`].
|
||||
/// Registers the bundled FiraMono with [`Assets<Font>`] at startup.
|
||||
pub struct FontPlugin;
|
||||
|
||||
impl Plugin for FontPlugin {
|
||||
@@ -17,11 +26,13 @@ impl Plugin for FontPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_font(asset_server: Option<Res<AssetServer>>, mut commands: Commands) {
|
||||
let Some(asset_server) = asset_server else {
|
||||
// AssetServer absent (e.g. MinimalPlugins in tests) — insert default.
|
||||
commands.insert_resource(FontResource(Handle::default()));
|
||||
return;
|
||||
};
|
||||
commands.insert_resource(FontResource(asset_server.load("fonts/main.ttf")));
|
||||
fn load_font(fonts: Option<ResMut<Assets<Font>>>, mut commands: Commands) {
|
||||
// Headless test fixtures use MinimalPlugins (no AssetPlugin → no
|
||||
// Assets<Font>). FontPlugin in that context is a no-op — consumers
|
||||
// already query `Option<Res<FontResource>>` and degrade cleanly.
|
||||
let Some(mut fonts) = fonts else { return };
|
||||
let font = Font::try_from_bytes(BUNDLED_FONT_BYTES.to_vec())
|
||||
.expect("bundled FiraMono failed to parse — binary is corrupt");
|
||||
let handle = fonts.add(font);
|
||||
commands.insert_resource(FontResource(handle));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user