Icon Generation Targets

mandatory

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.pngkhub.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-design

033). This section specifies the target rules so dev/kicon can

encode them; the generator has nothing to emit until the source glyphs are drawn. The KoderIcons Dart output feeds koder_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).

  1. 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.

  2. No background rect (Error): no root-level opaque <rect> that

    fills the full canvas.

  3. Square viewBox (Error): viewBox="0 0 N N" with identical width

    and height.

  4. No size attributes (Warning): absence of width="" and

    height="" that force non-square rendering; SVG should scale to any target size.

  5. Safe-zone (Warning): rendered content bbox fits within the inner

    66 % circle (r = 512 × 0.33 = 169 px from center). The Material Adaptive Icon safe zone is a target-level constraint, not a master-level one. Masters legitimately fill ~92 % of the canvas per products.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 (max

    distance 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-symbols validates 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 per ui-symbols.kmd §R8 instead — 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