Skip to content

feat: ctxDrawSymbol and symbolSize, SF Symbols drawn in a surface in the fill colour - #71

Merged
sidorares merged 1 commit into
mainfrom
feat/symbol-image
Sep 17, 2026
Merged

sidorares merged 1 commit into
mainfrom
feat/symbol-image

Conversation

@sidorares

@sidorares sidorares commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

For sidorares/react-x11#591: the system's icons by name, drawn into a surface for a renderer's own content. createStatusItem's image and a menu item's iconName already take an SF Symbol name; nothing that draws inside a window did, so an app whose tray icon is waveform could not show waveform in its own UI.

appkit-symbols-591

Rendered by this branch: the eight symbols the Hush port uses, at weights 300, 500 and 800 in three fill colours, and speaker.wave.3.fill at variableValue 0, 0.34, 0.67 and 1.

The verbs

  • native.ctxDrawSymbol(surface, name, x, y, width, height, options?) draws the symbol fitted into the rect, centred and keeping its proportions, in the current fill colour, and answers true; for a name the catalogue does not know it answers false and draws nothing. A symbol is a template, its shape in whatever colour it is drawn with, so it is drawn the way ctxDrawGlyphs draws a run: the template goes into a transparency layer and the fill colour through it with SourceIn, and the layer composites once through the surface's CTM and clip, at its global alpha and blend mode, over what is already drawn.
  • native.symbolSize(name, options?) answers { width, height } in points, the symbol's designed padding included, or null for an unknown name.

Both take pointSize, default 13; weight, 100 to 900, mapped to AppKit's weights the way matchFont maps them, a mapping now shared as FontWeightFromCss; scale, 'small', 'medium' or 'large'; and variableValue, 0 to 1, on macOS 13 and later. A name that is not a string is a TypeError.

In threaded mode both run on the calling thread, like every other ctx* verb and the font verbs: an NSImage drawn into a bitmap context is not a view, so it does not need the UI thread that bezels do. The test checks that a worker draws the same pixels as the main thread.

Tests

test/symbols.js, added to the test script and to CI:

  • symbolSize: a size that grows with the point size and the weight; null for an unknown name; the options optional
  • ctxDrawSymbol: opaque ink in the fill colour, inside its rect and centred in it; over a white background, the rest of the rect left white; false and no ink for an unknown name; the colour following the fill colour; the global alpha applied once; the clip; more ink at a heavier weight; less of a variable symbol at a lower value; a TypeError for a name that is not a string
  • the same pixels from a worker as on the main thread

Mutation check, each change rebuilt and run alone, all 7 caught:

mutant caught by
no SourceIn fill, so the template's black the fill colour checks
not centred in the rect the centring check
the weight ignored the size and ink weight checks
variableValue ignored the variable symbol check
no transparency layer, the fill flooding the rect the background check
no TypeError for a non-string name the TypeError check
true for an unknown name the unknown name check

CI also gains a step for test/text-features.js, which #70 added to the test script but not to the workflow.

npm test on this Mac: every test file passes.

…the fill colour

The system's icons by name, which createStatusItem's image and a menu
item's iconName already take, can now be drawn into a surface for a
renderer's own content, sidorares/react-x11#591.

ctxDrawSymbol draws the symbol fitted into a rect, centred, in the
current fill colour. The template's shape goes into a transparency layer
and the colour through it, so a symbol follows the CTM, the clip, the
global alpha and the blend mode as a glyph run does, over what is
already drawn. It answers false, drawing nothing, for a name the
catalogue does not know. symbolSize answers the symbol's size in points,
or null. Both take pointSize, weight, scale and variableValue, and run
on the calling thread in threaded mode, where they draw the same pixels.

The CSS weight mapping matchFont used is shared as FontWeightFromCss.
CI now runs test/text-features.js, which #70 added to the test script
only, and the new test/symbols.js.
@sidorares
sidorares merged commit 6ee673c into main Sep 17, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant