Overlay generator (pure, tested) — RioJoy.Core/Overlay:
- FontFitter ports the legacy calc-fontsize auto-fit search (validated against a
brute-force oracle); OverlayLayoutEngine ports create-data-layer's fit + h/v
justification and adds per-region 90 deg CCW rotation; OverlayTemplate/Region +
OverlayTemplateStore hold cell geometry/colour/rotation (regions.json).
- GoobieDataImporter parses the legacy .data label sheet into label rows.
- src/RioJoy.Overlay: SkiaSharp rasterizer (SkiaTextMeasurer shared with the engine
so measured layout == drawn output; SkiaOverlayRenderer -> PNG;
ProfileWallpaperGenerator). Verified end-to-end on the real cockpit art
(regions.json + riojoy.png + TEST.data) by OverlayRenderIntegrationTests.
- RioJoy.Tray/WallpaperApplier applies via SystemParametersInfo; RioCoordinator
generates+applies on profile activation (opt-in via AppConfig.OverlayTemplatePath).
Region geometry — tools/XcfRegionExtract parses riojoy.xcf (a GIMP-format binary
reader, no GIMP needed) into the 119-cell regions.json: per-layer offsets/size/
font/colour, with 90 deg CCW rotation on a-00 + b-10..b-1F. Base image riojoy.png.
NOTE: the wallpaper positions are a VGA chroma-split display artifact (6 displays),
not the cockpit's logical layout.
Mapping editor (first cut) — RioJoy.Tray/Editor/ProfileEditorForm renders the
config sheet's logical grid (SheetLayout parses the sheet CSV export). The iRIO
word is edited via ButtonBinding <-> RioMapEntry.Create (no hex bit-twiddling), with
a context-sensitive picker: keyboard keys by name (KeyCatalog VK names), joystick
Button N, hat direction, mouse/RIO-command enum names; modifiers only for keyboard.
Opened from the tray ("Edit profile..."). The sheet-grid layout is a starting point
that needs rework from a better-formatted source.
~220 xUnit tests green. Docs (PLAN.md, README) updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
76 lines
2.5 KiB
C#
76 lines
2.5 KiB
C#
using RioJoy.Core.Overlay;
|
|
using Xunit;
|
|
|
|
namespace RioJoy.Core.Tests.Overlay;
|
|
|
|
public class FontFitterTests
|
|
{
|
|
// Brute-force oracle: the largest integer size whose text fits, mirroring the
|
|
// routine's truncate-to-integer behavior and MinFontSize floor.
|
|
private static double BruteForceBestFit(
|
|
string text, ITextMeasurer m, double width, double height, double cap = 1000)
|
|
{
|
|
double best = FontFitter.MinFontSize;
|
|
for (double s = FontFitter.MinFontSize; s <= cap; s++)
|
|
{
|
|
TextExtent e = m.Measure(text, "f", s);
|
|
if (e.Width <= width && e.Height <= height)
|
|
best = s;
|
|
}
|
|
return best;
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("FIRE", 100, 20)]
|
|
[InlineData("GEAR UP", 120, 24)]
|
|
[InlineData("X", 50, 50)]
|
|
[InlineData("LONGER LABEL TEXT", 80, 16)]
|
|
[InlineData("WW", 10, 40)] // width-bound, tiny cell
|
|
public void BestFitSize_MatchesBruteForceOracle(string text, double w, double h)
|
|
{
|
|
var m = new LinearTextMeasurer();
|
|
|
|
double fit = FontFitter.BestFitSize(text, "f", w, h, m);
|
|
double oracle = BruteForceBestFit(text, m, w, h);
|
|
|
|
Assert.Equal(oracle, fit);
|
|
}
|
|
|
|
[Fact]
|
|
public void BestFitSize_ResultActuallyFits()
|
|
{
|
|
var m = new LinearTextMeasurer();
|
|
double size = FontFitter.BestFitSize("THROTTLE", "f", 90, 18, m);
|
|
|
|
TextExtent e = m.Measure("THROTTLE", "f", size);
|
|
Assert.True(e.Width <= 90 && e.Height <= 18, $"size {size} -> {e.Width}x{e.Height} should fit 90x18");
|
|
}
|
|
|
|
[Fact]
|
|
public void BestFitSize_NeverBelowMinimum()
|
|
{
|
|
var m = new LinearTextMeasurer();
|
|
// A cell far too small for any text still yields the floor, not 0/negative.
|
|
double size = FontFitter.BestFitSize("IMPOSSIBLE", "f", 1, 1, m);
|
|
Assert.Equal(FontFitter.MinFontSize, size);
|
|
}
|
|
|
|
[Fact]
|
|
public void BestFitSize_EmptyText_Terminates()
|
|
{
|
|
// Empty text has zero width; the loop must still converge (via repeated
|
|
// extents) rather than grow forever.
|
|
double size = FontFitter.BestFitSize("", "f", 100, 100, new LinearTextMeasurer());
|
|
Assert.True(size >= FontFitter.MinFontSize);
|
|
}
|
|
|
|
[Fact]
|
|
public void BestFitSize_LargerCell_AllowsLargerFont()
|
|
{
|
|
var m = new LinearTextMeasurer();
|
|
double small = FontFitter.BestFitSize("ABC", "f", 60, 12, m);
|
|
double big = FontFitter.BestFitSize("ABC", "f", 600, 120, m);
|
|
Assert.True(big > small);
|
|
}
|
|
}
|