app icon: regal engraved dragon (owner's ArtCraft drawing), Dock/taskbar/launcher wiring, palette docs

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Brandon ThomasandClaude Opus 5.5 committed 2026-10-02 09:45:30 -04:00
1 parent dabd179d5a
commit 437320509d
29 files changed
+321 -113

No files matched your search

+18 -1
View File
@@ -4,7 +4,7 @@ Every non-code asset in this repository (icons, images, fonts, example art, colo
**Policy (mandatory):** VectorCraft contains **no Adobe iconography, images, artwork, presets, swatch/brush/symbol/pattern libraries or ICC profiles**. Every asset is either original work by VectorCraft contributors, or third-party material under an open licence (OSI open source, public domain / CC0, or Creative Commons that allows redistribution), and it is attributed below. Screenshots of Adobe software are never committed.
Generated-in-code art is original and has no file to list. This covers the default swatches, brushes, symbols, patterns, graphic styles, image-trace presets, the app icon (`cargo xtask bundle`) and the vector tool cursors (`crates/ui-egui/src/cursors.rs`).
Generated-in-code art is original and has no file to list. This covers the default swatches, brushes, symbols, patterns, graphic styles, image-trace presets and the vector tool cursors (`crates/ui-egui/src/cursors.rs`).
| Asset | Author | Source | Licence | Notes |
|---|---|---|---|---|
@@ -16,6 +16,23 @@ Generated-in-code art is original and has no file to list. This covers the defau
| `docs/brand/artcraft-mark-black.svg` | ArtCraft team (project owner) | https://getartcraft.com/ | ArtCraft brand mark, owned by the project (licence/trademark terms to be confirmed by the owner) | Used in README only; not shipped in the app |
| `docs/brand/artcraft-mark.png` | ArtCraft team (project owner) | https://getartcraft.com/ | ArtCraft brand mark, owned by the project (licence/trademark terms to be confirmed by the owner) | Used in README only; not shipped in the app |
| `docs/brand/artcraft-mark.svg` | ArtCraft team (project owner) | https://getartcraft.com/ | ArtCraft brand mark, owned by the project (licence/trademark terms to be confirmed by the owner) | Used in README only; not shipped in the app |
| `assets/app-icon/LICENSE.txt` | (licence/readme text) | VectorCraft | MIT OR Apache-2.0 | |
| `assets/app-icon/README.md` | (licence/readme text) | VectorCraft | MIT OR Apache-2.0 | |
| `assets/app-icon/hicolor/128x128/apps/ai.storyteller.vectorcraft.png` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/app-icon/hicolor/16x16/apps/ai.storyteller.vectorcraft.png` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/app-icon/hicolor/24x24/apps/ai.storyteller.vectorcraft.png` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/app-icon/hicolor/256x256/apps/ai.storyteller.vectorcraft.png` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/app-icon/hicolor/32x32/apps/ai.storyteller.vectorcraft.png` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/app-icon/hicolor/48x48/apps/ai.storyteller.vectorcraft.png` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/app-icon/hicolor/512x512/apps/ai.storyteller.vectorcraft.png` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/app-icon/hicolor/64x64/apps/ai.storyteller.vectorcraft.png` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/app-icon/hicolor/scalable/apps/ai.storyteller.vectorcraft.svg` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/app-icon/vectorcraft-1024.png` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/app-icon/vectorcraft-macos-512.png` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/app-icon/vectorcraft-small.svg` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/app-icon/vectorcraft.icns` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/app-icon/vectorcraft.ico` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/app-icon/vectorcraft.svg` | Project owner (ArtCraft team) | Original artwork drawn in ArtCraft for VectorCraft, vectorised and rendered by `packaging/icons.sh` | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) | App icon (engraved dragon) |
| `assets/fonts/Inter-Medium.ttf` | Rasmus Andersson / The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) | Open-source typeface, not product iconography or artwork |
| `assets/fonts/Inter-Regular.ttf` | Rasmus Andersson / The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) | Open-source typeface, not product iconography or artwork |
| `assets/fonts/Inter-SemiBold.ttf` | Rasmus Andersson / The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) | Open-source typeface, not product iconography or artwork |
Generated
+41 -4
View File
@@ -3355,6 +3355,15 @@ dependencies = [
"syn 3.0.6",
]
[[package]]
name = "serde_spanned"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [
"serde_core",
]
[[package]]
name = "shlex"
version = "2.0.1"
@@ -3776,6 +3785,21 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "toml"
version = "1.1.6+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_parser",
"toml_writer",
"winnow",
]
[[package]]
name = "toml_datetime"
version = "1.1.1+spec-1.1.0"
@@ -3806,6 +3830,12 @@ dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
[[package]]
name = "tracing"
version = "0.1.44"
@@ -4026,6 +4056,7 @@ dependencies = [
"vectorcraft-engine",
"vectorcraft-ui-egui",
"webbrowser",
"winresource",
]
[[package]]
@@ -5177,6 +5208,16 @@ dependencies = [
"memchr",
]
[[package]]
name = "winresource"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087"
dependencies = [
"toml",
"version_check",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
@@ -5282,10 +5323,6 @@ name = "xtask"
version = "0.1.0"
dependencies = [
"serde_json",
"vectorcraft-color",
"vectorcraft-doc",
"vectorcraft-geom",
"vectorcraft-render",
]
[[package]]
+6
View File
@@ -7,6 +7,10 @@
</a>
</p>
<p align="center">
<img alt="VectorCraft app icon: an engraved dragon on VectorCraft red" src="assets/app-icon/vectorcraft-1024.png" width="128">
</p>
<h1 align="center">VectorCraft</h1>
<p align="center">
@@ -220,6 +224,8 @@ And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video stud
MIT OR Apache-2.0. Bundled fonts are OFL, Lucide icons are ISC, and all other icons and art are
original. Per-asset attribution is in [`ASSETS.md`](ASSETS.md) (see also [`NOTICE`](NOTICE)).
The app icon (an engraved dragon on VectorCraft red, `#e8573f`) is the owner's original artwork; its
palette and files are in [`assets/app-icon/`](assets/app-icon/README.md).
<sub>VectorCraft is an independent project and is not affiliated with or endorsed by Adobe. "Adobe", "Illustrator", "Photoshop", "Premiere Pro", "Lightroom" and "Acrobat" are trademarks of Adobe Inc., used here only to describe compatibility and workflow familiarity.</sub>
+4
View File
@@ -24,3 +24,7 @@ webbrowser = { workspace = true }
[target.'cfg(target_os = "macos")'.dependencies]
muda = { version = "0.21", default-features = false }
[build-dependencies]
# Icon + VERSIONINFO for vectorcraft.exe; build.rs only uses it when targeting Windows.
winresource = "0.1"
+28
View File
@@ -0,0 +1,28 @@
//! Windows only: embed the app icon and version info (VERSIONINFO) into `vectorcraft.exe`.
//!
//! On every other target this does nothing. A missing resource compiler is a warning, so a
//! cross-compile from macOS or Linux still links, unless `VECTORCRAFT_REQUIRE_WINRES=1` turns it into
//! an error (for release builds).
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=../../assets/app-icon/vectorcraft.ico");
println!("cargo:rerun-if-env-changed=VECTORCRAFT_REQUIRE_WINRES");
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {
return;
}
let mut res = winresource::WindowsResource::new();
res.set_icon("../../assets/app-icon/vectorcraft.ico")
.set("ProductName", "VectorCraft")
.set("FileDescription", "VectorCraft vector illustration editor")
.set("CompanyName", "Learning Machines LLC")
.set("LegalCopyright", "Copyright (c) the VectorCraft authors. MIT OR Apache-2.0.")
.set("OriginalFilename", "vectorcraft.exe")
.set("InternalName", "vectorcraft");
if let Err(e) = res.compile() {
if std::env::var_os("VECTORCRAFT_REQUIRE_WINRES").is_some() {
panic!("embedding Windows resources failed: {e}");
}
println!("cargo:warning=vectorcraft.exe built without icon/version resources: {e}");
}
}
+14 -1
View File
@@ -116,6 +116,17 @@ fn services() -> Services {
}
}
/// The window, Dock, taskbar and app-switcher icon (`assets/app-icon/`, see its README). macOS gets
/// the version with Apple's transparent margin; elsewhere the full-bleed tile. The app ID matches
/// `packaging/linux/ai.storyteller.vectorcraft.desktop` so Wayland docks find the launcher icon.
fn app_icon() -> egui::IconData {
#[cfg(target_os = "macos")]
let png: &[u8] = include_bytes!("../../../assets/app-icon/vectorcraft-macos-512.png");
#[cfg(not(target_os = "macos"))]
let png: &[u8] = include_bytes!("../../../assets/app-icon/hicolor/256x256/apps/ai.storyteller.vectorcraft.png");
eframe::icon_data::from_png_bytes(png).unwrap_or_default()
}
fn main() -> eframe::Result {
let mut control_port: Option<u16> = std::env::var("VECTORCRAFT_CONTROL_PORT").ok().and_then(|p| p.parse().ok());
let mut files = Vec::new();
@@ -138,7 +149,9 @@ fn main() -> eframe::Result {
.with_drag_and_drop(true)
.with_fullsize_content_view(true)
.with_titlebar_shown(false)
.with_title_shown(false),
.with_title_shown(false)
.with_icon(app_icon())
.with_app_id("ai.storyteller.vectorcraft"),
..Default::default()
};
eframe::run_native(
+12
View File
@@ -0,0 +1,12 @@
VectorCraft app icon (vectorcraft.svg, vectorcraft-small.svg and every file derived from them in this
directory: vectorcraft-1024.png, vectorcraft-macos-512.png, vectorcraft.icns, vectorcraft.ico, hicolor/).
Original artwork made for this project by the project owner, drawn in ArtCraft and vectorised for
VectorCraft (no third-party material). Licensed like the rest of VectorCraft, under either of
* Apache License, Version 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
* MIT license (https://opensource.org/licenses/MIT)
at your option.
Regenerate the derived files with packaging/icons.sh.
+51
View File
@@ -0,0 +1,51 @@
# VectorCraft app icon
**Creature:** a regal engraved dragon, VectorCraft's mascot.
**Style:** an engraving-style portrait in the Crafting Apps owl template (the FilmCraft owl): a tight
head-and-shoulders portrait on a full-bleed field, no frame or roundel, with the body running off the tile.
**Palette** (exactly three colours):
| Colour | Hex | Used for |
|---|---|---|
| Ink | `#0b0b0c` | line work and contour |
| Paper | `#efe9dc` | the figure |
| VectorCraft colour | `#e8573f` | the full-bleed field |
**Tile:** `viewBox="0 0 512 512"`, a rounded square with `rx=112`, clipped. The macOS files add Apple's
transparent margin (tile = 824/1024 of the canvas); Windows and Linux use the tile edge to edge.
**Provenance:** the owner's original ArtCraft drawing (2880 px, keyed to the palette), vectorised with
craftrules `assets/logo-options/_tools/vectorize_tile.py`. The drawing is kept in craftrules at
`craftrules/assets/app-icons/vectorcraft/source.png`, not here. `vectorcraft.svg` is the canonical
artwork. Licence: see `LICENSE.txt`.
## Files
| File | What | Used by |
|---|---|---|
| `vectorcraft.svg` | master vector (traced at 2048 px, about 700 KB) | source for everything below |
| `vectorcraft-small.svg` | lighter vector (traced at 1024 px, about 370 KB) | `hicolor/scalable` |
| `vectorcraft-1024.png` | full tile, 1024 px | docs, store listings |
| `vectorcraft-macos-512.png` | tile with Apple margin, 512 px | runtime Dock icon (`apps/vectorcraft/src/main.rs`) |
| `vectorcraft.icns` | macOS icon set, 16 to 1024 px | `cargo xtask bundle` (`CFBundleIconFile`) |
| `vectorcraft.ico` | Windows icon, 16 to 256 px | `apps/vectorcraft/build.rs` (embedded in the `.exe`) |
| `hicolor/<size>/apps/ai.storyteller.vectorcraft.png` | Linux theme icons, 16 to 512 px; the 256 one is also the runtime icon on Windows and Linux | `packaging/linux/ai.storyteller.vectorcraft.desktop` |
| `hicolor/scalable/apps/ai.storyteller.vectorcraft.svg` | Linux scalable icon | as above |
## How it reaches each OS
- **macOS:** the app sets the Dock / app-switcher icon at runtime (`ViewportBuilder::with_icon`); the
`.app` from `cargo xtask bundle` carries `vectorcraft.icns`.
- **Windows:** `build.rs` embeds `vectorcraft.ico` and VERSIONINFO with `winresource` (taskbar, Start
menu, Explorer, Alt-Tab); the runtime icon covers the title bar.
- **Linux:** install `packaging/linux/ai.storyteller.vectorcraft.desktop` to `share/applications/` and
`hicolor/` to `share/icons/`. The app sets its Wayland app ID / X11 class to
`ai.storyteller.vectorcraft`, so the dock matches the window to the launcher.
## Regenerate
Edit or replace `vectorcraft.svg` (and `vectorcraft-small.svg`), then run `packaging/icons.sh`
(needs `resvg`; `iconutil` on macOS for the `.icns`). It writes every PNG, the `.ico` (via
`cargo xtask ico`) and the `.icns`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><clipPath id="t"><rect width="512" height="512" rx="112"/></clipPath></defs><g clip-path="url(#t)"><rect width="512" height="512" fill="#e8573f"/><path fill="#efe9dc" fill-rule="evenodd" d="M103 505C97 505 86 503 85 502C85 502 85 501 85 500C86 499 87 496 87 494C88 492 89 489 90 488C90 487 91 486 91 485C93 475 95 472 103 455C105 451 107 446 108 445C108 443 110 441 111 439C121 423 124 419 126 416C131 409 140 402 146 399C149 397 150 394 149 393L147 392 149 388C150 384 152 378 155 376C155 375 157 373 157 371C158 369 159 368 159 368C160 368 165 361 165 360C165 359 167 353 169 351C169 350 171 348 172 347C173 346 174 344 174 344C174 344 175 343 175 343C176 342 177 342 177 341C177 340 179 339 180 339C181 338 183 337 183 337C183 336 185 335 186 334C187 333 188 332 190 330C192 325 199 317 203 314C210 310 211 309 212 307C212 302 219 294 225 291C230 288 232 287 232 286C232 285 233 282 235 280C236 279 238 277 238 276C239 275 241 273 242 272C244 271 244 271 243 268C242 267 247 260 249 259C254 255 256 253 252 254C251 254 250 254 250 253C250 251 251 246 252 244C253 243 253 242 253 241C253 240 253 239 254 238C255 235 255 234 253 233C252 232 252 232 252 231C252 230 252 229 251 229C251 229 251 228 251 227C252 224 249 218 246 217C243 216 243 216 245 222C247 230 246 236 244 240C242 242 242 242 241 239C241 237 238 232 236 229C235 228 233 227 229 225L224 222 220 223L216 223 211 221C206 218 205 217 203 215C201 212 199 212 199 215C199 218 202 228 203 229C204 230 205 231 205 232C206 234 207 235 208 235C213 238 216 242 212 241C211 241 209 241 207 241C199 239 191 235 185 229C183 227 180 224 178 223C177 223 174 221 172 219C166 214 162 211 161 211C154 213 153 213 154 215C155 215 156 217 156 218C157 218 158 220 159 222C162 226 161 227 157 224C152 221 144 218 138 218C134 218 133 218 132 217C130 215 125 216 126 218C126 219 124 220 122 220C119 219 119 221 123 225C127 229 129 230 134 231C137 231 138 231 138 232C138 233 128 236 123 236C121 236 120 236 120 237C119 238 116 239 112 238C110 237 107 237 102 237C100 237 99 237 98 237C97 236 96 237 97 240C98 246 99 248 100 250C101 251 102 252 102 253C102 254 104 255 105 257C112 263 103 262 93 255C91 254 89 253 89 253C88 254 93 270 96 273C100 277 96 277 88 274C85 272 80 266 78 261C78 260 77 258 76 257C75 255 73 254 73 253C72 249 70 252 70 258C70 262 69 262 67 260C64 256 61 242 63 235C63 233 63 232 61 228C58 223 58 221 59 219C59 217 59 217 57 216C55 214 54 212 53 208C52 207 51 204 50 201L48 197 48 193C49 190 49 188 50 187C50 186 50 184 50 183C50 179 50 178 51 177C52 176 52 174 52 173C52 172 52 171 57 167C65 161 65 160 67 160C69 160 71 159 75 156C76 154 77 154 78 155C78 155 79 156 80 156C82 157 83 157 83 158C83 160 84 160 88 159C89 158 92 158 93 158C95 157 95 157 96 156C97 154 97 154 101 154C106 154 108 153 111 150C113 149 114 148 115 149C118 149 122 143 122 138C123 128 124 126 129 120C132 118 134 115 135 115C135 114 135 114 136 114C136 114 137 113 138 112C139 112 140 111 140 111C142 111 146 106 146 105C146 104 147 103 147 103C149 102 153 98 154 95C157 91 161 89 160 93C160 95 160 95 161 95C161 95 162 95 162 95C161 88 168 77 175 72C179 68 181 69 179 73C177 78 177 82 180 81C181 81 184 76 186 72C188 65 196 56 201 54C204 53 204 53 203 56C203 58 202 60 202 61C202 63 202 65 202 66C202 68 202 69 202 70L202 71 204 70C207 69 208 67 209 63C209 61 210 59 211 58C211 57 213 54 214 52C216 48 218 46 219 46C219 46 221 45 222 44C223 42 224 41 224 41C225 41 226 41 227 40C228 39 229 38 230 38C230 38 231 37 233 36C235 33 239 33 241 34C243 35 244 35 248 34C255 31 257 32 253 37C252 38 250 41 249 43C247 45 246 47 245 48C244 49 243 50 243 51C243 52 243 53 243 54C241 59 242 60 246 57C248 55 249 54 250 54C252 54 254 52 255 50C255 47 256 47 258 46C260 45 261 43 261 40C262 38 263 36 264 36C265 36 267 34 268 33C272 29 275 28 278 27C279 27 281 27 283 27C285 26 288 26 290 25C302 24 301 24 303 22C304 21 306 20 306 20C307 21 313 18 313 17C313 17 315 16 318 15C320 13 323 12 324 12C326 11 334 11 334 12C334 13 333 14 332 15C330 17 328 19 328 20C328 21 327 22 325 23C320 26 319 29 322 29C325 29 324 31 316 39C310 46 308 48 308 49C308 49 307 51 306 52C305 53 304 54 304 55C303 56 302 57 301 57C300 58 300 58 300 60C301 62 300 62 299 64C297 66 296 68 297 69C299 69 304 67 305 65C307 63 307 63 310 63C313 63 315 61 320 56C322 53 323 52 324 52C325 51 326 51 328 50C329 49 331 48 332 48C332 48 335 47 337 46C340 43 341 43 343 42C347 42 353 40 355 39C358 38 368 37 370 38C374 38 373 41 369 42C363 44 360 49 365 50C370 51 372 51 376 47C377 46 379 46 380 45C381 45 383 44 385 43C386 43 389 42 391 41C394 41 397 40 404 37C410 34 428 35 433 38C434 39 433 41 430 41C429 42 427 42 425 43C424 43 422 44 422 44C421 44 420 44 419 45C418 45 417 46 416 46C410 47 401 54 401 57C401 59 401 60 398 62C390 68 388 75 393 78C394 79 394 79 394 80C394 80 394 81 394 81C394 81 392 83 390 84C387 86 385 89 384 90C382 92 380 95 379 95C377 95 373 100 374 101C375 101 375 101 375 102C375 103 376 104 378 104C381 105 379Line truncated

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><clipPath id="t"><rect width="512" height="512" rx="112"/></clipPath></defs><g clip-path="url(#t)"><rect width="512" height="512" fill="#e8573f"/><path fill="#efe9dc" fill-rule="evenodd" d="M103 505C97 505 86 503 85 502C85 502 85 501 85 500C86 499 87 496 87 494C88 492 89 489 90 488C90 487 91 486 91 485C93 475 95 472 103 455C105 451 107 446 108 445C108 443 110 441 111 439C121 423 124 419 126 416C131 409 140 402 146 399C149 397 150 394 149 393L147 392 149 388C150 384 152 378 155 376C155 375 157 373 157 371C158 369 159 368 159 368C160 368 165 361 165 360C165 359 167 353 169 351C169 350 171 348 172 347C173 346 174 344 174 344C174 344 175 343 175 343C176 342 177 342 177 341C177 340 179 339 180 339C181 338 183 337 183 337C183 336 185 335 186 334C187 333 188 332 190 330C192 325 199 317 203 314C210 310 211 309 212 307C212 302 219 294 225 291C230 288 232 287 232 286C232 285 233 282 235 280C236 279 238 277 238 276C239 275 241 273 242 272C244 271 244 271 243 268C242 267 247 260 249 259C254 255 256 253 252 254C251 254 250 254 250 253C250 251 251 246 252 244C253 243 253 242 253 241C253 240 253 239 254 238C255 235 255 234 253 233C252 232 252 232 252 231C252 230 252 229 251 229C251 229 251 228 251 227C252 224 249 218 246 217C243 216 243 216 245 222C247 230 246 236 244 240C242 242 242 242 241 239C241 237 238 232 236 229C235 228 233 227 229 225L224 222 220 223L216 223 211 221C206 218 205 217 203 215C201 212 199 212 199 215C199 218 202 228 203 229C204 230 205 231 205 232C206 234 207 235 208 235C213 238 216 242 212 241C211 241 209 241 207 241C199 239 191 235 185 229C183 227 180 224 178 223C177 223 174 221 172 219C166 214 162 211 161 211C154 213 153 213 154 215C155 215 156 217 156 218C157 218 158 220 159 222C162 226 161 227 157 224C152 221 144 218 138 218C134 218 133 218 132 217C130 215 125 216 126 218C126 219 124 220 122 220C119 219 119 221 123 225C127 229 129 230 134 231C137 231 138 231 138 232C138 233 128 236 123 236C121 236 120 236 120 237C119 238 116 239 112 238C110 237 107 237 102 237C100 237 99 237 98 237C97 236 96 237 97 240C98 246 99 248 100 250C101 251 102 252 102 253C102 254 104 255 105 257C112 263 103 262 93 255C91 254 89 253 89 253C88 254 93 270 96 273C100 277 96 277 88 274C85 272 80 266 78 261C78 260 77 258 76 257C75 255 73 254 73 253C72 249 70 252 70 258C70 262 69 262 67 260C64 256 61 242 63 235C63 233 63 232 61 228C58 223 58 221 59 219C59 217 59 217 57 216C55 214 54 212 53 208C52 207 51 204 50 201L48 197 48 193C49 190 49 188 50 187C50 186 50 184 50 183C50 179 50 178 51 177C52 176 52 174 52 173C52 172 52 171 57 167C65 161 65 160 67 160C69 160 71 159 75 156C76 154 77 154 78 155C78 155 79 156 80 156C82 157 83 157 83 158C83 160 84 160 88 159C89 158 92 158 93 158C95 157 95 157 96 156C97 154 97 154 101 154C106 154 108 153 111 150C113 149 114 148 115 149C118 149 122 143 122 138C123 128 124 126 129 120C132 118 134 115 135 115C135 114 135 114 136 114C136 114 137 113 138 112C139 112 140 111 140 111C142 111 146 106 146 105C146 104 147 103 147 103C149 102 153 98 154 95C157 91 161 89 160 93C160 95 160 95 161 95C161 95 162 95 162 95C161 88 168 77 175 72C179 68 181 69 179 73C177 78 177 82 180 81C181 81 184 76 186 72C188 65 196 56 201 54C204 53 204 53 203 56C203 58 202 60 202 61C202 63 202 65 202 66C202 68 202 69 202 70L202 71 204 70C207 69 208 67 209 63C209 61 210 59 211 58C211 57 213 54 214 52C216 48 218 46 219 46C219 46 221 45 222 44C223 42 224 41 224 41C225 41 226 41 227 40C228 39 229 38 230 38C230 38 231 37 233 36C235 33 239 33 241 34C243 35 244 35 248 34C255 31 257 32 253 37C252 38 250 41 249 43C247 45 246 47 245 48C244 49 243 50 243 51C243 52 243 53 243 54C241 59 242 60 246 57C248 55 249 54 250 54C252 54 254 52 255 50C255 47 256 47 258 46C260 45 261 43 261 40C262 38 263 36 264 36C265 36 267 34 268 33C272 29 275 28 278 27C279 27 281 27 283 27C285 26 288 26 290 25C302 24 301 24 303 22C304 21 306 20 306 20C307 21 313 18 313 17C313 17 315 16 318 15C320 13 323 12 324 12C326 11 334 11 334 12C334 13 333 14 332 15C330 17 328 19 328 20C328 21 327 22 325 23C320 26 319 29 322 29C325 29 324 31 316 39C310 46 308 48 308 49C308 49 307 51 306 52C305 53 304 54 304 55C303 56 302 57 301 57C300 58 300 58 300 60C301 62 300 62 299 64C297 66 296 68 297 69C299 69 304 67 305 65C307 63 307 63 310 63C313 63 315 61 320 56C322 53 323 52 324 52C325 51 326 51 328 50C329 49 331 48 332 48C332 48 335 47 337 46C340 43 341 43 343 42C347 42 353 40 355 39C358 38 368 37 370 38C374 38 373 41 369 42C363 44 360 49 365 50C370 51 372 51 376 47C377 46 379 46 380 45C381 45 383 44 385 43C386 43 389 42 391 41C394 41 397 40 404 37C410 34 428 35 433 38C434 39 433 41 430 41C429 42 427 42 425 43C424 43 422 44 422 44C421 44 420 44 419 45C418 45 417 46 416 46C410 47 401 54 401 57C401 59 401 60 398 62C390 68 388 75 393 78C394 79 394 79 394 80C394 80 394 81 394 81C394 81 392 83 390 84C387 86 385 89 384 90C382 92 380 95 379 95C377 95 373 100 374 101C375 101 375 101 375 102C375 103 376 104 378 104C381 105 379Line truncated

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><clipPath id="t"><rect width="512" height="512" rx="112"/></clipPath></defs><g clip-path="url(#t)"><rect width="512" height="512" fill="#e8573f"/><path fill="#efe9dc" fill-rule="evenodd" d="M101.8 505.4C96.2 504.8 86 502.9 84.7 502.2C84.2 502 84.5 501.2 85.9 498.9C86.3 498.4 86.8 496.6 87.3 494.8C88.4 490 88.7 489.1 89.6 488C90.6 486.9 91 486.1 91 484.8C91 484.4 91.3 483.4 91.6 482.7C91.9 482 92.5 480 92.9 478.4C93.7 474.3 93.8 474.2 96.2 469.1C97.4 466.6 98.7 463.8 99.1 462.8C100.1 460.3 101.7 457.1 103.9 453.1C105.2 450.9 106.2 448.7 106.9 446.6C108 443.2 108.8 441.7 111.5 437.7C112.5 436.2 113.2 434.9 113.2 434.9C113.2 434.8 114.3 433.2 115.6 431.2C116.9 429.2 118.8 426.3 119.8 424.7C124.2 418 128.6 412.3 132.2 408.9C134.9 406.4 143.5 399.8 144.5 399.5C144.8 399.4 145.8 399 146.6 398.7C148.2 398 148.3 398 149 395.1C149.5 393.3 149.4 392.8 148.5 392.8C147.6 392.8 147.5 392.2 148.1 389.9C148.5 388.6 148.7 387.3 148.7 386.9C148.8 385.2 152.6 377.8 154.5 375.9C155.6 374.7 156.4 373.3 157.4 370.9C158.3 368.6 158.7 368 159.6 367.5C160.2 367.1 162.8 364.1 164.2 362C164.6 361.5 164.9 360.7 164.9 360.1C164.9 359.5 165.3 358.3 165.6 357.5C166 356.6 166.6 355.1 166.9 354.2C167.2 353.3 167.9 352 168.3 351.4C168.8 350.9 169.6 349.7 170 348.9C170.4 348.1 171.1 347.3 171.5 347.1C172.3 346.7 173.1 345.9 173.8 344.5C174.2 343.8 174.8 343.2 175.4 342.9C176 342.6 176.7 341.8 177.3 341C178 340 178.4 339.6 179.6 339C180.5 338.6 181.8 337.7 182.6 337C183.5 336.2 185.1 335 186.2 334.2C188.2 332.8 188.4 332.5 189.4 329.9C190.3 327.4 191.1 325.9 192.3 324.9C192.8 324.3 193.8 323.1 194.4 322.2C195.1 321.2 195.9 320.3 196.3 320C197.4 319.5 199.3 317.7 200.1 316.5C200.5 316 201.3 315.3 202 314.9C202.6 314.6 204.6 313.3 206.3 312.1C208.1 310.9 209.9 309.6 210.3 309.4C211.1 308.9 211.1 308.7 211.3 307.2C211.5 304.2 214.4 299.6 217.6 297.1C218.6 296.3 219.8 295.2 220.1 294.6C221 293.1 221.9 292.5 225.2 290.7C231 287.7 231.3 287.4 231.5 286C231.7 284.4 232.9 282.5 235.3 279.8C236.4 278.6 237.8 276.8 238.5 275.9C239.8 273.9 240.4 273.4 241.9 272.4C243.4 271.5 243.6 271.1 243.1 269.3C242.5 267.1 242.8 266.4 244.9 263.5C247.7 259.6 248.1 259.1 249.9 257.9C252.9 256 255.9 252.9 254.3 253.3C252.2 253.9 251 254 250.4 253.6C249.6 253.1 249.6 253.4 250.4 249.6C251.1 246 251.5 245 252.5 243.8C253.4 242.8 253.7 241.8 253.3 241.2C252.8 240.5 253 239.5 253.8 237.5C254.8 234.8 254.7 233 253.5 233C252.5 233 251.9 232.1 251.9 230.8C252 229.8 251.9 229.3 251.5 228.8C251.1 228.3 251 227.9 251.1 226.8C251.2 226.1 251.2 225.4 251.1 225.2C250.9 225 250.7 224.4 250.6 223.9C250.5 223 249 219.8 248 218.2C247.5 217.3 247.4 217.3 246.3 217.3C245.6 217.3 245 217.1 244.8 217C244.7 216.8 244.3 216.8 244.1 216.8C243.6 217 243.7 218 244.9 222.4C245.8 225.6 245.9 225.7 245.9 230.5C245.9 235 245.8 235.5 245.3 237C244.7 238.6 242.6 242 242.3 242C241.9 242 241.5 241.4 241.5 240.8C241.5 239.4 239.8 235.4 237.7 232.1C235.4 228.3 233.5 226.6 229.8 225.2C228.6 224.8 227.1 224 226.4 223.5L225.1 222.6 222.5 222.7C221.1 222.8 218.8 222.9 217.4 222.9L215 222.9 210.9 220.8C206.3 218.5 204.2 216.9 202.9 214.6C202.1 213.1 202.1 213.1 200.7 212.9C199.6 212.8 199.2 213.2 198.9 214.9C198.7 216 200.1 221.4 201.3 224.6C201.8 225.9 202.3 227.3 202.5 227.8C202.6 228.3 203.2 229 203.6 229.5C204.1 230 205 231.4 205.7 232.7C206.8 235.1 206.9 235.2 208.9 235.7C210.3 236.1 215 240.6 214.5 241.1C214 241.7 211.6 241.6 210 240.9C209 240.4 208.3 240.3 206.8 240.3C204.4 240.3 202.6 239.8 199.4 238.3C198 237.7 196 236.9 194.9 236.4C192.2 235.4 188 232.6 186 230.4C181.8 225.9 180.4 224.6 178.1 223.4C175.6 222 173.3 220.3 171.9 218.6C171.4 218 170 216.9 168.8 216.1C167.6 215.3 166 214 165.1 213.3C163 211.4 162.5 211.2 160.8 211.4C160 211.5 158.8 211.6 158.1 211.7C157.4 211.7 156.7 211.9 156.5 212.1C156.3 212.3 155.7 212.5 155.3 212.6C153.5 212.9 153.3 213.9 154.7 215.1C155.2 215.6 155.8 216.4 156 216.9C156.4 218.1 157.5 219.9 159.6 222.6C161.2 224.6 161.5 225.3 161.1 225.6C160.6 225.9 158.1 224.9 156.4 223.8C154.3 222.4 149.5 220.3 145.9 219.2C142.7 218.2 136.9 217.8 136.2 218.5C135.4 219.2 133.7 218.7 132.1 217.2C130.6 215.9 129.2 215.7 127.4 216.6C126.7 216.9 125.9 217.3 125.7 217.3C125.4 217.4 125.3 217.7 125.2 218.2C125.2 219.4 124.5 219.9 122.8 219.6C119.9 219.3 119.9 219.3 119.9 220.2C120 221.2 120.6 222 122.7 224.4C126.3 228.3 128.9 229.9 132.6 230.3C136.8 230.7 138.6 231.3 138.1 232.2C137.2 233.7 126.6 236.3 122.1 236.2C119.9 236.1 119.9 236.1 119.9 236.7C119.9 237.5 119.2 237.9 116.9 238.3C115.3 238.5 115 238.5 112.9 237.9C111.6 237.5 109.7 237.1 108.6 237C107.5 236.9 106.4 236.7 106.2 236.6C106 236.5 105.5 236.6 105.2 236.8C103.7 237.9 99.4 237.9 97.4 236.8C96.5 236.4 96.4 236.4 96.6 237.3C97.9 245.7 99.1 249.3 100.9 251C101.5 251.6 101.7 252 102.4 253.8C102.8 254.8 104 256 105 256.5C105.8 256.9 108.2 259.5 108.2 260C108.3 260.1 108 260.4 107.8 260.6C106.2 262.1 98.7 259.5 93.5 255.7C90.5 253.6 89.2 252.7 89 252.9C88.7 25Line truncated

After

Width:  |  Height:  |  Size: 708 KiB

+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# Regenerate every app icon from assets/app-icon/vectorcraft.svg (the canonical artwork).
#
# Needs: resvg (brew install resvg / cargo install resvg). On macOS, iconutil also writes the
# .icns. The outputs are committed, so building and packaging never need these tools.
#
# packaging/icons.sh
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DIR="$ROOT/assets/app-icon"
SVG="$DIR/vectorcraft.svg"
ID="ai.storyteller.vectorcraft"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
command -v resvg >/dev/null || { echo "error: resvg not found (brew install resvg)" >&2; exit 1; }
# The SVG is a full-bleed 512 tile, used as-is on Windows and Linux. macOS icons follow Apple's grid:
# the tile is 824/1024 of the canvas with a transparent margin, so widen the viewBox around it.
MAC="$TMP/mac.svg"
sed 's/viewBox="0 0 512 512"/viewBox="-62.136 -62.136 636.272 636.272"/' "$SVG" >"$MAC"
grep -q 'viewBox="-62.136' "$MAC" || { echo "error: $SVG has no 512 viewBox" >&2; exit 1; }
render() { resvg -w "$2" -h "$2" "$1" "$3" </dev/null; }
render "$SVG" 1024 "$DIR/vectorcraft-1024.png"
# Runtime window/Dock icon on macOS (embedded by apps/vectorcraft/src/main.rs).
render "$MAC" 512 "$DIR/vectorcraft-macos-512.png"
# Linux hicolor theme (the 256 one is also the runtime icon on Windows and Linux).
for s in 16 24 32 48 64 128 256 512; do
mkdir -p "$DIR/hicolor/${s}x${s}/apps"
render "$SVG" "$s" "$DIR/hicolor/${s}x${s}/apps/$ID.png"
done
mkdir -p "$DIR/hicolor/scalable/apps"
cp "$DIR/vectorcraft-small.svg" "$DIR/hicolor/scalable/apps/$ID.svg"
# Windows .ico.
ICO_PNGS=()
for s in 16 20 24 32 40 48 64 128 256; do
render "$SVG" "$s" "$TMP/ico-$s.png"
ICO_PNGS+=("$TMP/ico-$s.png")
done
(cd "$ROOT" && cargo run -q -p xtask -- ico "$DIR/vectorcraft.ico" "${ICO_PNGS[@]}")
# macOS .icns.
if command -v iconutil >/dev/null; then
SET="$TMP/vectorcraft.iconset"
mkdir -p "$SET"
for s in 16 32 128 256 512; do
render "$MAC" "$s" "$SET/icon_${s}x${s}.png"
render "$MAC" $((s * 2)) "$SET/icon_${s}x${s}@2x.png"
done
iconutil -c icns -o "$DIR/vectorcraft.icns" "$SET"
else
echo "warning: iconutil not found (macOS only); vectorcraft.icns not regenerated" >&2
fi
echo "icons written to $DIR"
@@ -0,0 +1,14 @@
[Desktop Entry]
Type=Application
Name=VectorCraft
GenericName=Vector Graphics Editor
Comment=Draw and edit vector illustrations, SVG and PDF
Exec=vectorcraft %F
TryExec=vectorcraft
Icon=ai.storyteller.vectorcraft
Terminal=false
StartupNotify=true
StartupWMClass=ai.storyteller.vectorcraft
Categories=Graphics;2DGraphics;VectorGraphics;
Keywords=vector;illustration;svg;pdf;drawing;bezier;illustrator;
MimeType=image/svg+xml;application/pdf;
-4
View File
@@ -7,7 +7,3 @@ publish = false
[dependencies]
serde_json = { workspace = true }
vectorcraft-doc = { workspace = true }
vectorcraft-geom = { workspace = true }
vectorcraft-color = { workspace = true }
vectorcraft-render = { workspace = true }
+3 -102
View File
@@ -1,79 +1,9 @@
//! `cargo xtask bundle`: build the release app and wrap it as `dist/VectorCraft.app` (macOS), with an
//! icon rendered by VectorCraft's own renderer.
//! `cargo xtask bundle`: build the release app and wrap it as `dist/VectorCraft.app` (macOS), with the
//! committed app icon `assets/app-icon/vectorcraft.icns` (regenerate it with `packaging/icons.sh`).
use std::path::Path;
use std::process::Command;
use vectorcraft_color::{Color, Gradient, GradientKind, GradientPaint, GradientStop, Paint};
use vectorcraft_doc::{Appearance, Document, Node};
use vectorcraft_geom::{Anchor, PathData, Point, Rect, SubPath, shapes};
use vectorcraft_render::Renderer;
/// The app icon: a warm-gradient rounded square with a white pen nib (our own artwork).
pub fn icon_png(size: u32) -> Vec<u8> {
let s = 1024.0;
let mut d = Document::new(s, s);
let l = d.layers[0].id;
let grad = GradientPaint::new(Gradient {
kind: GradientKind::Linear,
stops: vec![
GradientStop { offset: 0.0, color: Color::from_hex("#ff9a3c").unwrap(), opacity: 1.0, midpoint: 0.5 },
GradientStop { offset: 1.0, color: Color::from_hex("#d4145a").unwrap(), opacity: 1.0, midpoint: 0.5 },
],
});
let mut gp = grad;
gp.angle = -45.0;
let bg = d.alloc_id();
d.insert(
Some(l),
9,
Node::path(
bg,
shapes::rounded_rectangle(Rect::new(100.0, 100.0, 924.0, 924.0), 185.0),
Appearance::basic(Paint::Gradient(Box::new(gp)), Paint::None, 0.0),
),
)
.unwrap();
// Pen nib.
let c = Point::new(512.0, 520.0);
let nib = PathData::single(SubPath::new(
vec![
Anchor::corner(Point::new(c.x, c.y + 250.0)),
Anchor::with_handles(Point::new(c.x - 170.0, c.y - 20.0), Point::new(c.x - 150.0, c.y + 80.0), Point::new(c.x - 170.0, c.y - 90.0)),
Anchor::corner(Point::new(c.x - 95.0, c.y - 230.0)),
Anchor::corner(Point::new(c.x + 95.0, c.y - 230.0)),
Anchor::with_handles(Point::new(c.x + 170.0, c.y - 20.0), Point::new(c.x + 170.0, c.y - 90.0), Point::new(c.x + 150.0, c.y + 80.0)),
],
true,
));
let n = d.alloc_id();
d.insert(Some(l), 9, Node::path(n, nib, Appearance::basic(Paint::solid(Color::WHITE), Paint::None, 0.0))).unwrap();
let hole = d.alloc_id();
d.insert(
Some(l),
9,
Node::path(
hole,
shapes::ellipse(Rect::from_center_size(Point::new(c.x, c.y - 30.0), (74.0, 74.0))),
Appearance::basic(Paint::Gradient(Box::new(GradientPaint::new(Gradient::default()))), Paint::None, 0.0),
),
)
.unwrap();
d.node_mut(hole).unwrap().appearance = Appearance::basic(Paint::solid(Color::from_hex("#e8573f").unwrap()), Paint::None, 0.0);
let slit = d.alloc_id();
d.insert(
Some(l),
9,
Node::path(
slit,
shapes::rectangle(Rect::new(c.x - 9.0, c.y + 5.0, c.x + 9.0, c.y + 245.0)),
Appearance::basic(Paint::solid(Color::from_hex("#e0457a").unwrap()), Paint::None, 0.0),
),
)
.unwrap();
Renderer::new().render_region(&d, Rect::new(0.0, 0.0, s, s), size as f64 / s, false).to_png()
}
pub fn run(root: &Path) -> Result<(), String> {
let status = Command::new(std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
.current_dir(root)
@@ -90,36 +20,7 @@ pub fn run(root: &Path) -> Result<(), String> {
std::fs::create_dir_all(app.join("Resources")).map_err(|e| e.to_string())?;
std::fs::copy(target.join("release/vectorcraft"), app.join("MacOS/VectorCraft")).map_err(|e| format!("copy app: {e}"))?;
std::fs::copy(target.join("release/vectorcraft-cli"), app.join("MacOS/vectorcraft-cli")).map_err(|e| format!("copy cli: {e}"))?;
// Icon set → .icns via iconutil (macOS build tool).
let iconset = root.join("dist/VectorCraft.iconset");
let _ = std::fs::remove_dir_all(&iconset);
std::fs::create_dir_all(&iconset).map_err(|e| e.to_string())?;
for (sz, name) in [
(16, "16x16"),
(32, "16x16@2x"),
(32, "32x32"),
(64, "32x32@2x"),
(128, "128x128"),
(256, "128x128@2x"),
(256, "256x256"),
(512, "256x256@2x"),
(512, "512x512"),
(1024, "512x512@2x"),
] {
std::fs::write(iconset.join(format!("icon_{name}.png")), icon_png(sz)).map_err(|e| e.to_string())?;
}
std::fs::write(root.join("dist/VectorCraft-icon.png"), icon_png(1024)).map_err(|e| e.to_string())?;
let ok = Command::new("iconutil")
.args(["-c", "icns"])
.arg(&iconset)
.arg("-o")
.arg(app.join("Resources/VectorCraft.icns"))
.status()
.map(|s| s.success())
.unwrap_or(false);
if !ok {
eprintln!("warning: iconutil failed or missing; bundle has no icon");
}
std::fs::copy(root.join("assets/app-icon/vectorcraft.icns"), app.join("Resources/VectorCraft.icns")).map_err(|e| format!("copy icon: {e}"))?;
let version = env!("CARGO_PKG_VERSION");
let plist = format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
+64
View File
@@ -0,0 +1,64 @@
//! `cargo xtask ico <out.ico> <png>...`: pack square PNGs (each at most 256 px) into a Windows `.ico`.
//!
//! Every entry is stored as PNG data, which Windows Vista and later read at every size. Used by
//! `packaging/icons.sh`.
use std::path::Path;
/// Width and height from a PNG's IHDR chunk.
fn png_size(png: &[u8]) -> Result<(u32, u32), String> {
if png.len() < 24 || &png[..8] != b"\x89PNG\r\n\x1a\n" || &png[12..16] != b"IHDR" {
return Err("not a PNG".into());
}
let be = |i: usize| u32::from_be_bytes([png[i], png[i + 1], png[i + 2], png[i + 3]]);
Ok((be(16), be(20)))
}
/// Build the `.ico` bytes from PNG images.
pub fn pack(pngs: &[Vec<u8>]) -> Result<Vec<u8>, String> {
let count = u16::try_from(pngs.len()).map_err(|_| "too many images")?;
let mut out = Vec::new();
out.extend_from_slice(&[0, 0, 1, 0]);
out.extend_from_slice(&count.to_le_bytes());
let mut offset = 6 + 16 * pngs.len() as u32;
for png in pngs {
let (w, h) = png_size(png)?;
if w > 256 || h > 256 {
return Err(format!("{w}x{h} is larger than 256 px"));
}
// 256 is stored as 0.
out.push((w % 256) as u8);
out.push((h % 256) as u8);
out.extend_from_slice(&[0, 0]); // palette size, reserved
out.extend_from_slice(&1u16.to_le_bytes()); // colour planes
out.extend_from_slice(&32u16.to_le_bytes()); // bits per pixel
out.extend_from_slice(&(png.len() as u32).to_le_bytes());
out.extend_from_slice(&offset.to_le_bytes());
offset += png.len() as u32;
}
for png in pngs {
out.extend_from_slice(png);
}
Ok(out)
}
pub fn run(args: &[&str]) -> Result<(), String> {
let [out, inputs @ ..] = args else {
return Err("usage: cargo xtask ico <out.ico> <png>...".into());
};
if inputs.is_empty() {
return Err("usage: cargo xtask ico <out.ico> <png>...".into());
}
let pngs = inputs.iter().map(|p| std::fs::read(p).map_err(|e| format!("{p}: {e}"))).collect::<Result<Vec<_>, _>>()?;
std::fs::write(Path::new(out), pack(&pngs)?).map_err(|e| format!("{out}: {e}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_non_png() {
assert!(pack(&[b"nope".to_vec()]).is_err());
}
}
+5 -1
View File
@@ -5,6 +5,7 @@
mod assets;
mod bundle;
mod ico;
mod layers;
mod stats;
@@ -21,7 +22,9 @@ commands:
ci fmt --check, clippy -D warnings, test, assets, layers, wasm (stops at first failure)
corpus [--download]
show where test corpora live; --download fetches PngSuite into corpus/pngsuite
bundle build dist/VectorCraft.app (macOS) with an icon rendered by VectorCraft
bundle build dist/VectorCraft.app (macOS) with assets/app-icon/vectorcraft.icns
ico <out.ico> <png>...
pack PNGs into a Windows .ico (used by packaging/icons.sh)
stats [--exact] count tests and lines per crate (--exact: ask the test harness via `-- --list`)
";
@@ -35,6 +38,7 @@ fn main() -> ExitCode {
Some("ci") => cmd_ci(),
Some("corpus") => cmd_corpus(rest.contains(&"--download")),
Some("bundle") => bundle::run(&root()),
Some("ico") => ico::run(&rest),
Some("stats") => stats::run(&root(), rest.contains(&"--exact")),
Some("-h" | "--help" | "help") | None => {
print!("{USAGE}");