fix(engine): silence usvg font-substitution warn spam
CI / Test & Lint (push) Failing after 6s
CI / Release Build (push) Has been skipped

The bundled hayeah card SVGs declare font-family="Arial" for rank/suit
text. usvg matches family names exactly, so on systems without Arial
installed (every Linux distro by default) every text node bridged a
log::warn! into our tracing output — 50+ lines per launch.

Two-part fix:
- svg_loader now populates a process-wide fontdb with system fonts
  (lazy via OnceLock) so substitution actually has faces to fall
  through to. usvg::Options::default() ships an empty fontdb, which
  meant text glyphs had nothing to fall back on at all.
- LogPlugin extends DEFAULT_FILTER with usvg::text=error so the
  residual "no match" warns drop. The substitution itself works; the
  message is purely informational because Arial truly isn't installed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-01 18:22:32 +00:00
parent 9a9026e33a
commit 78cf30e906
2 changed files with 41 additions and 1 deletions
+13
View File
@@ -100,6 +100,19 @@ fn main() {
.set(bevy::asset::AssetPlugin {
file_path: "../assets".to_string(),
..default()
})
// The bundled hayeah card SVGs declare `font-family="Arial"`
// for rank/suit text. usvg compares family names exactly,
// so on systems without Arial installed (every Linux
// distro by default) it bridges a `log::warn!` per text
// node into our tracing output — 50+ lines per game on
// launch. The substitution path in `svg_loader::shared_fontdb`
// already resolves the glyphs to whatever sans-serif the
// user does have; the warn is purely informational and
// dropping it leaves real errors visible.
.set(bevy::log::LogPlugin {
filter: format!("{},usvg::text=error", bevy::log::DEFAULT_FILTER),
..default()
}),
)
.add_plugins(AssetSourcesPlugin)
+28 -1
View File
@@ -19,6 +19,8 @@
//! loading via `load_with_settings(...)`. The default of 512×768 is a
//! safe fallback that fits a typical 2:3 playing card.
use std::sync::{Arc, OnceLock};
use bevy::asset::io::Reader;
use bevy::asset::{AssetLoader, LoadContext, RenderAssetUsages};
use bevy::image::Image;
@@ -27,6 +29,7 @@ use bevy::reflect::TypePath;
use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use usvg::fontdb;
/// Per-asset settings consumed by [`SvgLoader::load`].
///
@@ -102,7 +105,10 @@ impl AssetLoader for SvgLoader {
/// thumbnail generators) can rasterise without going through the
/// asset graph.
pub fn rasterize_svg(svg_bytes: &[u8], target: UVec2) -> Result<Image, SvgLoaderError> {
let opt = usvg::Options::default();
let opt = usvg::Options {
fontdb: shared_fontdb(),
..Default::default()
};
let tree = usvg::Tree::from_data(svg_bytes, &opt)?;
let svg_size = tree.size();
@@ -140,6 +146,27 @@ pub fn rasterize_svg(svg_bytes: &[u8], target: UVec2) -> Result<Image, SvgLoader
))
}
/// Returns a process-wide font database populated with the OS-installed
/// fonts the user has available. Initialised lazily on first SVG that
/// references text, then shared (via `Arc`) across every subsequent
/// rasterisation. `usvg::Options::default()` ships an empty `fontdb`,
/// so without this call any text glyph in an SVG renders with no font
/// match — the visible symptom on the bundled hayeah artwork is the
/// "No match for Arial font-family" warn spam plus glyphs that fall
/// through to whatever shape-only path usvg uses for missing fonts.
/// `load_system_fonts` is comparatively expensive (~50200 ms on a
/// typical desktop) so we only pay it once for the lifetime of the
/// process, gated by `OnceLock`.
fn shared_fontdb() -> Arc<fontdb::Database> {
static DB: OnceLock<Arc<fontdb::Database>> = OnceLock::new();
DB.get_or_init(|| {
let mut db = fontdb::Database::new();
db.load_system_fonts();
Arc::new(db)
})
.clone()
}
#[cfg(test)]
mod tests {
use super::*;