Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77159bb08e | ||
|
|
047a3bfc6f | ||
|
|
856e36c694 | ||
|
|
721d1118f3 | ||
|
|
3491b99321 | ||
|
|
546a65f77a | ||
|
|
77708ad8de | ||
|
|
129c9ca61e | ||
|
|
ceb6defcf3 | ||
|
|
0aba6ef6e4 | ||
|
|
980372f6d0 |
No files matched your search
@@ -1,5 +1,6 @@
|
||||
# Builds and tests the workspace on FreeBSD, inside a FreeBSD VM on a Linux runner
|
||||
# (GitHub has no FreeBSD runners). GPU tests skip themselves when the VM has no adapter.
|
||||
# The `package` job checks packaging/freebsd/package.sh (the release job runs it for real).
|
||||
name: FreeBSD
|
||||
|
||||
on:
|
||||
@@ -11,6 +12,8 @@ on:
|
||||
- Cargo.lock
|
||||
- "**/Cargo.toml"
|
||||
- .github/workflows/freebsd.yml
|
||||
- packaging/freebsd/**
|
||||
- packaging/env.sh
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
@@ -27,6 +30,13 @@ jobs:
|
||||
timeout-minutes: 300
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# The optional craft-fonts build input (Japanese fonts), so the tests that need it run here
|
||||
# rather than skip. Pinned; bump deliberately (craftrules standards/fonts.md).
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
repository: storytold/craft-fonts
|
||||
ref: abb83316d96aa59c1cf64784289e378fe9fa5695
|
||||
path: craft-fonts
|
||||
- name: Build and test in a FreeBSD VM
|
||||
uses: vmactions/freebsd-vm@c46abacb49f09938ca4e1702d15d836285d694cc # v1
|
||||
with:
|
||||
@@ -44,5 +54,51 @@ jobs:
|
||||
# The checkout is synced in under another owner; let tests that call git read it.
|
||||
git config --global --add safe.directory "*"
|
||||
rustc --version
|
||||
# The checkout above is synced into the VM with the workspace.
|
||||
export CRAFT_FONTS_DIR="$PWD/craft-fonts" CRAFT_FONTS_REQUIRED=1
|
||||
CARGO_PROFILE_DEV_DEBUG=0 cargo build --workspace --all-targets --locked
|
||||
CARGO_PROFILE_DEV_DEBUG=0 cargo test --workspace --locked
|
||||
|
||||
package:
|
||||
name: FreeBSD 14 (package)
|
||||
# The release build takes a while: only for PRs that touch FreeBSD packaging (or dependencies)
|
||||
# and on demand, not on every push to main.
|
||||
if: github.event_name != 'push'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 150
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# Japanese UI/document fonts, embedded by crates/text/build.rs (CRAFT_FONTS_DIR in the VM).
|
||||
# Bump the commit deliberately; see craftrules standards/fonts.md.
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
repository: storytold/craft-fonts
|
||||
ref: abb83316d96aa59c1cf64784289e378fe9fa5695
|
||||
path: craft-fonts
|
||||
- name: Package in a FreeBSD VM
|
||||
uses: vmactions/freebsd-vm@c46abacb49f09938ca4e1702d15d836285d694cc
|
||||
with:
|
||||
release: "14.3"
|
||||
usesh: true
|
||||
mem: 12288
|
||||
prepare: |
|
||||
pkg install -y bash curl git pkgconf cmake python3 libxkbcommon wayland libX11 libXcursor libXrandr libXi libxcb mesa-libs vulkan-loader gtk3 fontconfig freetype2 alsa-lib
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable
|
||||
run: |
|
||||
. "$HOME/.cargo/env"
|
||||
git config --global --add safe.directory "$PWD"
|
||||
# Build outside the checkout so only dist/ is copied back out of the VM.
|
||||
# The craft-fonts checkout is synced into the VM with the workspace.
|
||||
export CRAFT_FONTS_DIR="$PWD/craft-fonts" CRAFT_FONTS_REQUIRED=1
|
||||
export CARGO_TARGET_DIR=/var/tmp/vectorcraft-target CARGO_BUILD_JOBS=4
|
||||
bash packaging/freebsd/package.sh
|
||||
# Every part the package must have, the craft-fonts licences included.
|
||||
tar -tzf dist/release/vectorcraft-*-freebsd-x86_64.tar.gz >/var/tmp/contents.txt
|
||||
for want in '/bin/vectorcraft$' '/bin/vectorcraft-cli$' '/share/applications/.*\.desktop$' '/share/icons/hicolor/' '/share/doc/vectorcraft/NOTICE$' '/share/doc/vectorcraft/OFL-.*\.txt$'; do
|
||||
grep -Eq "$want" /var/tmp/contents.txt || { echo "missing from the package: $want"; exit 1; }
|
||||
done
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: freebsd-package
|
||||
path: dist/release/*
|
||||
if-no-files-found: error
|
||||
@@ -70,6 +70,13 @@ jobs:
|
||||
VECTORCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# Japanese UI/document fonts, embedded by crates/text/build.rs (CRAFT_FONTS_DIR on the build
|
||||
# step). Bump the commit deliberately; see craftrules standards/fonts.md.
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
repository: storytold/craft-fonts
|
||||
ref: abb83316d96aa59c1cf64784289e378fe9fa5695
|
||||
path: craft-fonts
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: aarch64-apple-darwin, x86_64-apple-darwin
|
||||
@@ -90,6 +97,8 @@ jobs:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
|
||||
CRAFT_FONTS_REQUIRED: "1"
|
||||
run: packaging/macos/package.sh --arch universal
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -112,11 +121,21 @@ jobs:
|
||||
include:
|
||||
- { 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:
|
||||
VECTORCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
VECTORCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# Japanese UI/document fonts, embedded by crates/text/build.rs (CRAFT_FONTS_DIR on the build
|
||||
# step). Bump the commit deliberately; see craftrules standards/fonts.md.
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
repository: storytold/craft-fonts
|
||||
ref: abb83316d96aa59c1cf64784289e378fe9fa5695
|
||||
path: craft-fonts
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
@@ -141,6 +160,8 @@ jobs:
|
||||
AZURE_SIGNING_ENDPOINT: ${{ secrets.AZURE_SIGNING_ENDPOINT }}
|
||||
AZURE_SIGNING_ACCOUNT: ${{ secrets.AZURE_SIGNING_ACCOUNT }}
|
||||
AZURE_CERT_PROFILE: ${{ secrets.AZURE_CERT_PROFILE }}
|
||||
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
|
||||
CRAFT_FONTS_REQUIRED: "1"
|
||||
run: ./packaging/windows/package.ps1 -Arch ${{ matrix.arch }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -167,6 +188,13 @@ jobs:
|
||||
NFPM_VERSION: 2.47.0
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# Japanese UI/document fonts, embedded by crates/text/build.rs (CRAFT_FONTS_DIR on the build
|
||||
# step). Bump the commit deliberately; see craftrules standards/fonts.md.
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
repository: storytold/craft-fonts
|
||||
ref: abb83316d96aa59c1cf64784289e378fe9fa5695
|
||||
path: craft-fonts
|
||||
- name: Install build and packaging tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
@@ -184,6 +212,9 @@ jobs:
|
||||
if: inputs.version != ''
|
||||
run: cargo xtask version set "$VECTORCRAFT_VERSION"
|
||||
- name: Build and package (AppImage, deb, rpm, tar.gz)
|
||||
env:
|
||||
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
|
||||
CRAFT_FONTS_REQUIRED: "1"
|
||||
run: packaging/linux/package.sh
|
||||
- name: Check the packages
|
||||
run: |
|
||||
@@ -201,6 +232,56 @@ jobs:
|
||||
path: dist/release/*
|
||||
if-no-files-found: error
|
||||
|
||||
freebsd:
|
||||
name: FreeBSD x86_64
|
||||
needs: version
|
||||
# GitHub has no FreeBSD runners: build in a FreeBSD 14.3 VM (same image as freebsd.yml).
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 150
|
||||
environment: release
|
||||
env:
|
||||
VECTORCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
VECTORCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# Japanese UI/document fonts, embedded by crates/text/build.rs (CRAFT_FONTS_DIR in the VM).
|
||||
# Bump the commit deliberately; see craftrules standards/fonts.md.
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
repository: storytold/craft-fonts
|
||||
ref: abb83316d96aa59c1cf64784289e378fe9fa5695
|
||||
path: craft-fonts
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
if: inputs.version != ''
|
||||
- name: Apply version override
|
||||
if: inputs.version != ''
|
||||
run: cargo xtask version set "$VECTORCRAFT_VERSION"
|
||||
- name: Build and package in a FreeBSD VM
|
||||
# v1.5.9, pinned by commit.
|
||||
uses: vmactions/freebsd-vm@c46abacb49f09938ca4e1702d15d836285d694cc
|
||||
with:
|
||||
release: "14.3"
|
||||
usesh: true
|
||||
mem: 12288
|
||||
# Passed through by name; never templated into the script.
|
||||
envs: "VECTORCRAFT_VERSION VECTORCRAFT_BUILD_DATE VECTORCRAFT_BUILD_SHA"
|
||||
prepare: |
|
||||
pkg install -y bash curl git pkgconf cmake python3 libxkbcommon wayland libX11 libXcursor libXrandr libXi libxcb mesa-libs vulkan-loader gtk3 fontconfig freetype2 alsa-lib
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable
|
||||
run: |
|
||||
. "$HOME/.cargo/env"
|
||||
git config --global --add safe.directory "$PWD"
|
||||
# Build outside the checkout so only dist/ is copied back out of the VM.
|
||||
# The craft-fonts checkout is synced into the VM with the workspace.
|
||||
export CRAFT_FONTS_DIR="$PWD/craft-fonts" CRAFT_FONTS_REQUIRED=1
|
||||
export CARGO_TARGET_DIR=/var/tmp/vectorcraft-target CARGO_BUILD_JOBS=4
|
||||
bash packaging/freebsd/package.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-freebsd
|
||||
path: dist/release/*
|
||||
if-no-files-found: error
|
||||
|
||||
web:
|
||||
name: Web (wasm)
|
||||
needs: version
|
||||
@@ -212,6 +293,13 @@ jobs:
|
||||
TRUNK_VERSION: 0.21.14
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# Japanese UI/document fonts, embedded by crates/text/build.rs (CRAFT_FONTS_DIR on the build
|
||||
# step). Bump the commit deliberately; see craftrules standards/fonts.md.
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
repository: storytold/craft-fonts
|
||||
ref: abb83316d96aa59c1cf64784289e378fe9fa5695
|
||||
path: craft-fonts
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
@@ -227,6 +315,9 @@ jobs:
|
||||
if: inputs.version != ''
|
||||
run: cargo xtask version set "$VECTORCRAFT_VERSION"
|
||||
- name: Build and zip
|
||||
env:
|
||||
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
|
||||
CRAFT_FONTS_REQUIRED: "1"
|
||||
run: packaging/web/package.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -236,7 +327,7 @@ jobs:
|
||||
|
||||
release:
|
||||
name: Draft GitHub Release
|
||||
needs: [version, macos, windows, linux, web]
|
||||
needs: [version, macos, windows, linux, freebsd, web]
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
permissions:
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
# Windows on ARM: package the ARM64 release the way release.yml does (cross-compiled on an x64
|
||||
# runner, unsigned here), then install that MSI on a Windows 11 ARM64 runner, check the installed
|
||||
# binaries are ARM64, run the CLI natively, and uninstall. (A native ARM64 `cargo test` job is left
|
||||
# out for now: PrintCraft saw an intermittent access violation in egui UI tests there.)
|
||||
name: Windows ARM64
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
paths:
|
||||
- Cargo.toml
|
||||
- Cargo.lock
|
||||
- "**/Cargo.toml"
|
||||
- "**/build.rs"
|
||||
- .github/workflows/windows-arm64.yml
|
||||
- packaging/windows/**
|
||||
schedule:
|
||||
- cron: "41 4 * * 1" # weekly, so drift in the runner images shows up
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: windows-arm64-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
jobs:
|
||||
package:
|
||||
name: package (cross-compiled on x64)
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@89b12181fb390509a0842a86cc55eeb8eb928c1d # stable
|
||||
with:
|
||||
targets: aarch64-pc-windows-msvc
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
|
||||
with:
|
||||
key: windows-arm64-package
|
||||
- name: Install WiX
|
||||
shell: pwsh
|
||||
run: dotnet tool install --global wix --version 5.0.2
|
||||
- name: Build and package (unsigned)
|
||||
shell: pwsh
|
||||
env:
|
||||
DIST: ${{ github.workspace }}\dist\arm64
|
||||
run: ./packaging/windows/package.ps1 -Arch arm64
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: vectorcraft-windows-arm64
|
||||
path: dist/arm64/*
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
install:
|
||||
name: install and run on ARM64
|
||||
needs: package
|
||||
runs-on: windows-11-arm
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: vectorcraft-windows-arm64
|
||||
path: dist
|
||||
- name: Install the MSI, check it, run it, uninstall it
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$msi = (Get-ChildItem dist/*.msi | Select-Object -First 1).FullName
|
||||
$p = Start-Process msiexec.exe -ArgumentList '/i', "`"$msi`"", '/qn', '/l*v', 'install.log' -Wait -PassThru
|
||||
if ($p.ExitCode -ne 0) { Get-Content install.log -Tail 60; throw "msiexec /i exited $($p.ExitCode)" }
|
||||
$dir = Join-Path $env:ProgramFiles 'VectorCraft'
|
||||
foreach ($exe in 'vectorcraft.exe', 'vectorcraft-cli.exe') {
|
||||
$path = Join-Path $dir $exe
|
||||
if (-not (Test-Path $path)) { throw "$exe not installed in $dir" }
|
||||
$bytes = [System.IO.File]::ReadAllBytes($path)
|
||||
$machine = [BitConverter]::ToUInt16($bytes, [BitConverter]::ToInt32($bytes, 0x3C) + 4)
|
||||
if ($machine -ne 0xAA64) { throw "$exe is for machine 0x$('{0:X}' -f $machine), not ARM64" }
|
||||
Write-Output "ok $path (ARM64)"
|
||||
}
|
||||
# The installed CLI runs natively on ARM64.
|
||||
$cli = Join-Path $dir 'vectorcraft-cli.exe'
|
||||
$out = & $cli --version 2>&1 | Out-String
|
||||
if ($LASTEXITCODE -ne 0) { Write-Output $out; throw "vectorcraft-cli --version exited $LASTEXITCODE" }
|
||||
Write-Output "ok vectorcraft-cli --version: $($out.Trim())"
|
||||
$p = Start-Process msiexec.exe -ArgumentList '/x', "`"$msi`"", '/qn' -Wait -PassThru
|
||||
if ($p.ExitCode -ne 0) { throw "msiexec /x exited $($p.ExitCode)" }
|
||||
if (Test-Path (Join-Path $dir 'vectorcraft.exe')) { throw 'uninstall left vectorcraft.exe behind' }
|
||||
Write-Output 'ok uninstalled'
|
||||
@@ -5,3 +5,5 @@ plan/
|
||||
/corpus
|
||||
/log
|
||||
.DS_Store
|
||||
# A craft-fonts checkout for CRAFT_FONTS_DIR (CI and release workflows check it out here)
|
||||
/craft-fonts
|
||||
@@ -22,6 +22,7 @@ Formerly **DrawCraft** (renamed 2026-10-01): old `.drawcraft` files and `"format
|
||||
- Creative Commons with redistribution allowed.
|
||||
- **Every asset file must have a row in [`ASSETS.md`](ASSETS.md)** (path, author, source URL, licence, notes). Put licence texts next to the assets (e.g. `assets/fonts/OFL-*.txt`) and summarize them in `NOTICE`.
|
||||
- `cargo xtask assets` (run by `cargo xtask ci`) fails on any unattributed asset.
|
||||
- **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this repo** (rules: craftrules [`standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md)). Never commit a font file here; add new fonts to craft-fonts. The app uses it only through the optional `CRAFT_FONTS_DIR` build option (`CRAFT_FONTS_DIR="$PWD/../craft-fonts" cargo xtask ci`; use an absolute path), read by `crates/text/build.rs` into `vectorcraft_text::CRAFT_FONTS`; never add it to a `Cargo.toml`. Everything must build, test and run with `CRAFT_FONTS` empty, and tests of its glyphs skip when it is. Details: `docs/development.md` › Fonts.
|
||||
- Code and original assets are MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`, `NOTICE`). The ArtCraft logos in `docs/brand/` are trademarks, not open source, under `docs/brand/LICENSE-brand.txt`: never modify them or use them outside VectorCraft.
|
||||
- Prefer art generated in code for defaults (swatches, brushes, symbols, patterns, cursors). If the provenance of an asset is unclear, don't add it.
|
||||
- Reference screenshots of Illustrator stay local under the gitignored `plan/` and are never committed, published or used as assets.
|
||||
|
||||
@@ -6,6 +6,10 @@ Every non-code asset in this repository (icons, images, fonts, example art, colo
|
||||
|
||||
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`).
|
||||
|
||||
**Font files are never committed here.** Fonts shared by the Crafting Apps live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts) (rules: craftrules [`standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md)). The small Latin UI/document fonts below predate that rule and stay.
|
||||
|
||||
**Optional build input: craft-fonts.** Builds made with `CRAFT_FONTS_DIR=<craft-fonts checkout>` (all official releases) embed the Japanese fonts listed in its [`ATTRIBUTION.md`](https://github.com/storytold/craft-fonts/blob/main/ATTRIBUTION.md): BIZ UDPGothic Regular and Bold, Shippori Mincho Regular and BIZ UDMincho Regular (web builds: BIZ UDPGothic Regular only), all under the SIL Open Font License 1.1. They are not files in this repository; release packages carry each one's `OFL-<family>.txt`. Builds without `CRAFT_FONTS_DIR` embed none of them.
|
||||
|
||||
| Asset | Author | Source | Licence | Notes |
|
||||
|---|---|---|---|---|
|
||||
| `docs/brand/artcraft-logo-white.png` | ArtCraft team (project owner) | https://getartcraft.com/ | ArtCraft trademark, see docs/brand/LICENSE-brand.txt (not open source) | Used in README only; not shipped in the app |
|
||||
@@ -46,8 +50,6 @@ Generated-in-code art is original and has no file to list. This covers the defau
|
||||
| `assets/fonts/OFL-JetBrainsMono.txt` | (licence text) | upstream project | — | |
|
||||
| `assets/fonts/OFL-SourceSans3.txt` | (licence text) | upstream project | — | |
|
||||
| `assets/fonts/OFL-SourceSerif4.txt` | (licence text) | upstream project | — | |
|
||||
| `assets/fonts/ShipporiMincho-Regular.ttf` | The Shippori Mincho Project Authors (FONTDASU) | https://github.com/google/fonts/tree/9710da1eacb3be272583c3224dcb70f9da6eadbb/ofl/shipporimincho | OFL-1.1 (`assets/fonts/OFL-ShipporiMincho.txt`) | Japanese UI/document fallback; SHA-256 `769b5269f0f9bc6534b352c0e6bd856a566e03ff788f107191c2d835863570b2` |
|
||||
| `assets/fonts/OFL-ShipporiMincho.txt` | The Shippori Mincho Project Authors (FONTDASU) | https://github.com/google/fonts/blob/9710da1eacb3be272583c3224dcb70f9da6eadbb/ofl/shipporimincho/OFL.txt | OFL-1.1 | Licence for Shippori Mincho |
|
||||
| `assets/icons/LICENSE-lucide.txt` | (licence text) | upstream project | — | |
|
||||
| `assets/icons/align-center-horizontal.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
|
||||
| `assets/icons/align-center-vertical.svg` | Lucide Icons and Contributors | https://lucide.dev (lucide-static v1.49.0; header kept in file) | ISC (`assets/icons/LICENSE-lucide.txt`) | |
|
||||
|
||||
@@ -22,6 +22,7 @@ Formerly **DrawCraft** (renamed 2026-10-01): old `.drawcraft` files and `"format
|
||||
- Creative Commons with redistribution allowed.
|
||||
- **Every asset file must have a row in [`ASSETS.md`](ASSETS.md)** (path, author, source URL, licence, notes). Put licence texts next to the assets (e.g. `assets/fonts/OFL-*.txt`) and summarize them in `NOTICE`.
|
||||
- `cargo xtask assets` (run by `cargo xtask ci`) fails on any unattributed asset.
|
||||
- **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this repo** (rules: craftrules [`standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md)). Never commit a font file here; add new fonts to craft-fonts. The app uses it only through the optional `CRAFT_FONTS_DIR` build option (`CRAFT_FONTS_DIR="$PWD/../craft-fonts" cargo xtask ci`; use an absolute path), read by `crates/text/build.rs` into `vectorcraft_text::CRAFT_FONTS`; never add it to a `Cargo.toml`. Everything must build, test and run with `CRAFT_FONTS` empty, and tests of its glyphs skip when it is. Details: `docs/development.md` › Fonts.
|
||||
- Code and original assets are MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`, `NOTICE`). The ArtCraft logos in `docs/brand/` are trademarks, not open source, under `docs/brand/LICENSE-brand.txt`: never modify them or use them outside VectorCraft.
|
||||
- Prefer art generated in code for defaults (swatches, brushes, symbols, patterns, cursors). If the provenance of an asset is unclear, don't add it.
|
||||
- Reference screenshots of Illustrator stay local under the gitignored `plan/` and are never committed, published or used as assets.
|
||||
|
||||
@@ -30,6 +30,7 @@ based in part on the work of the Independent JPEG Group.
|
||||
WebAssembly plug-ins run in the wasmi interpreter (MIT OR Apache-2.0); its wasmparser dependency is
|
||||
under the Apache License 2.0 with the LLVM exception.
|
||||
|
||||
Shippori Mincho: Copyright 2021 The Shippori Mincho Project Authors
|
||||
(https://github.com/fontdasu/ShipporiMincho). SIL Open Font License 1.1;
|
||||
see assets/fonts/OFL-ShipporiMincho.txt.
|
||||
Builds made with craft-fonts (the CRAFT_FONTS_DIR build option; all official releases) embed the
|
||||
fonts listed in https://github.com/storytold/craft-fonts/blob/main/ATTRIBUTION.md (today BIZ
|
||||
UDPGothic, Shippori Mincho and BIZ UDMincho; web builds BIZ UDPGothic Regular only) under the SIL
|
||||
Open Font License 1.1. Their license texts ship with the release packages as OFL-<family>.txt.
|
||||
@@ -154,6 +154,10 @@ cd apps/vectorcraft-web && trunk build --release # web build → dist
|
||||
cargo xtask ci # fmt, clippy, tests, layering, wasm, vendor names
|
||||
```
|
||||
|
||||
Japanese fonts come from [storytold/craft-fonts](https://github.com/storytold/craft-fonts), an optional
|
||||
build input (releases always use it): `CRAFT_FONTS_DIR="$PWD/../craft-fonts" cargo run --release -p vectorcraft` (an absolute path).
|
||||
Without it, Japanese text uses the installed system fonts. See [`docs/development.md`](docs/development.md#fonts-craft-fonts-optional-build-input).
|
||||
|
||||
### Use it from Claude Code and other agents
|
||||
|
||||
Register the MCP server with Claude Code:
|
||||
@@ -241,7 +245,9 @@ VectorCraft is dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-AP
|
||||
Copyright (c) 2026 ArtCraft Team and the VectorCraft contributors. Required notices are in [NOTICE](NOTICE).
|
||||
|
||||
Bundled fonts, icons, images and other assets keep their own open licenses; each one is listed
|
||||
with its author, source and license in [ASSETS.md](ASSETS.md).
|
||||
with its author, source and license in [ASSETS.md](ASSETS.md). Release builds also embed the
|
||||
Japanese fonts of [craft-fonts](https://github.com/storytold/craft-fonts/blob/main/ATTRIBUTION.md)
|
||||
(SIL Open Font License 1.1).
|
||||
|
||||
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).
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
Copyright 2021 The Shippori Mincho Project Authors (https://github.com/fontdasu/ShipporiMincho)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
@@ -0,0 +1,74 @@
|
||||
//! The optional craft-fonts build input (https://github.com/storytold/craft-fonts,
|
||||
//! `docs/integration.md`): with `CRAFT_FONTS_DIR=<checkout>` set, every font in its
|
||||
//! `fonts/manifest.txt` is embedded as `CRAFT_FONTS`; unset, `CRAFT_FONTS` is empty and the app
|
||||
//! uses its own and the installed fonts. `CRAFT_FONTS_REQUIRED=1` turns a bad checkout into a build
|
||||
//! error (release builds). Nothing is fetched: the build reads only the local checkout.
|
||||
//!
|
||||
//! Web (wasm32) builds embed only the UI font, BIZ UDPGothic Regular (~4.7 MB): the browser has no
|
||||
//! system fonts, and all four fonts (~24 MB) would push the `.wasm` past static hosts' per-file
|
||||
//! limits (Cloudflare Pages: 25 MiB).
|
||||
|
||||
use std::fmt::Write as _;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn main() {
|
||||
println!("cargo::rerun-if-env-changed=CRAFT_FONTS_DIR");
|
||||
println!("cargo::rerun-if-env-changed=CRAFT_FONTS_REQUIRED");
|
||||
let mut src = String::from("pub static CRAFT_FONTS: &[CraftFont] = &[\n");
|
||||
if let Some(dir) = std::env::var_os("CRAFT_FONTS_DIR").map(PathBuf::from) {
|
||||
let dir = from_workspace(dir);
|
||||
match craft_fonts(&dir) {
|
||||
Ok(entries) => src.push_str(&entries),
|
||||
Err(e) if std::env::var_os("CRAFT_FONTS_REQUIRED").is_some() => {
|
||||
println!("cargo::error=CRAFT_FONTS_DIR={}: {e}", dir.display());
|
||||
}
|
||||
Err(e) => println!("cargo::warning=building without craft-fonts: CRAFT_FONTS_DIR={}: {e}", dir.display()),
|
||||
}
|
||||
}
|
||||
src.push_str("];\n");
|
||||
let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap_or_default()).join("craft_fonts.rs");
|
||||
if let Err(e) = std::fs::write(&out, src) {
|
||||
println!("cargo::error=writing {}: {e}", out.display());
|
||||
}
|
||||
}
|
||||
|
||||
/// A relative `CRAFT_FONTS_DIR` is taken from the workspace root (where `cargo` is usually run),
|
||||
/// not from this crate's directory, which is where build scripts run.
|
||||
fn from_workspace(dir: PathBuf) -> PathBuf {
|
||||
if dir.is_absolute() {
|
||||
return dir;
|
||||
}
|
||||
let crate_dir = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap_or_default());
|
||||
crate_dir.join("../..").join(dir)
|
||||
}
|
||||
|
||||
/// The one craft-fonts face web builds embed (family, style).
|
||||
const WEB_FONT: (&str, &str) = ("BIZ UDPGothic", "Regular");
|
||||
|
||||
/// One `CraftFont { .. }` initialiser per manifest line.
|
||||
fn craft_fonts(dir: &Path) -> Result<String, String> {
|
||||
let manifest = dir.join("fonts/manifest.txt");
|
||||
println!("cargo::rerun-if-changed={}", manifest.display());
|
||||
let text = std::fs::read_to_string(&manifest).map_err(|e| format!("{}: {e}", manifest.display()))?;
|
||||
let wasm = std::env::var("CARGO_CFG_TARGET_ARCH").is_ok_and(|a| a == "wasm32");
|
||||
let mut out = String::new();
|
||||
for line in text.lines().map(str::trim).filter(|l| !l.is_empty() && !l.starts_with('#')) {
|
||||
let f: Vec<&str> = line.split(" | ").map(str::trim).collect();
|
||||
let [family, style, file, scripts, ..] = f.as_slice() else {
|
||||
return Err(format!("malformed manifest line: {line}"));
|
||||
};
|
||||
if wasm && !(*family == WEB_FONT.0 && *style == WEB_FONT.1) {
|
||||
continue;
|
||||
}
|
||||
let path = dir.join(file).canonicalize().map_err(|e| format!("{file}: {e}"))?;
|
||||
println!("cargo::rerun-if-changed={}", path.display());
|
||||
let scripts: Vec<String> = scripts.split(',').map(|s| format!("{:?}", s.trim())).collect();
|
||||
let _ = writeln!(
|
||||
out,
|
||||
" CraftFont {{ family: {family:?}, style: {style:?}, scripts: &[{}], bytes: include_bytes!({:?}) }},",
|
||||
scripts.join(", "),
|
||||
path.display().to_string(),
|
||||
);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//! Fonts from the optional craft-fonts build input (<https://github.com/storytold/craft-fonts>).
|
||||
//!
|
||||
//! Built with `CRAFT_FONTS_DIR=<craft-fonts checkout>`, [`CRAFT_FONTS`] holds every font in its
|
||||
//! manifest (today the Japanese UI and document fonts); built without it, it is empty and Japanese
|
||||
//! text falls back to the installed system fonts. Release builds always set it. See `build.rs`
|
||||
//! and craftrules `standards/fonts.md`.
|
||||
|
||||
/// A font from the optional craft-fonts build input (empty unless built with `CRAFT_FONTS_DIR`).
|
||||
pub struct CraftFont {
|
||||
pub family: &'static str,
|
||||
pub style: &'static str,
|
||||
/// ISO 15924 scripts the font is for, e.g. `"Jpan"`.
|
||||
pub scripts: &'static [&'static str],
|
||||
pub bytes: &'static [u8],
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/craft_fonts.rs"));
|
||||
|
||||
impl CraftFont {
|
||||
/// Is the font for Japanese text?
|
||||
pub fn is_japanese(&self) -> bool {
|
||||
self.scripts.contains(&"Jpan")
|
||||
}
|
||||
/// A serif (Mincho) family, for document text.
|
||||
pub fn is_mincho(&self) -> bool {
|
||||
self.family.contains("Mincho")
|
||||
}
|
||||
/// A sans (Gothic) family, for UI text.
|
||||
pub fn is_gothic(&self) -> bool {
|
||||
self.family.contains("Gothic")
|
||||
}
|
||||
/// A bold face.
|
||||
pub fn is_bold(&self) -> bool {
|
||||
crate::style_weight(self.style) >= 600.0
|
||||
}
|
||||
}
|
||||
|
||||
/// The craft-fonts faces for Japanese text, best for document text first: Mincho (serif) families,
|
||||
/// then the others, regular weights before bold. Empty when built without craft-fonts.
|
||||
pub fn japanese_document_fonts() -> Vec<&'static CraftFont> {
|
||||
let mut v: Vec<&'static CraftFont> = CRAFT_FONTS.iter().filter(|f| f.is_japanese()).collect();
|
||||
v.sort_by_key(|f| (!f.is_mincho(), f.is_bold()));
|
||||
v
|
||||
}
|
||||
|
||||
/// The craft-fonts faces for Japanese text, best for UI text first: Gothic (sans) families, then
|
||||
/// the others; `bold` puts bold faces before regular ones. Empty when built without craft-fonts.
|
||||
pub fn japanese_ui_fonts(bold: bool) -> Vec<&'static CraftFont> {
|
||||
let mut v: Vec<&'static CraftFont> = CRAFT_FONTS.iter().filter(|f| f.is_japanese()).collect();
|
||||
v.sort_by_key(|f| (!f.is_gothic(), f.is_bold() != bold));
|
||||
v
|
||||
}
|
||||
@@ -16,9 +16,6 @@ use skrifa::{GlyphId, MetadataProvider};
|
||||
/// The family used when a requested family is unknown (Illustrator's Myriad Pro analogue).
|
||||
pub const FALLBACK_FAMILY: &str = "Source Sans 3";
|
||||
|
||||
/// Bundled Japanese font bytes, shared with UI glyph fallback.
|
||||
pub static SHIPPORI_MINCHO_REGULAR: &[u8] = include_bytes!("../../../assets/fonts/ShipporiMincho-Regular.ttf");
|
||||
|
||||
static BUNDLED: &[&[u8]] = &[
|
||||
include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf"),
|
||||
include_bytes!("../../../assets/fonts/SourceSans3-Semibold.ttf"),
|
||||
@@ -29,7 +26,6 @@ static BUNDLED: &[&[u8]] = &[
|
||||
include_bytes!("../../../assets/fonts/Inter-Medium.ttf"),
|
||||
include_bytes!("../../../assets/fonts/Inter-SemiBold.ttf"),
|
||||
include_bytes!("../../../assets/fonts/JetBrainsMono-Regular.ttf"),
|
||||
SHIPPORI_MINCHO_REGULAR,
|
||||
];
|
||||
|
||||
enum FontBytes {
|
||||
@@ -387,7 +383,10 @@ impl FontDb {
|
||||
/// A database holding the bundled fonts, whose system font scan reads `font_dirs`.
|
||||
pub fn with_font_dirs(font_dirs: Vec<PathBuf>) -> Self {
|
||||
let mut faces = Vec::new();
|
||||
for data in BUNDLED {
|
||||
// The bundled fonts, then the Japanese craft-fonts faces (when built with them), Mincho
|
||||
// first: fallbacks for Japanese text after the requested and bundled fonts.
|
||||
let craft = crate::craft_fonts::japanese_document_fonts().into_iter().map(|f| f.bytes);
|
||||
for data in BUNDLED.iter().copied().chain(craft) {
|
||||
for (i, family, style) in enumerate_faces(data) {
|
||||
if let Some(f) = make_face(FontBytes::Static(data), i, family, style, None) {
|
||||
faces.push(Arc::new(f));
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod composer;
|
||||
pub mod craft_fonts;
|
||||
pub mod edit;
|
||||
pub mod embed;
|
||||
mod features;
|
||||
@@ -19,8 +20,9 @@ mod layout;
|
||||
mod shape;
|
||||
pub mod thread;
|
||||
|
||||
pub use craft_fonts::{CRAFT_FONTS, CraftFont};
|
||||
pub use features::OtFeatures;
|
||||
pub use fontdb::{FALLBACK_FAMILY, FontDb, FontFace, SHIPPORI_MINCHO_REGULAR, style_weight, system_font_dirs};
|
||||
pub use fontdb::{FALLBACK_FAMILY, FontDb, FontFace, style_weight, system_font_dirs};
|
||||
use kurbo::{Affine, BezPath, Point, Rect, Vec2};
|
||||
pub use layout::{layout, layout_with};
|
||||
pub use vectorcraft_doc::TextObject;
|
||||
|
||||
@@ -422,8 +422,47 @@ fn tab_stops_position_text() {
|
||||
assert!((x_of(&p, 4) - 72.0).abs() < 1e-6);
|
||||
}
|
||||
|
||||
/// Are the Japanese craft-fonts faces built in? Tests of Japanese glyphs skip (and say so) when
|
||||
/// they aren't: the bundled fonts have none, and [`db`] reads no system fonts.
|
||||
fn japanese_fonts() -> bool {
|
||||
let built_in = CRAFT_FONTS.iter().any(|f| f.is_japanese());
|
||||
if !built_in {
|
||||
eprintln!("skipped: built without craft-fonts (set CRAFT_FONTS_DIR to a craft-fonts checkout to run it)");
|
||||
}
|
||||
built_in
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn japanese_text_uses_the_craft_fonts_mincho_after_the_bundled_fonts() {
|
||||
if !japanese_fonts() {
|
||||
return;
|
||||
}
|
||||
let l = layout(db(), &point("日本語の文字", style(20.0)));
|
||||
assert_eq!(l.glyphs.len(), 6);
|
||||
assert!(l.glyphs.iter().all(|g| g.gid != 0 && !g.outline.elements().is_empty()), "real glyphs, no tofu");
|
||||
let face = db().face_covering('日').unwrap();
|
||||
assert!(face.family.contains("Mincho"), "document text falls back to a Mincho face: {}", face.family);
|
||||
assert!(CRAFT_FONTS.iter().any(|f| f.family == face.family));
|
||||
// Latin keeps the bundled fallback family.
|
||||
assert_eq!(db().face_covering('a').unwrap().family, FALLBACK_FAMILY);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn japanese_text_lays_out_without_the_craft_fonts() {
|
||||
// Built either way, Japanese text lays out (as missing glyphs when no font has them).
|
||||
let l = layout(db(), &point("日本語の文字 abc", style(20.0)));
|
||||
assert_eq!(l.lines.len(), 1);
|
||||
assert!(l.glyphs.iter().all(|g| g.advance.is_finite()));
|
||||
if CRAFT_FONTS.is_empty() {
|
||||
assert!(db().families().iter().all(|f| !f.contains("Mincho") && !f.contains("Gothic")), "no Japanese font is bundled");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vertical_japanese_columns_have_upright_ink_and_edit_geometry() {
|
||||
if !japanese_fonts() {
|
||||
return;
|
||||
}
|
||||
let mut t = point("日本語\n縦書き", style(30.0));
|
||||
t.vertical = true;
|
||||
let l = layout(db(), &t);
|
||||
|
||||
@@ -132,6 +132,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn japanese_glyphs_are_available_without_system_fonts() {
|
||||
if !vectorcraft_text::CRAFT_FONTS.iter().any(|f| f.is_japanese()) {
|
||||
eprintln!("skipped: built without craft-fonts (set CRAFT_FONTS_DIR to a craft-fonts checkout to run it)");
|
||||
return;
|
||||
}
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let mut output = ctx.run_ui(egui::RawInput::default(), |_| {});
|
||||
|
||||
@@ -254,13 +254,28 @@ pub fn install_fonts(ctx: &egui::Context) {
|
||||
let mut mono = vec!["JetBrainsMono".to_string()];
|
||||
mono.extend(fallback);
|
||||
fonts.families.insert(FontFamily::Name(FONT_MONO.into()), mono);
|
||||
add(&mut fonts, "japanese", vectorcraft_text::SHIPPORI_MINCHO_REGULAR);
|
||||
for stack in fonts.families.values_mut() {
|
||||
stack.push("japanese".into());
|
||||
}
|
||||
add_craft_fonts(&mut fonts);
|
||||
ctx.set_fonts(fonts);
|
||||
}
|
||||
|
||||
/// The egui name of a craft-fonts face.
|
||||
pub(crate) fn craft_font_name(f: &vectorcraft_text::CraftFont) -> String {
|
||||
format!("craft-fonts {} {}", f.family, f.style)
|
||||
}
|
||||
|
||||
/// The Japanese craft-fonts faces (when built with `CRAFT_FONTS_DIR`) as fallbacks at the end of
|
||||
/// every family, after the app's own fonts and before the installed fonts `ui_fonts` adds: BIZ
|
||||
/// UDPGothic first (bold first in the semibold family), then the Mincho faces.
|
||||
fn add_craft_fonts(fonts: &mut FontDefinitions) {
|
||||
for f in vectorcraft_text::CRAFT_FONTS.iter().filter(|f| f.is_japanese()) {
|
||||
fonts.font_data.insert(craft_font_name(f), Arc::new(FontData::from_static(f.bytes)));
|
||||
}
|
||||
for (family, stack) in fonts.families.iter_mut() {
|
||||
let bold = *family == FontFamily::Name(FONT_UI_SEMIBOLD.into());
|
||||
stack.extend(vectorcraft_text::craft_fonts::japanese_ui_fonts(bold).into_iter().map(craft_font_name));
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply tokens to egui's global style.
|
||||
pub fn apply(ctx: &egui::Context, b: Brightness) {
|
||||
let t = Tokens::for_brightness(b);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
//! Fallback fonts for the app's own text: layer and file names (and anything else the UI shows) in
|
||||
//! Chinese, Japanese, Korean or other scripts the UI fonts lack are drawn in an installed font that
|
||||
//! covers them. Nothing is bundled for them: the first time a frame paints a character the UI fonts
|
||||
//! covers them. Builds made with craft-fonts (`CRAFT_FONTS_DIR`, all releases) carry Japanese fonts,
|
||||
//! which `theme::install_fonts` adds after the UI fonts, so Japanese never gets here; nothing else
|
||||
//! is bundled for them: the first time a frame paints a character the UI fonts
|
||||
//! don't have, an installed font covering it is looked for (in the background on native) and added
|
||||
//! to egui's families as their last fallback, from the next frame on. Text the UI fonts cover costs
|
||||
//! no font loading or memory. On the web, which has no system fonts, such characters stay
|
||||
//! missing-glyph boxes.
|
||||
//! missing-glyph boxes (Japanese too, unless built with craft-fonts).
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
@@ -171,6 +173,50 @@ mod tests {
|
||||
assert!(fonts.pending.is_none() && fonts.added.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn japanese_renders_with_the_craft_fonts_without_installed_fonts() {
|
||||
if !vectorcraft_text::CRAFT_FONTS.iter().any(|f| f.is_japanese()) {
|
||||
eprintln!("skipped: built without craft-fonts (set CRAFT_FONTS_DIR to a craft-fonts checkout to run it)");
|
||||
return;
|
||||
}
|
||||
let text = "日本語の文字";
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let mut fonts = UiFonts::default();
|
||||
frame(&ctx, &mut fonts, text);
|
||||
// Real glyphs (no tofu) in every family from the first frame, with no installed font looked for.
|
||||
assert!(fonts.pending.is_none() && fonts.added.is_empty(), "no system-font search");
|
||||
let families: Vec<FontFamily> = ctx.fonts(|f| f.definitions().families.keys().cloned().collect());
|
||||
for family in families {
|
||||
let stack = ctx.fonts(|f| f.definitions().families.get(&family).cloned().unwrap_or_default());
|
||||
assert!(stack.last().is_some_and(|n| n.starts_with("craft-fonts ")), "{family:?}: craft-fonts last: {stack:?}");
|
||||
for c in text.chars() {
|
||||
assert!(ctx.fonts_mut(|f| f.has_glyph(&FontId::new(13.0, family.clone()), c)), "{family:?} draws {c}");
|
||||
}
|
||||
}
|
||||
// The UI prefers BIZ UDPGothic.
|
||||
let stack = ctx.fonts(|f| f.definitions().families.get(&FontFamily::Proportional).cloned().unwrap_or_default());
|
||||
let first = stack.iter().find(|n| n.starts_with("craft-fonts ")).cloned().unwrap_or_default();
|
||||
assert_eq!(first, "craft-fonts BIZ UDPGothic Regular", "{stack:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_ui_works_without_the_craft_fonts() {
|
||||
// Built either way, the fonts install and Japanese text lays out (as installed-font
|
||||
// fallbacks or missing-glyph boxes when no font has it).
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let mut fonts = UiFonts::default();
|
||||
frame(&ctx, &mut fonts, "日本語の文字 Layer 1");
|
||||
fonts.finish(&ctx);
|
||||
frame(&ctx, &mut fonts, "日本語の文字 Layer 1");
|
||||
if vectorcraft_text::CRAFT_FONTS.is_empty() {
|
||||
let names: Vec<String> = ctx.fonts(|f| f.definitions().font_data.keys().cloned().collect());
|
||||
assert!(names.iter().all(|n| !n.starts_with("craft-fonts")), "{names:?}");
|
||||
}
|
||||
assert!(has_glyph(&ctx, 'L'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cjk_names_get_an_installed_fallback_font() {
|
||||
let text = "Logo 标志 ロゴ 로고";
|
||||
|
||||
@@ -24,6 +24,40 @@ How the web shell (`apps/vectorcraft-web/src/web.rs`) differs from desktop:
|
||||
- **No control server:** browsers can't listen on TCP. To automate the web build, drive headless Chrome with `--remote-debugging-port`.
|
||||
- Quick smoke test: `"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless=new --enable-unsafe-webgpu --screenshot=web.png --window-size=1440,900 --virtual-time-budget=15000 http://127.0.0.1:8766/` (headless Chrome on macOS gets a real WebGPU adapter).
|
||||
|
||||
## Fonts: craft-fonts (optional build input)
|
||||
|
||||
Font files are never committed to this repository. Fonts shared by the Crafting Apps live in
|
||||
[storytold/craft-fonts](https://github.com/storytold/craft-fonts); the rules are in craftrules
|
||||
[`standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md). To add
|
||||
a font, add it there. (The Latin fonts in `assets/fonts/` predate the rule and stay.)
|
||||
|
||||
VectorCraft builds, tests and runs without craft-fonts. To embed its Japanese fonts (BIZ UDPGothic
|
||||
for UI text, Shippori Mincho and BIZ UDMincho for document text), point the `CRAFT_FONTS_DIR` build
|
||||
option at a checkout:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/storytold/craft-fonts ../craft-fonts
|
||||
CRAFT_FONTS_DIR="$PWD/../craft-fonts" cargo run --release -p vectorcraft
|
||||
CRAFT_FONTS_DIR="$PWD/../craft-fonts" cargo xtask ci # also runs the Japanese-glyph tests
|
||||
```
|
||||
|
||||
- `crates/text/build.rs` reads the checkout's `fonts/manifest.txt` and embeds every font it lists as
|
||||
`vectorcraft_text::CRAFT_FONTS` (empty without `CRAFT_FONTS_DIR`). Give it an absolute path:
|
||||
build scripts run in the crate's directory (a relative path is taken from the workspace root
|
||||
as a convenience, but CI and the docs always use absolute paths). Nothing is downloaded, and
|
||||
craft-fonts is never a `Cargo.toml` dependency. A bad path is a build warning, or an error with
|
||||
`CRAFT_FONTS_REQUIRED=1` (release builds).
|
||||
- Web (wasm32) builds embed only BIZ UDPGothic Regular (+4.7 MB of `.wasm`), to stay well under
|
||||
static hosts' per-file limits (Cloudflare Pages: 25 MiB). Shippori Mincho, which this repo used to
|
||||
embed everywhere, was 8.7 MB, so the web build is still smaller than before.
|
||||
- Document text: the Japanese faces join the font database after the bundled fonts (Mincho first),
|
||||
so they are the fallback for Japanese after the requested font; installed system fonts come after.
|
||||
- UI: `theme::install_fonts` adds them at the end of every egui family (BIZ UDPGothic first), after
|
||||
the UI fonts and before the installed fonts `ui_fonts.rs` discovers for other scripts.
|
||||
- Tests that need Japanese glyphs skip with a message when built without craft-fonts. The FreeBSD
|
||||
CI job and every release job (`release.yml`) check craft-fonts out at a pinned commit; release
|
||||
packages carry each embedded font's `OFL-<family>.txt`.
|
||||
|
||||
## Vendor names gate
|
||||
|
||||
`cargo xtask brands` (part of `cargo xtask ci`) fails when user-visible text names another vendor's products or company: string literals in Rust sources (command labels and params docs, menus, panels, MCP tool definitions), `Cargo.toml` descriptions and packaging files. Comments and test code are not checked. Say "the reference app" or name the feature itself. A line that must keep an old name, such as an alias that files or preferences from earlier versions still use, carries a `brand-ok` comment.
|
||||
|
||||
@@ -50,6 +50,19 @@ copy_docs() {
|
||||
for f in README.md LICENSE LICENSE-MIT LICENSE-APACHE COPYRIGHT; do
|
||||
if [ -f "$ROOT/$f" ]; then cp "$ROOT/$f" "$dest/"; fi
|
||||
done
|
||||
copy_font_licences "$dest"
|
||||
}
|
||||
|
||||
# Built with craft-fonts (CRAFT_FONTS_DIR, set by release.yml): the embedded fonts' licences go with
|
||||
# the package, as OFL-<family dir>.txt. Nothing to do without it.
|
||||
copy_font_licences() {
|
||||
local dest="$1" ofl family
|
||||
[ -n "${CRAFT_FONTS_DIR:-}" ] || return 0
|
||||
for ofl in "$CRAFT_FONTS_DIR"/fonts/*/OFL.txt; do
|
||||
[ -f "$ofl" ] || continue
|
||||
family="$(basename "$(dirname "$ofl")")"
|
||||
cp "$ofl" "$dest/OFL-$family.txt"
|
||||
done
|
||||
}
|
||||
|
||||
# Portable SHA-256 of a file (prints just the hash).
|
||||
|
||||
Executable
+70
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build and package VectorCraft for FreeBSD:
|
||||
#
|
||||
# $DIST/vectorcraft-<version>-freebsd-x86_64.tar.gz a /usr/local-style tree:
|
||||
# vectorcraft-<version>-freebsd-x86_64/{bin, share/applications, share/icons, share/mime,
|
||||
# share/metainfo, share/doc/vectorcraft}
|
||||
#
|
||||
# Install by copying the tree's contents into /usr/local, e.g.
|
||||
# tar -xzf vectorcraft-*-freebsd-x86_64.tar.gz --strip-components 1 -C /usr/local
|
||||
#
|
||||
# Usage: packaging/freebsd/package.sh [--skip-build]
|
||||
#
|
||||
# Needs: bash, cargo and the build dependencies listed in .github/workflows/freebsd.yml. FreeBSD's
|
||||
# install(1) has no -D, so directories are created first.
|
||||
set -euo pipefail
|
||||
# shellcheck source=../env.sh
|
||||
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
|
||||
LINUX="$ROOT/packaging/linux"
|
||||
APP_ID=ai.storyteller.vectorcraft
|
||||
|
||||
SKIP_BUILD=0
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--skip-build) SKIP_BUILD=1; shift ;;
|
||||
-h | --help) sed -n '2,13p' "$0"; exit 0 ;;
|
||||
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# FreeBSD calls x86_64 "amd64"; release names use the Rust/Linux spelling.
|
||||
case "$(uname -m)" in
|
||||
amd64 | x86_64) ARCH=x86_64 ;;
|
||||
arm64 | aarch64) ARCH=aarch64 ;;
|
||||
*) echo "unsupported architecture $(uname -m)" >&2; exit 2 ;;
|
||||
esac
|
||||
BASENAME="vectorcraft-$VERSION-freebsd-$ARCH"
|
||||
|
||||
echo "==> VectorCraft $VERSION for FreeBSD $ARCH"
|
||||
|
||||
if [ "$SKIP_BUILD" = 0 ]; then
|
||||
(cd "$ROOT" && cargo build --release --locked -p vectorcraft -p vectorcraft-cli)
|
||||
fi
|
||||
BIN="$CARGO_TARGET_DIR/release"
|
||||
WORK="$CARGO_TARGET_DIR/freebsd-package"
|
||||
STAGE="$WORK/$BASENAME"
|
||||
rm -rf "$WORK"
|
||||
|
||||
# ---- stage a /usr/local-style tree ---------------------------------------------------------------
|
||||
mkdir -p "$STAGE/bin" "$STAGE/share/applications" "$STAGE/share/mime/packages" "$STAGE/share/metainfo" \
|
||||
"$STAGE/share/icons" "$STAGE/share/doc/vectorcraft"
|
||||
install -m 755 "$BIN/vectorcraft" "$BIN/vectorcraft-cli" "$STAGE/bin/"
|
||||
strip "$STAGE/bin/vectorcraft" "$STAGE/bin/vectorcraft-cli" 2>/dev/null || true
|
||||
# The desktop entry, MIME type and metainfo are the freedesktop files the Linux packages use.
|
||||
install -m 644 "$LINUX/$APP_ID.desktop" "$STAGE/share/applications/$APP_ID.desktop"
|
||||
install -m 644 "$LINUX/$APP_ID.mime.xml" "$STAGE/share/mime/packages/$APP_ID.xml"
|
||||
sed -e "s/@VERSION@/$VERSION/g" -e "s/@DATE@/$VECTORCRAFT_BUILD_DATE/g" \
|
||||
"$LINUX/$APP_ID.metainfo.xml.in" >"$STAGE/share/metainfo/$APP_ID.metainfo.xml"
|
||||
cp -R "$ROOT/assets/app-icon/hicolor" "$STAGE/share/icons/"
|
||||
copy_docs "$STAGE/share/doc/vectorcraft"
|
||||
for f in NOTICE ASSETS.md; do
|
||||
if [ -f "$ROOT/$f" ]; then cp "$ROOT/$f" "$STAGE/share/doc/vectorcraft/"; fi
|
||||
done
|
||||
|
||||
# ---- .tar.gz -------------------------------------------------------------------------------------
|
||||
tar -C "$WORK" -czf "$DIST/$BASENAME.tar.gz" "$BASENAME"
|
||||
echo "wrote $DIST/$BASENAME.tar.gz"
|
||||
|
||||
"$STAGE/bin/vectorcraft-cli" --version
|
||||
echo "==> done"
|
||||
ls -lh "$DIST"
|
||||
@@ -108,6 +108,8 @@ sed -e "s/@VERSION@/$VERSION/g" -e "s/@SHORT_VERSION@/$SHORT_VERSION/g" \
|
||||
"$HERE/Info.plist.in" >"$APP/Contents/Info.plist"
|
||||
plutil -lint "$APP/Contents/Info.plist"
|
||||
printf 'APPL????' >"$APP/Contents/PkgInfo"
|
||||
# The licences of the craft-fonts fonts embedded in the binary (release builds).
|
||||
copy_font_licences "$APP/Contents/Resources"
|
||||
|
||||
# Sign inside-out: nested code first, then the bundle itself (no --deep on the final signature).
|
||||
# Today the only nested code is the main executable; frameworks/helpers would be signed here too.
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
.EXAMPLE
|
||||
pwsh packaging/windows/package.ps1 -Arch x64
|
||||
pwsh packaging/windows/package.ps1 -Arch x86 -SkipBuild
|
||||
pwsh packaging/windows/package.ps1 -Arch arm64 # cross-compiled; needs the MSVC ARM64 build tools
|
||||
#>
|
||||
param(
|
||||
[ValidateSet('x64', 'x86')] [string] $Arch = 'x64',
|
||||
[ValidateSet('x64', 'x86', 'arm64')] [string] $Arch = 'x64',
|
||||
[switch] $SkipBuild
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
@@ -44,7 +45,7 @@ if (-not $Version) { throw 'could not read [workspace.package] version from Carg
|
||||
# MSI ProductVersion is numeric (major.minor.build); pre-release tags are dropped there.
|
||||
$MsiVersion = ($Version -split '-')[0]
|
||||
|
||||
$Target = if ($Arch -eq 'x64') { 'x86_64-pc-windows-msvc' } else { 'i686-pc-windows-msvc' }
|
||||
$Target = switch ($Arch) { 'x64' { 'x86_64-pc-windows-msvc' } 'x86' { 'i686-pc-windows-msvc' } 'arm64' { 'aarch64-pc-windows-msvc' } }
|
||||
$Dist = if ($env:DIST) { $env:DIST } else { Join-Path $Root 'dist\release' }
|
||||
$TargetDir = if ($env:CARGO_TARGET_DIR) { $env:CARGO_TARGET_DIR } else { Join-Path $Root 'target' }
|
||||
New-Item -ItemType Directory -Force -Path $Dist | Out-Null
|
||||
@@ -65,6 +66,23 @@ if (-not $SkipBuild) {
|
||||
}
|
||||
|
||||
$Bin = Join-Path $TargetDir "$Target\release"
|
||||
|
||||
# Check both binaries' PE headers before packaging. Machine (COFF header) must match -Arch, so an
|
||||
# x64 build can never ship labelled arm64. Subsystem (optional header): 2 = Windows GUI, 3 = console.
|
||||
# The app must be GUI (no console window opens with it); the CLI must stay console so its output
|
||||
# reaches the terminal.
|
||||
function Get-PeHeader([string] $Path) {
|
||||
$bytes = [System.IO.File]::ReadAllBytes($Path)
|
||||
$pe = [BitConverter]::ToInt32($bytes, 0x3C)
|
||||
return @{ Machine = [BitConverter]::ToUInt16($bytes, $pe + 4); Subsystem = [BitConverter]::ToUInt16($bytes, $pe + 0x5C) }
|
||||
}
|
||||
$Machine = switch ($Arch) { 'x64' { 0x8664 } 'x86' { 0x14C } 'arm64' { 0xAA64 } }
|
||||
foreach ($check in @(@('vectorcraft.exe', 2), @('vectorcraft-cli.exe', 3))) {
|
||||
$h = Get-PeHeader (Join-Path $Bin $check[0])
|
||||
if ($h.Machine -ne $Machine) { throw "$($check[0]) is for machine 0x$('{0:X}' -f $h.Machine), expected 0x$('{0:X}' -f $Machine) ($Arch)" }
|
||||
if ($h.Subsystem -ne $check[1]) { throw "$($check[0]) has PE subsystem $($h.Subsystem), expected $($check[1])" }
|
||||
Write-Output "ok $($check[0]): $Arch, PE subsystem $($h.Subsystem)"
|
||||
}
|
||||
$Stage = Join-Path $TargetDir "windows-package\$Arch"
|
||||
Remove-Item -Recurse -Force $Stage -ErrorAction SilentlyContinue
|
||||
New-Item -ItemType Directory -Force -Path $Stage | Out-Null
|
||||
@@ -92,9 +110,22 @@ foreach ($f in 'README.md', 'LICENSE', 'LICENSE-MIT', 'LICENSE-APACHE') {
|
||||
$p = Join-Path $Root $f
|
||||
if (Test-Path $p) { Copy-Item $p $Portable }
|
||||
}
|
||||
# Built with craft-fonts (CRAFT_FONTS_DIR, release builds): the embedded fonts' licences.
|
||||
if ($env:CRAFT_FONTS_DIR) {
|
||||
foreach ($ofl in Get-ChildItem -Path (Join-Path $env:CRAFT_FONTS_DIR 'fonts\*\OFL.txt') -ErrorAction SilentlyContinue) {
|
||||
Copy-Item $ofl.FullName (Join-Path $Portable "OFL-$($ofl.Directory.Name).txt")
|
||||
}
|
||||
}
|
||||
$Zip = Join-Path $Dist "vectorcraft-$Version-windows-$Arch-portable.zip"
|
||||
Remove-Item -Force $Zip -ErrorAction SilentlyContinue
|
||||
Compress-Archive -Path $Portable -DestinationPath $Zip
|
||||
|
||||
Invoke-Native 'vectorcraft-cli --version' { & (Join-Path $Stage 'vectorcraft-cli.exe') --version }
|
||||
# Smoke-test the CLI when this machine can run it. An ARM64 build made on an x64 runner can't run
|
||||
# here; .github/workflows/windows-arm64.yml installs and runs it on ARM64 instead.
|
||||
$HostArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant()
|
||||
if ($Arch -ne 'arm64' -or $HostArch -eq 'arm64') {
|
||||
Invoke-Native 'vectorcraft-cli --version' { & (Join-Path $Stage 'vectorcraft-cli.exe') --version }
|
||||
} else {
|
||||
Write-Output "skipping vectorcraft-cli --version: an $Arch build doesn't run on this $HostArch machine"
|
||||
}
|
||||
Get-Item $Msi, $Zip | Format-Table Name, Length
|
||||
Reference in new issue
Block a user