mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
CI: one test binary per crate, nextest, line tables only (#3855)
* CI: line tables only for workspace crates, no debug info for dependencies * CI: one integration-test binary per crate Each crate's tests/*.rs files become modules of tests/it/main.rs. Files that change process-wide state (Color Settings, the font database, installed plug-ins, the working directory), the fuzz sweeps and the test targets the Affinity corpus workflow names stay their own binaries. * Docs: name the integration tests' new paths * CI: xtask test with nextest and doctests, one wasm check, clippy --keep-going cargo xtask ci runs the tests with cargo nextest (cargo test when it is not installed), builds them with --lib --bins --tests, runs the doctests as their own step, and checks the wasm crates in one cargo check --keep-going call.
This commit is contained in:
@@ -124,11 +124,15 @@ todo = "deny"
|
||||
unimplemented = "deny"
|
||||
unreachable = "deny"
|
||||
|
||||
# Workspace crates keep line tables, so panics and backtraces name file and line; dependencies
|
||||
# carry no debug info. Full debug info would make the dev target dir several times larger.
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 2
|
||||
debug = false
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
debug = "line-tables-only"
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
//! The CLI opens every readable format (PDF, .ai, images) through the engine's `document.open`.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
//! Integration tests: one test binary for the crate, one module per area.
|
||||
// Unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
mod cli;
|
||||
mod fileio;
|
||||
mod mcp_e2e;
|
||||
@@ -1,6 +1,4 @@
|
||||
//! End-to-end MCP over stdio against the built `vectorcraft-cli mcp --headless` binary.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio};
|
||||
-3
@@ -1,6 +1,3 @@
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::process::{Command, Stdio};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
//! `vectorcraft --help` prints the usage and exits before the app starts (#2110): run from a
|
||||
//! terminal, SSH or an agent's shell it used to open the window (or hang without a display).
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
//! Integration tests: one test binary for the crate, one module per area.
|
||||
// Unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
mod automation_args;
|
||||
mod help;
|
||||
@@ -92,10 +92,10 @@ text-frame fixture.
|
||||
|
||||
## Validation
|
||||
|
||||
* `engine/tests/affinity_features.rs` covers pages, several artboards per canvas and per spread,
|
||||
* `engine/tests/it/affinity_features.rs` covers pages, several artboards per canvas and per spread,
|
||||
master pages, tables, paragraph formats, text scale, layer effects, overlays and embedded
|
||||
documents (whose artboards don't become artboards) on synthetic documents, and mutates them.
|
||||
`engine/tests/affinity_local.rs` compares every artboard of local documents with their thumbnail
|
||||
`engine/tests/it/affinity_local.rs` compares every artboard of local documents with their thumbnail
|
||||
when `AFFINITY_SAMPLES` points at a folder (never in CI; nothing is committed).
|
||||
* Unit tests on synthetic containers built by the `synth` feature (stored, zlib and zstd entries,
|
||||
checksums, budgets, cycles, hostile streams, every truncation) and property tests of random
|
||||
@@ -106,7 +106,7 @@ text-frame fixture.
|
||||
feature probes plus one separately identified derived regression input from
|
||||
[SethRobinson/Patchy](https://github.com/SethRobinson/Patchy). Only document data and provenance
|
||||
notices were inspected, never that project's importer or authoring scripts.
|
||||
* `tests/real_files.rs` parses every file and freezes the modeled features of all 33 current
|
||||
* `tests/it/real_files.rs` parses every file and freezes the modeled features of all 33 current
|
||||
`.af` files. Native raster tests compare decoded pixels with independent embedded images;
|
||||
text tests assert Unicode run boundaries, fallback fonts and feature retention.
|
||||
* `engine/tests/affinity_corpus.rs` requires editable native import, exact `.vectorcraft`
|
||||
@@ -123,7 +123,7 @@ text-frame fixture.
|
||||
documents with layer effects, colour overlays (also over tables), line styles, paragraph and
|
||||
character formats, tables, master pages, CMYK pixel layers and embedded documents (the embedding
|
||||
and the embedded stream), and checks that whatever opens also renders and exports.
|
||||
`tests/import_limits.rs` builds hostile documents against the limits below: grids of 16 million
|
||||
`tests/it/import_limits.rs` builds hostile documents against the limits below: grids of 16 million
|
||||
cells, grids and stories shared by many layers, 200 000 paragraphs, a document embedded hundreds
|
||||
of times. `fuzz/` has `cargo-fuzz` targets for the whole reader (`container`) and the object
|
||||
stream (`stream`):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Unit tests on synthetic inputs; `tests/real_files.rs` covers public Affinity documents.
|
||||
//! Unit tests on synthetic inputs; `tests/it/real_files.rs` covers public Affinity documents.
|
||||
|
||||
mod preview {
|
||||
use crate::container::header;
|
||||
|
||||
@@ -2,14 +2,8 @@
|
||||
//! stories, paragraphs and embedded documents stay within the whole read's budget, however often
|
||||
//! a file links or embeds the same content. Original synthetic documents in the reader's
|
||||
//! accepted schema; no Affinity software or files were used.
|
||||
#![cfg(feature = "synth")]
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
mod support {
|
||||
pub mod text_fixture;
|
||||
}
|
||||
|
||||
use support::text_fixture::*;
|
||||
use crate::text_fixture::*;
|
||||
use vectorcraft_affinity::{
|
||||
Document, Error, Limits,
|
||||
model::{Kind, Node},
|
||||
@@ -0,0 +1,20 @@
|
||||
//! Integration tests: one test binary for the crate, one module per area.
|
||||
// Unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
#[path = "../support/corpus.rs"]
|
||||
mod corpus;
|
||||
#[path = "../support/features.rs"]
|
||||
mod features;
|
||||
#[cfg(feature = "synth")]
|
||||
#[path = "../support/text_fixture.rs"]
|
||||
mod text_fixture;
|
||||
|
||||
#[cfg(feature = "synth")]
|
||||
mod import_limits;
|
||||
#[cfg(feature = "synth")]
|
||||
mod outline_limits;
|
||||
mod raster;
|
||||
mod real_files;
|
||||
#[cfg(feature = "synth")]
|
||||
mod text;
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Original synthetic graph tests for bounded `.af` outline expansion. These model the reader's
|
||||
//! accepted schema, without using Affinity software, third-party parser code or external assets.
|
||||
#![cfg(feature = "synth")]
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_affinity::{
|
||||
Document, Limits,
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Pixel retention on current Affinity 3 documents published under MIT by Patchy.
|
||||
//! Assertions use this reader's archive/stream and the original embedded image bytes;
|
||||
//! no third-party Affinity parser, source code or application is used as an oracle.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_affinity::{
|
||||
Archive, Limits,
|
||||
@@ -9,8 +8,7 @@ use vectorcraft_affinity::{
|
||||
stream,
|
||||
};
|
||||
|
||||
#[path = "support/corpus.rs"]
|
||||
mod corpus;
|
||||
use crate::corpus;
|
||||
|
||||
fn images(nodes: &[Node], out: &mut Vec<Image>) {
|
||||
for node in nodes {
|
||||
@@ -2,16 +2,13 @@
|
||||
//! `corpus/affinity`, or any folder of `.af`/`.afdesign`/`.afphoto`/`.afpub` files named by
|
||||
//! `AFFINITY_SAMPLES` (searched recursively). CI requires a complete, checksum-verified pinned
|
||||
//! corpus via `AFFINITY_CORPUS_REQUIRED=1`; ordinary local runs may skip an absent corpus.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use vectorcraft_affinity::{Archive, Limits, stream};
|
||||
|
||||
#[path = "support/corpus.rs"]
|
||||
mod corpus;
|
||||
#[path = "support/features.rs"]
|
||||
mod features;
|
||||
use crate::corpus;
|
||||
use crate::features;
|
||||
|
||||
fn files(dir: &Path, out: &mut Vec<PathBuf>) {
|
||||
for e in std::fs::read_dir(dir).into_iter().flatten().flatten() {
|
||||
@@ -90,7 +87,7 @@ fn public_documents_map_to_the_model() {
|
||||
#[test]
|
||||
fn affinity3_feature_retention_matches_pinned_signatures() {
|
||||
let Some(dir) = corpus::pinned() else { return };
|
||||
let expected = include_str!("support/affinity3-signatures.txt");
|
||||
let expected = include_str!("../support/affinity3-signatures.txt");
|
||||
let mut checked = std::collections::BTreeSet::new();
|
||||
for line in expected.lines().filter(|l| !l.starts_with('#') && !l.is_empty()) {
|
||||
let (name, signature) = line.split_once('\t').expect("signature row must have a tab");
|
||||
@@ -108,7 +105,7 @@ fn affinity3_feature_retention_matches_pinned_signatures() {
|
||||
doc.saved_by
|
||||
);
|
||||
}
|
||||
let manifest = include_str!("../../../xtask/affinity-corpus.sha256");
|
||||
let manifest = include_str!("../../../../xtask/affinity-corpus.sha256");
|
||||
let af_names: std::collections::BTreeSet<_> =
|
||||
manifest.lines().filter_map(|l| l.split_once(" ")).map(|(_, n)| n).filter(|n| n.ends_with(".af")).collect();
|
||||
assert_eq!(checked, af_names, "every pinned .af file needs a reviewed structural baseline");
|
||||
@@ -1,14 +1,8 @@
|
||||
//! Synthetic `.af` version-12 text regression oracles; see support/text_fixture.rs for provenance.
|
||||
//! Unicode ranges below test the scalar-index contract corroborated by the pinned Affinity 3
|
||||
//! mixed-run fixture. Broad complex typography still needs independent visual validation.
|
||||
#![cfg(feature = "synth")]
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
mod support {
|
||||
pub mod corpus;
|
||||
pub mod text_fixture;
|
||||
}
|
||||
use support::text_fixture::*;
|
||||
use crate::text_fixture::*;
|
||||
use vectorcraft_affinity::{
|
||||
Document, Limits,
|
||||
model::{Align, Kind, Paragraph, Text},
|
||||
@@ -179,7 +173,7 @@ fn stored_fallback_font_is_used_only_when_it_names_a_font() {
|
||||
|
||||
#[test]
|
||||
fn real_affinity3_text_fixtures_confirm_unicode_and_caps_fields() {
|
||||
let Some(corpus) = support::corpus::pinned() else { return };
|
||||
let Some(corpus) = crate::corpus::pinned() else { return };
|
||||
// Files fetched at the commit and sha256 in xtask/affinity-corpus.sha256. No Patchy parser
|
||||
// source was read; assertions describe this crate's parsing of its published MIT documents.
|
||||
let load = |name: &str| vectorcraft_affinity::read(&std::fs::read(corpus.join(name)).unwrap(), Limits::default()).unwrap();
|
||||
+2
-5
@@ -2,12 +2,9 @@
|
||||
//! layer effects and embedded documents, on original synthetic documents built with the reader's
|
||||
//! `synth` feature (no Affinity software or files were used). The fixtures follow DesignCraft's
|
||||
//! Affinity import tests, whose reader VectorCraft shares.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
#[path = "support/affinity_docs.rs"]
|
||||
mod affinity_docs;
|
||||
#[path = "../../affinity/tests/support/text_fixture.rs"]
|
||||
mod text_fixture;
|
||||
use crate::affinity_docs;
|
||||
use crate::text_fixture;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
//! stored in the file. Reports the mean difference per artboard (0–255, both on white); with
|
||||
//! `AFFINITY_SAMPLES_OUT` set, writes each board's thumbnail crop and render side by side there.
|
||||
//! Skipped when `AFFINITY_SAMPLES` is unset (as in CI).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Artboard clicks fit the object without changing its art or selection, and are one undo step.
|
||||
//! The second click of a double-click on an object does not fit another artboard, and that
|
||||
//! double-click does not open Artboard Options. Empty artboard space still does.
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_doc::NodeId;
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Clear deletes its exact targets, including compound members (#786).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_doc::NodeId;
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//! Compound appearance and fill-rule edits from a directly selected member (#1197).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::{NodeId, NodeKind};
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Linked measurements, history, portable artwork and hostile command input.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_engine::{
|
||||
Session, ViewInfo,
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//! Visible art is readable by the Eyedropper without making it editable (#1251).
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_doc::NodeId;
|
||||
-2
@@ -1,7 +1,5 @@
|
||||
//! `edit.findReplace` keeps per-paragraph styles lined up with the text when a find or replace
|
||||
//! string holds a line break, as `text.editRange` does.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_doc::{Justify, NodeId, NodeKind};
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Inline graphics in text drawn on the canvas and written to SVG and PDF: a symbol (a red
|
||||
//! circle drawn in code) set inline in a line of type.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::{Document, NodeId, NodeKind, TextObject};
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Minimal reproductions of engine bugs found by the property tests. Each is `#[ignore]`d with a
|
||||
//! `BUG:` reason until fixed; remove the attribute when the fix lands.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_engine::Session;
|
||||
@@ -1,8 +1,6 @@
|
||||
//! A file whose import left something out isn't written over silently: its hidden text, art or
|
||||
//! layers would be gone for good.
|
||||
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_engine::Session;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
//! Integration tests: one test binary for the crate, one module per area.
|
||||
// Unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
#[path = "../support/affinity_docs.rs"]
|
||||
mod affinity_docs;
|
||||
#[path = "../../../affinity/tests/support/text_fixture.rs"]
|
||||
mod text_fixture;
|
||||
|
||||
mod affinity_features;
|
||||
mod affinity_local;
|
||||
mod artboard_click;
|
||||
mod clear_commands;
|
||||
mod compound_attributes;
|
||||
mod dimensions;
|
||||
mod eyedropper_sources;
|
||||
mod find_replace_paras;
|
||||
mod inline_text;
|
||||
mod known_bugs;
|
||||
mod lossy_overwrite;
|
||||
mod model_based;
|
||||
mod param_keys;
|
||||
mod pen_click_after_smooth;
|
||||
mod pen_endpoint_picking;
|
||||
mod ruler_origin;
|
||||
mod selection_commands;
|
||||
mod stroke_visibility;
|
||||
mod text_stroke_scale;
|
||||
mod three_d_light_color;
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Model-based / stateful property tests: random command sequences drawn from the registry, with
|
||||
//! structural invariants checked after every step and history/serialization invariants at the end.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use proptest::prelude::*;
|
||||
use serde_json::json;
|
||||
@@ -1,6 +1,5 @@
|
||||
//! A caller's params are checked against the command's description, but a journal replays as it
|
||||
//! was recorded (#1442).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_engine::{Session, check_params};
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//! A click after a smooth anchor: the new corner has no handles, so the segment curves from the previous anchor's side only.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_doc::NodeKind;
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//! Original tiny-geometry controls for Pen endpoint/Alt integration with continue-any-path.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_doc::{Node, NodeId, NodeKind};
|
||||
@@ -1,5 +1,4 @@
|
||||
//! #1212: ruler coordinates are saved metadata, independent of the artwork's coordinates.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::RulerMode;
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//! Selection command results and validation for agent callers (#784).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::NodeId;
|
||||
@@ -1,5 +1,4 @@
|
||||
//! Painting a previously unpainted zero-width stroke restores the existing 1 pt default.
|
||||
#![allow(clippy::unwrap_used)]
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_doc::NodeId;
|
||||
@@ -1,6 +1,5 @@
|
||||
//! A stroke weight set on type is a document-space weight, whether the type was enlarged by its
|
||||
//! font size or by a transform (issue #3241).
|
||||
#![allow(clippy::unwrap_used, clippy::panic)]
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_doc::{NodeId, NodeKind};
|
||||
-1
@@ -1,5 +1,4 @@
|
||||
//! Colored 3D lighting remains a live command and survives native and vector output.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::{Document, Node, NodeId};
|
||||
use vectorcraft_engine::Session;
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Linked images in `.vectorcraft` files: their link details round-trip, and an image only linked
|
||||
//! images show is saved as its preview.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
//! Integration tests: one test binary for the crate, one module per area.
|
||||
// Unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
mod links;
|
||||
mod native_v3;
|
||||
mod native_v4;
|
||||
mod save_options;
|
||||
mod streaming;
|
||||
@@ -1,7 +1,5 @@
|
||||
//! `.vectorcraft` v3: compression, pruning unused images, keys from newer versions, colour
|
||||
//! profiles as a document field, saving for older versions and the embedded preview.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::io::Write as _;
|
||||
use std::sync::Arc;
|
||||
@@ -1,7 +1,5 @@
|
||||
//! `.vectorcraft` v4: large data after the JSON (images, the PDF, profiles), small files still plain
|
||||
//! JSON, older versions inline, damaged offsets refused.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
//! The native save options of the file itself: Include Linked Files, embedded ICC profiles and the
|
||||
//! PDF a PDF-compatible file carries, each round-tripping (and ignored by readers that don't
|
||||
//! need them).
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
//! before (the same JSON and data after it, unpacked; the same gzip header), files written the old
|
||||
//! way still open, entries are read whatever their order, and the limits apply per part of the
|
||||
//! file.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::{Read, Write};
|
||||
@@ -0,0 +1,12 @@
|
||||
//! Integration tests: one test binary for the crate, one module per area.
|
||||
// Unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
#[path = "../common/mod.rs"]
|
||||
mod common;
|
||||
|
||||
mod ops;
|
||||
mod pathfinder_pinned;
|
||||
mod prop_pathops;
|
||||
mod props;
|
||||
mod regressions;
|
||||
@@ -1,5 +1,3 @@
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use kurbo::{ParamCurve, Point, Rect, Shape as _};
|
||||
-1
@@ -1,7 +1,6 @@
|
||||
//! The exact output of every Pathfinder operation, pinned so that work on the windings and the
|
||||
//! coverage tests can't change it: overlapping ellipses, rectangles and stars (even-odd and
|
||||
//! non-zero, repeated keys for Merge) at a few counts.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_geom::{FillRule, PathData, Point, Rect, SubPath, shapes};
|
||||
use vectorcraft_pathops::{PathfinderOp, Shape, pathfinder};
|
||||
@@ -1,9 +1,7 @@
|
||||
//! Path-operation properties on concave polygons, ellipses and random smooth curves (complements
|
||||
//! `props.rs`, which uses rectangles and circles).
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
mod common;
|
||||
use crate::common;
|
||||
|
||||
use proptest::prelude::*;
|
||||
use vectorcraft_geom::{FillRule, PathData, Point, Rect};
|
||||
@@ -1,6 +1,4 @@
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
mod common;
|
||||
use crate::common;
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Pixel-sample "golden" tests on small fixtures: gradients, blend modes, clip groups, stroke
|
||||
//! alignment/dashes/arrowheads, effects, text, and opacity folding vs layer compositing.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_color::{BlendMode, Color, Gradient, GradientKind, GradientPaint, GradientStop, Paint};
|
||||
@@ -1,6 +1,4 @@
|
||||
//! Gradients on type: a vector across the text, and run strokes fitted like object strokes.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_color::Paint;
|
||||
@@ -0,0 +1,10 @@
|
||||
//! Integration tests: one test binary for the crate, one module per area.
|
||||
// Unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
mod dash_align;
|
||||
mod golden;
|
||||
mod gradient_alpha;
|
||||
mod gradient_text;
|
||||
mod radial_focal;
|
||||
mod stroke_geometry;
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Pixel tests for the shared stroke geometry (`vectorcraft_effects::stroke`) on the canvas:
|
||||
//! dotted lines (zero-length dashes) and arrowheads (hollow kinds, alignment, compositing).
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_color::{Color, Gradient, GradientKind, GradientPaint, GradientStop, Paint};
|
||||
use vectorcraft_doc::{Appearance, AppearanceItem, ArrowAlign, Arrowhead, Dash, Document, LineCap, StrokeLayer};
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Blend modes and opacity masks in SVG export look as on the canvas: every mode follows the
|
||||
//! reference formulas when resvg draws the file, and masks take luminance the canvas's way.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_color::blend::blend_rgb;
|
||||
use vectorcraft_color::{BlendMode, Color, Paint};
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Colour bitmap glyphs (emoji): outlined text writes their images; live text leaves them to the
|
||||
//! viewer, and embedded fonts don't carry them.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use skrifa::raw::TableProvider;
|
||||
use vectorcraft_doc::{CharStyle, Document, Node, NodeKind, TextObject};
|
||||
@@ -1,8 +1,6 @@
|
||||
//! The appearance of groups and layers in SVG export: their own fills and strokes are baked into
|
||||
//! paths painting the members (below or above them as the contents slot says), their geometry
|
||||
//! effects into the members, and their raster effects filter the whole group.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
//! CSS for objects (the CSS Properties panel): shapes give backgrounds, borders and corner radii,
|
||||
//! type its font, gradients `linear-gradient()`/`radial-gradient()`, and every property both write
|
||||
//! reads as SVG export writes it.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_color::{BlendMode, Color, Gradient, GradientGeom, GradientKind, GradientPaint, Paint};
|
||||
use vectorcraft_doc::{Appearance, CharStyle, Document, Effect, Justify, LiveShape, Node, NodeId, NodeKind, TextObject};
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Dashes fitted to corners export as the canvas's filled outlines (a dash array can't fit them);
|
||||
//! exact dashes stay a `stroke-dasharray`. Checked by rendering the exported SVG with resvg.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
use vectorcraft_doc::{Appearance, AppearanceItem, Dash, Document, Node, StrokeLayer};
|
||||
@@ -1,6 +1,5 @@
|
||||
//! Synthetic XML 1.0/SVG round trips: https://www.w3.org/TR/xml/#AVNormalize
|
||||
//! https://www.w3.org/TR/xml/#NT-NameChar https://www.w3.org/TR/xml/#charsets
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
use std::collections::HashSet;
|
||||
use usvg::roxmltree;
|
||||
use vectorcraft_doc::{Document, Node};
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Live effects in SVG export: a fill or stroke's own raster effects become filters on that item's
|
||||
//! element, and geometry effects on type are baked through its outlines.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use serde_json::json;
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
+2
-4
@@ -1,8 +1,6 @@
|
||||
//! SVG Options → Encoding, SVG Profile and embedded fonts: UTF-16 and ISO 8859-1 files read
|
||||
//! back, entity styling parses in usvg, SVG Tiny 1.2 has no filters, masks, symbols or style
|
||||
//! sheets, and embedded fonts hold only the glyphs used where their licence allows it.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -287,7 +285,7 @@ fn variable_font_settings_are_written_embedded_and_read_back() {
|
||||
/// The bundled Source Sans 3 Regular renamed `family` (13 characters, as long as the original
|
||||
/// name) with OS/2 `fsType` set to `fs_type`.
|
||||
fn licensed_font(family: &str, fs_type: u16) -> Vec<u8> {
|
||||
let mut data = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf").to_vec();
|
||||
let mut data = include_bytes!("../../../../assets/fonts/SourceSans3-Regular.ttf").to_vec();
|
||||
let table = |data: &[u8], tag: &[u8; 4]| {
|
||||
let f = skrifa::FontRef::new(data).unwrap();
|
||||
let r = f.table_directory.table_records().iter().find(|r| r.tag().to_be_bytes() == *tag).unwrap();
|
||||
@@ -320,7 +318,7 @@ fn font_licences_are_respected() {
|
||||
let fonts = embedded(&o.svg);
|
||||
assert_eq!(fonts.len(), 1, "{:?}", o.warnings);
|
||||
assert_eq!(fonts[0].0, "\"Wholed Sans 3\"");
|
||||
let size = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf").len();
|
||||
let size = include_bytes!("../../../../assets/fonts/SourceSans3-Regular.ttf").len();
|
||||
assert!(fonts[0].2.len() + 64 >= size, "embedded whole: {} of {size} bytes", fonts[0].2.len());
|
||||
assert!(o.warnings.iter().any(|w| w.contains("Sealed Sans 3") && w.contains("doesn't allow embedding")), "{:?}", o.warnings);
|
||||
assert!(o.warnings.iter().any(|w| w.contains("Wholed Sans 3") && w.contains("whole")), "{:?}", o.warnings);
|
||||
@@ -1,6 +1,4 @@
|
||||
//! Radial gradients' focal points as `fx`/`fy`, out and back in.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_color::{Gradient, GradientGeom, GradientKind, GradientPaint, Paint};
|
||||
use vectorcraft_doc::{Appearance, Document, Node};
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Font weights in SVG and CSS: every style is written with its numeric weight (Semibold 600,
|
||||
//! Light 300, Black 900…; Bold as `bold`), and SVG import reads the numbers back to the same face.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_doc::{CharStyle, Document, Node, NodeKind, TextObject};
|
||||
use vectorcraft_geom::Point;
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Attributes in other applications' XML namespaces (https://www.w3.org/TR/xml-names/) survive a
|
||||
//! round trip through VectorCraft: kept on import, written back on export with their namespaces
|
||||
//! declared on the root.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
use usvg::roxmltree;
|
||||
use vectorcraft_doc::{Document, ForeignAttribute, Node, NodeKind};
|
||||
use vectorcraft_svg::{ExportOptions, MAX_FOREIGN, MAX_FOREIGN_VALUE, export, export_full, import, import_with_report};
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Gradient defs: text gradients span the laid-out text, stroke gradients the stroked area,
|
||||
//! identical gradients share a def named after their swatch, and midpoints survive re-import.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_color::{Color, Gradient, GradientKind, GradientPaint, GradientStop, Paint};
|
||||
use vectorcraft_doc::{Appearance, CharStyle, Document, Node, NodeKind, TextObject};
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Object IDs: names survive the round trip (Layer Names), Minimal writes only referenced ids,
|
||||
//! Unique ids never collide, and other apps' label attributes become object names.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
//! SVG import details: linked images read from the SVG's folder and kept linked (a placeholder
|
||||
//! and a warning when missing), SVG images as vector art, nested clips, undisplayed objects as
|
||||
//! hidden objects, and reflected or repeated gradients.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
//! SVG import keeps what the canvas edits live: `<pattern>` becomes a pattern swatch, `<symbol>`
|
||||
//! with `<use>` a symbol and its instances, blurs and drop-shadow chains live effects; and
|
||||
//! export → import keeps them.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
//! SVG `<text>` import fidelity: placement in the tree, type on a path, per-character positions,
|
||||
//! paints and the style cascade.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_color::{GradientKind, Paint};
|
||||
use vectorcraft_doc::{CharStyle, Document, Justify, Node, NodeKind, TextKind, TextObject};
|
||||
@@ -1,7 +1,5 @@
|
||||
//! SVG import units: physical lengths keep their printed size, pixels and user units map 1:1 to
|
||||
//! points, and the document's units follow the file's.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_doc::{Document, Node, NodeKind, Unit};
|
||||
use vectorcraft_geom::Rect;
|
||||
@@ -1,7 +1,6 @@
|
||||
//! SVG Interactivity: event attributes (https://www.w3.org/TR/SVG11/script.html#EventAttributes)
|
||||
//! and `<script xlink:href>` links (https://www.w3.org/TR/SVG11/script.html#ScriptElement), written
|
||||
//! by export and read back by import as text that nothing runs.
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
//! Integration tests: one test binary for the crate, one module per area.
|
||||
// Unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
mod blend_export;
|
||||
mod colour_glyphs;
|
||||
mod container_export;
|
||||
mod css_rules;
|
||||
mod dash_align;
|
||||
mod data_attributes;
|
||||
mod effects_export;
|
||||
mod encoding_profile_fonts;
|
||||
mod focal;
|
||||
mod font_weights;
|
||||
mod foreign_attributes;
|
||||
mod gradients;
|
||||
mod ids;
|
||||
mod import_details;
|
||||
mod import_live;
|
||||
mod import_text;
|
||||
mod import_units;
|
||||
mod interactivity;
|
||||
mod mask_options;
|
||||
mod non_scaling_stroke;
|
||||
mod options;
|
||||
mod prop_svg;
|
||||
mod raster_paints;
|
||||
mod roundtrip;
|
||||
mod stroke_export;
|
||||
mod svg_glyphs;
|
||||
mod symbols_layers;
|
||||
mod text_layout;
|
||||
mod text_paint_order;
|
||||
mod text_stroke;
|
||||
mod title_control_chars;
|
||||
@@ -1,8 +1,6 @@
|
||||
//! Opacity-mask options survive SVG (M3.87): exported masks list the options that differ from the
|
||||
//! defaults in `data-vectorcraft-mask`, and import restores them and the original mask art (without
|
||||
//! the backdrop rectangle and inverting filter that draw the options in SVG).
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
use vectorcraft_doc::{Appearance, Document, Node, OpacityMask};
|
||||
@@ -1,7 +1,5 @@
|
||||
//! `vector-effect="non-scaling-stroke"` on import: the stroke keeps its screen width and dashes
|
||||
//! whatever the transforms above it, on shapes and type; a warning where it can't be kept.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_doc::{Document, Node, NodeKind, TextObject};
|
||||
use vectorcraft_svg::import_with_report;
|
||||
@@ -1,7 +1,5 @@
|
||||
//! SVG Options across each other: every styling, id mode and tspan mode reads back, and linked
|
||||
//! images are named after their bytes (and are formats browsers show).
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
use vectorcraft_doc::{Appearance, AppearanceItem, CharStyle, Document, ImageBlob, ImageObject, Node, NodeKind, TextKind, TextObject, TextRun};
|
||||
@@ -1,7 +1,5 @@
|
||||
//! SVG export/import properties: never panics, always re-imports, and plain art survives the round
|
||||
//! trip visually (render comparison with a perceptual tolerance).
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use proptest::prelude::*;
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
@@ -1,8 +1,6 @@
|
||||
//! Paints SVG can't express: freeform gradients fill and stroke objects as an `<image>` of their
|
||||
//! colour field clipped to the shape (or the stroke's outline), and the SVG reads back looking as
|
||||
//! the canvas paints it.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_color::freeform::spread_scale;
|
||||
use vectorcraft_color::{Color, Freeform, FreeformPoint, Gradient, GradientKind, GradientPaint, Paint};
|
||||
@@ -1,5 +1,3 @@
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
use std::sync::Arc;
|
||||
|
||||
use vectorcraft_color::{BlendMode, Color, Gradient, GradientGeom, GradientKind, GradientPaint, Paint};
|
||||
@@ -1,8 +1,6 @@
|
||||
//! Strokes in SVG export look as on the canvas: arrowheads, width profiles, dots and brushes are
|
||||
//! written as the canvas's filled outlines or art, aligned strokes are clipped or masked, and open
|
||||
//! paths stroke centred. Checked by rendering the exported SVG with resvg.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
use vectorcraft_doc::{Appearance, AppearanceItem, Arrowhead, Dash, Document, LineCap, LineJoin, Node, StrokeAlign, StrokeLayer, WidthProfile};
|
||||
@@ -1,7 +1,5 @@
|
||||
//! OpenType SVG glyphs: `glyph_art` makes a glyph's art of its SVG document, as the importer opens
|
||||
//! SVG files, in font units.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use proptest::prelude::*;
|
||||
use vectorcraft_doc::color::{Color, Paint};
|
||||
@@ -3,8 +3,6 @@
|
||||
//! hidden with their `data-*` attributes, clipped layers come in as clipping layers, layers and
|
||||
//! sublayers keep their names and nesting (a one-layer file's from its root element), and the
|
||||
//! editing data notices edits made elsewhere.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
//! Laid-out type in SVG: every line of area type is its own positioned `<tspan>`, rich text keeps
|
||||
//! its leading, baseline shift, scaling and kerning, and a renderer with the same font draws the
|
||||
//! export where the canvas draws the text.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
//! `paint-order="stroke"` on imported type: the characters' stroke becomes the object's own stroke
|
||||
//! below the Characters row, so it paints under the letters as on shapes.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
use vectorcraft_doc::{AppearanceItem, Document, Node, NodeKind, StrokeLayer, TextObject};
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Strokes on type in SVG: character stroke options on live text (and back), runs that differ
|
||||
//! from the first, and the object's own strokes drawn over the glyph outlines.
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
use vectorcraft_doc::{AppearanceItem, CharStyle, Dash, Document, LineCap, LineJoin, Node, NodeKind, StrokeLayer, TextObject, TextRun};
|
||||
@@ -1,5 +1,3 @@
|
||||
// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness).
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
use vectorcraft_doc::Document;
|
||||
use vectorcraft_svg::{ExportOptions, export, import};
|
||||
|
||||
@@ -273,7 +273,7 @@ impl Op {
|
||||
pub fn command(&self, s: &Session) -> (String, Value) {
|
||||
let doc = s.active().map(|d| d.doc.clone());
|
||||
// Children of compound paths are excluded: grouping one of them nests a group inside the
|
||||
// compound (known bug, see crates/engine/tests/known_bugs.rs).
|
||||
// compound (known bug, see crates/engine/tests/it/known_bugs.rs).
|
||||
let art: Vec<_> = doc
|
||||
.as_ref()
|
||||
.map(|d| {
|
||||
@@ -313,7 +313,7 @@ impl Op {
|
||||
Op::Ungroup => c("object.ungroup", json!({})),
|
||||
Op::Arrange(i) => {
|
||||
let i = *i as usize % ARRANGE.len();
|
||||
// Known bug (crates/engine/tests/known_bugs.rs): Bring to Front/Forward panics when
|
||||
// Known bug (crates/engine/tests/it/known_bugs.rs): Bring to Front/Forward panics when
|
||||
// the selection spans several parents. Avoid generating that case.
|
||||
let multi_parent = doc.as_ref().zip(s.active()).is_some_and(|(d, st)| {
|
||||
let mut parents: Vec<_> = st.selection.objects.iter().map(|id| d.parent_of(*id)).collect();
|
||||
|
||||
+29
-1
@@ -1,5 +1,6 @@
|
||||
# Development
|
||||
|
||||
> **Last updated:** 2026-10-23 · **Change:** Tests: cargo xtask test, one integration-test binary per crate, the dev profile's debug info.
|
||||
> **Last updated:** 2026-10-21 · **Change:** the interface faces as vectorcraft-ui-egui's `interface-fonts` feature, and `theme::install_fonts` for an embedder that draws the panels itself.
|
||||
> **Last updated:** 2026-10-21 · **Change:** leaving out the bundled fonts with the egui panels (vectorcraft-ui-egui's and vectorcraft-mcp's `bundled-fonts` features).
|
||||
> **Last updated:** 2026-10-14 · **Change:** leaving out the bundled fonts (`bundled-fonts` feature).
|
||||
@@ -297,6 +298,31 @@ other `sv-*` locale resolve to it), Georgian (`ka`, complete; `ka-GE` and `ka_GE
|
||||
|
||||
`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, test files and `#[cfg(test)]` modules 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, or a name another project defines, such as a shared MIME type, carries a `brand-ok` comment. In the Linux desktop entry, whose comments take whole lines, the comment goes on the `#` line just above it; `MARK_ABOVE` in `xtask/src/brands.rs` lists the files that accept a mark there.
|
||||
|
||||
## Tests
|
||||
|
||||
- `cargo xtask test` runs the workspace's unit and integration tests with
|
||||
[cargo-nextest](https://nexte.st) when it is installed (`cargo install cargo-nextest --locked`),
|
||||
otherwise with `cargo test`, and then the doctests (`cargo test --workspace --doc`), which nextest
|
||||
doesn't run. `cargo xtask ci` runs the same two as its `test` and `doctests` steps.
|
||||
- The test build leaves out the examples (`--lib --bins --tests`); clippy (`--all-targets`) still
|
||||
checks them.
|
||||
- Each crate has one integration-test binary: its test files are modules of `tests/it/main.rs`. Run
|
||||
one file's tests with `cargo nextest run -p vectorcraft-svg --test it roundtrip::` (or `cargo test`
|
||||
with the same arguments). A new test file gets a `mod` line in `main.rs`.
|
||||
- A test file stays a binary of its own (`tests/<name>.rs`) when it changes process-wide state that
|
||||
other tests would see, because `cargo test` runs a binary's tests as threads of one process (nextest
|
||||
runs each test in its own process). In `crates/engine/tests`: the Color Settings
|
||||
(`color_settings.rs`), the font database and Fonts folder (`removed_font.rs`, `font_folder.rs`,
|
||||
`text_import_fuzz.rs`), installed plug-ins (`plugin_export.rs`, `plugin_fuzz.rs`), the working
|
||||
directory (`open_relative_path.rs`) and every command run with junk parameters
|
||||
(`command_sweep.rs`); the `system_fonts` tests of `vectorcraft-text` and `vectorcraft-pdf` too. The
|
||||
fuzz files under Robustness › Fuzzing keep their own binaries, so a deeper search runs alone, and so
|
||||
do `affinity_artboards`, `affinity_text` and `affinity_corpus`, which the Affinity corpus workflow
|
||||
names.
|
||||
- The dev profile builds the workspace crates with line tables only (`debug = "line-tables-only"`),
|
||||
so panics and backtraces still name file and line, and dependencies without debug info. For a
|
||||
debugger session, `CARGO_PROFILE_DEV_DEBUG=true cargo build` gives full debug info.
|
||||
|
||||
## Robustness: VectorCraft never crashes
|
||||
|
||||
A crash takes the user's unsaved work with it, and much of what the app reads is untrusted: SVG,
|
||||
@@ -319,7 +345,8 @@ in the code:
|
||||
| `todo!`, `unimplemented!` | don't merge unfinished paths; return an error that says what isn't supported |
|
||||
|
||||
`clippy.toml` allows `unwrap`, `expect` and `panic` inside `#[test]` and `#[cfg(test)]` code.
|
||||
Integration-test files under `tests/` and `examples/`, and the `testkit` crate, opt out with
|
||||
Integration tests (`tests/it/main.rs` and the test files that are binaries of their own), examples
|
||||
and the `testkit` crate opt out with
|
||||
`#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]`. Tests use `panic!("…")`
|
||||
instead of `unreachable!()`.
|
||||
|
||||
@@ -462,6 +489,7 @@ Scores and hour estimates are unchanged.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-23 | minor | Tests: `cargo xtask test` (nextest, then doctests), one integration-test binary per crate and the files that keep their own, the dev profile's debug info. |
|
||||
| 2026-10-21 | minor | Fonts › Leaving out the bundled fonts: vectorcraft-ui-egui's `interface-fonts` feature, and `theme::install_fonts` for an embedder that draws the panels without `VectorcraftApp::logic`. |
|
||||
| 2026-10-21 | minor | M3: highlighted-text Eyedropper sampling preserves ranges and rejects unsupported source/target transparency atomically; regression-tested; scores and estimates unchanged. |
|
||||
| 2026-10-21 | minor | Fonts › Leaving out the bundled fonts: vectorcraft-ui-egui's and vectorcraft-mcp's `bundled-fonts` features, and the engine declared without default features. |
|
||||
|
||||
@@ -96,7 +96,7 @@ the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPri
|
||||
| PDF | open, place, save | yes (layers, masks, editable text, security, overprint, File Info from the XMP packet and the document information dictionary; Crop To › Trim takes the bleed box as the document's bleed) | yes (presets, PDF/X-1a/3/4, layers, marks and bleed, ICC, fast web view, 1.3+) | fuzzed import; shared guarded writer and windowed reader ([pdf-backend.md](pdf-backend.md)), metadata, incomplete-content and dependency-source regressions; PDF text opens where the file draws it, glyph by glyph (#722: kerning or none, spacing per character, text rise as baseline shift, vertical writing, justified text boxes); alpha soft masks drawn as images import with the mask (#1551); a clip only counts as covering the page when its shape does (#1542); a PDF another app copied pastes as editable vector art (#1499); letter-spaced type on a path keeps its length and its glyph-by-glyph stroke comes in as one text object (#1586, #1585); a crafted name tree with shared nodes no longer takes exponential time on open (#1781); invisible text (rendering mode 3: a scanned page's recognized text) opens as type with no fill or stroke and saves back as invisible, searchable text (#2675); PDF/X-1a and X-3 flattening in a CMYK document keeps CMYK inks where it rasterizes (#2160) |
|
||||
| EPS | open, place, save | yes (own PostScript L3 interpreter, Illustrator EPS editing data structure) | yes (L2/L3, TIFF previews) | CID-keyed CJK type not read; #505; images encoded with `CCITTFaxDecode` (ITU-T T.4 Group 3 and T.6 Group 4, with `K`, `Columns`, `Rows`, `EndOfBlock`, `EndOfLine`, `EncodedByteAlign` and `BlackIs1`) draw, and damaged data keeps the rows decoded before its first damaged row; Type 42 (TrueType) fonts set type at its size (#1553); a clip only counts as covering the page when its shape does (#1542); an EPS another app copied pastes as editable vector art (`clipboard.importEps`, #1499); a stitched shading keeps its hard colour changes on import (#1654); CMYK images, CMYK JPEGs included, keep their ink amounts on import, as from PDF, and the pixels a `MaskColor` or `ImageType 3` mask hides become a grayscale opacity mask; ruzstd 0.9.1 for compressed editing data (#1807, RUSTSEC-2026-0344) |
|
||||
| PostScript `.ps` | open, place | yes (the EPS reader reads its first page; `.epsf` files open and place as EPS) | via Print to PostScript | `tests_epsimport.rs` |
|
||||
| SVG / SVGZ | open, place, save | yes | yes (SVG Options, Preserve Editing, symbols, filters, rich text) | strongest format; tests in `crates/svg/tests`; text on a path follows basic shapes as their SVG 2 equivalent paths (#1527); CSS transforms and the `font` shorthand (#1565), percentage lengths in symbols (#1596), Inkscape flowed text as area type (#1568) and Inkscape pages as artboards (#1601) import; an unsupported filter is named in its warning (#1598); an artboard exported on its own (Export As with Use Artboards, Export for Screens, Asset Export) has the artboard's name as `<title>`, Save the document's (#3544); attributes in other applications' XML namespaces (`plotter:pen="2"`) kept on their objects from import to export, as `data-*` attributes are (`crates/svg/tests/foreign_attributes.rs`) |
|
||||
| SVG / SVGZ | open, place, save | yes | yes (SVG Options, Preserve Editing, symbols, filters, rich text) | strongest format; tests in `crates/svg/tests`; text on a path follows basic shapes as their SVG 2 equivalent paths (#1527); CSS transforms and the `font` shorthand (#1565), percentage lengths in symbols (#1596), Inkscape flowed text as area type (#1568) and Inkscape pages as artboards (#1601) import; an unsupported filter is named in its warning (#1598); an artboard exported on its own (Export As with Use Artboards, Export for Screens, Asset Export) has the artboard's name as `<title>`, Save the document's (#3544); attributes in other applications' XML namespaces (`plotter:pen="2"`) kept on their objects from import to export, as `data-*` attributes are (`crates/svg/tests/it/foreign_attributes.rs`) |
|
||||
| DXF | open, export | yes | yes (R12–2018) | R13–2018 closed SPLINE flags and control-point closure on import verified (#1299); circular cubic paths export as native CIRCLE/ARC where stroke semantics permit; R12 weighted strokes remain POLYLINE while non-circular curves keep the existing fallback (`crates/cad/src/tests.rs`); [Autodesk group 70](https://help.autodesk.com/cloudhelp/2024/ENU/AutoCAD-DXF/files/GUID-E1F884F8-AA90-4864-A215-3182D47A9C74.htm). CNC preset and VCarve QA remain open |
|
||||
| DWG | open, export | no | no | no open spec; DXF instead |
|
||||
| EMF / WMF | open, export | yes | yes | an oversized embedded PNG or JPEG is refused from its header, before any pixels are decoded (#1776) |
|
||||
|
||||
+12
-12
@@ -485,9 +485,9 @@ since 2026-10-01.
|
||||
- **Artboard history selection:** headless pointer/key tests in `panels/properties.rs` pass for panel New → Undo → Delete/Backspace/Properties, Duplicate → Undo → Delete, pruning removed indices and preserving valid multiselection. They assert board survivors, artwork and object selection, Move/Scale Artwork options, saved state and Undo/Redo. Full workspace CI passes.
|
||||
- **Artboard tab-switch follow-up:** changing the active document through its tab aligns the Artboard tool with the displayed board before Properties Delete; clicking the already-active tab preserves multiselection and Move/Scale Artwork options. Three headless `chrome::tests::artboard_document_tab_*` regressions cover actual tab A → B → A clicks followed by Properties Delete, surviving boards and guides, preserved artwork/symbols and Undo/Redo; controls cover same-tab multiselection, Move/Scale Artwork options and other tools. Historical focused validation on `dbd2497b` recorded one expected RED failure with two passing controls, then all three GREEN tests passed. All three also passed in historical full CI on `a0671777`. A full CI retry on the five-unit combined source at `a0671777` passed all seven stages with 6,613 test outcomes on 2026-10-14. An earlier full-CI run on the identical source panicked in the randomized `ai_mutated_save_never_panics` test; the cause remains unresolved. This five-fix batch does not claim to repair that failure.
|
||||
- **Artboard clean active-tab close (2026-10-15):** with the Artboard tool active, closing a clean active tab restores the surviving document's displayed board before Properties Delete. Nine targeted checks passed: three existing tab regressions, one clean close/Delete regression and five existing unsaved-document controls. The close regression checks the correct surviving board, preserved artwork/symbols and object selection, affected guides, one Delete edit, saved state and Undo/Redo. Three reviewed original full-app headless captures show the active tab before closing, the surviving document's shown board after closing, and the correct survivor after Properties Delete, using the actual tab × and Delete controls.
|
||||
- **Colour emoji with bitmap glyphs:** `sbix` (Apple Color Emoji) and `CBDT` (Noto Color Emoji) glyphs draw as their largest strike's PNG, placed with the type and unpainted by its fill; PDF writes them in Type 3 glyphs or as images, SVG as images with outlined text, EPS and legacy `.ai` as images, and Create Outlines makes embedded images. Synthetic test fonts in `crates/text/src/test_fonts.rs`; tests `crates/text/src/tests_bitmaps.rs`, `crates/render/src/tests_bitmapglyphs.rs`, `crates/svg/tests/colour_glyphs.rs`, `pdf::tests::colour_bitmap_glyphs_export_as_images`, the `eps` `colour_bitmap_glyphs_*` tests and `tests_pathops::create_outlines_turns_bitmap_glyphs_into_images`. COLR and OpenType SVG fonts remain.
|
||||
- **COLR colour fonts:** v0 and v1 glyphs draw as vector layers (solid colours, linear and radial gradients, clips, blend modes, the foreground colour from the run's fill) on the canvas, as paths in SVG, EPS, legacy `.ai` and PDF with outlined text, as Type 3 text in PDF, and as editable groups with Create Outlines; sweep gradients and Porter-Duff compositing draw the glyph as an image. Measured by `tests_colr`, `render::tests_colrglyphs`, `pdf::tests::colr_glyphs_export_as_vector_art`, `svg/tests/colour_glyphs.rs`, `eps` `colr_glyphs_*`, `tests_pathops::create_outlines_turns_colr_glyphs_into_layer_groups`. Scores unchanged (estimated).
|
||||
- **OpenType SVG colour fonts:** each glyph's SVG document is read by the SVG importer (registered by the engine) into vector art in the run's colours, then drawn, exported and outlined as COLR glyphs are; PDF real text writes them as text that draws nothing and draws their art where they sit. Measured by `tests_svg_glyphs`, `svg/tests/svg_glyphs.rs`, `tests_colourfonts::create_outlines_turns_svg_glyphs_into_groups`, `tests_colourfonts::pdf_real_text_draws_svg_glyphs_as_art_and_keeps_their_characters`. Scores unchanged (estimated).
|
||||
- **Colour emoji with bitmap glyphs:** `sbix` (Apple Color Emoji) and `CBDT` (Noto Color Emoji) glyphs draw as their largest strike's PNG, placed with the type and unpainted by its fill; PDF writes them in Type 3 glyphs or as images, SVG as images with outlined text, EPS and legacy `.ai` as images, and Create Outlines makes embedded images. Synthetic test fonts in `crates/text/src/test_fonts.rs`; tests `crates/text/src/tests_bitmaps.rs`, `crates/render/src/tests_bitmapglyphs.rs`, `crates/svg/tests/it/colour_glyphs.rs`, `pdf::tests::colour_bitmap_glyphs_export_as_images`, the `eps` `colour_bitmap_glyphs_*` tests and `tests_pathops::create_outlines_turns_bitmap_glyphs_into_images`. COLR and OpenType SVG fonts remain.
|
||||
- **COLR colour fonts:** v0 and v1 glyphs draw as vector layers (solid colours, linear and radial gradients, clips, blend modes, the foreground colour from the run's fill) on the canvas, as paths in SVG, EPS, legacy `.ai` and PDF with outlined text, as Type 3 text in PDF, and as editable groups with Create Outlines; sweep gradients and Porter-Duff compositing draw the glyph as an image. Measured by `tests_colr`, `render::tests_colrglyphs`, `pdf::tests::colr_glyphs_export_as_vector_art`, `svg/tests/it/colour_glyphs.rs`, `eps` `colr_glyphs_*`, `tests_pathops::create_outlines_turns_colr_glyphs_into_layer_groups`. Scores unchanged (estimated).
|
||||
- **OpenType SVG colour fonts:** each glyph's SVG document is read by the SVG importer (registered by the engine) into vector art in the run's colours, then drawn, exported and outlined as COLR glyphs are; PDF real text writes them as text that draws nothing and draws their art where they sit. Measured by `tests_svg_glyphs`, `svg/tests/it/svg_glyphs.rs`, `tests_colourfonts::create_outlines_turns_svg_glyphs_into_groups`, `tests_colourfonts::pdf_real_text_draws_svg_glyphs_as_art_and_keeps_their_characters`. Scores unchanged (estimated).
|
||||
- **Character-style Auto leading:** capture and explicit style attributes retain Auto as a reset, distinct from omitted leading. Redefine and later numeric updates preserve local leading overrides; the Character Styles marker recognizes Auto correctly. Regression assertions cover partial definitions, Undo/Redo and native save/load serialization. Public references: [Character and paragraph styles](https://helpx.adobe.com/illustrator/using/character-paragraph-styles.html) and [Leading](https://helpx.adobe.com/illustrator/using/line-character-spacing.html).
|
||||
- **Text styles redefined to the defaults:** New and Redefine Character Style and Paragraph Style capture every attribute of the selected text, including those at their defaults (Auto kerning, Roman baseline alignment, the default Hyphenation options, the Every-line Composer, no tab stops), and `charStyle.setAttrs` and `paraStyle.setAttrs` keep an attribute set to its default value. After a redefinition, text whose value still equals the old definition's takes the new value, a default included; local overrides and attributes the new definition leaves out stay. The style panels show "+" only where the text differs from its style, including type with Auto alignment. Public reference: [Character and paragraph styles](https://web.archive.org/web/20260825113735/https://helpx.adobe.com/illustrator/using/character-paragraph-styles.html).
|
||||
- **Auto Leading outside the layout:** a paragraph's Justification… › Auto Leading (<https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/adjust-word-and-letterspacing-in-justified-text.html>) also sets the Leading the Character panel shows for Auto type (blank where the selected paragraphs differ), the leading the Alt+↑/↓ nudge (`type.step`) steps from, each paragraph's own, and the lines Fill with Placeholder Text counts in area type. Evidence: `ParaStyle::effective_leading` (`crates/doc/src/text.rs` test `paragraph_effective_leading_follows_auto_leading`), `tests_prefs::auto_leading_steps_and_fills_from_the_paragraph_auto_leading`, `panels::character` test `leading_field_shows_the_paragraph_auto_leading`.
|
||||
@@ -500,7 +500,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- **Width-point finite-arithmetic follow-up (G1/M3):** Adjoining adjustments avoid intermediate overflow; non-finite normalized or resulting factors are rejected before committing. Engine regressions in `tests_widthpoints.rs` cover finite extremes, small nonzero results, atomic rejection, Undo/Redo and native save/reopen. Historical focused validation on the `a3357121` integration observed 2 expected RED failures with 11 passing controls, then all 13 tests passing with the repair; none were ignored. All three added regressions passed in current full CI. A full CI retry on the five-unit combined source at `a0671777` passed all seven stages with 6,613 test outcomes on 2026-10-14. An earlier full-CI run on the identical source panicked in the randomized `ai_mutated_save_never_panics` test; the cause remains unresolved. This five-fix batch does not claim to repair that failure. Scores and hours unchanged.
|
||||
- **Width-point creation (G1):** double-click a stroked segment with the Width tool to open Width Point Edit using sampled side widths. OK creates the point and optionally adjusts adjoining widths in one undo step. Cancel or Delete discards a prospective point without changing the document or redo history. Command and pointer tests cover profiles, compound paths, adjoining adjustment, existing-point editing and Undo/Redo. Message-catalog checks and full CI passed; the original capture verifies opening and Cancel, and the dialog layout was reviewed (2026-10-13: 28 focused Width tests, all seven cargo xtask ci stages, and a headless opening/Cancel capture with original PNG review). [Variable-width strokes](https://helpx.adobe.com/illustrator/using/stroke-object.html).
|
||||
- **Rulers (#1212):** drag the intersection to preview and set a document zero point, or double-click it to reset to the active artboard's top-left. Transform, Properties and Control X/Y fields measure from it and convert entered positions back to absolute command coordinates. The origin survives every native format version and compression; geometry, guides and patterns stay in place. Public reference: [Use rulers](https://helpx.adobe.com/ca/illustrator/desktop/measure-and-align/grids-and-guides/use-rulers.html).
|
||||
- **Global and artboard rulers:** View › Rulers › Change to Global / Artboard Rulers (Cmd+Alt+R, `view.globalRulers`). A new document has artboard rulers: the rulers and the Transform, Properties, Control and Info X/Y count from the active artboard's top-left corner, or the zero point dragged out for that artboard, which moves with it; with global rulers one document zero point serves every artboard. Guide readouts and Shift ruler-tick snapping follow the same zero point. The mode is saved with the document; files from before it open with global rulers, as they measured. Regression tests: `engine/tests/ruler_origin.rs`, `ui-egui/src/tests_ruler_origin.rs`, `tools/src/rulerguide.rs`. Public reference: [About rulers](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/about-rulers.html).
|
||||
- **Global and artboard rulers:** View › Rulers › Change to Global / Artboard Rulers (Cmd+Alt+R, `view.globalRulers`). A new document has artboard rulers: the rulers and the Transform, Properties, Control and Info X/Y count from the active artboard's top-left corner, or the zero point dragged out for that artboard, which moves with it; with global rulers one document zero point serves every artboard. Guide readouts and Shift ruler-tick snapping follow the same zero point. The mode is saved with the document; files from before it open with global rulers, as they measured. Regression tests: `engine/tests/it/ruler_origin.rs`, `ui-egui/src/tests_ruler_origin.rs`, `tools/src/rulerguide.rs`. Public reference: [About rulers](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/about-rulers.html).
|
||||
- **Video display options (#2849):** Artboard Options' Show Video Safe Areas and Video Ruler Pixel Aspect Ratio, and View › Rulers › Show Video Rulers ([Set video display options for artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/set-video-display-options-for-artboards.html)). Each artboard has its own (`artboard.setProps`/`artboard.new` `showVideoSafeAreas`, `videoRulerPixelAspectRatio` 0.1–10, default 1), set in one undo step and written to the native format only when on or not 1, so other documents save byte for byte as before. The help page names the safe areas without numbers; our choice is [EBU R 95](https://tech.ebu.ch/docs/r/r095.pdf)'s (note 5): the canvas outlines the action-safe area 3.5% and the title-safe (graphics-safe) area 5% in from each edge, 1 point wide and turned with the view; exports and print leave them out. Show Video Rulers (`view.videoRulers`) shows the rulers counting the active artboard in pixels from its top-left corner, whatever the ruler origin: a pixel is as many points wide as the ratio and one point tall, so a 720 × 480 pt artboard at 0.9 reads 800 × 480. Hide Video Rulers returns them to the General unit; Shift ruler-tick snapping of new guides keeps the General unit. Regression tests: `tests_panelcmds.rs` (`artboard_video_display_options_*`, `artboard_display_marks_stay_out_of_exports`), `tests_docsetup.rs` (`an_artboard_shows_its_video_safe_areas`), `canvas.rs` (`video_rulers_count_the_artboard_in_pixels_of_its_aspect_ratio`), `tests_ruler_origin.rs` (`show_video_rulers_counts_the_active_artboard_in_pixels`), `dialogs/tests.rs` (`artboard_options_show_video_display_options`).
|
||||
|
||||
- **Document Setup modal handoff:** Document Setup → Edit Artboards applies valid settings, closes the dialog and activates the Artboard tool. Pointer-click tests cover successful handoff, invalid settings, OK, Cancel and Undo in `dialogs/document_setup.rs`.
|
||||
@@ -540,7 +540,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- The control server writes each reply through a 64 KiB buffer. On Windows 11 it sends the 17 MB `document.inspect` reply of a document with 50,000 objects on one layer to a client over loopback in 0.09 s instead of 34 s.
|
||||
- MCP server with 25 tools (drawing, text, effects, Pathfinder, transforms, graphs, text wrap, export, screenshots, any command), attached to the running app or headless. Tool descriptions and replies agree with the engine: format lists come from its format table, `save_file` says it keeps the document's own format, `inspect_document` reports the paint type's characters show, and selection exports report the encoder's warnings. `--automation-read-root` / `--automation-write-root` (on `vectorcraft-cli mcp` and on `vectorcraft --control`) confine the files commands read and write to chosen folders, links followed, with the flags PhotoCraft takes (#832); each flag may be given more than once, a path inside any of its folders being allowed (#3546, `file_access::tests::several_roots_of_a_kind_each_grant_their_access`, `cli::mcp_automation_roots_take_several_folders`). A long export can be cancelled (`notifications/cancelled` stops it at the next page or group and writes nothing), reports page progress to a client that sent a progress token, and does not make other requests wait: engine-free ones answer at once, the rest are refused as busy after two seconds (#2165, #2171, #2172).
|
||||
- MCP protocol: five prompts (`prompts/list` / `prompts/get`: poster, icon set, recolour, trace-and-style, export set), `completion/complete` for prompt arguments and template variables read from the live catalogues (formats, effect ids, command ids, trace presets, swatch and object ids), four resource templates (`vectorcraft://object/{id}`, `//command/{id}`, `//effect/{id}`, `//swatch/{name}`) so an agent reads one thing instead of the whole document, and `logging/setLevel` with `notifications/message`. `window.panel` ids are case-insensitive (display labels work too), `vectorcraft://object/{id}` reads through `document.node {summary: true}` (the same reply bytes from one lookup instead of summarizing the whole document: a leaf read on a 2,000-object document drops from ~20 ms to ~0.06 ms), large containers slice with `document.node {depth?, childLimit?}` (truncated levels report `childCount`), `document.inspect` and the `inspect_document` tool slice the same way so a depth-0 read is the layer skeleton (~9.7 KB instead of ~2 MB on a 2,000-object document), `document.find` locates nodes by name/kind/text with ancestor paths, and the CLI sweeps the protocol surface over stdio against the compiled binary. `docs/mcp.md` has a Protocol section covering the capability table and what is deliberately absent (subscriptions, progress, cancellation, sampling, elicitation, Streamable HTTP — all of which need the transport to interleave reads).
|
||||
- Headless CLI (`vectorcraft-cli run`, `convert`, `info`, `bench`, `perf`, `mcp`). A `run` step, like a `command_batch` step over MCP, can use an earlier step's result (`"$1.id"`, #785); `run --commands FILE` (or `-` for standard input) reads the steps from a JSON array or JSON Lines, past the Windows command-line limit (#3147, `apps/vectorcraft-cli/tests/cli.rs` `run_reads_*`, `run_refuses_malformed_commands_naming_the_step`).
|
||||
- Headless CLI (`vectorcraft-cli run`, `convert`, `info`, `bench`, `perf`, `mcp`). A `run` step, like a `command_batch` step over MCP, can use an earlier step's result (`"$1.id"`, #785); `run --commands FILE` (or `-` for standard input) reads the steps from a JSON array or JSON Lines, past the Windows command-line limit (#3147, `apps/vectorcraft-cli/tests/it/cli.rs` `run_reads_*`, `run_refuses_malformed_commands_naming_the_step`).
|
||||
- File › Scripts › Check Script… reads a script written for Illustrator (`.jsx`, `.jsxinc` or a compiled `.jsxbin`) without running any of it and reports what it needs (the document model, files, dialogs, menu commands, features only Illustrator has), what it would do outside the document, the menu commands and document-model members it uses, its directives and included files, and the problems that would stop it loading (`script.check`, which also reads `.js` files); File › Open and drag-and-drop show the report for `.jsx`, `.jsxinc` and `.jsxbin` files.
|
||||
- **UI:** Illustrator 2026 layout restyled to measured values:
|
||||
- Medium Dark theme, categorized and Advanced toolbars (a tool group's flyout opens on a right press, a long press or a press on its corner triangle), 35 pt document tabs, 33 pt panel tabs.
|
||||
@@ -630,7 +630,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
A selected clip group shows the outline and anchors of its clipping path, not of the art the path clips, and its hover highlight and key object outline follow the clipping path too. That art shows its whole outline when it is selected on its own: with Edit Contents, Direct or Group Selection, or in isolation mode (#2422).
|
||||
Clear deletes individual selected compound-path members without deleting their siblings, and explicit object ids work without a selection; Cut still removes the same objects it copies (#786).
|
||||
A command that leaves out an object selected inside a selected group also leaves it out when `ids` list it together with a group that holds it. Make Repeat and Live Paint no longer fail on such a list, New Symbol no longer stores the object twice, and `object.transform` no longer moves it twice.
|
||||
- **Compound paths:** Make keeps the backmost object's appearance and each contour; selecting a member in Direct Selection reads and changes the compound's fill rule in Attributes. Direction remains a per-member edit. Rendered holes, unchanged contour geometry, no-op history and Undo/Redo are covered by `engine/tests/compound_attributes.rs` (#1197). Behaviour reference: [public compound-path documentation](https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/create-compound-paths.html). Make Compound Path on a group (such as outlined type) keeps the paint of its backmost path instead of drawing nothing, and compound paths joined into one keep their holes (#1537).
|
||||
- **Compound paths:** Make keeps the backmost object's appearance and each contour; selecting a member in Direct Selection reads and changes the compound's fill rule in Attributes. Direction remains a per-member edit. Rendered holes, unchanged contour geometry, no-op history and Undo/Redo are covered by `engine/tests/it/compound_attributes.rs` (#1197). Behaviour reference: [public compound-path documentation](https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/create-compound-paths.html). Make Compound Path on a group (such as outlined type) keeps the paint of its backmost path instead of drawing nothing, and compound paths joined into one keep their holes (#1537).
|
||||
- **Saved selections:** Select ▸ Save Selection… asks for a name and keeps the selection in the document (saved with the file, undoable, up to 25); each one is listed at the bottom of the Select menu and a click selects its objects again; Edit Selection… lists them to rename or delete, applied together on OK. A file's saved selections are capped and checked against its objects when it opens.
|
||||
Select ▸ Update Selection replaces the objects of the saved selection last recalled with the selected objects, in one undo step that marks the document changed (`select.update`). After Edit Selection… renames that saved selection, Update Selection replaces it under the new name; after Edit Selection… deletes it, Update Selection is disabled until another saved selection is recalled or the delete is undone. Undo and Redo do not change which saved selection is recalled.
|
||||
- **Drawing modes:** in Draw Inside, a double-click with a selection tool outside the object being drawn into sets the drawing mode to Draw Normal, as Shift+D does. Pastes follow the drawing mode ([drawing modes](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/learn-drawing-basics/drawing-modes-overview.html)) (#3705): in Draw Inside, Paste, Paste in Place and Paste on All Artboards put the copies inside the object drawn into, in one undo step, Paste Remembers Layers aside; in Draw Behind, Paste in Place and Paste on All Artboards put them just below the selection; Paste in Front and Paste in Back ignore the mode. Shift+D cycles Draw Normal → Draw Behind → Draw Inside → Draw Normal, skipping Draw Inside unless one path, compound path or type is selected. With the object drawn into isolated (a double-click on it with the Selection tool), art drawn or pasted inside it takes isolation mode into its clip group, so it can be clicked and isn't dimmed, as art added in isolation mode belongs with what is isolated ([isolation mode](https://helpx.adobe.com/illustrator/desktop/manage-objects/select-objects/isolate-objects.html)) (#3705); undo or redo that removes the isolated object ends isolation mode.
|
||||
@@ -701,7 +701,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
A move pressed on a path's stroke, or within 6 px of its outline away from its anchors and center, is held by the nearest point of that path, with the Selection, Group Selection or Direct Selection tool, and a straight segment Direct Selection drags is held by its own nearest point. That point is what snaps, so the path lands exactly on a ruler guide (with "align") instead of the pressed spot landing there and the path a few pixels beside it, an offset that showed only zoomed far in (#3701). A press in the middle of a fill still snaps the pressed spot: the public [Align objects with guides](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/align-graphic-objects-with-guides.html) page has Snap to Point align the point under the pointer, which is placed exactly where the object should meet the guide.
|
||||
A bounding-box handle dragged with the Selection or Free Transform tool snaps what it moves as a dragged point snaps (#2054). With View › Snap to Grid on, it snaps to the nearest gridline, and with Snap to Pixel on, it snaps to whole pixels. With Smart Guides off and Snap to Point on, it snaps to the other objects' anchors and the ruler guides within the Snap to Point distance. With Smart Guides and Snap to Point both on, it lines up with the ruler guides as well as the other objects. Shift still keeps the proportions, and Alt still scales about the center.
|
||||
A user slice's edge or corner handle dragged with the Slice Selection tool snaps as the Slice tool's drag does. With View › Snap to Grid on, it snaps to the nearest gridline, and with Snap to Pixel on, it snaps to whole pixels. Otherwise, with Smart Guides on, it lands on or lines up with the anchors, centers and edges of the art and the artboards, and with Smart Guides off and Snap to Point on, it snaps to the art's anchors and the ruler guides within the Snap to Point distance. Then a ruler guide within 5 px, shown and unlocked, moves the side or corner onto it. Shift still keeps the proportions, and Alt still resizes about the center.
|
||||
- **Artboard click-to-fit (#1227):** a plain click fits the topmost editable object or group to a new artboard, including its stroke like Fit to Selected Art, and makes the new artboard active. Art and its selection stay unchanged; creation is one undo step. Handles, modified clicks, drags, Escape and tool changes retain their meaning. Tested inside an artboard and on the pasteboard (`crates/engine/tests/artboard_click.rs`); the public [CS6 Printing Guide](https://helpx.adobe.com/content/dam/help/en/indesign/pdf/CS6_PrintGuide.pdf) documents the object-click gesture. This legacy gesture adds evidence without regrading the 2026 canvas controls.
|
||||
- **Artboard click-to-fit (#1227):** a plain click fits the topmost editable object or group to a new artboard, including its stroke like Fit to Selected Art, and makes the new artboard active. Art and its selection stay unchanged; creation is one undo step. Handles, modified clicks, drags, Escape and tool changes retain their meaning. Tested inside an artboard and on the pasteboard (`crates/engine/tests/it/artboard_click.rs`); the public [CS6 Printing Guide](https://helpx.adobe.com/content/dam/help/en/indesign/pdf/CS6_PrintGuide.pdf) documents the object-click gesture. This legacy gesture adds evidence without regrading the 2026 canvas controls.
|
||||
- **Rotated views:** with the view rotated, Fit Artboard in Window, Fit All in Window and the status bar's artboard navigator fit the artboard or the art as the view turns it, and a Zoom tool marquee (Animated Zoom or GPU Performance off) zooms to the area dragged across. Ruler guides on no artboard turn with the view. The canvas draws the selection marquee where the tools select, Show Hidden Characters draws the marks of type partly inside the canvas, the Contextual Task Bar stays under the selection, and the Navigator's red box outlines what the window shows.
|
||||
- **Views:** the status bar's artboard navigator (first, previous, next, last; `view.goToArtboard`) goes to an artboard, fits it and synchronizes the Artboard tool selection when that tool is active; ordinary object selection is preserved. Actual Next-button regressions cover keyboard/Properties deletion and Undo in `chrome.rs`. Fit Artboard in Window and Actual Size show the navigator's artboard. The Artboards panel highlights that active artboard: clicking a row makes it active (the Artboard tool's too, while it is chosen) as does clicking an artboard with the Artboard tool, double-clicking its number also fits it in the window and double-clicking its name renames it. Duplicate Artboards, or a row dragged onto New Artboard, copies the artboard with its art (#446). Escape leaves Presentation Mode, `view.screenMode` from it goes back to Normal Screen Mode, and the screen mode isn't saved with the preferences: the app always starts in Normal Screen Mode with its menus and panels (#472). View › Pixel Preview shows the art as the document pixels it rasterizes to, one per point and hard-edged when magnified, turning with a rotated view (it used to draw the smooth vectors, with only the pixel grid over them). In Outline view a template layer keeps drawing in Preview, dimmed, so a picture placed to be traced stays visible under the outlines (#1505, fixes #1486).
|
||||
In Presentation Mode, → and ← go to the next and the previous artboard, and a click anywhere goes to the next one, as the status bar navigator's Next and Previous do (`view.goToArtboard`): on the last artboard → and a click leave the view as it is, and so does ← on the first. The arrow keys do not move the selection there, and the tool does not receive a click, so a click selects and draws nothing (behavior reference: [View artwork](https://helpx.adobe.com/illustrator/using/viewing-artwork.html)).
|
||||
@@ -817,7 +817,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- **RGB input to other commands in a CMYK document:** type colors (`text.create`, `text.setStyle`, `text.setRangeStyle`), a Shape Builder hex fill, `paint.editGradient` stops, freeform gradient points, gradient mesh points (the Mesh tool gives a new point the fill's color in its own model), the Symbol Stainer's color and the swatch commands (`swatch.new {colors}`, `swatch.newGroup`, `swatch.editGroup`, `swatch.edit`) separate RGB through the color settings as `paint.setFill` does: with Generic CMYK, `text.setStyle {fill: "#53aadf"}` gives C59 M12 Y3 K0. A color a swatch already has keeps its model, and a swatch color given with `mode` is converted into that mode only.
|
||||
- **Swatches made from a shown color in a CMYK document:** the Color panel menu's Create New Swatch, an Alt-click on the Swatches panel's New Swatch button, the New Swatch dialog for a gradient, a Fill or Stroke proxy or the Gradient panel's thumbnail dropped on the Swatches panel (or Alt-dropped on a swatch of its kind), and the Gradient panel's Add to Swatches and Save to Swatches keep each color's model, as the Color Guide's and Color Themes' saved colors do: an RGB object's color gives an RGB swatch, and a gradient's RGB stops stay RGB.
|
||||
- **Sample Color from Screen:** an eyedropper button in the Color Picker and `ui.sampleScreenColor` (which can be given a key in Keyboard Shortcuts) pick a color anywhere on the screen with the system's sampler: `NSColorSampler` on macOS, the EyeDropper API in browsers that have it. The color becomes the fill or the stroke as one undo step, or the Color Picker's color, converted from display sRGB into the working RGB space. Windows, Linux and other browsers have no sampler yet; the button is gray there.
|
||||
- **Stroke visibility (#1253):** adding paint to a previously unpainted zero-width stroke restores the existing 1 pt default (`PaintDefaults::default`, `Appearance::set_stroke`), for the selected appearance item, the new-art defaults and Swap Fill/Stroke. Positive widths are kept; recoloring a painted zero-width stroke keeps its explicit zero. A real Eyedropper-to-new-shape sequence reproduces the zero-width inheritance. Five engine regressions cover creation, defaults, targeted items, preserved widths and one-step Undo/Redo (`crates/engine/tests/stroke_visibility.rs`).
|
||||
- **Stroke visibility (#1253):** adding paint to a previously unpainted zero-width stroke restores the existing 1 pt default (`PaintDefaults::default`, `Appearance::set_stroke`), for the selected appearance item, the new-art defaults and Swap Fill/Stroke. Positive widths are kept; recoloring a painted zero-width stroke keeps its explicit zero. A real Eyedropper-to-new-shape sequence reproduces the zero-width inheritance. Five engine regressions cover creation, defaults, targeted items, preserved widths and one-step Undo/Redo (`crates/engine/tests/it/stroke_visibility.rs`).
|
||||
- **Gradients:** lossless, validated gradient params; gradients follow rotate, reflect, shear, non-uniform scale and distortions; the Gradient tool edits the fill or stroke of the active proxy, type included; an interactive on-canvas annotator (move, length/angle, rotate, add/move/delete/duplicate stops, midpoints, a stop popover, Delete and arrow keys). The Gradient panel has the Fill/Stroke proxy, a gradient-swatch dropdown with Save to Swatches, midpoint locations, Alt-drag to copy or swap stops, swatch drops on the ramp and a stop eyedropper; copied appearances and graphic styles place gradients on each target's own bounds; gradient handles snap and honour Constrain Angle. Gradient stops stay linked to global and spot swatches. Freeform gradients (points and lines) are edited on the canvas; radial gradients have an extent ellipse, an aspect handle and an off-centre focal point (SVG `fx`/`fy`, PDF two-point radials). A stroke's gradient runs within, along or across it. Object ▸ Expand turns gradients into N objects or a gradient mesh.
|
||||
With a gradient stop or a freeform gradient point selected, Alt-clicking a color swatch in the Swatches panel recolors it instead of the inactive Fill or Stroke.
|
||||
- **Freeform gradient colors in their own model:** Object ▸ Expand and Object ▸ Create Gradient Mesh (`object.mesh.create`) mix a freeform gradient's points in the color model they share: points of C50 M20 Y0 K10 give mesh points of C50 M20 Y0 K10, and RGB points keep their values in a working RGB space other than sRGB. Points in different models mix in display RGB, and a CMYK document stores the result as CMYK through the color settings. Points colored along CMYK stops (`paint.editGradient {stops}`, or a CMYK gradient switched to freeform) are CMYK, and so are a point `paint.freeform.splitLine` adds between CMYK points and a point `paint.freeform.addPoint` adds without `color` to CMYK points. Public reference: [Gradients](https://web.archive.org/web/20250714042311/https://helpx.adobe.com/illustrator/using/gradients.html) and [Meshes](https://web.archive.org/web/20250812201019/https://helpx.adobe.com/illustrator/using/meshes.html), which do not state how colors in different models mix; the rule is VectorCraft's own.
|
||||
@@ -888,9 +888,9 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
A gap between glyphs of two sizes is measured against the smaller one, so a word set beside a much larger one with no space glyph between them (a browser's margin between two sizes, as in `≤ 4` and a small `hours`) keeps its word space (#3623).
|
||||
SVG import's style cascade, used for hidden objects, non-scaling strokes and type, tests each element only against the `<style>` rules for its id, its classes and its element name, and the rules whose selector ends in none of these (`*`, `[x]`).
|
||||
A `.svgz` file copied in a file manager pastes as its art, as a copied `.svg` file does.
|
||||
SVG import keeps attributes in other applications' XML namespaces (`plotter:pen="2"`) on the objects and layers they belong to, as it keeps their `data-*` attributes: copies, duplicates and `.vectorcraft` files keep them, and SVG export writes them back with each namespace declared once on the root (the rest of #522; `crates/svg/tests/foreign_attributes.rs`, `tests_attributes.rs`).
|
||||
SVG import keeps attributes in other applications' XML namespaces (`plotter:pen="2"`) on the objects and layers they belong to, as it keeps their `data-*` attributes: copies, duplicates and `.vectorcraft` files keep them, and SVG export writes them back with each namespace declared once on the root (the rest of #522; `crates/svg/tests/it/foreign_attributes.rs`, `tests_attributes.rs`).
|
||||
In a `.ai` or EPS opened from its editing data, type has its text object's opacity and blend mode, whether it is made from the file's text document or taken from the page. Where such type has transparency and comes in with other art (the rest of its look, or page type and strokes that go with it), a group of them has it. Where the page draws its type, or the strokes around it, with transparency that is neither its text object's nor its text object's combined with that of the objects around it, the page's type and strokes keep their own instead (`crates/eps/src/import/tests_native.rs`). Where the page draws type of no text object over text objects made from the text document, that type is left out only when, for each piece of it, one of those text objects has the piece's Latin letters and digits in its text, in capitals or not; other such type is kept as the page's other type of no text object is.
|
||||
In SVG export without Fewer `<tspan>` Elements, each character of live type that justification or Mojikumi moves (the letter gaps of a Justify All line without word spaces, the gaps between the characters of a justified Japanese line, closed-up punctuation, the space between Japanese and Latin letters) starts its own positioned `<tspan>`, and type scaled horizontally on a justified line is stretched to its own width (`crates/svg/tests/text_layout.rs`).
|
||||
In SVG export without Fewer `<tspan>` Elements, each character of live type that justification or Mojikumi moves (the letter gaps of a Justify All line without word spaces, the gaps between the characters of a justified Japanese line, closed-up punctuation, the space between Japanese and Latin letters) starts its own positioned `<tspan>`, and type scaled horizontally on a justified line is stretched to its own width (`crates/svg/tests/it/text_layout.rs`).
|
||||
SVG export (live text), legacy `.ai` saves (text objects), DXF export (`TEXT` entities) and the CSS Properties panel write area type that Shrink Text to Fit scales down at the size it draws at, and scale its tracking, kerning, leading and baseline shifts the same way where the format holds them.
|
||||
A PDF saved with Illustrator's editing data opens through it as a PDF-compatible `.ai` does, with its layers and groups and with art reaching past its page unclipped; such a PDF holds the data in `AIPDFPrivateData` streams (part of #868). A shape a PDF or `.ai` paints with a gradient (a clip path round a shading) opens as one path filled with the gradient, not a clip group round a rectangle, so Shape Builder, Live Paint and Pathfinder work on its outline (#1304).
|
||||
In a `.ai` opened from its editing data, a text object on a layer that shows and doesn't print stays on that layer, made from the file's text document, even where the page doesn't draw it (`crates/eps/src/import/tests_native.rs`). Such a `.ai` opens without the PDF part's warnings that text was kept as outlines when no outlined glyphs come in, and a warning counts the text objects that hold the page's type next to glyphs the page has only as outlines, which don't come in (`crates/engine/src/cmd/fileio/tests_aiimport.rs`). Where the art of a `.ai` or EPS opened from its editing data differs a little from the file's page, the warning gives the share of the page that differs away from type. When that share is 0.2% or less, the warning names the type and gives the share of the page that differs where the type is (`crates/eps/src/import/tests_native.rs`).
|
||||
@@ -1058,7 +1058,7 @@ hidden, colours unchanged on average. SVG and PDF write it as an image at the Do
|
||||
(the documentation: the gradient converted to pixels with subtle noise, at that resolution); PDF with Preserve Editing
|
||||
keeps the gradient editable. Strokes and freeform gradients ignore it (the documentation: fills only). Tests:
|
||||
`render::tests_dither`, `engine::tests_gradient::dither_is_a_fill_gradient_option_that_saves_and_loads`,
|
||||
`svg/tests/raster_paints.rs`, `pdf::tests::dithered_gradient_fills_export_as_images_and_strokes_as_shadings` and the
|
||||
`svg/tests/it/raster_paints.rs`, `pdf::tests::dithered_gradient_fills_export_as_images_and_strokes_as_shadings` and the
|
||||
Gradient panel's `the_dither_box_dithers_the_gradient_fill_and_is_off_for_strokes`. Scores and hours unchanged.
|
||||
|
||||
## Gradient Method: Classic and Perceptual (2026-10-22)
|
||||
@@ -1075,7 +1075,7 @@ gradients draw exactly as before. Freeform gradients mix their own way. With Dit
|
||||
mix with dither's noise, on the canvas and in the SVG and PDF images; a Perceptual stroke's image has no noise (dither
|
||||
applies to fills). Tests: `color::gradient::tests` (perceptual midpoint, expansion, Classic unchanged, save format),
|
||||
`engine::tests_gradient::method_is_a_gradient_option_that_saves_and_loads`, `render::tests_dither`,
|
||||
`svg/tests/raster_paints.rs`, `pdf::tests::perceptual_gradients_export_as_images_and_classic_ones_as_shadings`,
|
||||
`svg/tests/it/raster_paints.rs`, `pdf::tests::perceptual_gradients_export_as_images_and_classic_ones_as_shadings`,
|
||||
`pdf::tests::dithered_perceptual_gradients_export_as_images_with_noise_on_the_fill` and the Gradient panel's
|
||||
`the_method_menu_makes_the_gradient_perceptual`. Scores and hours unchanged.
|
||||
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ against Illustrator's public documentation; **estimated** unless noted.
|
||||
| Full font-name matching (OpenType name ID 4) | done | #1258: loaded/cataloged faces and Missing Fonts searches; localized names and variable default faces; Windows font-service QA pending |
|
||||
| Variable fonts | done | named instances as styles; Character panel › Variable Font sets each axis (`fontAxes`); the font menu's Font Technology › Variable lists only variable font families, with the script, kind and ★ filters ([Find, apply, and adjust variable fonts](https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/find-apply-and-adjust-variable-fonts.html)); a family with a variable file beside static ones counts as variable. Evidence: `font_menu::tests::the_variable_filter_lists_only_variable_families`, `tests_axes::a_family_with_variation_axes_is_variable`, `tests_sysfonts::a_family_with_a_variable_file_beside_static_ones_is_variable` |
|
||||
| Fallback glyphs keep the text's weight and slant | done | #2491: a character from a fallback face is set in that family's face nearest the run's weight and slant; Bold Italic of a missing family is drawn Bold (Source Sans 3 has no Bold Italic) rather than Italic |
|
||||
| Colour fonts (bitmap, COLR, OpenType SVG) | partial | bitmap glyphs (`sbix`, `CBDT`) done: the largest strike's PNG, transformed with the type, fill and stroke ignored; PDF Type 3 glyphs or images, SVG images with outlined text (live text leaves emoji to the viewer, their fonts not embedded), EPS and legacy `.ai` images, Create Outlines images; not `sbix` duplicate, flipped, JPEG or TIFF glyphs, or `CBDT` BGRA or mask data, or ruby; in envelopes they warp as images do, and a Warp effect moves them without bending them. COLR (v0 and v1) done: a glyph's layers as vector art (solid colours, linear and radial gradients, clip glyphs and boxes, blend modes, transforms, palette 0, the foreground colour from the run's fill, variable fonts at the face's instance), transformed with the type; drawn on the canvas, written as paths in SVG, EPS, legacy `.ai` and PDF with outlined text, as text in PDF (Type 3 glyphs), and Create Outlines makes groups of editable paths. Paints documents have no form for draw the glyph as an image (256 px per em): sweep gradients, Porter-Duff compositing, radial gradients from a circle that isn't the centre, repeating or reflecting gradients. Not ruby. COLR and SVG glyphs also go with type that geometry effects reshape (on the canvas and in exports), into Expand Appearance and EMF/WMF export, as their art, and bitmap glyphs as images. OpenType SVG done: a glyph's SVG document (its element `glyph<gid>`) is read by the SVG importer into vector art in font units, `currentColor` in the run's fill (and the legacy `context-fill` and `context-stroke` in its fill and stroke), a `viewBox` mapped onto the em square, and then drawn, exported and outlined as COLR glyphs are; in PDF real text such glyphs are text that draws nothing, with their art drawn where they sit. Images in glyph documents are in the art, holding their pixels until a document takes them in; text, scripts and embedded content are left out, as the table rules them out. Not CPAL palette variables. Evidence: `tests_svg_glyphs`, `svg/tests/svg_glyphs.rs`, `tests_colourfonts::svg_glyph_images_draw_and_outline`, `tests_colourfonts::create_outlines_turns_svg_glyphs_into_groups`, `tests_colourfonts::pdf_real_text_draws_svg_glyphs_as_art_and_keeps_their_characters`, `tests_colourfonts::expand_appearance_keeps_the_colour_glyphs_of_type_with_effects`, `render::tests_colrglyphs::colour_glyphs_go_with_type_that_a_geometry_effect_reshapes`, `tests_bitmaps`, `tests_colr`, `render::tests_bitmapglyphs`, `render::tests_colrglyphs`, `pdf::tests::colour_bitmap_glyphs_export_as_images`, `pdf::tests::colr_glyphs_export_as_vector_art`, `svg/tests/colour_glyphs.rs`, `eps` `colour_bitmap_glyphs_*` and `colr_glyphs_*`, `tests_pathops::create_outlines_turns_bitmap_glyphs_into_images`, `tests_pathops::create_outlines_turns_colr_glyphs_into_layer_groups` |
|
||||
| Colour fonts (bitmap, COLR, OpenType SVG) | partial | bitmap glyphs (`sbix`, `CBDT`) done: the largest strike's PNG, transformed with the type, fill and stroke ignored; PDF Type 3 glyphs or images, SVG images with outlined text (live text leaves emoji to the viewer, their fonts not embedded), EPS and legacy `.ai` images, Create Outlines images; not `sbix` duplicate, flipped, JPEG or TIFF glyphs, or `CBDT` BGRA or mask data, or ruby; in envelopes they warp as images do, and a Warp effect moves them without bending them. COLR (v0 and v1) done: a glyph's layers as vector art (solid colours, linear and radial gradients, clip glyphs and boxes, blend modes, transforms, palette 0, the foreground colour from the run's fill, variable fonts at the face's instance), transformed with the type; drawn on the canvas, written as paths in SVG, EPS, legacy `.ai` and PDF with outlined text, as text in PDF (Type 3 glyphs), and Create Outlines makes groups of editable paths. Paints documents have no form for draw the glyph as an image (256 px per em): sweep gradients, Porter-Duff compositing, radial gradients from a circle that isn't the centre, repeating or reflecting gradients. Not ruby. COLR and SVG glyphs also go with type that geometry effects reshape (on the canvas and in exports), into Expand Appearance and EMF/WMF export, as their art, and bitmap glyphs as images. OpenType SVG done: a glyph's SVG document (its element `glyph<gid>`) is read by the SVG importer into vector art in font units, `currentColor` in the run's fill (and the legacy `context-fill` and `context-stroke` in its fill and stroke), a `viewBox` mapped onto the em square, and then drawn, exported and outlined as COLR glyphs are; in PDF real text such glyphs are text that draws nothing, with their art drawn where they sit. Images in glyph documents are in the art, holding their pixels until a document takes them in; text, scripts and embedded content are left out, as the table rules them out. Not CPAL palette variables. Evidence: `tests_svg_glyphs`, `svg/tests/it/svg_glyphs.rs`, `tests_colourfonts::svg_glyph_images_draw_and_outline`, `tests_colourfonts::create_outlines_turns_svg_glyphs_into_groups`, `tests_colourfonts::pdf_real_text_draws_svg_glyphs_as_art_and_keeps_their_characters`, `tests_colourfonts::expand_appearance_keeps_the_colour_glyphs_of_type_with_effects`, `render::tests_colrglyphs::colour_glyphs_go_with_type_that_a_geometry_effect_reshapes`, `tests_bitmaps`, `tests_colr`, `render::tests_bitmapglyphs`, `render::tests_colrglyphs`, `pdf::tests::colour_bitmap_glyphs_export_as_images`, `pdf::tests::colr_glyphs_export_as_vector_art`, `svg/tests/it/colour_glyphs.rs`, `eps` `colour_bitmap_glyphs_*` and `colr_glyphs_*`, `tests_pathops::create_outlines_turns_bitmap_glyphs_into_images`, `tests_pathops::create_outlines_turns_colr_glyphs_into_layer_groups` |
|
||||
| Every-line and Single-line composers | done | tuned by eye, never compared side by side |
|
||||
| Optical Margin Alignment | done | Type › Optical Margin Alignment, `type.opticalMarginAlignment`: per type object, with one setting for all the frames of a thread, in point and area type of left-to-right paragraphs, and at the brackets of type on a path whose first paragraph runs left to right; right-to-left paragraphs of point and area type, and type on a path whose first paragraph runs right to left, do not hang |
|
||||
| System Layout | done | Character panel menu › System Layout, `type.systemLayout`: the whole document, off by default and for documents from before it, saved only when on. On, every text object advances by whole points (at least one when a character takes space). New point, area and path type, threaded frames, placed text, graph and dimension labels and pasted plain text inherit it. Evidence: `tests::system_layout_rounds_positive_advances_to_whole_points`, `typecmd::area_tests::system_layout_sets_the_document_and_every_text_object`, `panels::character::tests::system_layout_menu_item_toggles_the_document` |
|
||||
|
||||
+39
-21
@@ -32,7 +32,8 @@ commands:
|
||||
pdf-deps verify immutable shared PDF writer and patched Krilla sources
|
||||
layers enforce the crate dependency layering (plan/architecture.md §3)
|
||||
wasm cargo check --target wasm32-unknown-unknown for the wasm-safe crates and the web app
|
||||
ci fmt --check, clippy -D warnings, test, no-bundled-fonts, pdf-deps, assets, brands, cursors (warnings only), layers, wasm (stops at first failure)
|
||||
test the workspace tests (cargo nextest when installed, else cargo test), then the doctests
|
||||
ci fmt --check, clippy -D warnings, test, doctests, no-bundled-fonts, pdf-deps, assets, brands, cursors (warnings only), layers, wasm (stops at first failure)
|
||||
corpus [--download] [--affinity]
|
||||
show where test corpora live; --download fetches PngSuite into corpus/pngsuite,
|
||||
--affinity the pinned public Affinity documents into corpus/affinity
|
||||
@@ -55,6 +56,7 @@ fn main() -> ExitCode {
|
||||
Some("layers") => cmd_layers(),
|
||||
Some("pdf-deps") => pdf_deps::run(&root()),
|
||||
Some("wasm") => cmd_wasm(),
|
||||
Some("test") => cmd_test().and_then(|()| cmd_doctests()),
|
||||
Some("ci") => cmd_ci(),
|
||||
Some("corpus") if rest.contains(&"--affinity") => affinity_corpus::fetch(),
|
||||
Some("corpus") => cmd_corpus(rest.contains(&"--download")),
|
||||
@@ -204,21 +206,43 @@ fn wasm_set() -> Result<Vec<String>, String> {
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// One `cargo check` over the whole wasm set: the crates share one build, and `--keep-going`
|
||||
/// reports every crate that fails rather than only the first.
|
||||
fn cmd_wasm() -> Result<(), String> {
|
||||
let set = wasm_set()?;
|
||||
let mut results = Vec::new();
|
||||
let mut c = cargo();
|
||||
c.args(["check", "--target", "wasm32-unknown-unknown", "--keep-going"]);
|
||||
for pkg in &set {
|
||||
let mut c = cargo();
|
||||
c.args(["check", "--target", "wasm32-unknown-unknown", "-p", pkg]);
|
||||
let ok = run(c, &format!("cargo check --target wasm32-unknown-unknown -p {pkg}")).is_ok();
|
||||
results.push((pkg.clone(), ok));
|
||||
c.args(["-p", pkg]);
|
||||
}
|
||||
println!("\nwasm32-unknown-unknown check:");
|
||||
for (p, ok) in &results {
|
||||
println!(" {:<6} {p}", if *ok { "ok" } else { "FAIL" });
|
||||
run(c, &format!("cargo check --target wasm32-unknown-unknown --keep-going ({} crates: {})", set.len(), set.join(", ")))
|
||||
}
|
||||
|
||||
/// Whether `cargo nextest` is installed.
|
||||
fn has_nextest() -> bool {
|
||||
cargo().args(["nextest", "--version"]).output().is_ok_and(|o| o.status.success())
|
||||
}
|
||||
|
||||
/// The workspace's unit and integration tests, without examples (clippy still checks those).
|
||||
/// nextest runs each test in its own process, in parallel across all test binaries.
|
||||
fn cmd_test() -> Result<(), String> {
|
||||
const TARGETS: [&str; 3] = ["--lib", "--bins", "--tests"];
|
||||
let mut c = cargo();
|
||||
if has_nextest() {
|
||||
c.args(["nextest", "run", "--workspace"]).args(TARGETS);
|
||||
run(c, "cargo nextest run --workspace --lib --bins --tests")
|
||||
} else {
|
||||
eprintln!("note: cargo-nextest is not installed; running cargo test (cargo install cargo-nextest for faster runs)");
|
||||
c.args(["test", "--workspace"]).args(TARGETS);
|
||||
run(c, "cargo test --workspace --lib --bins --tests")
|
||||
}
|
||||
let failed = results.iter().filter(|r| !r.1).count();
|
||||
if failed == 0 { Ok(()) } else { Err(format!("{failed} crate(s) failed the wasm check")) }
|
||||
}
|
||||
|
||||
/// The doctests, which nextest doesn't run.
|
||||
fn cmd_doctests() -> Result<(), String> {
|
||||
let mut c = cargo();
|
||||
c.args(["test", "--workspace", "--doc"]);
|
||||
run(c, "cargo test --workspace --doc")
|
||||
}
|
||||
|
||||
/// `cargo xtask ci`: each step in turn, stopping at the first that fails; the summary names the
|
||||
@@ -238,18 +262,12 @@ fn cmd_ci() -> Result<(), String> {
|
||||
"clippy",
|
||||
Box::new(|| {
|
||||
let mut c = cargo();
|
||||
c.args(["clippy", "--workspace", "--all-targets", "--", "-D", "warnings"]);
|
||||
run(c, "cargo clippy --workspace --all-targets -- -D warnings")
|
||||
}),
|
||||
),
|
||||
(
|
||||
"test",
|
||||
Box::new(|| {
|
||||
let mut c = cargo();
|
||||
c.args(["test", "--workspace"]);
|
||||
run(c, "cargo test --workspace")
|
||||
c.args(["clippy", "--workspace", "--all-targets", "--keep-going", "--", "-D", "warnings"]);
|
||||
run(c, "cargo clippy --workspace --all-targets --keep-going -- -D warnings")
|
||||
}),
|
||||
),
|
||||
("test", Box::new(cmd_test)),
|
||||
("doctests", Box::new(cmd_doctests)),
|
||||
(
|
||||
"no-bundled-fonts",
|
||||
Box::new(|| {
|
||||
|
||||
Reference in New Issue
Block a user