mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 14:14:11 +00:00
Merge main into fix/outline-stroke-keeps-effects-on-outline
This commit is contained in:
@@ -23,3 +23,27 @@ jobs:
|
||||
uses: storytold/craft-common-actions/.github/workflows/packaging-lint.yml@main
|
||||
with:
|
||||
extra-checks-script: packaging/check-app-specific.sh
|
||||
|
||||
windows-install-policy:
|
||||
timeout-minutes: 10
|
||||
name: Windows installation policy (no app build)
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install the pinned packaging toolchain
|
||||
shell: pwsh
|
||||
run: |
|
||||
$tools = Join-Path $env:RUNNER_TEMP 'craft-install-policy-wix'
|
||||
dotnet tool install --tool-path $tools wix --version 5.0.2
|
||||
if ($LASTEXITCODE -ne 0) { throw 'WiX installation failed.' }
|
||||
$tools >> $env:GITHUB_PATH
|
||||
$policy = Get-Content -Raw -LiteralPath packaging/windows/install-policy.json | ConvertFrom-Json
|
||||
foreach ($extension in @($policy.extensions)) {
|
||||
& (Join-Path $tools 'wix.exe') extension add -g "WixToolset.$extension.wixext/5.0.2"
|
||||
if ($LASTEXITCODE -ne 0) { throw "WiX extension $extension installation failed." }
|
||||
}
|
||||
- name: Build and check non-executable installer fixtures
|
||||
shell: pwsh
|
||||
run: ./packaging/windows/test-install-policy.ps1 -OutputDirectory (Join-Path $env:RUNNER_TEMP 'craft-install-policy-tests')
|
||||
|
||||
@@ -32,4 +32,8 @@ jobs:
|
||||
- run: cargo clippy --locked -p vectorcraft --all-targets -- -D warnings
|
||||
- run: cargo test --locked -p vectorcraft
|
||||
- name: Check pending submission and queue refusal feedback
|
||||
run: cargo test --locked -p vectorcraft-ui-egui tests_printps
|
||||
shell: bash
|
||||
# A filter that matches nothing still passes, so fail unless some test ran.
|
||||
run: |
|
||||
cargo test --locked -p vectorcraft-ui-egui --lib -- tests::printps | tee printps.log
|
||||
grep -Eq 'test result: ok\. [1-9][0-9]* passed' printps.log || { echo "::error::tests::printps matched no tests"; exit 1; }
|
||||
|
||||
@@ -27,8 +27,10 @@ permissions:
|
||||
|
||||
jobs:
|
||||
windows-arm64:
|
||||
# Intentionally tracked at @main (not SHA-pinned) so org-wide CI improvements, security fixes, and build optimizations propagate without PR churn across all craftapp repos.
|
||||
uses: storytold/craft-common-actions/.github/workflows/windows-arm64.yml@main
|
||||
# Immutable supporting workflow revision until the new install contract lands upstream.
|
||||
uses: storytold/craft-common-actions/.github/workflows/windows-arm64.yml@8bb4e5ab95443171104ad88113fe1ef9b0e5ce3f
|
||||
with:
|
||||
app-name: vectorcraft
|
||||
app-dir: VectorCraft
|
||||
install-relative-path: Programs/ArtCraft/VectorCraft
|
||||
install-scope: perUser
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
# Experimental, unsupported Windows 7 SP1 x64 portable build (docs/windows7.md). It runs by hand and
|
||||
# weekly and for changes to the compatibility path; the weekly run shows when a dependency update starts
|
||||
# importing a post-Windows-7 API. packaging/windows/windows7.ps1 does the work: it is the only build
|
||||
# that uses the patched vendor/windows-link. A modern Windows runner proves compilation and host
|
||||
# smoke tests only: run the checklist in docs/windows7.md on actual Windows 7 before claiming support.
|
||||
name: Windows 7 x64 compatibility
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "Cargo.toml"
|
||||
- "Cargo.lock"
|
||||
- "apps/vectorcraft/Cargo.toml"
|
||||
- "apps/vectorcraft/src/printing.rs"
|
||||
- "apps/vectorcraft/src/printing/**"
|
||||
- "crates/ui-egui/src/print.rs"
|
||||
- "crates/ui-egui/src/tests/printps.rs"
|
||||
- "packaging/windows/windows7.ps1"
|
||||
- ".github/workflows/windows7.yml"
|
||||
schedule:
|
||||
- cron: "23 5 * * 1" # weekly
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-2022
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
|
||||
with:
|
||||
arch: x64
|
||||
- uses: dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f # master
|
||||
with:
|
||||
toolchain: nightly-2026-10-01
|
||||
components: rust-src
|
||||
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
||||
with:
|
||||
key: windows7-x64
|
||||
- name: Build, check and test the portable package
|
||||
shell: pwsh
|
||||
run: ./packaging/windows/windows7.ps1 -Test
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: vectorcraft-windows7-x64-portable
|
||||
path: dist/release/vectorcraft-windows7-x64-portable.zip
|
||||
if-no-files-found: error
|
||||
@@ -35,6 +35,7 @@ Formerly **DrawCraft** (renamed 2026-10-01): old `.drawcraft` files and `"format
|
||||
- **Never crash: no panics in shipped code.** A crash loses the user's unsaved work. Files, pasted data, MCP and control messages, and command params are all untrusted input.
|
||||
- Fallible work returns `Result` (or `Option`); callers handle it or propagate it with `?`. Failures reach the user as an error message (`EngineError` for commands, the crate's own error type or `String` below the engine), never as an abort.
|
||||
- Workspace clippy lints ban these outside tests, so `cargo xtask ci` fails on them: `unwrap()`, `expect()`, `panic!`, `unreachable!`, `todo!`, `unimplemented!`. Use instead: `?`, `.ok_or(…)?`, `let … else { return … }`, `if let`, `unwrap_or` / `unwrap_or_default` / `unwrap_or_else`, `.first()` / `.last()` / `.split_last()`, and a real error for a match arm you believe is impossible.
|
||||
- New workspace crates must include `[lints] workspace = true` in `Cargo.toml` to inherit `[workspace.lints]`. Production crate roots must declare `#![forbid(unsafe_code)]` before modules or other crate items; crate-level `//!` documentation may precede it.
|
||||
- Don't panic implicitly either: use `.get(i)` instead of `v[i]` or `&s[a..b]` when the index comes from data. Don't divide integers by something that can be zero, cap sizes and counts read from input (allocations, images, loops), and check that floats are finite before casting them or looping on them.
|
||||
- Don't silence errors just to satisfy the lint: use `let _ =` or `.ok()` only where ignoring the failure is the right behaviour, with a comment saying why.
|
||||
- Tests may unwrap and panic (`#[test]`, `#[cfg(test)]`, `tests/`, `testkit`). Integration-test files start with an `#![allow(…)]` for that.
|
||||
|
||||
Generated
+28
-29
@@ -1310,7 +1310,6 @@ dependencies = [
|
||||
"egui-wgpu",
|
||||
"egui-winit",
|
||||
"egui_glow",
|
||||
"glow",
|
||||
"glutin",
|
||||
"glutin-winit",
|
||||
"image",
|
||||
@@ -6036,7 +6035,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"block2 0.6.2",
|
||||
@@ -6077,7 +6076,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-affinity"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
@@ -6088,7 +6087,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-brush"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"kurbo 0.13.1",
|
||||
"serde",
|
||||
@@ -6100,7 +6099,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-cad"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
"kurbo 0.13.1",
|
||||
@@ -6116,7 +6115,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-cli"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"objc2-core-foundation",
|
||||
@@ -6138,7 +6137,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-color"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"kurbo 0.13.1",
|
||||
"moxcms",
|
||||
@@ -6150,7 +6149,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-doc"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"hayro-ccitt",
|
||||
@@ -6169,7 +6168,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-effects"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
"kurbo 0.13.1",
|
||||
@@ -6185,7 +6184,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-engine"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"craft-docpath",
|
||||
"crc32fast",
|
||||
@@ -6224,7 +6223,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-eps"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"fax",
|
||||
"flate2",
|
||||
@@ -6248,7 +6247,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-format"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"proptest",
|
||||
@@ -6263,7 +6262,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-geom"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"kurbo 0.13.1",
|
||||
"proptest",
|
||||
@@ -6275,7 +6274,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-mcp"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
@@ -6294,7 +6293,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-metafile"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
"kurbo 0.13.1",
|
||||
@@ -6308,7 +6307,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-pathops"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"kurbo 0.13.1",
|
||||
"linesweeper",
|
||||
@@ -6320,7 +6319,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-pdf"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"chrono",
|
||||
@@ -6351,7 +6350,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-plugins"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -6365,7 +6364,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-render"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"gif",
|
||||
@@ -6394,14 +6393,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-scriptcheck"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"proptest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-svg"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"craft-docpath",
|
||||
"flate2",
|
||||
@@ -6426,7 +6425,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-testkit"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"fax",
|
||||
"image",
|
||||
@@ -6444,7 +6443,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-text"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"harfrust 0.13.3",
|
||||
"icu_segmenter",
|
||||
@@ -6465,7 +6464,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-three-d"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"vectorcraft-geom",
|
||||
"vectorcraft-pathops",
|
||||
@@ -6473,7 +6472,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-tools"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
"kurbo 0.13.1",
|
||||
@@ -6489,7 +6488,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-trace"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"image",
|
||||
"kurbo 0.13.1",
|
||||
@@ -6501,7 +6500,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-ui-egui"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"craft-keymap",
|
||||
@@ -6539,7 +6538,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vectorcraft-web"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
@@ -7670,7 +7669,7 @@ checksum = "9440ea3e5aeabb0ac63af70daf835274065238cdd0cec83418f417eae38bacee"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
+4
-1
@@ -4,7 +4,7 @@ members = ["crates/*", "apps/*", "xtask"]
|
||||
default-members = ["crates/*", "apps/vectorcraft", "apps/vectorcraft-cli", "xtask"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.95"
|
||||
@@ -116,6 +116,9 @@ webbrowser = "1"
|
||||
unsafe_code = "deny"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
cognitive_complexity = "deny"
|
||||
too_many_arguments = "deny"
|
||||
wildcard_imports = "deny"
|
||||
dbg_macro = "warn"
|
||||
# VectorCraft never crashes: shipped code returns `Result`/`Option` instead of panicking (see
|
||||
# AGENTS.md › Robustness). Tests may unwrap (clippy.toml).
|
||||
|
||||
@@ -30,11 +30,6 @@ 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.
|
||||
|
||||
The experimental Windows 7 build (packaging/windows/windows7.ps1) compiles a modified copy of the
|
||||
windows-link crate 0.2.1 (vendor/windows-link; Microsoft, MIT OR Apache-2.0) that binds CoTaskMemFree
|
||||
through ole32.dll. Its license texts are in vendor/windows-link and ship in that build's ZIP; every
|
||||
other build uses the unmodified crate from crates.io.
|
||||
|
||||
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 (BIZ UDPGothic,
|
||||
Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC and Noto Sans Arabic in the current manifest;
|
||||
|
||||
@@ -227,8 +227,6 @@ claude mcp add vectorcraft -- /path/to/vectorcraft-cli mcp --automation-read-roo
|
||||
|
||||
The details are in [`docs/mcp.md`](docs/mcp.md) and [`docs/control-protocol.md`](docs/control-protocol.md).
|
||||
|
||||
For the experimental, unsupported 64-bit Windows 7 build, see [Windows 7 instructions](docs/windows7.md).
|
||||
|
||||
## Status
|
||||
|
||||
VectorCraft is under active development. [**ROADMAP.md**](ROADMAP.md) has the stage, the numbers by dimension
|
||||
|
||||
@@ -35,7 +35,6 @@ winsafe = { version = "0.0.29", default-features = false, features = ["kernel",
|
||||
# DirectWrite's system font collection and GDI's font list (the desktop app's src/system_fonts.rs,
|
||||
# shared): fonts that font services such as Adobe Fonts load in place (#579, #1258). MIT OR
|
||||
# Apache-2.0.
|
||||
[target.'cfg(all(windows, not(target_vendor = "win7")))'.dependencies]
|
||||
windows = { version = "0.62", features = ["Win32_Graphics_DirectWrite", "Win32_Graphics_Gdi"] }
|
||||
|
||||
# CoreText's font manager (the desktop app's src/mac_fonts.rs, shared): fonts that apps and font
|
||||
|
||||
@@ -46,7 +46,7 @@ mod perf;
|
||||
#[path = "../../vectorcraft/src/prefs_dir.rs"]
|
||||
mod prefs_dir;
|
||||
/// The desktop app's DirectWrite font lister, shared: exports and MCP find the same fonts.
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
#[path = "../../vectorcraft/src/system_fonts.rs"]
|
||||
mod system_fonts;
|
||||
|
||||
@@ -162,7 +162,7 @@ fn help() -> Result<(), String> {
|
||||
|
||||
fn main() -> ExitCode {
|
||||
// Before any font lookup: the fonts font services load (#579).
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
system_fonts::install();
|
||||
#[cfg(target_os = "macos")]
|
||||
mac_fonts::install();
|
||||
|
||||
@@ -447,113 +447,158 @@ fn mcp_protocol_over_stdio() {
|
||||
let mut stdin = child.stdin.take().unwrap();
|
||||
let mut lines = BufReader::new(child.stdout.take().unwrap()).lines();
|
||||
let mut notes = vec![];
|
||||
|
||||
fn send(stdin: &mut std::process::ChildStdin, v: Value) {
|
||||
writeln!(stdin, "{v}").unwrap();
|
||||
exercise_mcp_core(&mut stdin, &mut lines, &mut notes);
|
||||
exercise_mcp_resource_errors(&mut stdin, &mut lines, &mut notes);
|
||||
exercise_mcp_document_resources(&mut stdin, &mut lines, &mut notes);
|
||||
exercise_mcp_logging(&mut stdin, &mut lines, &mut notes);
|
||||
exercise_mcp_parse_error(&mut stdin, &mut lines, &mut notes);
|
||||
drop(stdin);
|
||||
for line in lines {
|
||||
let v: Value = serde_json::from_str(&line.unwrap()).unwrap();
|
||||
assert_eq!(v["jsonrpc"], "2.0", "{v}");
|
||||
notes.push(v);
|
||||
}
|
||||
// Next reply with this id; notifications (no id) are stashed for later.
|
||||
fn recv(lines: &mut std::io::Lines<BufReader<std::process::ChildStdout>>, notes: &mut Vec<Value>, id: u64) -> Value {
|
||||
loop {
|
||||
let line = lines.next().unwrap().unwrap();
|
||||
let v: Value = serde_json::from_str(&line).unwrap();
|
||||
assert_eq!(v["jsonrpc"], "2.0", "{v}");
|
||||
if v.get("id").and_then(|i| i.as_u64()) == Some(id) {
|
||||
return v;
|
||||
}
|
||||
notes.push(v);
|
||||
let out = child.wait_with_output().unwrap();
|
||||
assert!(out.status.success());
|
||||
assert!(!String::from_utf8_lossy(&out.stderr).contains("panicked"));
|
||||
let messages = notes.iter().filter(|n| n["method"] == "notifications/message").count();
|
||||
assert!(messages > 0, "no log records after logging/setLevel: {notes:?}");
|
||||
}
|
||||
|
||||
fn mcp_send(stdin: &mut std::process::ChildStdin, v: Value) {
|
||||
writeln!(stdin, "{v}").unwrap();
|
||||
}
|
||||
|
||||
fn mcp_recv(lines: &mut std::io::Lines<std::io::BufReader<std::process::ChildStdout>>, notes: &mut Vec<Value>, id: u64) -> Value {
|
||||
loop {
|
||||
let line = lines.next().unwrap().unwrap();
|
||||
let v: Value = serde_json::from_str(&line).unwrap();
|
||||
assert_eq!(v["jsonrpc"], "2.0", "{v}");
|
||||
if v.get("id").and_then(|i| i.as_u64()) == Some(id) {
|
||||
return v;
|
||||
}
|
||||
notes.push(v);
|
||||
}
|
||||
}
|
||||
|
||||
send(
|
||||
&mut stdin,
|
||||
fn exercise_mcp_core(
|
||||
stdin: &mut std::process::ChildStdin,
|
||||
lines: &mut std::io::Lines<std::io::BufReader<std::process::ChildStdout>>,
|
||||
notes: &mut Vec<Value>,
|
||||
) {
|
||||
mcp_send(
|
||||
stdin,
|
||||
json!({"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"0"}}}),
|
||||
);
|
||||
let r = recv(&mut lines, &mut notes, 1);
|
||||
let r = mcp_recv(lines, notes, 1);
|
||||
assert_eq!(r["result"]["serverInfo"]["name"], "vectorcraft");
|
||||
assert!(r["result"]["capabilities"]["prompts"].is_object(), "{r}");
|
||||
send(&mut stdin, json!({"jsonrpc":"2.0","method":"notifications/initialized"}));
|
||||
mcp_send(stdin, json!({"jsonrpc":"2.0","method":"notifications/initialized"}));
|
||||
|
||||
send(&mut stdin, json!({"jsonrpc":"2.0","id":2,"method":"prompts/list","params":{}}));
|
||||
let r = recv(&mut lines, &mut notes, 2);
|
||||
mcp_send(stdin, json!({"jsonrpc":"2.0","id":2,"method":"prompts/list","params":{}}));
|
||||
let r = mcp_recv(lines, notes, 2);
|
||||
assert!(r["result"]["prompts"].as_array().is_some_and(|p| !p.is_empty()), "{r}");
|
||||
send(&mut stdin, json!({"jsonrpc":"2.0","id":3,"method":"prompts/get","params":{"name":"poster","arguments":{"brief":"a jazz festival"}}}));
|
||||
let r = recv(&mut lines, &mut notes, 3);
|
||||
mcp_send(stdin, json!({"jsonrpc":"2.0","id":3,"method":"prompts/get","params":{"name":"poster","arguments":{"brief":"a jazz festival"}}}));
|
||||
let r = mcp_recv(lines, notes, 3);
|
||||
assert!(r["result"]["messages"][0]["content"]["text"].as_str().is_some_and(|t| t.contains("a jazz festival")), "{r}");
|
||||
|
||||
send(
|
||||
&mut stdin,
|
||||
mcp_send(
|
||||
stdin,
|
||||
json!({"jsonrpc":"2.0","id":4,"method":"completion/complete","params":{"ref":{"type":"ref/prompt","name":"export-set"},"argument":{"name":"formats","value":"sv"}}}),
|
||||
);
|
||||
let r = recv(&mut lines, &mut notes, 4);
|
||||
let r = mcp_recv(lines, notes, 4);
|
||||
assert!(r["result"]["completion"]["values"].as_array().is_some_and(|v| v.contains(&json!("svg"))), "{r}");
|
||||
|
||||
send(&mut stdin, json!({"jsonrpc":"2.0","id":5,"method":"resources/list","params":{}}));
|
||||
assert!(recv(&mut lines, &mut notes, 5)["result"]["resources"].as_array().is_some_and(|v| v.len() == 3));
|
||||
send(&mut stdin, json!({"jsonrpc":"2.0","id":6,"method":"resources/templates/list","params":{}}));
|
||||
assert!(recv(&mut lines, &mut notes, 6)["result"]["resourceTemplates"].as_array().is_some_and(|v| v.len() >= 4));
|
||||
mcp_send(stdin, json!({"jsonrpc":"2.0","id":5,"method":"resources/list","params":{}}));
|
||||
assert!(mcp_recv(lines, notes, 5)["result"]["resources"].as_array().is_some_and(|v| v.len() == 3));
|
||||
mcp_send(stdin, json!({"jsonrpc":"2.0","id":6,"method":"resources/templates/list","params":{}}));
|
||||
assert!(mcp_recv(lines, notes, 6)["result"]["resourceTemplates"].as_array().is_some_and(|v| v.len() >= 4));
|
||||
|
||||
send(
|
||||
&mut stdin,
|
||||
mcp_send(
|
||||
stdin,
|
||||
json!({"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"draw_shape","arguments":{"shape":"rectangle","x":10,"y":10,"width":60,"height":40}}}),
|
||||
);
|
||||
let r = recv(&mut lines, &mut notes, 7);
|
||||
let r = mcp_recv(lines, notes, 7);
|
||||
assert_eq!(r["result"]["isError"], false, "{r}");
|
||||
let id: i64 = serde_json::from_str::<Value>(r["result"]["content"][0]["text"].as_str().unwrap()).unwrap()["id"].as_i64().unwrap();
|
||||
|
||||
send(&mut stdin, json!({"jsonrpc":"2.0","id":8,"method":"resources/read","params":{"uri": format!("vectorcraft://object/{id}")}}));
|
||||
let r = recv(&mut lines, &mut notes, 8);
|
||||
mcp_send(stdin, json!({"jsonrpc":"2.0","id":8,"method":"resources/read","params":{"uri": format!("vectorcraft://object/{id}")}}));
|
||||
let r = mcp_recv(lines, notes, 8);
|
||||
let body: Value = serde_json::from_str(r["result"]["contents"][0]["text"].as_str().unwrap()).unwrap();
|
||||
assert_eq!(body["id"].as_i64(), Some(id), "{body}");
|
||||
}
|
||||
|
||||
fn exercise_mcp_resource_errors(
|
||||
stdin: &mut std::process::ChildStdin,
|
||||
lines: &mut std::io::Lines<std::io::BufReader<std::process::ChildStdout>>,
|
||||
notes: &mut Vec<Value>,
|
||||
) {
|
||||
// Unknown resources (-32002) and bad template values (-32602) stay apart.
|
||||
send(&mut stdin, json!({"jsonrpc":"2.0","id":9,"method":"resources/read","params":{"uri":"vectorcraft://nope" }}));
|
||||
assert_eq!(recv(&mut lines, &mut notes, 9)["error"]["code"], -32002);
|
||||
send(&mut stdin, json!({"jsonrpc":"2.0","id":10,"method":"resources/read","params":{"uri":"vectorcraft://object/"}}));
|
||||
assert_eq!(recv(&mut lines, &mut notes, 10)["error"]["code"], -32602);
|
||||
mcp_send(stdin, json!({"jsonrpc":"2.0","id":9,"method":"resources/read","params":{"uri":"vectorcraft://nope" }}));
|
||||
assert_eq!(mcp_recv(lines, notes, 9)["error"]["code"], -32002);
|
||||
mcp_send(stdin, json!({"jsonrpc":"2.0","id":10,"method":"resources/read","params":{"uri":"vectorcraft://object/"}}));
|
||||
assert_eq!(mcp_recv(lines, notes, 10)["error"]["code"], -32602);
|
||||
}
|
||||
|
||||
fn exercise_mcp_document_resources(
|
||||
stdin: &mut std::process::ChildStdin,
|
||||
lines: &mut std::io::Lines<std::io::BufReader<std::process::ChildStdout>>,
|
||||
notes: &mut Vec<Value>,
|
||||
) {
|
||||
// Large containers slice through run_command document.node: a truncated level reports
|
||||
// childCount, and a bad limit is a tool error, not a crash or a full read.
|
||||
send(
|
||||
&mut stdin,
|
||||
mcp_send(
|
||||
stdin,
|
||||
json!({"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"draw_shape","arguments":{"shape":"ellipse","x":90,"y":10,"width":30,"height":30}}}),
|
||||
);
|
||||
assert_eq!(recv(&mut lines, &mut notes, 20)["result"]["isError"], false);
|
||||
let run = |id: u64, command: &str, params: Value| json!({"jsonrpc":"2.0","id":id,"method":"tools/call","params":{"name":"run_command","arguments":{"command":command,"params":params}}});
|
||||
send(&mut stdin, run(21, "select.all", json!({})));
|
||||
recv(&mut lines, &mut notes, 21);
|
||||
send(&mut stdin, run(22, "object.group", json!({})));
|
||||
let r = recv(&mut lines, &mut notes, 22);
|
||||
assert_eq!(mcp_recv(lines, notes, 20)["result"]["isError"], false);
|
||||
mcp_send(stdin, run_command(21, "select.all", json!({})));
|
||||
mcp_recv(lines, notes, 21);
|
||||
mcp_send(stdin, run_command(22, "object.group", json!({})));
|
||||
let r = mcp_recv(lines, notes, 22);
|
||||
let group = serde_json::from_str::<Value>(r["result"]["content"][0]["text"].as_str().unwrap()).unwrap()["id"].clone();
|
||||
send(&mut stdin, run(23, "document.node", json!({"id": group, "summary": true, "childLimit": 1})));
|
||||
let r = recv(&mut lines, &mut notes, 23);
|
||||
mcp_send(stdin, run_command(23, "document.node", json!({"id": group, "summary": true, "childLimit": 1})));
|
||||
let r = mcp_recv(lines, notes, 23);
|
||||
let sliced: Value = serde_json::from_str(r["result"]["content"][0]["text"].as_str().unwrap()).unwrap();
|
||||
assert_eq!(sliced["children"].as_array().map(Vec::len), Some(1), "{sliced}");
|
||||
assert_eq!(sliced["childCount"], 2, "{sliced}");
|
||||
send(&mut stdin, run(24, "document.node", json!({"id": group, "summary": true, "depth": -1})));
|
||||
assert_eq!(recv(&mut lines, &mut notes, 24)["result"]["isError"], true);
|
||||
mcp_send(stdin, run_command(24, "document.node", json!({"id": group, "summary": true, "depth": -1})));
|
||||
assert_eq!(mcp_recv(lines, notes, 24)["result"]["isError"], true);
|
||||
|
||||
// Skeleton then drill: a depth-0 inspect is the layer skeleton with counts, and
|
||||
// document.find locates nodes without dumping the tree.
|
||||
send(&mut stdin, run(25, "document.inspect", json!({"depth": 0})));
|
||||
let r = recv(&mut lines, &mut notes, 25);
|
||||
mcp_send(stdin, run_command(25, "document.inspect", json!({"depth": 0})));
|
||||
let r = mcp_recv(lines, notes, 25);
|
||||
let skel: Value = serde_json::from_str(r["result"]["content"][0]["text"].as_str().unwrap()).unwrap();
|
||||
assert!(skel["layers"][0].get("children").is_none(), "{skel}");
|
||||
assert!(skel["artboards"].as_array().is_some_and(|a| !a.is_empty()), "{skel}");
|
||||
send(&mut stdin, run(26, "document.find", json!({"kind": "ellipse"})));
|
||||
let r = recv(&mut lines, &mut notes, 26);
|
||||
mcp_send(stdin, run_command(26, "document.find", json!({"kind": "ellipse"})));
|
||||
let r = mcp_recv(lines, notes, 26);
|
||||
let found: Value = serde_json::from_str(r["result"]["content"][0]["text"].as_str().unwrap()).unwrap();
|
||||
assert_eq!(found["total"], 1, "{found}");
|
||||
assert!(found["matches"][0]["path"].as_array().is_some_and(|p| !p.is_empty()), "{found}");
|
||||
send(&mut stdin, run(27, "document.find", json!({})));
|
||||
assert_eq!(recv(&mut lines, &mut notes, 27)["result"]["isError"], true);
|
||||
mcp_send(stdin, run_command(27, "document.find", json!({})));
|
||||
assert_eq!(mcp_recv(lines, notes, 27)["result"]["isError"], true);
|
||||
}
|
||||
|
||||
fn exercise_mcp_logging(
|
||||
stdin: &mut std::process::ChildStdin,
|
||||
lines: &mut std::io::Lines<std::io::BufReader<std::process::ChildStdout>>,
|
||||
notes: &mut Vec<Value>,
|
||||
) {
|
||||
// The binary installed its logger: records arrive as notifications/message, and only
|
||||
// after the client asked for them.
|
||||
send(&mut stdin, json!({"jsonrpc":"2.0","id":11,"method":"logging/setLevel","params":{"level":"debug"}}));
|
||||
assert!(recv(&mut lines, &mut notes, 11).get("error").is_none());
|
||||
send(&mut stdin, json!({"jsonrpc":"2.0","id":12,"method":"tools/list","params":{}}));
|
||||
assert!(recv(&mut lines, &mut notes, 12)["result"]["tools"].as_array().is_some_and(|v| !v.is_empty()));
|
||||
mcp_send(stdin, json!({"jsonrpc":"2.0","id":11,"method":"logging/setLevel","params":{"level":"debug"}}));
|
||||
assert!(mcp_recv(lines, notes, 11).get("error").is_none());
|
||||
mcp_send(stdin, json!({"jsonrpc":"2.0","id":12,"method":"tools/list","params":{}}));
|
||||
assert!(mcp_recv(lines, notes, 12)["result"]["tools"].as_array().is_some_and(|v| !v.is_empty()));
|
||||
}
|
||||
|
||||
fn exercise_mcp_parse_error(
|
||||
stdin: &mut std::process::ChildStdin,
|
||||
lines: &mut std::io::Lines<std::io::BufReader<std::process::ChildStdout>>,
|
||||
notes: &mut Vec<Value>,
|
||||
) {
|
||||
// Malformed input is a parse error, and the server keeps serving afterwards.
|
||||
stdin.write_all(b"{not json\n").unwrap();
|
||||
let parse_err: Value = loop {
|
||||
@@ -566,21 +611,12 @@ fn mcp_protocol_over_stdio() {
|
||||
};
|
||||
assert_eq!(parse_err["error"]["code"], -32700);
|
||||
assert!(parse_err["id"].is_null(), "{parse_err}");
|
||||
send(&mut stdin, json!({"jsonrpc":"2.0","id":13,"method":"ping","params":{}}));
|
||||
assert!(recv(&mut lines, &mut notes, 13).get("error").is_none());
|
||||
drop(stdin);
|
||||
// Drain whatever the debug window queued; every line off the protocol stream parses.
|
||||
for line in lines.by_ref() {
|
||||
let line = line.unwrap();
|
||||
let v: Value = serde_json::from_str(&line).unwrap();
|
||||
assert_eq!(v["jsonrpc"], "2.0", "{v}");
|
||||
notes.push(v);
|
||||
}
|
||||
let out = child.wait_with_output().unwrap();
|
||||
assert!(out.status.success());
|
||||
assert!(!String::from_utf8_lossy(&out.stderr).contains("panicked"));
|
||||
let messages = notes.iter().filter(|n| n["method"] == "notifications/message").count();
|
||||
assert!(messages > 0, "no log records after logging/setLevel: {notes:?}");
|
||||
mcp_send(stdin, json!({"jsonrpc":"2.0","id":13,"method":"ping","params":{}}));
|
||||
assert!(mcp_recv(lines, notes, 13).get("error").is_none());
|
||||
}
|
||||
|
||||
fn run_command(id: u64, command: &str, params: Value) -> Value {
|
||||
json!({"jsonrpc":"2.0","id":id,"method":"tools/call","params":{"name":"run_command","arguments":{"command":command,"params":params}}})
|
||||
}
|
||||
|
||||
/// `vectorcraft-cli commands | head -1` panicked with "failed printing to
|
||||
|
||||
@@ -13,8 +13,6 @@ workspace = true
|
||||
default = ["wgpu", "accessibility"]
|
||||
wgpu = ["eframe/wgpu", "dep:winsafe"]
|
||||
accessibility = ["eframe/accesskit"]
|
||||
# Build with --no-default-features --features windows7 and the win7 Rust target.
|
||||
windows7 = ["eframe/glow"]
|
||||
|
||||
[dependencies]
|
||||
vectorcraft-engine = { workspace = true, features = ["bundled-fonts"] }
|
||||
@@ -37,9 +35,6 @@ image = { workspace = true }
|
||||
webbrowser = { workspace = true }
|
||||
# The window's monitors, size and position (the same winit eframe runs on).
|
||||
winit = { version = "0.30", default-features = false }
|
||||
|
||||
# The win7 target retains the viewer fallback; craft-print's WinRT PDF renderer needs newer Windows.
|
||||
[target.'cfg(all(not(target_arch = "wasm32"), not(target_vendor = "win7")))'.dependencies]
|
||||
craft-print = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
@@ -56,7 +51,6 @@ winsafe = { version = "0.0.29", default-features = false, features = ["user"], o
|
||||
# Win32_UI_WindowsAndMessaging has `MSG` and `WM_POINTERDOWN`, which src/windows_stylus.rs uses to
|
||||
# read a pen's barrel button from its pointer messages. Win32_UI_Input_Pointer has
|
||||
# `GetPointerPenInfo`, which it uses to read the pen's pressure and which end of the pen is down.
|
||||
[target.'cfg(all(windows, not(target_vendor = "win7")))'.dependencies]
|
||||
windows = { version = "0.62", features = ["Win32_Graphics_DirectWrite", "Win32_Graphics_Gdi", "Win32_UI_Input_Pointer", "Win32_UI_WindowsAndMessaging"] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use super::prefs::save_prefs;
|
||||
use super::*;
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
use super::windows_stylus;
|
||||
use super::{App, VectorcraftApp, file_access, window};
|
||||
|
||||
/// The parts of [`App::raw_input_hook`] that every window needs, for the OS windows of floating
|
||||
/// panels: a click or Escape that closes the macOS menu bar's open menu, and the pen barrel
|
||||
@@ -10,12 +12,12 @@ pub(crate) fn native_input_hook(app: &VectorcraftApp) -> Option<vectorcraft_ui_e
|
||||
app.services.native_menu.is_some().then(|| std::sync::Arc::new(mac_menu::raw_input_hook) as vectorcraft_ui_egui::NativeInputHook)
|
||||
}
|
||||
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn native_input_hook(_: &VectorcraftApp) -> Option<vectorcraft_ui_egui::NativeInputHook> {
|
||||
Some(std::sync::Arc::new(windows_stylus::raw_input_hook))
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", all(windows, not(target_vendor = "win7")))))]
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
pub(crate) fn native_input_hook(_: &VectorcraftApp) -> Option<vectorcraft_ui_egui::NativeInputHook> {
|
||||
None
|
||||
}
|
||||
@@ -41,7 +43,7 @@ impl eframe::App for App {
|
||||
mac_menu::raw_input_hook(raw);
|
||||
}
|
||||
// A pen contact with the barrel button held presses the secondary button (Windows Ink).
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
windows_stylus::raw_input_hook(raw);
|
||||
file_access::unconfined(|| self.app.raw_input_hook(ctx, raw));
|
||||
}
|
||||
@@ -65,15 +67,10 @@ impl eframe::App for App {
|
||||
discard_marked_text();
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "windows7"))]
|
||||
fn on_exit(&mut self) {
|
||||
file_access::unconfined(|| save_prefs(&self.app));
|
||||
end_before_teardown(self.frames);
|
||||
}
|
||||
#[cfg(feature = "windows7")]
|
||||
fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) {
|
||||
file_access::unconfined(|| save_prefs(&self.app));
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
||||
@@ -164,7 +164,9 @@ mod win {
|
||||
|
||||
use clipboard_win::{formats, options::NoClear, raw};
|
||||
|
||||
use super::*;
|
||||
use super::{
|
||||
BITMAP, Cursor, Flavour, PNG, SVG, SystemClipboard, TEXT, copied_file, decode_png, dib_image, png_flavour, text_of, windows_formats,
|
||||
};
|
||||
|
||||
/// The device-independent bitmaps, the one that carries alpha first. Each is there whenever the
|
||||
/// other or a `CF_BITMAP` is: the system converts between them.
|
||||
@@ -309,7 +311,7 @@ mod win {
|
||||
mod portable {
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::*;
|
||||
use super::{BITMAP, Flavour, PNG, SystemClipboard, TEXT, copied_file, decode_png, png_flavour, text_of};
|
||||
|
||||
/// What our last write put on the clipboard.
|
||||
enum Ours {
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
use super::*;
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))]
|
||||
use super::portal_save;
|
||||
#[cfg(target_os = "linux")]
|
||||
use super::tablet;
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
use super::windows_stylus;
|
||||
use super::{ClipboardProbeFactory, FilePick, PickRequest, Services, clipboard, cursor_theme, fileio, printing};
|
||||
|
||||
/// The window file dialogs belong to.
|
||||
pub(crate) type Parent = Option<std::sync::Arc<winit::window::Window>>;
|
||||
@@ -266,7 +272,7 @@ pub(crate) fn services(parent: Parent, ui: egui::Context) -> Services {
|
||||
#[cfg(target_os = "macos")]
|
||||
stylus: mac_tablet::start(),
|
||||
// Windows reads it from the pen's pointer messages (`windows_stylus`).
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
stylus: Some(windows_stylus::start()),
|
||||
read: Some(Box::new(|p: &str| std::fs::read(p).map_err(|e| e.to_string()))),
|
||||
write: Some(Box::new(write_file)),
|
||||
|
||||
@@ -15,13 +15,6 @@
|
||||
//! keyboard isn't confined.
|
||||
#![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")]
|
||||
|
||||
#[cfg(all(feature = "windows7", any(feature = "wgpu", feature = "accessibility")))]
|
||||
compile_error!("windows7 requires --no-default-features (wgpu and accessibility must be disabled)");
|
||||
#[cfg(all(windows, feature = "windows7", not(target_vendor = "win7")))]
|
||||
compile_error!("windows7 requires --target x86_64-win7-windows-msvc; the ordinary Windows target still imports newer APIs");
|
||||
#[cfg(all(target_vendor = "win7", not(feature = "windows7")))]
|
||||
compile_error!("the win7 target requires --no-default-features --features windows7");
|
||||
|
||||
mod automation_args;
|
||||
mod clipboard;
|
||||
#[cfg(feature = "wgpu")]
|
||||
@@ -50,7 +43,7 @@ mod open_documents;
|
||||
mod portal_save;
|
||||
mod prefs_dir;
|
||||
mod printing;
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
mod system_fonts;
|
||||
mod tablet;
|
||||
mod window;
|
||||
@@ -190,7 +183,7 @@ fn main() -> std::process::ExitCode {
|
||||
let logger = logging::install();
|
||||
vectorcraft_ui_egui::i18n::detect_system_lang_in_background();
|
||||
// Before the first font scan (the app's start): the fonts font services load (#579).
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
system_fonts::install();
|
||||
#[cfg(target_os = "macos")]
|
||||
mac_fonts::install();
|
||||
@@ -295,7 +288,7 @@ fn main() -> std::process::ExitCode {
|
||||
// With Windows Ink, winit reports a pen as a touch and drops its barrel button and which end
|
||||
// is down; the message hook notes each pointer contact and whether it starts with the barrel
|
||||
// button held, and writes the pen's end into the pen feed (`windows_stylus`).
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
let options = {
|
||||
use winit::platform::windows::EventLoopBuilderExtWindows as _;
|
||||
let mut options = options;
|
||||
@@ -436,10 +429,6 @@ fn main() -> std::process::ExitCode {
|
||||
let (loss, ctx) = (graphics_loss.clone(), cc.egui_ctx.clone());
|
||||
rs.device.set_device_lost_callback(move |reason, msg| loss.report(&ctx, format!("{reason:?}: {msg}")));
|
||||
}
|
||||
#[cfg(feature = "windows7")]
|
||||
{
|
||||
app.graphics_adapter = Some("OpenGL (Windows 7 compatibility)".into());
|
||||
}
|
||||
app.custom_titlebar = custom_titlebar;
|
||||
if let Some(port) = control_port {
|
||||
let rx = control_server::start(port, cc.egui_ctx.clone());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{VectorcraftApp, fileio, prefs_dir};
|
||||
|
||||
/// Where UI preferences live: ~/Library/Application Support/VectorCraft (macOS),
|
||||
/// %APPDATA%\VectorCraft (Windows), $XDG_CONFIG_HOME or ~/.config/vectorcraft (Linux).
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
//! Desktop printer transport. VectorCraft composes every page and copy before submission;
|
||||
//! craft-print owns discovery and spooling. Windows 7 keeps its viewer-based transport because
|
||||
//! Windows.Data.Pdf is unavailable there.
|
||||
#[cfg(not(all(windows, any(feature = "windows7", target_vendor = "win7"))))]
|
||||
//! craft-print owns discovery and spooling.
|
||||
use std::process::Command;
|
||||
use vectorcraft_ui_egui::print::{PrintJob, PrintService, Printer};
|
||||
|
||||
#[cfg(all(windows, any(feature = "windows7", target_vendor = "win7")))]
|
||||
mod windows7;
|
||||
#[cfg(all(windows, any(feature = "windows7", target_vendor = "win7")))]
|
||||
use windows7 as system;
|
||||
|
||||
pub struct SystemPrint;
|
||||
impl PrintService for SystemPrint {
|
||||
fn printers(&mut self) -> Vec<Printer> {
|
||||
@@ -36,9 +29,8 @@ impl PrintService for SystemPrint {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(windows, any(feature = "windows7", target_vendor = "win7"))))]
|
||||
mod system {
|
||||
use super::*;
|
||||
use super::{Command, PrintJob, Printer};
|
||||
pub fn printers() -> Vec<Printer> {
|
||||
craft_print::printers().into_iter().map(|p| Printer { name: p.name, default: p.default }).collect()
|
||||
}
|
||||
@@ -95,7 +87,7 @@ mod system {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, windows, not(any(feature = "windows7", target_vendor = "win7"))))]
|
||||
#[cfg(all(test, windows))]
|
||||
mod tests {
|
||||
use super::system::setup_command;
|
||||
use std::ffi::OsStr;
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
//! The legacy viewer print verb is needed on Windows 7, where Windows.Data.Pdf is absent.
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use vectorcraft_ui_egui::print::{PrintJob, Printer};
|
||||
|
||||
/// Where a job's PDF waits for the spooler: a fresh file in the temp folder. Files of jobs more
|
||||
/// than an hour old (the print verb reads them after Print returns) are removed first.
|
||||
fn job_file(pdf: &[u8]) -> Result<PathBuf, String> {
|
||||
let dir = std::env::temp_dir().join("vectorcraft-print");
|
||||
std::fs::create_dir_all(&dir).map_err(|e| format!("can't make {}: {e}", dir.display()))?;
|
||||
for e in std::fs::read_dir(&dir).into_iter().flatten().flatten() {
|
||||
let old = e.metadata().and_then(|m| m.modified()).ok().and_then(|t| t.elapsed().ok()).is_some_and(|age| age.as_secs() > 3600);
|
||||
if old {
|
||||
// Another job may still be reading it: leave it for next time.
|
||||
let _ = std::fs::remove_file(e.path());
|
||||
}
|
||||
}
|
||||
let stamp = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_nanos()).unwrap_or_default();
|
||||
let path = dir.join(format!("job-{}-{stamp}.pdf", std::process::id()));
|
||||
// The viewer reads asynchronously, so this cannot use immediate drop cleanup. Never
|
||||
// truncate an existing path, and remove incomplete files or refused launches immediately.
|
||||
use std::io::Write as _;
|
||||
let mut file =
|
||||
std::fs::OpenOptions::new().write(true).create_new(true).open(&path).map_err(|e| format!("can't create {}: {e}", path.display()))?;
|
||||
if let Err(e) = file.write_all(pdf).and_then(|_| file.flush()) {
|
||||
drop(file);
|
||||
let _ = std::fs::remove_file(&path);
|
||||
return Err(format!("can't write {}: {e}", path.display()));
|
||||
}
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Run `c` → its output, or its error message.
|
||||
fn output(c: &mut Command) -> Result<String, String> {
|
||||
let program = c.get_program().to_string_lossy().to_string();
|
||||
let out = c.output().map_err(|e| format!("can't run {program}: {e}"))?;
|
||||
if out.status.success() {
|
||||
return Ok(String::from_utf8_lossy(&out.stdout).into_owned());
|
||||
}
|
||||
let err = String::from_utf8_lossy(&out.stderr).trim().to_string();
|
||||
Err(if err.is_empty() { format!("{program} failed ({})", out.status) } else { err })
|
||||
}
|
||||
|
||||
/// PowerShell's `<Default>\t<Name>` lines → the printers.
|
||||
fn windows_printers(text: &str) -> Vec<Printer> {
|
||||
text.lines()
|
||||
.filter_map(|l| l.trim_end_matches('\r').split_once('\t'))
|
||||
.filter(|(_, name)| !name.trim().is_empty())
|
||||
.map(|(default, name)| Printer { name: name.trim().to_string(), default: default.trim().eq_ignore_ascii_case("true") })
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// `s` as a PowerShell single-quoted string.
|
||||
fn ps_quote(s: &str) -> String {
|
||||
format!("'{}'", s.replace('\'', "''"))
|
||||
}
|
||||
|
||||
fn powershell(script: &str) -> Command {
|
||||
use std::os::windows::process::CommandExt as _;
|
||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||
let mut c = Command::new(
|
||||
std::path::PathBuf::from(std::env::var_os("SystemRoot").unwrap_or_else(|| "C:\\Windows".into()))
|
||||
.join("System32")
|
||||
.join("WindowsPowerShell")
|
||||
.join("v1.0")
|
||||
.join("powershell.exe"),
|
||||
);
|
||||
c.args([
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-Command",
|
||||
&format!("$ErrorActionPreference = 'Stop'; [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false); {script}"),
|
||||
])
|
||||
.creation_flags(CREATE_NO_WINDOW);
|
||||
c
|
||||
}
|
||||
|
||||
pub fn printers() -> Vec<Printer> {
|
||||
let script = "Get-WmiObject Win32_Printer | ForEach-Object { \"$($_.Default)`t$($_.Name)\" }";
|
||||
output(&mut powershell(script)).map(|t| windows_printers(&t)).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn submit(job: &PrintJob, file: &std::path::Path) -> Result<String, String> {
|
||||
// The PDF viewer's print verb reads the file after this returns: it stays for a while.
|
||||
let path = ps_quote(&file.to_string_lossy());
|
||||
let script = match job.printer {
|
||||
Some(p) => format!(
|
||||
"Start-Process -FilePath {path} -Verb PrintTo -ArgumentList {} -WindowStyle Hidden",
|
||||
ps_quote(&format!("\"{}\"", p.replace('"', "")))
|
||||
),
|
||||
None => format!("Start-Process -FilePath {path} -Verb Print -WindowStyle Hidden"),
|
||||
};
|
||||
output(&mut powershell(&script)).map(|_| String::new()).map_err(|e| format!("no app prints PDF files here ({e})"))
|
||||
}
|
||||
|
||||
pub fn setup(_printer: Option<&str>) -> Result<(), String> {
|
||||
let mut command = {
|
||||
let mut command = Command::new("control.exe");
|
||||
command.arg("printers");
|
||||
command
|
||||
};
|
||||
command.spawn().map(|_| ()).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub fn print(job: &PrintJob) -> Result<String, String> {
|
||||
let file = job_file(job.pdf)?;
|
||||
let result = submit(job, &file);
|
||||
if result.is_err() {
|
||||
// No viewer accepted the launch, so no asynchronous reader needs this document.
|
||||
let _ = std::fs::remove_file(file);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn powershell_lines_and_quotes_are_preserved() {
|
||||
assert_eq!(windows_printers("True\tOffice Laser\r\nFalse\t "), [Printer { name: "Office Laser".into(), default: true }]);
|
||||
assert_eq!(ps_quote("it's.pdf"), "'it''s.pdf'");
|
||||
}
|
||||
#[test]
|
||||
fn job_files_hold_the_pdf_for_the_async_viewer() {
|
||||
let path = job_file(b"%PDF-1.7 test").unwrap();
|
||||
assert_eq!(std::fs::read(&path).unwrap(), b"%PDF-1.7 test");
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::*;
|
||||
use super::window;
|
||||
#[cfg(target_os = "linux")]
|
||||
use super::xwayland;
|
||||
|
||||
/// 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
|
||||
@@ -113,8 +115,6 @@ pub(crate) fn native_options(custom_titlebar: bool) -> eframe::NativeOptions {
|
||||
.with_title_shown(false)
|
||||
.with_icon(app_icon())
|
||||
.with_app_id("ai.storyteller.vectorcraft"),
|
||||
#[cfg(feature = "windows7")]
|
||||
renderer: eframe::Renderer::Glow,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
//! mouse message that Windows did not make from a pen or a touch
|
||||
//! (<https://learn.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages>)
|
||||
//! clears the feed.
|
||||
#![cfg_attr(not(all(windows, not(target_vendor = "win7"))), allow(dead_code))]
|
||||
#![cfg_attr(not(windows), allow(dead_code))]
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::sync::OnceLock;
|
||||
@@ -68,7 +68,7 @@ pub fn start() -> StylusFeed {
|
||||
/// message it takes from the thread's queue, before dispatching it: notes each pointer contact's id
|
||||
/// and whether the barrel button was held, and writes the pen's pressure and end into the feed.
|
||||
/// It never consumes the message.
|
||||
#[cfg(all(windows, not(target_vendor = "win7")))]
|
||||
#[cfg(windows)]
|
||||
#[allow(unsafe_code)]
|
||||
pub fn msg_hook(msg: *const std::ffi::c_void) -> bool {
|
||||
use windows::Win32::UI::Input::Pointer::{GetPointerPenInfo, POINTER_FLAG_INCONTACT, POINTER_PEN_INFO};
|
||||
|
||||
@@ -6,3 +6,4 @@ allow-panic-in-tests = true
|
||||
disallowed-types = [
|
||||
{ path = "egui::ComboBox", reason = "use craft_ui::ComboBox: egui's combo box menu can't grow back while open (egui#5225)" },
|
||||
]
|
||||
cognitive-complexity-threshold = 30
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::read::MAX_TREE_DEPTH;
|
||||
use super::*;
|
||||
use super::{Affine, Kind, Node, ObjId, Point, Rect, Stream, Tag};
|
||||
|
||||
/// The bounds of what visible `nodes` draw (control points of outlines, corners of images, frames
|
||||
/// and tables), in document pixels.
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::bounds::{describe, is_container, mask_extent, nodes_bounds, pages, rect, tree_bounds, union};
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, Archive, BTreeMap, Blend, Document, Effect, Error, HashSet, Image, Kind, Limits, Node, ObjId, Paint, Path, Point, Rect, Spread, Stream,
|
||||
Tag, paint, stream,
|
||||
};
|
||||
|
||||
/// Largest embedded colour profile kept (real CMYK profiles are a few megabytes).
|
||||
const MAX_PROFILE_BYTES: usize = 32 << 20;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::profile::{PressureProfile, WidthProfile};
|
||||
use super::stroke_style::{ArrowAlign, Arrowhead, Dash, LineCap, LineJoin, StrokeAlign};
|
||||
use super::*;
|
||||
use super::{BlendMode, Deserialize, Paint, Serialize};
|
||||
|
||||
/// A live effect in an appearance stack. Parameters are interpreted by `vectorcraft-effects`.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Deserialize, Serialize};
|
||||
|
||||
/// Variable-width profile: (position 0..1 along the path, left width factor, right width factor),
|
||||
/// in order along the path. Two points at the same position make a discontinuous point: the
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum LineCap {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Arc, Deserialize, Node, NodeId, NodeKind, Rect, Serialize, SymbolBoxes, skip};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Artboard {
|
||||
|
||||
@@ -3,7 +3,10 @@ use super::graphic_style::default_graphic_styles;
|
||||
use super::guides::{GridPrefs, RulerMode};
|
||||
use super::limits::{DocError, FORMAT_VERSION, MAX_COORD, MAX_NEXT_ID};
|
||||
use super::units::{ColorMode, Unit};
|
||||
use super::*;
|
||||
use super::{
|
||||
Arc, BTreeMap, ColorProfiles, DocMetadata, DocSetup, Document, LAYER_COLORS, LayerColor, Node, NodeId, NodeKind, PatternDef, Places, Point,
|
||||
RasterEffectsSettings, Rect, Vec2,
|
||||
};
|
||||
|
||||
impl Document {
|
||||
/// Is everything but the layers the same in `other`, as the next version of the document after
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use super::graphic_style::GraphicStyle;
|
||||
use super::limits::MAX_NEXT_ID;
|
||||
use super::records::{ImageBlob, SavedSelection, SavedView};
|
||||
use super::*;
|
||||
use super::{
|
||||
Arc, Document, ImageObject, Node, NodeKind, SYMBOL_EXPORT_TYPE, SYMBOL_NINE_SLICE, SYMBOL_REGISTRATION, SymbolExportType, SymbolNineSlice,
|
||||
SymbolRegistration,
|
||||
};
|
||||
|
||||
impl Document {
|
||||
/// The pixels image `im` shows: its own ([`ImageObject::pixels`]) or this document's.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::document_ext::free_id;
|
||||
use super::guides::Guide;
|
||||
use super::*;
|
||||
use super::{Affine, Arc, Document, Node, NodeId, NodeKind, PatternDef, Point, Rect, Selection, SymbolBoxes, Vec2, artboard_holds};
|
||||
|
||||
impl Document {
|
||||
/// Union of the geometric bounds of `ids` (their visual bounds when `visual`), with symbol
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Appearance, Deserialize, Knockout, Node, Rect, Serialize, skip};
|
||||
|
||||
/// A named graphic style (Graphic Styles panel): an appearance plus the object's transparency.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Deserialize, NodeId, Serialize, Vec2};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Guide {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::NodeId;
|
||||
|
||||
/// Coordinates beyond this many points exceed the supported document range.
|
||||
pub const MAX_COORD: f64 = 4.0e6;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Deserialize, PathData, Serialize};
|
||||
|
||||
// =====================================================================================
|
||||
// Data
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::expand::{Hooks, Outliner, Warper, map_node};
|
||||
use super::mesh::{GradientMesh, MeshPoint, grid_len, grid_points, nodes_bounds, surface_of};
|
||||
use super::*;
|
||||
use super::{Affine, Arc, Color, Deserialize, Node, NodeId, NodeKind, PathData, Point, Rect, Serialize, Vec2, yes};
|
||||
|
||||
/// How an envelope distorts its content.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use super::envelope::{EnvelopeOptions, expand_envelope};
|
||||
use super::mesh::GradientMesh;
|
||||
use super::warp::map_nonlinear;
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, Appearance, AppearanceItem, Arc, FillLayer, ImageObject, Node, NodeId, NodeKind, Paint, PathData, Point, Rect, SubPath, Vec2,
|
||||
blend_expand,
|
||||
};
|
||||
|
||||
/// Optional hook that converts nodes the pure evaluation can't map (text) to outlines.
|
||||
pub type Outliner<'a> = Option<&'a dyn Fn(&Node) -> Option<Node>>;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use super::envelope::{EnvelopeKind, H_DOWN, H_LEFT, H_RIGHT, H_UP, envelope_grid, mesh_at};
|
||||
use super::warp::{WarpStyle, flatten_subpath, resample, warp_point};
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, Anchor, Arc, Color, CubicBez, Deserialize, Node, ParamCurve, PathData, Point, Rect, Serialize, Shape, SubPath, Vec2, fifty, lerp_color,
|
||||
one,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MeshPoint {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Anchor, BezPath, CubicBez, PI, ParamCurve, PathData, Point, SubPath};
|
||||
|
||||
// =====================================================================================
|
||||
// Warp maps (shared with vectorcraft-effects' Warp effects)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::kind::NodeKind;
|
||||
use super::live_shape::LiveShape;
|
||||
use super::*;
|
||||
use super::{Deserialize, Node, Serialize};
|
||||
|
||||
/// Window → Attributes: an object's settings that don't change how it prints.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::kind::NodeKind;
|
||||
use super::*;
|
||||
use super::{BezPath, FillRule, Node, Outliner, Rect, shapes};
|
||||
|
||||
/// Unites filled regions (each under its own fill rule) into one path filled non-zero. Booleans
|
||||
/// live above this crate (`vectorcraft-pathops`), so callers supply it.
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::attributes::ObjectAttributes;
|
||||
use super::kind::NodeKind;
|
||||
use super::live_shape::LiveShape;
|
||||
use super::opacity::OpacityMask;
|
||||
use super::*;
|
||||
use super::{Node, PathData};
|
||||
|
||||
/// The parts of a [`Node`] that few objects have: kept out of line, so that the nodes without
|
||||
/// them stay small ([`Node::extras`]).
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Deserialize, Serialize};
|
||||
|
||||
/// Stable per-document object id. Never reused.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::ids::LayerColor;
|
||||
use super::live_shape::LiveShape;
|
||||
use super::*;
|
||||
use super::{Affine, Arc, BlendSpec, Deserialize, EnvelopeKind, FillRule, GradientMesh, Node, PathData, RepeatSpec, Serialize, TextObject, yes};
|
||||
|
||||
/// Embedded or linked raster image.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Affine, CornerKind, Deserialize, PathData, Point, Rect, Serialize, cut_corners, shapes};
|
||||
|
||||
/// Live shape parameters (Illustrator's "Live Shapes"). The node's path is regenerated from these
|
||||
/// while the shape stays live; editing anchors directly converts it to a plain path.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::kind::NodeKind;
|
||||
use super::*;
|
||||
use super::{Arc, Color, Deserialize, Node, Paint, Serialize, yes};
|
||||
|
||||
/// Opacity mask: the luminance of the mask art sets the object's opacity (white = opaque).
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -3,7 +3,7 @@ use super::ids::{LayerColor, NodeId};
|
||||
use super::kind::NodeKind;
|
||||
use super::live_shape::{Scaling, moves_only};
|
||||
use super::opacity::Knockout;
|
||||
use super::*;
|
||||
use super::{Affine, Appearance, Arc, BlendMode, EnvelopeKind, FillRule, Node, PathData, Point, Rect, RepeatSpec, SYMBOL_SQUARE, Shape, SymbolBoxes};
|
||||
|
||||
impl Node {
|
||||
pub fn new(id: NodeId, kind: NodeKind) -> Self {
|
||||
|
||||
@@ -3,7 +3,7 @@ use super::extras::{Extras, NO_EXTRAS, NodeExtras};
|
||||
use super::ids::NodeId;
|
||||
use super::kind::NodeKind;
|
||||
use super::opacity::{Knockout, OpacityMask};
|
||||
use super::*;
|
||||
use super::{Appearance, BlendMode, Deserialize, Node, PathData, Serialize, one, yes};
|
||||
|
||||
/// How a [`Node`] is written: the flat field list of the file format, in its order, borrowed from
|
||||
/// the node and its extras (nothing is copied). The node reads back through `NodeWire`.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Arc, Deserialize, Node, NodeId, Point, Serialize};
|
||||
|
||||
/// New art on a locked artboard ([`Document::new_art`]). Setting it changes the document in
|
||||
/// place, so a selection change that sets it doesn't replace the document.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::para_style::FirstBaseline;
|
||||
use super::*;
|
||||
use super::{Deserialize, PathData, Serialize};
|
||||
|
||||
/// Area Type Options "Align" (vertical): where the lines of each row/column sit in it.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::para_style::ParaDirection;
|
||||
use super::*;
|
||||
use super::{Appearance, AppearanceItem, Color, Dash, Deserialize, FillLayer, LineCap, LineJoin, Paint, Serialize, StrokeAlign, StrokeLayer};
|
||||
|
||||
/// Character attributes (the Character panel).
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -3,7 +3,7 @@ use super::char_style::CharStyle;
|
||||
use super::para_style::ParaStyle;
|
||||
use super::runs::{RubySpan, TextKind, TextRun, move_anchors, reverse_from_end};
|
||||
use super::tabs_lists::ParaList;
|
||||
use super::*;
|
||||
use super::{Affine, PathData, Point, Rect, TextObject, TextStyleScale, Vec2};
|
||||
|
||||
impl TextObject {
|
||||
/// None for collapsed or unrepresentable transforms, whose type dimensions cannot
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::char_style::CharStyle;
|
||||
use super::tabs_lists::{ParaList, TabStop};
|
||||
use super::*;
|
||||
use super::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Justify {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::char_style::CharStyle;
|
||||
use super::*;
|
||||
use super::{Affine, Deserialize, PathData, Point, Rect, Serialize, Vec2, yes};
|
||||
|
||||
/// A named character or paragraph style: the attributes it sets (a subset of [`CharStyle`] or
|
||||
/// [`ParaStyle`] fields, by their serialized names). Text using it records the name.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::char_style::{CharPosition, CharStyle};
|
||||
use super::*;
|
||||
use super::{Deserialize, Serialize};
|
||||
|
||||
/// Tab stop alignment (Tabs panel).
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{Deserialize, Serialize};
|
||||
|
||||
/// Measurement units (display only; the model is always in points).
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::*;
|
||||
use super::{CROP_MARKS, EXTRUDE, INFLATE, PATHFINDER_EFFECTS, RASTERIZE, REVOLVE, ROTATE, TRAP, Value, json, trap};
|
||||
|
||||
/// Catalogue entry for one effect.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
||||
@@ -4,7 +4,7 @@ use serde_json::Value;
|
||||
use vectorcraft_geom::kurbo::ParamCurveArclen;
|
||||
use vectorcraft_geom::{Affine, Anchor, ParamCurve, PathData, Point, Rect, SubPath, Vec2};
|
||||
|
||||
use crate::util::*;
|
||||
use crate::util::{catmull_rom, fit_piece, flag, map_nonlinear_within, mean_size, noise, normal_at, num, pieces, seed, seg_cubic, smooth_points};
|
||||
|
||||
fn diag(b: Rect) -> f64 {
|
||||
b.width().hypot(b.height()).max(1e-6)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::params::{is_geometry, merged_params};
|
||||
use super::*;
|
||||
use super::{
|
||||
Appearance, AppearanceItem, BezPath, Effect, FillRule, Node, NodeKind, PathData, Rect, StrokeLayer, Value, WarpStyle, distort, stroke, stylize,
|
||||
util, warp,
|
||||
};
|
||||
|
||||
/// Context the geometry effects may need from the object's appearance.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
@@ -167,7 +170,7 @@ fn builds_within(id: &str) -> bool {
|
||||
|
||||
/// [`apply_one`] whose result may pass `max` when the effect doesn't build within it.
|
||||
fn apply_unchecked(id: &str, p: &Value, path: &PathData, b: Rect, ctx: &GeomContext, max: usize) -> PathData {
|
||||
use util::*;
|
||||
use util::{join, num};
|
||||
if let Some(plugin) = vectorcraft_plugins::effect::plugin_id(id) {
|
||||
let paints = vectorcraft_plugins::effect::Paints { appearance: ctx.appearance, item: ctx.item };
|
||||
return vectorcraft_plugins::effect::apply(plugin, p, path, b, ctx.rule, paints).unwrap_or_else(|| path.clone());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::catalog::{catalog_index, plugin_info};
|
||||
use super::*;
|
||||
use super::{CROP_MARKS, Effect, Map, Node, PIXEL_EFFECTS, RASTERIZE, Value, is_adjustment, is_pathfinder, is_three_d, json, util};
|
||||
|
||||
/// Dialog defaults for `id` (`None` for unknown effects).
|
||||
pub fn default_params(id: &str) -> Option<Value> {
|
||||
|
||||
@@ -1755,6 +1755,10 @@ fn brush_strokes_params_take_defaults_and_stay_in_range() {
|
||||
fx("brushStrokes.sumiE", json!({"strokeWidth": 2, "strokePressure": 16, "contrast": -1})),
|
||||
PixelFx::SumiE { width: 3.0, pressure: 15.0, contrast: 0.0 }
|
||||
);
|
||||
brush_strokes_outsets();
|
||||
}
|
||||
|
||||
fn brush_strokes_outsets() {
|
||||
// Outsets: the strokes and the shifts reach out of the object as far as into it; Accented
|
||||
// Edges and Sumi-e keep the object's shape.
|
||||
let b = Rect::new(0.0, 0.0, 100.0, 50.0);
|
||||
@@ -2561,6 +2565,10 @@ fn artistic_params_take_defaults_and_stay_in_range() {
|
||||
fx("artistic.plasticWrap", json!({"highlightStrength": -1, "detail": 99, "smoothness": 0})),
|
||||
PixelFx::PlasticWrap { highlight: 0.0, detail: 15.0, smoothness: 1.0 }
|
||||
);
|
||||
artistic_outsets_and_docs();
|
||||
}
|
||||
|
||||
fn artistic_outsets_and_docs() {
|
||||
for id in [
|
||||
"artistic.coloredPencil",
|
||||
"artistic.cutout",
|
||||
|
||||
@@ -9,7 +9,7 @@ use vectorcraft_geom::{Point, Rect};
|
||||
|
||||
use crate::merged_params;
|
||||
use crate::pixel::{self, PixelFx, PixelSpace};
|
||||
use crate::util::*;
|
||||
use crate::util::{flag, num, text};
|
||||
|
||||
/// Effect › Rasterize's id.
|
||||
pub const RASTERIZE: &str = "rasterize";
|
||||
|
||||
@@ -5,7 +5,7 @@ use vectorcraft_geom::kurbo;
|
||||
use vectorcraft_geom::shapes::{self, KAPPA};
|
||||
use vectorcraft_geom::{Affine, Anchor, PathData, Point, Rect, SubPath, corners};
|
||||
|
||||
use crate::util::*;
|
||||
use crate::util::{catmull_rom, flag, noise, num, seed};
|
||||
|
||||
/// Convert to Shape (rectangle / rounded rectangle / ellipse) around the bounds.
|
||||
pub fn convert_to_shape(id: &str, b: Rect, p: &Value) -> PathData {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
use serde_json::Value;
|
||||
use vectorcraft_geom::{PathData, Point, Rect};
|
||||
|
||||
use crate::util::*;
|
||||
use crate::util::{map_nonlinear_within, num, text};
|
||||
|
||||
pub use vectorcraft_doc::live::{WarpStyle, warp_point};
|
||||
|
||||
|
||||
@@ -28,8 +28,11 @@ use vectorcraft_geom::Rect;
|
||||
use super::edit::{outermost, selected_roots};
|
||||
use super::opacitymask::percent;
|
||||
use super::paint::paint_from;
|
||||
use super::*;
|
||||
use super::{AppearanceAttrs, EyedropperAttrs, FillAttrs, StrokeAttrs};
|
||||
use super::{
|
||||
CommandSpec, NodeId, Result, Session, bad, check_numbers, check_switches, cmd, has_doc, has_selection, id_param, ids_param, index, leaf_targets,
|
||||
ok, paint_targets, str_param, switch_or, targets,
|
||||
};
|
||||
use crate::EngineError;
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -8,7 +8,7 @@ use vectorcraft_doc::{ExportAsset, NodeId};
|
||||
|
||||
use super::edit::{roots_of, selected_roots};
|
||||
use super::fileio::{SCREEN_SETTINGS_KEYS, check_screen_settings, export_crop, export_screens, store_screen_settings};
|
||||
use super::*;
|
||||
use super::{CommandSpec, EngineError, Result, Session, bad, bool_or, checked_ids_param, cmd, has_doc, ok, str_param};
|
||||
|
||||
/// The longest asset name kept (characters).
|
||||
const MAX_NAME: usize = 255;
|
||||
|
||||
@@ -10,7 +10,7 @@ use vectorcraft_geom::FillRule;
|
||||
|
||||
use super::overprint::{AttributesInfo, apply_overprint, edit_counted, overprint_jobs};
|
||||
use super::path::paths_in;
|
||||
use super::*;
|
||||
use super::{CommandSpec, NodeId, Result, Session, bad, cmd, has_doc, str_param, targets};
|
||||
|
||||
/// How many URLs the Attributes panel's recent list keeps.
|
||||
const RECENT_URLS: usize = 10;
|
||||
|
||||
@@ -22,7 +22,7 @@ use super::clipboard::resources::SwatchChoices;
|
||||
use super::patterncmds::add_pattern;
|
||||
use super::swatch::str_list;
|
||||
use super::swatchlib::{FileData, LibraryFile, LibraryInfo, stem};
|
||||
use super::*;
|
||||
use super::{CommandSpec, Result, Session, always, bad, bool_or, cmd, has_doc, str_param};
|
||||
|
||||
/// The extension of saved brush libraries: native documents.
|
||||
pub const BRUSHES_EXT: &str = vectorcraft_format::EXTENSION;
|
||||
|
||||
@@ -18,7 +18,10 @@ use vectorcraft_geom::{Affine, Point, Vec2};
|
||||
use super::edit::{outermost, selected_roots};
|
||||
use super::pathops::{remove_each, separate};
|
||||
use super::swatch::{anchor, names_param};
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, Result, Session, bad, check_numbers, cmd, color_value, find_command, has_doc, has_selection, id_param, ids_param, number_or, ok,
|
||||
str_param, switch_or,
|
||||
};
|
||||
use crate::EngineError;
|
||||
|
||||
mod brushes;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
AppearanceItem, Arc, BTreeSet, Bristle, Brush, BrushKind, Color, Document, EngineError, Node, NodeId, NodeKind, Paint, Result, Session, Value,
|
||||
anchor, bad, brush, check_numbers, find_command, id_param, ids_json, json, name_param, names_param, ok, roots, selection_art, set_unknown,
|
||||
str_param,
|
||||
};
|
||||
|
||||
// ---------- brushes ----------
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, AppearanceItem, Arc, CURRENT_SYMBOL, Color, Document, EngineError, FillLayer, HALF, Node, NodeId, NodeKind, Paint, Point, Result,
|
||||
SIZES_KEY, Session, Symbol, SymbolExportType, SymbolNineSlice, SymbolRegistration, Value, Vec2, bad, brush, color_value, ids_json, json,
|
||||
name_param, number_or, ok, remove_each, roots, selection_art, separate, set_unknown, str_param, switch_or,
|
||||
};
|
||||
|
||||
// ---------- symbols ----------
|
||||
|
||||
|
||||
@@ -27,7 +27,10 @@ use super::draw2::replace_with_pieces;
|
||||
use super::edit::{outermost, selected_roots};
|
||||
use super::paint::paint_from;
|
||||
use super::pathops::{Beside, shape_node};
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, EngineError, Result, Session, always, bad, check_numbers, cmd, color_value, f64_or, finite_numbers, has_doc, id_param, ids_param,
|
||||
number_or, point_param, str_param, switch_or,
|
||||
};
|
||||
|
||||
mod live_paint;
|
||||
mod shape_builder;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Appearance, Arc, Beside, Document, EDGE_NAME, EngineError, FACE_NAME, GapOptions, GapSize, LIVE_PAINT_NAME, Node, NodeId, Paint, PathData, Point,
|
||||
Rect, Result, SOURCES_NAME, Selection, Session, SubPath, Value, b, bad, build_children, edge_near, face_at, finite, id_param, ids_json,
|
||||
is_live_paint, json, number_or, paint_from, po, point_param, repaint_from, replace_roots, root_ids, source_nodes, source_shapes, switch_or,
|
||||
};
|
||||
|
||||
/// `options` as stored on a Live Paint group: none for the defaults.
|
||||
fn stored_gap_options(options: GapOptions) -> Option<Box<GapOptions>> {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Beside, BuilderMap, EngineError, FillRule, Node, NodeId, PathData, Point, Rect, Result, Session, SubPath, Touched, Value, b, bad, check_numbers,
|
||||
f64_or, fill_param, finite, gap_param, ids_json, json, marquee_param, po, points_param, remnant, replace_roots, replace_with_pieces, root_ids,
|
||||
shape_node, switch_or,
|
||||
};
|
||||
|
||||
pub(super) fn sb_merge(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "shapeBuilder.merge";
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Appearance, AppearanceItem, Arc, Beside, Color, Document, EngineError, LineCap, LineJoin, LiveShape, Node, NodeId, NodeKind, Paint, Point,
|
||||
Result, Session, StrokeLayer, TRACE_NAME, TraceView, Value, bad, fill_only, ids_json, json, root_ids, shape_node, str_param, switch_or, tr,
|
||||
};
|
||||
|
||||
// ---------- Image Trace ----------
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ pub use flavours::{AI, BITMAP, EMF, EPS, FILE_HEAD, Flavour, PASTE_ORDER, PDF, P
|
||||
pub(crate) use resources::SwatchChoices;
|
||||
pub use resources::{ArtResources, art_resources};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, Result, Session, always, bad, cmd, f64_or, has_doc, point_param, str_param};
|
||||
use crate::DocState;
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
|
||||
@@ -12,7 +12,7 @@ use vectorcraft_geom::{Affine, Point};
|
||||
|
||||
use super::super::fileio;
|
||||
use super::super::place::pt_per_px;
|
||||
use super::*;
|
||||
use super::{Clipboard, CommandSpec, Result, Session, always, bad, cmd, f64_or, has_doc, looks_like_svg, point_param, str_param};
|
||||
|
||||
/// Plain text: a type-only copy's text, else the SVG markup (Include SVG Code).
|
||||
pub const TEXT: &str = "text/plain";
|
||||
|
||||
@@ -18,7 +18,9 @@ use vectorcraft_doc::recolor::{ColorVisitor, Reach, map_links, recolor_node};
|
||||
use vectorcraft_doc::{Document, ImageBlob, Node, NodeId, NodeKind};
|
||||
|
||||
use super::edit::selected_roots;
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, EngineError, Result, Session, always, bad, bool_or, cmd, color_value, f64_or, has_selection, ids_param, str_param, unique_name,
|
||||
};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
|
||||
@@ -17,7 +17,9 @@ use vectorcraft_color::{Color, Paint};
|
||||
use vectorcraft_doc::ColorMode;
|
||||
use vectorcraft_render::proof;
|
||||
|
||||
use super::*;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use super::EngineError;
|
||||
use super::{CommandSpec, Result, Session, always, bad, bool_or, cmd, color_value, has_doc, str_param, targets};
|
||||
|
||||
/// `Document.unknown` key under which files before format v3 kept the assigned profiles (migrated
|
||||
/// to `Document::color_profiles` on load).
|
||||
|
||||
@@ -9,7 +9,7 @@ use vectorcraft_color::harmony::{Harmony, THEME_SIZE};
|
||||
use vectorcraft_color::recolor::ColorKey;
|
||||
use vectorcraft_tools::params::color_json;
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, Result, Session, always, bad, cmd, color_value, has_doc, str_param};
|
||||
|
||||
/// A saved colour theme ([`crate::Prefs::color_themes`]).
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -5,7 +5,10 @@ use vectorcraft_doc::{Appearance, CharStyle, LiveShape, Node, NodeKind, TextObje
|
||||
use vectorcraft_geom::{Affine, Anchor, AnchorKind, FillRule, PathData, Point, Rect, SubPath, shapes};
|
||||
|
||||
use super::newart::NewArt;
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, NodeId, Result, Session, bad, bool_or, check_numbers, check_switches, cmd, color_value, f64_or, f64_req, finite_numbers, has_doc,
|
||||
number_or, str_param, switch_or,
|
||||
};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
|
||||
@@ -8,7 +8,7 @@ use vectorcraft_doc::NodeId;
|
||||
use vectorcraft_svg::{CssOptions, CssSheet, css_rules};
|
||||
|
||||
use super::fileio::{default_name, encode_with_warnings, isolated, write_file};
|
||||
use super::*;
|
||||
use super::{CommandSpec, Result, Session, bad, cmd, has_doc, str_param, targets};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
|
||||
@@ -15,7 +15,7 @@ use vectorcraft_pathops::BoolOp;
|
||||
use super::draw2::{components, erase_outline, ids_json, replace_with_pieces};
|
||||
use super::edit::selected_roots;
|
||||
use super::pathops::{Beside, node_path};
|
||||
use super::*;
|
||||
use super::{CommandSpec, Result, Session, bad, cmd, finite_numbers, has_selection, point_param, str_param};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Linked dimensions: generated groups share the existing text/path render and export pipeline.
|
||||
use super::*;
|
||||
use super::{CommandSpec, EngineError, Result, Session, cmd, has_doc};
|
||||
use serde_json::{Value, json};
|
||||
use std::sync::Arc;
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
|
||||
@@ -14,7 +14,10 @@ use vectorcraft_tools::distort::perspective::{self as persp, PerspectiveGrid, Pl
|
||||
use vectorcraft_tools::distort::{PinSet, arap, collect_points, mesh_for, project_node, stroke_owner, warp_from_rest, warp_node_with};
|
||||
|
||||
use super::edit::{duplicate_in, outermost, selected_roots};
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, Result, Session, bad, check_numbers, check_switches, cmd, f64_or, f64_req, finite_numbers, has_doc, has_selection, id_param,
|
||||
ids_param, matrix_param, number_or, ok, point_indices, point_param, str_param, switch_or, targets,
|
||||
};
|
||||
use crate::EngineError;
|
||||
|
||||
mod perspective;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Affine, Arc, Document, EngineError, Homography, NodeId, NodeKind, PerspectiveGrid, Plane, Point, Result, Session, Value, bad, check_numbers,
|
||||
check_switches, collect_points, duplicate_in, finite_numbers, id_param, ids_param, json, matrix_param, number_or, ok, outermost, persp,
|
||||
point_param, project_node, selected_roots, str_param, switch_or,
|
||||
};
|
||||
|
||||
// ---------- perspective grid ----------
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use vectorcraft_doc::{ColorMode, Document, Node, NodeId, NodeKind, RepeatKind, R
|
||||
use vectorcraft_geom::{Affine, Rect};
|
||||
use vectorcraft_text::embed::Embedding;
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, Result, Session, bad, bool_or, cmd, has_doc, has_selection, str_param, targets};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
|
||||
@@ -12,7 +12,10 @@ use vectorcraft_geom::Vec2;
|
||||
use super::edit::selected_roots;
|
||||
use super::pathops::Beside;
|
||||
use super::select::{selectable_ids, walk_selectable};
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, EngineError, Point, Result, Session, bad, bool_or, checked_ids_param, cmd, f64_or, f64_req, has_doc, has_selection, id_param, ok,
|
||||
str_param,
|
||||
};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
|
||||
@@ -7,7 +7,7 @@ use vectorcraft_doc::{Background, CharPosition, CharStyle, DocSetup, Document, E
|
||||
|
||||
use super::flatten::{FlattenOptions, FlattenerPreset};
|
||||
|
||||
use super::*;
|
||||
use super::{CommandSpec, Result, Session, bad, cmd, color_value, has_doc};
|
||||
|
||||
const C: &str = "document.setup";
|
||||
|
||||
|
||||
@@ -17,7 +17,10 @@ use vectorcraft_tools::draw2::{eraser_area, eraser_tip};
|
||||
use super::create::{add_look, insert_look};
|
||||
use super::newart::NewArt;
|
||||
use super::pathops::{Beside, remove_each};
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, Result, Session, bad, check_numbers, cmd, f64_or, has_anchors, has_doc, has_selection, id_param, index, number_or, ok, str_param,
|
||||
switch_or,
|
||||
};
|
||||
use crate::EngineError;
|
||||
|
||||
mod anchors;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::common::{MAX_INPUT, path_kind, path_mut, point_entry, usize_opt, usize_req, within_input_bound};
|
||||
use super::*;
|
||||
use super::{
|
||||
Anchor, BTreeSet, Beside, Color, Document, EngineError, Node, NodeId, NodeKind, Paint, PathData, Point, Result, Selection, Session, Value, Vec2,
|
||||
add_look, bad, check_numbers, f64_or, id_param, json, number_or, ok, po, str_param, switch_or,
|
||||
};
|
||||
|
||||
// ---------- Curvature ----------
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::anchors::{convert_anchor, curvature, curvature_edit, remove_anchor, r
|
||||
use super::cut::{erase_region, erase_segments, knife, split};
|
||||
use super::freehand::{blob, freehand};
|
||||
use super::smooth::{join_scrub, smooth_region};
|
||||
use super::*;
|
||||
use super::{CommandSpec, cmd, has_anchors, has_doc, has_selection};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use super::*;
|
||||
use super::{
|
||||
Appearance, Arc, Beside, Cap, Color, Document, EngineError, FillRule, Join, Node, NodeId, NodeKind, Paint, PathData, Point, PressureProfile,
|
||||
Rect, Result, Selection, Session, SubPath, Value, Vec2, bad, index, json, po,
|
||||
};
|
||||
|
||||
// ---------- helpers ----------
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use super::common::{
|
||||
dist_to_polyline, ids_json, path_mut, path_of, points_param, poly_bounds, replace_with_pieces, sibling_with, target_paths, usize_opt,
|
||||
};
|
||||
use super::*;
|
||||
use super::{
|
||||
Anchor, AnchorKind, Beside, BezPath, BoolOp, Cap, EngineError, FillRule, Join, NodeId, ParamCurve, PathData, Point, Result, Session, SubPath,
|
||||
Value, bad, eraser_area, eraser_tip, f64_or, fill_contains, id_param, json, number_or, po,
|
||||
};
|
||||
|
||||
/// Split a filled path into connected pieces (each outer contour with its holes).
|
||||
pub(in super::super) fn components(pd: &PathData) -> Vec<PathData> {
|
||||
|
||||
@@ -2,7 +2,10 @@ use super::common::{
|
||||
brush_region, path_kind, path_mut, path_of, points_param, poly_bounds, samples_param, stroke_paint, stroke_pressure, subpath_len, target_paths,
|
||||
within_input_bound,
|
||||
};
|
||||
use super::*;
|
||||
use super::{
|
||||
Anchor, Appearance, BTreeSet, BoolOp, Color, EngineError, FillRule, NewArt, NodeId, NodeKind, Paint, PathData, Point, PressureProfile, Result,
|
||||
Selection, Session, SimplifyOptions, SubPath, Value, bad, id_param, insert_look, json, number_or, po, remove_each, str_param, switch_or,
|
||||
};
|
||||
|
||||
// ---------- Pencil / Paintbrush ----------
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use super::common::{dist_to_polyline, ids_json, path_mut, path_of, points_param, poly_bounds, target_paths};
|
||||
use super::*;
|
||||
use super::{
|
||||
Anchor, BezPath, CubicBez, NodeId, ParamCurve, ParamCurveNearest, PathData, Point, Result, Session, SimplifyOptions, SubPath, Value, json,
|
||||
number_or, po,
|
||||
};
|
||||
|
||||
// ---------- Smooth ----------
|
||||
|
||||
|
||||
@@ -8,7 +8,11 @@ use vectorcraft_doc::{Document, Node, NodeId, RulerMode, Unit};
|
||||
use vectorcraft_geom::{Affine, Point, Vec2};
|
||||
|
||||
use super::clipboard::SwatchChoices;
|
||||
use super::*;
|
||||
use super::{
|
||||
CommandSpec, Result, Session, always, bad, bool_or, can_clear_history, can_redo, can_undo, checked_ids_param, cmd, f64_or, f64_req,
|
||||
finite_numbers, has_clipboard, has_doc, has_selection, has_selection_or_artboard_tool, has_selection_or_guides, id_param, index, ok, point_param,
|
||||
str_param,
|
||||
};
|
||||
use crate::{Clipboard, EngineError, inspect};
|
||||
|
||||
/// Register editing commands, including document-to-document artwork drops and their parameter contracts.
|
||||
|
||||
@@ -15,7 +15,7 @@ use vectorcraft_render::effects;
|
||||
use super::appearance::{ItemTarget, aligned, appearance_targets, index_param, item_target, item_target_at, shared_stack};
|
||||
use super::expand::has_seeded;
|
||||
use super::rasterfx;
|
||||
use super::*;
|
||||
use super::{CommandSpec, EngineError, NodeId, Result, Session, always, bad, cmd, fileio, has_doc, str_param};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
|
||||
@@ -22,7 +22,7 @@ use super::edit::selected_roots;
|
||||
use super::menucmds::{run_raw, squash};
|
||||
use super::object::as_clipping_path;
|
||||
use super::pathops::{assemble, node_path, outline_strokes, replace_leaves, styled_copy, with_transparency};
|
||||
use super::*;
|
||||
use super::{CommandSpec, EngineError, Result, Session, bad, bool_or, cmd, f64_or, has_selection, str_param};
|
||||
|
||||
const C: &str = "object.expand";
|
||||
/// Expand Gradient To → Specify … Objects: the default and the most.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user