fix(assets,theme): remove assert in svg_loader, log theme failures, fix default theme id (#58, #63, #64)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
funman300
2026-05-27 19:36:05 -07:00
parent 35fde160fa
commit 0437c36463
3 changed files with 38 additions and 14 deletions
+11 -8
View File
@@ -24,6 +24,7 @@ use std::sync::{Arc, OnceLock};
use bevy::asset::io::Reader;
use bevy::asset::{AssetLoader, LoadContext, RenderAssetUsages};
use bevy::image::Image;
use bevy::log::warn;
use bevy::math::UVec2;
use bevy::reflect::TypePath;
use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat};
@@ -156,7 +157,7 @@ pub fn rasterize_svg(svg_bytes: &[u8], target: UVec2) -> Result<Image, SvgLoader
/// share the same canonical face.
const BUNDLED_FONT_BYTES: &[u8] = include_bytes!("../../../assets/fonts/main.ttf");
/// Returns a process-wide font database holding only the bundled
/// Returns a process-wide font database that tries to load the bundled
/// FiraMono-Medium face. Initialised lazily on first SVG that references
/// text, then shared (via `Arc`) across every subsequent rasterisation.
///
@@ -165,17 +166,19 @@ const BUNDLED_FONT_BYTES: &[u8] = include_bytes!("../../../assets/fonts/main.ttf
/// such request directly to FiraMono so rasterisation is deterministic
/// across machines and the system font path is never consulted.
///
/// Aborts the program if the embedded bytes don't parse — bundled at
/// compile time, so a parse failure means the binary is corrupt.
/// If the embedded bytes fail to yield any faces, log a warning and
/// fall back to an empty database so startup can continue.
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_font_data(BUNDLED_FONT_BYTES.to_vec());
assert!(
db.faces().next().is_some(),
"bundled FiraMono failed to parse — binary is corrupt"
);
let loaded_faces = db.load_font_source(fontdb::Source::Binary(Arc::new(
BUNDLED_FONT_BYTES.to_vec(),
)));
if loaded_faces.is_empty() {
let e = "no faces loaded from bundled bytes";
warn!("Failed to load bundled FiraMono font: {e}");
}
Arc::new(db)
})
.clone()
+26 -5
View File
@@ -21,11 +21,12 @@
use std::path::Path;
use bevy::log::warn;
use bevy::prelude::{App, Plugin, Resource, Startup};
use serde::Deserialize;
use super::ThemeMeta;
use crate::assets::{user_theme_dir, DARK_THEME_MANIFEST_URL};
use crate::assets::{DARK_THEME_MANIFEST_URL, user_theme_dir};
/// One entry in the [`ThemeRegistry`] — the data the picker UI needs
/// to render a row and load the theme on selection.
@@ -143,7 +144,7 @@ fn classic_entry() -> ThemeEntry {
/// Walks `user_dir`, treating every immediate subdirectory as a
/// candidate theme. A subdirectory contributes one entry if and only
/// if it contains a `theme.ron` whose `meta` block parses cleanly and
/// passes `ThemeMeta::validate`. Failed candidates are silently
/// passes `ThemeMeta::validate`. Failed candidates are warned and
/// skipped — broken themes don't poison discovery.
fn discover_user_themes(user_dir: &Path) -> Vec<ThemeEntry> {
let mut out = Vec::new();
@@ -184,9 +185,29 @@ struct ManifestMetaOnly {
/// [`ThemeEntry`]. Returns `None` for any I/O / parse / validation
/// failure — discovery is best-effort.
fn read_meta_only(manifest_path: &Path) -> Option<ThemeEntry> {
let bytes = std::fs::read(manifest_path).ok()?;
let parsed: ManifestMetaOnly = ron::de::from_bytes(&bytes).ok()?;
parsed.meta.validate().ok()?;
let theme_id = manifest_path
.parent()
.and_then(Path::file_name)
.and_then(|name| name.to_str())
.unwrap_or("<unknown>");
let bytes = match std::fs::read(manifest_path) {
Ok(bytes) => bytes,
Err(e) => {
warn!("Skipping theme '{}': {}", theme_id, e);
return None;
}
};
let parsed: ManifestMetaOnly = match ron::de::from_bytes(&bytes) {
Ok(parsed) => parsed,
Err(e) => {
warn!("Skipping theme '{}': {}", theme_id, e);
return None;
}
};
if let Err(e) = parsed.meta.validate() {
warn!("Skipping theme '{}': {}", theme_id, e);
return None;
}
let id = parsed.meta.id.clone();
let display_name = parsed.meta.name.clone();
let manifest_url = format!("themes://{id}/theme.ron");