Icon Generation Targets
Regras de geração de variantes de ícone por plataforma (Android mipmaps, iOS app icons, Linux hicolor, macOS .icns, Windows .ico, web favicons, Store listing). Codificadas em `dev/kicon`. Nunca desenhar PNGs por densidade à mão — rodar `kicon generate` a partir do master SVG.
Applicability
Every icon shipped by a Koder module. Source of truth for kicon generate (see dev/kicon). Human readers consult this when reasoning about why a specific file exists; machines consult the tool, which encodes these rules in code — no drift is possible when apps use kicon.
Master SVG prerequisites
See meta/docs/stack/specs/icons/products.kmd. This spec assumes the master SVG is valid per that contract (viewBox 0 0 512 512, no background rect, content within inner 66 % circle, ≥ 4 % margin).
Target matrix
android
Covers both the legacy ic_launcher bitmaps (Android ≤ 7) and the adaptive-icon foreground for Android 8+.
| Path relative to module | Size | Source region | Alpha |
|---|---|---|---|
android/app/src/main/res/mipmap-mdpi/ic_launcher.png |
48×48 | full canvas | yes |
android/app/src/main/res/mipmap-hdpi/ic_launcher.png |
72×72 | full canvas | yes |
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png |
96×96 | full canvas | yes |
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png |
144×144 | full canvas | yes |
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png |
192×192 | full canvas | yes |
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png |
432×432 | full canvas (adaptive icon foreground) | yes |
Legacy bitmaps also emitted as ic_launcher_round.png with a soft mask (rounded-square corner mask applied at raster time).
linux
FreeDesktop hicolor icon theme.
| Path relative to module | Size |
|---|---|
linux/icon.png |
512×512 |
linux/share/icons/hicolor/16x16/apps/<slug>.png |
16×16 |
linux/share/icons/hicolor/24x24/apps/<slug>.png |
24×24 |
linux/share/icons/hicolor/32x32/apps/<slug>.png |
32×32 |
linux/share/icons/hicolor/48x48/apps/<slug>.png |
48×48 |
linux/share/icons/hicolor/64x64/apps/<slug>.png |
64×64 |
linux/share/icons/hicolor/128x128/apps/<slug>.png |
128×128 |
linux/share/icons/hicolor/256x256/apps/<slug>.png |
256×256 |
linux/share/icons/hicolor/512x512/apps/<slug>.png |
512×512 |
Slug = kicon.toml slug field or the module directory name.
web
| Path relative to module | Size | Alpha | Notes |
|---|---|---|---|
site/favicon-16.png |
16×16 | no | flattened against white; favicon.ico wrapper generated separately |
site/favicon-32.png |
32×32 | no | idem |
site/apple-touch-icon.png |
180×180 | no | iOS webapp icon — iOS ignores alpha |
site/maskable-512.png |
512×512 | yes | PWA maskable icon — full canvas inc. safe zone |
site/og-image.png |
1200×630 | cond. | kicon#048: NEVER a stretched-master placeholder. Source order: (1) rasterize site/og-image.svg if present (author's rich source wins); (2) else compose the real OG via engines/sdk/koder-og (icon + [app/package].name + .description) when a description exists; (3) else SKIP (never clobbers an existing rich og-image.png) |
store
Koder Hub listing. Full-canvas display, no launcher cropping.
| Path relative to module | Size |
|---|---|
icon-256.png |
256×256 |
icon-512.png |
512×512 |
flutter-app
Embedded asset PNGs that a Flutter app loads via Image.asset('assets/icons/<slug>.png'). Distinct from the android target, which writes platform-launcher mipmaps. This is the in-bundle asset every widget call site (AppBar logo, splash screen, settings header, system tray via tray_manager, Linux update notifications via local_notifier's AssetsLinuxIcon) renders with the app icon.
Detection: emitted only when <module>/app/pubspec.yaml exists OR <module>/app/assets/icons/ already exists. On non-Flutter modules the generator returns silently with zero emissions, so it can sit in the default target list without surprising side-effects.
Aliases: <module>/kicon.toml may declare additional names, byte- identical copies under each alias name. Used to keep widget call sites stable across rebrands (e.g. koder-hub.png → khub.png):
[targets.flutter-app]
aliases = ["koder-hub", "hub"]| Path relative to module | Size | Background |
|---|---|---|
app/assets/icons/<slug>.png |
512×512 | transparent |
app/assets/icons/<alias-1>.png … |
512×512 | transparent |
Spec authority: productsdevkicon#020. Origin: Hub v2.30.0/v2.30.1 shipped a new launcher with a stale AppBar logo because nobody re-generated the asset PNG (manual cp was the only path).
ios (v0.2 — deferred)
Full AppIcon.appiconset with 20+ sizes for iPhone / iPad / Spotlight / Notification / Settings / App Store (1024×1024). No alpha (iOS fills the background white or per-app tint).
macos (v0.2 — deferred)
.icns bundle with ic07 (128×128), ic08 (256×256), ic09 (512×512), ic10 (1024×1024), plus Retina variants ic11ic12ic13/ic14.
windows (v0.2 — deferred)
.ico file embedding 163248/256 PNG images.
dark variants — icon-dark.svg (iOS 18 dark / Android 13+ themed)
A module MAY ship an optional second master icon-dark.svg alongside icon.svg (same dir, same viewBox). When present, the ios and android targets emit dark/themed variants from it; when absent, nothing extra is emitted (no warning). The dark master faces the same validation gate as the light master (safe-zone, margin, bg-rect) — a violation FAILs the generate exactly like the light master (override via --force / --allow-out-of-safe-area).
| Platform | Output | Source | Notes |
|---|---|---|---|
| iOS | Icon-<n>-dark.png per size + Contents.json appearances:[{luminosity: dark}] paired to each light entry |
icon-dark.svg |
dark variants keep alpha (transparent) — iOS composites them over its own dark background |
| Android | ic_launcher_monochrome.png (the Android 13+ themed layer) rendered from the dark master |
icon-dark.svg |
when no dark master ships, the monochrome layer falls back to the light foreground (the OS tints it either way) |
Design: detection is by presence of icon-dark.svg (no separate target enum / koder.toml flag) — a module either ships the second master or it doesn't. --check parity includes the dark artefacts. Webdesktopwindows have no dark icon variant (those surfaces theme the surrounding chrome, not the launcher icon).
verge-symbols — UI glyph-set family target
Family-level, not per-module. Every target above generates a single product/launcher icon from one master SVG. This target is the odd one out: it generates the whole Verge Symbols UI glyph family (the flat monochrome glyph set — specs/icons/ui-symbols.kmd) once from the inventory of source glyphs, producing the artifacts every Koder UI consumes via KoderIcon. It is invoked for the Verge Symbols family itself, not for each product.
Input (not a single master SVG): the glyph source set listed in meta/brand/koder-design/symbols/glyph-inventory-v1.kmd (140 glyphs v1), one source SVG per glyph, drawn per the ui-symbols.kmd geometry (grid 24, fill-flattened, per-opsz masters @ 162024).
Outputs (per ui-symbols.kmd §R8):
| Output | Path | Consumer |
|---|---|---|
| Variable icon font | dist/verge-symbols.woff2 (+.ttf) |
web (koder_web_kit), Flutter (IconData) — self-hosted, font-display: swap |
| SVG sprite | dist/verge-symbols.svg (<symbol id="kx-<name>">) |
web inline, docs, design-gen |
Flutter KoderIcons |
dist/koder_icons.dart (IconData(0xE<nnn>, fontFamily:'VergeSymbols', fontPackage:'koder_kit') per glyph) |
koder_kit (#071) |
| PNG ladder | dist/png/<name>-{16,20,24,32,48}.png |
fallback / vetor-less contexts — opsz-correct master per size, not downscale |
Codepoint assignment: stable Private-Use-Area range, assigned by glyph name (the inventory is the contract), append-only across versions — a glyph never changes codepoint (policies/always-on.kmd). The assignment map is emitted to dist/codepoints.json and is the source of truth re-read on every regen (never reassigned).
Ligatures (additive, kicon#041): the .woff2/.ttf also carries a GSUB liga lookup mapping each glyph name (kebab/snake) + its R10.2 aliases to the glyph, so typing the name renders the icon (Material parity). The PUA codepoint stays the canonical delivery (ui-symbols.kmd §R8); ligature is an opt-in convenience layered on top — component glyphs are blank zero-advance glyphs cmap'd by their ASCII code (no collision with the PUA block). Built by build_font.py (_add_ligature_gsub); regression build_font_test.py.
Background: transparent always; currentColor / fill=none root for runtime theming (ui-symbols.kmd §R7). No flatten-to-white (glyphs are never favicons).
Status: deferred until the family exists (
meta/brand/koder-design033). This section specifies the target rules so
dev/kiconcanencode them; the generator has nothing to emit until the source glyphs are drawn. The
KoderIconsDart output feedskoder_kit#071.
Background handling per target
| Target | Transparent background? | Fallback for formats that disallow alpha |
|---|---|---|
| android | yes (all) | n/a — all Android formats support alpha |
| linux | yes | n/a |
| web | favicon: no; rest: yes | flatten onto white for favicon |
| store | yes | n/a |
| flutter-app | yes | n/a — Flutter widgets honour alpha |
| ios | no | flatten onto white |
| macos | yes | n/a |
| windows | yes (PNG inside .ico) | n/a |
| verge-symbols | yes (currentColor/fill=none) |
n/a — glyphs are never favicons; runtime-themed |
Validation that must pass before generation
All of these run in kicon validate; kicon generate refuses to emit without a clean validation pass unless --force is set (CI only).
- Margin (Error): rendered bbox leaves ≥ 4 % (20 px) on every side
of the 512 canvas. Matches
products.kmd§2 ("centralização óptica com tolerância ±4px"). This is the master-level margin contract. - No background rect (Error): no root-level opaque
<rect>thatfills the full canvas.
- Square viewBox (Error):
viewBox="0 0 N N"with identical widthand height.
- No size attributes (Warning): absence of
width=""andheight=""that force non-square rendering; SVG should scale to any target size. - Safe-zone (Warning): rendered content bbox fits within the inner
66 % circle (
r = 512 × 0.33 = 169 pxfrom center). The Material Adaptive Icon safe zone is a target-level constraint, not a master-level one. Masters legitimately fill ~92 % of the canvas perproducts.kmd§2, so this warns on every Koder master and is downgraded per-icon by[icon].allow_out_of_safe_area. Promoted to Error would block every existing product.
Adaptive-foreground fit (data-adaptive-fit, kicon#051). How the ic_launcher_foreground.png / ic_launcher.png / ic_launcher_monochrome.png PNGs handle the safe zone is declared by the master, never inferred (mirrors §2.2 data-keyline):
- default (attribute absent): full-bleed — the master passes
through at full canvas and its own §R7 design margin provides squircle safety. This is kicon#041's owner-ratified default.
data-adaptive-fit="safe-zone": the artwork's true reach (maxdistance from centre to any opaque pixel, alpha ≥ 200 — drop-shadow ignored) is scaled onto the safe radius (
0.33·canvas), so it clears every OEM launcher mask. Declared by corner-/edge-filling silhouettes that would otherwise overflow an aggressive squircle (the Koder Hub honeycomb: its hexagonal hull reaches r≈205 while its bbox corners sit empty — the §2.2 case a flat fraction or bbox-fill mis-handles).
Reach (not bbox) is measured, so a non-rectangular hull nests as large as the safe circle allows. The earlier flat adaptiveSafeZoneFraction constant (0.6667 → 0.55 → 1.0) is retired: a single fraction conflates the wrapper margin with the master's built-in margin, so no value ever fit every icon.
verge-symbolsvalidates by a different rulebook. The checks above are for product-icon masters (512 canvas, adaptive-icon safe-zone). The glyph-set target validates each glyph perui-symbols.kmd §R8instead — Error: fits canvas 24 with trim ≥ 2px, PUA codepoint unique + stable, R10 naming, outer stroke edge on whole pixel @16/@24; Warning: keyline anchoring (± overshoot), round cap outside the closed motion list, legibility @16. The 512/safe-zone rules do not apply to glyphs.
Canonical reference
products/dev/kicon/cmd/kicon/main.go— the Go reference implementation.- `productsdevkiconinternaltargets