app icon: regal engraved dragon (owner's ArtCraft drawing), Dock/taskbar/launcher wiring, palette docs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
19
ASSETS.md
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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]]
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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`.
|
||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 715 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 365 KiB |
|
After Width: | Height: | Size: 348 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 365 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 708 KiB |
|
|
@ -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;
|
||||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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"?>
|
||||
|
|
|
|||
|
|
@ -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,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}");
|
||||
|
|
|
|||