Redraw every icon on the pixel grid it renders at

On a 1x screen TDesign's glyphs smeared at 14 and 16px: their 24-unit
strokes land between pixels, so every ~1px line covered two at half ink.
All 37 icon/size pairs are now TDesign's shapes redrawn on their own grid
(viewBox 0 0 N N, so one unit is one CSS pixel):

- Straight lines are runs of whole pixels that end on pixel edges, with
  no caps or joins. WebKit drops square caps on a path of several
  subpaths, and Skia strokes 1px lines as hairlines that leave a joined
  corner pixel three-quarters inked.
- Slants and curves are 1.25px so they read as heavy as the crisp runs.
  Their ends, vertices and circle extremes sit on pixel centres.
- server24 keeps TDesign's 24px geometry; only its dots move onto whole
  pixels.

Only closeCircle24 (TDesign's own 24px grid) and the spinner still come
from @ng-icons/tdesign-icons. This also fixes add14 from 5d23809, whose
square-capped ends rendered half-inked in WebKit. icons.spec.ts now also
checks that every grid name's glyph uses that grid's viewBox.

Checked at 1x and 2x in WebKit (the app's macOS engine) and in Chrome
(Skia, as in WebView2 and WebKitGTK); straight runs fill whole pixels in
both. In WebKit at 1x, a copy of the tab bar, toolbar, file rows, menu
and shortcut-row markup rendered every straight-line glyph with zero
blur, including icons laid out on fractional pixels.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-10-09 13:49:42 +02:00
co-authored by Claude
parent 5d23809d48
commit 5be41db032
6 changed files with 123 additions and 90 deletions
+6 -3
View File
@@ -376,8 +376,10 @@ component; tokens → `ui/src/styles.css`.
A name is `<icon><px>`: the pixel grid its glyph is drawn for, which must match
the size token where it renders (`server14` at `--icon-size-sm`, `server16` at
`-md`, `server24` at `-xl`). The one exception is `tdesignLoading`, which spins.
`pixel-icons.ts` holds the glyphs redrawn on their grids, and names not yet
redrawn alias TDesign's (`@ng-icons/tdesign-icons`). Templates can't type-check
Every glyph is TDesign's shape redrawn on its pixel grid in `pixel-icons.ts`,
as whole-pixel runs plus 1.25px slants (rules in gotchas). Only `closeCircle24`
(TDesign's own 24px grid) and the spinner still come from
`@ng-icons/tdesign-icons`. Templates can't type-check
icon names, and an unregistered one renders nothing (a console warning only), so
`icons.spec.ts` checks every `<ng-icon>` in the skins. TS code that picks an icon
takes the `Icon14` / `Icon16` types.
@@ -517,7 +519,8 @@ TDesign's rule (an icon is 2px larger than its text, in whole pixels) through
four rem tokens, and every `<ng-icon>` takes one: `--icon-size-sm` 14px (beside
11–12px text, small icon buttons), `-md` 16px (13–14px text: file rows, window
titles; the path-bar buttons), `-lg` 20px and `-xl` 24px (lone spinners and
status marks). **Interface scale** (`settings.uiScale`,
status marks). The glyphs are drawn on the sm, md and xl grids (`icons.ts`).
**Interface scale** (`settings.uiScale`,
0.5–4 in 0.1 steps): one root font-size lever — `applyUiScale` sets
`html { font-size: 16px * scale }`, and since EVERY size in the app is rem
(panel tokens like `--row-h`, all Tailwind utilities, ng-icon sizes), the