From 68403d6102790f094f25e5653dbfa0283741d576 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Tue, 8 Sep 2026 19:16:55 +0200 Subject: [PATCH] =?UTF-8?q?feat(icons):=20=ED=95=9C=EC=B9=B8=20on=20a=20ja?= =?UTF-8?q?de=20ground?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PWA manifest declared icon-192.png, icon-512.png and favicon.svg. None of the three existed, so install-to-home-screen and the browser tab both resolved to nothing. That was a defect in what the last pass claimed as done. Drawn procedurally with Pillow rather than authored as SVG: there is no dependable SVG rasteriser here to convert from, and ImageMagick falls back to its MSVG renderer, which cannot be trusted with text. Pillow's FreeType + RAQM stack shapes Hangul correctly, which is the only hard requirement. The mark uses the app's own register — the serif face already used for 한칸 on the boot screen, in --jade on --bg. Two glyphs read well at 180px and up; at favicon sizes 한칸 turns to mush, so the small sizes carry 한 alone. icon-192 · icon-512 · apple-touch-icon (180) 한칸 icon-512-maskable 한칸 at 56%, inside Android's circular crop favicon.ico (16/32/48) · favicon-32 한 Sizing is a binary search on the drawn ink extent, and centring uses the ink bounds rather than the font's line box — CJK metrics leave asymmetric space above and below, which would otherwise sit the mark visibly high. Re-running the generator writes byte-identical files. Android launcher icons are untouched; Android work is out of scope. Co-Authored-By: Claude Opus 5 --- app/index.html | 4 +- app/public/apple-touch-icon.png | Bin 0 -> 4584 bytes app/public/favicon-32.png | Bin 0 -> 1025 bytes app/public/favicon.ico | Bin 0 -> 4346 bytes app/public/icon-192.png | Bin 0 -> 5163 bytes app/public/icon-512-maskable.png | Bin 0 -> 11241 bytes app/public/icon-512.png | Bin 0 -> 13924 bytes app/vite.config.ts | 10 ++-- tools/icons/make-icons.py | 99 +++++++++++++++++++++++++++++++ 9 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 app/public/apple-touch-icon.png create mode 100644 app/public/favicon-32.png create mode 100644 app/public/favicon.ico create mode 100644 app/public/icon-192.png create mode 100644 app/public/icon-512-maskable.png create mode 100644 app/public/icon-512.png create mode 100644 tools/icons/make-icons.py diff --git a/app/index.html b/app/index.html index 6d43959..74799ee 100644 --- a/app/index.html +++ b/app/index.html @@ -5,7 +5,9 @@ Hankan — 한국어 읽기 - + + +
diff --git a/app/public/apple-touch-icon.png b/app/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a9c612bc567d941ad883fe459622922857594de2 GIT binary patch literal 4584 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 literal 0 HcmV?d00001 diff --git a/app/public/favicon-32.png b/app/public/favicon-32.png new file mode 100644 index 0000000000000000000000000000000000000000..1289c143b311a004ba1b7100b14375fafd92a117 GIT binary patch literal 1025 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 literal 0 HcmV?d00001 diff --git a/app/public/icon-512-maskable.png b/app/public/icon-512-maskable.png new file mode 100644 index 0000000000000000000000000000000000000000..9ccc4bfec7824e71192302ec24685482ebf9d644 GIT binary patch literal 11241 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 literal 0 HcmV?d00001 diff --git a/app/public/icon-512.png b/app/public/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d7cdf578dfe29a84f8f6f712c9b9303eaab09b21 GIT binary patch literal 13924 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 literal 0 HcmV?d00001 diff --git a/app/vite.config.ts b/app/vite.config.ts index aaa10c4..5b268e6 100644 --- a/app/vite.config.ts +++ b/app/vite.config.ts @@ -35,7 +35,7 @@ export default defineConfig({ react(), VitePWA({ registerType: "autoUpdate", - includeAssets: ["favicon.svg"], + includeAssets: ["favicon.ico", "favicon-32.png", "apple-touch-icon.png"], manifest: { name: "Hankan — 한국어 읽기", short_name: "Hankan", @@ -46,10 +46,12 @@ export default defineConfig({ background_color: "#F1F4F1", theme_color: "#0F6B5C", icons: [ - { src: "icon-192.png", sizes: "192x192", type: "image/png" }, - { src: "icon-512.png", sizes: "512x512", type: "image/png" }, + { src: "icon-192.png", sizes: "192x192", type: "image/png", purpose: "any" }, + { src: "icon-512.png", sizes: "512x512", type: "image/png", purpose: "any" }, + // Android masks icons to a circle; this one keeps 한칸 inside the + // safe zone so the crop cannot clip it. { - src: "icon-512.png", + src: "icon-512-maskable.png", sizes: "512x512", type: "image/png", purpose: "maskable", diff --git a/tools/icons/make-icons.py b/tools/icons/make-icons.py new file mode 100644 index 0000000..ab83c09 --- /dev/null +++ b/tools/icons/make-icons.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""Hankan's app icons: 한칸 on a jade ground. + +Drawn procedurally with Pillow rather than authored as SVG, because there is +no dependable SVG rasteriser to convert from — and an icon that renders +differently on the build machine than in the browser is worse than no icon. +Pillow's FreeType + RAQM stack shapes Hangul correctly, which is the only +hard requirement here. + +The mark follows the app's own register: the serif face it already uses for +한칸 on the boot screen, in the app's jade and paper colours. + +Two glyphs read well at 180px and up. At favicon sizes 한칸 turns to mush, so +the small sizes carry 한 alone — the same mark, cropped to what survives. + + python3 tools/icons/make-icons.py + +Deterministic: re-running writes byte-identical files. +""" + +from pathlib import Path + +from PIL import Image, ImageDraw, ImageFont + +ROOT = Path(__file__).resolve().parents[2] +OUT = ROOT / "app" / "public" + +# --serif, matching the boot screen and display numerals. +FONT = "/usr/share/fonts/opentype/noto/NotoSerifCJK-Bold.ttc" + +JADE = (15, 107, 92) # --jade #0F6B5C +PAPER = (241, 244, 241) # --bg #F1F4F1 + +WORDMARK = "한칸" +GLYPH = "한" + + +def render(size: int, text: str, *, coverage: float, out: Path) -> None: + """Draw `text` centred on a jade square, optically centred by ink bounds. + + `coverage` is the fraction of the square the ink should span. Android + masks icons to a circle and can crop ~10% off each edge, so a maskable + icon passes a smaller value to keep the mark inside the safe zone. + """ + img = Image.new("RGB", (size, size), JADE) + draw = ImageDraw.Draw(img) + + # Binary-search the point size that lands the ink at the target width. + # Cheaper than solving it: font metrics include side bearings and CJK + # line gap, so the drawn extent is not a fixed ratio of the point size. + target = size * coverage + lo, hi = 1, size * 2 + best = ImageFont.truetype(FONT, 1) + while lo <= hi: + mid = (lo + hi) // 2 + font = ImageFont.truetype(FONT, mid) + left, top, right, bottom = draw.textbbox((0, 0), text, font=font) + extent = max(right - left, bottom - top) + if extent <= target: + best, lo = font, mid + 1 + else: + hi = mid - 1 + + # Centre on the ink itself, not the font's line box — CJK metrics leave + # asymmetric space above and below, which would sit the mark visibly high. + left, top, right, bottom = draw.textbbox((0, 0), text, font=best) + x = (size - (right - left)) / 2 - left + y = (size - (bottom - top)) / 2 - top + draw.text((x, y), text, font=best, fill=PAPER) + + img.save(out, "PNG", optimize=True) + print(f" {out.relative_to(ROOT)} {size}×{size} {out.stat().st_size:,}B") + + +def main() -> None: + OUT.mkdir(parents=True, exist_ok=True) + print("Icons → app/public/") + + render(192, WORDMARK, coverage=0.74, out=OUT / "icon-192.png") + render(512, WORDMARK, coverage=0.74, out=OUT / "icon-512.png") + # Android crops to a circle: keep the mark well inside the safe zone. + render(512, WORDMARK, coverage=0.56, out=OUT / "icon-512-maskable.png") + render(180, WORDMARK, coverage=0.72, out=OUT / "apple-touch-icon.png") + + # Small sizes: one glyph, so it is still legible in a browser tab. + render(32, GLYPH, coverage=0.82, out=OUT / "favicon-32.png") + + ico = OUT / "favicon.ico" + base = Image.new("RGB", (48, 48), JADE) + d = ImageDraw.Draw(base) + f = ImageFont.truetype(FONT, 44) + l, t, r, b = d.textbbox((0, 0), GLYPH, font=f) + d.text(((48 - (r - l)) / 2 - l, (48 - (b - t)) / 2 - t), GLYPH, font=f, fill=PAPER) + base.save(ico, "ICO", sizes=[(16, 16), (32, 32), (48, 48)]) + print(f" {ico.relative_to(ROOT)} 16/32/48 {ico.stat().st_size:,}B") + + +if __name__ == "__main__": + main()