Vector images (resource/vectors/, the vector piece)
This works like resource/images/ but is resolution-independent: SVG glyphs are staged per
backend into whatever form its toolkit loads natively and surfaced through typed constants and
one piece:
vector(res::vectors::home).tint(color).frame(24.0, 24.0)
res::vectors::… constants are generated by day-build (a VectorName per file, so a typo is a
compile error and presence is guaranteed). Vector and image names share one per-backend resolution
namespace (From<VectorName> for ImageName), so the name-based channels (nav-item icons, tab
icons, toolbar_button(...).image(…)) accept vectors unchanged.
Source forms
- Plain
.svg— the whole document is the glyph (raw Material Symbols downloads work as-is). - SF Symbol template
.svg— the SF Symbols app’s export (and third-party generators of the same shape):#SymbolsholdingWeight-Scalevariant groups. Day reduces it to the canonical Regular variant for non-Apple targets (cut out textually, re-boxed to its measured content). .symbolset/bundle — an Xcode custom-symbol bundle; its inner template SVG routes through the same reduction.
Text must be outlined (<text> is a hard build error; shaping is not compiled in).
Per-backend staging (§18.3)
At build, every glyph gets a raster cache PNG (build/day/vectors/raster/, 256 px), a
staged glyph SVG (build/day/vectors/svg/), and, where the art converts, XAML geometry
(build/day/vectors/xaml/). day pack stages the same caches on the one path that does not
run a build first (iOS packs straight through xcodebuild), so a packed app carries its glyphs
whether or not the tree was built before.
The raster cache is a build input, not a shipping form. What a target carries is
build/day/vectors/fallback/<toolkit>/: on gtk that is every glyph, on qt every glyph the
vector piece may draw (its icon channels read the staged SVG instead), and on a toolkit that
draws vectors throughout it is only the art the vector pipeline could not express, usually
nothing. Bundling the rest would ship a second copy of every glyph and would let a broken
vector path go unnoticed behind a raster that still looks right (two XAML bugs survived their
first review that way). day build reports the split per target
(Vectors xaml: 81/81 glyph(s) vector). What ships on top:
| Backend | Native form |
|---|---|
| android-mdc | VectorDrawable in res/drawable/ (solid fills/strokes, both fill rules, and linear/radial gradients on fill and stroke via aapt:attr, API 24, which is the scaffold’s minSdk; clips/masks/filters, and the gradients VD’s model cannot state, fall back to the raster at xxxhdpi, loudly). A VD gradient carries no transform: linear bands are perpendicular to start→end and a radial is a true circle, so a gradient sheared out of perpendicular, a radial stretched to an ellipse, or an SVG focal point rasterizes rather than drawing subtly wrong art. gradientUnits="objectBoundingBox" is not such a case; usvg resolves it to user space before the emitter sees it |
| linux-gtk, macos-gtk | the staged SVG for the icon channels; gdk-pixbuf loads SVG through librsvg, and from_file_at_size renders the glyph at the row’s icon size rather than downsampling the 256 px cache. The vector piece still draws the raster |
| linux-qt, macos-qt | the staged SVG for the icon channels; Qt’s SVG icon engine (the libqsvg imageformats plugin) renders at the size each icon asks for, so nav rows, tab icons and toolbar images stay sharp at any scale. The vector piece still draws the raster |
| macos-appkit | the staged SVG via DAY_VECTOR_SVG_ROOT (NSImage renders SVG files at display size on macOS 11+); other desktop dev launches use the raster cache via DAY_VECTOR_RASTER_ROOT (probed by resolve_image_file) |
| ios-uikit | the SVG in a DayPieces asset-catalog imageset with preserves-vector-representation (Xcode 12+), so UIImage(named:) renders at display size |
| web-dom | the SVG beside assets/images/; day-dom asks for .svg for the names in the page’s window.__DAY_VECTORS list (via the shim’s vector: env keys) and the browser renders it; the raster stays beside it as the older-host fallback |
| harmony-arkui | the SVG in rawfile day/ (same stem as the raster); day-arkui probes day/<name>.svg via the resource manager and ArkUI’s Image renders it natively; raster names keep the png |
| windows-xaml | XAML geometry via DAY_VECTOR_XAML_ROOT: a Path in a scaling Viewbox in page content, a PathIcon in the nav pane’s icon slot, so the glyph is redrawn at every size rather than scaled from a cache. Emitted by the CLI (day_vector::to_xaml_geometry) as absolute M/L/C/Z only: the grammar is SVG’s, but a command XAML’s parser rejects fails the whole geometry, so quadratics are elevated to cubics (exact) and arcs are flattened upstream by usvg. Same subset as the VectorDrawable emission — gradients/clips/masks/filters stage no geometry and fall back to the raster |
Packed desktop apps carry the same forms without the launch env: a .app ships
Contents/Resources/vectors/{svg,raster} (probed exe-relative: the SVGs by
resolve_vector_svg, the rasters by resolve_image_file); an AppImage/flatpak ships
share/<name>/vectors/{raster,svg} with the launcher exporting the same
DAY_VECTOR_*_ROOT roots a dev launch would; the Windows payload merges the raster cache
into the exe-relative images/, where both the piece resolution and the nav rows’
ms-appx:///images/ loads already look, and ships vectors/xaml beside the exe; geometry
specs are not loadable images, so they cannot merge into images/. The glyph SVGs do not ship
to Windows at all: that backend draws the geometry and nothing there reads an SVG.
Weights
vector(...).weight(VectorWeight::Light | Bold) resolves through __light/__bold suffixed
names; Regular is the glyph’s own name.
Template-form sources (SF template SVGs, .symbolset bundles) contribute true per-weight art
(their Light/Bold variants, with the template’s own fallback ladder for sparse exports) and stage
all three.
A plain SVG has no weight axis, so it stages once: its suffixed names alias back to the base
glyph in every resolver (resolve_image_file, resolve_vector_svg, resolve_vector_xaml,
Android’s drawableByName, and web-dom’s URL builder), so .weight(…) degrades to Regular rather
than to a missing asset, on every backend. Aliasing costs far less than copying: staging the
variants instead cost 3× with no benefit, and in Day-Showcase 38 of 39 sources were byte-identical
across all three names (117 staged glyphs and 708 KB of rasters became 41 and 244 KB).
An exact variant always wins over an alias, so a template’s real __bold art is never shadowed by
its own Regular.
Tint
.tint(color) recolors a monochrome glyph on every backend: template rendering +
contentTintColor on AppKit, alwaysTemplate + tintColor on UIKit, setImageTintList on
Android, pixel recolor on GTK, a QPainter SourceIn fill over the rendered glyph on Qt, a CSS mask
painted with the tint on web (a tinted glyph is a masked element, not an <img>, because the
browser cannot recolor an image’s own pixels), SVG fill color (NODE_IMAGE_FILL_COLOR) on ArkUI,
and a brush on the Path shapes on XAML, composed over the geometry when the glyph is realized.
One staged glyph therefore serves every tint at every size without a second asset.
None (and every raster image(…)) means “as authored”.
A tint can follow a signal. .tint(…) takes a plain color or anything reactive; a change
repaints the realized view through ImagePatch::Tint rather than rebuilding it, so a glyph that
recolors with the selection or the theme keeps its native view (and its layout, and any animation
in flight). Implemented on AppKit, UIKit, Android, GTK, Qt and web; XAML and ArkUI still take the
tint at realize only, so a reactive tint there lands on the next rebuild.
A tint follows the art it was authored with: the color fills where the glyph filled and strokes
where it stroked, so an outline glyph stays an outline rather than becoming a silhouette. Where
XAML has no geometry to draw (art outside the convertible subset) the tint degrades to a
monochrome BitmapIcon over the raster (still tinted, but from the 256 px cache).
Nav-menu rows have their own arm, item(…).icon_tint(color) (docs/navigation.md): per-row
recolor on AppKit (contentTintColor), UIKit (tintColor), GTK (pixel recolor), Qt
(QPainter SourceIn over the pixmap), Android (compound-drawable setTint, best-effort after
the nav host mounts), ArkUI (SVG fill color), XAML (Foreground on the row’s PathIcon), and
web (the mask painted with the tint instead of currentColor). Untinted rows keep each
backend’s template default (theme foreground / secondary label); ArkUI’s untinted raster rows
draw as authored (fill color is SVG-only).
Lint
day lint validates every vector source: unreadable/unparseable art, glyph-embedded <text>
(a template’s Notes/Guides documentation text is fine; only the extracted glyph matters), an
empty .symbolset, a template whose Regular variant fails extraction, and, when android-mdc
is a declared target, art outside the VectorDrawable subset (day::lint::vector-raster-fallback,
a heads-up that Android ships the raster).
Not yet
Apple-native symbol weights (the .symbolset catalog staging that would enable them; staged
weights are template-extracted everywhere today, Apple included), and the opt-in runtime
rasterizer for downloaded SVGs (day-piece-remote-image’s planned svg arm).