macOS DMG: branded window background and icon layout

The DMG opened as a plain Finder window. It now shows the app icon's figure on
the GridCraft colour, with GridCraft.app and the Applications link side by side.
Same approach as PhotoCraft's and VectorCraft's (storytold/photocraft#1201,
storytold/vectorcraft#493).

- packaging/macos/dmg/: background.svg (source; text outlined, no paper grain,
  the figure an <image> of assets/app-icon), background.tiff (1x + 2x HiDPI,
  16-colour palette, Deflate, sRGB; ~26 KB)
  and dmg-layout.DS_Store (Finder view settings: window, icon size 128,
  positions, background).
- generate.py writes the TIFF and the layout on any OS, byte-for-byte
  reproducible, with nothing from the machine that ran it (no paths, users,
  disk names or volume UUIDs).
- package.sh copies them into the staging folder; the image is still built with
  hdiutil makehybrid. Nothing extra runs in the signing job.
- The volume name drops the version: the layout finds the background through
  an alias that includes the volume name. The DMG file name keeps the version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Xus BadiaandClaude Opus 5.5 committed 2026-10-09 07:43:50 +02:00
1 parent fb823899c5
commit be33eba7d1
8 files changed
+244 -1

No files matched your search

+2
View File
@@ -18,6 +18,8 @@ Generated-in-code art is original and has no file to list: the UI icon set (`cra
| `docs/brand/artcraft-mark-black.png` | ArtCraft Team | craftrules `assets/brand/` | ArtCraft brand terms | Trademark |
| `docs/brand/LICENSE-brand.txt` | (licence text) | craftrules | — | |
| `assets/app-icon/gridcraft.svg` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | PLACEHOLDER app icon (ledger page); replaced by the owner's creature art later |
| `packaging/macos/dmg/background.svg` | @XusBadia | original (hand-written SVG, derived from the GridCraft app icon `assets/app-icon/gridcraft.svg`, referenced via `<image>`, not copied; text is outlined glyph paths; `packaging/macos/dmg/generate.py` renders it to `background.tiff`) | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | GridCraft macOS DMG window background for the Finder window; added 2026-10-08 |
| `packaging/macos/dmg/background.tiff` | @XusBadia | Rendered from `packaging/macos/dmg/background.svg` by `packaging/macos/dmg/generate.py` | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | 1x + 2x HiDPI TIFF for the macOS DMG window; includes the app icon (see its row) and LittleCMS's built-in sRGB profile ("No copyright, use freely") |
| `assets/app-icon/gridcraft-1024.png` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, rendered from gridcraft.svg |
| `assets/app-icon/gridcraft-macos-512.png` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, macOS grid render |
| `assets/app-icon/gridcraft.icns` | GridCraft contributors | original (hand-written SVG; `packaging/icons.sh`) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | Placeholder icon, macOS bundle icon |
+3
View File
@@ -101,6 +101,9 @@ and upload their artifacts, the environment refuses macOS and Windows, and no re
- **Notarization:** the app is zipped and sent with `xcrun notarytool submit --wait`, then
stapled. The DMG (with an `Applications` link) is signed, notarized and stapled too, and
checked with `codesign --verify --strict`, `stapler validate` and `spctl -a -vvv`.
Its Finder window (background, icon size and positions) comes from
[`packaging/macos/dmg/`](../packaging/macos/dmg/README.md), and its volume is named `GridCraft`
without the version, which the window's background needs; the DMG file name keeps the version.
- **CLI:** the universal `gridcraft-cli` is signed, zipped and notarized.
Locally, without certificates, the script signs ad-hoc (`codesign -s -`) and skips notarization:
+44
View File
@@ -0,0 +1,44 @@
# macOS DMG window
What Finder shows when the DMG opens: a 660 × 400 pt background with the app icon and the
`Applications` link side by side. `package.sh` copies these files into the image; nothing here is
generated at build time, so the DMG still builds with `hdiutil makehybrid` (no mounted device, no
Finder scripting on CI, nothing extra installed in the signing job).
| File | What |
|---|---|
| `background.svg` | Source of the background: the app icon (`assets/app-icon/gridcraft.svg`, linked, not copied) cropped as a cover on the GridCraft colour field (`#1f9d55`), Ink and Paper. Its text (Inter, JetBrains Mono) is outlined, so rendering it needs no fonts. No paper grain: noise doesn't compress, and this keeps `background.tiff` small. |
| `background.tiff` | The background at 1x (660 × 400 px, 72 dpi) and 2x (1320 × 800 px, 144 dpi) in one HiDPI TIFF (16-colour palette, Deflate, sRGB). Goes to `.background/background.tiff`. |
| `dmg-layout.DS_Store` | Finder's view settings for the volume: window size, icon size 128, GridCraft.app at (326, 205), `Applications` at (574, 205), and the background. Goes to `.DS_Store` in the image; named so it isn't mistaken for (or ignored like) a Finder-generated `.DS_Store`. |
| `generate.py` | Writes `background.tiff` and `dmg-layout.DS_Store` from the SVG and the layout above. |
## The volume name has no version
The mounted volume is called `GridCraft`, not `GridCraft <version>`. The layout points at the background
through an alias that includes the volume name, and Finder resolves it by that name: with a
versioned name the window keeps its size and icon positions but shows no background (tested).
The DMG file name (`gridcraft-<version>-macos-<arch>.dmg`) still carries the version.
## Rules
- **Finder draws the icon labels in black in light and dark mode** when a window has a background,
so the area under both icons stays light (Paper).
- **Nothing goes inside the icon boxes:** artwork keeps 10 pt clear of each 128 pt icon box and of
the label strip under it.
## Regenerate
Edit `background.svg` (or the layout constants in `generate.py`), then run, on any OS:
```sh
pip install 'pillow>=12' ds_store==1.3.3 mac_alias==2.2.3
python3 packaging/macos/dmg/generate.py # needs resvg on PATH
```
Both outputs are byte-for-byte reproducible and carry nothing from the machine that ran it: the
SVG renders without any fonts, and `dmg-layout.DS_Store` is written from scratch, its background
alias holding only the volume name and `/.background/background.tiff`. If you add text to the SVG,
outline it (`usvg` from resvg converts text to paths). The window is 660 × 432 with Finder's 32 pt
title bar; the content area is 660 × 400.
This follows VectorCraft's `packaging/macos/dmg/` (storytold/vectorcraft#493).
+31
View File
@@ -0,0 +1,31 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="660" height="400" viewBox="0 0 660 400">
<defs><clipPath id="panel"><rect width="232" height="400"/></clipPath><pattern id="hatch" width="2.6" height="2.6" patternUnits="userSpaceOnUse" patternTransform="rotate(-38)">
<rect width="2.6" height="1.05" fill="#0b0b0c"/></pattern><filter id="grain" x="0" y="0" width="100%" height="100%">
<feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" seed="7"/>
<feColorMatrix values="0 0 0 0 0.04 0 0 0 0 0.04 0 0 0 0 0.05 0.5 0 0 0 -0.225"/>
</filter></defs>
<rect width="660" height="400" fill="#efe9dc"/>
<g clip-path="url(#panel)"><rect width="232" height="400" fill="#1f9d55"/><g transform="translate(-68.3,11.4) scale(0.72)"><image href="../../../assets/app-icon/gridcraft.svg" width="512" height="512"/></g></g>
<line x1="232.5" y1="0" x2="232.5" y2="400" stroke="#0b0b0c" stroke-width="1.2"/>
<line x1="236.5" y1="0" x2="236.5" y2="400" stroke="#0b0b0c" stroke-width="0.5"/>
<line x1="267" y1="88.5" x2="624" y2="88.5" stroke="#0b0b0c" stroke-width="0.6"/>
<path d="M271.5,81.5 L276,88.5 L271.5,95.5 L267,88.5 Z" fill="#1f9d55"/>
<rect x="408" y="197.0" width="19" height="16" fill="#efe9dc" stroke="#0b0b0c" stroke-opacity="0.45" stroke-width="0.7"/><rect x="427" y="197.0" width="19" height="16" fill="#efe9dc" stroke="#0b0b0c" stroke-opacity="0.45" stroke-width="0.7"/><rect x="446" y="197.0" width="19" height="16" fill="#efe9dc" stroke="#0b0b0c" stroke-opacity="0.45" stroke-width="0.7"/><rect x="465" y="197.0" width="19" height="16" fill="#efe9dc" stroke="#0b0b0c" stroke-opacity="0.45" stroke-width="0.7"/><rect x="408" y="197.0" width="76" height="16" fill="#1f9d55" fill-opacity="0.18" stroke="#1f9d55" stroke-width="2"/><rect x="481" y="210.0" width="6" height="6" fill="#1f9d55" stroke="#efe9dc" stroke-width="1"/><path d="M498.1,205.0 L487.1,210.5 L487.1,199.5 Z" fill="#0b0b0c"/>
<line x1="267" y1="335.5" x2="624" y2="335.5" stroke="#0b0b0c" stroke-width="0.6"/>
<g id="text">
<g>
<path fill="#0b0b0c" stroke="none" d="M 277.6162 72.29297 Q 274.59863 72.29297 272.32813 70.93799 Q 270.05762 69.58301 268.7832 67.0708 Q 267.5088 64.55859 267.5088 61.11621 Q 267.5088 57.600582 268.81982 55.08838 Q 270.13086 52.576176 272.38672 51.22851 Q 274.64258 49.88086 277.49902 49.88086 Q 279.90137 49.88086 281.85693 50.80371 Q 283.8125 51.726563 285.0796 53.367188 Q 286.34668 55.00781 286.6836 57.146484 L 282.68457 57.146484 Q 282.15723 55.40332 280.8462 54.392574 Q 279.53516 53.381836 277.54297 53.381836 Q 275.78516 53.381836 274.40088 54.27539 Q 273.0166 55.168945 272.2256 56.890137 Q 271.43457 58.61133 271.43457 61.086914 Q 271.43457 64.79297 273.15576 66.79248 Q 274.87695 68.79199 277.63086 68.79199 Q 280.0918 68.79199 281.55664 67.407715 Q 283.02148 66.02344 283.08008 63.66504 L 278.04102 63.66504 L 278.04102 60.588867 L 286.87402 60.588867 L 286.87402 63.210938 Q 286.87402 66.00879 285.68018 68.052246 Q 284.48633 70.0957 282.39893 71.194336 Q 280.31152 72.29297 277.6162 72.29297 Z M 289.9922 72 L 289.9922 55.623047 L 293.6836 55.623047 L 293.6836 58.362305 L 293.85938 58.362305 Q 294.29883 56.941402 295.39746 56.172363 Q 296.4961 55.40332 297.90234 55.40332 Q 298.63477 55.40332 299.29395 55.50586 L 299.29395 59.006836 Q 299.05957 58.948246 298.53223 58.882324 Q 298.00488 58.816406 297.4922 58.816406 Q 295.8955 58.816406 294.84814 59.805172 Q 293.80078 60.79394 293.80078 62.36133 L 293.80078 72 Z M 301.5918 72 L 301.5918 55.623047 L 305.4004 55.623047 L 305.4004 72 Z M 303.4961 53.308594 Q 302.5879 53.308594 301.93604 52.700684 Q 301.28418 52.092773 301.28418 51.213863 Q 301.28418 50.34961 301.93604 49.7417 Q 302.5879 49.13379 303.4961 49.13379 Q 304.41895 49.13379 305.0708 49.7417 Q 305.72266 50.34961 305.72266 51.213863 Q 305.72266 52.092773 305.0708 52.700684 Q 304.41895 53.308594 303.4961 53.308594 Z M 314.97852 72.29297 Q 313.04492 72.29297 311.51416 71.3042 Q 309.9834 70.31543 309.1045 68.42578 Q 308.2256 66.53613 308.2256 63.84082 Q 308.2256 61.11621 309.11914 59.233883 Q 310.0127 57.351563 311.54346 56.38477 Q 313.07422 55.41797 314.96387 55.41797 Q 316.4287 55.41797 317.3589 55.90869 Q 318.28906 56.399418 318.83105 57.080566 Q 319.37305 57.76172 319.66602 58.33301 L 319.82715 58.33301 L 319.82715 50.17383 L 323.63574 50.17383 L 323.63574 72 L 319.9004 72 L 319.9004 69.40723 L 319.66602 69.40723 Q 319.3584 69.993164 318.80176 70.66699 Q 318.24512 71.34082 317.31494 71.816895 Q 316.38477 72.29297 314.97852 72.29297 Z M 316.01855 69.1582 Q 317.8789 69.1582 318.88965 67.686035 Q 319.9004 66.21387 319.9004 63.82617 Q 319.9004 61.438473 318.89697 59.995605 Q 317.89355 58.552734 316.01855 58.552734 Q 314.08496 58.552734 313.10352 60.046875 Q 312.12207 61.54102 312.12207 63.82617 Q 312.12207 66.12598 313.11084 67.64209 Q 314.0996 69.1582 316.01855 69.1582 Z M 336.6416 72.29297 Q 333.7705 72.29297 331.51465 70.95996 Q 329.2588 69.62695 327.95508 67.12207 Q 326.65137 64.61719 326.65137 61.101563 Q 326.65137 57.571285 327.9624 55.059082 Q 329.27344 52.54688 331.5293 51.213863 Q 333.78516 49.88086 336.6416 49.88086 Q 339.0293 49.88086 340.98486 50.774414 Q 342.94043 51.66797 344.21484 53.359863 Q 345.48926 55.051754 345.8408 57.439453 L 341.9004 57.439453 Q 341.53418 55.50586 340.09863 54.443848 Q 338.6631 53.381836 336.7002 53.381836 Q 334.01953 53.381836 332.29834 55.381344 Q 330.57715 57.38086 330.57715 61.101563 Q 330.57715 64.86621 332.30566 66.8291 Q 334.03418 68.79199 336.68555 68.79199 Q 338.64844 68.79199 340.0913 67.72998 Q 341.53418 66.66797 341.9004 64.734375 L 345.85547 64.734375 Q 345.54785 66.8291 344.34668 68.54297 Q 343.1455 70.256836 341.18262 71.2749 Q 339.21973 72.29297 336.6416 72.29297 Z M 348.76855 72 L 348.76855 55.623047 L 352.46 55.623047 L 352.46 58.362305 L 352.63574 58.362305 Q 353.0752 56.941402 354.17383 56.172363 Q 355.27246 55.40332 356.6787 55.40332 Q 357.41113 55.40332 358.0703 55.50586 L 358.0703 59.006836 Q 357.83594 58.948246 357.3086 58.882324 Q 356.78125 58.816406 356.26855 58.816406 Q 354.67188 58.816406 353.6245 59.805172 Q 352.57718 60.79394 352.57718 62.36133 L 352.57718 72 Z M 364.46973 72.336914 Q 362.917 72.336914 361.67188 71.77295 Q 360.42673 71.208984 359.709 70.11035 Q 358.9912 69.01172 358.9912 67.40039 Q 358.9912 65.55469 359.8921 64.53662 Q 360.79297 63.518555 362.2212 63.057125 Q 363.64944 62.595703 365.23145 62.43457 Q 367.37012 62.200195 368.32227 62.00244 Q 369.27444 61.804688 369.27444 60.95508 L 369.27444 60.88184 Q 369.27444 59.680664 368.542 59.01416 Q 367.80957 58.34766 366.41797 58.34766 Q 364.98242 58.34766 364.1328 58.977543 Q 363.2832 59.607426 362.99023 60.413086 L 359.45996 59.695313 Q 360.17773 57.527344 362.06006 56.472656 Q 363.94238 55.41797 366.40335 55.41797 Q 367.56055 55.41797 368.73975 55.688965 Q 369.91895 55.95996 370.9004 56.597168 Q 371.8818 57.234375 372.48242 58.325684 Q 373.083 59.416992 373.083 61.057617 L 373.083 72 L 369.43558 72 L 369.43558 69.74414 L 369.28906 69.74414 Q 368.74707 70.79883 367.56055 Line truncated
</g>
<g>
<path fill="#0b0b0c" fill-opacity="0.7" stroke="none" d="M 267.765 108.31 L 267.765 107.590004 L 271.635 107.590004 L 271.635 108.31 Z M 267.765 110.47 L 267.765 109.74999 L 271.635 109.74999 L 271.635 110.47 Z M 273.345 112 L 273.345 111.262 L 274.686 111.262 L 274.686 106.168 L 273.345 106.168 L 273.345 105.42999 L 276.85498 105.42999 L 276.85498 106.168 L 275.514 106.168 L 275.514 111.262 L 276.85498 111.262 L 276.85498 112 Z M 278.61 112 L 278.61 105.42999 L 279.69 105.42999 L 281.697 111.055 Q 281.679 110.83 281.6565 110.501495 Q 281.634 110.173 281.62048 109.80851 Q 281.607 109.444 281.607 109.12 L 281.607 105.42999 L 282.38998 105.42999 L 282.38998 112 L 281.31 112 L 279.31198 106.37501 Q 279.33 106.591 279.348 106.9195 Q 279.366 107.24801 279.3795 107.61251 Q 279.39297 107.977 279.39297 108.31 L 279.39297 112 Z M 285.936 112.09 Q 284.98203 112.09 284.415 111.586 Q 283.84802 111.082 283.84802 110.19999 L 284.65802 110.19999 Q 284.65802 110.749 285.009 111.059494 Q 285.36002 111.37 285.936 111.37 Q 286.49402 111.37 286.82703 111.046 Q 287.16 110.722 287.16 110.19999 Q 287.16 109.804 286.94852 109.5115 Q 286.737 109.219 286.341 109.111 L 285.342 108.832 Q 284.721 108.652 284.3655 108.1885 Q 284.01 107.72499 284.01 107.095 Q 284.01 106.573 284.24402 106.177 Q 284.47803 105.781006 284.901 105.5605 Q 285.324 105.34 285.87302 105.34 Q 286.431 105.34 286.85852 105.5605 Q 287.286 105.781006 287.52902 106.1725 Q 287.772 106.564 287.772 107.07701 L 286.962 107.07701 Q 286.962 106.627 286.656 106.3435 Q 286.35 106.06 285.87302 106.06 Q 285.39603 106.06 285.099 106.3435 Q 284.802 106.627 284.802 107.07701 Q 284.802 107.437 284.9955 107.6935 Q 285.18903 107.95 285.54 108.04901 L 286.566 108.337 Q 287.22302 108.517 287.58752 109.0165 Q 287.95203 109.516 287.95203 110.19999 Q 287.95203 111.064 287.403 111.577 Q 286.854 112.09 285.936 112.09 Z M 290.89502 112 L 290.89502 106.168 L 289.095 106.168 L 289.095 105.42999 L 293.505 105.42999 L 293.505 106.168 L 291.70502 106.168 L 291.70502 112 Z M 294.45 112 L 296.16 105.42999 L 297.249 105.42999 L 298.95 112 L 298.131 112 L 297.699 110.254 L 295.71 110.254 L 295.278 112 Z M 295.872 109.57 L 297.528 109.57 L 297.024 107.545 Q 296.88 106.96901 296.799 106.58201 Q 296.718 106.19499 296.7 106.078 Q 296.682 106.19499 296.601 106.58201 Q 296.52 106.96901 296.376 107.536 Z M 300.57 112 L 300.57 105.42999 L 301.38 105.42999 L 301.38 111.262 L 304.35 111.262 L 304.35 112 Z M 305.97 112 L 305.97 105.42999 L 306.78 105.42999 L 306.78 111.262 L 309.75 111.262 L 309.75 112 Z M 314.565 113.08 Q 313.278 112.792 312.5715 111.874 Q 311.86502 110.956 311.86502 109.57 L 311.86502 107.95 Q 311.86502 106.564 312.5715 105.64601 Q 313.278 104.72799 314.565 104.44 L 314.565 105.22301 Q 313.989 105.358 313.56152 105.736 Q 313.134 106.114 312.9045 106.6765 Q 312.67502 107.23899 312.67502 107.95 L 312.67502 109.57 Q 312.67502 110.272 312.9045 110.839 Q 313.134 111.406 313.56152 111.784 Q 313.989 112.16201 314.565 112.297005 Z M 316.05002 112 L 317.76 105.42999 L 318.849 105.42999 L 320.55002 112 L 319.73102 112 L 319.299 110.254 L 317.31 110.254 L 316.87802 112 Z M 317.47202 109.57 L 319.128 109.57 L 318.624 107.545 Q 318.48 106.96901 318.39902 106.58201 Q 318.318 106.19499 318.30002 106.078 Q 318.282 106.19499 318.20102 106.58201 Q 318.12 106.96901 317.976 107.536 Z M 321.81 112 L 321.81 111.262 L 323.592 111.262 L 323.592 106.131996 L 321.81 107.464 L 321.81 106.537 L 323.295 105.42999 L 324.402 105.42999 L 324.402 111.262 L 325.86 111.262 L 325.86 112 Z M 327.435 113.08 L 327.435 112.297005 Q 328.02 112.16201 328.443 111.784 Q 328.866 111.406 329.0955 110.839 Q 329.32498 110.272 329.32498 109.57 L 329.32498 107.95 Q 329.32498 107.23899 329.0955 106.6765 Q 328.866 106.114 328.443 105.736 Q 328.02 105.358 327.435 105.22301 L 327.435 104.44 Q 328.722 104.72799 329.4285 105.64601 Q 330.13498 106.564 330.13498 107.95 L 330.13498 109.57 Q 330.13498 110.956 329.4285 111.874 Q 328.722 112.792 327.435 113.08 Z M 339.90002 109.66 Q 339.56702 109.66 339.3645 109.462 Q 339.16202 109.26399 339.16202 108.949005 Q 339.16202 108.616 339.3645 108.409 Q 339.56702 108.202 339.90002 108.202 Q 340.233 108.202 340.43552 108.409 Q 340.638 108.616 340.638 108.949005 Q 340.638 109.26399 340.43552 109.462 Q 340.233 109.66 339.90002 109.66 Z M 350.73596 112.09 Q 349.782 112.09 349.215 111.586 Q 348.648 111.082 348.648 110.19999 L 349.458 110.19999 Q 349.458 110.749 349.80902 111.059494 Q 350.16 111.37 350.73596 111.37 Q 351.294 111.37 351.627 111.046 Q 351.96 110.722 351.96 110.19999 Q 351.96 109.804 351.7485 109.5115 Q 351.537 109.219 351.141 109.111 L 350.142 108.832 Q 349.52097 108.652 349.1655 108.1885 Q 348.81 107.72499 348.81 107.095 Q 348.81 106.573 349.044 106.177 Q 349.278 105.781006 349.701 105.5605 Q 350.124 105.34 350.673 105.34 Q 351.231 105.34 351.6585 105.5605 Q 352.08603 105.781006 352.32898 106.1725 Q 352.57202 106.564 352.57202 107.07701 L 351.762 107.07701 Q 351.762 106.627 351.45602 106.3435Line truncated
</g>
<g>
<path fill="#0b0b0c" fill-opacity="0.65" stroke="none" d="M 270.4912 358 L 267.88623 358 L 267.88623 349.99707 L 270.5664 349.99707 Q 271.74805 349.99707 272.5913 350.4778 Q 273.43457 350.9585 273.88574 351.8501 Q 274.3369 352.7417 274.3369 353.9878 Q 274.3369 355.23923 273.88306 356.13623 Q 273.4292 357.0332 272.56714 357.5166 Q 271.70508 358 270.4912 358 Z M 269.11084 356.92578 L 270.4214 356.92578 Q 271.78027 356.92578 272.45703 356.16574 Q 273.1338 355.40573 273.1338 353.9878 Q 273.1338 352.5752 272.4651 351.82327 Q 271.7964 351.07126 270.48584 351.07126 L 269.11084 351.07126 Z M 277.3247 358 L 277.3247 349.99707 L 280.19287 349.99707 Q 281.1167 349.99707 281.73438 350.31934 Q 282.35205 350.6416 282.6582 351.20825 Q 282.96436 351.77487 282.96436 352.51074 Q 282.96436 353.3164 282.59912 353.90457 Q 282.2339 354.49268 281.50342 354.7666 L 283.26514 358 L 281.87402 358 L 280.25195 354.97607 Q 280.21973 354.97607 280.18213 354.97607 L 278.54932 354.97607 L 278.54932 358 Z M 278.54932 353.9287 L 280.06396 353.9287 Q 280.94482 353.9287 281.33423 353.55542 Q 281.72363 353.18216 281.72363 352.51074 Q 281.72363 351.83398 281.33154 351.4392 Q 280.93945 351.04443 280.05322 351.04443 L 278.54932 351.04443 Z M 285.33984 358 L 288.18652 349.99707 L 289.69043 349.99707 L 292.59082 358 L 291.26416 358 L 290.52832 355.88916 L 287.38086 355.88916 L 286.6665 358 Z M 287.72998 354.86862 L 290.16846 354.86862 L 289.70654 353.54733 Q 289.5454 353.06934 289.35742 352.43823 Q 289.16943 351.80713 288.9331 350.9585 Q 288.69678 351.81784 288.51147 352.4543 Q 288.32617 353.09082 288.17578 353.54733 Z M 298.21045 358.10742 Q 297.1255 358.10742 296.3037 357.6079 Q 295.48193 357.1084 295.0254 356.1899 Q 294.56885 355.27148 294.56885 354.00388 Q 294.56885 352.72562 295.03345 351.80444 Q 295.49805 350.8833 296.30908 350.38647 Q 297.12012 349.88965 298.15674 349.88965 Q 299.02148 349.88965 299.7251 350.2253 Q 300.4287 350.56104 300.88794 351.15454 Q 301.34717 351.74805 301.48145 352.52686 L 300.2246 352.52686 Q 300.0044 351.82327 299.4807 351.41504 Q 298.95703 351.00684 298.16748 351.00684 Q 297.49072 351.00684 296.95093 351.3506 Q 296.41113 351.6943 296.09692 352.36304 Q 295.7827 353.03174 295.7827 353.99854 Q 295.7827 354.96533 296.09692 355.63135 Q 296.41113 356.29736 296.95898 356.6438 Q 297.50684 356.99023 298.21582 356.99023 Q 299.1665 356.99023 299.74927 356.42896 Q 300.33203 355.86768 300.35352 354.90625 L 298.39307 354.90625 L 298.39307 353.89648 L 301.55127 353.89648 L 301.55127 354.81494 Q 301.55127 355.8247 301.1189 356.56323 Q 300.68652 357.30173 299.93457 357.7046 Q 299.18262 358.10742 298.21045 358.10742 Z M 312.33936 358.10742 Q 311.30273 358.10742 310.48364 357.61328 Q 309.66455 357.11914 309.19458 356.20068 Q 308.7246 355.2822 308.7246 354.00388 Q 308.7246 352.7202 309.19458 351.7991 Q 309.66455 350.87793 310.48364 350.3838 Q 311.30273 349.88965 312.33936 349.88965 Q 313.37598 349.88965 314.1897 350.3838 Q 315.00342 350.87793 315.4734 351.7991 Q 315.94336 352.7202 315.94336 354.00388 Q 315.94336 355.2822 315.4734 356.20337 Q 315.00342 357.1245 314.1897 357.616 Q 313.37598 358.10742 312.33936 358.10742 Z M 312.33936 356.99023 Q 313.02148 356.99023 313.56128 356.6465 Q 314.10107 356.30273 314.41528 355.63672 Q 314.7295 354.9707 314.7295 354.00388 Q 314.7295 353.03174 314.41528 352.36304 Q 314.10107 351.6943 313.56128 351.3506 Q 313.02148 351.00684 312.33936 351.00684 Q 311.65186 351.00684 311.10938 351.3506 Q 310.5669 351.6943 310.2527 352.36304 Q 309.93848 353.03174 309.93848 354.00388 Q 309.93848 354.9707 310.2527 355.63672 Q 310.5669 356.30273 311.10938 356.6465 Q 311.65186 356.99023 312.33936 356.99023 Z M 318.93652 358 L 318.93652 349.99707 L 320.34912 349.99707 L 323.3623 354.76123 Q 323.5503 355.062 323.80273 355.51053 Q 324.05518 355.95898 324.32373 356.5337 Q 324.28076 355.9536 324.26196 355.42453 Q 324.24316 354.8955 324.24316 354.55176 L 324.24316 349.99707 L 325.4839 349.99707 L 325.4839 358 L 324.06592 358 L 321.36426 353.74072 Q 321.16553 353.4238 320.9856 353.1123 Q 320.80566 352.80078 320.59082 352.41135 Q 320.37598 352.02194 320.08594 351.48483 Q 320.12354 352.23682 320.1504 352.82764 Q 320.17725 353.41846 320.17725 353.72998 L 320.17725 358 Z M 328.35352 351.0498 L 328.35352 349.99707 L 334.56787 349.99707 L 334.56787 351.0498 L 332.08105 351.0498 L 332.08105 358 L 330.85107 358 L 330.85107 351.0498 Z M 340.43994 358.10742 Q 339.40332 358.10742 338.58423 357.61328 Q 337.76514 357.11914 337.29517 356.20068 Q 336.8252 355.2822 336.8252 354.00388 Q 336.8252 352.7202 337.29517 351.7991 Q 337.76514 350.87793 338.58423 350.3838 Q 339.40332 349.88965 340.43994 349.88965 Q 341.47656 349.88965 342.29028 350.3838 Q 343.104 350.87793 343.57397 351.7991 Q 344.0439 352.7202 344.0439 354.00388 Q 344.0439 355.2822 343.57397 356.20337 Q 343.104 357.1245 342.29028 357.616 Q 341.47656 358.10742 340.43994 358.10742 Z M 340.43994 356.99023 Q 341.12207 356.99023 341.66187 356.6465 Q 342.20166 356.30273 342.51587 35Line truncated
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.
Binary file not shown.
+158
View File
@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""Regenerate background.tiff and dmg-layout.DS_Store for the macOS DMG window (see README.md here).
Needs resvg on PATH and:
pip install 'pillow>=12' ds_store==1.3.3 mac_alias==2.2.3
Runs on any OS, no Mac needed. Both outputs come from this script alone, so they carry nothing
from the machine that made them: no local paths, user or disk names, dates or volume UUIDs.
python3 packaging/macos/dmg/generate.py
"""
import datetime
import os
import shutil
import subprocess
import sys
import tempfile
from ds_store import DSStore
from mac_alias import Alias, TargetInfo, VolumeInfo
from PIL import Image, ImageCms
HERE = os.path.dirname(os.path.abspath(__file__))
# Keep in sync with package.sh (volume name) and README.md (layout).
VOLUME = "GridCraft"
WIDTH, HEIGHT = 660, 400 # window content, pt; the background's 1x size
TITLE_BAR = 32
ICON_SIZE = 128
PALETTE = 16 # colours in background.tiff
ICONS = {"GridCraft.app": (326, 205), "Applications": (574, 205)}
# A fixed date for the alias and the colour profile. Finder never matches it against the image
# (each build is a new volume): it finds the background by volume name and path.
FIXED_DATE = datetime.datetime(2026, 10, 8, tzinfo=datetime.timezone.utc)
def render(svg, width, out):
# The SVG's text is already outlined and no fonts are loaded, so every machine draws the
# same pixels.
subprocess.run(
["resvg", "--skip-system-fonts", "-w", str(width), svg, out],
check=True,
)
with Image.open(out) as im:
return im.convert("RGB")
def quantize(im):
# No dithering: dither noise compresses badly and the artwork has flat fills.
return im.quantize(PALETTE, method=Image.Quantize.FASTOCTREE, dither=Image.Dither.NONE)
def srgb_profile():
# LittleCMS's built-in sRGB ("No copyright, use freely"), with its creation date fixed.
icc = bytearray(ImageCms.ImageCmsProfile(ImageCms.createProfile("sRGB")).tobytes())
d = FIXED_DATE
icc[24:36] = b"".join(v.to_bytes(2, "big") for v in (d.year, d.month, d.day, 0, 0, 0))
if icc[84:100] != bytes(16):
sys.exit("error: the sRGB profile has an ID; recompute it after fixing the date")
return bytes(icc)
def write_tiff(svg, out):
"""1x (72 dpi) and 2x (144 dpi) in one TIFF, as `tiffutil -cathidpicheck` writes them."""
with tempfile.TemporaryDirectory() as tmp:
one, two = (
quantize(render(svg, WIDTH * s, os.path.join(tmp, f"{s}x.png"))) for s in (1, 2)
)
two.encoderinfo = {"dpi": (144, 144)} # the rest as the first page
# Opaque 16-colour palette, Deflate: the artwork is three inks plus antialiasing, and a palette
# keeps the file under ~100 KB (repository assets must stay small). As PhotoCraft's.
one.save(
out,
"TIFF",
save_all=True,
append_images=[two],
compression="tiff_adobe_deflate", # brand-ok: Pillow's name for TIFF compression 8 (Deflate)
dpi=(72, 72),
icc_profile=srgb_profile(),
)
with Image.open(out) as im:
im.seek(1)
if im.info.get("dpi") != (144, 144):
sys.exit("error: the 2x page isn't 144 dpi (needs Pillow 12 or later)")
strips = []
for page in (0, 1):
im.seek(page)
strips += zip(im.tag_v2[273], im.tag_v2[279]) # StripOffsets, StripByteCounts
# TIFF keeps directories and tag data on even offsets. When a strip ends on an odd one, libtiff
# leaves the padding byte after it uninitialised, so the file changed from run to run with
# identical pixels. Zero it: nothing refers to that byte.
starts = {o for o, _ in strips}
with open(out, "r+b") as f:
for o, n in strips:
if (o + n) % 2 and o + n not in starts:
f.seek(o + n)
f.write(bytes(1))
def background_alias():
# What Finder stores for /.background/background.tiff on the volume, minus anything about the
# machine (dmgbuild writes the same kind of alias from a mounted image). Disk type 5 (ejectable),
# the volume flags and the folder/file ids (25, 26) are what Finder wrote on a test image; the ids
# are only hints, as the volume's creation date.
volume = VolumeInfo(VOLUME, FIXED_DATE, b"H+", 5, 0x0D02, b"\0\0")
volume.posix_path = f"/Volumes/{VOLUME}"
target = TargetInfo(0, "background.tiff", 25, 26, FIXED_DATE, b"\0\0\0\0", b"\0\0\0\0")
target.folder_name = ".background"
target.cnid_path = [25]
target.carbon_path = f"{VOLUME}:.background:\0background.tiff"
target.posix_path = "/.background/background.tiff"
return Alias(volume=volume, target=target).to_bytes()
def write_ds_store(out):
if os.path.exists(out):
os.remove(out)
with DSStore.open(out, "w+") as d:
d["."]["bwsp"] = {
"ShowStatusBar": False,
"ShowToolbar": False,
"ShowTabView": False,
"ContainerShowSidebar": False,
"ShowSidebar": False,
"WindowBounds": f"{{{{200, 528}}, {{{WIDTH}, {HEIGHT + TITLE_BAR}}}}}",
}
d["."]["icvp"] = {
"viewOptionsVersion": 1,
"backgroundType": 2,
"backgroundImageAlias": background_alias(),
"backgroundColorRed": 1.0,
"backgroundColorGreen": 1.0,
"backgroundColorBlue": 1.0,
"gridOffsetX": 0.0,
"gridOffsetY": 0.0,
"gridSpacing": 100.0,
"arrangeBy": "none",
"showIconPreview": True,
"showItemInfo": False,
"labelOnBottom": True,
"textSize": 16.0,
"iconSize": float(ICON_SIZE),
}
d["."]["vSrn"] = ("long", 1)
for name, pos in ICONS.items():
d[name]["Iloc"] = pos
def main():
if not shutil.which("resvg"):
sys.exit("error: resvg not found (brew install resvg / cargo install resvg --locked)")
write_tiff(os.path.join(HERE, "background.svg"), os.path.join(HERE, "background.tiff"))
write_ds_store(os.path.join(HERE, "dmg-layout.DS_Store"))
if __name__ == "__main__":
main()
+6 -1
View File
@@ -131,10 +131,15 @@ STAGE="$WORK/dmg"
mkdir -p "$STAGE"
ditto "$APP" "$STAGE/GridCraft.app"
ln -s /Applications "$STAGE/Applications"
# Finder window layout: background, icon size and positions (packaging/macos/dmg/README.md).
mkdir -p "$STAGE/.background"
cp "$HERE/dmg/background.tiff" "$STAGE/.background/background.tiff"
cp "$HERE/dmg/dmg-layout.DS_Store" "$STAGE/.DS_Store"
rm -f "$DMG" "$WORK/raw.dmg"
# makehybrid + convert builds the image without attaching a device, unlike `create -srcfolder`,
# which is flaky on CI runners ("Resource busy") and hangs in sandboxed sessions.
hdiutil makehybrid -hfs -hfs-volume-name "GridCraft $VERSION" -hfs-openfolder "$STAGE" -o "$WORK/raw.dmg" "$STAGE"
# The volume name has no version: .DS_Store finds the background through an alias that includes it.
hdiutil makehybrid -hfs -hfs-volume-name "GridCraft" -hfs-openfolder "$STAGE" -o "$WORK/raw.dmg" "$STAGE"
hdiutil convert "$WORK/raw.dmg" -format UDZO -imagekey zlib-level=9 -o "$DMG"
rm -f "$WORK/raw.dmg"
sign "$DMG"