Commit Graph

5 Commits

Author SHA1 Message Date
funman300 17f9b518f1 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>
2026-05-02 18:33:54 +00:00
funman300 fdb6c2ecfe fix(engine): bundle FiraMono into SVG fontdb as last-resort fallback
The hayeah card SVGs reference Bitstream Vera Sans and Arial by name.
The lenient FontResolver from efa063f appends Family::SansSerif and
Family::Serif so unmatched named families fall through to whatever
the system serves under those CSS generics — which works on machines
with a normal fontconfig setup, and silently fails on minimal Linux
installs, fresh Wayland sessions, or chroots where the generic
aliases don't resolve to anything either. The visible symptom on the
player's second machine was "card font didn't carry over": rank and
suit glyphs vanished from the cards because every lookup path hit a
None.

shared_fontdb now also include_bytes!()s the bundled
assets/fonts/main.ttf into the fontdb after load_system_fonts, and
pins each CSS generic (sans-serif, serif, monospace, cursive,
fantasy) to "Fira Mono". Named-family lookups still prefer the
system db first when those families exist, so machines with a normal
font setup behave identically; only when SansSerif/Serif fall through
does the resolver land on FiraMono — guaranteed present because it's
embedded in the binary.

The bundled font is ~170 KB; the binary already include_bytes!()s the
six audio WAVs and the embedded card-theme SVGs, so this fits the
existing self-contained-binary policy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 21:41:35 +00:00
funman300 efa063fb8f fix(engine): fall through to system default font on unmatched family
CI / Test & Lint (push) Failing after 5s
CI / Release Build (push) Has been skipped
Replaces the previous LogPlugin-filter approach (which suppresses the
warn message) with a fix at the source: a custom usvg FontResolver
that appends `sans-serif` and `serif` to every family-lookup query.

usvg's default selector queries fontdb with [SVG-requested families,
Serif] and emits `log::warn!("No match for '{family}'")` when the
query returns None. On systems without the SVG's named family (Arial
on Linux, etc.), every text node logs a warn even though the system
has perfectly good fonts available — the warn is a false negative
because fontdb's named-family lookup is exact-match only.

The new resolver appends both `Family::SansSerif` and `Family::Serif`
to the query, both resolved by fontdb (via fontconfig on Linux or
built-in defaults elsewhere) to whatever the system has installed.
The query now finds *some* face on any reasonably configured machine,
so `id.is_none()` is never true and the warn branch never fires. The
visible behaviour: SVGs that request unavailable named families now
silently use the system's default sans-serif font.

Reverts the LogPlugin filter from main.rs — silencing warns at the
log level was the wrong layer; fixing the lookup is.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 18:41:02 +00:00
funman300 78cf30e906 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>
2026-05-01 18:22:32 +00:00
funman300 b8fb3fbd6e feat(engine): SVG → Image asset loader (Card theme phase 1)
Implements the runtime SVG rasterisation pipeline that the card-theme
system (CARD_PLAN.md) is built on. Bevy 0.18 has no native SVG support;
this loader bridges usvg (parser) + resvg (renderer) + tiny-skia (CPU
pixmap) so the rest of the engine consumes themes as plain
Handle<Image>. Rasterisation happens once per (asset, settings) pair at
load time — Bevy's asset cache absorbs the cost.

solitaire_engine/src/assets/
  mod.rs           — module entrypoint
  svg_loader.rs    — SvgLoader (AssetLoader for .svg → Image)
                     SvgLoaderSettings { target_size: UVec2 } default 512×768
                     SvgLoaderError (Io / Parse / PixmapAlloc) via thiserror
                     rasterize_svg() helper exposed for non-asset-graph
                     callers (the future zip-importer validation step)

The rasteriser scales-to-fit while preserving aspect ratio, centring
the SVG inside the target box so a non-2:3 source doesn't pin to the
top-left corner.

7 new unit tests — default + custom target size, zero-dimension reject,
malformed-input reject, RGBA byte-count, extension advertisement, and
a compile-time guard that SvgLoaderSettings still satisfies the
AssetLoader::Settings trait bounds.

Workspace deps added: usvg 0.47, resvg 0.47, tiny-skia 0.12 (latest
minor versions; CARD_PLAN.md called out the placeholder numbers
needed verification).

cargo build / cargo clippy --workspace --all-targets -- -D warnings
/ cargo test --workspace all green (913 passed, 0 failed, 9 ignored —
+7 from the new loader tests).
2026-05-01 05:05:30 +00:00