Merge pull request #131 from XusBadia/dmg-background

macOS DMG: branded window background and icon layout
This commit is contained in:
Brandon Thomas authored and GitHub committed 2026-10-09 09:20:37 -04:00
commit 66c7ce7e96
7 files changed
+241 -1

No files matched your search

+2
View File
@@ -60,3 +60,5 @@ Generated-in-code art is original and has no file to list: the UI icon set (`cra
| `docs/brand/artcraft-mark.svg`, `docs/brand/artcraft-mark.png` | ArtCraft / Storyteller | ArtCraft brand kit (project owner) | ArtCraft trademark, see docs/brand/LICENSE-brand.txt (not open source) | The mark; its outline is drawn in code by `crates/ui-egui/src/about.rs` (About splash, start screen) |
| `docs/brand/artcraft-mark-black.svg`, `docs/brand/artcraft-mark-black.png` | ArtCraft / Storyteller | ArtCraft brand kit (project owner) | ArtCraft trademark, see docs/brand/LICENSE-brand.txt (not open source) | Mark for light backgrounds |
| `docs/brand/LICENSE-brand.txt` | (licence text) | ArtCraft Team | — | Terms for the ArtCraft Marks in `docs/brand/` |
| `packaging/macos/dmg/background.svg` | @XusBadia | original work for this repository, derived from the DesignCraft app icon (`assets/app-icon/designcraft.svg`, referenced via `<image>`, not copied); text is outlined Inter / JetBrains Mono glyph paths | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | DesignCraft macOS DMG window background for the Finder window; `packaging/macos/dmg/generate.py` renders it to `background.tiff` (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") |
+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/designcraft.svg`, linked, not copied) cropped as a cover on the DesignCraft colour field (`#7bb51c`), 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, DesignCraft.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 `DesignCraft`, not `DesignCraft <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 (`designcraft-<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="#7bb51c"/><g transform="translate(-134.0,-40.0) scale(1.0)"><image href="../../../assets/app-icon/designcraft.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="#7bb51c"/>
<rect x="408" y="198" width="14" height="14" fill="#efe9dc" stroke="#0b0b0c" stroke-width="1.3"/><path d="M412,201.5 L418,205 L412,208.5 Z" fill="#7bb51c"/><line x1="424" y1="205" x2="476" y2="205" stroke="#7bb51c" stroke-width="1.6" stroke-dasharray="4 3"/><rect x="478" y="198" width="14" height="14" fill="#efe9dc" stroke="#0b0b0c" stroke-width="1.3"/><path d="M482,201.5 L488,205 L482,208.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 275.58008 72 L 268.19727 72 L 268.19727 50.17383 L 275.72656 50.17383 Q 278.97852 50.17383 281.31494 51.484863 Q 283.65137 52.7959 284.89648 55.234863 Q 286.1416 57.673832 286.1416 61.057617 Q 286.1416 64.4707 284.88916 66.92432 Q 283.63672 69.37793 281.271 70.688965 Q 278.90527 72 275.58008 72 Z M 272.1084 68.61621 L 275.38965 68.61621 Q 282.28906 68.61621 282.28906 61.057617 Q 282.28906 53.557617 275.4922 53.557617 L 272.1084 53.557617 Z M 296.42285 72.336914 Q 293.94727 72.336914 292.16748 71.296875 Q 290.3877 70.256836 289.42822 68.36719 Q 288.46875 66.47754 288.46875 63.914063 Q 288.46875 61.394527 289.41357 59.48291 Q 290.3584 57.571285 292.09424 56.49463 Q 293.83008 55.41797 296.17383 55.41797 Q 298.16602 55.41797 299.87256 56.28955 Q 301.5791 57.161133 302.61914 58.984863 Q 303.65918 60.80859 303.65918 63.679688 L 303.65918 64.88086 L 292.2627 64.88086 Q 292.33594 67.06348 293.48584 68.206055 Q 294.63574 69.34863 296.4668 69.34863 Q 297.72656 69.34863 298.62744 68.80664 Q 299.52832 68.26465 299.90918 67.22461 L 303.41016 67.94238 Q 302.82422 69.91992 300.98584 71.12842 Q 299.14746 72.336914 296.42285 72.336914 Z M 292.27734 62.288086 L 299.95313 62.288086 Q 299.77734 60.530273 298.83252 59.46826 Q 297.8877 58.40625 296.18848 58.40625 Q 294.4453 58.40625 293.42725 59.526855 Q 292.40918 60.64746 292.27734 62.288086 Z M 312.62207 72.336914 Q 309.79492 72.336914 307.95654 71.12842 Q 306.11816 69.91992 305.69336 67.69336 L 309.25293 67.01953 Q 309.89746 69.421875 312.6953 69.421875 Q 314.10156 69.421875 314.9292 68.85791 Q 315.75684 68.293945 315.75684 67.458984 Q 315.75684 66.06738 313.54492 65.583984 L 310.74707 64.9834 Q 306.14746 63.9873 306.14746 60.398438 Q 306.14746 58.874996 306.98242 57.76172 Q 307.81738 56.648438 309.3042 56.033203 Q 310.79102 55.41797 312.7539 55.41797 Q 315.5371 55.41797 317.15576 56.575195 Q 318.7744 57.73242 319.27246 59.63672 L 315.87402 60.310547 Q 315.58105 59.460938 314.84863 58.860348 Q 314.1162 58.25976 312.79785 58.25976 Q 311.59668 58.25976 310.77637 58.794434 Q 309.95605 59.3291 309.95605 60.16406 Q 309.95605 60.88184 310.49072 61.34326 Q 311.0254 61.804688 312.28516 62.083004 L 315.06836 62.66894 Q 319.65332 63.65039 319.65332 67.09277 Q 319.65332 68.64551 318.75977 69.82471 Q 317.8662 71.00391 316.27686 71.67041 Q 314.6875 72.336914 312.62207 72.336914 Z M 322.40527 72 L 322.40527 55.623047 L 326.21387 55.623047 L 326.21387 72 Z M 324.30957 53.308594 Q 323.40137 53.308594 322.7495 52.700684 Q 322.09766 52.092773 322.09766 51.213863 Q 322.09766 50.34961 322.7495 49.7417 Q 323.40137 49.13379 324.30957 49.13379 Q 325.23242 49.13379 325.88428 49.7417 Q 326.53613 50.34961 326.53613 51.213863 Q 326.53613 52.092773 325.88428 52.700684 Q 325.23242 53.308594 324.30957 53.308594 Z M 336.81738 78.47461 Q 333.8584 78.47461 332.0713 77.390625 Q 330.28418 76.30664 329.66895 74.680664 L 332.8916 73.52344 Q 333.27246 74.22656 334.14404 74.90039 Q 335.01563 75.57422 336.80273 75.57422 Q 338.5752 75.57422 339.64453 74.74658 Q 340.71387 73.918945 340.71387 72.16113 L 340.71387 69.07031 L 340.3916 69.07031 Q 340.09863 69.65625 339.55664 70.28613 Q 339.01465 70.916016 338.10645 71.333496 Q 337.19824 71.75098 335.77734 71.75098 Q 333.90234 71.75098 332.37158 70.864746 Q 330.84082 69.978516 329.93994 68.206055 Q 329.03906 66.43359 329.03906 63.76758 Q 329.03906 61.086914 329.93994 59.219234 Q 330.84082 57.351563 332.3789 56.38477 Q 333.917 55.41797 335.80664 55.41797 Q 337.25684 55.41797 338.19434 55.901367 Q 339.13184 56.38477 339.68848 57.065918 Q 340.24512 57.74707 340.5381 58.33301 L 340.74316 58.33301 L 340.74316 55.623047 L 344.47852 55.623047 L 344.47852 72.043945 Q 344.47852 74.24121 343.48242 75.66211 Q 342.48633 77.08301 340.7578 77.77881 Q 339.0293 78.47461 336.81738 78.47461 Z M 336.84668 68.74805 Q 338.70703 68.74805 339.71777 67.41504 Q 340.72852 66.08203 340.72852 63.73828 Q 340.72852 61.409176 339.73242 59.980957 Q 338.73633 58.552734 336.84668 58.552734 Q 334.9131 58.552734 333.92432 60.032227 Q 332.93555 61.511723 332.93555 63.73828 Q 332.93555 66.00879 333.93164 67.37842 Q 334.92773 68.74805 336.84668 68.74805 Z M 351.8301 62.405273 L 351.8301 72 L 348.02148 72 L 348.02148 55.623047 L 351.625 55.623047 L 351.66898 58.889645 Q 353.13376 55.41797 356.7373 55.41797 Q 359.27145 55.41797 360.80228 57.0293 Q 362.333 58.640625 362.333 61.58496 L 362.333 72 L 358.52438 72 L 358.52438 62.170902 Q 358.52438 60.530273 357.6748 59.592777 Q 356.82523 58.655273 355.3311 58.655273 Q 353.80762 58.655273 352.81882 59.63672 Q 351.8301 60.618164 351.8301 62.405273 Z M 375.35352 72.29297 Q 372.48242 72.29297 370.22653 70.95996 Q 367.9707 69.62695 366.667 67.12207 Q 365.36328 64.61719 365.36328 61.101563 Q 365.36328 57.571285 366.67432 55.059082 Q 367.98535 52.54688 370.2412 51.213863 Q 372.49707 49.88086 375.35352 49.88086 Q 377.74124 49.88086 379.69678 50.774414 Q 381.65234 51.66797 382.92676 53.359863 Q 384.20114 55.051754 384.5527 57.4Line truncated
</g>
<g>
<path fill="#0b0b0c" fill-opacity="0.7" stroke="none" d="M 267.81 112 L 267.81 111.262 L 269.592 111.262 L 269.592 106.131996 L 267.81 107.464 L 267.81 106.537 L 269.295 105.42999 L 270.402 105.42999 L 270.402 111.262 L 271.86 111.262 L 271.86 112 Z M 273.17398 112 L 273.17398 111.253006 L 275.361 108.913 Q 275.83798 108.409 276.03598 108.01301 Q 276.234 107.617 276.234 107.23899 Q 276.234 106.699 275.9235 106.3795 Q 275.613 106.06 275.082 106.06 Q 274.533 106.06 274.2135 106.388504 Q 273.89398 106.717 273.89398 107.275 L 273.08398 107.275 Q 273.111 106.384 273.651 105.862 Q 274.19098 105.34 275.082 105.34 Q 275.991 105.34 276.5265 105.853 Q 277.06198 106.36599 277.06198 107.24801 Q 277.06198 107.716 276.80548 108.238 Q 276.54898 108.76 275.92798 109.408 L 274.155 111.262 L 277.143 111.262 L 277.143 112 Z M 285.97202 112.09 Q 285.07202 112.09 284.532 111.586 Q 283.992 111.082 283.992 110.19999 L 283.992 108.85 Q 283.992 107.968 284.532 107.464 Q 285.07202 106.95999 285.97202 106.95999 Q 286.82703 106.95999 287.35352 107.41901 Q 287.88 107.878 287.907 108.670006 L 287.09702 108.670006 Q 287.07 108.193 286.773 107.9365 Q 286.476 107.68 285.97202 107.68 Q 285.441 107.68 285.12152 107.9815 Q 284.802 108.28299 284.802 108.841 L 284.802 110.19999 Q 284.802 110.758 285.12152 111.064 Q 285.441 111.37 285.97202 111.37 Q 286.476 111.37 286.773 111.10899 Q 287.07 110.848 287.09702 110.380005 L 287.907 110.380005 Q 287.88 111.172 287.35352 111.631004 Q 286.82703 112.09 285.97202 112.09 Z M 291.30002 112.072 Q 290.409 112.072 289.8825 111.55 Q 289.35602 111.028 289.35602 110.092 L 289.35602 108.958 Q 289.35602 108.01301 289.87802 107.4955 Q 290.4 106.978 291.30002 106.978 Q 292.2 106.978 292.72202 107.4955 Q 293.24402 108.01301 293.24402 108.958 L 293.24402 110.092 Q 293.24402 111.028 292.7175 111.55 Q 292.191 112.072 291.30002 112.072 Z M 291.30002 111.35199 Q 291.831 111.35199 292.1325 111.055 Q 292.43402 110.758 292.43402 110.182 L 292.43402 108.868 Q 292.43402 108.29201 292.1325 107.995 Q 291.831 107.698006 291.30002 107.698006 Q 290.778 107.698006 290.47202 107.995 Q 290.16602 108.29201 290.16602 108.868 L 290.16602 110.182 Q 290.16602 110.758 290.47202 111.055 Q 290.778 111.35199 291.30002 111.35199 Z M 297.42 112 Q 296.763 112 296.3715 111.61751 Q 295.98 111.235 295.98 110.605 L 295.98 106.168 L 294.27 106.168 L 294.27 105.42999 L 296.79 105.42999 L 296.79 110.605 Q 296.79 110.911 296.961 111.0865 Q 297.132 111.262 297.42 111.262 L 298.95 111.262 L 298.95 112 Z M 302.091 112.09 Q 301.236 112.09 300.723 111.581505 Q 300.21 111.07299 300.21 110.19999 L 300.21 107.05 L 301.02 107.05 L 301.02 110.19999 Q 301.02 110.74 301.30798 111.059494 Q 301.596 111.379 302.091 111.379 Q 302.595 111.379 302.88748 111.059494 Q 303.18 110.74 303.18 110.19999 L 303.18 107.05 L 303.99 107.05 L 303.99 110.19999 Q 303.99 111.07299 303.468 111.581505 Q 302.94598 112.09 302.091 112.09 Z M 305.34 112 L 305.34 107.05 L 306.042 107.05 L 306.042 107.707 L 306.06 107.707 Q 306.10498 107.374 306.3255 107.167 Q 306.546 106.95999 306.888 106.95999 Q 307.21198 106.95999 307.43698 107.149 Q 307.662 107.338 307.761 107.661995 L 307.77 107.661995 Q 307.83298 107.338 308.0535 107.149 Q 308.274 106.95999 308.616 106.95999 Q 309.093 106.95999 309.3765 107.3155 Q 309.65997 107.671 309.65997 108.238 L 309.65997 112 L 308.904 112 L 308.904 108.229004 Q 308.904 107.932 308.75998 107.7565 Q 308.616 107.581 308.36398 107.581 Q 308.121 107.581 307.977 107.752 Q 307.83298 107.923004 307.83298 108.21999 L 307.83298 112 L 307.167 112 L 307.167 108.229004 Q 307.167 107.932 307.02298 107.7565 Q 306.879 107.581 306.636 107.581 Q 306.384 107.581 306.24 107.752 Q 306.09598 107.923004 306.09598 108.21999 L 306.09598 112 Z M 311.028 112 L 311.028 107.05 L 311.838 107.05 L 311.838 107.995 L 311.84702 107.995 Q 311.91 107.5 312.252 107.23 Q 312.59402 106.95999 313.161 106.95999 Q 313.90802 106.95999 314.349 107.41901 Q 314.79 107.878 314.79 108.670006 L 314.79 112 L 313.98 112 L 313.98 108.813995 Q 313.98 108.247 313.6965 107.9455 Q 313.41302 107.644 312.927 107.644 Q 312.423 107.644 312.13052 107.959 Q 311.838 108.274 311.838 108.85 L 311.838 112 Z M 318.10202 112.072 Q 317.27402 112.072 316.81952 111.6985 Q 316.36502 111.325 316.36502 110.641 L 317.17502 110.641 Q 317.17502 110.983 317.418 111.1765 Q 317.661 111.37 318.10202 111.37 L 318.49802 111.37 Q 318.948 111.37 319.1955 111.172 Q 319.443 110.97401 319.443 110.614 Q 319.443 109.975 318.858 109.894 L 317.49002 109.696 Q 316.986 109.624 316.707 109.277504 Q 316.428 108.931 316.428 108.364 Q 316.428 107.716 316.8735 107.34699 Q 317.319 106.978 318.10202 106.978 L 318.49802 106.978 Q 319.21802 106.978 319.67252 107.333496 Q 320.127 107.689 320.163 108.265 L 319.344 108.265 Q 319.32602 108.00399 319.0965 107.83299 Q 318.867 107.661995 318.49802 107.661995 L 318.10202 107.661995 Q 317.68802 107.661995 317.454 107.851 Q 317.22 108.04 317.22 108.364 Q 317.22 108.88601 317.697 108.958 L 318.975 109.138 Q 320.23502 Line 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: 41 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 = "DesignCraft"
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 = {"DesignCraft.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/DesignCraft.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 "DesignCraft $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 "DesignCraft" -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"