7 Commits
Author SHA1 Message Date
Brandon ThomasandClaude Opus 5.5 43e33add44 Bump version to 0.4.0 (from 0.3.0) for release; refresh contributors
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 21:01:59 -04:00
Brandon Thomas 86f3199b40 Merge pull request #13 from XusBadia/dmg-background
macOS DMG: branded window background and icon layout
2026-10-09 09:21:09 -04:00
Xus BadiaandClaude Opus 5.5 15c35620c4 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 CADCraft colour, with CADCraft.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; ~21 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 <[email protected]>
2026-10-09 07:43:54 +02:00
Brandon ThomasandClaude Opus 5.5 59631c8d4f Bump version to 0.3.0 (from 0.2.0) for release
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 10:02:20 -04:00
ab154a5552 README Downloads: where to get it first, then builds with Windows before macOS (#19)
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-08 09:50:35 -04:00
7c7c1f9a3d Release: Flatpak x86_64/aarch64, AppImage zsync updates, README Downloads (#16)
* Release: Flatpak x86_64/aarch64, AppImage zsync updates, README Downloads

- AppImages embed gh-releases-zsync update information and ship a .zsync beside them.
- New flatpak job repackages each Linux tarball into cadcraft-<ver>-linux-<arch>.flatpak
  (bundle manifest + flatpak-bundle.sh); the from-source Flathub manifest moves to runtime
  26.08 with the same commented, minimal finish-args (packaging-lint checks they agree).
- Jobs that sign nothing (version, linux, flatpak, freebsd, web) leave the release
  environment, so a branch dispatch is a dry run; the draft-release job runs only on release.
- README: Downloads section listing every build.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Release: check AppImage update info without extract-and-run; fix the deb/rpm description

APPIMAGE_EXTRACT_AND_RUN makes the runtime pass --appimage-updateinformation to the app.
The package description was PhotoCraft's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-08 09:40:49 -04:00
Brandon ThomasandClaude Opus 5.5 834fc19b4c Bump version to 0.2.0 (from 0.1.0) for release
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-08 08:13:20 -04:00
19 changed files with 577 additions and 59 deletions

No files matched your search

+10 -1
View File
@@ -54,4 +54,13 @@ jobs:
desktop-file-validate packaging/linux/ai.storyteller.cadcraft.desktop
sed -e 's/@VERSION@/0.0.0/' -e 's/@DATE@/2026-01-01/' packaging/linux/ai.storyteller.cadcraft.metainfo.xml.in >"$RUNNER_TEMP/ai.storyteller.cadcraft.metainfo.xml"
appstreamcli validate --no-net --explain "$RUNNER_TEMP/ai.storyteller.cadcraft.metainfo.xml"
python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.cadcraft.yml')); assert m['id'] == 'ai.storyteller.cadcraft' and m['command'] == 'cadcraft'"
python3 - <<'EOF'
import yaml
src = yaml.safe_load(open("packaging/linux/flatpak/ai.storyteller.cadcraft.yml"))
bundle = yaml.safe_load(open("packaging/linux/flatpak/ai.storyteller.cadcraft.bundle.yml"))
for m in (src, bundle):
assert m["id"] == "ai.storyteller.cadcraft" and m["command"] == "cadcraft", m["id"]
for key in ("runtime", "runtime-version", "sdk", "finish-args"):
assert src[key] == bundle[key], f"Flatpak manifests disagree on {key}: {src[key]} vs {bundle[key]}"
print("Flatpak manifests ok")
EOF
+57 -14
View File
@@ -1,12 +1,13 @@
# Release pipeline: every push to `release` builds signed installers for every platform and
# creates or updates the draft GitHub Release "CADCraft v<version>". See docs/releasing.md.
#
# Only the `release` branch builds: pushes to it trigger the run, and a manual dispatch from any
# other ref is skipped by the guard on the `version` job (every other job needs it). Pushing to
# `release` is limited by a repository ruleset to org admins and the release-managers team.
# All jobs run in the `release` environment (restricted to the `release` branch), which holds
# the signing secrets. Every secret is optional: missing ones produce unsigned artifacts and a
# warning, never a failed build.
# Pushes to `release` trigger the run; pushing there is limited by a repository ruleset to org
# admins and the release-managers team. The jobs that sign (macOS, Windows) and the one that
# drafts the GitHub Release run in the `release` environment (restricted to the `release` branch),
# which holds the signing secrets. Every secret is optional: missing ones produce unsigned
# artifacts and a warning, never a failed build. The other jobs (version, Linux, Flatpak, FreeBSD,
# web) sign nothing and get no secrets, so a manual dispatch on any other branch is a dry run of
# them: the environment refuses the signing jobs there, and the draft-release job is skipped.
name: Release
on:
@@ -37,9 +38,7 @@ env:
jobs:
version:
name: version
if: github.ref == 'refs/heads/release'
runs-on: ubuntu-latest
environment: release
outputs:
version: ${{ steps.v.outputs.version }}
date: ${{ steps.v.outputs.date }}
@@ -117,7 +116,6 @@ jobs:
- { arch: x64, target: x86_64-pc-windows-msvc }
- { arch: x86, target: i686-pc-windows-msvc }
# Windows on ARM: cross-compiled on the x64 runner, so signing and WiX run as above.
# .github/workflows/windows-arm64.yml installs and runs it on ARM64 hardware.
- { arch: arm64, target: aarch64-pc-windows-msvc }
env:
CADCRAFT_VERSION: ${{ needs.version.outputs.version }}
@@ -161,7 +159,7 @@ jobs:
# The oldest GitHub-hosted image, so the binaries only need glibc >= 2.35 (Ubuntu 22.04+,
# Debian 12+, Fedora 36+, RHEL 10). See docs/releasing.md.
runs-on: ${{ matrix.runner }}
environment: release
# No `environment: release`: this job signs nothing, so it gets no release secrets.
strategy:
fail-fast: false
matrix:
@@ -179,7 +177,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libxkbcommon-dev libwayland-dev libx11-dev libxrandr-dev libxi-dev libgl1-mesa-dev \
desktop-file-utils appstream python3-yaml
desktop-file-utils appstream python3-yaml zsync
curl -fsSL -o "$RUNNER_TEMP/nfpm.deb" \
"https://github.com/goreleaser/nfpm/releases/download/v${NFPM_VERSION}/nfpm_${NFPM_VERSION}_${{ matrix.deb }}.deb"
sudo dpkg -i "$RUNNER_TEMP/nfpm.deb"
@@ -201,6 +199,11 @@ jobs:
for appimage in dist/release/*.AppImage; do
chmod +x "$appimage"
APPIMAGE_EXTRACT_AND_RUN=1 "$appimage" --version
# Update information is embedded, and the .zsync for delta updates sits beside it.
# Without APPIMAGE_EXTRACT_AND_RUN: with it, the runtime passes --appimage-* options
# on to the app (which then tries to open a window) instead of answering them.
"$appimage" --appimage-updateinformation | grep -F 'gh-releases-zsync|'
test -s "$appimage.zsync"
done
- uses: actions/upload-artifact@v4
with:
@@ -208,11 +211,49 @@ jobs:
path: dist/release/*
if-no-files-found: error
flatpak:
name: Flatpak ${{ matrix.arch }}
needs: [version, linux]
# Repackages the Linux job's tarball (the same binaries as the AppImage/deb/rpm); no Rust
# build here. 24.04 for a newer flatpak-builder (1.4) than 22.04 ships.
runs-on: ${{ matrix.runner }}
# No `environment: release`: this job signs nothing, so it gets no release secrets.
strategy:
fail-fast: false
matrix:
include:
- { arch: x86_64, runner: ubuntu-24.04 }
- { arch: aarch64, runner: ubuntu-24.04-arm }
env:
CADCRAFT_VERSION: ${{ needs.version.outputs.version }}
CADCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: release-linux-${{ matrix.arch }}
path: dist/linux
- name: Install flatpak-builder
run: |
sudo apt-get update
# librsvg2-common: the SVG pixbuf loader that the host's `appstreamcli compose` (run by
# flatpak-builder) needs for any scalable icon.
sudo apt-get install -y --no-install-recommends flatpak flatpak-builder librsvg2-common
# Ubuntu 24.04's AppArmor blocks the unprivileged user namespaces bubblewrap needs.
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
- name: Build the Flatpak bundle
run: packaging/linux/flatpak-bundle.sh "dist/linux/cadcraft-$CADCRAFT_VERSION-linux-${{ matrix.arch }}.tar.gz"
- uses: actions/upload-artifact@v4
with:
name: release-flatpak-${{ matrix.arch }}
path: dist/release/*.flatpak
if-no-files-found: error
web:
name: Web (wasm)
needs: version
runs-on: ubuntu-latest
environment: release
# No `environment: release`: this job signs nothing, so it gets no release secrets.
env:
CADCRAFT_VERSION: ${{ needs.version.outputs.version }}
CADCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
@@ -246,7 +287,7 @@ jobs:
needs: version
# GitHub has no FreeBSD runners: build inside a FreeBSD VM on a Linux runner.
runs-on: ubuntu-latest
environment: release
# No `environment: release`: this job signs nothing, so it gets no release secrets.
timeout-minutes: 300
env:
CADCRAFT_VERSION: ${{ needs.version.outputs.version }}
@@ -287,7 +328,9 @@ jobs:
release:
name: Draft GitHub Release
needs: [version, macos, windows, linux, web, freebsd]
needs: [version, macos, windows, linux, flatpak, web, freebsd]
# Only the `release` branch publishes; a dry run on another branch stops before this job.
if: github.ref == 'refs/heads/release'
runs-on: ubuntu-latest
environment: release
permissions:
+2
View File
@@ -37,6 +37,8 @@ Generated-in-code assets are original and have no file to list:
| `assets/app-icon/cadcraft-256.png` | CADCraft contributors | generated | MIT OR Apache-2.0 | |
| `assets/app-icon/cadcraft-1024.png` | CADCraft contributors | generated | MIT OR Apache-2.0 | |
| `assets/app-icon/cadcraft-macos-512.png` | CADCraft contributors | generated (macOS margin variant) | MIT OR Apache-2.0 | |
| `packaging/macos/dmg/background.svg` | @XusBadia | original work for this repository (2026-10-08), "CADCraft macOS DMG window background"; derived from the CADCraft app icon (`assets/app-icon/`, referenced via `<image>`, not copied); text is outlined Inter / JetBrains Mono glyph paths (OFL fonts the project already uses); no third-party material modified | MIT OR Apache-2.0 (same as the code: `LICENSE-MIT`, `LICENSE-APACHE`) | Finder window background of the macOS DMG; `packaging/macos/dmg/generate.py` renders it to `background.tiff` (see `packaging/macos/dmg/README.md`) |
| `packaging/macos/dmg/background.tiff` | @XusBadia | Rendered from `packaging/macos/dmg/background.svg` by `packaging/macos/dmg/generate.py` | MIT OR Apache-2.0 (same as the code: `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") |
| `examples/apartment.dxf` | CADCraft contributors | sample drawing made with cadcraft-cli commands | MIT OR Apache-2.0 | Original |
| `docs/images/ui-apartment.png` | CADCraft contributors | screenshot of CADCraft itself (examples/apartment.dxf) | MIT OR Apache-2.0 | Original; no Autodesk UI |
| `docs/images/ui-layout.png` | CADCraft contributors | screenshot of CADCraft itself (a layout of examples/apartment.dxf) | MIT OR Apache-2.0 | Original; no Autodesk UI |
Generated
+16 -16
View File
@@ -534,7 +534,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "cadcraft"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"cadcraft-engine",
"cadcraft-io",
@@ -550,7 +550,7 @@ dependencies = [
[[package]]
name = "cadcraft-cli"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"cadcraft-engine",
"cadcraft-io",
@@ -561,14 +561,14 @@ dependencies = [
[[package]]
name = "cadcraft-color"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"serde",
]
[[package]]
name = "cadcraft-constraints"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"cadcraft-doc",
"cadcraft-geom",
@@ -578,7 +578,7 @@ dependencies = [
[[package]]
name = "cadcraft-doc"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"cadcraft-color",
"cadcraft-geom",
@@ -589,21 +589,21 @@ dependencies = [
[[package]]
name = "cadcraft-dwg"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"acadrust",
]
[[package]]
name = "cadcraft-dxf"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"thiserror 2.0.21",
]
[[package]]
name = "cadcraft-engine"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"cadcraft-color",
"cadcraft-constraints",
@@ -620,7 +620,7 @@ dependencies = [
[[package]]
name = "cadcraft-fonts"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"cadcraft-geom",
"skrifa 0.47.0",
@@ -628,14 +628,14 @@ dependencies = [
[[package]]
name = "cadcraft-geom"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"serde",
]
[[package]]
name = "cadcraft-io"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"cadcraft-color",
"cadcraft-doc",
@@ -651,7 +651,7 @@ dependencies = [
[[package]]
name = "cadcraft-mcp"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"cadcraft-engine",
"cadcraft-io",
@@ -662,7 +662,7 @@ dependencies = [
[[package]]
name = "cadcraft-render"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"cadcraft-color",
"cadcraft-doc",
@@ -674,7 +674,7 @@ dependencies = [
[[package]]
name = "cadcraft-ui-egui"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"cadcraft-color",
"cadcraft-doc",
@@ -693,7 +693,7 @@ dependencies = [
[[package]]
name = "cadcraft-web"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"cadcraft-engine",
"cadcraft-io",
@@ -4514,7 +4514,7 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
[[package]]
name = "xtask"
version = "0.1.0"
version = "0.4.0"
dependencies = [
"serde_json",
]
+1 -1
View File
@@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
# default-members
[workspace.package]
version = "0.1.0"
version = "0.4.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.90"
+44
View File
@@ -58,6 +58,7 @@
<a href="#drive-it-from-agents-mcp-and-the-cli">Agents, MCP and the CLI</a> ·
<a href="#architecture">Architecture</a> ·
<a href="#roadmap">Roadmap</a> ·
<a href="#downloads">Downloads</a> ·
<a href="#the-crafting-apps">The Crafting Apps</a> ·
<a href="#license-and-credits">License and credits</a>
</p>
@@ -171,6 +172,49 @@ checks formatting, clippy, tests, asset attribution, the crate layering and the
See [ROADMAP.md](ROADMAP.md) for milestones, measured parity and the estimate of remaining work.
## Downloads
**Download CADCraft** from GitHub: the [latest release](https://github.com/storytold/cadcraft/releases/latest) has every build listed below, and [all releases](https://github.com/storytold/cadcraft/releases) has earlier versions and their notes. `<ver>` in the file names is the version number, and `SHA256SUMS.txt` lists a checksum for every file.
### Windows
| Build | Installer | Portable |
|---|---|---|
| x64 (64-bit Intel/AMD) | `cadcraft-<ver>-windows-x64.msi` | `cadcraft-<ver>-windows-x64-portable.zip` |
| arm64 (Snapdragon and other ARM PCs) | `cadcraft-<ver>-windows-arm64.msi` | `cadcraft-<ver>-windows-arm64-portable.zip` |
| x86 (32-bit) | `cadcraft-<ver>-windows-x86.msi` | `cadcraft-<ver>-windows-x86-portable.zip` |
Installers and executables are code-signed.
### macOS
| Build | File | Notes |
|---|---|---|
| App, universal (Apple silicon + Intel) | `cadcraft-<ver>-macos-universal.dmg` | Signed and notarized |
| Command-line tool, universal | `cadcraft-cli-<ver>-macos-universal.zip` | Signed and notarized |
### Linux
| Format | x86_64 | aarch64 (ARM64) | Notes |
|---|---|---|---|
| AppImage | `cadcraft-<ver>-linux-x86_64.AppImage` | `cadcraft-<ver>-linux-aarch64.AppImage` | Runs anywhere; updates itself with [AppImageUpdate](https://github.com/AppImageCommunity/AppImageUpdate) (`.zsync` files) |
| Flatpak | `cadcraft-<ver>-linux-x86_64.flatpak` | `cadcraft-<ver>-linux-aarch64.flatpak` | Sandboxed; `flatpak install --user <file>` |
| Debian/Ubuntu | `cadcraft-<ver>-linux-x86_64.deb` | `cadcraft-<ver>-linux-aarch64.deb` | |
| Fedora/RHEL/openSUSE | `cadcraft-<ver>-linux-x86_64.rpm` | `cadcraft-<ver>-linux-aarch64.rpm` | |
| Tarball | `cadcraft-<ver>-linux-x86_64.tar.gz` | `cadcraft-<ver>-linux-aarch64.tar.gz` | Unpack anywhere |
### FreeBSD
| Build | File |
|---|---|
| x86_64 | `cadcraft-<ver>-freebsd-x86_64.tar.gz` |
### Web (WebAssembly)
| Build | File | Notes |
|---|---|---|
| Static site | `cadcraft-web-<ver>.zip` | Runs in a modern browser; host it on any static server |
## The Crafting Apps
CADCraft is one of the **Crafting Apps**: free, open-source creative tools from the
+37 -11
View File
@@ -2,23 +2,49 @@
"schema": 1,
"repo": "storytold/cadcraft",
"branch": "main",
"head": "14e143bfdd55fe9063ba7d87dbbc692e5c901063",
"head_date": "2026-10-07T18:45:23Z",
"commits": 18,
"head": "86f3199b400c271a1166103519b3785e4ac88e72",
"head_date": "2026-10-08T13:12:32Z",
"commits": 26,
"unattributed_commits": 0,
"contributors": [
{
"login": "echelon",
"people_entry": true,
"github_name": null,
"prs": 1,
"commits": 18,
"lines_added": 69334,
"lines_deleted": 2968,
"prs": 4,
"commits": 23,
"lines_added": 70258,
"lines_deleted": 3353,
"binary_added": 26,
"binary_deleted": 5,
"first_commit": "2026-10-06T00:15:47Z",
"last_commit": "2026-10-07T18:45:23Z"
"last_commit": "2026-10-08T13:50:35Z"
},
{
"login": "h-a-n-a-s-h-i",
"people_entry": false,
"github_name": null,
"prs": 0,
"commits": 2,
"lines_added": 34,
"lines_deleted": 34,
"binary_added": 0,
"binary_deleted": 0,
"first_commit": "2026-10-08T12:13:20Z",
"last_commit": "2026-10-08T14:02:20Z"
},
{
"login": "XusBadia",
"people_entry": false,
"github_name": null,
"prs": 1,
"commits": 1,
"lines_added": 244,
"lines_deleted": 1,
"binary_added": 2,
"binary_deleted": 0,
"first_commit": "2026-10-08T13:12:32Z",
"last_commit": "2026-10-08T13:12:32Z"
}
],
"models": [
@@ -27,9 +53,9 @@
"company": "Anthropic",
"model": "Claude Opus",
"version": "5.5",
"commits": 17,
"lines_added": 69327,
"lines_deleted": 2968
"commits": 25,
"lines_added": 70529,
"lines_deleted": 3388
}
]
}
+2 -2
View File
@@ -3,8 +3,8 @@
The canonical recipe is craftrules' [`release/playbook.md`](https://github.com/storytold/craftrules/blob/main/release/playbook.md); this file covers only what's CADCraft-specific.
- **Version:** `[workspace.package] version` in the root `Cargo.toml` is the single source. `cargo xtask version` prints it; `cargo xtask version set X.Y.Z[-pre]` changes it. The app shows it in About, `cadcraft --version` and `cadcraft-cli --version`; release builds also embed the commit (`CADCRAFT_BUILD_SHA`).
- **Cut a release:** bump the version on `main`, then push `main` to `release` (`git push origin main:release`). Every push to `release` runs `.github/workflows/release.yml` and creates or updates the draft GitHub Release `CADCraft v<version>`. A `workflow_dispatch` run with `version: X.Y.Z-rc.N` makes a test build.
- **Artifacts:** macOS universal `.dmg` (Developer ID signed, notarized, stapled) and CLI `.zip`; Windows x64 and x86 `.msi` + portable `.zip` (Azure Trusted Signing); Linux x86_64 `.AppImage`, `.deb`, `.rpm`, `.tar.gz` (+ Flatpak manifest in `packaging/linux/flatpak/`); FreeBSD x86_64 `.tar.gz` (built in a FreeBSD VM); web `.zip` (wasm + `index.html`, see `packaging/web/README.md`); `SHA256SUMS.txt`.
- **Cut a release:** bump the version on `main`, then push `main` to `release` (`git push origin main:release`). Every push to `release` runs `.github/workflows/release.yml` and creates or updates the draft GitHub Release `CADCraft v<version>`. A `workflow_dispatch` run with `version: X.Y.Z-rc.N` makes a test build. A dispatch on any other branch is a dry run: the unsigned jobs (version, Linux, Flatpak, FreeBSD, web) build, the `release` environment refuses the signing jobs (macOS, Windows), and no release is drafted.
- **Artifacts:** macOS universal `.dmg` (Developer ID signed, notarized, stapled) and CLI `.zip`; Windows x64, x86 and arm64 `.msi` + portable `.zip` (Azure Trusted Signing); Linux x86_64 and aarch64 `.AppImage` (+ `.AppImage.zsync` for AppImageUpdate delta updates), `.flatpak` bundle (repackages the tarball, `packaging/linux/flatpak-bundle.sh`), `.deb`, `.rpm`, `.tar.gz` (+ the from-source Flathub manifest in `packaging/linux/flatpak/`); FreeBSD x86_64 `.tar.gz` (built in a FreeBSD VM); web `.zip` (wasm + `index.html`, see `packaging/web/README.md`); `SHA256SUMS.txt`.
- **Secrets:** the twelve signing secrets live in the `release` environment (deployable only from the `release` branch, which only org admins and release-managers can push). All are optional: a missing one produces unsigned artifacts with a warning.
- **File types:** `.dxf` and `.dwg` (macOS document types, Windows "Open with", Linux MIME types).
- **Local packaging test:** `packaging/macos/package.sh` (ad-hoc signed DMG), `packaging/linux/package.sh`, `packaging/web/package.sh`.
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# Repackage a CADCraft Linux tarball as a single-file Flatpak bundle:
#
# $DIST/cadcraft-<version>-linux-<arch>.flatpak
#
# Usage: packaging/linux/flatpak-bundle.sh [--no-test] [TARBALL]
#
# TARBALL defaults to $DIST/cadcraft-<version>-linux-<arch>.tar.gz from package.sh. The bundle
# is built for the host architecture (x86_64 or aarch64), which must match the tarball's.
# Needs flatpak, flatpak-builder and the SVG pixbuf loader (librsvg2-common) for the host's
# `appstreamcli compose`; the freedesktop runtime and SDK named in the manifest are
# installed per-user from Flathub. Unless --no-test, the bundle is then installed per-user and
# `cadcraft-cli --version` is run inside the sandbox as a smoke test.
# Manifest: packaging/linux/flatpak/ai.storyteller.cadcraft.bundle.yml.
set -euo pipefail
# shellcheck source=../env.sh
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
HERE="$ROOT/packaging/linux"
APP_ID=ai.storyteller.cadcraft
FLATHUB=https://dl.flathub.org/repo/flathub.flatpakrepo
TEST=1
TARBALL=""
while [ $# -gt 0 ]; do
case "$1" in
--no-test) TEST=0; shift ;;
-h | --help) sed -n '2,14p' "$0"; exit 0 ;;
-*) echo "unknown argument: $1" >&2; exit 2 ;;
*) TARBALL="$1"; shift ;;
esac
done
ARCH="$(uname -m)"
case "$ARCH" in
x86_64) ;;
aarch64 | arm64) ARCH=aarch64 ;;
*) echo "unsupported architecture $ARCH" >&2; exit 2 ;;
esac
BASENAME="cadcraft-$VERSION-linux-$ARCH"
TARBALL="${TARBALL:-$DIST/$BASENAME.tar.gz}"
[ -f "$TARBALL" ] || { echo "error: $TARBALL not found (run packaging/linux/package.sh --formats tar first)" >&2; exit 1; }
for tool in flatpak flatpak-builder; do
command -v "$tool" >/dev/null || { echo "error: $tool not found (apt install flatpak flatpak-builder librsvg2-common)" >&2; exit 1; }
done
echo "==> CADCraft $VERSION Flatpak bundle for $ARCH from $(basename "$TARBALL")"
WORK="$CARGO_TARGET_DIR/flatpak-bundle"
rm -rf "$WORK"
mkdir -p "$WORK/stage"
tar -xzf "$TARBALL" -C "$WORK/stage" --strip-components=1
[ -x "$WORK/stage/bin/cadcraft" ] || { echo "error: $TARBALL has no bin/cadcraft" >&2; exit 1; }
cp "$HERE/flatpak/$APP_ID.bundle.yml" "$WORK/$APP_ID.yml"
flatpak remote-add --user --if-not-exists flathub "$FLATHUB"
# --disable-rofiles-fuse: no FUSE in containers and CI runners.
flatpak-builder --user --install-deps-from=flathub --disable-rofiles-fuse --force-clean \
--default-branch=stable --repo="$WORK/repo" --state-dir="$WORK/state" "$WORK/build" "$WORK/$APP_ID.yml"
OUT="$DIST/$BASENAME.flatpak"
# --runtime-repo lets `flatpak install` fetch the freedesktop runtime from Flathub if missing.
flatpak build-bundle --runtime-repo="$FLATHUB" "$WORK/repo" "$OUT" "$APP_ID" stable
echo "wrote $OUT"
if [ "$TEST" = 1 ]; then
flatpak install --user -y --noninteractive --reinstall "$OUT"
flatpak info --user "$APP_ID"
flatpak run --command=cadcraft-cli "$APP_ID" --version
flatpak run --command=sh "$APP_ID" -c 'ls /app/share/applications /app/share/metainfo /app/share/mime/packages /app/share/icons/hicolor/256x256/apps'
fi
echo "==> done"
ls -lh "$OUT"
@@ -0,0 +1,50 @@
# Flatpak manifest for the single-file bundle attached to every GitHub release
# (cadcraft-<version>-linux-<arch>.flatpak). Build it with packaging/linux/flatpak-bundle.sh.
#
# It repackages the release's own Linux tarball (cadcraft-<version>-linux-<arch>.tar.gz, built
# on Ubuntu 22.04 by package.sh) instead of compiling Rust inside flatpak-builder: no network and
# no vendored crate sources during the build, a few seconds instead of many minutes, and
# byte-for-byte the same binaries as the AppImage/deb/rpm. The script unpacks the tarball into
# `stage/` next to a copy of this file. The binaries need glibc >= 2.35 and load X11, Wayland,
# xkbcommon and Vulkan at runtime; the freedesktop runtime has all of them, and the GPU driver
# comes from its org.freedesktop.Platform.GL extension.
#
# ai.storyteller.cadcraft.yml (same directory) is the from-source manifest for Flathub. Keep
# runtime, runtime-version, sdk and finish-args identical in both (packaging-lint checks this).
id: ai.storyteller.cadcraft
runtime: org.freedesktop.Platform
runtime-version: '26.08'
sdk: org.freedesktop.Sdk
command: cadcraft
finish-args:
# Display: Wayland first; X11 only when no Wayland compositor is running.
- --socket=wayland
- --socket=fallback-x11
# X11 shared memory (MIT-SHM). Without it the X11 fallback is slow or fails.
- --share=ipc
# GPU access for the wgpu canvas (Vulkan, or GL as a fallback).
- --device=dri
# Open/Save dialogs go through the file-chooser portal (rfd) and drag-and-drop through the
# document portal, so picked files work anywhere without broad filesystem access. Documents
# and Pictures are granted so drawings passed by path (the command line, Open Recent, xrefs
# and images next to a DXF) work in the usual places.
- --filesystem=xdg-documents
- --filesystem=xdg-pictures
# Deliberately not granted: network (the optional --control server listens on 127.0.0.1 in
# the sandbox; `flatpak override --user --share=network ai.storyteller.cadcraft` exposes it to
# host agents) and home/host filesystem access.
modules:
- name: cadcraft
buildsystem: simple
build-commands:
- install -Dm755 bin/cadcraft /app/bin/cadcraft
- install -Dm755 bin/cadcraft-cli /app/bin/cadcraft-cli
- install -Dm644 share/applications/ai.storyteller.cadcraft.desktop /app/share/applications/ai.storyteller.cadcraft.desktop
# .dxf and .dwg.
- install -Dm644 share/mime/packages/ai.storyteller.cadcraft.xml /app/share/mime/packages/ai.storyteller.cadcraft.xml
# Already has the release's version and date substituted by package.sh.
- install -Dm644 share/metainfo/ai.storyteller.cadcraft.metainfo.xml /app/share/metainfo/ai.storyteller.cadcraft.metainfo.xml
- mkdir -p /app/share/icons /app/share/doc && cp -R share/icons/hicolor /app/share/icons/ && cp -R share/doc/cadcraft /app/share/doc/
sources:
- type: dir
path: stage
@@ -8,22 +8,32 @@
# flatpak run ai.storyteller.cadcraft
#
# For Flathub, replace the `dir` source with a `git` source pinned to a release tag and commit.
# The single-file .flatpak attached to each release comes from ai.storyteller.cadcraft.bundle.yml,
# which repackages the prebuilt release binaries. Keep runtime-version and finish-args identical.
id: ai.storyteller.cadcraft
runtime: org.freedesktop.Platform
runtime-version: '25.08'
runtime-version: '26.08'
sdk: org.freedesktop.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.rust-stable
command: cadcraft
finish-args:
- --share=ipc
# Display: Wayland first; X11 only when no Wayland compositor is running.
- --socket=wayland
- --socket=fallback-x11
# X11 shared memory (MIT-SHM). Without it the X11 fallback is slow or fails.
- --share=ipc
# GPU access for the wgpu canvas (Vulkan, or GL as a fallback).
- --device=dri
# Open/Save dialogs use the file-chooser portal (rfd), and drag-and-drop uses the document
# portal, so broad filesystem access isn't needed. Pictures is granted for opening files by
# path from the command line.
# Open/Save dialogs go through the file-chooser portal (rfd) and drag-and-drop through the
# document portal, so picked files work anywhere without broad filesystem access. Documents
# and Pictures are granted so drawings passed by path (the command line, Open Recent, xrefs
# and images next to a DXF) work in the usual places.
- --filesystem=xdg-documents
- --filesystem=xdg-pictures
# Deliberately not granted: network (the optional --control server listens on 127.0.0.1 in
# the sandbox; `flatpak override --user --share=network ai.storyteller.cadcraft` exposes it to
# host agents) and home/host filesystem access.
build-options:
append-path: /usr/lib/sdk/rust-stable/bin
modules:
@@ -34,8 +44,9 @@ modules:
CARGO_HOME: /run/build/cadcraft/cargo
CARGO_NET_OFFLINE: 'true'
build-commands:
- cargo --offline build --release --locked -p cadcraft
- cargo --offline build --release --locked -p cadcraft -p cadcraft-cli
- install -Dm755 target/release/cadcraft /app/bin/cadcraft
- install -Dm755 target/release/cadcraft-cli /app/bin/cadcraft-cli
- install -Dm644 packaging/linux/ai.storyteller.cadcraft.desktop /app/share/applications/ai.storyteller.cadcraft.desktop
- install -Dm644 packaging/linux/ai.storyteller.cadcraft.mime.xml /app/share/mime/packages/ai.storyteller.cadcraft.xml
- mkdir -p /app/share/icons && cp -R assets/app-icon/hicolor /app/share/icons/
+3 -3
View File
@@ -17,9 +17,9 @@ vendor: Learning Machines LLC
homepage: https://getartcraft.com/apps/cadcraft
license: MIT OR Apache-2.0
description: |-
Open-source, native image editor that works the way Photoshop users expect.
Layers, masks, adjustment layers, layer styles, type and brushes; opens and
saves layered PSD/PSB files; 8/16/32-bit documents; GPU compositing (wgpu).
Open-source computer-aided design and drafting: draft, dimension and plot 2D
drawings with a familiar command line, object snaps, layers, dimensions, hatches
and blocks. Opens and saves DXF and DWG; exports SVG and PNG.
Includes cadcraft-cli, a headless converter and command runner.
# The binaries link only glibc and libgcc_s directly. Windowing (X11/Wayland, xkbcommon) and the
+24 -4
View File
@@ -2,6 +2,7 @@
# Build and package CADCraft for Linux (<arch> is x86_64 or aarch64):
#
# $DIST/cadcraft-<version>-linux-<arch>.AppImage any distro with glibc >= the build host's
# $DIST/cadcraft-<version>-linux-<arch>.AppImage.zsync delta updates (needs zsyncmake)
# $DIST/cadcraft-<version>-linux-<arch>.deb Debian, Ubuntu, Mint, Pop!_OS, ...
# $DIST/cadcraft-<version>-linux-<arch>.rpm Fedora, openSUSE, RHEL, ...
# $DIST/cadcraft-<version>-linux-<arch>.tar.gz plain FHS-style tree (bin/, share/)
@@ -10,7 +11,8 @@
#
# Needs: cargo; nfpm for deb/rpm (https://nfpm.goreleaser.com); appimagetool for the AppImage
# (downloaded into $CARGO_TARGET_DIR if missing). Build on an old distro (CI: Ubuntu 22.04,
# glibc 2.35) so the binaries run on newer ones. Optional: desktop-file-validate, appstreamcli.
# glibc 2.35) so the binaries run on newer ones. Optional: desktop-file-validate, appstreamcli,
# zsyncmake (the zsync package) for the AppImage's .zsync.
set -euo pipefail
# shellcheck source=../env.sh
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
@@ -108,11 +110,29 @@ if has appimage; then
chmod +x "$TOOL"
fi
fi
OUT="$DIST/$BASENAME.AppImage"
# Absolute, because appimagetool runs in $DIST below (CARGO_TARGET_DIR or APPIMAGETOOL may be
# relative).
OUT="$(cd "$DIST" && pwd)/$BASENAME.AppImage"
APPDIR="$(cd "$APPDIR" && pwd)"
TOOL="$(cd "$(dirname "$TOOL")" && pwd)/$(basename "$TOOL")"
# A .zsync left by an earlier run would hide a missing zsyncmake and describe another file.
rm -f "$OUT.zsync"
# Update information: AppImageUpdate, AppImageLauncher and the like read it from the file and
# fetch only the blocks that changed in a newer release, through the .zsync published next to
# each AppImage on GitHub Releases. `latest` is the newest published release that is not a
# pre-release. A fork's builds point at its own releases through GITHUB_REPOSITORY.
REPO="${GITHUB_REPOSITORY:-storytold/cadcraft}"
UPDATE_INFO="gh-releases-zsync|${REPO%%/*}|${REPO#*/}|latest|cadcraft-*-linux-$ARCH.AppImage.zsync"
# Extract-and-run: works without FUSE (containers, CI). The output embeds the static runtime,
# so users don't need libfuse2 either.
ARCH="$ARCH" APPIMAGE_EXTRACT_AND_RUN=1 "$TOOL" --no-appstream "$APPDIR" "$OUT"
# so users don't need libfuse2 either. With zsyncmake on the host (CI installs the zsync
# package) appimagetool also writes the .zsync, into its working directory, hence the cd.
(cd "$DIST" && ARCH="$ARCH" APPIMAGE_EXTRACT_AND_RUN=1 "$TOOL" --no-appstream -u "$UPDATE_INFO" "$APPDIR" "$OUT")
echo "wrote $OUT"
if [ -s "$OUT.zsync" ]; then
echo "wrote $OUT.zsync"
else
warn "zsyncmake not found, so $OUT.zsync was not written; AppImage delta updates need it"
fi
fi
"$STAGE/usr/bin/cadcraft-cli" --version
+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/cadcraft-1024.png`, linked, not copied) cropped as a cover on the CADCraft colour field (`#14a3c7`), 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, CADCraft.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 `CADCraft`, not `CADCraft <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 (`cadcraft-<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).
+34
View File
@@ -0,0 +1,34 @@
<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="#14a3c7"/><g transform="translate(-49.6,5.6) scale(0.72)"><image href="../../../assets/app-icon/cadcraft-1024.png" 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="#14a3c7"/>
<line x1="408" y1="191" x2="408" y2="213" stroke="#0b0b0c" stroke-width="0.7"/><line x1="404" y1="205" x2="482" y2="205" stroke="#0b0b0c" stroke-width="1"/><line x1="404" y1="209" x2="412" y2="201" stroke="#0b0b0c" stroke-width="1.8"/><path d="M498.6,205.0 L486.6,210.0 L486.6,200.0 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.49902 72.29297 Q 274.62793 72.29297 272.37207 70.95996 Q 270.1162 69.62695 268.8125 67.12207 Q 267.5088 64.61719 267.5088 61.101563 Q 267.5088 57.571285 268.81982 55.059082 Q 270.13086 52.54688 272.38672 51.213863 Q 274.64258 49.88086 277.49902 49.88086 Q 279.88672 49.88086 281.8423 50.774414 Q 283.79785 51.66797 285.07227 53.359863 Q 286.34668 55.051754 286.69824 57.439453 L 282.7578 57.439453 Q 282.3916 55.50586 280.95605 54.443848 Q 279.5205 53.381836 277.55762 53.381836 Q 274.87695 53.381836 273.15576 55.381344 Q 271.43457 57.38086 271.43457 61.101563 Q 271.43457 64.86621 273.1631 66.8291 Q 274.8916 68.79199 277.54297 68.79199 Q 279.50586 68.79199 280.94873 67.72998 Q 282.3916 66.66797 282.7578 64.734375 L 286.7129 64.734375 Q 286.40527 66.8291 285.2041 68.54297 Q 284.00293 70.256836 282.04004 71.2749 Q 280.07715 72.29297 277.49902 72.29297 Z M 287.58984 72 L 295.20703 50.17383 L 300.17285 50.17383 L 307.96582 72 L 303.67383 72 L 301.81348 66.59473 L 293.6543 66.59473 L 291.8672 72 Z M 294.69434 63.445316 L 300.74414 63.445316 L 299.792 60.66211 Q 299.3086 59.15332 298.78857 57.322266 Q 298.26855 55.49121 297.65332 53.220707 Q 297.06738 55.50586 296.56934 57.35889 Q 296.0713 59.211914 295.6172 60.66211 Z M 317.76367 72 L 310.38086 72 L 310.38086 50.17383 L 317.91016 50.17383 Q 321.1621 50.17383 323.49854 51.484863 Q 325.83496 52.7959 327.08008 55.234863 Q 328.3252 57.673832 328.3252 61.057617 Q 328.3252 64.4707 327.07275 66.92432 Q 325.8203 69.37793 323.4546 70.688965 Q 321.08887 72 317.76367 72 Z M 314.292 68.61621 L 317.57324 68.61621 Q 324.47266 68.61621 324.47266 61.057617 Q 324.47266 53.557617 317.67578 53.557617 L 314.292 53.557617 Z M 340.84766 72.29297 Q 337.97656 72.29297 335.7207 70.95996 Q 333.46484 69.62695 332.16113 67.12207 Q 330.85742 64.61719 330.85742 61.101563 Q 330.85742 57.571285 332.16846 55.059082 Q 333.4795 52.54688 335.73535 51.213863 Q 337.9912 49.88086 340.84766 49.88086 Q 343.23535 49.88086 345.19092 50.774414 Q 347.14648 51.66797 348.42093 53.359863 Q 349.6953 55.051754 350.04688 57.439453 L 346.1064 57.439453 Q 345.74023 55.50586 344.3047 54.443848 Q 342.86914 53.381836 340.90625 53.381836 Q 338.2256 53.381836 336.5044 55.381344 Q 334.7832 57.38086 334.7832 61.101563 Q 334.7832 64.86621 336.51172 66.8291 Q 338.24023 68.79199 340.8916 68.79199 Q 342.8545 68.79199 344.29736 67.72998 Q 345.74023 66.66797 346.1064 64.734375 L 350.06152 64.734375 Q 349.7539 66.8291 348.55273 68.54297 Q 347.35156 70.256836 345.3887 71.2749 Q 343.42578 72.29297 340.84766 72.29297 Z M 352.9746 72 L 352.9746 55.623047 L 356.666 55.623047 L 356.666 58.362305 L 356.8418 58.362305 Q 357.28125 56.941402 358.37988 56.172363 Q 359.47852 55.40332 360.88477 55.40332 Q 361.61722 55.40332 362.27637 55.50586 L 362.27637 59.006836 Q 362.042 58.948246 361.51465 58.882324 Q 360.98734 58.816406 360.4746 58.816406 Q 358.87793 58.816406 357.83057 59.805172 Q 356.78317 60.79394 356.78317 62.36133 L 356.78317 72 Z M 368.6758 72.336914 Q 367.12305 72.336914 365.87793 71.77295 Q 364.6328 71.208984 363.91507 70.11035 Q 363.19727 69.01172 363.19727 67.40039 Q 363.19727 65.55469 364.09814 64.53662 Q 364.99902 63.518555 366.42725 63.057125 Q 367.85544 62.595703 369.4375 62.43457 Q 371.57617 62.200195 372.52832 62.00244 Q 373.4805 61.804688 373.4805 60.95508 L 373.4805 60.88184 Q 373.4805 59.680664 372.74805 59.01416 Q 372.01563 58.34766 370.62402 58.34766 Q 369.1885 58.34766 368.33887 58.977543 Q 367.48926 59.607426 367.1963 60.413086 L 363.66602 59.695313 Q 364.38382 57.527344 366.2661 56.472656 Q 368.14844 55.41797 370.60934 55.41797 Q 371.7666 55.41797 372.9458 55.688965 Q 374.125 55.95996 375.10645 56.597168 Q 376.0879 57.234375 376.68848 58.325684 Q 377.28906 59.416992 377.28906 61.057617 L 377.28906 72 L 373.64157 72 L 373.64157 69.74414 L 373.49512 69.74414 Q 372.95313 70.79883 371.7666 71.56787 Q 370.58008 72.336914 368.6758 72.336914 Z M 369.65723 69.509766 Q 371.41504 69.509766 372.4624 68.49902 Q 373.50977 67.48828 373.50977 66.12598 L 373.50977 64.19238 Q 373.24606 64.39746 372.5723 64.55127 Q 371.89844 64.70508 371.14404 64.81494 Q 370.38965 64.924805 369.8916 64.99805 Q 368.6025 65.17383 367.7456 65.70117 Q 366.88867 66.228516 366.88867 67.356445 Q 366.88867 68.41113 367.66504 68.96045 Q 368.44144 69.509766 369.65723 69.509766 Z M 389.4453 55.623047 L 389.4453 58.61133 L 385.9004 58.61133 L 385.9004 72 L 382.10645 72 L 382.10645 58.61133 L 379.10352 58.61133 L 379.10352 55.623047 L 382.10645 55.623047 L 382.10645 54.055664 Q 382.10645 52.429688 382.79492 51.33838 Q 383.4834 50.24707 384.63327 49.697758 Q 385.7832 49.148438 387.18948 49.148438 Q 388.2002 49.148438 388.97656 49.309566 Q 389.7529 49.470703 390.11914 49.60254 L 389.34277 52.561527 Q 389.09375 52.488277 388.73486 52.407715 Q 388.37598 52.32715 387.89258 52.32715 Q 386.82324 52.32715 386.36185 52.854492 Q 385.9004 53.381836 385.9004 54.348633 L 385.9004 55.623047 Z M 398.4375 55.623047 L 398.4375 5Line truncated
</g>
<g>
<path fill="#0b0b0c" fill-opacity="0.7" stroke="none" d="M 267.36 112 L 269.25 108.670006 L 267.459 105.42999 L 268.395 105.42999 L 269.412 107.374 Q 269.493 107.527 269.5785 107.7025 Q 269.664 107.878 269.718 107.977 Q 269.763 107.878 269.8485 107.7025 Q 269.934 107.527 270.015 107.374 L 271.05 105.42999 L 271.941 105.42999 L 270.15 108.616 L 272.04 112 L 271.113 112 L 269.988 109.912 Q 269.907 109.75901 269.8215 109.5835 Q 269.736 109.408 269.691 109.299995 Q 269.646 109.408 269.5605 109.578995 Q 269.475 109.74999 269.394 109.903 L 268.26 112 Z M 280.44598 112.09 Q 279.537 112.09 279.00598 111.581505 Q 278.47498 111.07299 278.47498 110.19999 L 279.28497 110.19999 Q 279.28497 110.74 279.59998 111.055 Q 279.91498 111.37 280.455 111.37 Q 280.995 111.37 281.31 111.055 Q 281.625 110.74 281.625 110.19999 L 281.625 109.795 Q 281.625 109.255 281.319 108.94 Q 281.013 108.625 280.491 108.625 L 278.69998 108.625 L 278.69998 105.42999 L 282.201 105.42999 L 282.201 106.168 L 279.456 106.168 L 279.474 107.887 L 280.53598 107.887 Q 281.44498 107.887 281.94 108.3865 Q 282.435 108.88601 282.435 109.795 L 282.435 110.19999 Q 282.435 111.07299 281.89948 111.581505 Q 281.36398 112.09 280.44598 112.09 Z M 284.91 112 L 287.25 106.168 L 284.74802 106.168 L 284.74802 107.23 L 283.93802 107.23 L 283.93802 105.42999 L 288.114 105.42999 L 288.114 106.186 L 285.79202 112 Z M 292.2 112 L 292.2 110.56 L 289.23 110.56 L 289.23 109.291 L 291.624 105.42999 L 292.542 105.42999 L 290.04 109.47099 L 290.04 109.82201 L 292.2 109.82201 L 292.2 108.21999 L 293.01 108.21999 L 293.01 112 Z M 296.7 112.09 Q 296.367 112.09 296.1645 111.892 Q 295.962 111.694 295.962 111.379 Q 295.962 111.046 296.1645 110.839 Q 296.367 110.63201 296.7 110.63201 Q 297.033 110.63201 297.2355 110.839 Q 297.438 111.046 297.438 111.379 Q 297.438 111.694 297.2355 111.892 Q 297.033 112.09 296.7 112.09 Z M 302.1 112.09 Q 301.19098 112.09 300.6555 111.5635 Q 300.12 111.037 300.12 110.155 L 300.12 107.275 Q 300.12 106.393 300.6555 105.86649 Q 301.19098 105.34 302.1 105.34 Q 303.009 105.34 303.5445 105.86649 Q 304.08 106.393 304.08 107.275 L 304.08 110.155 Q 304.08 110.74 303.837 111.1765 Q 303.594 111.613 303.1485 111.8515 Q 302.703 112.09 302.1 112.09 Z M 302.1 111.39701 Q 302.63998 111.39701 302.9685 111.0505 Q 303.297 110.704 303.297 110.155 L 303.297 107.275 Q 303.297 106.726 302.9685 106.3795 Q 302.63998 106.033 302.1 106.033 Q 301.56 106.033 301.2315 106.3795 Q 300.90298 106.726 300.90298 107.275 L 300.90298 110.155 Q 300.90298 110.704 301.2315 111.0505 Q 301.56 111.39701 302.1 111.39701 Z M 302.1 109.21 Q 301.857 109.21 301.7085 109.075 Q 301.56 108.94 301.56 108.688 Q 301.56 108.445 301.7085 108.3145 Q 301.857 108.184006 302.1 108.184006 Q 302.343 108.184006 302.4915 108.3145 Q 302.63998 108.445 302.63998 108.688 Q 302.63998 108.94 302.4915 109.075 Q 302.343 109.21 302.1 109.21 Z M 307.5 112.09 Q 306.59097 112.09 306.05548 111.5635 Q 305.52 111.037 305.52 110.155 L 305.52 107.275 Q 305.52 106.393 306.05548 105.86649 Q 306.59097 105.34 307.5 105.34 Q 308.409 105.34 308.9445 105.86649 Q 309.47998 106.393 309.47998 107.275 L 309.47998 110.155 Q 309.47998 110.74 309.237 111.1765 Q 308.994 111.613 308.5485 111.8515 Q 308.103 112.09 307.5 112.09 Z M 307.5 111.39701 Q 308.03998 111.39701 308.3685 111.0505 Q 308.697 110.704 308.697 110.155 L 308.697 107.275 Q 308.697 106.726 308.3685 106.3795 Q 308.03998 106.033 307.5 106.033 Q 306.96 106.033 306.6315 106.3795 Q 306.30298 106.726 306.30298 107.275 L 306.30298 110.155 Q 306.30298 110.704 306.6315 111.0505 Q 306.96 111.39701 307.5 111.39701 Z M 307.5 109.21 Q 307.257 109.21 307.1085 109.075 Q 306.96 108.94 306.96 108.688 Q 306.96 108.445 307.1085 108.3145 Q 307.257 108.184006 307.5 108.184006 Q 307.74298 108.184006 307.89148 108.3145 Q 308.03998 108.445 308.03998 108.688 Q 308.03998 108.94 307.89148 109.075 Q 307.74298 109.21 307.5 109.21 Z M 317.89502 112 L 317.89502 109.54301 L 315.915 105.42999 L 316.779 105.42999 L 318.057 108.13 Q 318.165 108.355 318.228 108.512505 Q 318.29102 108.670006 318.30902 108.742004 Q 318.327 108.670006 318.3945 108.512505 Q 318.462 108.355 318.57 108.13 L 319.848 105.42999 L 320.685 105.42999 L 318.70502 109.54301 L 318.70502 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.5 112.09 Q 333.59097 112.09 333.05548 111.5635 Q 332.52 111.037 332.52 110.155 L 332.52 107.275 Q 332.52 106.393 333.05548 105.86649 Q 333.59097 105.34 334.5 105.34 Q 335.409 105.34 335.9445 105.86Line truncated
</g>
<g>
<path fill="#14a3c7" stroke="none" d="M 430.57397 199 L 430.57397 198.25302 L 432.761 195.913 Q 433.23798 195.409 433.43597 195.013 Q 433.634 194.61699 433.634 194.239 Q 433.634 193.699 433.3235 193.37949 Q 433.01297 193.06 432.48196 193.06 Q 431.93298 193.06 431.6135 193.3885 Q 431.29398 193.717 431.29398 194.275 L 430.48398 194.275 Q 430.511 193.384 431.051 192.862 Q 431.59097 192.33998 432.48196 192.33998 Q 433.391 192.33998 433.92648 192.853 Q 434.46198 193.366 434.46198 194.248 Q 434.46198 194.716 434.2055 195.238 Q 433.94894 195.75998 433.328 196.40802 L 431.55502 198.26198 L 434.543 198.26198 L 434.543 199 Z M 438.80002 199 L 438.80002 197.56 L 435.83002 197.56 L 435.83002 196.29102 L 438.224 192.43 L 439.142 192.43 L 436.64005 196.471 L 436.64005 196.82202 L 438.80002 196.82202 L 438.80002 195.22002 L 439.61 195.22002 L 439.61 199 Z M 443.30002 199.09 Q 442.67902 199.09 442.202 198.847 Q 441.725 198.604 441.4595 198.1675 Q 441.194 197.731 441.194 197.16402 Q 441.194 196.543 441.5495 196.093 Q 441.905 195.643 442.499 195.544 L 442.499 195.51698 Q 441.986 195.4 441.68 194.9995 Q 441.374 194.599 441.374 194.077 Q 441.374 193.564 441.617 193.1725 Q 441.86002 192.781 442.29654 192.5605 Q 442.733 192.33998 443.30002 192.33998 Q 443.87604 192.33998 444.30804 192.5605 Q 444.74002 192.781 444.983 193.1725 Q 445.226 193.564 445.226 194.077 Q 445.226 194.599 444.92 194.9995 Q 444.614 195.4 444.101 195.51698 L 444.101 195.544 Q 444.695 195.643 445.0505 196.093 Q 445.406 196.543 445.406 197.16402 Q 445.406 197.731 445.1405 198.1675 Q 444.875 198.604 444.4025 198.847 Q 443.93002 199.09 443.30002 199.09 Z M 443.30002 195.20198 Q 443.80402 195.20198 444.11002 194.905 Q 444.416 194.608 444.416 194.13101 Q 444.416 193.654 444.11002 193.35701 Q 443.80402 193.06 443.30002 193.06 Q 442.805 193.06 442.49454 193.35701 Q 442.18402 193.654 442.18402 194.13101 Q 442.18402 194.608 442.49454 194.905 Q 442.805 195.20198 443.30002 195.20198 Z M 443.30002 198.361 Q 443.87604 198.361 444.236 198.0235 Q 444.596 197.686 444.596 197.146 Q 444.596 196.597 444.236 196.2595 Q 443.87604 195.922 443.30002 195.922 Q 442.72397 195.922 442.364 196.2595 Q 442.004 196.597 442.004 197.146 Q 442.004 197.686 442.364 198.0235 Q 442.72397 198.361 443.30002 198.361 Z M 448.7 199.09 Q 448.367 199.09 448.1645 198.892 Q 447.96198 198.69398 447.96198 198.37901 Q 447.96198 198.046 448.1645 197.839 Q 448.367 197.632 448.7 197.632 Q 449.03296 197.632 449.2355 197.839 Q 449.438 198.046 449.438 198.37901 Q 449.438 198.69398 449.2355 198.892 Q 449.03296 199.09 448.7 199.09 Z M 454.1 199.09 Q 453.19098 199.09 452.6555 198.5635 Q 452.12 198.03702 452.12 197.155 L 452.12 194.275 Q 452.12 193.393 452.6555 192.86652 Q 453.19098 192.33998 454.1 192.33998 Q 455.009 192.33998 455.5445 192.86652 Q 456.08 193.393 456.08 194.275 L 456.08 197.155 Q 456.08 197.74 455.837 198.1765 Q 455.594 198.613 455.1485 198.8515 Q 454.70303 199.09 454.1 199.09 Z M 454.1 198.397 Q 454.63998 198.397 454.96854 198.0505 Q 455.297 197.704 455.297 197.155 L 455.297 194.275 Q 455.297 193.726 454.96854 193.37949 Q 454.63998 193.033 454.1 193.033 Q 453.56 193.033 453.2315 193.37949 Q 452.90298 193.726 452.90298 194.275 L 452.90298 197.155 Q 452.90298 197.704 453.2315 198.0505 Q 453.56 198.397 454.1 198.397 Z M 454.1 196.21 Q 453.857 196.21 453.70853 196.07498 Q 453.56 195.94 453.56 195.688 Q 453.56 195.445 453.70853 195.3145 Q 453.857 195.184 454.1 195.184 Q 454.343 195.184 454.49152 195.3145 Q 454.63998 195.445 454.63998 195.688 Q 454.63998 195.94 454.49152 196.07498 Q 454.343 196.21 454.1 196.21 Z M 459.5 199.09 Q 458.59097 199.09 458.05548 198.5635 Q 457.52002 198.03702 457.52002 197.155 L 457.52002 194.275 Q 457.52002 193.393 458.05548 192.86652 Q 458.59097 192.33998 459.5 192.33998 Q 460.409 192.33998 460.94452 192.86652 Q 461.47998 193.393 461.47998 194.275 L 461.47998 197.155 Q 461.47998 197.74 461.237 198.1765 Q 460.994 198.613 460.5485 198.8515 Q 460.103 199.09 459.5 199.09 Z M 459.5 198.397 Q 460.03998 198.397 460.3685 198.0505 Q 460.697 197.704 460.697 197.155 L 460.697 194.275 Q 460.697 193.726 460.3685 193.37949 Q 460.03998 193.033 459.5 193.033 Q 458.96 193.033 458.6315 193.37949 Q 458.303 193.726 458.303 194.275 L 458.303 197.155 Q 458.303 197.704 458.6315 198.0505 Q 458.96 198.397 459.5 198.397 Z M 459.5 196.21 Q 459.257 196.21 459.1085 196.07498 Q 458.96 195.94 458.96 195.688 Q 458.96 195.445 459.1085 195.3145 Q 459.257 195.184 459.5 195.184 Q 459.74298 195.184 459.89148 195.3145 Q 460.03998 195.445 460.03998 195.688 Q 460.03998 195.94 459.89148 196.07498 Q 459.74298 196.21 459.5 196.21 Z"/>
</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: 40 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 = "CADCraft"
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 = {"CADCraft.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/CADCraft.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 "CADCraft $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 "CADCraft" -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"