From 564b25957de45ec9e45169d0398199ccc59ff570 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 15 Jun 2026 01:10:20 +0100 Subject: [PATCH] Add Gjallar bitmap family verifier --- docs/architecture.md | 24 +++++--- src/Gjallar/Program.cs | 13 ++++- tools/verify_bitmap_family.ps1 | 102 +++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 tools/verify_bitmap_family.ps1 diff --git a/docs/architecture.md b/docs/architecture.md index bdd6e22..6c958a9 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -87,11 +87,21 @@ and Ubuntu Sans; those are not substitutes for Gjallar's raster cell fonts. For any scale Gjallar claims as Japanese-capable, the font must provide Latin, hiragana, and katakana in the same fixed cell grid. The target ladder is: -- display: roughly 16px or larger; -- body: roughly 12px; -- small: roughly 10px. +- display: 16x16; +- body: 14x14; +- small: 12x12. -`PsfFont` reads Unicode-mapped PSF fonts and `FontAtlas` prefers -kana-capable faces when hiragana or katakana appears in panel titles or body text. The -asset line is still explicit: package or generate approved Unicode raster -atlases for the 16/12/10 ladder before claiming full Japanese pixel parity. +`PsfFont` reads Unicode-mapped PSF fonts and `FontAtlas` selects from the +packaged Shinonome bitmap family. Default startup now treats that family as a +hard contract: if any required raster is missing, mis-sized, or missing kana, +Gjallar fails loudly instead of falling back to random system console fonts. + +The repo-native verification path is: + +- `tools/verify_bitmap_family.ps1` + +That verifier builds Gjallar, runs specimen mode against a temp framebuffer, and +proves the shipped 12/14/16 family loads with zero missing glyphs for a mixed +Latin/hiragana/katakana specimen string including `メタめ`. The verifier uses +`--specimen-text-file` so UTF-8 specimen content does not depend on shell +argument encoding quirks. diff --git a/src/Gjallar/Program.cs b/src/Gjallar/Program.cs index 567aa96..7bcc59a 100644 --- a/src/Gjallar/Program.cs +++ b/src/Gjallar/Program.cs @@ -40,7 +40,7 @@ internal sealed record GjallarConfig( IntArg(args, "--height", 0), StringArg(args, "--font", ""), StringArg(args, "--mouse", "/dev/input/mice"), - StringArg(args, "--specimen-text", "")); + ResolveSpecimenText(args)); private static string StringArg(IReadOnlyList args, string name, string fallback) { @@ -57,6 +57,17 @@ internal sealed record GjallarConfig( private static int IntArg(IReadOnlyList args, string name, int fallback) => int.TryParse(StringArg(args, name, ""), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) ? value : fallback; + + private static string ResolveSpecimenText(IReadOnlyList args) + { + var file = StringArg(args, "--specimen-text-file", ""); + if (!string.IsNullOrWhiteSpace(file)) + { + return File.ReadAllText(file, Encoding.UTF8); + } + + return StringArg(args, "--specimen-text", ""); + } } internal sealed class GjallarRenderer : IDisposable diff --git a/tools/verify_bitmap_family.ps1 b/tools/verify_bitmap_family.ps1 new file mode 100644 index 0000000..9714321 --- /dev/null +++ b/tools/verify_bitmap_family.ps1 @@ -0,0 +1,102 @@ +param( + [string] $GjallarRoot = "E:\Projects\Gjallar", + [string] $SpecimenText = "" +) + +$ErrorActionPreference = "Stop" + +if ([string]::IsNullOrWhiteSpace($SpecimenText)) { + $SpecimenText = "VOID " + + [string]([char]0x30E1) + [string]([char]0x30BF) + [string]([char]0x3081) + " " + + [string]([char]0x3042) + [string]([char]0x30A2) + " " + + [string]([char]0x304B) + [string]([char]0x306A) + " " + + [string]([char]0x30AB) + [string]([char]0x30CA) +} + +$projectPath = Join-Path $GjallarRoot "src\Gjallar\Gjallar.csproj" +$buildDir = Join-Path $GjallarRoot "src\Gjallar\bin\Debug\net10.0" +$exePath = Join-Path $buildDir "Gjallar.exe" +$scratchDir = Join-Path $GjallarRoot "scratch" +$framebufferPath = Join-Path $scratchDir "verify-bitmap-family.fb" +$statusPath = Join-Path $scratchDir "verify-bitmap-family.status.json" +$frameDumpPath = Join-Path $scratchDir "verify-bitmap-family.ppm" +$specimenPath = Join-Path $scratchDir "verify-bitmap-family-specimen.txt" + +dotnet build $projectPath | Out-Host + +if (!(Test-Path $exePath)) { + throw "Gjallar executable not found at $exePath" +} + +New-Item -ItemType Directory -Force -Path $scratchDir | Out-Null +[System.IO.File]::WriteAllText($specimenPath, $SpecimenText, [System.Text.Encoding]::UTF8) + +$bufferBytes = 1920 * 1080 * 4 +$file = [System.IO.File]::Open($framebufferPath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite) +try { + $file.SetLength($bufferBytes) +} +finally { + $file.Dispose() +} + +& $exePath ` + --fb $framebufferPath ` + --width 1920 ` + --height 1080 ` + --frames 1 ` + --mouse "" ` + --stats-path $statusPath ` + --frame-dump-path $frameDumpPath ` + --specimen-text-file $specimenPath | Out-Host + +if (!(Test-Path $statusPath)) { + throw "Gjallar did not write status to $statusPath" +} + +$status = Get-Content $statusPath -Raw | ConvertFrom-Json + +if ($status.status -ne "specimen-rendered") { + throw "Expected specimen-rendered status, got '$($status.status)'" +} + +$expectedFamily = @( + @{ width = 12; height = 12 } + @{ width = 14; height = 14 } + @{ width = 16; height = 16 } +) + +$actualFamily = @($status.fonts.loaded | ForEach-Object { + [pscustomobject]@{ + width = [int]$_.width + height = [int]$_.height + kana = [bool]$_.kana + } +}) + +if ($actualFamily.Count -ne $expectedFamily.Count) { + throw "Expected $($expectedFamily.Count) loaded packaged fonts, got $($actualFamily.Count)" +} + +for ($i = 0; $i -lt $expectedFamily.Count; $i++) { + $expected = $expectedFamily[$i] + $actual = $actualFamily[$i] + if ($actual.width -ne $expected.width -or $actual.height -ne $expected.height) { + throw ("Loaded font mismatch at index {0}: expected {1}x{2}, got {3}x{4}" -f $i, $expected.width, $expected.height, $actual.width, $actual.height) + } + if (-not $actual.kana) { + throw "Loaded font $($actual.width)x$($actual.height) is not kana-capable" + } +} + +foreach ($entry in @($status.fonts.specimenSupport)) { + if ($entry.missing.Count -ne 0) { + throw "Font $($entry.width)x$($entry.height) is missing specimen glyphs: $($entry.missing -join ', ')" + } +} + +Write-Host "Gjallar bitmap family verification passed." +Write-Host " specimenText=$SpecimenText" +Write-Host " loaded=$((@($actualFamily) | ForEach-Object { ""$($_.width)x$($_.height)"" }) -join ', ')" +Write-Host " statusPath=$statusPath" +Write-Host " frameDumpPath=$frameDumpPath"