macOS DMG: branded window background and icon layout (#1201)

The DMG opens on a 660x400 Finder window with the PhotoCraft artwork, 128 pt icons and the
Applications link beside the app. packaging/macos/dmg/ holds the SVG source, a reproducible
generator, an 87 KB HiDPI palette TIFF (was 1.0 MB) and the .DS_Store layout. The volume is
named "PhotoCraft" (the background alias resolves by 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 authored and GitHub committed 2026-10-08 23:11:00 -04:00
1 parent f0ae12dd22
commit bfc59c784f
9 files changed
+241 -1

No files matched your search

+1
View File
@@ -10,3 +10,4 @@ target/
/book/output
/craft-fonts
*.log
__pycache__/
+1
View File
@@ -26,6 +26,7 @@ next to them.
| `assets/dict/en_US-scowl-50.txt.gz` | English word list for Check Spelling (SCOWL 2020.12.07, size 50, en_US; 72,403 words) | Kevin Atkinson and the SCOWL contributors | <http://wordlist.aspell.net/> | SCOWL license (permissive, keep the notice), [`assets/dict/LICENSE-SCOWL.txt`](assets/dict/LICENSE-SCOWL.txt), which also has the build recipe |
| `crates/cms/profiles/photocraft-coated-cmyk.icc` | PhotoCraft Coated CMYK (synthetic) ICC profile | PhotoCraft contributors (generated by `photocraft-cms`, `src/synth.rs`) | Original work, see [`crates/cms/README.md`](crates/cms/README.md) | CC0-1.0 |
| `assets/app-icon/` (all files) | PhotoCraft app icon (nine-tailed kitsune) | The project owner (drawn in ArtCraft, vectorised) | Original work, see [`assets/app-icon/README.md`](assets/app-icon/README.md) | MIT OR Apache-2.0, [`assets/app-icon/LICENSE.txt`](assets/app-icon/LICENSE.txt) |
| `packaging/macos/dmg/` (all files) | PhotoCraft DMG window background and layout | @XusBadia, from the PhotoCraft app icon | Original work, see [`packaging/macos/dmg/README.md`](packaging/macos/dmg/README.md) | MIT OR Apache-2.0, like the code |
### Optional build input: craft-fonts (not files in this repo)
+3
View File
@@ -85,6 +85,9 @@ in `ci.yml`. Rules: `../craftrules/standards/fonts.md`; build option: `docs/deve
ticket is stapled to the app. The app goes on a DMG (`hdiutil`, with an `Applications` link
to drag onto). The DMG is signed, notarized and stapled too. The script checks the results
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 `PhotoCraft`
without the version, which the window's background needs; the DMG file name keeps the version.
- **CLI:** the universal `photocraft-cli` is signed with the same Developer ID, the hardened
runtime and a secure timestamp (identifier `ai.storyteller.photocraft-cli`), zipped, and the zip
is sent to `notarytool`. Only `.app`, `.dmg` and `.pkg` can hold a stapled ticket, not a bare
+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/photocraft-small.svg`, linked, not copied) cropped as a cover on the PhotoCraft colour field (`#2f7bf5`), Ink and Paper. Its text (Inter, JetBrains Mono) is outlined, so rendering it needs no fonts. |
| `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; about 90 KB). Goes to `.background/background.tiff`. |
| `dmg-layout.DS_Store` | Finder's view settings for the volume: window size, icon size 128, PhotoCraft.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 `PhotoCraft`, not `PhotoCraft <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 (`photocraft-<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).
+28
View File
@@ -0,0 +1,28 @@
<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></defs>
<rect width="660" height="400" fill="#efe9dc"/>
<g clip-path="url(#panel)"><rect width="232" height="400" fill="#2f7bf5"/><g transform="translate(-47.5,-21.8) scale(1.12)"><image href="../../../assets/app-icon/photocraft-small.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="#2f7bf5"/>
<path d="M408,204 L465,200 L463,189 L492,205 L463,221 L465,210 L408,206 Z" fill="url(#hatch)"/><path d="M408,204 L465,200 L463,189 L492,205 L463,221 L465,210 L408,206 Z" fill="none" stroke="#0b0b0c" stroke-width="0.9" stroke-linejoin="round"/>
<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 268.19727 72 L 268.19727 50.17383 L 276.35645 50.17383 Q 278.86133 50.17383 280.5752 51.11133 Q 282.28906 52.04883 283.16797 53.68213 Q 284.04688 55.31543 284.04688 57.395508 Q 284.04688 59.49023 283.16064 61.12353 Q 282.2744 62.75684 280.5459 63.687008 Q 278.81738 64.61719 276.29785 64.61719 L 272.1084 64.61719 L 272.1084 72 Z M 272.1084 61.394527 L 275.78516 61.394527 Q 277.99707 61.394527 279.02246 60.266605 Q 280.04785 59.13867 280.04785 57.395508 Q 280.04785 55.65234 279.02246 54.55371 Q 277.99707 53.45508 275.75586 53.45508 L 272.1084 53.45508 Z M 290.68066 62.405273 L 290.68066 72 L 286.87207 72 L 286.87207 50.17383 L 290.62207 50.17383 L 290.62207 58.655273 Q 292.1162 55.41797 295.5879 55.41797 Q 298.12207 55.41797 299.66016 57.0293 Q 301.19824 58.640625 301.19824 61.58496 L 301.19824 72 L 297.375 72 L 297.375 62.170902 Q 297.375 60.530273 296.5254 59.592777 Q 295.67578 58.655273 294.18164 58.655273 Q 292.6582 58.655273 291.66943 59.63672 Q 290.68066 60.618164 290.68066 62.405273 Z M 311.8457 72.336914 Q 309.458 72.336914 307.7002 71.2749 Q 305.94238 70.21289 304.9829 68.31592 Q 304.02344 66.418945 304.02344 63.899414 Q 304.02344 61.350586 304.9829 59.438965 Q 305.94238 57.527344 307.7002 56.472656 Q 309.458 55.41797 311.8457 55.41797 Q 314.2334 55.41797 315.9912 56.472656 Q 317.74902 57.527344 318.71582 59.438965 Q 319.68262 61.350586 319.68262 63.899414 Q 319.68262 66.418945 318.71582 68.31592 Q 317.74902 70.21289 315.9912 71.2749 Q 314.2334 72.336914 311.8457 72.336914 Z M 311.8457 69.24609 Q 313.1787 69.24609 314.05762 68.52832 Q 314.93652 67.81055 315.37598 66.59473 Q 315.81543 65.37891 315.81543 63.884766 Q 315.81543 62.375977 315.37598 61.160156 Q 314.93652 59.944336 314.05762 59.226563 Q 313.1787 58.50879 311.8457 58.50879 Q 310.52734 58.50879 309.64844 59.226563 Q 308.76953 59.944336 308.3374 61.160156 Q 307.90527 62.375977 307.90527 63.884766 Q 307.90527 65.37891 308.3374 66.59473 Q 308.76953 67.81055 309.64844 68.52832 Q 310.52734 69.24609 311.8457 69.24609 Z M 330.08105 55.623047 L 330.08105 58.61133 L 326.917 58.61133 L 326.917 67.38574 Q 326.917 68.2793 327.29053 68.69678 Q 327.66406 69.11426 328.54297 69.11426 Q 328.80664 69.11426 329.23145 69.04834 Q 329.65625 68.98242 329.93457 68.92383 L 330.53516 71.868164 Q 329.90527 72.05859 329.2461 72.13916 Q 328.5869 72.21973 327.98633 72.21973 Q 325.62793 72.21973 324.36816 71.04785 Q 323.1084 69.87598 323.1084 67.70801 L 323.1084 58.61133 L 320.7793 58.61133 L 320.7793 55.623047 L 323.1084 55.623047 L 323.1084 51.726563 L 326.917 51.726563 L 326.917 55.623047 Z M 339.41016 72.336914 Q 337.02246 72.336914 335.26465 71.2749 Q 333.50684 70.21289 332.54736 68.31592 Q 331.5879 66.418945 331.5879 63.899414 Q 331.5879 61.350586 332.54736 59.438965 Q 333.50684 57.527344 335.26465 56.472656 Q 337.02246 55.41797 339.41016 55.41797 Q 341.79785 55.41797 343.55566 56.472656 Q 345.31348 57.527344 346.28027 59.438965 Q 347.24707 61.350586 347.24707 63.899414 Q 347.24707 66.418945 346.28027 68.31592 Q 345.31348 70.21289 343.55566 71.2749 Q 341.79785 72.336914 339.41016 72.336914 Z M 339.41016 69.24609 Q 340.74316 69.24609 341.62207 68.52832 Q 342.50098 67.81055 342.94043 66.59473 Q 343.37988 65.37891 343.37988 63.884766 Q 343.37988 62.375977 342.94043 61.160156 Q 342.50098 59.944336 341.62207 59.226563 Q 340.74316 58.50879 339.41016 58.50879 Q 338.0918 58.50879 337.2129 59.226563 Q 336.33398 59.944336 335.90186 61.160156 Q 335.46973 62.375977 335.46973 63.884766 Q 335.46973 65.37891 335.90186 66.59473 Q 336.33398 67.81055 337.2129 68.52832 Q 338.0918 69.24609 339.41016 69.24609 Z M 359.54977 72.29297 Q 356.6787 72.29297 354.42285 70.95996 Q 352.167 69.62695 350.86328 67.12207 Q 349.55957 64.61719 349.55957 61.101563 Q 349.55957 57.571285 350.8706 55.059082 Q 352.18167 52.54688 354.4375 51.213863 Q 356.69336 49.88086 359.54977 49.88086 Q 361.9375 49.88086 363.89307 50.774414 Q 365.8486 51.66797 367.12305 53.359863 Q 368.39746 55.051754 368.74902 57.439453 L 364.8086 57.439453 Q 364.44238 55.50586 363.00687 54.443848 Q 361.5713 53.381836 359.6084 53.381836 Q 356.92773 53.381836 355.20654 55.381344 Q 353.48532 57.38086 353.48532 61.101563 Q 353.48532 64.86621 355.2139 66.8291 Q 356.94238 68.79199 359.59375 68.79199 Q 361.5566 68.79199 362.9995 67.72998 Q 364.44238 66.66797 364.8086 64.734375 L 368.76367 64.734375 Q 368.45605 66.8291 367.25488 68.54297 Q 366.0537 70.256836 364.09082 71.2749 Q 362.12793 72.29297 359.54977 72.29297 Z M 371.6768 72 L 371.6768 55.623047 L 375.36816 55.623047 L 375.36816 58.362305 L 375.54395 58.362305 Q 375.9834 56.941402 377.08203 56.172363 Q 378.18066 55.40332 379.5869 55.40332 Q 380.31934 55.40332 380.97852 55.50586 L 380.97852 59.006836 Q 380.74414 58.948246 380.2168 58.882324 Q 379.68945 58.816406 379.17676 58.816406 Q 377.5801 58.816406 376.5327 59.805172 Q 375.48535 60.79394 375.48535 62.36133 L 375.48535 72 Z M 387.37793 72.336914 Q 385.82516 72.336914 384.58008 71.77295 Q 3Line truncated
</g>
<g>
<path fill="#0b0b0c" fill-opacity="0.7" stroke="none" d="M 267 113.619995 L 267 112.882 L 267.945 112.882 Q 268.422 112.882 268.701 112.6075 Q 268.98 112.33301 268.98 111.865 L 268.98 109.183 L 267.54 109.183 L 267.54 108.445 L 268.98 108.445 L 268.98 106.68099 Q 268.98 106.08699 269.3445 105.75849 Q 269.709 105.42999 270.366 105.42999 L 271.815 105.42999 L 271.815 106.15 L 270.366 106.15 Q 269.79 106.15 269.79 106.69001 L 269.79 108.445 L 271.815 108.445 L 271.815 109.183 L 269.79 109.183 L 269.79 111.865 Q 269.79 112.657 269.286 113.1385 Q 268.782 113.619995 267.945 113.619995 Z M 273.07498 112.98999 L 276.27 104.53 L 277.125 104.53 L 273.93 112.98999 Z M 278.57397 112 L 278.57397 111.253006 L 280.761 108.913 Q 281.23798 108.409 281.43597 108.01301 Q 281.634 107.617 281.634 107.23899 Q 281.634 106.699 281.3235 106.3795 Q 281.013 106.06 280.482 106.06 Q 279.93298 106.06 279.6135 106.388504 Q 279.29398 106.717 279.29398 107.275 L 278.48398 107.275 Q 278.511 106.384 279.051 105.862 Q 279.59097 105.34 280.482 105.34 Q 281.391 105.34 281.92648 105.853 Q 282.46198 106.36599 282.46198 107.24801 Q 282.46198 107.716 282.20547 108.238 Q 281.94897 108.76 281.328 109.408 L 279.555 111.262 L 282.543 111.262 L 282.543 112 Z M 285.90002 112.09 Q 285.56702 112.09 285.3645 111.892 Q 285.16202 111.694 285.16202 111.379 Q 285.16202 111.046 285.3645 110.839 Q 285.56702 110.63201 285.90002 110.63201 Q 286.233 110.63201 286.43552 110.839 Q 286.638 111.046 286.638 111.379 Q 286.638 111.694 286.43552 111.892 Q 286.233 112.09 285.90002 112.09 Z M 291.30002 112.09 Q 290.67902 112.09 290.202 111.84701 Q 289.725 111.604 289.4595 111.167496 Q 289.194 110.730995 289.194 110.164 Q 289.194 109.54301 289.5495 109.09301 Q 289.905 108.643 290.499 108.544 L 290.499 108.517 Q 289.986 108.4 289.68 107.9995 Q 289.374 107.599 289.374 107.07701 Q 289.374 106.564 289.617 106.1725 Q 289.86002 105.781006 290.2965 105.5605 Q 290.733 105.34 291.30002 105.34 Q 291.876 105.34 292.308 105.5605 Q 292.74002 105.781006 292.983 106.1725 Q 293.226 106.564 293.226 107.07701 Q 293.226 107.599 292.92 107.9995 Q 292.614 108.4 292.101 108.517 L 292.101 108.544 Q 292.695 108.643 293.0505 109.09301 Q 293.406 109.54301 293.406 110.164 Q 293.406 110.730995 293.1405 111.167496 Q 292.875 111.604 292.4025 111.84701 Q 291.93 112.09 291.30002 112.09 Z M 291.30002 108.202 Q 291.80402 108.202 292.11002 107.90501 Q 292.41602 107.608 292.41602 107.13099 Q 292.41602 106.65401 292.11002 106.357 Q 291.80402 106.06 291.30002 106.06 Q 290.805 106.06 290.4945 106.357 Q 290.18402 106.65401 290.18402 107.13099 Q 290.18402 107.608 290.4945 107.90501 Q 290.805 108.202 291.30002 108.202 Z M 291.30002 111.361 Q 291.876 111.361 292.236 111.023506 Q 292.596 110.68599 292.596 110.146 Q 292.596 109.597 292.236 109.2595 Q 291.876 108.922 291.30002 108.922 Q 290.724 108.922 290.364 109.2595 Q 290.004 109.597 290.004 110.146 Q 290.004 110.68599 290.364 111.023506 Q 290.724 111.361 291.30002 111.361 Z M 302.1 109.66 Q 301.767 109.66 301.56448 109.462 Q 301.362 109.26399 301.362 108.949005 Q 301.362 108.616 301.56448 108.409 Q 301.767 108.202 302.1 108.202 Q 302.43298 108.202 302.6355 108.409 Q 302.83798 108.616 302.83798 108.949005 Q 302.83798 109.26399 302.6355 109.462 Q 302.43298 109.66 302.1 109.66 Z M 311.01 112 L 311.01 111.262 L 312.79202 111.262 L 312.79202 106.131996 L 311.01 107.464 L 311.01 106.537 L 312.49503 105.42999 L 313.60202 105.42999 L 313.60202 111.262 L 315.06 111.262 L 315.06 112 Z M 316.275 112.98999 L 319.47 104.53 L 320.325 104.53 L 317.13 112.98999 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.17398 112 L 327.17398 111.253006 L 329.361 108.913 Q 329.83798 108.409 330.03598 108.01301 Q 330.234 107.617 330.234 107.23899 Q 330.234 106.699 329.9235 106.3795 Q 329.613 106.06 329.082 106.06 Q 328.533 106.06 328.2135 106.388504 Q 327.89398 106.717 327.89398 107.275 L 327.08398 107.275 Q 327.111 106.384 327.651 105.862 Q 328.19098 105.34 329.082 105.34 Q 329.991 105.34 330.5265 105.853 Q 331.06198 106.36599 331.06198 107.24801 Q 331.06198 107.716 330.80548 108.238 Q 330.54898 108.76 329.92798 109.408 L 328.155 111.262 L 331.143 111.262 L 331.143 112 Z M 334.44598 112.09 Q 333.537 112.09 333.00598 111.581505 Q 332.47498 111.07299 332.47498 110.19999 L 333.28497 110.19999 Q 333.28497 110.74 333.59998 111.055 Q 333.91498 111.37 334.455 111.37 Q 334.995 111.37 335.31 111.055 Q 335.625 110.74 335.625 110.19999 L 335.625 109.795 Q 335.625 109.255 335.319 108.94 Q 335.013 108.625 334.491 108.625 L 332.69998 108.625 L 332.69998 105.42999 L 336.201 105.42999 L 336.201 106.168 L 333.456 106.168 L 333.474 107.887 L 334.53598 107.887 Q 335.44498 107.887 335.94 108.3865 Q 336.435 108.88601 336.435 109.795 L 336.435 110.19999 Q 336.435 111.07299 335.89948 111.581505 Q 335.36398 112.09 334.44598 112.09 Z M 345.10202 112.072 Q 344.27Line 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: 34 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 = "PhotoCraft"
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 = {"PhotoCraft.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 ~90 KB (RGB was ~200 KB; repository assets must stay small).
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
@@ -134,10 +134,15 @@ STAGE="$WORK/dmg"
mkdir -p "$STAGE"
ditto "$APP" "$STAGE/PhotoCraft.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 "PhotoCraft $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 "PhotoCraft" -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"