Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/MUI.Web/Components/AnsiQuote.razor
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
break;

default:
<figure class="ansi">
<figure class="@(DoubleWidth ? "ansi wide" : "ansi")">
@* role="img" prunes the art from the a11y tree like aria-hidden would, but unlike
aria-hidden it leaves the region focusable — needed because it scrolls and a
keyboard is how some readers scroll it. *@
Expand Down
25 changes: 20 additions & 5 deletions src/MUI.Web/wwwroot/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,14 @@
--glow: 0 0 8px color-mix(in srgb, var(--accent) 55%, transparent);

--sans: "Hanken Grotesk", ui-sans-serif, system-ui, -apple-system, sans-serif;
--mono: "Cascadia Mono", ui-monospace, "SFMono-Regular", Consolas, monospace;
/*
Menlo, DejaVu Sans Mono and Liberation Mono are here for the block elements, not the letters.
`ui-monospace` and "SFMono-Regular" resolve only in Safari, so a Mac reader in Chrome or Firefox
without Cascadia or Consolas fell to the generic `monospace` (Courier), which has no █ or ▄,
and each block came from whatever face the browser found next.
*/
--mono: "Cascadia Mono", ui-monospace, "SFMono-Regular", Menlo, Consolas, "DejaVu Sans Mono",
"Liberation Mono", monospace;
--kick: "JetBrains Mono", var(--mono);

/*
Expand All @@ -101,8 +108,15 @@
screen from a Chinese, Japanese or Korean game is column art built on the assumption that a Han
glyph is exactly twice the advance of an ASCII one, which holds for Noto Sans Mono CJK, Sarasa
Mono and MS Gothic and does NOT hold for PingFang, Microsoft YaHei or Hiragino Sans — those are
proportional, and falling into one turns pkuxkx's bordered banner into a ragged staircase. The
Latin faces stay in front so an ASCII screen is unchanged.
proportional, and falling into one turns pkuxkx's bordered banner into a ragged staircase.

Only a screen that carries a double-width glyph is drawn in it (`figure.ansi.wide`). These
faces draw the East Asian Ambiguous characters — █ ▄ ▀ ░ and the box-drawing set — two cells
wide, which is what a GBK terminal does and what a CJK banner is drawn for, and exactly wrong
for an ASCII banner built out of blocks. A reader with none of the Latin faces in front (most
Linux desktops, which ship Noto Sans Mono CJK; a Mac with Office's MS Gothic) got every block
at twice the width of the spaces between them. Measured in Chromium against Noto Sans Mono
CJK SC at 12px: space 6px, █ 12px.

No webfont, deliberately. Noto Sans Mono CJK SC is some eight megabytes a weight, and
subsetting is per-screen — a different glyph set for every game, regenerated on every crawl.
Expand Down Expand Up @@ -1650,9 +1664,10 @@ figure.ansi .quote {
figure.ansi .quote:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
figure.ansi .quote pre {
margin: 0; padding: 14px 16px;
font: var(--fs-sm)/1.4 var(--mono-cjk); font-variant-ligatures: none;
font: var(--fs-sm)/1.4 var(--mono); font-variant-ligatures: none;
color: #aaaaaa; white-space: pre;
}
figure.ansi.wide pre { font-family: var(--mono-cjk); }
figure.ansi figcaption { display: flex; gap: 8px; flex-wrap: wrap; }

/*
Expand All @@ -1676,7 +1691,7 @@ figure.ansi figcaption { display: flex; gap: 8px; flex-wrap: wrap; }
.screen-text > pre { margin-top: 8px; }
.screen-text pre {
margin: 0;
font: var(--fs-sm)/1.7 var(--mono-cjk); font-variant-ligatures: none;
font: var(--fs-sm)/1.7 var(--mono); font-variant-ligatures: none;
color: var(--text);
white-space: pre-wrap;
overflow-wrap: anywhere;
Expand Down
21 changes: 21 additions & 0 deletions tests/MUI.Web.Tests/Text/AnsiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,27 @@ public async Task TheCaptionStatesTheWidestRowInCellsRatherThanAHalvedGrid()
await Assert.That(html).DoesNotContain($"{Ansi.DefaultColumns / 2}×");
}

/// <summary>
/// Only a screen with a double-width glyph is drawn in the CJK faces.
/// </summary>
/// <remarks>Those faces draw █ and ▄ two cells wide, so an ASCII banner built from blocks came out
/// with every block twice the width of the spaces beside it (mygame-5).</remarks>
[Test]
public async Task OnlyAScreenWithWideGlyphsIsMarkedForTheCjkFaces()
{
var blocks = await Render.ComponentAsync<AnsiQuote>(new()
{
["Screen"] = Ansi.Parse(" ██ ██\n ▄▄▄▄▄▄\nconnect <name>", suppressedByOwner: false),
});
var han = await Render.ComponentAsync<AnsiQuote>(new()
{
["Screen"] = Ansi.Parse("漢字\n漢字\n漢字", suppressedByOwner: false),
});

await Assert.That(blocks).Contains("<figure class=\"ansi\">");
await Assert.That(han).Contains("<figure class=\"ansi wide\">");
}

[Test]
public async Task TheTextAlternativeCarriesNoColourCodes()
{
Expand Down
Loading