collective

Browse / github.com/storytold/vectorcraft / patch

feat(io): export DWG (2000–2018) through the DXF writer and acadrust

feat(io): export DWG (2000–2018) through the DXF writer and acadrust ## What and why VectorCraft listed DWG as unavailable because "no openly licensed writer exists". One does now: the [acadrust](https://github.com/hakanaktt/acadrust) crate (MPL-2.0), which CADCraft already uses in `crates/dwg`. This PR adds DWG export the same way: VectorCraft writes the drawing as DXF (`vectorcraft_cad::export`, so every DXF option applies), and acadrust converts those bytes to DWG. - New crate `crates/dwg` (`vectorcraft-dwg`, layer `Standalone`): `from_dxf(&[u8]) -> Result<Dwg, String>`. acadrust is a native-only dependency (`cfg(not(wasm32))`), used unmodified;

Status
open
Score
0.00
Adopters
0 other operators
Author
Alex-BB44 (imported from pull request #3068, open)
Base
bc1ae0825264c5ca4b27bd718d6e04f2121b8bb3
Size
+921 −200 in 52 files
Review
pending
Shared
2026-10-28
Id
473c6f050ab6587b837e316d

How it went

No other operator has reported using it yet.

Diff

raw · Cargo.lock Cargo.toml NOTICE crates/dwg/Cargo.toml crates/dwg/src/lib.rs crates/dwg/src/tests.rs crates/engine/Cargo.toml crates/engine/src/cmd/fileio/dxf.rs crates/engine/src/cmd/fileio/encode.rs crates/engine/src/cmd/fileio/load.rs crates/engine/src/cmd/fileio/mod.rs crates/engine/src/cmd/fileio/screens.rs crates/engine/src/cmd/fileio/tests.rs crates/engine/src/cmd/fileio/tests_dxf.rs crates/engine/src/cmd/place/mod.rs crates/mcp/src/tests.rs crates/mcp/src/tests_fileio.rs crates/mcp/src/tools.rs crates/ui-egui/src/dialogs/dxf_options.rs crates/ui-egui/src/dialogs/export_as.rs crates/ui-egui/src/dialogs/tests_dxf.rs crates/ui-egui/src/i18n/bg.tsv crates/ui-egui/src/i18n/cs.tsv crates/ui-egui/src/i18n/da.tsv crates/ui-egui/src/i18n/de.tsv crates/ui-egui/src/i18n/el.tsv crates/ui-egui/src/i18n/es.tsv crates/ui-egui/src/i18n/fi.tsv crates/ui-egui/src/i18n/fr.tsv crates/ui-egui/src/i18n/hu.tsv crates/ui-egui/src/i18n/id.tsv crates/ui-egui/src/i18n/it.tsv crates/ui-egui/src/i18n/ja.tsv crates/ui-egui/src/i18n/ka.tsv crates/ui-egui/src/i18n/ko.tsv crates/ui-egui/src/i18n/nl.tsv crates/ui-egui/src/i18n/pl.tsv crates/ui-egui/src/i18n/pt-br.tsv crates/ui-egui/src/i18n/ru.tsv crates/ui-egui/src/i18n/sv.tsv crates/ui-egui/src/i18n/uk.tsv crates/ui-egui/src/i18n/vi.tsv crates/ui-egui/src/i18n/zh-hans.tsv crates/ui-egui/src/i18n/zh-hant.tsv crates/ui-egui/src/io.rs crates/ui-egui/src/menus.rs docs/control-protocol.md docs/file-format-parity.md docs/mcp.md docs/roadmap.md docs/target-app-parity.md xtask/src/layers.rs

diff --git a/Cargo.lock b/Cargo.lock--- a/Cargo.lock+++ b/Cargo.lock@@ -2,6 +2,30 @@ # It is not intended for manual editing. version = 4 +[[package]]+name = "acadrust"+version = "0.6.4"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "94862189cab6e282ed0433a801a216cd1a712953813284a48fa81c67223e38f6"+dependencies = [+ "anyhow",+ "bitflags 2.13.2",+ "byteorder",+ "encoding_rs",+ "flate2",+ "foldhash 0.2.0",+ "indexmap",+ "itoa",+ "memmap2",+ "nom 7.1.3",+ "oem_cp",+ "once_cell",+ "rayon",+ "ryu",+ "thiserror 2.0.21",+ "web-time",+]+ [[package]] name = "accesskit" version = "0.24.1"@@ -20,7 +44,7 @@ dependencies = [  "accesskit",  "accesskit_consumer 0.36.0",  "atspi-common",- "phf",+ "phf 0.13.1",  "serde",  "zvariant", ]@@ -578,6 +602,12 @@ dependencies = [  "syn 3.0.6", ] +[[package]]+name = "byteorder"+version = "1.5.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"+ [[package]] name = "byteorder-lite" version = "0.1.0"@@ -788,6 +818,12 @@ dependencies = [  "libc", ] +[[package]]+name = "core_detect"+version = "1.0.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"+ [[package]] name = "core_maths" version = "0.1.1"@@ -1164,6 +1200,20 @@ dependencies = [  "bytemuck", ] +[[package]]+name = "encoding_rs"+version = "0.8.42"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679"+dependencies = [+ "cfg-if",+ "core_detect",+ "multiversion_no_op",+ "rustversion",+ "scopeguard",+ "simdutf8",+]+ [[package]] name = "endi" version = "1.1.1"@@ -1789,7 +1839,7 @@ dependencies = [  "hayro-syntax",  "kurbo 0.13.1",  "moxcms",- "phf",+ "phf 0.13.1",  "rustc-hash 2.1.3",  "siphasher",  "skrifa 0.47.0",@@ -2410,6 +2460,12 @@ dependencies = [  "autocfg", ] +[[package]]+name = "minimal-lexical"+version = "0.2.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"+ [[package]] name = "miniz_oxide" version = "0.8.9"@@ -2458,6 +2514,12 @@ dependencies = [  "windows-sys 0.61.2", ] +[[package]]+name = "multiversion_no_op"+version = "1.0.0"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"+ [[package]] name = "naga" version = "30.0.1"@@ -2532,6 +2594,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]]+name = "nom"+version = "7.1.3"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"+dependencies = [+ "memchr",+ "minimal-lexical",+]+ [[package]] name = "nom" version = "8.0.0"@@ -2893,6 +2965,15 @@ dependencies = [  "objc2-foundation 0.2.2", ] +[[package]]+name = "oem_cp"+version = "2.1.2"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "8c7959d3349f484aec36462f98226983578bf7d33f9b034c32967489c7000e1f"+dependencies = [+ "phf 0.11.3",+]+ [[package]] name = "once_cell" version = "1.21.4"@@ -3028,6 +3109,15 @@ dependencies = [  "indexmap", ] +[[package]]+name = "phf"+version = "0.11.3"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"+dependencies = [+ "phf_shared 0.11.3",+]+ [[package]] name = "phf" version = "0.13.1"@@ -3035,7 +3125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [  "phf_macros",- "phf_shared",+ "phf_shared 0.13.1",  "serde", ] @@ -3046,7 +3136,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" dependencies = [  "fastrand",- "phf_shared",+ "phf_shared 0.13.1", ]  [[package]]@@ -3056,12 +3146,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [  "phf_generator",- "phf_shared",+ "phf_shared 0.13.1",  "proc-macro2",  "quote",  "syn 2.0.119", ] +[[package]]+name = "phf_shared"+version = "0.11.3"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"+dependencies = [+ "siphasher",+]+ [[package]] name = "phf_shared" version = "0.13.1"@@ -4322,7 +4421,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" dependencies = [  "memchr",- "nom",+ "nom 8.0.0",  "petgraph", ] @@ -4649,6 +4748,13 @@ dependencies = [  "zune-jpeg", ] +[[package]]+name = "vectorcraft-dwg"+version = "0.14.0"+dependencies = [+ "acadrust",+]+ [[package]] name = "vectorcraft-effects" version = "0.14.0"@@ -4686,6 +4792,7 @@ dependencies = [  "vectorcraft-cad",  "vectorcraft-color",  "vectorcraft-doc",+ "vectorcraft-dwg",  "vectorcraft-eps",  "vectorcraft-format",  "vectorcraft-geom",diff --git a/Cargo.toml b/Cargo.toml--- a/Cargo.toml+++ b/Cargo.toml@@ -42,6 +42,7 @@ vectorcraft-metafile = { path = "crates/metafile" } vectorcraft-plugins = { path = "crates/plugins" } vectorcraft-affinity = { path = "crates/affinity" } vectorcraft-scriptcheck = { path = "crates/scriptcheck" }+vectorcraft-dwg = { path = "crates/dwg" }  # foundation kurbo = { version = "0.13", features = ["serde"] }diff --git a/NOTICE b/NOTICE--- a/NOTICE+++ b/NOTICE@@ -24,6 +24,11 @@ Rust dependencies (vello_cpu, kurbo, peniko, skrifa, harfrust, usvg, linesweeper fetched through crates.io under their own permissive licenses (mostly MIT and/or Apache-2.0; also BSD, ISC, Zlib, BSL-1.0 and Unicode-3.0). +One dependency is under a weak-copyleft licence: the acadrust crate (DWG writing,+https://github.com/hakanaktt/acadrust) is licensed under the Mozilla Public License 2.0 and is used+unmodified as a separate library (crates/dwg), as CADCraft does. Its source is available from+crates.io and the repository above.+ JPEG export uses the jpeg-encoder crate (MIT OR Apache-2.0, and the IJG License): this software is based in part on the work of the Independent JPEG Group. diff --git a/crates/dwg/Cargo.toml b/crates/dwg/Cargo.tomlnew file mode 100644--- /dev/null+++ b/crates/dwg/Cargo.toml@@ -0,0 +1,14 @@+[package]+name = "vectorcraft-dwg"+description = "DWG export through a DXF bridge: VectorCraft's DXF bytes written as DWG by the acadrust crate (MPL-2.0, used unmodified)"+version.workspace = true+edition.workspace = true+license.workspace = true+rust-version.workspace = true++[lints]+workspace = true++[target.'cfg(not(target_arch = "wasm32"))'.dependencies]+# MPL-2.0: used as an unmodified dependency only, as CADCraft's crates/dwg does (see NOTICE).+acadrust = "0.6.4"diff --git a/crates/dwg/src/lib.rs b/crates/dwg/src/lib.rsnew file mode 100644--- /dev/null+++ b/crates/dwg/src/lib.rs@@ -0,0 +1,109 @@+//! DWG export through a DXF bridge.+//!+//! VectorCraft writes a DWG file by writing the drawing as DXF (`vectorcraft-cad`, every DXF option+//! applies) and converting those bytes with the `acadrust` crate (MPL-2.0, used unmodified as a+//! dependency), the bridge CADCraft's `crates/dwg` uses. Keeping the dependency behind this+//! byte-level API isolates it: nothing else in VectorCraft depends on its types. Native targets+//! only (acadrust memory-maps files); the web build answers an error.+#![forbid(unsafe_code)]+#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]++/// The DWG versions written: the DXF version id that picks each (`vectorcraft_cad::DxfVersion`+/// ids) and its `$ACADVER` code. DWG starts at 2000: acadrust doesn't write older DWG.+pub const VERSIONS: &[(&str, &str)] =+    &[("2000", "AC1015"), ("2004", "AC1018"), ("2007", "AC1021"), ("2010", "AC1024"), ("2013", "AC1027"), ("2018", "AC1032")];++/// True when the bytes start like a DWG file (`AC10xx`).+pub fn is_dwg(bytes: &[u8]) -> bool {+    bytes.len() >= 6 && bytes.starts_with(b"AC10") && bytes.get(4..6).is_some_and(|v| v.iter().all(u8::is_ascii_digit))+}++/// A DWG file and what the conversion left out.+#[derive(Clone, Debug, Default, PartialEq, Eq)]+pub struct Dwg {+    pub bytes: Vec<u8>,+    /// Entities of the DXF the DWG writer can't write (types acadrust doesn't know), left out.+    pub left_out: usize,+    /// Non-graphical objects of the DXF left out for the same reason (dictionaries, records).+    pub left_out_objects: usize,+}++#[cfg(not(target_arch = "wasm32"))]+mod native {+    use std::io::Cursor;+    use std::panic::AssertUnwindSafe;++    use super::Dwg;++    /// Oldest DWG version acadrust writes.+    const MIN: acadrust::DxfVersion = acadrust::DxfVersion::AC1015;++    /// Convert DXF bytes into a DWG file of the version the DXF declares (`$ACADVER`), 2000 or+    /// later.+    pub fn from_dxf(dxf: &[u8]) -> Result<Dwg, String> {+        // acadrust is third-party code: a panic anywhere in it (reading, the clean-up, writing) is+        // an error here, never a crash.+        std::panic::catch_unwind(AssertUnwindSafe(|| convert(dxf))).map_err(|_| "the DXF→DWG conversion failed".to_string())?+    }++    fn convert(dxf: &[u8]) -> Result<Dwg, String> {+        let mut doc = acadrust::DxfReader::from_reader(Cursor::new(dxf.to_vec())).and_then(|r| r.read()).map_err(|e| format!("DXF→DWG: {e}"))?;+        if doc.version < MIN {+            return Err(format!("DWG is written as version 2000 or later, not {:?}", doc.version));+        }+        let (left_out, left_out_objects) = drop_unwritable(&mut doc);+        let bytes = acadrust::DwgWriter::write_to_vec(&doc).map_err(|e| format!("DWG: {e}"))?;+        Ok(Dwg { bytes, left_out, left_out_objects })+    }++    /// Leave out what acadrust can't write: entities and objects it read from the DXF without+    /// knowing their type carry no DWG data to write (read from DXF, `raw_dwg_data` is always+    /// `None`), and acadrust 0.6.4 refuses the whole file for one of them. The limit is the+    /// writer's, not DWG's. `entities()` is acadrust's flat list of every entity, those inside+    /// block definitions included (test `unknown_entities_are_left_out_in_blocks_too`). Returns how+    /// many (entities, objects). References to a removed object+    /// (a dictionary entry) are not followed; VectorCraft's DXF has no objects acadrust doesn't+    /// know, so this guards a future DXF feature rather than a case that occurs today.+    fn drop_unwritable(doc: &mut acadrust::CadDocument) -> (usize, usize) {+        let version = doc.version;+        let entities: Vec<acadrust::Handle> = doc+            .entities()+            .filter_map(|e| match e {+                acadrust::entities::EntityType::Unknown(u) if u.raw_dwg_data.is_none() || u.dwg_source_version.is_some_and(|v| v != version) => {+                    Some(u.common.handle)+                }+                _ => None,+            })+            .collect();+        for h in &entities {+            doc.remove_entity(*h);+        }+        let objects: Vec<acadrust::Handle> = doc+            .objects+            .iter()+            .filter_map(|(h, o)| match o {+                acadrust::objects::ObjectType::Unknown { raw_dwg_data, raw_dwg_version, .. }+                    if raw_dwg_data.is_none() || raw_dwg_version.is_some_and(|v| v != version) =>+                {+                    Some(*h)+                }+                _ => None,+            })+            .collect();+        for h in &objects {+            doc.objects.remove(h);+        }+        (entities.len(), objects.len())+    }+}++#[cfg(not(target_arch = "wasm32"))]+pub use native::from_dxf;++#[cfg(target_arch = "wasm32")]+pub fn from_dxf(_dxf: &[u8]) -> Result<Dwg, String> {+    Err("DWG can't be written in the web app: export DXF, or DWG from the desktop app".into())+}++#[cfg(all(test, not(target_arch = "wasm32")))]+mod tests;diff --git a/crates/dwg/src/tests.rs b/crates/dwg/src/tests.rsnew file mode 100644--- /dev/null+++ b/crates/dwg/src/tests.rs@@ -0,0 +1,139 @@+// Test helpers outside `#[test]` functions unwrap too (clippy.toml allows it only inside them).+#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]++use std::io::Cursor;++use super::*;++/// A minimal ASCII DXF of `acadver` holding one LINE and one CIRCLE on layer `Art`.+fn dxf(acadver: &str) -> Vec<u8> {+    let pairs: &[(&str, &str)] = &[+        ("0", "SECTION"),+        ("2", "HEADER"),+        ("9", "$ACADVER"),+        ("1", acadver),+        ("0", "ENDSEC"),+        ("0", "SECTION"),+        ("2", "ENTITIES"),+        ("0", "LINE"),+        ("8", "Art"),+        ("10", "0.0"),+        ("20", "0.0"),+        ("30", "0.0"),+        ("11", "100.0"),+        ("21", "50.0"),+        ("31", "0.0"),+        ("0", "CIRCLE"),+        ("8", "Art"),+        ("10", "50.0"),+        ("20", "25.0"),+        ("30", "0.0"),+        ("40", "10.0"),+        ("0", "ENDSEC"),+        ("0", "EOF"),+    ];+    pairs.iter().flat_map(|(c, v)| format!("{c:>3}\r\n{v}\r\n").into_bytes()).collect()+}++/// The entity type names of a DWG file, as acadrust reads it back.+fn read_back(dwg: &[u8]) -> (acadrust::DxfVersion, Vec<String>) {+    let doc = acadrust::DwgReader::from_stream(Cursor::new(dwg.to_vec())).read().unwrap();+    let mut kinds: Vec<String> = doc.entities().map(|e| format!("{e:?}").split(['(', ' ', '{']).next().unwrap_or_default().to_string()).collect();+    kinds.sort();+    (doc.version, kinds)+}++#[test]+fn writes_the_version_the_dxf_declares() {+    for (id, acadver) in VERSIONS {+        let out = from_dxf(&dxf(acadver)).unwrap_or_else(|e| panic!("{id}: {e}"));+        assert!(is_dwg(&out.bytes), "{id}");+        assert_eq!(out.bytes.get(0..6), Some(acadver.as_bytes()), "{id}");+        assert_eq!((out.left_out, out.left_out_objects), (0, 0), "{id}");+        let (version, kinds) = read_back(&out.bytes);+        assert_eq!(format!("{version:?}"), *acadver, "{id}");+        assert_eq!(kinds, ["Circle", "Line"], "{id}");+    }+}++/// [`dxf`] of AC1032 with one entity of a type no DXF reader knows in ENTITIES and one inside a+/// BLOCK.+fn dxf_with_unknown_entities() -> Vec<u8> {+    let pairs: &[(&str, &str)] = &[+        ("0", "SECTION"),+        ("2", "HEADER"),+        ("9", "$ACADVER"),+        ("1", "AC1032"),+        ("0", "ENDSEC"),+        ("0", "SECTION"),+        ("2", "BLOCKS"),+        ("0", "BLOCK"),+        ("8", "0"),+        ("2", "Mark"),+        ("70", "0"),+        ("10", "0.0"),+        ("20", "0.0"),+        ("30", "0.0"),+        ("3", "Mark"),+        ("0", "ZZ_MADE_UP"),+        ("8", "0"),+        ("10", "1.0"),+        ("20", "1.0"),+        ("0", "LINE"),+        ("8", "0"),+        ("10", "0.0"),+        ("20", "0.0"),+        ("11", "5.0"),+        ("21", "5.0"),+        ("0", "ENDBLK"),+        ("8", "0"),+        ("0", "ENDSEC"),+        ("0", "SECTION"),+        ("2", "ENTITIES"),+        ("0", "ZZ_MADE_UP"),+        ("8", "Art"),+        ("10", "2.0"),+        ("20", "2.0"),+        ("0", "LINE"),+        ("8", "Art"),+        ("10", "0.0"),+        ("20", "0.0"),+        ("11", "100.0"),+        ("21", "50.0"),+        ("0", "ENDSEC"),+        ("0", "EOF"),+    ];+    pairs.iter().flat_map(|(c, v)| format!("{c:>3}\r\n{v}\r\n").into_bytes()).collect()+}++#[test]+fn unknown_entities_are_left_out_in_blocks_too() {+    let out = from_dxf(&dxf_with_unknown_entities()).unwrap();+    assert_eq!(out.left_out, 2, "the one in ENTITIES and the one in the block");+    let (_, kinds) = read_back(&out.bytes);+    assert!(kinds.iter().filter(|k| *k == "Line").count() >= 1, "{kinds:?}");+    assert!(!kinds.iter().any(|k| k.contains("Unknown")), "{kinds:?}");+}++#[test]+fn older_dxf_versions_are_refused() {+    for acadver in ["AC1009", "AC1012", "AC1014"] {+        let e = from_dxf(&dxf(acadver)).unwrap_err();+        assert!(e.contains("2000 or later"), "{acadver}: {e}");+    }+}++#[test]+fn garbage_is_an_error_not_a_crash() {+    assert!(from_dxf(b"").is_err());+    assert!(from_dxf(b"this is not a DXF file").is_err());+    assert!(from_dxf(&dxf("AC1032")[..40]).is_err());+}++#[test]+fn detects_magic() {+    assert!(is_dwg(b"AC1032\0\0\0\0"));+    assert!(!is_dwg(b"  0\r\nSECTION"));+    assert!(!is_dwg(b"AC10"));+    assert!(is_dwg(b"AC1015"), "the six-byte signature alone");+}diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml--- a/crates/engine/Cargo.toml+++ b/crates/engine/Cargo.toml@@ -30,6 +30,7 @@ vectorcraft-svg = { workspace = true } vectorcraft-format = { workspace = true } vectorcraft-pdf = { workspace = true } vectorcraft-cad = { workspace = true }+vectorcraft-dwg = { workspace = true } vectorcraft-eps = { workspace = true } vectorcraft-metafile = { workspace = true } vectorcraft-plugins = { workspace = true }diff --git a/crates/engine/src/cmd/fileio/dxf.rs b/crates/engine/src/cmd/fileio/dxf.rs--- a/crates/engine/src/cmd/fileio/dxf.rs+++ b/crates/engine/src/cmd/fileio/dxf.rs@@ -1,6 +1,7 @@-//! DXF export for every DXF path (`document.export {format: dxf}`, `document.exportDxf`, Export As-//! → DXF Options): the options parsed from the params, one drawing per artboard with Use-//! Artboards, and the formats that can't be written (DWG, PICT) with what to use instead.+//! DXF and DWG export for every path (`document.export {format: dxf|dwg}`, `document.exportDxf`,+//! `document.exportDwg`, Export As → DXF/DWG Options): the options parsed from the params, one+//! drawing per artboard with Use Artboards, and the formats that can't be written (PICT) with what+//! to use instead. DWG is the DXF drawing converted by `vectorcraft-dwg` (2000 and later).  use std::sync::Arc; @@ -16,15 +17,26 @@ use super::{ArtboardPick, Encoded, FormatOption}; const C: &str = "document.export";  pub fn specs() -> Vec<CommandSpec> {-    vec![cmd!(-        "document.exportDxf",-        "Export DXF",-        [],-        None,-        "{path?, version?: R12|R13|R14|2000|2004|2007|2010|2013|2018 (default), unit?: mm (default; any unit name: pt, in, cm…), scale?: 1 (drawing units per unit), scaleLineweights?: false, colors?: 8|16|256|true (default; true colour needs 2004+, else 256), rasterFormat?: png (default)|jpeg (placed images, written next to the file), preserve?: appearance (default: type as outlines; strokes a CAD line can't draw as filled outlines; brush art)|editability (type as text; every stroke a line with its lineweight and dashes), alterPaths?: false (every stroke as its filled outline), outlineText?: false, selectedOnly?: false (the selected objects, in their layers), artboard?: 0 (the origin is its bottom-left corner), useArtboards?: true (one drawing per chosen artboard, artboards?/range?, holding the art over it: {stem}-{artboard}.dxf) | false (the origin at the art's bounds)} → {path, bytes, warnings, files?, linked?: [image files]}; no path → {dataBase64, …, linked?: [{name, dataBase64}]}. Layers become DXF layers (hidden: off; locked; non-printing: not plotted); paths become polylines and splines, fills their outlines (which laser and cutter software reads) and solid hatches. Same as document.export {format: dxf}",-        has_doc,-        export_dxf-    )]+    vec![+        cmd!(+            "document.exportDxf",+            "Export DXF",+            [],+            None,+            "{path?, version?: R12|R13|R14|2000|2004|2007|2010|2013|2018 (default), unit?: mm (default; any unit name: pt, in, cm…), scale?: 1 (drawing units per unit), scaleLineweights?: false, colors?: 8|16|256|true (default; true colour needs 2004+, else 256), rasterFormat?: png (default)|jpeg (placed images, written next to the file), preserve?: appearance (default: type as outlines; strokes a CAD line can't draw as filled outlines; brush art)|editability (type as text; every stroke a line with its lineweight and dashes), alterPaths?: false (every stroke as its filled outline), outlineText?: false, selectedOnly?: false (the selected objects, in their layers), artboard?: 0 (the origin is its bottom-left corner), useArtboards?: true (one drawing per chosen artboard, artboards?/range?, holding the art over it: {stem}-{artboard}.dxf) | false (the origin at the art's bounds)} → {path, bytes, warnings, files?, linked?: [image files]}; no path → {dataBase64, …, linked?: [{name, dataBase64}]}. Layers become DXF layers (hidden: off; locked; non-printing: not plotted); paths become polylines and splines, fills their outlines (which laser and cutter software reads) and solid hatches. Same as document.export {format: dxf}",+            has_doc,+            export_dxf+        ),+        cmd!(+            "document.exportDwg",+            "Export DWG",+            [],+            None,+            "{path?, version?: 2000|2004|2007|2010|2013|2018 (default), …the other options of document.exportDxf} → as document.exportDxf. The drawing document.exportDxf writes, as a DWG file (desktop only: the web app answers an error); objects DWG can't hold in that version are left out, with a warning. Same as document.export {format: dwg}",+            has_doc,+            export_dwg+        ),+    ] }  /// The DXF options `document.formats` lists.@@ -34,42 +46,76 @@ pub(super) const OPTIONS: &[FormatOption] = &[     super::RANGE,     super::USE_ARTBOARDS,     FormatOption { name: "version", ty: "string", default: "\"2018\"", description: "R12, R13, R14, 2000, 2004, 2007, 2010, 2013 or 2018" },-    FormatOption {-        name: "unit",-        ty: "string",-        default: "\"mm\"",-        description: "the art unit that `scale` drawing units stand for (pt, in, mm, cm, m, ft…)",-    },-    FormatOption { name: "scale", ty: "number", default: "1", description: "drawing units per unit (1 mm = 1 unit)" },-    FormatOption { name: "scaleLineweights", ty: "boolean", default: "false", description: "lineweights grow with the scale" },-    FormatOption {-        name: "colors",-        ty: "string",-        default: "\"true\"",-        description: "8, 16 or 256 indexed colours, or true colour (DXF 2004 and later)",-    },-    FormatOption {-        name: "rasterFormat",-        ty: "string",-        default: "\"png\"",-        description: "png or jpeg: the files placed images are written to, next to the drawing",-    },-    FormatOption {-        name: "preserve",-        ty: "string",-        default: "\"appearance\"",-        description: "appearance (type as outlines, strokes a CAD line can't draw as filled outlines) or editability (type as text, strokes as lines)",-    },-    FormatOption {-        name: "alterPaths",-        ty: "boolean",-        default: "false",-        description: "Alter Paths for Appearance: every stroke as its filled outline",-    },-    FormatOption { name: "outlineText", ty: "boolean", default: "false", description: "type as glyph outlines (always with preserve: appearance)" },-    FormatOption { name: "selectedOnly", ty: "boolean", default: "false", description: "only the selected objects, in their layers" },+    UNIT,+    SCALE,+    SCALE_LINEWEIGHTS,+    COLORS,+    RASTER_FORMAT,+    PRESERVE,+    ALTER_PATHS,+    OUTLINE_TEXT,+    SELECTED_ONLY, ]; +/// The DWG options `document.formats` lists: the DXF ones, from version 2000.+pub(super) const DWG_OPTIONS: &[FormatOption] = &[+    super::ARTBOARD,+    super::ARTBOARDS,+    super::RANGE,+    super::USE_ARTBOARDS,+    FormatOption { name: "version", ty: "string", default: "\"2018\"", description: DWG_VERSIONS },+    UNIT,+    SCALE,+    SCALE_LINEWEIGHTS,+    COLORS,+    RASTER_FORMAT,+    PRESERVE,+    ALTER_PATHS,+    OUTLINE_TEXT,+    SELECTED_ONLY,+];++/// The versions DWG is written as (`vectorcraft_dwg::VERSIONS`).+const DWG_VERSIONS: &str = "2000, 2004, 2007, 2010, 2013 or 2018";++const UNIT: FormatOption = FormatOption {+    name: "unit",+    ty: "string",+    default: "\"mm\"",+    description: "the art unit that `scale` drawing units stand for (pt, in, mm, cm, m, ft…)",+};+const SCALE: FormatOption = FormatOption { name: "scale", ty: "number", default: "1", description: "drawing units per unit (1 mm = 1 unit)" };+const SCALE_LINEWEIGHTS: FormatOption =+    FormatOption { name: "scaleLineweights", ty: "boolean", default: "false", description: "lineweights grow with the scale" };+const COLORS: FormatOption = FormatOption {+    name: "colors",+    ty: "string",+    default: "\"true\"",+    description: "8, 16 or 256 indexed colours, or true colour (version 2004 and later)",+};+const RASTER_FORMAT: FormatOption = FormatOption {+    name: "rasterFormat",+    ty: "string",+    default: "\"png\"",+    description: "png or jpeg: the files placed images are written to, next to the drawing",+};+const PRESERVE: FormatOption = FormatOption {+    name: "preserve",+    ty: "string",+    default: "\"appearance\"",+    description: "appearance (type as outlines, strokes a CAD line can't draw as filled outlines) or editability (type as text, strokes as lines)",+};+const ALTER_PATHS: FormatOption = FormatOption {+    name: "alterPaths",+    ty: "boolean",+    default: "false",+    description: "Alter Paths for Appearance: every stroke as its filled outline",+};+const OUTLINE_TEXT: FormatOption =+    FormatOption { name: "outlineText", ty: "boolean", default: "false", description: "type as glyph outlines (always with preserve: appearance)" };+const SELECTED_ONLY: FormatOption =+    FormatOption { name: "selectedOnly", ty: "boolean", default: "false", description: "only the selected objects, in their layers" };+ /// A format that can be neither opened nor written, and what to use instead. #[derive(Clone, Copy, Debug)] pub struct Unsupported {@@ -80,21 +126,14 @@ pub struct Unsupported {     pub hint: &'static str, } -/// Formats people ask for that VectorCraft doesn't read or write. Append-only.-pub const UNSUPPORTED: &[Unsupported] = &[-    Unsupported {-        id: "dwg",-        label: "DWG",-        extensions: &["dwg"],-        hint: "DWG is not available (no openly licensed writer exists): export DXF instead, which CAD apps open",-    },-    Unsupported {-        id: "pict",-        label: "PICT",-        extensions: &["pict", "pct", "pic"],-        hint: "PICT is a retired format and is not supported: convert the file to PDF or PNG first",-    },-];+/// Formats people ask for that VectorCraft doesn't read or write. An entry leaves when VectorCraft+/// comes to write or read its format (DWG did).+pub const UNSUPPORTED: &[Unsupported] = &[Unsupported {+    id: "pict",+    label: "PICT",+    extensions: &["pict", "pct", "pic"],+    hint: "PICT is a retired format and is not supported: convert the file to PDF or PNG first",+}];  /// The unsupported format an id or extension (any case, leading dot allowed) names. pub fn unsupported(id_or_ext: &str) -> Option<&'static Unsupported> {@@ -133,36 +172,38 @@ pub(crate) fn text(v: &Value) -> String {     } } -/// `field` read by `read`, or an error listing what it may be.-fn choice<T>(field: &str, v: Option<String>, read: impl Fn(&str) -> Option<T>, default: T, allowed: &str) -> Result<T> {+/// `field` of a `kind` (DXF or DWG) export read by `read`, or an error listing what it may be.+fn choice<T>(kind: &str, field: &str, v: Option<String>, read: impl Fn(&str) -> Option<T>, default: T, allowed: &str) -> Result<T> {     match v {         None => Ok(default),-        Some(s) => read(&s).ok_or_else(|| bad(C, format!("DXF {field} `{s}`: {allowed}"))),+        Some(s) => read(&s).ok_or_else(|| bad(C, format!("{kind} {field} `{s}`: {allowed}"))),     } } -/// The DXF options in `p` (the region and crop are set per drawing) and the artboard choice.-fn parse(p: &Value) -> Result<(DxfOptions, ArtboardPick)> {-    let q: Params = if p.is_object() { Params::deserialize(p).map_err(|e| bad(C, format!("DXF options: {e}")))? } else { Params::default() };+/// The DXF options in `p` (the region and crop are set per drawing) and the artboard choice; with+/// `dwg`, the versions DWG is written as.+fn parse(p: &Value, dwg: bool) -> Result<(DxfOptions, ArtboardPick)> {+    let kind = if dwg { "DWG" } else { "DXF" };+    let q: Params = if p.is_object() { Params::deserialize(p).map_err(|e| bad(C, format!("{kind} options: {e}")))? } else { Params::default() };     let d = DxfOptions::default();     let scale = q.scale.unwrap_or(d.scale);     if !(scale.is_finite() && vectorcraft_cad::SCALE_RANGE.contains(&scale)) {-        return Err(bad(C, format!("DXF scale must be a positive number up to {}, not {scale}", vectorcraft_cad::SCALE_RANGE.end())));+        return Err(bad(C, format!("{kind} scale must be a positive number up to {}, not {scale}", vectorcraft_cad::SCALE_RANGE.end())));     }+    let version = if dwg {+        let read = |s: &str| DxfVersion::from_id(s).filter(|v| *v >= DxfVersion::R2000);+        choice(kind, "version", q.version.as_ref().map(text), read, d.version, DWG_VERSIONS)?+    } else {+        choice(kind, "version", q.version.as_ref().map(text), DxfVersion::from_id, d.version, "R12, R13, R14, 2000, 2004, 2007, 2010, 2013 or 2018")?+    };     let opts = DxfOptions {-        version: choice(-            "version",-            q.version.as_ref().map(text),-            DxfVersion::from_id,-            d.version,-            "R12, R13, R14, 2000, 2004, 2007, 2010, 2013 or 2018",-        )?,-        unit: choice("unit", q.unit, Unit::named, d.unit, "a unit such as mm, cm, in or pt")?,+        version,+        unit: choice(kind, "unit", q.unit, Unit::named, d.unit, "a unit such as mm, cm, in or pt")?,         scale,         scale_lineweights: q.scale_lineweights.unwrap_or(d.scale_lineweights),-        colors: choice("colors", q.colors.as_ref().map(text), ColorDepth::from_id, d.colors, "8, 16, 256 or true")?,-        raster: choice("rasterFormat", q.raster_format, RasterFormat::from_id, d.raster, "png or jpeg")?,-        preserve: choice("preserve", q.preserve, Preserve::from_id, d.preserve, "appearance or editability")?,+        colors: choice(kind, "colors", q.colors.as_ref().map(text), ColorDepth::from_id, d.colors, "8, 16, 256 or true")?,+        raster: choice(kind, "rasterFormat", q.raster_format, RasterFormat::from_id, d.raster, "png or jpeg")?,+        preserve: choice(kind, "preserve", q.preserve, Preserve::from_id, d.preserve, "appearance or editability")?,         alter_paths: q.alter_paths.unwrap_or(d.alter_paths),         outline_text: q.outline_text.unwrap_or(d.outline_text),         ..d@@ -170,16 +211,18 @@ fn parse(p: &Value) -> Result<(DxfOptions, ArtboardPick)> {     Ok((opts, q.boards)) } -/// Check the DXF options in `p` without exporting (dialogs keep themselves open on an error).+/// Check the DXF options in `p` without exporting (dialogs keep themselves open on an error);+/// `format: dwg` checks them as DWG options. pub fn check(p: &Value) -> std::result::Result<(), String> {-    parse(p).map(|_| ()).map_err(|e| e.to_string())+    let dwg = p.get("format").and_then(Value::as_str).and_then(super::format).is_some_and(|f| f.id == "dwg");+    parse(p, dwg).map(|_| ()).map_err(|e| e.to_string()) } -/// Encode `doc` as DXF: one drawing whose origin is the chosen artboard's bottom-left corner-/// (default the first), or with `use_artboards` one drawing per chosen artboard (default all)-/// holding the art over it.-pub(super) fn encode(doc: &Document, p: &Value, use_artboards: Option<bool>) -> Result<Encoded> {-    let (opts, pick) = parse(p)?;+/// Encode `doc` as DXF, or with `dwg` as DWG: one drawing whose origin is the chosen artboard's+/// bottom-left corner (default the first), or with `use_artboards` one drawing per chosen artboard+/// (default all) holding the art over it.+pub(super) fn encode(doc: &Document, p: &Value, use_artboards: Option<bool>, dwg: bool) -> Result<Encoded> {+    let (opts, pick) = parse(p, dwg)?;     let n = doc.artboards.len();     let rect = |b: usize| doc.artboards.get(b).map(|a| a.rect).ok_or_else(|| bad(C, format!("no artboard {}", b + 1)));     let drawings: Vec<(Option<usize>, Rect, bool)> = match use_artboards {@@ -192,16 +235,30 @@ pub(super) fn encode(doc: &Document, p: &Value, use_artboards: Option<bool>) ->             let b = match pick.resolve(n).map_err(|e| bad(C, e))?.as_deref() {                 None => 0,                 Some([b]) => *b,-                Some(_) => return Err(bad(C, "a DXF drawing has one origin: pass useArtboards: true for one drawing per artboard")),+                Some(_) => return Err(bad(C, "a DXF or DWG drawing has one origin: pass useArtboards: true for one drawing per artboard")),             };             vec![(Some(b), rect(b)?, false)]         }     };     let mut enc = Encoded::default();+    // What the DWG writer left out, over every drawing: (entities, other objects).+    let mut left_out = (0, 0);     for (board, region, crop) in drawings {         let out = vectorcraft_cad::export(doc, &DxfOptions { region, crop, ..opts.clone() }).map_err(|e| bad(C, e))?;-        enc.files.push((board, out.bytes));-        for w in out.warnings {+        let mut warnings = out.warnings;+        let bytes = if dwg {+            let d = vectorcraft_dwg::from_dxf(&out.bytes).map_err(|e| bad(C, e))?;+            left_out.0 += d.left_out;+            left_out.1 += d.left_out_objects;+            // The DXF writer's version warnings ("true colour needs DXF 2004 or later") hold for+            // the DWG made from it; only that phrase is reworded, never names a warning quotes.+            warnings = warnings.into_iter().map(|w| w.replacen("needs DXF ", "needs DWG ", 1)).collect();+            d.bytes+        } else {+            out.bytes+        };+        enc.files.push((board, bytes));+        for w in warnings {             if !enc.warnings.contains(&w) {                 enc.warnings.push(w);             }@@ -215,11 +272,36 @@ pub(super) fn encode(doc: &Document, p: &Value, use_artboards: Option<bool>) ->     if enc.files.is_empty() {         return Err(bad(C, "the document has no artboard"));     }+    if let Some(w) = left_out_warning(left_out, enc.files.len()) {+        enc.warnings.push(w);+    }     Ok(enc) } +/// The warning for what the DWG writer left out of `drawings` files: (entities, other objects, in+/// all of them), else none.+pub(super) fn left_out_warning((entities, objects): (usize, usize), drawings: usize) -> Option<String> {+    let n = |k: usize, one: &str, many: &str| format!("{k} {}", if k == 1 { one } else { many });+    let what = match (entities, objects) {+        (0, 0) => return None,+        (e, 0) => n(e, "object", "objects"),+        (0, o) => n(o, "non-graphical record", "non-graphical records"),+        (e, o) => format!("{} and {}", n(e, "object", "objects"), n(o, "non-graphical record", "non-graphical records")),+    };+    let of = if drawings > 1 { format!("these {drawings} drawings") } else { "this drawing".to_string() };+    Some(format!("the DWG writer can't write {what} of {of}: left out"))+}+ fn export_dxf(s: &mut Session, p: &Value) -> Result<Value> {+    export_as(s, p, "dxf")+}++fn export_dwg(s: &mut Session, p: &Value) -> Result<Value> {+    export_as(s, p, "dwg")+}++fn export_as(s: &mut Session, p: &Value, format: &str) -> Result<Value> {     let mut q = if p.is_object() { p.clone() } else { json!({}) };-    q["format"] = json!("dxf");+    q["format"] = json!(format);     super::export::export(s, &q) }diff --git a/crates/engine/src/cmd/fileio/encode.rs b/crates/engine/src/cmd/fileio/encode.rs--- a/crates/engine/src/cmd/fileio/encode.rs+++ b/crates/engine/src/cmd/fileio/encode.rs@@ -466,7 +466,7 @@ fn encode_files(doc: &Document, f: &Format, p: &Value) -> Result<Encoded> {     let doc = &*doc.without_edit_modes();     let n = doc.artboards.len();     // SVG reads its own `useArtboards` (an SVG option).-    let use_artboards = if f.raster || matches!(f.id, "pdf" | "ai" | "dxf" | "emf" | "wmf") { use_artboards(p)? } else { None };+    let use_artboards = if f.raster || matches!(f.id, "pdf" | "ai" | "dxf" | "dwg" | "emf" | "wmf") { use_artboards(p)? } else { None };     // `rect`: that area of the page, as if it were the only artboard (the document is not changed).     if let Some(rect) = page_rect(f, p)? {         let mut q = super::export::without_artboards(p);@@ -494,7 +494,7 @@ fn encode_files(doc: &Document, f: &Format, p: &Value) -> Result<Encoded> {         }         "txt" => super::text::encode(doc, p)?,         "svg" | "svgz" => return super::svg::encode(doc, p, f.id == "svgz").map_err(|e| bad(C, e)),-        "dxf" => return super::dxf::encode(doc, p, use_artboards),+        "dxf" | "dwg" => return super::dxf::encode(doc, p, use_artboards, f.id == "dwg"),         // EPS reads its own `useArtboards` (the art's bounds unless asked).         "eps" => return super::eps::encode(doc, p),         "emf" => return super::metafile::encode(doc, p, use_artboards, vectorcraft_metafile::Kind::Emf),diff --git a/crates/engine/src/cmd/fileio/load.rs b/crates/engine/src/cmd/fileio/load.rs--- a/crates/engine/src/cmd/fileio/load.rs+++ b/crates/engine/src/cmd/fileio/load.rs@@ -203,11 +203,28 @@ fn every_page(bytes: &[u8], opts: &LoadOptions) -> bool {     vectorcraft_pdf::info(bytes, opts.password.as_deref()).is_ok_and(|i| parse_range(range, i.pages.len()).is_ok_and(|p| p.len() == i.pages.len())) } +/// Why `name` with `bytes` can't be read as a DWG drawing, when it is one: by its content (any name,+/// even a readable one such as `.dxf` or `.png`; no readable format starts like a DWG) or by its+/// `.dwg` name. Opening and Place check it before picking a reader.+pub fn dwg_refusal(verb: &str, name: &str, bytes: &[u8]) -> Option<String> {+    let named = super::format_for_name(name).is_some_and(|f| f.id == "dwg");+    (vectorcraft_dwg::is_dwg(bytes) || named)+        .then(|| format!("can't {verb} `{}`: VectorCraft doesn't read DWG drawings: save it as DXF in the CAD app and {verb} that", file_name(name)))+}+ /// [`load`] with `document.open` options (the PDF pages, box and password; the DXF options). pub fn load_with(name: &str, bytes: &[u8], opts: &LoadOptions) -> Result<Loaded> {-    let format = detect(name, bytes).ok_or_else(|| match super::unsupported(&super::extension(name)) {-        Some(u) => err(format!("can't open `{}`: {}", file_name(name), u.hint)),-        None => err(format!("can't open `{name}`: not a format VectorCraft reads (see document.formats)")),+    if let Some(e) = dwg_refusal("open", name, bytes) {+        return Err(err(e));+    }+    let format = detect(name, bytes).ok_or_else(|| match (super::unsupported(&super::extension(name)), super::format_for_name(name)) {+        (Some(u), _) => err(format!("can't open `{}`: {}", file_name(name), u.hint)),+        // Formats that are only written, such as plain text. (`detect` falls back on a readable+        // extension itself, so a readable format never gets here; the guard keeps it so.)+        (None, Some(f)) if f.write && !f.read => {+            err(format!("can't open `{}`: VectorCraft writes {} files but doesn't read them (see document.formats)", file_name(name), f.label))+        }+        _ => err(format!("can't open `{name}`: not a format VectorCraft reads (see document.formats)")),     })?;     let title = file_name(name);     let mut converted = false;diff --git a/crates/engine/src/cmd/fileio/mod.rs b/crates/engine/src/cmd/fileio/mod.rs--- a/crates/engine/src/cmd/fileio/mod.rs+++ b/crates/engine/src/cmd/fileio/mod.rs@@ -13,7 +13,7 @@ //!   per-format options (`file.formatOptions`); [`save_with`] is the one save path of every frontend. //! - `pdf`: PDF settings and presets for every PDF export, `document.exportPdf`. //! - `pdfimport`: the PDF pages, box and password `document.open` and Place read, `document.pdfInfo`.-//! - `dxf`: DXF export options, `document.exportDxf`, and the formats that can't be written (DWG, PICT).+//! - `dxf`: DXF and DWG export options, `document.exportDxf`, `document.exportDwg`, and the formats that can't be written (PICT). //! - `eps`: EPS export options (flattened transparency, previews, the embedded document), `document.exportEps`. //! - `dxfimport`: the DXF options `document.open` and Place read, `document.dxfInfo`. //! - `metafile`: EMF and WMF export, open and place.@@ -62,8 +62,8 @@ pub(crate) use load::import_svg; pub(crate) use load::native_file; pub(crate) use load::source; pub use load::{-    Loaded, RasterImage, ReadFile, detect, file_name, load, load_with, open_bytes, open_bytes_with, open_read, open_template, raster_image,-    read_for_open, texture_image,+    Loaded, RasterImage, ReadFile, detect, dwg_refusal, file_name, load, load_with, open_bytes, open_bytes_with, open_read, open_template,+    raster_image, read_for_open, texture_image, }; pub use load::{losses_summary, overwrite_losses}; pub use naming::{NameParts as FileNameParts, parse_date as parse_file_date, render as render_file_name};@@ -117,7 +117,7 @@ pub fn specs() -> Vec<CommandSpec> {             "Serialize Document",             [],             None,-            "{format?: vectorcraft (default)|template|svg|svgz|pdf|ai|png|jpg|webp|gif|png8|txt|dxf|eps|emf|wmf|tiff|bmp|tga|psd (ait, a .ait template, is written by file.saveAsTemplate), …the format's options (see document.formats; SVG ones also as svg: {…}), selectedOnly?: false (the selected objects alone, in their layers)} → {text, warnings} for svg (plus dataBase64, the file, when its encoding isn't UTF-8), else {dataBase64, warnings}; an SVG of several artboards also gives files: [{name, text}], linked images linked: [{name, dataBase64}]",+            "{format?: vectorcraft (default)|template|svg|svgz|pdf|ai|png|jpg|webp|gif|png8|txt|dxf|dwg|eps|emf|wmf|tiff|bmp|tga|psd (ait, a .ait template, is written by file.saveAsTemplate), …the format's options (see document.formats; SVG ones also as svg: {…}), selectedOnly?: false (the selected objects alone, in their layers)} → {text, warnings} for svg (plus dataBase64, the file, when its encoding isn't UTF-8), else {dataBase64, warnings}; an SVG of several artboards also gives files: [{name, text}], linked images linked: [{name, dataBase64}]",             has_doc,             export::serialize         ),@@ -126,7 +126,7 @@ pub fn specs() -> Vec<CommandSpec> {             "Export Document",             [],             None,-            "{path?, format?: svg|svgz|pdf|ai|png|jpg|webp|gif|png8|txt|dxf|eps|emf|wmf|tiff|bmp|tga|psd|vectorcraft|template (default: from the path's extension, else png; png8 writes an indexed .png; ait, a .ait template, is written by file.saveAsTemplate; an exporter plug-in's format is plugin.<plug-in id>, which an extension only it lists picks too), params?: {…} (an exporter plug-in's parameters; missing ones take their defaults, see plugin.info), selectedOnly?: false (the selected objects alone, in their layers), artboard?: 0, artboards?: [i…], rect?: [x, y, width, height] (raster formats: that area of the page in points, y down, instead of an artboard; not with artboard, artboards, range or useArtboards), range?: \"1-3, 5\" | \"all\" (1-based; PDF and .ai write one page per artboard, default all; SVG writes one file per artboard, {stem}-{artboard}.svg; raster formats write one artboard), useArtboards?: true (raster: one file per chosen artboard, default all, {stem}-{artboard}.{ext}; pdf/ai: every page) | false (pdf/ai/raster: the bounds of the visible art; SVG has it as an SVG option), ppi?: 72 (raster formats: pixels per inch, stored in the file; wins over scale), scale?: 1 (pixels per point), background?: transparent|white|black|\"#rrggbb\" (jpg: white when transparent), antiAlias?: none|art (default)|type (text snapped to pixels), interlaced?: false (png, Adam7), embedIcc?: true (png: an sRGB chunk marks the colours as sRGB), quality?: 90 (jpg: 0–100), colorModel?: rgb|cmyk|gray (jpg), method?: baseline|optimized|progressive, scans?: 3 (3–5, progressive), embedIcc?: true, imageMap?: none|client|server (an HTML or NCSA map of the objects with a URL, written as <stem>.html / <stem>.map), colors?: 256 (gif/png8: 2–256), reduction?: perceptual|selective (default)|adaptive|web|blackWhite|gray, dither?: none|diffusion (default)|pattern|noise, ditherAmount?: 100, transparency?: true, matte?: white|\"#rrggbb\"|none, interlaced?, lossless?: true (webp: lossy WebP isn't available yet: written lossless, with a warning), encoding?: utf8|utf16 (txt: the stories in stacking order, back to front, a thread once; utf16 with a byte order mark), lineEndings?: lf|crlf, selectionOnly?: false, svg?: {styling, outlineText, images, objectIds, decimals, minify, responsive, useArtboards, preserveEditing, metadata, fewerTspans, hiddenLayers, encoding, profile, embedFonts} (see document.formats), …the SVG options given flat instead, …the PDF options of document.exportPdf, …the DXF options of document.exportDxf (useArtboards: one drawing per artboard), …the EPS options of document.exportEps (useArtboards: one file per artboard, {stem}_{artboard}.eps; else the visible art), …emf and wmf write one picture of the artboard (useArtboards: true one file per chosen artboard, false the bounds of the visible art; EMF keeps curves, clipping, transparent images and gradients as images clipped to their shape; WMF flattens curves into polygons behind a placeable header; what a format leaves out comes back in warnings), colorModel?: rgb|cmyk|gray (tiff: rgb keeps transparency as an alpha channel), lzw?: true, byteOrder?: little|big, embedIcc?: true, colorModel?: rgb|gray (bmp), depth?: 24 (bmp: 1 black and white, 4|8 a palette by reduction and dither, 16, 24, 32 with alpha), fileFormat?: windows|os2 (1, 4, 8 or 24 bits), rle?: false (4 and 8 bits, windows), flipRows?: false (top-down rows), depth?: 24 (tga: 16 one-bit alpha, 24, 32 with alpha), colorModel?: rgb|cmyk|gray (psd), layers?: true (each top-level layer a pixel layer with its opacity and blend mode; a background colour is a Background layer; false: one flat image, on white), maxEditability?: false (layers and sublayers become groups, each object a layer named as in the Layers panel, text by its text; clipping, masked or knockout layers stay one layer), hiddenLayers?: false (hidden layers and objects written hidden instead of left out), embedIcc?: true (at most 30000 pixels a side; formats without alpha are flattened on white)} → {path, format, bytes, warnings, files?: [path…] (several), linked?: [path…] (linked images, image maps)}; no path → {dataBase64, format, bytes, warnings, files?: [{name, dataBase64}], linked?: [{name, dataBase64}]}. Never changes the document's path",+            "{path?, format?: svg|svgz|pdf|ai|png|jpg|webp|gif|png8|txt|dxf|dwg|eps|emf|wmf|tiff|bmp|tga|psd|vectorcraft|template (default: from the path's extension, else png; png8 writes an indexed .png; ait, a .ait template, is written by file.saveAsTemplate; an exporter plug-in's format is plugin.<plug-in id>, which an extension only it lists picks too), params?: {…} (an exporter plug-in's parameters; missing ones take their defaults, see plugin.info), selectedOnly?: false (the selected objects alone, in their layers), artboard?: 0, artboards?: [i…], rect?: [x, y, width, height] (raster formats: that area of the page in points, y down, instead of an artboard; not with artboard, artboards, range or useArtboards), range?: \"1-3, 5\" | \"all\" (1-based; PDF and .ai write one page per artboard, default all; SVG writes one file per artboard, {stem}-{artboard}.svg; raster formats write one artboard), useArtboards?: true (raster: one file per chosen artboard, default all, {stem}-{artboard}.{ext}; pdf/ai: every page) | false (pdf/ai/raster: the bounds of the visible art; SVG has it as an SVG option), ppi?: 72 (raster formats: pixels per inch, stored in the file; wins over scale), scale?: 1 (pixels per point), background?: transparent|white|black|\"#rrggbb\" (jpg: white when transparent), antiAlias?: none|art (default)|type (text snapped to pixels), interlaced?: false (png, Adam7), embedIcc?: true (png: an sRGB chunk marks the colours as sRGB), quality?: 90 (jpg: 0–100), colorModel?: rgb|cmyk|gray (jpg), method?: baseline|optimized|progressive, scans?: 3 (3–5, progressive), embedIcc?: true, imageMap?: none|client|server (an HTML or NCSA map of the objects with a URL, written as <stem>.html / <stem>.map), colors?: 256 (gif/png8: 2–256), reduction?: perceptual|selective (default)|adaptive|web|blackWhite|gray, dither?: none|diffusion (default)|pattern|noise, ditherAmount?: 100, transparency?: true, matte?: white|\"#rrggbb\"|none, interlaced?, lossless?: true (webp: lossy WebP isn't available yet: written lossless, with a warning), encoding?: utf8|utf16 (txt: the stories in stacking order, back to front, a thread once; utf16 with a byte order mark), lineEndings?: lf|crlf, selectionOnly?: false, svg?: {styling, outlineText, images, objectIds, decimals, minify, responsive, useArtboards, preserveEditing, metadata, fewerTspans, hiddenLayers, encoding, profile, embedFonts} (see document.formats), …the SVG options given flat instead, …the PDF options of document.exportPdf, …the DXF options of document.exportDxf (useArtboards: one drawing per artboard; dwg takes them too, version 2000–2018), …the EPS options of document.exportEps (useArtboards: one file per artboard, {stem}_{artboard}.eps; else the visible art), …emf and wmf write one picture of the artboard (useArtboards: true one file per chosen artboard, false the bounds of the visible art; EMF keeps curves, clipping, transparent images and gradients as images clipped to their shape; WMF flattens curves into polygons behind a placeable header; what a format leaves out comes back in warnings), colorModel?: rgb|cmyk|gray (tiff: rgb keeps transparency as an alpha channel), lzw?: true, byteOrder?: little|big, embedIcc?: true, colorModel?: rgb|gray (bmp), depth?: 24 (bmp: 1 black and white, 4|8 a palette by reduction and dither, 16, 24, 32 with alpha), fileFormat?: windows|os2 (1, 4, 8 or 24 bits), rle?: false (4 and 8 bits, windows), flipRows?: false (top-down rows), depth?: 24 (tga: 16 one-bit alpha, 24, 32 with alpha), colorModel?: rgb|cmyk|gray (psd), layers?: true (each top-level layer a pixel layer with its opacity and blend mode; a background colour is a Background layer; false: one flat image, on white), maxEditability?: false (layers and sublayers become groups, each object a layer named as in the Layers panel, text by its text; clipping, masked or knockout layers stay one layer), hiddenLayers?: false (hidden layers and objects written hidden instead of left out), embedIcc?: true (at most 30000 pixels a side; formats without alpha are flattened on white)} → {path, format, bytes, warnings, files?: [path…] (several), linked?: [path…] (linked images, image maps)}; no path → {dataBase64, format, bytes, warnings, files?: [{name, dataBase64}], linked?: [{name, dataBase64}]}. Never changes the document's path",             has_doc,             export::export         ),@@ -135,7 +135,7 @@ pub fn specs() -> Vec<CommandSpec> {             "Export Selection…",             ["File"],             None,-            "{path?, format?: png|jpg|webp|svg|svgz|pdf|ai|gif|png8|tiff|bmp|tga|psd|txt|dxf|eps|emf|wmf|vectorcraft|template (default: from the extension, else png), scale?: 1, …the format's options} the selected objects cropped to their bounds (template layers left out) → {path, format, bytes, warnings, bounds} (no path → {dataBase64, format, bytes, warnings, bounds}); warnings say what the format approximated or left out, as for document.export",+            "{path?, format?: png|jpg|webp|svg|svgz|pdf|ai|gif|png8|tiff|bmp|tga|psd|txt|dxf|dwg|eps|emf|wmf|vectorcraft|template (default: from the extension, else png), scale?: 1, …the format's options} the selected objects cropped to their bounds (template layers left out) → {path, format, bytes, warnings, bounds} (no path → {dataBase64, format, bytes, warnings, bounds}); warnings say what the format approximated or left out, as for document.export",             has_selection,             export::export_selection         ),@@ -198,7 +198,7 @@ pub fn specs() -> Vec<CommandSpec> {             "File Formats",             [],             None,-            "{} → {formats: [{id, label, extensions, mime, read, write, raster, options: {name: {type, default, description}}, plugin?: the id of the exporter plug-in that adds the format (its id is plugin.<plug-in id>; see plugin.list)}], readable: [id…], writable: [id…], openExtensions: [ext…], unsupported: [{id, label, extensions, hint}] (formats asked for that can be neither opened nor written, such as DWG and PICT, with what to use instead)}",+            "{} → {formats: [{id, label, extensions, mime, read, write, raster, options: {name: {type, default, description}}, plugin?: the id of the exporter plug-in that adds the format (its id is plugin.<plug-in id>; see plugin.list)}], readable: [id…], writable: [id…], openExtensions: [ext…], unsupported: [{id, label, extensions, hint}] (formats asked for that can be neither opened nor written, such as PICT, with what to use instead)}",             always,             formats         ),@@ -635,6 +635,18 @@ pub const FORMATS: &[Format] = &[     Format { id: "png8", label: "PNG-8", extensions: &["png"], mime: "image/png", read: false, write: true, raster: true, options: PALETTE_OPTIONS },     Format { id: "txt", label: "Text", extensions: TEXT_EXTS, mime: "text/plain", read: false, write: true, raster: false, options: text::OPTIONS },     Format { id: "dxf", label: "DXF", extensions: &["dxf"], mime: "image/vnd.dxf", read: true, write: true, raster: false, options: dxf::OPTIONS },+    // Written as the DXF drawing converted (`vectorcraft-dwg`), not read. The web app has no DWG+    // writer (acadrust is native only): there it is neither read nor written.+    Format {+        id: "dwg",+        label: "DWG",+        extensions: &["dwg"],+        mime: "image/vnd.dwg",+        read: false,+        write: cfg!(not(target_arch = "wasm32")),+        raster: false,+        options: dxf::DWG_OPTIONS,+    },     Format {         id: "eps",         label: "EPS",@@ -885,6 +897,8 @@ pub fn writable_format(format_param: Option<&str>, path: Option<&str>) -> std::r         Ok(f)     } else if TEMPLATE_FORMATS.contains(&f.id) {         Err(format!("{} files are written by file.saveAsTemplate", f.label))+    } else if !f.read {+        Err(format!("{} files are written by the desktop app only (see document.formats)", f.label))     } else {         Err(format!("{} files can be opened but not written (see document.formats)", f.label))     }diff --git a/crates/engine/src/cmd/fileio/screens.rs b/crates/engine/src/cmd/fileio/screens.rs--- a/crates/engine/src/cmd/fileio/screens.rs+++ b/crates/engine/src/cmd/fileio/screens.rs@@ -196,7 +196,7 @@ impl Row {             return Err(bad(C, "each format is an object {format, scale?, suffix?}"));         }         let format = writable(C, Some(str_param(row, "format").unwrap_or("png")), None)?;-        if matches!(format.id, "template" | "txt" | "dxf" | "emf" | "wmf") {+        if matches!(format.id, "template" | "txt" | "dxf" | "dwg" | "emf" | "wmf") {             return Err(bad(C, "Export for Screens writes png, png8, jpg, webp, gif, tiff, bmp, tga, psd, svg, svgz, pdf, eps, ai or vectorcraft"));         }         // Vector formats have no pixel size: it doesn't apply and adds no @2x suffix (not even onediff --git a/crates/engine/src/cmd/fileio/tests.rs b/crates/engine/src/cmd/fileio/tests.rs--- a/crates/engine/src/cmd/fileio/tests.rs+++ b/crates/engine/src/cmd/fileio/tests.rs@@ -406,6 +406,7 @@ fn export_extensions_name_every_writable_format() {             "vctemplate",             "txt",             "dxf",+            "dwg",             "eps",             "emf",             "wmf",@@ -441,6 +442,7 @@ fn formats_query_lists_readers_writers_and_options() {             "png8",             "txt",             "dxf",+            "dwg",             "eps",             "emf",             "wmf",@@ -532,7 +534,7 @@ fn export_without_path_returns_bytes_and_never_retargets() {     let r = s.execute("document.export", &json!({"format": "jpg"})).unwrap();     assert_eq!(&b64(&r)[..2], [0xFF, 0xD8]);     assert!(r.get("path").is_none());-    assert!(s.execute("document.export", &json!({"path": dir.join("x.dwg").to_string_lossy()})).is_err(), "DWG can't be written");+    assert!(s.execute("document.export", &json!({"path": dir.join("x.pict").to_string_lossy()})).is_err(), "PICT can't be written");     // A never-saved document hands its native bytes back.     let mut s = session(10.0, 10.0, 1);     let r = s.execute("document.save", &json!({})).unwrap();diff --git a/crates/engine/src/cmd/fileio/tests_dxf.rs b/crates/engine/src/cmd/fileio/tests_dxf.rs--- a/crates/engine/src/cmd/fileio/tests_dxf.rs+++ b/crates/engine/src/cmd/fileio/tests_dxf.rs@@ -1,5 +1,5 @@-//! DXF export through the commands: options, artboards, selection, images, and the formats that-//! can't be written (DWG, PICT).+//! DXF and DWG export through the commands: options, artboards, selection, images, and the+//! formats that can't be written (PICT).  use serde_json::{Value, json}; @@ -169,16 +169,17 @@ fn formats_list_dxf_and_what_can_not_be_written() {     }     assert!(r["writable"].as_array().unwrap().contains(&json!("dxf")));     let unsupported: Vec<&str> = r["unsupported"].as_array().unwrap().iter().map(|u| u["id"].as_str().unwrap()).collect();-    assert_eq!(unsupported, ["dwg", "pict"]);+    assert_eq!(unsupported, ["pict"]);+    let dwg = r["formats"].as_array().unwrap().iter().find(|f| f["id"] == "dwg").unwrap();+    assert_eq!((dwg["read"].as_bool(), dwg["write"].as_bool()), (Some(false), Some(true)));+    assert!(dwg["options"]["version"]["description"].as_str().unwrap().starts_with("2000"));+    assert!(r["writable"].as_array().unwrap().contains(&json!("dwg")));+    assert!(!r["readable"].as_array().unwrap().contains(&json!("dwg"))); }  #[test]-fn dwg_and_pict_say_what_to_use_instead() {+fn pict_says_what_to_use_instead() {     let mut s = two_boards();-    for p in [json!({"format": "dwg"}), json!({"path": "/tmp/plan.DWG"})] {-        let e = s.execute("document.export", &p).unwrap_err().to_string();-        assert!(e.contains("DWG is not available") && e.contains("DXF"), "{p}: {e}");-    }     let e = s.execute("document.export", &json!({"format": "pict"})).unwrap_err().to_string();     assert!(e.contains("PICT is a retired format"), "{e}");     let e = s@@ -186,9 +187,125 @@ fn dwg_and_pict_say_what_to_use_instead() {         .unwrap_err()         .to_string();     assert!(e.contains("old.pct") && e.contains("PICT"), "{e}");+}++/// The DWG version code a file starts with.+fn acadver(dwg: &[u8]) -> &str {+    std::str::from_utf8(&dwg[..6]).unwrap()+}++#[test]+fn dwg_is_the_dxf_drawing_written_as_dwg() {+    let mut s = two_boards();+    // By format, by command and by extension; 2018 unless a version is asked for.+    for (cmd, p) in [+        ("document.export", json!({"format": "dwg"})),+        ("document.exportDwg", json!({})),+        ("document.export", json!({"format": "DWG", "version": "2000"})),+        ("document.exportDwg", json!({"version": 2010})),+    ] {+        let r = s.execute(cmd, &p).unwrap_or_else(|e| panic!("{cmd} {p}: {e}"));+        let want = match p["version"].to_string().trim_matches('"') {+            "2000" => "AC1015",+            "2010" => "AC1024",+            _ => "AC1032",+        };+        let bytes = b64(&r);+        assert!(vectorcraft_dwg::is_dwg(&bytes), "{cmd} {p}");+        assert_eq!(acadver(&bytes), want, "{cmd} {p}");+        assert_eq!(r["format"], "dwg", "{cmd} {p}");+    }+    let dir = std::env::temp_dir().join(format!("vc-dwg-{}", std::process::id()));+    std::fs::create_dir_all(&dir).unwrap();+    let path = dir.join("plan.DWG").to_string_lossy().to_string();+    let r = s.execute("document.export", &json!({"path": path})).unwrap();+    assert_eq!(r["format"], "dwg");+    assert_eq!(acadver(&std::fs::read(&path).unwrap()), "AC1032");+    let _ = std::fs::remove_dir_all(&dir);+}++#[test]+fn dwg_writes_one_drawing_per_artboard() {+    let mut s = two_boards();+    let r = s.execute("document.exportDwg", &json!({"useArtboards": true})).unwrap();+    let files = r["files"].as_array().unwrap();+    assert_eq!(files.len(), 2, "{r}");+    for f in files {+        assert!(f["name"].as_str().unwrap().ends_with(".dwg"), "{f}");+        assert!(vectorcraft_dwg::is_dwg(&b64(f)), "{f}");+    }+    let e = s.execute("document.exportDwg", &json!({"artboards": [0, 1]})).unwrap_err().to_string();+    assert!(e.contains("one origin"), "{e}");+}++#[test]+fn dwg_versions_start_at_2000() {+    let mut s = two_boards();+    for v in ["R12", "R13", "R14", "1999"] {+        let e = s.execute("document.exportDwg", &json!({"version": v})).unwrap_err().to_string();+        assert!(e.contains(&format!("DWG version `{v}`")) && e.contains("2000, 2004"), "{v}: {e}");+    }+    let e = s.execute("document.exportDwg", &json!({"scale": -1})).unwrap_err().to_string();+    assert!(e.contains("DWG scale"), "{e}");+    // The same versions are fine for DXF, and the dialog check tells them apart by format.+    assert!(s.execute("document.exportDxf", &json!({"version": "R12"})).is_ok());+    assert!(dxf::check(&json!({"format": "dxf", "version": "R12"})).is_ok());+    assert!(dxf::check(&json!({"format": "dwg", "version": "R12"})).is_err());+    assert!(dxf::check(&json!({"format": "DWG", "version": "R12"})).is_err(), "any case, as the export takes it");+    assert!(dxf::check(&json!({"format": ".dwg", "version": "R12"})).is_err(), "the extension names it too");+    assert!(dxf::check(&json!({"format": "dwg", "version": "2004"})).is_ok());+}++#[test]+fn dwg_keeps_placed_images_and_says_dwg_in_its_warnings() {+    let mut s = super::tests_svg::image_session();+    let r = s.execute("document.exportDwg", &json!({"version": "2000"})).unwrap();+    assert_eq!(acadver(&b64(&r)), "AC1015");+    assert_eq!(r["linked"].as_array().unwrap().len(), 1, "the image file the drawing links: {r}");+    let warnings: Vec<&str> = r["warnings"].as_array().unwrap().iter().map(|w| w.as_str().unwrap()).collect();+    assert!(!warnings.iter().any(|w| w.contains("left out")), "VectorCraft's DXF is written whole: {warnings:?}");+    // True colour on 2000: the DXF writer's warning, about the DWG.+    assert!(warnings.iter().any(|w| w.contains("true colour") && w.contains("DWG") && !w.contains("DXF")), "{warnings:?}");+}++#[test]+fn what_the_dwg_writer_leaves_out_is_counted_in_words() {+    assert_eq!(dxf::left_out_warning((0, 0), 1), None);+    assert_eq!(dxf::left_out_warning((1, 0), 1).unwrap(), "the DWG writer can't write 1 object of this drawing: left out");+    assert_eq!(dxf::left_out_warning((3, 0), 1).unwrap(), "the DWG writer can't write 3 objects of this drawing: left out");+    assert_eq!(dxf::left_out_warning((0, 1), 1).unwrap(), "the DWG writer can't write 1 non-graphical record of this drawing: left out");+    assert_eq!(+        dxf::left_out_warning((2, 5), 1).unwrap(),+        "the DWG writer can't write 2 objects and 5 non-graphical records of this drawing: left out"+    );+    assert_eq!(dxf::left_out_warning((2, 0), 3).unwrap(), "the DWG writer can't write 2 objects of these 3 drawings: left out");+}++#[test]+fn a_dwg_file_says_it_is_written_not_read() {+    let mut s = two_boards();     let e = s         .execute("document.open", &json!({"name": "site.dwg", "dataBase64": vectorcraft_format::base64_encode(b"AC1032\0\0")}))         .unwrap_err()         .to_string();-    assert!(e.contains("DWG is not available"), "{e}");+    assert!(e.contains("site.dwg") && e.contains("doesn't read DWG drawings"), "{e}");+    // DWG content under any name, a readable one too (it would reach that reader otherwise), and a+    // `.dwg` name whatever it holds; opening and placing alike.+    for (name, bytes) in [+        ("plan.bin", &b"AC1032\0\0"[..]),+        ("plan.dxf", b"AC1018\0\0"),+        ("plan.png", b"AC1015\0\0"),+        ("plan.docx", b"AC1024\0\0"),+        ("plan.txt", b"AC1027\0\0"),+        ("empty.DWG", b""),+    ] {+        for cmd in ["document.open", "file.place"] {+            let e = s.execute(cmd, &json!({"name": name, "dataBase64": vectorcraft_format::base64_encode(bytes)})).unwrap_err().to_string();+            assert!(e.contains(name) && e.contains("doesn't read DWG drawings") && e.contains("save it as DXF"), "{cmd} {name}: {e}");+        }+    }+    // Other write-only formats keep their own message.+    let e =+        s.execute("document.open", &json!({"name": "notes.txt", "dataBase64": vectorcraft_format::base64_encode(b"hi")})).unwrap_err().to_string();+    assert!(!e.contains("DWG"), "{e}"); }diff --git a/crates/engine/src/cmd/place/mod.rs b/crates/engine/src/cmd/place/mod.rs--- a/crates/engine/src/cmd/place/mod.rs+++ b/crates/engine/src/cmd/place/mod.rs@@ -142,6 +142,10 @@ pub(crate) fn pt_per_px(ppi: Option<(f64, f64)>) -> (f64, f64) { fn load(p: &Value, cmd: &str, board: Option<Rect>) -> Result<Loaded> {     let src = fileio::source(p, cmd)?;     let name = fileio::file_name(src.name);+    // A DWG drawing, before any reader picked by name (text, RTF, Word) takes it.+    if let Some(e) = fileio::dwg_refusal("place", src.name, &src.bytes) {+        return Err(bad(cmd, e));+    }     // An Affinity file is recognised by its signature, whatever its name says.     if fileio::TEXT_EXTS.contains(&fileio::extension(src.name).as_str()) && !vectorcraft_affinity::is_affinity(&src.bytes) {         let text = text::import(&src.bytes, text::TextOptions::parse(p, cmd)?, cmd)?;diff --git a/crates/mcp/src/tests.rs b/crates/mcp/src/tests.rs--- a/crates/mcp/src/tests.rs+++ b/crates/mcp/src/tests.rs@@ -484,7 +484,7 @@ fn errors_are_tool_results_not_crashes() {         ("type_text", json!({"text": "hi"})),         ("screenshot", json!({"window": true})),         ("open_file", json!({"path": "/definitely/not/here.svg"})),-        ("export", json!({"path": "/tmp/out.dwg"})),+        ("export", json!({"path": "/tmp/out.pict"})),         ("redo", json!({})),         ("press_key", json!({"key": "F13"})),     ];diff --git a/crates/mcp/src/tests_fileio.rs b/crates/mcp/src/tests_fileio.rs--- a/crates/mcp/src/tests_fileio.rs+++ b/crates/mcp/src/tests_fileio.rs@@ -74,6 +74,7 @@ fn export_tool_lists_engine_formats_and_options() {             "png8",             "txt",             "dxf",+            "dwg",             "eps",             "emf",             "wmf",diff --git a/crates/mcp/src/tools.rs b/crates/mcp/src/tools.rs--- a/crates/mcp/src/tools.rs+++ b/crates/mcp/src/tools.rs@@ -363,7 +363,7 @@ pub fn tool_definitions() -> Vec<Value> {         tool(             "export",             "Export",-            "Export the active document with the engine's document.export (the same bytes in the app and headless), in any format of the `format` enum: vectors (svg/svgz with the artboard as viewBox, eps, dxf, emf, wmf), pdf and ai (one page per artboard: all, or `artboard` / `range`; ai is the PDF-compatible .ai file that Save writes, which carries the document when every artboard is written in order), rasters (one rendered artboard: png, jpg, webp, gif, png8 as an indexed .png, tiff, bmp, tga, psd with its layers), txt (the stories, back to front), vectorcraft or template (a native copy or template; the document keeps its path). run_command document.formats lists each format's options. For SVG/SVGZ, top-level `outlineText: true` writes text as paths; DXF takes it too (its type is outlined anyway unless `preserve: editability`). PDF instead uses `options.advanced.outlineText: true`, default false (live text). `selection: true` exports only the selected objects, cropped to their bounds. Template layers are left out; live effects are kept (geometry baked, SVG filters for shadows/glows/blur). Without `path` the bytes come back as dataBase64.",+            "Export the active document with the engine's document.export (the same bytes in the app and headless), in any format of the `format` enum: vectors (svg/svgz with the artboard as viewBox, eps, dxf, dwg (the DXF drawing as DWG 2000–2018, desktop only), emf, wmf), pdf and ai (one page per artboard: all, or `artboard` / `range`; ai is the PDF-compatible .ai file that Save writes, which carries the document when every artboard is written in order), rasters (one rendered artboard: png, jpg, webp, gif, png8 as an indexed .png, tiff, bmp, tga, psd with its layers), txt (the stories, back to front), vectorcraft or template (a native copy or template; the document keeps its path). run_command document.formats lists each format's options. For SVG/SVGZ, top-level `outlineText: true` writes text as paths; DXF takes it too (its type is outlined anyway unless `preserve: editability`). PDF instead uses `options.advanced.outlineText: true`, default false (live text). `selection: true` exports only the selected objects, cropped to their bounds. Template layers are left out; live effects are kept (geometry baked, SVG filters for shadows/glows/blur). Without `path` the bytes come back as dataBase64.",             obj(                 json!({                     "format": {"type": "string", "enum": FORMATS.iter().filter(|f| f.write).map(|f| f.id).collect::<Vec<_>>(), "description": "Default: from the path's extension"},diff --git a/crates/ui-egui/src/dialogs/dxf_options.rs b/crates/ui-egui/src/dialogs/dxf_options.rs--- a/crates/ui-egui/src/dialogs/dxf_options.rs+++ b/crates/ui-egui/src/dialogs/dxf_options.rs@@ -1,10 +1,11 @@-//! DXF Options (Export As → DXF): version, scale (1 unit of the art = N drawing units) and-//! lineweights, number of colours, the format of placed images, Preserve Appearance or Maximize-//! Editability, and Export Selected Art Only, Alter Paths for Appearance and Outline Text.+//! DXF Options (Export As → DXF) and DWG Options (Export As → DWG, the same drawing from version+//! 2000): version, scale (1 unit of the art = N drawing units) and lineweights, number of colours,+//! the format of placed images, Preserve Appearance or Maximize Editability, and Export Selected+//! Art Only, Alter Paths for Appearance and Outline Text. //!-//! Fields: `document.export` params (`format: dxf`, `path?`, the artboard choice) and the DXF-//! options of `document.exportDxf`. OK remembers the options for next time and writes the file-//! (asking where when there is no path); bad options keep the dialog open.+//! Fields: `document.export` params (`format: dxf|dwg`, `path?`, the artboard choice) and the DXF+//! options of `document.exportDxf`. OK remembers the options for next time (DXF and DWG share them)+//! and writes the file (asking where when there is no path); bad options keep the dialog open.  use serde_json::{Map, Value, json}; use vectorcraft_cad::{ColorDepth, DxfVersion, Preserve, RasterFormat};@@ -19,7 +20,7 @@ use crate::{VectorcraftApp, io, widgets}; pub(super) const KIND: &str = "dxfOptions";  pub(super) const SPEC: DialogSpec = DialogSpec {-    heading: |_| tl!("DXF Options").into(),+    heading: |d| if is_dwg(d) { tl!("DWG Options") } else { tl!("DXF Options") }.into(),     body,     confirm,     ok: Some("Export"),@@ -51,21 +52,40 @@ fn defaults() -> Map<String, Value> {     m } -/// Open DXF Options for an export whose params (path, artboard choice, any DXF option) are-/// `params`, over the options used last.+/// Is the dialog writing DWG (else DXF)?+fn is_dwg(d: &Dialog) -> bool {+    d.str("format") == "dwg"+}++/// The versions the dialog offers: every DXF version, or for DWG those from 2000.+fn versions(dwg: bool) -> impl Iterator<Item = DxfVersion> {+    DxfVersion::ALL.iter().copied().filter(move |v| !dwg || *v >= DxfVersion::R2000)+}++/// Open DXF Options, or DWG Options for `format: dwg`, for an export whose params (path, artboard+/// choice, any DXF option) are `params`, over the options used last. pub fn open(app: &mut VectorcraftApp, params: &Value) {+    let dwg = params.get("format").and_then(Value::as_str).and_then(fileio::format).is_some_and(|f| f.id == "dwg");     let mut fields = defaults();-    for given in [&app.ui.dxf_options, params] {-        if let Some(o) = given.as_object() {-            fields.extend(o.iter().map(|(k, v)| (k.clone(), v.clone())));-        }+    if let Some(o) = app.ui.dxf_options.as_object() {+        fields.extend(o.iter().map(|(k, v)| (k.clone(), v.clone())));+    }+    // A DXF version used last that DWG isn't written as starts DWG Options on the default; one+    // the params ask for is kept, so OK says what's wrong with it. (`version` may be a number:+    // 2018 and "2018" alike.)+    let version = fields.get("version").and_then(|v| DxfVersion::from_id(&v.as_str().map_or_else(|| v.to_string(), str::to_string)));+    if dwg && !version.is_some_and(|v| versions(true).any(|w| w == v)) {+        fields.insert("version".into(), json!(DxfVersion::default().id()));+    }+    if let Some(o) = params.as_object() {+        fields.extend(o.iter().map(|(k, v)| (k.clone(), v.clone())));     }-    fields.insert("format".into(), json!("dxf"));+    fields.insert("format".into(), json!(if dwg { "dwg" } else { "dxf" }));     app.ui.dialog = Some(Dialog::new(KIND, Value::Object(fields))); }  fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {-    let versions: Vec<(&str, &str)> = DxfVersion::ALL.iter().map(|v| (v.id(), v.label())).collect();+    let versions: Vec<(&str, &str)> = versions(is_dwg(d)).map(|v| (v.id(), v.label())).collect();     choice(ui, d, "version", tl!("Version:"), &versions);     ui.add_space(4.0);     scale_row(ui, d);@@ -155,5 +175,5 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {         Value::Object(d.fields.iter().filter(|(k, _)| !PER_EXPORT.contains(&k.as_str())).map(|(k, v)| (k.clone(), v.clone())).collect());     app.ui.dialog = None;     let path = d.fields.get("path").and_then(Value::as_str).map(str::to_string);-    io::export(app, Some("dxf"), path, &params)+    io::export(app, Some(if is_dwg(d) { "dwg" } else { "dxf" }), path, &params) }diff --git a/crates/ui-egui/src/dialogs/export_as.rs b/crates/ui-egui/src/dialogs/export_as.rs--- a/crates/ui-egui/src/dialogs/export_as.rs+++ b/crates/ui-egui/src/dialogs/export_as.rs@@ -1,10 +1,10 @@ //! File → Export → Export As…: the format and Use Artboards (All or a range: one file per //! artboard, or one page each in a PDF); without artboards the export covers the visible art. OK //! picks the file, then the format's options dialog follows (PNG/JPEG/WebP/PNG-8/GIF Options, SVG-//! Options, Text Export Options, DXF Options, EPS Options, and Save PDF for artboards as pages),-//! else the file is written. DWG is listed, greyed out, with what to use instead. The formats of-//! installed exporter plug-ins come after the built-in ones: they write the whole document, and one-//! with parameters asks for them before writing (`plugin::open_export`).+//! Options, Text Export Options, DXF and DWG Options, EPS Options, and Save PDF for artboards as+//! pages), else the file is written. The formats of installed exporter plug-ins come after the+//! built-in ones: they write the whole document, and one with parameters asks for them before+//! writing (`plugin::open_export`).  use std::sync::LazyLock; @@ -22,8 +22,6 @@ pub(super) const SPEC: DialogSpec = /// The built-in formats Export As writes (the native format and `.ai` are Save As's). static BUILT_IN: LazyLock<Vec<&'static Format>> =     LazyLock::new(|| fileio::FORMATS.iter().filter(|f| f.write && !matches!(f.id, "vectorcraft" | "ai")).collect());-/// Formats listed after them that can't be written (`fileio::unsupported` ids), greyed out.-const UNAVAILABLE: [(&str, &str); 1] = [("dwg", "DWG (use DXF)")];  /// The formats Export As writes: the built-in ones, then those of the installed exporter plug-ins. pub(super) fn formats() -> Vec<&'static Format> {@@ -35,22 +33,13 @@ fn from_plugin(f: &Format) -> bool {     vectorcraft_plugins::export::by_format(f.id).is_some() } -/// What to use instead when the field names a format that can't be written (DWG).-fn unavailable(d: &Dialog) -> Option<(&'static str, &'static str)> {-    let id = d.str("format");-    let (_, label) = UNAVAILABLE.iter().find(|u| u.0 == id)?;-    Some((label, fileio::unsupported(&id)?.hint))-}- /// Open the dialog on `format` (an id or extension, default PNG); `p` may preset `useArtboards` /// and `range`. pub fn open(app: &mut VectorcraftApp, format: Option<&str>, p: &Value) {     let n = app.session.active().map_or(0, |s| s.doc.artboards.len());-    let missing = format.and_then(|f| UNAVAILABLE.iter().find(|u| u.0.eq_ignore_ascii_case(f.trim_start_matches('.'))));-    let format = match missing {-        Some((id, _)) => id,-        None => format.and_then(fileio::format).filter(|f| formats().iter().any(|g| g.id == f.id)).map_or("png", |f| f.id),-    };+    // A format this build can't write (DWG in the web app) stays named, with why, rather than+    // silently becoming PNG.+    let format = format.and_then(fileio::format).filter(|f| formats().iter().any(|g| g.id == f.id) || not_here(f).is_some()).map_or("png", |f| f.id);     let range = p.get("range").and_then(Value::as_str);     app.ui.dialog = Some(Dialog::new(         "exportAs",@@ -63,6 +52,18 @@ pub fn open(app: &mut VectorcraftApp, format: Option<&str>, p: &Value) {     )); } +/// Why `f` can't be written in this build, when it is a format VectorCraft writes elsewhere (DWG+/// in the web app).+fn not_here(f: &Format) -> Option<String> {+    (!f.write && !f.read).then(|| fileio::writable_format(Some(f.id), None).err()).flatten()+}++/// The format the field names that can't be written here, and why.+fn unavailable(d: &Dialog) -> Option<(&'static str, String)> {+    let f = fileio::format(&d.str("format"))?;+    Some((f.label, not_here(f)?))+}+ /// The chosen format (PNG when the field names none Export As writes). fn chosen(d: &Dialog) -> Option<&'static Format> {     let id = d.str("format");@@ -77,17 +78,17 @@ fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {     let missing = unavailable(d);     egui::Grid::new("export-as").num_columns(2).spacing([10.0, 8.0]).show(ui, |ui| {         ui.label(egui::RichText::new(tl!("Format:")).color(t.text_dim));-        let shown = missing.map_or(f.label, |(label, _)| label);+        let shown = missing.as_ref().map_or(f.label, |(label, _)| label);         let all = formats();-        let labels: Vec<&str> = all.iter().map(|f| f.label).chain(UNAVAILABLE.iter().map(|u| u.1)).collect();-        if let Some(g) = widgets::dropdown_with(ui, "export-as-format", shown, &labels, 160.0, |i| i < all.len()).and_then(|i| all.get(i)) {+        let labels: Vec<&str> = all.iter().map(|f| f.label).collect();+        if let Some(g) = widgets::dropdown(ui, "export-as-format", shown, &labels, 160.0).and_then(|i| all.get(i)) {             d.fields.insert("format".into(), json!(g.id));         }         ui.end_row();     });-    if let Some((_, hint)) = missing {+    if let Some((_, why)) = &missing {         ui.add_space(6.0);-        form::caption(ui, hint);+        form::caption(ui, why);     }     ui.add_space(10.0);     // An exporter plug-in writes the whole document: Use Artboards is not its to apply.@@ -121,8 +122,8 @@ fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {  /// Check the artboard range, pick the file, then show the format's options or write the file. fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {-    if let Some((_, hint)) = unavailable(d) {-        return Err(hint.into());+    if let Some((_, why)) = unavailable(d) {+        return Err(why);     }     let f = chosen(d).ok_or("no format to export to")?;     let plugin = vectorcraft_plugins::export::by_format(f.id);@@ -169,7 +170,7 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {                 png_options::open(app, f, params);                 return Ok(Value::Null);             }-            if f.id == "dxf" {+            if matches!(f.id, "dxf" | "dwg") {                 dxf_options::open(app, &params);                 return Ok(Value::Null);             }diff --git a/crates/ui-egui/src/dialogs/tests_dxf.rs b/crates/ui-egui/src/dialogs/tests_dxf.rs--- a/crates/ui-egui/src/dialogs/tests_dxf.rs+++ b/crates/ui-egui/src/dialogs/tests_dxf.rs@@ -1,4 +1,4 @@-//! Export As → DXF Options, and DWG listed as unavailable, drawn headlessly.+//! Export As → DXF Options and DWG Options, drawn headlessly.  use serde_json::json; @@ -61,18 +61,46 @@ fn bad_dxf_options_keep_the_dialog_open() { }  #[test]-fn dwg_is_listed_but_points_to_dxf() {+fn export_as_dwg_shows_dwg_options_then_writes_dwg() {     let (mut app, written) = app(1);+    // A DXF version used last that DWG isn't written as (R12): DWG Options start on the default,+    // the other options carry over.+    app.ui.dxf_options = json!({"version": "R12", "unit": "Inches"});     app.run("file.exportAs", json!({"format": "dwg"})).unwrap();     assert_eq!(app.ui.dialog.as_ref().unwrap().str("format"), "dwg");     frame(&mut app);-    let e = confirm(&mut app).unwrap_err();-    assert!(e.contains("DWG") && e.contains("DXF"), "{e}");-    assert_eq!(kind(&app), Some("exportAs"), "the dialog stays open to pick DXF");-    set(&mut app, "format", json!("dxf"));     confirm(&mut app).unwrap();-    assert_eq!(kind(&app), Some("dxfOptions"));-    assert!(written.borrow().is_empty());+    assert_eq!(kind(&app), Some("dxfOptions"), "DWG Options follow");+    let d = app.ui.dialog.as_ref().unwrap();+    assert_eq!(+        (d.str("format"), d.str("path"), d.str("version"), d.str("unit")),+        ("dwg".into(), "/out/Untitled-1.dwg".into(), "2018".into(), "Inches".into())+    );+    assert_eq!((dxf_options::SPEC.heading)(d), "DWG Options");+    frame(&mut app);+    set(&mut app, "version", json!("R14"));+    let e = confirm(&mut app).unwrap_err();+    assert!(e.contains("DWG version `R14`"), "{e}");+    assert_eq!(kind(&app), Some("dxfOptions"), "a version DWG isn't written as keeps the dialog open");+    set(&mut app, "version", json!("2000"));+    frame(&mut app);+    let r = confirm(&mut app).unwrap();+    assert_eq!(r["path"], "/out/Untitled-1.dwg");+    assert_eq!(names(&written), ["/out/Untitled-1.dwg"]);+    assert_eq!(&written.borrow()[0].1[..6], b"AC1015");+    // A version the params ask for is kept (OK then says what's wrong with it); "DWG" and ".dwg"+    // name the format too.+    for format in ["DWG", ".dwg"] {+        crate::menus::invoke(&mut app, "ui.dxfOptionsDialog", json!({"format": format, "version": "R12"}));+        let d = app.ui.dialog.as_ref().unwrap();+        assert_eq!((d.str("format"), d.str("version")), ("dwg".into(), "R12".into()), "{format}");+        assert!(confirm(&mut app).unwrap_err().contains("DWG version `R12`"), "{format}");+    }+    // DXF Options keep offering every DXF version.+    crate::menus::invoke(&mut app, "ui.dxfOptionsDialog", json!({"version": "R12"}));+    let d = app.ui.dialog.as_ref().unwrap();+    assert_eq!((d.str("format"), d.str("version")), ("dxf".into(), "R12".into()));+    assert_eq!((dxf_options::SPEC.heading)(d), "DXF Options"); }  #[test]diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv--- a/crates/ui-egui/src/i18n/bg.tsv+++ b/crates/ui-egui/src/i18n/bg.tsv@@ -838,6 +838,7 @@ 	DXF Import Options	Опции за импортиране на DXF 	DXF Info	Информация за DXF 	DXF Options	Опции за DXF+	DWG Options	Опции за DWG 	DXF Options Dialog	Диалогов прозорец с опции за DXF 	e.g. 1-3, 5	напр. 1-3, 5 	Each page prints as one image, at the document's raster effects resolution.	Всяка страница се отпечатва като едно изображение при разделителната способност на растерните ефекти на документа.@@ -1000,6 +1001,7 @@ 	Export CSS…	Експортиране на CSS… 	Export Document	Експортиране на документ 	Export DXF	Експортиране в DXF+	Export DWG	Експортиране в DWG 	Export EPS	Експортиране в EPS 	Export for Screens	Експортиране за екрани 	Export for Screens Settings	Настройки за експортиране за екрани@@ -4658,7 +4660,6 @@ lighting	Light	Светлина 	Current Colors	Текущи цветове 	Current Colors (0)	Текущи цветове (0) 	Custom Scale	Персонализиран мащаб-	DWG (use DXF)	DWG (използване на DXF) 	Default Graphic Style	Стил на графика по подразбиране 	Diamond (open)	Диамант (отворен) 	Differences from VectorCraft Default:	Разлики от настройките по подразбиране на VectorCraft:diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv--- a/crates/ui-egui/src/i18n/cs.tsv+++ b/crates/ui-egui/src/i18n/cs.tsv@@ -2111,6 +2111,7 @@ lighting	Light	Světlo 	Scaling	Měřítko 	Alter Paths for Appearance	Upravit cesty podle vzhledu 	DXF Options	Možnosti DXF+	DWG Options	Možnosti DWG 	Export Option	Možnost exportu 	Export Selected Art Only	Exportovat jen vybranou kresbu 	Number of Colors:	Počet barev:@@ -3079,6 +3080,7 @@ theme	Appearance	Motiv 	DXF Info	Informace o DXF 	Export Document	Exportovat dokument 	Export DXF	Exportovat DXF+	Export DWG	Exportovat DWG 	Export EPS	Exportovat EPS 	Export for Screens	Exportovat pro obrazovky 	Export for Screens Settings	Nastavení exportu pro obrazovky@@ -4542,6 +4544,7 @@ imageTrace	Outline	Obrys @msg	{C}: no effect at index {from}	{C}: na indexu {from} není žádný efekt @msg	{_1} failed: {e}	{_1} selhalo: {e} @msg	{_1} files can be opened but not written (see document.formats)	soubory {_1} lze otevřít, ale ne zapisovat (viz document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	soubory {_1} zapisuje pouze desktopová aplikace (viz document.formats) @msg	{_1} files are written by file.saveAsTemplate	soubory {_1} zapisuje file.saveAsTemplate @msg	{_1} font families available	Dostupné rodiny písem: {_1} @msg	{_1} is a live effect (Effect › Plug-ins)	{_1} je živý efekt (Efekt › Zásuvné moduly)@@ -4983,7 +4986,6 @@ imageTrace	Outline	Obrys 	16 bits/pixel	16 bitů na pixel 	24 bits/pixel	24 bitů na pixel 	32 bits/pixel	32 bitů na pixel-	DWG (use DXF)	DWG (použijte DXF) 	Artistic	Umělecké 	Film Grain…	Filmové zrno… 	Cutout…	Výřez…diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv--- a/crates/ui-egui/src/i18n/da.tsv+++ b/crates/ui-egui/src/i18n/da.tsv@@ -1927,6 +1927,7 @@ lighting	Light	Lys 	Scaling	Skalering 	Alter Paths for Appearance	Ændr stier for udseendet 	DXF Options	DXF-indstillinger+	DWG Options	DWG-indstillinger 	Export Option	Eksportindstilling 	Export Selected Art Only	Eksportér kun markeret grafik 	Number of Colors:	Antal farver:@@ -2853,6 +2854,7 @@ theme	Appearance	Udseende 	DXF Info	DXF-info 	Export Document	Eksportér dokument 	Export DXF	Eksportér DXF+	Export DWG	Eksportér DWG 	Export EPS	Eksportér EPS 	Export for Screens	Eksportér til skærme 	Export for Screens Settings	Indstillinger for eksport til skærme@@ -4683,7 +4685,6 @@ imageTrace	Outline	Kontur 	16 bits/pixel	16 bits/pixel 	24 bits/pixel	24 bits/pixel 	32 bits/pixel	32 bits/pixel-	DWG (use DXF)	DWG (brug DXF) 	Artistic	Kunstnerisk 	Film Grain…	Filmkorn… 	Highlight Area	Fremhævet områdediff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv--- a/crates/ui-egui/src/i18n/de.tsv+++ b/crates/ui-egui/src/i18n/de.tsv@@ -839,6 +839,7 @@ 	DXF Import Options	DXF-Importoptionen 	DXF Info	DXF-Informationen 	DXF Options	DXF-Optionen+	DWG Options	DWG-Optionen 	DXF Options Dialog	Dialogfeld „DXF-Optionen“ 	e.g. 1-3, 5	z. B. 1-3, 5 	Each page prints as one image, at the document's raster effects resolution.	Jede Seite wird als ein Bild gedruckt, in der Rastereffektauflösung des Dokuments.@@ -1001,6 +1002,7 @@ 	Export CSS…	CSS exportieren… 	Export Document	Dokument exportieren 	Export DXF	DXF exportieren+	Export DWG	DWG exportieren 	Export EPS	EPS exportieren 	Export for Screens	Für Bildschirme exportieren 	Export for Screens Settings	Einstellungen für „Für Bildschirme exportieren“@@ -4179,6 +4181,7 @@ theme	Appearance	Erscheinungsbild @msg	{C}: no effect at index {from}	{C}: kein Effekt bei Index {from} @msg	{_1} failed: {e}	{_1} ist fehlgeschlagen: {e} @msg	{_1} files can be opened but not written (see document.formats)	{_1}-Dateien können geöffnet, aber nicht geschrieben werden (siehe document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	{_1}-Dateien schreibt nur die Desktop-App (siehe document.formats) @msg	{_1} files are written by file.saveAsTemplate	{_1}-Dateien werden von file.saveAsTemplate geschrieben @msg	{_1} font families available	{_1} Schriftfamilien verfügbar @msg	{_1} is a live effect (Effect › Plug-ins)	{_1} ist ein Live-Effekt (Effekt › Plug-ins)diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv--- a/crates/ui-egui/src/i18n/el.tsv+++ b/crates/ui-egui/src/i18n/el.tsv@@ -809,6 +809,7 @@ 	DXF Import Options	Επιλογές εισαγωγής DXF 	DXF Info	Πληροφορίες DXF 	DXF Options	Επιλογές DXF+	DWG Options	Επιλογές DWG 	DXF Options Dialog	Παράθυρο επιλογών DXF 	e.g. 1-3, 5	π.χ. 1-3, 5 	Earth Tones	Γήινοι τόνοι@@ -966,6 +967,7 @@ 	Export CSS…	Εξαγωγή CSS… 	Export Document	Εξαγωγή εγγράφου 	Export DXF	Εξαγωγή DXF+	Export DWG	Εξαγωγή DWG 	Export EPS	Εξαγωγή EPS 	Export for Screens	Εξαγωγή για οθόνες 	Export for Screens Settings	Ρυθμίσεις εξαγωγής για οθόνες@@ -4215,6 +4217,7 @@ theme	Appearance	Εμφάνιση @msg	{C}: no effect at index {from}	{C}: δεν υπάρχει εφέ στον δείκτη {from} @msg	{_1} failed: {e}	η ενέργεια {_1} απέτυχε: {e} @msg	{_1} files can be opened but not written (see document.formats)	τα αρχεία {_1} μπορούν να ανοιχτούν αλλά όχι να αποθηκευτούν (δείτε document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	τα αρχεία {_1} αποθηκεύονται μόνο από την εφαρμογή υπολογιστή (δείτε document.formats) @msg	{_1} files are written by file.saveAsTemplate	τα αρχεία {_1} αποθηκεύονται με το file.saveAsTemplate @msg	{_1} font families available	{_1} διαθέσιμες οικογένειες γραμματοσειρών @msg	{_1} is a live effect (Effect › Plug-ins)	το {_1} είναι δυναμικό εφέ (Εφέ › Πρόσθετα)@@ -4538,7 +4541,6 @@ theme	Appearance	Εμφάνιση 	Current Colors	Τρέχοντα χρώματα 	Current Colors (0)	Τρέχοντα χρώματα (0) 	Custom Scale	Προσαρμοσμένη κλίμακα-	DWG (use DXF)	DWG (χρησιμοποιήστε DXF) 	Default Graphic Style	Προεπιλεγμένο στυλ γραφικών 	Diamond (open)	Ρόμβος (ανοιχτός) 	Differences from VectorCraft Default:	Διαφορές από τις προεπιλογές VectorCraft:diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv--- a/crates/ui-egui/src/i18n/es.tsv+++ b/crates/ui-egui/src/i18n/es.tsv@@ -853,6 +853,7 @@ 	DXF Import Options	Opciones de importación DXF 	DXF Info	Información DXF 	DXF Options	Opciones DXF+	DWG Options	Opciones DWG 	DXF Options Dialog	Cuadro de diálogo Opciones DXF 	e.g. 1-3, 5	p. ej. 1-3, 5 	Each page prints as one image, at the document's raster effects resolution.	Cada página se imprime como una imagen, con la resolución de efectos de rasterización del documento.@@ -1017,6 +1018,7 @@ 	Export CSS…	Exportar CSS… 	Export Document	Exportar documento 	Export DXF	Exportar DXF+	Export DWG	Exportar DWG 	Export EPS	Exportar EPS 	Export for Screens	Exportar para pantallas 	Export for Screens Settings	Ajustes de Exportar para pantallas@@ -4241,6 +4243,7 @@ axis	Both	Ambos @msg	{C}: no effect at index {from}	{C}: no hay ningún efecto en el índice {from} @msg	{_1} failed: {e}	{_1} falló: {e} @msg	{_1} files can be opened but not written (see document.formats)	los archivos {_1} se pueden abrir pero no escribir (consulta document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	los archivos {_1} solo los escribe la aplicación de escritorio (consulta document.formats) @msg	{_1} files are written by file.saveAsTemplate	los archivos {_1} se escriben con file.saveAsTemplate @msg	{_1} font families available	{_1} familias de fuentes disponibles @msg	{_1} is a live effect (Effect › Plug-ins)	{_1} es un efecto interactivo (Efecto › Plugins)@@ -4871,7 +4874,6 @@ effect	Regular	Regular 	16 bits/pixel	16 bits/píxel 	24 bits/pixel	24 bits/píxel 	32 bits/pixel	32 bits/píxel-	DWG (use DXF)	DWG (usa DXF) file-filter	All readable files	Todos los archivos legibles file-filter	All placeable files	Todos los archivos que se pueden colocar file-filter	Swatch libraries	Bibliotecas de muestrasdiff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv--- a/crates/ui-egui/src/i18n/fi.tsv+++ b/crates/ui-egui/src/i18n/fi.tsv@@ -2072,6 +2072,7 @@ lighting	Light	Valo 	Scaling	Skaalaus 	Alter Paths for Appearance	Muuta polkuja ulkoasun mukaan 	DXF Options	DXF-asetukset+	DWG Options	DWG-asetukset 	Export Option	Vientiasetus 	Export Selected Art Only	Vie vain valittu kuvitus 	Number of Colors:	Värien määrä:@@ -3028,6 +3029,7 @@ theme	Appearance	Ulkoasu 	DXF Info	DXF-tiedot 	Export Document	Vie asiakirja 	Export DXF	Vie DXF+	Export DWG	Vie DWG 	Export EPS	Vie EPS 	Export for Screens	Vie näytöille 	Export for Screens Settings	Näytöille viennin asetukset@@ -4462,6 +4464,7 @@ imageTrace	Outline	Ääriviiva @msg	{C}: no effect at index {from}	{C}: ei tehostetta indeksissä {from} @msg	{_1} failed: {e}	{_1} epäonnistui: {e} @msg	{_1} files can be opened but not written (see document.formats)	{_1}-tiedostoja voi avata mutta ei kirjoittaa (ks. document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	{_1}-tiedostoja kirjoittaa vain työpöytäsovellus (ks. document.formats) @msg	{_1} files are written by file.saveAsTemplate	{_1}-tiedostot kirjoitetaan komennolla file.saveAsTemplate @msg	{_1} font families available	{_1} fonttiperhettä käytettävissä @msg	{_1} is a live effect (Effect › Plug-ins)	{_1} on live-tehoste (Tehoste › Laajennukset)@@ -4889,7 +4892,6 @@ imageTrace	Outline	Ääriviiva 	16 bits/pixel	16 bittiä/pikseli 	24 bits/pixel	24 bittiä/pikseli 	32 bits/pixel	32 bittiä/pikseli-	DWG (use DXF)	DWG (käytä DXF:ää) 	Artistic	Taiteelliset 	Film Grain…	Filmin rakeisuus… 	Cutout…	Leikkaus…diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv--- a/crates/ui-egui/src/i18n/fr.tsv+++ b/crates/ui-egui/src/i18n/fr.tsv@@ -855,6 +855,7 @@ 	DXF Import Options	Options d’importation DXF 	DXF Info	Informations DXF 	DXF Options	Options DXF+	DWG Options	Options DWG 	DXF Options Dialog	Boîte de dialogue Options DXF 	e.g. 1-3, 5	p. ex. 1-3, 5 	Each page prints as one image, at the document's raster effects resolution.	Chaque page s’imprime comme une seule image, à la résolution des effets de pixellisation du document.@@ -1017,6 +1018,7 @@ 	Export CSS…	Exporter le CSS… 	Export Document	Exporter le document 	Export DXF	Exporter en DXF+	Export DWG	Exporter en DWG 	Export EPS	Exporter en EPS 	Export for Screens	Exporter pour les écrans 	Export for Screens Settings	Paramètres d’Exporter pour les écrans@@ -4238,6 +4240,7 @@ axis	Both	Les deux @msg	{C}: no effect at index {from}	{C} : aucun effet à l’indice {from} @msg	{_1} failed: {e}	échec de {_1} : {e} @msg	{_1} files can be opened but not written (see document.formats)	les fichiers {_1} peuvent être ouverts mais pas écrits (voir document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	les fichiers {_1} ne sont écrits que par l’application de bureau (voir document.formats) @msg	{_1} files are written by file.saveAsTemplate	les fichiers {_1} sont écrits par file.saveAsTemplate @msg	{_1} font families available	{_1} familles de polices disponibles @msg	{_1} is a live effect (Effect › Plug-ins)	{_1} est un effet dynamique (Effet › Plug-ins)@@ -4746,7 +4749,6 @@ lighting	Light	Lumière 	Current Colors	Couleurs actuelles 	Current Colors (0)	Couleurs actuelles (0) 	Custom Scale	Échelle personnalisée-	DWG (use DXF)	DWG (utiliser DXF) 	Default Graphic Style	Style graphique par défaut 	Diamond (open)	Losange (ouvert) 	Differences from VectorCraft Default:	Différences par rapport à VectorCraft par défaut :diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv--- a/crates/ui-egui/src/i18n/hu.tsv+++ b/crates/ui-egui/src/i18n/hu.tsv@@ -1112,10 +1112,10 @@ 	Duplicate {kind} Style	Stílus kettőzése: {kind} 	Duplication	Kettőzés 	Dutch	Holland-	DWG (use DXF)	DWG (használd a DXF-et) 	DXF Import Options	DXF-importálás beállításai 	DXF Info	DXF-adatok 	DXF Options	DXF-beállítások+	DWG Options	DWG-beállítások 	DXF Options Dialog	DXF-beállítások párbeszédablak 	e.g. 1-3, 5	pl. 1-3, 5 	Each page prints as one image, at the document's raster effects resolution.	Minden oldal egyetlen képként nyomtatódik, a dokumentum raszteres effektusainak felbontásával.@@ -1299,6 +1299,7 @@ 	Export CSS…	CSS exportálása… 	Export Document	Dokumentum exportálása 	Export DXF	DXF exportálása+	Export DWG	DWG exportálása 	Export EPS	EPS exportálása 	Export for Screens	Exportálás képernyőkre 	Export for Screens Settings	Exportálás képernyőkre – beállítások@@ -4976,6 +4977,7 @@ imageTrace	Outline	Körvonal @msg	{_1} failed: {e}	sikertelen: {_1}: {e} @msg	{_1} files	{_1} fájlok @msg	{_1} files can be opened but not written (see document.formats)	ezek a fájlok megnyithatók, de nem írhatók: {_1} (lásd: document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	ezeket a fájlokat csak az asztali alkalmazás írja: {_1} (lásd: document.formats) @msg	{_1} files are written by file.saveAsTemplate	ezeket a fájlokat a file.saveAsTemplate írja: {_1} @msg	{_1} font families available	Elérhető betűcsaládok: {_1} @msg	{_1} has no {_2} style	ennek nincs ilyen stílusa: {_1} ({_2})diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv--- a/crates/ui-egui/src/i18n/id.tsv+++ b/crates/ui-egui/src/i18n/id.tsv@@ -1927,6 +1927,7 @@ lighting	Light	Cahaya 	Scaling	Penskalaan 	Alter Paths for Appearance	Ubah jalur untuk tampilan 	DXF Options	Opsi DXF+	DWG Options	Opsi DWG 	Export Option	Opsi ekspor 	Export Selected Art Only	Ekspor hanya karya seni terpilih 	Number of Colors:	Jumlah warna:@@ -2853,6 +2854,7 @@ theme	Appearance	Tampilan 	DXF Info	Info DXF 	Export Document	Ekspor dokumen 	Export DXF	Ekspor DXF+	Export DWG	Ekspor DWG 	Export EPS	Ekspor EPS 	Export for Screens	Ekspor untuk layar 	Export for Screens Settings	Pengaturan ekspor untuk layar@@ -4683,7 +4685,6 @@ imageTrace	Outline	Kontur 	16 bits/pixel	16 bit/piksel 	24 bits/pixel	24 bit/piksel 	32 bits/pixel	32 bit/piksel-	DWG (use DXF)	DWG (gunakan DXF) 	Artistic	Artistik 	Film Grain…	Butiran film… 	Highlight Area	Area sorotandiff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv--- a/crates/ui-egui/src/i18n/it.tsv+++ b/crates/ui-egui/src/i18n/it.tsv@@ -853,6 +853,7 @@ 	DXF Import Options	Opzioni di importazione DXF 	DXF Info	Informazioni DXF 	DXF Options	Opzioni DXF+	DWG Options	Opzioni DWG 	DXF Options Dialog	Finestra di dialogo Opzioni DXF 	e.g. 1-3, 5	es. 1-3, 5 	Each page prints as one image, at the document's raster effects resolution.	Ogni pagina viene stampata come un'unica immagine, alla risoluzione degli effetti raster del documento.@@ -1017,6 +1018,7 @@ 	Export CSS…	Esporta CSS… 	Export Document	Esporta documento 	Export DXF	Esporta DXF+	Export DWG	Esporta DWG 	Export EPS	Esporta EPS 	Export for Screens	Esporta per schermi 	Export for Screens Settings	Impostazioni Esporta per schermi@@ -4280,6 +4282,7 @@ axis	Both	Entrambi @msg	{C}: no effect at index {from}	{C}: nessun effetto all'indice {from} @msg	{_1} failed: {e}	errore in {_1}: {e} @msg	{_1} files can be opened but not written (see document.formats)	i file {_1} possono essere aperti ma non scritti (vedi document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	i file {_1} vengono scritti solo dall'app desktop (vedi document.formats) @msg	{_1} files are written by file.saveAsTemplate	i file {_1} vengono scritti da file.saveAsTemplate @msg	{_1} font families available	{_1} famiglie di font disponibili @msg	{_1} is a live effect (Effect › Plug-ins)	{_1} è un effetto dinamico (Effetto › Plug-in)diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv--- a/crates/ui-egui/src/i18n/ja.tsv+++ b/crates/ui-egui/src/i18n/ja.tsv@@ -1177,6 +1177,7 @@ 	DXF Import Options	DXF 読み込みオプション 	DXF Info	DXF 情報 	DXF Options	DXF オプション+	DWG Options	DWG オプション 	DXF Options Dialog	DXF オプションダイアログ 	Darken	比較 (暗) 	Darkness	濃さ@@ -1432,6 +1433,7 @@ 	Export CSS	CSS を書き出し 	Export CSS…	CSS を書き出し… 	Export DXF	DXF を書き出し+	Export DWG	DWG を書き出し 	Export Document	ドキュメントを書き出し 	Export EPS	EPS を書き出し 	Export Keyboard Shortcuts…	キーボードショートカットを書き出し…@@ -3920,6 +3922,7 @@ swatch	Bright Violet	ビビッドバイオレット @msg	this file was saved without PDF compatibility, and its editing data can't be read ({why})	このファイルは PDF 互換性なしで保存されており、編集データを読み込めません ({why}) @msg	the export wrote no file	書き出しでファイルが作成されませんでした @msg	{_1} files can be opened but not written (see document.formats)	{_1} ファイルは開けますが、保存や書き出しはできません (document.formats を参照してください)+@msg	{_1} files are written by the desktop app only (see document.formats)	{_1} ファイルはデスクトップアプリでのみ書き出せます (document.formats を参照してください) @msg	{_1} files are written by file.saveAsTemplate	{_1} ファイルは file.saveAsTemplate で保存します @msg	{path}: no file system here (pass dataBase64 to open; omit the path to get dataBase64)	{path}: この環境にはファイルシステムがありません (開くには dataBase64 を渡し、dataBase64 を受け取るにはパスを省略してください) @msg	Save writes {_1}, not {_2}: use document.export for other formats	保存できる形式は {_1} のみで、{_2} 形式では保存できません: ほかの形式には document.export を使用してくださいdiff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv--- a/crates/ui-egui/src/i18n/ka.tsv+++ b/crates/ui-egui/src/i18n/ka.tsv@@ -1826,6 +1826,7 @@ lighting	Light	სინათლე 	Scaling	მასშტაბირება 	Alter Paths for Appearance	ტრაექტორიების შეცვლა იერსახის შესანარჩუნებლად 	DXF Options	DXF-ის პარამეტრები+	DWG Options	DWG-ის პარამეტრები 	Export Option	ექსპორტის პარამეტრი 	Export Selected Art Only	მხოლოდ მონიშნული ნახატის ექსპორტირება 	Number of Colors:	ფერების რაოდენობა:@@ -2666,6 +2667,7 @@ theme	Appearance	იერსახე 	DXF Info	DXF ინფორმაცია 	Export Document	დოკუმენტის ექსპორტირება 	Export DXF	DXF ექსპორტი+	Export DWG	DWG ექსპორტი 	Export EPS	EPS ექსპორტი 	Export for Screens	ეკრანებისთვის ექსპორტირება 	Export for Screens Settings	ეკრანებისთვის ექსპორტის პარამეტრები@@ -4015,6 +4017,7 @@ effect	Regular	ჩვეულებრივი @msg	{C}: no effect at index {from}	{C}: ინდექსზე {from} ეფექტი არ არის @msg	{_1} failed: {e}	{_1} ვერ შესრულდა: {e} @msg	{_1} files can be opened but not written (see document.formats)	{_1} ფაილების გახსნა შესაძლებელია, ჩაწერა — არა (იხ. document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	{_1} ფაილებს მხოლოდ დესკტოპის აპი წერს (იხ. document.formats) @msg	{_1} font families available	ხელმისაწვდომი შრიფტების ოჯახები: {_1} @msg	{_1} is a live effect (Effect › Plug-ins)	{_1} ცოცხალი ეფექტია (ეფექტი › პლაგინები) @msg	{_1} is an exporter (File › Export › Export As…)	{_1} ექსპორტის პლაგინია (ფაილი › ექსპორტირება › ექსპორტირება ფორმატში…)@@ -4411,7 +4414,6 @@ effect	Regular	ჩვეულებრივი 	16 bits/pixel	16 ბიტი/პიქსელი 	24 bits/pixel	24 ბიტი/პიქსელი 	32 bits/pixel	32 ბიტი/პიქსელი-	DWG (use DXF)	DWG (გამოიყენეთ DXF) # Strings added upstream (sync 2026-10-13). 	Horizontal & Vertical Align Center	ჰორიზონტალურად და ვერტიკალურად ცენტრში სწორება 	Find Linked Files	დაკავშირებული ფაილების მოძებნაdiff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv--- a/crates/ui-egui/src/i18n/ko.tsv+++ b/crates/ui-egui/src/i18n/ko.tsv@@ -1172,6 +1172,7 @@ 	DXF Import Options	DXF 가져오기 옵션 	DXF Info	DXF 정보 	DXF Options	DXF 옵션+	DWG Options	DWG 옵션 	DXF Options Dialog	DXF 옵션 대화상자 	Darken	어둡게 	Darkness	어둡기@@ -1427,6 +1428,7 @@ 	Export CSS	CSS 내보내기 	Export CSS…	CSS 내보내기… 	Export DXF	DXF 내보내기+	Export DWG	DWG 내보내기 	Export Document	문서 내보내기 	Export EPS	EPS 내보내기 	Export Keyboard Shortcuts…	단축키 내보내기…@@ -3852,6 +3854,7 @@ theme	Appearance	외관 @msg	this file was saved without PDF compatibility, and its editing data can't be read ({why})	이 파일은 PDF 호환성 없이 저장되어 편집 데이터를 읽을 수 없습니다({why}) @msg	the export wrote no file	내보내기에서 파일이 만들어지지 않았습니다 @msg	{_1} files can be opened but not written (see document.formats)	{_1} 파일은 열 수 있지만 쓸 수 없습니다(document.formats 참조)+@msg	{_1} files are written by the desktop app only (see document.formats)	{_1} 파일은 데스크톱 앱에서만 쓸 수 있습니다(document.formats 참조) @msg	{_1} files are written by file.saveAsTemplate	{_1} 파일은 file.saveAsTemplate로 기록합니다 @msg	{path}: no file system here (pass dataBase64 to open; omit the path to get dataBase64)	{path}: 여기에는 파일 시스템이 없습니다(열려면 dataBase64를 전달하고, dataBase64를 얻으려면 경로를 생략하세요) @msg	Save writes {_1}, not {_2}: use document.export for other formats	저장은 {_2}이(가) 아닌 {_1}(으)로 기록합니다. 다른 형식은 document.export를 사용하세요diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv--- a/crates/ui-egui/src/i18n/nl.tsv+++ b/crates/ui-egui/src/i18n/nl.tsv@@ -1941,6 +1941,7 @@ lighting	Light	Licht 	Scaling	Schaling 	Alter Paths for Appearance	Paden aanpassen voor uiterlijk 	DXF Options	DXF-opties+	DWG Options	DWG-opties 	Export Option	Exportoptie 	Export Selected Art Only	Alleen geselecteerde artwork exporteren 	Number of Colors:	Aantal kleuren:@@ -2867,6 +2868,7 @@ theme	Appearance	Vormgeving 	DXF Info	DXF-info 	Export Document	Document exporteren 	Export DXF	DXF exporteren+	Export DWG	DWG exporteren 	Export EPS	EPS exporteren 	Export for Screens	Exporteren voor schermen 	Export for Screens Settings	Instellingen voor schermexport@@ -4695,7 +4697,6 @@ imageTrace	Outline	Contour 	16 bits/pixel	16 bits/pixel 	24 bits/pixel	24 bits/pixel 	32 bits/pixel	32 bits/pixel-	DWG (use DXF)	DWG (gebruik DXF) 	Artistic	Artistiek 	Film Grain…	Filmkorrel… 	Highlight Area	Hooglichtgebieddiff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv--- a/crates/ui-egui/src/i18n/pl.tsv+++ b/crates/ui-egui/src/i18n/pl.tsv@@ -2026,6 +2026,7 @@ lighting	Light	Światło 	Scaling	Skalowanie 	Alter Paths for Appearance	Zmień ścieżki dla zachowania wyglądu 	DXF Options	Opcje DXF+	DWG Options	Opcje DWG 	Export Option	Opcja eksportu 	Export Selected Art Only	Eksportuj tylko zaznaczoną kompozycję 	Number of Colors:	Liczba kolorów:@@ -2957,6 +2958,7 @@ theme	Appearance	Wygląd 	DXF Info	Informacje DXF 	Export Document	Eksportuj dokument 	Export DXF	Eksportuj DXF+	Export DWG	Eksportuj DWG 	Export EPS	Eksportuj EPS 	Export for Screens	Eksportuj na ekrany 	Export for Screens Settings	Ustawienia eksportu na ekrany@@ -4367,6 +4369,7 @@ imageTrace	Outline	Kontur @msg	{C}: no effect at index {from}	{C}: brak efektu o indeksie {from} @msg	{_1} failed: {e}	{_1} — niepowodzenie: {e} @msg	{_1} files can be opened but not written (see document.formats)	pliki {_1} można otwierać, ale nie zapisywać (patrz document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	pliki {_1} zapisuje tylko aplikacja desktopowa (patrz document.formats) @msg	{_1} files are written by file.saveAsTemplate	pliki {_1} zapisuje file.saveAsTemplate @msg	{_1} font families available	dostępne rodziny czcionek: {_1} @msg	{_1} is a live effect (Effect › Plug-ins)	{_1} to efekt na żywo (Efekt › Wtyczki)@@ -4816,7 +4819,6 @@ imageTrace	Outline	Kontur 	16 bits/pixel	16 bitów/piksel 	24 bits/pixel	24 bity/piksel 	32 bits/pixel	32 bity/piksel-	DWG (use DXF)	DWG (użyj DXF) 	Artistic	Artystyczne 	Film Grain…	Ziarno filmu… 	Cutout…	Wycinanka…diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv--- a/crates/ui-egui/src/i18n/pt-br.tsv+++ b/crates/ui-egui/src/i18n/pt-br.tsv@@ -593,6 +593,7 @@ 	DXF Import Options	Opções de Importação DXF 	DXF Info	Informações DXF 	DXF Options	Opções DXF+	DWG Options	Opções DWG 	DXF Options Dialog	Diálogo de Opções DXF 	Dark	Escuro 	Darken	Escurecer@@ -908,6 +909,7 @@ 	Export CSS…	Exportar CSS… 	Export Data Sets…	Exportar Conjuntos de Dados… 	Export DXF	Exportar DXF+	Export DWG	Exportar DWG 	Export Document	Exportar Documento 	Export EPS	Exportar EPS 	Export Keyboard Shortcuts…	Exportar Atalhos de Teclado…@@ -3821,6 +3823,7 @@ axis	Both	Ambos @msg	{C}: no effect at index {from}	{C}: nenhum efeito no índice {from} @msg	{_1} failed: {e}	{_1} falhou: {e} @msg	{_1} files can be opened but not written (see document.formats)	arquivos {_1} podem ser abertos, mas não gravados (veja document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	arquivos {_1} só são gravados pelo aplicativo para desktop (veja document.formats) @msg	{_1} files are written by file.saveAsTemplate	arquivos {_1} são gravados por file.saveAsTemplate @msg	{_1} font families available	{_1} famílias de fontes disponíveis @msg	{_1} is a live effect (Effect › Plug-ins)	{_1} é um efeito dinâmico (Efeito › Plug-ins)@@ -4867,7 +4870,6 @@ effect	Regular	Regular 	16 bits/pixel	16 bits/pixel 	24 bits/pixel	24 bits/pixel 	32 bits/pixel	32 bits/pixel-	DWG (use DXF)	DWG (use DXF) file-filter	All readable files	Todos os arquivos legíveis file-filter	All placeable files	Todos os arquivos que podem ser colocados file-filter	Swatch libraries	Bibliotecas de amostrasdiff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv--- a/crates/ui-egui/src/i18n/ru.tsv+++ b/crates/ui-egui/src/i18n/ru.tsv@@ -851,6 +851,7 @@ 	DXF Import Options	Параметры импорта DXF 	DXF Info	Сведения о DXF 	DXF Options	Параметры DXF+	DWG Options	Параметры DWG 	DXF Options Dialog	Диалоговое окно «Параметры DXF» 	e.g. 1-3, 5	напр. 1-3, 5 	Each page prints as one image, at the document's raster effects resolution.	Каждая страница печатается как одно изображение с разрешением растровых эффектов документа.@@ -1015,6 +1016,7 @@ 	Export CSS…	Экспортировать CSS… 	Export Document	Экспортировать документ 	Export DXF	Экспортировать DXF+	Export DWG	Экспортировать DWG 	Export EPS	Экспортировать EPS 	Export for Screens	Экспортировать для экранов 	Export for Screens Settings	Параметры экспорта для экранов@@ -4276,6 +4278,7 @@ axis	Both	Оба @msg	{C}: no effect at index {from}	{C}: нет эффекта с индексом {from} @msg	{_1} failed: {e}	{_1} завершился с ошибкой: {e} @msg	{_1} files can be opened but not written (see document.formats)	файлы {_1} можно открыть, но не записать (см. document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	файлы {_1} записывает только настольное приложение (см. document.formats) @msg	{_1} files are written by file.saveAsTemplate	файлы {_1} записывает file.saveAsTemplate @msg	{_1} font families available	доступно семейств шрифтов: {_1} @msg	{_1} is a live effect (Effect › Plug-ins)	{_1} — динамический эффект (Эффект › Модули)diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv--- a/crates/ui-egui/src/i18n/sv.tsv+++ b/crates/ui-egui/src/i18n/sv.tsv@@ -840,6 +840,7 @@ 	DXF Import Options	Importalternativ för DXF 	DXF Info	DXF-info 	DXF Options	DXF-alternativ+	DWG Options	DWG-alternativ 	DXF Options Dialog	Dialogruta för DXF-alternativ 	e.g. 1-3, 5	t.ex. 1-3, 5 	Each page prints as one image, at the document's raster effects resolution.	Varje sida skrivs ut som en bild, med dokumentets upplösning för rastereffekter.@@ -1002,6 +1003,7 @@ 	Export CSS…	Exportera CSS… 	Export Document	Exportera dokument 	Export DXF	Exportera DXF+	Export DWG	Exportera DWG 	Export EPS	Exportera EPS 	Export for Screens	Exportera för skärmar 	Export for Screens Settings	Inställningar för Exportera för skärmar@@ -4163,6 +4165,7 @@ axis	Both	Båda @msg	{C}: no effect at index {from}	{C}: ingen effekt vid index {from} @msg	{_1} failed: {e}	{_1} misslyckades: {e} @msg	{_1} files can be opened but not written (see document.formats)	{_1}-filer kan öppnas men inte skrivas (se document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	{_1}-filer skrivs bara av skrivbordsappen (se document.formats) @msg	{_1} files are written by file.saveAsTemplate	{_1}-filer skrivs av file.saveAsTemplate @msg	{_1} font families available	{_1} teckensnittsfamiljer tillgängliga @msg	{_1} is a live effect (Effect › Plug-ins)	{_1} är en live-effekt (Effekt › Insticksprogram)@@ -4664,7 +4667,6 @@ lighting	Light	Ljus 	Current Colors	Aktuella färger 	Current Colors (0)	Aktuella färger (0) 	Custom Scale	Anpassad skala-	DWG (use DXF)	DWG (använd DXF) 	Default Graphic Style	Standardgrafikstil 	Diamond (open)	Romb (öppen) 	Differences from VectorCraft Default:	Skillnader från VectorCraft-standard:diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv--- a/crates/ui-egui/src/i18n/uk.tsv+++ b/crates/ui-egui/src/i18n/uk.tsv@@ -847,6 +847,7 @@ 	DXF Import Options	Параметри імпорту DXF 	DXF Info	Відомості про DXF 	DXF Options	Параметри DXF+	DWG Options	Параметри DWG 	DXF Options Dialog	Діалогове вікно «Параметри DXF» 	e.g. 1-3, 5	напр. 1-3, 5 	Each page prints as one image, at the document's raster effects resolution.	Кожна сторінка друкується як одне зображення з роздільною здатністю растрових ефектів документа.@@ -1011,6 +1012,7 @@ 	Export CSS…	Експортувати CSS… 	Export Document	Експортувати документ 	Export DXF	Експортувати DXF+	Export DWG	Експортувати DWG 	Export EPS	Експортувати EPS 	Export for Screens	Експортувати для екранів 	Export for Screens Settings	Параметри експорту для екранів@@ -4264,6 +4266,7 @@ axis	Both	Обидві @msg	{C}: no effect at index {from}	{C}: немає ефекту з індексом {from} @msg	{_1} failed: {e}	{_1} завершився з помилкою: {e} @msg	{_1} files can be opened but not written (see document.formats)	файли {_1} можна відкрити, але не записати (див. document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	файли {_1} записує лише настільна програма (див. document.formats) @msg	{_1} files are written by file.saveAsTemplate	файли {_1} записує file.saveAsTemplate @msg	{_1} font families available	доступно сімейства шрифтів: {_1} @msg	{_1} is a live effect (Effect › Plug-ins)	{_1} - динамічний ефект (Ефект › Модулі)diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv--- a/crates/ui-egui/src/i18n/vi.tsv+++ b/crates/ui-egui/src/i18n/vi.tsv@@ -999,6 +999,7 @@ 	DXF Import Options	Tùy chọn nhập DXF 	DXF Info	Thông tin DXF 	DXF Options	Tùy chọn DXF+	DWG Options	Tùy chọn DWG 	DXF Options Dialog	Hộp thoại tùy chọn DXF 	e.g. 1-3, 5	ví dụ: 1-3, 5 	Each page prints as one image, at the document's raster effects resolution.	Mỗi trang in như một ảnh, theo độ phân giải hiệu ứng raster của tài liệu.@@ -1177,6 +1178,7 @@ 	Export CSS…	Xuất CSS… 	Export Document	Xuất tài liệu 	Export DXF	Xuất DXF+	Export DWG	Xuất DWG 	Export EPS	Xuất EPS 	Export for Screens	Xuất cho màn hình 	Export for Screens Settings	Cài đặt xuất cho màn hình@@ -4634,6 +4636,7 @@ theme	Appearance	Giao diện @msg	{_1} failed: {e}	{_1} thất bại: {e} @msg	{_1} files	{_1} tệp @msg	{_1} files can be opened but not written (see document.formats)	tệp {_1} có thể mở nhưng không thể ghi (xem document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	tệp {_1} chỉ được ghi bởi ứng dụng máy tính (xem document.formats) @msg	{_1} files are written by file.saveAsTemplate	tệp {_1} được ghi bằng file.saveAsTemplate @msg	{_1} font families available	{_1} họ phông chữ khả dụng @msg	{_1} has no {_2} style	{_1} không có kiểu {_2}diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv--- a/crates/ui-egui/src/i18n/zh-hans.tsv+++ b/crates/ui-egui/src/i18n/zh-hans.tsv@@ -803,6 +803,7 @@ 	DXF Import Options	DXF 导入选项 	DXF Info	DXF 信息 	DXF Options	DXF 选项+	DWG Options	DWG 选项 	DXF Options Dialog	DXF 选项对话框 	e.g. 1-3, 5	例如 1-3, 5 	Each page prints as one image, at the document's raster effects resolution.	每页会以文档的栅格效果分辨率打印为单一图像。@@ -966,6 +967,7 @@ 	Export CSS…	导出 CSS… 	Export Document	导出文档 	Export DXF	导出 DXF+	Export DWG	导出 DWG 	Export EPS	导出 EPS 	Export for Screens	导出为屏幕所用格式 	Export for Screens Settings	导出为屏幕所用格式设置@@ -3675,6 +3677,7 @@ imageTrace	Outline	轮廓 @msg	this file was saved without PDF compatibility, and its editing data can't be read ({why})	此文件保存时未启用 PDF 兼容性,且无法读取其编辑数据 ({why}) @msg	the export wrote no file	导出未写入任何文件 @msg	{_1} files can be opened but not written (see document.formats)	{_1} 文件可以打开,但不能写入 (请参阅 document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	{_1} 文件只能由桌面应用写入 (请参阅 document.formats) @msg	{_1} files are written by file.saveAsTemplate	{_1} 文件由 file.saveAsTemplate 写入 @msg	{path}: no file system here (pass dataBase64 to open; omit the path to get dataBase64)	{path}:此处没有文件系统 (要打开文件,请传入 dataBase64;省略路径可获取 dataBase64) @msg	Save writes {_1}, not {_2}: use document.export for other formats	保存会写入 {_1},而不是 {_2}:其他格式请使用 document.exportdiff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv--- a/crates/ui-egui/src/i18n/zh-hant.tsv+++ b/crates/ui-egui/src/i18n/zh-hant.tsv@@ -822,6 +822,7 @@ 	DXF Import Options	DXF 匯入選項 	DXF Info	DXF 資訊 	DXF Options	DXF 選項+	DWG Options	DWG 選項 	DXF Options Dialog	DXF 選項對話框 	e.g. 1-3, 5	例如 1-3, 5 	Each page prints as one image, at the document's raster effects resolution.	每頁會以文件的點陣效果解析度列印為單一影像。@@ -988,6 +989,7 @@ 	Export CSS…	匯出 CSS… 	Export Document	匯出文件 	Export DXF	匯出 DXF+	Export DWG	匯出 DWG 	Export EPS	匯出 EPS 	Export for Screens	匯出為螢幕適用 	Export for Screens Settings	匯出為螢幕適用設定@@ -1681,6 +1683,7 @@ file-filter	Templates	範本 @msg	this file was saved without PDF compatibility, and its editing data can't be read ({why})	此檔案儲存時未啟用 PDF 相容性,且無法讀取其編輯資料 ({why}) @msg	the export wrote no file	匯出未寫入任何檔案 @msg	{_1} files can be opened but not written (see document.formats)	{_1} 檔案可以開啟,但無法寫入 (請參閱 document.formats)+@msg	{_1} files are written by the desktop app only (see document.formats)	{_1} 檔案只能由桌面應用程式寫入 (請參閱 document.formats) @msg	{_1} files are written by file.saveAsTemplate	{_1} 檔案由 file.saveAsTemplate 寫入 @msg	{path}: no file system here (pass dataBase64 to open; omit the path to get dataBase64)	{path}:此處沒有檔案系統 (若要開啟,請傳入 dataBase64;不提供路徑即可取得 dataBase64) @msg	Save writes {_1}, not {_2}: use document.export for other formats	「儲存」會寫入 {_1},而非 {_2}:其他格式請使用 document.exportdiff --git a/crates/ui-egui/src/io.rs b/crates/ui-egui/src/io.rs--- a/crates/ui-egui/src/io.rs+++ b/crates/ui-egui/src/io.rs@@ -1132,7 +1132,7 @@ mod tests {         assert!(vectorcraft_format::sniff(&w[1].1));         assert_eq!(app.session.active().unwrap().path.as_deref(), Some("/tmp/doc.vectorcraft"));         drop(w);-        assert!(export(&mut app, None, Some("/tmp/x.dwg".into()), &Value::Null).is_err(), "DWG can't be written");+        assert!(export(&mut app, None, Some("/tmp/x.pict".into()), &Value::Null).is_err(), "PICT can't be written");     }      #[test]diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs--- a/crates/ui-egui/src/menus.rs+++ b/crates/ui-egui/src/menus.rs@@ -776,7 +776,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[         "ui.dxfOptionsDialog",         "DXF Options Dialog",         "",-        "{path?, useArtboards?, range?, …document.exportDxf options} open DXF Options (dialog `dxfOptions`: fields = these options over the ones used last); OK checks them, remembers them and writes path (else asks). Export As… → DXF opens it too",+        "{format?: dxf (default)|dwg, path?, useArtboards?, range?, …document.exportDxf options} open DXF Options, or DWG Options for format: dwg (dialog `dxfOptions`: fields = these options over the ones used last; DWG offers the versions from 2000); OK checks them, remembers them and writes path (else asks). Export As… → DXF or DWG opens it too",     ),     (         "links.editOriginal",diff --git a/docs/control-protocol.md b/docs/control-protocol.md--- a/docs/control-protocol.md+++ b/docs/control-protocol.md@@ -597,8 +597,10 @@ DXF Options: Export As… with the DXF format (`file.exportAs {format: "dxf"}`, (`8`, `16`, `256`, `true`), `rasterFormat` (`png`, `jpeg`), `preserve` (`appearance`, `editability`), `selectedOnly`, `alterPaths`, `outlineText`, plus `path` and the artboard choice. `ui.dialog.confirm` checks them (a bad value, or Export Selected Art Only with nothing selected, keeps the dialog open), remembers them for the next DXF Options and-writes the file (asking for a path when there is none). Export As also lists DWG, greyed out: setting its `format` to-`dwg` shows, and on confirm answers, the hint to export DXF instead.+writes the file (asking for a path when there is none). Export As… with DWG (`file.exportAs {format: "dwg"}`), or+`ui.dxfOptionsDialog {format: "dwg", …}`, opens the same dialog as DWG Options (`format: "dwg"`): the same fields, with+the versions DWG is written as (2000 to 2018); DXF and DWG share the options remembered. DWG is written on the desktop+only: the web app answers an error.  DXF Import Options: opening a DXF drawing through the app (`app.open`, `file.open`, a drop) or placing one (`file.place` with a `.dxf` file and no `dxf` options, which answers `{"dialog": "dxfImport"}`) opens the `dxfImport`diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md--- a/docs/file-format-parity.md+++ b/docs/file-format-parity.md@@ -1,6 +1,6 @@ # File format parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-18 · **Change:** minor (PDF invisible text, as a scanned page's recognized text is, opens as type with no fill or stroke and is written back as invisible text, #2675; DXF circular cubic paths export as native CIRCLE/ARC where stroke semantics permit (R13–2018 for weighted strokes; R12 weighted strokes remain POLYLINE) (#1299); non-circular curves stay SPLINE; scores and hours unchanged; an Illustrator 3 file without layers, as image editors export their paths, opens with its art on one layer, storytold/photocraft#6036; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; batch exporting, #1935; legacy `.ai` runs as PostScript with a prolog of its own, #868; legacy `.ai` of version 7 saved, with type as text, hidden objects and an artboard picker, and its type read, #868; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; later 2026-10-14 merges: SVG `:not()` selectors, #1557, stacked selectors, #1703, linked files, #1870; DXF VPORT, #1789; nesting caps, #1852; hostile PDF, `.ai`, EPS, Affinity and native data, #1862, #1863, #1871–#1873; failed saves, #1685, #1693, #1694; type in symbols after reopening, #1738; scores unchanged; verified PngSuite downloads, #1729; EPS stitched shadings, #1653; oversized images in WMF/EMF refused from the header, #1776; PDF name trees with shared nodes, #1781; native size limits for files read from memory, #1783; private temporary files on Unix, #1778; ruzstd 0.9.1, #1807; scores unchanged; SVG import: CSS transforms and the `font` shorthand, #1565, percentage lengths in symbols, #1596, Inkscape flowed text and pages, #1568, #1601, filter warnings, #1598; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment in JPEG, TIFF and PSD, #1584; saves to folders that refuse the temporary file, #1620 in part; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; compact native saves for documents with a lot of path data, #1492; PDF and EPS paste as vector art, #1499; `.ai` gradient stops listed upwards, #1530; SVG text on basic shapes, #1527; PDF soft masks drawn as images, #1551; Type 42 fonts in EPS, #1553; clips that only seem to cover the page, #1542; images too large to decode are reported, #1540; a `.ai` page's art past its artboard no longer lies over the page before it when read as its PDF part; grayscale TIFFs with an alpha channel or extra channels place and open, #1466 in part; a faded or masked fill keeps its stroke apart on PDF and `.ai` import, #1434; SVG knockout groups without masks, #1435; Affinity import: a text frame too short for its first line is made taller, #1423; `.ai` editing data: a compound path takes its last path's paint and keeps paths grouped inside it, #1410; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` gradient matrices, meshes and artboard names from the editing data; `.ai` art styles, clipping fills and compound looks from the editing data; `editingData: "always"` opens a `.ai` from its editing data; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` gradient matrices, meshes and artboard names from the editing data; `.ai` art styles, clipping fills and compound looks from the editing data; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` gradient matrices, meshes and artboard names from the editing data; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; SVG knockout groups of opaque art without masks; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` keeps its RGB numbers; PDF import keeps a stroke apart from a fill masked or faded by its group) · **Target:** Adobe Illustrator 2026 (30.x)+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-20 · **Change:** minor (DWG export (2000–2018, desktop) converts the DXF output with the acadrust crate, #3068; PDF invisible text, as a scanned page's recognized text is, opens as type with no fill or stroke and is written back as invisible text, #2675; DXF circular cubic paths export as native CIRCLE/ARC where stroke semantics permit (R13–2018 for weighted strokes; R12 weighted strokes remain POLYLINE) (#1299); non-circular curves stay SPLINE; scores and hours unchanged; an Illustrator 3 file without layers, as image editors export their paths, opens with its art on one layer, storytold/photocraft#6036; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; batch exporting, #1935; legacy `.ai` runs as PostScript with a prolog of its own, #868; legacy `.ai` of version 7 saved, with type as text, hidden objects and an artboard picker, and its type read, #868; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; later 2026-10-14 merges: SVG `:not()` selectors, #1557, stacked selectors, #1703, linked files, #1870; DXF VPORT, #1789; nesting caps, #1852; hostile PDF, `.ai`, EPS, Affinity and native data, #1862, #1863, #1871–#1873; failed saves, #1685, #1693, #1694; type in symbols after reopening, #1738; scores unchanged; verified PngSuite downloads, #1729; EPS stitched shadings, #1653; oversized images in WMF/EMF refused from the header, #1776; PDF name trees with shared nodes, #1781; native size limits for files read from memory, #1783; private temporary files on Unix, #1778; ruzstd 0.9.1, #1807; scores unchanged; SVG import: CSS transforms and the `font` shorthand, #1565, percentage lengths in symbols, #1596, Inkscape flowed text and pages, #1568, #1601, filter warnings, #1598; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment in JPEG, TIFF and PSD, #1584; saves to folders that refuse the temporary file, #1620 in part; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; compact native saves for documents with a lot of path data, #1492; PDF and EPS paste as vector art, #1499; `.ai` gradient stops listed upwards, #1530; SVG text on basic shapes, #1527; PDF soft masks drawn as images, #1551; Type 42 fonts in EPS, #1553; clips that only seem to cover the page, #1542; images too large to decode are reported, #1540; a `.ai` page's art past its artboard no longer lies over the page before it when read as its PDF part; grayscale TIFFs with an alpha channel or extra channels place and open, #1466 in part; a faded or masked fill keeps its stroke apart on PDF and `.ai` import, #1434; SVG knockout groups without masks, #1435; Affinity import: a text frame too short for its first line is made taller, #1423; `.ai` editing data: a compound path takes its last path's paint and keeps paths grouped inside it, #1410; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` gradient matrices, meshes and artboard names from the editing data; `.ai` art styles, clipping fills and compound looks from the editing data; `editingData: "always"` opens a `.ai` from its editing data; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` gradient matrices, meshes and artboard names from the editing data; `.ai` art styles, clipping fills and compound looks from the editing data; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` gradient matrices, meshes and artboard names from the editing data; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; SVG knockout groups of opaque art without masks; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` keeps its RGB numbers; PDF import keeps a stroke apart from a fill masked or faded by its group) · **Target:** Adobe Illustrator 2026 (30.x)  Every format Illustrator opens, places, saves or exports, against VectorCraft's format table (`FORMATS`, `OPEN_EXTS`, `PLACE_EXTS` in `crates/engine/src/cmd/fileio/mod.rs`). Illustrator's list comes from its public@@ -19,7 +19,7 @@ from the public specifications, and users' own `.ai` files are used locally only |---|---:|---:|---| | Formats opened or placed | ~30 | 25 | missing: FreeHand, CorelDRAW, DWG, HEIC/HEIF, JPEG 2000, PICT, PCX, Pixar, Word `.doc`, PS | | Formats saved (Save As) | 6 (AI, AIT, PDF, EPS, SVG, SVGZ) | 6 + native | `.ai` written PDF-compatible (no Illustrator editing data) or as legacy version 7 (layers and groups, one artboard per file) |-| Formats exported | ~16 | 17 | missing: DWG, 3D (OBJ/USDA/glTF); extra: GIF/PNG-8 palette export, DXF up to 2018, layered PSD |+| Formats exported | ~16 | 18 | missing: 3D (OBJ/USDA/glTF); extra: GIF/PNG-8 palette export, DXF up to 2018, layered PSD | | **Overall (weighted by use)** | | **~75%** | `.ai` and PDF dominate the weighting |  EPS editing-data follow-up (2026-10-16): both mesh-corner layouts (with and without opacity)@@ -98,7 +98,7 @@ the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPri | 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) | | 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 |+| DWG | open, export | no | yes (2000–2018, desktop) | the DXF export converted by the acadrust crate (MPL-2.0, as CADCraft does); opening a DWG answers an error | | EMF / WMF | open, export | yes | yes | an oversized embedded PNG or JPEG is refused from its header, before any pixels are decoded (#1776) | | PSD | open, place, export | yes (merged image, every mode and depth) | yes (layered, max editability) | placing as layers (Photoshop Import Options) missing; the embedded ICC profile's tags are 4-byte aligned (#1584) | | PSB | open | yes (merged image) | no | |@@ -130,6 +130,7 @@ the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPri  | Date | Change | Summary | |---|---|---|+| 2026-10-20 | minor | DWG export (2000–2018, desktop) converts the DXF output with the acadrust crate, #3068; opening a DWG still answers an error; scores unchanged | | 2026-10-18 | minor | PDF import: invisible text (rendering mode 3, which carries a scanned page's recognized text) opens as type with no fill or stroke instead of being dropped without a word; when it can't be type (text opened as outlines, Type 3 fonts) it is left out with a warning. PDF export writes characters with no fill or stroke as invisible text in rendering mode 3, so a scanned PDF opened and saved keeps its searchable text, in PDF/X-1a and PDF/A too (#2675); own-fixture tests; scores and hours unchanged | | 2026-10-18 | minor | DXF circular cubic paths export as native CIRCLE/ARC where stroke semantics permit (R13–2018 for weighted strokes; R12 weighted strokes remain POLYLINE); R12 stroked circles/arcs retain their POLYLINE width, and ClosePath geometry with an implicit closing chord is not promoted to CIRCLE (#1299); scores and hours unchanged | | 2026-10-17 | minor | PDF Place and clipboard paste preserve live text; explicit outlines remain supported on Place. Synthetic regressions; scores and hours unchanged. |diff --git a/docs/mcp.md b/docs/mcp.md--- a/docs/mcp.md+++ b/docs/mcp.md@@ -2983,13 +2983,15 @@ selected objects in their layers (every export takes it), and `useArtboards: tru artboard holding the art over it. What DXF can't hold (gradients and patterns as one colour, blending, opacity masks, raster effects, clipping) comes back in `warnings`. -DWG can't be written (no openly licensed writer exists): `document.formats` lists it under `unsupported` with that-hint, and exporting to it answers with the hint to export DXF. PICT files are not supported either and say so when-opened.+DWG is written as the same drawing (`document.exportDwg`, `document.export {format: "dwg"}` or a `.dwg` path, with+the DXF options and `version` 2000 to 2018, default 2018): VectorCraft writes the DXF and converts it with the+acadrust crate (MPL-2.0, the bridge CADCraft uses), on the desktop only. DWG files aren't read: opening one answers an+error. PICT files are not supported and say so when opened.  ```json {"name":"run_command","arguments":{"command":"document.exportDxf","params":{"path":"/tmp/plan.dxf","version":"2013","unit":"mm","scale":10,"preserve":"editability"}}} {"name":"export","arguments":{"path":"/tmp/plan.dxf","options":{"useArtboards":true,"colors":256}}}+{"name":"run_command","arguments":{"command":"document.exportDwg","params":{"path":"/tmp/plan.dwg","version":"2004","unit":"mm"}}} ```  ## DXF importdiff --git a/docs/roadmap.md b/docs/roadmap.md--- a/docs/roadmap.md+++ b/docs/roadmap.md@@ -1,6 +1,6 @@ # Roadmap: milestones and what's next -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-20 · **Change:** minor (G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; DXF circular cubic paths export as native CIRCLE/ARC with R12 weighted strokes kept as POLYLINE and closing-chord safeguards (#1299); cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Image Trace: Auto Color keeps rare but distinct colours; Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; Image Trace: Raster::new returns an error for a buffer of the wrong length; M12 graph pruning and junction merging measured; M12 exact crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; correction: M12's Image Trace quick presets leave the panel as it was, the Image Trace button opens it; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 remaining (8–14 h); Warp Options controls verified by focused checks and original-image review; estimates unchanged; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; later 2026-10-14 merges: per-corner radius, #1682; Width Point Edit from the Width tool, #1814, #1630; guides, #1782, #1836; anchor X/Y/W/H, #1824; toolbar and All Tools, #1751; macOS save sheets, #1855; rendering caches, #1667–#1792; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; Dim Images, #1683; estimates unchanged; M12 row: Image Trace's enhanced presets; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Expand on repeats, #1756; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace #1647, #1657; estimates unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1601; Warp Arc, #1578; Pathfinder strokes, #1570; saves, #1620 in part; OpenType features, #1607; intersect Smart Guides, #1602 in part; estimates unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; M12 row: Image Trace's presets and options as they are now; Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts, #1539; estimates unchanged; Direct Selection anchor precedence, #1508; dialog slider rails, #1483; template layers in Outline view, #1486; type that does not fit its frame is reported, #1471; 3D on outlined type, #1481; print-size images, #1517; French catalog, #1511; estimates unchanged; Split Into Grid options, #1462; Transform Objects and Reflect's angle, #1448 and #1460; Pathfinder on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; estimates unchanged; M9: Extrude & Bevel, Inflate and Rotate; `.ai` compound path paint, #1410, overprint on import, #1388, text-only PDF parts, #1387; Transform dialogs preview, #1412, and transforms of direct-selected anchors, #1408; strokes wider than their shape, #1396; estimates unchanged; `.ai` editing-data follow-ups, #1390, #1391, #1392; dialog keyboard focus, #1367 and #1395; symbol instance redraw cache, #1386; font rescan, #1376; estimates unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click, #1239; custom ruler origin, #1212; M11: Artboard object clicks create a fitted artboard (#1227); estimate unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; DXF control-point SPLINE closure read, #1299 follow-up; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; M6: Simplify preview/counts and valid topology selection (#1272); estimates unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; M14: RPM runtime metadata (#1275); estimates unchanged; M5: squared-distance texture redraws (#1257); clean-machine/platform drag measurements still pending; estimates unchanged; M2: Curvature point display improved (#1236, in part); estimates unchanged; M7: full-name font matching (#1258); Windows font-service QA pending; estimates unchanged; M1: no-op anchor Align keeps Undo, Redo and saved state (#1180 follow-up); estimates unchanged; M14: dedicated Eyedropper cursor (#1269); estimates unchanged; M3: Eyedropper reads visible locked and outside-isolation sources (#1251); estimates unchanged; M2: Direct Selection segment picks and Delete (#1278); estimates unchanged; M3: adding paint to an unpainted zero-width stroke restores 1 pt (#1253); estimates unchanged; M12: graph designs, graph data, Graph Type and label options; M14: native macOS save Format menu, #866; M6: compound appearance and member fill-rule fixes, #1197; estimates unchanged; M9: PDF raster-effect cap warnings; G11: #893 fixed, #937 open; M7: accurate character controls after scaling, #1034; estimates unchanged; MCP `add_text` exposes the existing `vertical` option for point, area and path type; horizontal remains the default; scores and hours unchanged; Vertical fullwidth question and exclamation marks keep their nominal glyph upright when the font has no vertical alternate; scores and hours unchanged.; M2: Curvature curve geometry, #1236 in part; Width-point finite-arithmetic follow-up validated; scores and hours unchanged; Image Trace adjustment discovery; legacy `.ai` of version 7 saved, #868; estimates unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; later 2026-10-14 merges: per-corner radius, #1682; Width Point Edit from the Width tool, #1814, #1630; guides, #1782, #1836; anchor X/Y/W/H, #1824; toolbar and All Tools, #1751; macOS save sheets, #1855; rendering caches, #1667–#1792; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; Dim Images, #1683; estimates unchanged; M12 row: Image Trace's enhanced presets; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Expand on repeats, #1756; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace #1647, #1657; estimates unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1601; Warp Arc, #1578; Pathfinder strokes, #1570; saves, #1620 in part; OpenType features, #1607; intersect Smart Guides, #1602 in part; estimates unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; M12 row: Image Trace's presets and options as they are now; Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts, #1539; estimates unchanged; Direct Selection anchor precedence, #1508; dialog slider rails, #1483; template layers in Outline view, #1486; type that does not fit its frame is reported, #1471; 3D on outlined type, #1481; print-size images, #1517; French catalog, #1511; estimates unchanged; Split Into Grid options, #1462; Transform Objects and Reflect's angle, #1448 and #1460; Pathfinder on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; estimates unchanged; M9: Extrude & Bevel, Inflate and Rotate; `.ai` compound path paint, #1410, overprint on import, #1388, text-only PDF parts, #1387; Transform dialogs preview, #1412, and transforms of direct-selected anchors, #1408; strokes wider than their shape, #1396; estimates unchanged; `.ai` editing-data follow-ups, #1390, #1391, #1392; dialog keyboard focus, #1367 and #1395; symbol instance redraw cache, #1386; font rescan, #1376; estimates unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click, #1239; custom ruler origin, #1212; M11: Artboard object clicks create a fitted artboard (#1227); estimate unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; DXF control-point SPLINE closure read, #1299 follow-up; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; M6: Simplify preview/counts and valid topology selection (#1272); estimates unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; M14: RPM runtime metadata (#1275); estimates unchanged; M5: squared-distance texture redraws (#1257); clean-machine/platform drag measurements still pending; estimates unchanged; M2: Curvature point display improved (#1236, in part); estimates unchanged; M7: full-name font matching (#1258); Windows font-service QA pending; estimates unchanged; M1: no-op anchor Align keeps Undo, Redo and saved state (#1180 follow-up); estimates unchanged; M14: dedicated Eyedropper cursor (#1269); estimates unchanged; M3: Eyedropper reads visible locked and outside-isolation sources (#1251); estimates unchanged; M2: Direct Selection segment picks and Delete (#1278); estimates unchanged; M3: adding paint to an unpainted zero-width stroke restores 1 pt (#1253); estimates unchanged; M12: graph designs, graph data, Graph Type and label options; M14: native macOS save Format menu, #866; M6: compound appearance and member fill-rule fixes, #1197; estimates unchanged; M9: PDF raster-effect cap warnings; G11: #893 fixed, #937 open; M7: accurate character controls after scaling, #1034; estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-20 · **Change:** minor (DWG export (2000–2018, desktop) converts the DXF output with the acadrust crate, #3068; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; DXF circular cubic paths export as native CIRCLE/ARC with R12 weighted strokes kept as POLYLINE and closing-chord safeguards (#1299); cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Image Trace: Auto Color keeps rare but distinct colours; Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; Image Trace: Raster::new returns an error for a buffer of the wrong length; M12 graph pruning and junction merging measured; M12 exact crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; correction: M12's Image Trace quick presets leave the panel as it was, the Image Trace button opens it; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 remaining (8–14 h); Warp Options controls verified by focused checks and original-image review; estimates unchanged; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; later 2026-10-14 merges: per-corner radius, #1682; Width Point Edit from the Width tool, #1814, #1630; guides, #1782, #1836; anchor X/Y/W/H, #1824; toolbar and All Tools, #1751; macOS save sheets, #1855; rendering caches, #1667–#1792; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; Dim Images, #1683; estimates unchanged; M12 row: Image Trace's enhanced presets; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Expand on repeats, #1756; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace #1647, #1657; estimates unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1601; Warp Arc, #1578; Pathfinder strokes, #1570; saves, #1620 in part; OpenType features, #1607; intersect Smart Guides, #1602 in part; estimates unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; M12 row: Image Trace's presets and options as they are now; Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts, #1539; estimates unchanged; Direct Selection anchor precedence, #1508; dialog slider rails, #1483; template layers in Outline view, #1486; type that does not fit its frame is reported, #1471; 3D on outlined type, #1481; print-size images, #1517; French catalog, #1511; estimates unchanged; Split Into Grid options, #1462; Transform Objects and Reflect's angle, #1448 and #1460; Pathfinder on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; estimates unchanged; M9: Extrude & Bevel, Inflate and Rotate; `.ai` compound path paint, #1410, overprint on import, #1388, text-only PDF parts, #1387; Transform dialogs preview, #1412, and transforms of direct-selected anchors, #1408; strokes wider than their shape, #1396; estimates unchanged; `.ai` editing-data follow-ups, #1390, #1391, #1392; dialog keyboard focus, #1367 and #1395; symbol instance redraw cache, #1386; font rescan, #1376; estimates unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click, #1239; custom ruler origin, #1212; M11: Artboard object clicks create a fitted artboard (#1227); estimate unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; DXF control-point SPLINE closure read, #1299 follow-up; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; M6: Simplify preview/counts and valid topology selection (#1272); estimates unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; M14: RPM runtime metadata (#1275); estimates unchanged; M5: squared-distance texture redraws (#1257); clean-machine/platform drag measurements still pending; estimates unchanged; M2: Curvature point display improved (#1236, in part); estimates unchanged; M7: full-name font matching (#1258); Windows font-service QA pending; estimates unchanged; M1: no-op anchor Align keeps Undo, Redo and saved state (#1180 follow-up); estimates unchanged; M14: dedicated Eyedropper cursor (#1269); estimates unchanged; M3: Eyedropper reads visible locked and outside-isolation sources (#1251); estimates unchanged; M2: Direct Selection segment picks and Delete (#1278); estimates unchanged; M3: adding paint to an unpainted zero-width stroke restores 1 pt (#1253); estimates unchanged; M12: graph designs, graph data, Graph Type and label options; M14: native macOS save Format menu, #866; M6: compound appearance and member fill-rule fixes, #1197; estimates unchanged; M9: PDF raster-effect cap warnings; G11: #893 fixed, #937 open; M7: accurate character controls after scaling, #1034; estimates unchanged; MCP `add_text` exposes the existing `vertical` option for point, area and path type; horizontal remains the default; scores and hours unchanged; Vertical fullwidth question and exclamation marks keep their nominal glyph upright when the font has no vertical alternate; scores and hours unchanged.; M2: Curvature curve geometry, #1236 in part; Width-point finite-arithmetic follow-up validated; scores and hours unchanged; Image Trace adjustment discovery; legacy `.ai` of version 7 saved, #868; estimates unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; later 2026-10-14 merges: per-corner radius, #1682; Width Point Edit from the Width tool, #1814, #1630; guides, #1782, #1836; anchor X/Y/W/H, #1824; toolbar and All Tools, #1751; macOS save sheets, #1855; rendering caches, #1667–#1792; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; Dim Images, #1683; estimates unchanged; M12 row: Image Trace's enhanced presets; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Expand on repeats, #1756; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace #1647, #1657; estimates unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1601; Warp Arc, #1578; Pathfinder strokes, #1570; saves, #1620 in part; OpenType features, #1607; intersect Smart Guides, #1602 in part; estimates unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; M12 row: Image Trace's presets and options as they are now; Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts, #1539; estimates unchanged; Direct Selection anchor precedence, #1508; dialog slider rails, #1483; template layers in Outline view, #1486; type that does not fit its frame is reported, #1471; 3D on outlined type, #1481; print-size images, #1517; French catalog, #1511; estimates unchanged; Split Into Grid options, #1462; Transform Objects and Reflect's angle, #1448 and #1460; Pathfinder on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; estimates unchanged; M9: Extrude & Bevel, Inflate and Rotate; `.ai` compound path paint, #1410, overprint on import, #1388, text-only PDF parts, #1387; Transform dialogs preview, #1412, and transforms of direct-selected anchors, #1408; strokes wider than their shape, #1396; estimates unchanged; `.ai` editing-data follow-ups, #1390, #1391, #1392; dialog keyboard focus, #1367 and #1395; symbol instance redraw cache, #1386; font rescan, #1376; estimates unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click, #1239; custom ruler origin, #1212; M11: Artboard object clicks create a fitted artboard (#1227); estimate unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; DXF control-point SPLINE closure read, #1299 follow-up; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; M6: Simplify preview/counts and valid topology selection (#1272); estimates unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; M14: RPM runtime metadata (#1275); estimates unchanged; M5: squared-distance texture redraws (#1257); clean-machine/platform drag measurements still pending; estimates unchanged; M2: Curvature point display improved (#1236, in part); estimates unchanged; M7: full-name font matching (#1258); Windows font-service QA pending; estimates unchanged; M1: no-op anchor Align keeps Undo, Redo and saved state (#1180 follow-up); estimates unchanged; M14: dedicated Eyedropper cursor (#1269); estimates unchanged; M3: Eyedropper reads visible locked and outside-isolation sources (#1251); estimates unchanged; M2: Direct Selection segment picks and Delete (#1278); estimates unchanged; M3: adding paint to an unpainted zero-width stroke restores 1 pt (#1253); estimates unchanged; M12: graph designs, graph data, Graph Type and label options; M14: native macOS save Format menu, #866; M6: compound appearance and member fill-rule fixes, #1197; estimates unchanged; M9: PDF raster-effect cap warnings; G11: #893 fixed, #937 open; M7: accurate character controls after scaling, #1034; estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)  Forward-looking plan. Where VectorCraft stands is in [`ROADMAP.md`](../ROADMAP.md) and [target-app-parity.md](target-app-parity.md); the ranked work list is [gaps.md](gaps.md). Hours are Opus 5.5@@ -53,7 +53,7 @@ Checked 2026-10-10 from the code, tests and open issues; no workflow fails, so V | M1 | Selection, transform, layers, MCP (common command tools, strict keys, session resources and version-aware result hints) | ✅ mostly done (malformed MCP app replies discard the connection without replay, #1761; bounded control socket waits and connection-slot recovery, #1726; bounded synthetic control input, #1709; no-op anchor Align keeps Undo, Redo and saved state (#1180 follow-up); transform reference point snaps to anchors/centres; rotated objects keep a rotated bounding box and their angle; Free Transform, Scale and Reflect along a rotated box pending) | 4–6 | | M2 | Drawing tools + smart guides | ✅ mostly done (Curvature point markers without direction handles (#1236, in part); Direct Selection segment click/Delete preserves neighboring edges, #1278; Flare, Reshape, Live Corners widget dragging and per-corner radii and kinds landed, on any path's corners; Shaper rough-shape recognition and orientation steps, the remaining Pen modifier nuances; Shaper Groups with editable sources and separate fill/stroke edits landed; custom ruler origin with drag preview, active-artboard reset, relative X/Y fields and native round-trip (#1212); construction widget/refinements pending) | 12–18 | | M3 | Paint & appearance (swatches, color, gradient, stroke, appearance, transparency, styles) | ✅ done (M3.7–M3.98): swatches, groups, libraries, tints, Lab spots and Registration; Eyedropper reads visible locked and outside-isolation sources (#1251); Color Picker, Color panel, Color Guide, Color Themes, Edit Colors and Recolor Artwork; gradients that follow every transform, the annotator, linked stops, freeform gradients, focal points, gradients on strokes and Expand; visible strokes when paint is added to inherited None/zero-width art (#1253); stroke geometry, arrowheads, dashes fitted to corners, stroke on type, width profiles and the Width tool (Shift-double-click enables adjoining adjustment in Width Point Edit; finite-arithmetic follow-up regressions and full CI passed ([Validation evidence](target-app-parity.md#evidence-what-ships-today-by-area))), Scale Strokes and preview bounds; Appearance targeting, container appearance, target circles, Expand Appearance; knockout, isolation, blend accuracy, CMYK blending, overprint, Attributes, masks and clipping, Flatten Transparency; graphic styles, links and libraries. Left: freeform and mixed spot/process gradients export as stops or process colours to SVG/PDF; confirm the Scale Strokes & Effects default | 4–8 |-| M4 | Files (native, SVG, PDF, raster, Export for Screens, clipboard interop) | ✅ done (M4.14–M4.98 on 2026-10-05): Save As/Save a Copy/Revert/templates, Data Recovery and background save, SVG import and export fidelity (units, text, symbols, filters, SVGZ, Preserve Editing, SVG Options), shared guarded PDF writer and windowed reader ([pdf-backend.md](pdf-backend.md)), PDF import (colours, layers, masks, editable text, security) and export (presets, marks, bleed, ICC and output intent, raster effects, subset fonts, PDF/X-1a, X-3 and X-4, PDF layers, overprint, page thumbnails, fast web view, PDF 1.3), Place and Links, Package, EPS and DXF in and out (EPS legacy mesh corners, vector opacity masks and raster page comparison verified; closed SPLINE flags round-trip and circular paths export as native CIRCLE/ARC, #1299), EMF/WMF, TIFF/BMP/Targa/PSD/GIF/PNG-8 export, Print and print presets, slices, Save for Web, TIFF in Export for Screens and Asset Export, clipboard flavours, File Info; AF.1–AF.4 add Affinity import (native layers, artboards, vectors, text and images from Affinity 1–3 files; the embedded preview only as a warned fallback; AF.4 expands current `.af` fixtures and every-board round-trip validation). Left: DWG (use DXF), PSD placement as layers, overprint read back from PDF, Affinity export, polish | 3–6 (existing scope; Affinity export unestimated) |+| M4 | Files (native, SVG, PDF, raster, Export for Screens, clipboard interop) | ✅ done (M4.14–M4.98 on 2026-10-05): Save As/Save a Copy/Revert/templates, Data Recovery and background save, SVG import and export fidelity (units, text, symbols, filters, SVGZ, Preserve Editing, SVG Options), shared guarded PDF writer and windowed reader ([pdf-backend.md](pdf-backend.md)), PDF import (colours, layers, masks, editable text, security) and export (presets, marks, bleed, ICC and output intent, raster effects, subset fonts, PDF/X-1a, X-3 and X-4, PDF layers, overprint, page thumbnails, fast web view, PDF 1.3), Place and Links, Package, EPS and DXF in and out (EPS legacy mesh corners, vector opacity masks and raster page comparison verified; closed SPLINE flags round-trip and circular paths export as native CIRCLE/ARC, #1299), EMF/WMF, TIFF/BMP/Targa/PSD/GIF/PNG-8 export, Print and print presets, slices, Save for Web, TIFF in Export for Screens and Asset Export, clipboard flavours, File Info; AF.1–AF.4 add Affinity import (native layers, artboards, vectors, text and images from Affinity 1–3 files; the embedded preview only as a warned fallback; AF.4 expands current `.af` fixtures and every-board round-trip validation). Left: DWG import (export is done, through the DXF drawing), PSD placement as layers, overprint read back from PDF, Affinity export, polish | 3–6 (existing scope; Affinity export unestimated) | | M3 | Paint & appearance (swatches, color, gradient, stroke, appearance, transparency, styles) | ✅ done (M3.7–M3.98): swatches, groups, libraries, tints, Lab spots and Registration; Eyedropper reads visible locked and outside-isolation sources (#1251); Color Picker, Color panel, Color Guide, Color Themes, Edit Colors and Recolor Artwork; gradients that follow every transform, the annotator, linked stops, freeform gradients, focal points, gradients on strokes and Expand; visible strokes when paint is added to inherited None/zero-width art (#1253); stroke geometry, arrowheads, dashes fitted to corners, stroke on type, width profiles and the Width tool (Shift-double-click enables adjoining adjustment in Width Point Edit), Scale Strokes and preview bounds; Appearance targeting, container appearance, target circles, Expand Appearance; knockout, isolation, blend accuracy, CMYK blending, overprint, Attributes, masks and clipping, Flatten Transparency; graphic styles, links and libraries. Left: freeform and mixed spot/process gradients export as stops or process colours to SVG/PDF; confirm the Scale Strokes & Effects default | 4–8 | | M5 | Performance | 🟡 background render + caches + MT done; `vectorcraft-cli bench` and `vectorcraft-cli perf` (budget suite); file format v2 opens 3× faster (50k paths: 722 → 244 ms); raster effects (glows, shadows, blur, feather) no longer force the whole frame single-threaded (filtered offscreen per effect, verified equal to the single-threaded reference); Craquelure/Mosaic Tiles/Stained Glass distance searches optimized with pixel-invariance checks and the `texture_drag_perf` probe (#1257); effect-heavy demos need a clean-machine benchmark; dirty-region rendering, GPU backend spike pending | 15–25 | | M6 | Path operations (Pathfinder, Shape Builder, offset…) | ✅ mostly done (single-result Pathfinder names and SVG IDs retained (#1293); Divide open-cutting-path geometry, appearance and history verified (#1307; existing segment cap remains); Simplify preview/counts and rewritten-path component selection verified (#1272; auto/slider/advanced UI remains open); Compound appearance and member fill-rule edits verified (#1197); Boolean precision on almost-horizontal edges fixed and the property tests made deterministic; Shape Builder gap detection, large-offset bug open) | 3–6 |@@ -88,6 +88,7 @@ M3 follow-up (#1968): native saves retain the per-document transparency grid set  | Date | Change | Summary | |---|---|---|+| 2026-10-20 | minor | DWG export (2000–2018, desktop) converts the DXF output with the acadrust crate, #3068; opening a DWG still answers an error; scores unchanged | | 2026-10-19 | minor | Cache status-readout sample widths per UI, language, document unit and pixels per point; regressions check reuse across live frames and refresh after display-setting changes. Scores and estimates unchanged. | | 2026-10-19 | minor | Status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates. Contributor original-file acceptance confirmed; scores and estimates unchanged. | | 2026-10-18 | minor | M4/DXF #1299: circular cubic paths export as native CIRCLE/ARC where stroke semantics permit; R12 weighted strokes retain POLYLINE width and closed partial arcs with an implicit closing chord are not promoted to CIRCLE. Scores and hours unchanged. |diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md--- a/docs/target-app-parity.md+++ b/docs/target-app-parity.md@@ -1,6 +1,6 @@ # Parity with Adobe Illustrator -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-20 · **Change:** minor (live-effect plug-ins given their object's fills and strokes and the item they sit on, G17; plug-in parameters: labels, units, groups, help and conditions in their dialogs, text and number-list parameters, up to 256 of them, G17; exporter plug-ins: plug-in file formats in Export As and document.export, G17; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; Libraries panel: the list scrolls, items and groups reorder by dragging, items drag into and out of groups (`library.moveItem` before/after, `library.moveGroup`); scores unchanged; PDF invisible text opens as type with no fill or stroke and is written back invisible, #2675; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Artboard Options' X and Y with a reference point, #1876; Image Trace: Auto Color keeps rare but distinct colours; Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; Image Trace: Raster::new returns an error for a buffer of the wrong length; M12 maximal-disc graph pruning and junction merging measured; M12 crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; installed plug-ins stay installed after a restart, #2541; Group Selection picks and moves one path of a compound path, #2441 in part; the Move dialog opens on the last move and its last checkboxes; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace Flat Logo traces line art on white paper instead of a blank result; score and hours unchanged; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; MCP cancellation, progress and busy answers during long exports, #2165, #2171, #2172; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; native family-name fallback from English-only typographic names, #2208; scores unchanged; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 remaining (8–14 h); G5: Symbol Options Export Type (Graphic / Movie Clip); Localization: Brazilian Portuguese complete under the tests, #1914, 71% → 74%; G6 image variables: Automation ~60% → ~62% / detail ~65% → ~68%, hours lowered; Warp Options controls verified by focused checks and original-image review; estimates unchanged; G11: Asset Export Art Optimized seam (#983 / #1037) and supersampled thumbs; Export for Screens 85% → 90%; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; later 2026-10-14 merges: anchor X/Y/W/H, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar and All Tools, #1751; Actions panel selection, #1629 in part; redo during a drag, #1719; preferences written on OK, #1747; type in symbols after reopening, #1738; macOS save sheets, #1855, the Format menu withdrawn; stroke outlines shared, #1667; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; effects, #1864, Dim Images, #1683; duplicated rows of the 2026-10-05 table noted for the owner; scores unchanged; Image Trace: Shapes rework, stroke caps, enhanced presets, gradients over noise, measured in `image-trace-quality.md`, score unchanged; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Escape while typing, #1686; Pen clicks, #1663; Expand on repeats, #1756; Roughen and Scribble, #1643, #1648; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace levelling and Flat Logo on scans, #1647, #1657; scores unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1598, #1601; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment, #1584; saves, #1620 in part; Warp Arc, #1578; Zig Zag, #1558; Pathfinder strokes, #1570; OpenType features, #1607; intersect Smart Guides, #1602 in part; a guide's position in the Transform panel, #1597 in part, a convenience with no parity claim; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; Units › Identify Objects By (`identifyObjectsBy`) acts on Layers, Variables and Layer Options, #394, scores unchanged; Appearance of Black › Printing / Exporting (`blackOutput`) acts on RGB raster export and Print as Bitmap, #394, scores unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; Color panel: the mode picked last comes back after a restart, #1675; Image Trace: shared edges, memory, thin lines, strokes, corners, and the Shapes, Auto Grouping and Gradients options, measured in `image-trace-quality.md`, score unchanged; 2026-10-13 merges: Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes for `.ai`, PDF, EPS and SVG, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts lists commands outside the menus, #1539; correction: Average and Join with a corner or smooth choice is missing; scores unchanged; late 2026-10-12 merges: type that does not fit its frame is reported, #1471; Tabs panel drags, #1458; template layers in Outline view, #1486; dialog slider rails, #1483; Direct Selection anchor precedence, #1508; print-size images, #1517; 3D on outlined type, #1481; French catalog, #1511; scores unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; Localization: Vietnamese catalog added, 63% → 71%, hours 60–100 → 55–90, full readiness unchanged at ~62%; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; Image Trace: colour presets no longer shatter noisy flat art and photos trace into smooth tones, measured in `image-trace-quality.md`, score unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; MCP `add_text` exposes the existing `vertical` option for point, area and path type; horizontal remains the default; scores and hours unchanged; Vertical fullwidth question and exclamation marks keep their nominal glyph upright when the font has no vertical alternate; scores and hours unchanged.; Curvature curve geometry, #1236 in part; Width-point finite-arithmetic follow-up validated; scores and hours unchanged; Image Trace adjustment discovery; File interop: legacy `.ai` of version 7 saved with type as text, hidden objects kept and an artboard picker, #868, scores unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; later 2026-10-14 merges: anchor X/Y/W/H, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar and All Tools, #1751; Actions panel selection, #1629 in part; redo during a drag, #1719; preferences written on OK, #1747; type in symbols after reopening, #1738; macOS save sheets, #1855, the Format menu withdrawn; stroke outlines shared, #1667; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; effects, #1864, Dim Images, #1683; duplicated rows of the 2026-10-05 table noted for the owner; scores unchanged; Image Trace: Shapes rework, stroke caps, enhanced presets, gradients over noise, measured in `image-trace-quality.md`, score unchanged; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Escape while typing, #1686; Pen clicks, #1663; Expand on repeats, #1756; Roughen and Scribble, #1643, #1648; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace levelling and Flat Logo on scans, #1647, #1657; scores unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1598, #1601; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment, #1584; saves, #1620 in part; Warp Arc, #1578; Zig Zag, #1558; Pathfinder strokes, #1570; OpenType features, #1607; intersect Smart Guides, #1602 in part; a guide's position in the Transform panel, #1597 in part, a convenience with no parity claim; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; Units › Identify Objects By (`identifyObjectsBy`) acts on Layers, Variables and Layer Options, #394, scores unchanged; Appearance of Black › Printing / Exporting (`blackOutput`) acts on RGB raster export and Print as Bitmap, #394, scores unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; Color panel: the mode picked last comes back after a restart, #1675; Image Trace: shared edges, memory, thin lines, strokes, corners, and the Shapes, Auto Grouping and Gradients options, measured in `image-trace-quality.md`, score unchanged; 2026-10-13 merges: Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes for `.ai`, PDF, EPS and SVG, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts lists commands outside the menus, #1539; correction: Average and Join with a corner or smooth choice is missing; scores unchanged; late 2026-10-12 merges: type that does not fit its frame is reported, #1471; Tabs panel drags, #1458; template layers in Outline view, #1486; dialog slider rails, #1483; Direct Selection anchor precedence, #1508; print-size images, #1517; 3D on outlined type, #1481; French catalog, #1511; scores unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; Localization: Vietnamese catalog added, 63% → 71%, hours 60–100 → 55–90, full readiness unchanged at ~62%; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; Image Trace: colour presets no longer shatter noisy flat art and photos trace into smooth tones, measured in `image-trace-quality.md`, score unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)+> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-20 · **Change:** minor (DWG export (2000–2018, desktop) converts the DXF output with the acadrust crate, #3068; live-effect plug-ins given their object's fills and strokes and the item they sit on, G17; plug-in parameters: labels, units, groups, help and conditions in their dialogs, text and number-list parameters, up to 256 of them, G17; exporter plug-ins: plug-in file formats in Export As and document.export, G17; G2: Effect › Artistic › Smudge Stick, Sponge and Watercolor; Sketch › Bas Relief, Charcoal, Photocopy and Stamp, #2533; Photoshop filters 45/57, 12 remaining (8–14 h); interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; Libraries panel: the list scrolls, items and groups reorder by dragging, items drag into and out of groups (`library.moveItem` before/after, `library.moveGroup`); scores unchanged; PDF invisible text opens as type with no fill or stroke and is written back invisible, #2675; status-readout sample widths cached by language, document unit and pixels per point; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; cached canvas dimensions match the renderer limit before cache-key creation, view construction and worker submission; cached canvas reprojection preserves scale during resize, rotation and DPI changes; interactive Mirror Repeat axis; bounding-box handles take priority over the axis body while the center and end handles retain their move and rotation actions; 3D lighting: one to ten colored lights, smooth colored Inflate, legacy white-light appearance preserved; surface batching and restored seam minimum; scores and estimates unchanged; Blend spine Anchor Point conversions, selection/stacking priority and Shift regression coverage (#2544); scores and hours unchanged; Artboard Options' X and Y with a reference point, #1876; Image Trace: Auto Color keeps rare but distinct colours; Image Trace: large images trace with a warning; anchor limit raised to 8,000,000; Image Trace: Raster::new returns an error for a buffer of the wrong length; M12 maximal-disc graph pruning and junction merging measured; M12 crop EDT and Guo–Hall thinning measured; M12 centre-lines baseline measured; scores and hours unchanged; installed plug-ins stay installed after a restart, #2541; Group Selection picks and moves one path of a compound path, #2441 in part; the Move dialog opens on the last move and its last checkboxes; Image Trace: a higher Paths never gives fewer segments on collapsed loops; Image Trace: the notch rule reads only its own piece; Image Trace: an edge's sub-pixel shift is the same from either side; Image Trace Flat Logo traces line art on white paper instead of a blank result; score and hours unchanged; Image Trace: a simpler contour walk with no precomputed direction table; transparency grid visibility saved in native documents, #1968; MCP cancellation, progress and busy answers during long exports, #2165, #2171, #2172; EPS editing data: legacy mesh corners, vector opacity masks and compound member paint retained; raster page comparison filters minification without relaxing limits; scores and hours unchanged; native family-name fallback from English-only typographic names, #2208; scores unchanged; batch exporting, #1935; G2: Effect › Sketch › Halftone Pattern; Photoshop filters 45/57, 12 remaining (8–14 h); G5: Symbol Options Export Type (Graphic / Movie Clip); Localization: Brazilian Portuguese complete under the tests, #1914, 71% → 74%; G6 image variables: Automation ~60% → ~62% / detail ~65% → ~68%, hours lowered; Warp Options controls verified by focused checks and original-image review; estimates unchanged; G11: Asset Export Art Optimized seam (#983 / #1037) and supersampled thumbs; Export for Screens 85% → 90%; existing-guide Escape cancellation validated 2026-10-14; scores and hours unchanged; open-path anchor Delete keeps surviving runs separate; estimates unchanged; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; Artboard tab-switch follow-up historically validated on `a0671777`; scores and hours unchanged; Artboard clean active-tab close/Delete follow-up validated 2026-10-15; later 2026-10-14 merges: anchor X/Y/W/H, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar and All Tools, #1751; Actions panel selection, #1629 in part; redo during a drag, #1719; preferences written on OK, #1747; type in symbols after reopening, #1738; macOS save sheets, #1855, the Format menu withdrawn; stroke outlines shared, #1667; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; effects, #1864, Dim Images, #1683; duplicated rows of the 2026-10-05 table noted for the owner; scores unchanged; Image Trace: Shapes rework, stroke caps, enhanced presets, gradients over noise, measured in `image-trace-quality.md`, score unchanged; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Escape while typing, #1686; Pen clicks, #1663; Expand on repeats, #1756; Roughen and Scribble, #1643, #1648; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace levelling and Flat Logo on scans, #1647, #1657; scores unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1598, #1601; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment, #1584; saves, #1620 in part; Warp Arc, #1578; Zig Zag, #1558; Pathfinder strokes, #1570; OpenType features, #1607; intersect Smart Guides, #1602 in part; a guide's position in the Transform panel, #1597 in part, a convenience with no parity claim; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; Units › Identify Objects By (`identifyObjectsBy`) acts on Layers, Variables and Layer Options, #394, scores unchanged; Appearance of Black › Printing / Exporting (`blackOutput`) acts on RGB raster export and Print as Bitmap, #394, scores unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; Color panel: the mode picked last comes back after a restart, #1675; Image Trace: shared edges, memory, thin lines, strokes, corners, and the Shapes, Auto Grouping and Gradients options, measured in `image-trace-quality.md`, score unchanged; 2026-10-13 merges: Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes for `.ai`, PDF, EPS and SVG, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts lists commands outside the menus, #1539; correction: Average and Join with a corner or smooth choice is missing; scores unchanged; late 2026-10-12 merges: type that does not fit its frame is reported, #1471; Tabs panel drags, #1458; template layers in Outline view, #1486; dialog slider rails, #1483; Direct Selection anchor precedence, #1508; print-size images, #1517; 3D on outlined type, #1481; French catalog, #1511; scores unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; Localization: Vietnamese catalog added, 63% → 71%, hours 60–100 → 55–90, full readiness unchanged at ~62%; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; Image Trace: colour presets no longer shatter noisy flat art and photos trace into smooth tones, measured in `image-trace-quality.md`, score unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; MCP `add_text` exposes the existing `vertical` option for point, area and path type; horizontal remains the default; scores and hours unchanged; Vertical fullwidth question and exclamation marks keep their nominal glyph upright when the font has no vertical alternate; scores and hours unchanged.; Curvature curve geometry, #1236 in part; Width-point finite-arithmetic follow-up validated; scores and hours unchanged; Image Trace adjustment discovery; File interop: legacy `.ai` of version 7 saved with type as text, hidden objects kept and an artboard picker, #868, scores unchanged; Revolve: automatic smooth shading and default-on Cap with continuous rims; PDF.R1: shared PDF reader with windowed content scanning and explicit incomplete-content errors; scores and estimates unchanged; PDF.W1: guarded shared PDF writer; output behavior, scores and estimates unchanged; later 2026-10-14 merges: anchor X/Y/W/H, #1824; Snap to Grid over Snap to Pixel, #1816; toolbar and All Tools, #1751; Actions panel selection, #1629 in part; redo during a drag, #1719; preferences written on OK, #1747; type in symbols after reopening, #1738; macOS save sheets, #1855, the Format menu withdrawn; stroke outlines shared, #1667; untrusted files, #1852, #1862–#1873; failed-save dialog, #1685; effects, #1864, Dim Images, #1683; duplicated rows of the 2026-10-05 table noted for the owner; scores unchanged; Image Trace: Shapes rework, stroke caps, enhanced presets, gradients over noise, measured in `image-trace-quality.md`, score unchanged; verified PngSuite downloads, #1729; synthetic control-input budgets, #1709; 2026-10-14 merges: anchor Align and Distribute, #1636; Direct Selection and Delete fixes, #1674, #1666, #1743; Escape while typing, #1686; Pen clicks, #1663; Expand on repeats, #1756; Roughen and Scribble, #1643, #1648; file and input hardening, #1776–#1783, #1786, #1807; MCP limits and envelope, #1727, #1728; Image Trace levelling and Flat Logo on scans, #1647, #1657; scores unchanged; later 2026-10-13 merges: SVG import, #1565, #1596, #1568, #1598, #1601; type on a path from PDF and `.ai`, #1585, #1586; ICC tag alignment, #1584; saves, #1620 in part; Warp Arc, #1578; Zig Zag, #1558; Pathfinder strokes, #1570; OpenType features, #1607; intersect Smart Guides, #1602 in part; a guide's position in the Transform panel, #1597 in part, a convenience with no parity claim; scores unchanged; TIFF is available in Export for Screens and Asset Export with shared format settings; scores and hours unchanged; G1/M11: Artboard deletion and history selection normalization; history regressions and full workspace CI pass; scores and hours unchanged; Character styles retain explicit Auto leading, propagate resets and preserve local leading overrides; scores and hours unchanged; Width Point Edit: Shift-double-click enables adjoining adjustment; plain double-click keeps it off; scores and hours unchanged; Units › Identify Objects By (`identifyObjectsBy`) acts on Layers, Variables and Layer Options, #394, scores unchanged; Appearance of Black › Printing / Exporting (`blackOutput`) acts on RGB raster export and Print as Bitmap, #394, scores unchanged; per-corner radius fields with a link toggle, split from #1594; control transport deadlines, #1726; Color panel: the mode picked last comes back after a restart, #1675; Image Trace: shared edges, memory, thin lines, strokes, corners, and the Shapes, Auto Grouping and Gradients options, measured in `image-trace-quality.md`, score unchanged; 2026-10-13 merges: Polish interface, #1498; compact native saves, #1492; PDF and EPS paste as vector art, #1499; import fixes for `.ai`, PDF, EPS and SVG, #1528, #1530, #1531, #1544, #1551, #1553; guide objects, #1534; Join after Average, #1533; Keyboard Shortcuts lists commands outside the menus, #1539; correction: Average and Join with a corner or smooth choice is missing; scores unchanged; late 2026-10-12 merges: type that does not fit its frame is reported, #1471; Tabs panel drags, #1458; template layers in Outline view, #1486; dialog slider rails, #1483; Direct Selection anchor precedence, #1508; print-size images, #1517; 3D on outlined type, #1481; French catalog, #1511; scores unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; Localization: Vietnamese catalog added, 63% → 71%, hours 60–100 → 55–90, full readiness unchanged at ~62%; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks; Image Trace: colour presets no longer shatter noisy flat art and photos trace into smooth tones, measured in `image-trace-quality.md`, score unchanged; Split Into Grid options, #1462; Transform Objects and Reflect across an angle, #1448 and #1460; Pathfinder panel on a group, #1450; unknown parameter keys refused, #1442; grayscale TIFFs with alpha, #1466 in part; scores and hours unchanged; Image Trace places edges to a fraction of a pixel in every mode, measured in `image-trace-quality.md`, score unchanged; 3D and Materials: Extrude & Bevel, Inflate and Rotate added, 3D ~8% → ~25% (estimated), menu stubs 25 → 22; Affinity text frames too short for their first line show it, #1423; Transform dialogs preview, #1412, and the transform tools and commands act on direct-selected anchors, #1408; `.ai` compound path paint, #1410; strokes wider than their shape, #1396; corner drags hide the bounding box, #1229; scores and hours unchanged; PDF and `.ai` import keep overprint, #1388, scores unchanged; `.ai` editing-data follow-ups: gradient stops, type of objects with an appearance, short area-type frames (#1390, #1391, #1392); dialog keyboard focus on every open (#1382, #1395); art flush with an artboard (#1375); font rescan (#1376); symbol instance redraw cache (#1386); scores and hours unchanged; grid tool options, #1377 in part, scores unchanged; `.ai` placed (linked) files read from the editing data, #1264, scores unchanged; gradient shapes import as paths, #1304; white constant alpha masks, #1286 in part; Live Corners widget click selects its corner, #1239; scores and hours unchanged; custom ruler origin, #1212; Artboard click-to-fit verified for objects, groups, visible bounds, undo and cancellation (#1227); scores and hours unchanged; Pathfinder single-result source names and SVG IDs retained, #1293; Localization: Korean catalog added, 54% → 63%, full readiness unchanged at ~62%; desktop print transport adopts craft-print, Windows 7 fallback retained, scores unchanged; Image Trace: an image over 64 megapixels traces scaled down instead of being refused, scores unchanged; Czech render-site localization follow-up; scores and estimates unchanged; Document Setup: Edit Artboards closes the modal after applying valid settings; pointer-click regressions; scores and hours unchanged; Artboards: Escape exits editing; an active drag cancels first; shortcut-routing regression; scores and hours unchanged; Path operations: Simplify preview/counts and component selection (#1272); scores unchanged; G2: Film Grain added; Photoshop effects 45/57; 28 filters remain; Platforms: RPM runtime capabilities and glibc floor (#1275); scores unchanged; Raster effects: squared-distance texture redraws (#1257); pixels unchanged; scores and hours unchanged; Drawing: Curvature point display improved (#1236, in part); scores unchanged; Type core: full-name font matching (#1258), scores and hours unchanged; Selection: no-op anchor Align preserves history and saved state (#1180 follow-up); scores unchanged; UI chrome: Eyedropper cursor and precise-setting regressions (#1269); scores unchanged; Colour: Eyedropper reads visible locked and outside-isolation sources (#1251); scores and hours unchanged; Selection: segment click/Delete preserves neighboring edges (#1278); scores unchanged; Colour: unpainted zero-width strokes regain 1 pt when painted (#1253); scores and hours unchanged; graphs: designs and markers, data, Graph Type and label options; UI chrome: native macOS save Format menu, #866, scores unchanged; compound paths inherit the backmost appearance and member fill-rule edits reach their owner, #1197; scores unchanged; Type core: Middle Eastern digit types done, Character panel menu › Digits; PDF export warns when the pixel cap lowers raster-effect resolution; Drawing tools: Live Corners round corners between curved sides, #1144; Type advanced: proportional vertical metrics (`vpal`) done, #966; Shape Builder: #893 fixed, merged regions of crossing lines no longer keep seams; scores unchanged while #937 is open; #1034: document-point character controls after object/group scaling; full readiness recomputed as the standard's additive weighted sum, ~62%; per-audience hours; essentials-user score ~75% added; ready-for-real-work re-examined: 50% → 55%, mainstream 65% added; earlier the same day: full re-measure against Illustrator 2026 30.x; merged the ROADMAP's parity estimate, honest assessment and "Shipped so far" into this file; SVG knockout groups of opaque art export without masks) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)  The authoritative parity assessment. [`ROADMAP.md`](../ROADMAP.md) summarizes it; [`gaps.md`](gaps.md) is the work list made from it. Deep areas have their own checklists: [UI and interaction](ui-parity.md),@@ -345,7 +345,7 @@ about 1–1.5 agent-hours each, so the other rows (estimated on the older scale) | Layers, artboards, document setup | 5 | 82% | Layers panel: a confirmation before deleting layers that hold art, Collect for Export, row context menus, the Layer Options dialog for several rows with mixed values, Paste Remembers Layers for sublayers, drag-scrolling a long panel while dragging rows; artboard preset polish (artboard escape routing and document setup modal handoff done; Move Artwork for artboard X/Y edits, Rearrange All Artboards, Document Setup, New Document and the rest of the Layers panel are done) | 4–7 | | View & navigation | 3 | 70% | New View/Edit Views, multiple windows/arrange, Snap to Pixel/Glyph (print tiling is done) | 9–14 | | Guides, grids, smart guides, snapping, rulers | 3 | 85% | global/video rulers, Spacing Guides' distance readouts and spacing for resized or drawn art, Smart Guides' angle and distance readouts while drawing, Alt swapping a ruler guide's orientation, ruler guides selected together with art (marquee, Shift-click), ruler guides as Layers rows with their own lock and hide (#451: Lock Guides locks them all; Make Guides paths are Layers rows already) (dragging existing guides is done, #414; new guides snapping to side midpoints and artboards, and artboard guides, #451; the drawing tools snapping, with Construction Guides, #506; Spacing Guides for moves, #394; moves snapping to the grid, #740; ruler guides pulling drawn points with Smart Guides on, and "center" and "anchor" labels while hovering with the Selection tool, #812) | 2–4 |-| File formats | 6 | 85% | Affinity export, and the rest of Affinity import (effects, adjustments, brushes, master pages, live symbol instances), DWG (no open spec: DXF instead), PSD placement as layers (Photoshop Import Options; the merged image is done), EPS checked against more real files users send (Inkscape files open as vectors; other generators' constructs are tested with programs written from the PostScript Language Reference only; CID-keyed CJK type and `CCITTFaxDecode` images not read), legacy `.ai` files saved without the prolog that defines their operators (their own operator set isn't read yet), the rest of Illustrator's editing data (appearances and live effects, symbols, pattern fills, brushes, placed files: #637), the remaining fidelity polish; EPS, DXF, EMF/WMF, TIFF, BMP, Targa, PSD export, SVGZ, PDF security, PDF/X, PDF layers, presets and Affinity import are done | 6–10 (existing scope; Affinity export unestimated) |+| File formats | 6 | 85% | Affinity export, and the rest of Affinity import (effects, adjustments, brushes, master pages, live symbol instances), DWG import (no open spec; DWG export converts the DXF output, #3068), PSD placement as layers (Photoshop Import Options; the merged image is done), EPS checked against more real files users send (Inkscape files open as vectors; other generators' constructs are tested with programs written from the PostScript Language Reference only; CID-keyed CJK type and `CCITTFaxDecode` images not read), legacy `.ai` files saved without the prolog that defines their operators (their own operator set isn't read yet), the rest of Illustrator's editing data (appearances and live effects, symbols, pattern fills, brushes, placed files: #637), the remaining fidelity polish; EPS, DXF, EMF/WMF, TIFF, BMP, Targa, PSD export, SVGZ, PDF security, PDF/X, PDF layers, presets and Affinity import are done | 6–10 (existing scope; Affinity export unestimated) | | Export for Screens, Asset Export, slices, Save for Web | 3 | 85% | polish only (all four are done) | 2–4 | | Print, colour management, separations, flattener | 3 | 80% | print preview fidelity (flattener presets in PDF and print, overprint in PDF and composite print, Print dialog, presets, PostScript, marks, separations, Print Tiling, print overprint/flattener/bitmap options and printer profile are done) | 1–2 | | Automation | 3 | 65% | scripting surface, batch (MCP drops malformed app replies without replay; Variables: text/visibility variables bound to objects, an object may carry several, dataset rows applied in one undo step, panel and Window › Variables with dialogs; image/graph kinds and dataset import/export open) | 6–10 |@@ -382,7 +382,7 @@ since 2026-10-01. | **Large missing subsystems** | 0–20% | 3D & Materials (~25%, initial 3D types without materials), Photoshop-style raster effects and the Effect Gallery (~19 of ~56 filters), SVG Filters, CJK composition (vertical type with kinsoku, tate-chu-yoko and the font's vertical metrics; ruby, mojikumi and proportional vertical metrics open), scripting | | **Interaction fidelity:** modifiers, cursors, small behaviours | ~30–40% | The dedicated pass hasn't started, and there has been no side-by-side session with Illustrator yet. A power user notices this first | | **Look & feel** | ~75–80% | Measured against Illustrator 2026 screenshots (2026-10-02); the panels added since haven't been re-measured |-| **File interop** | ~85% | SVG/SVGZ, PDF and PDF-compatible `.ai` (and legacy version 7 `.ai` out, keeping layers, groups and type as text), EPS, DXF, EMF/WMF, raster formats, PSD export and PSD/PSB placement (merged image), Place and Links, Package, Print, clipboard flavours. Affinity documents (Affinity 1–3) open natively as editable art, checked against 51 pinned public files (33 current `.af`), with selected saved-thumbnail comparisons and every-board native/SVG/PDF/PSD checks for current files; Affinity export is missing and effects, adjustments and brushes don't import. Affinity isn't an Illustrator feature, so this doesn't raise the parity score. The editing copy inside an Illustrator EPS or `.ai` is read for its structure: layers, groups, names, hidden objects and type, colours, gradients, transparency, images and every artboard (see Out of scope for what it doesn't read yet); DWG has no open spec |+| **File interop** | ~85% | SVG/SVGZ, PDF and PDF-compatible `.ai` (and legacy version 7 `.ai` out, keeping layers, groups and type as text), EPS, DXF, DWG export (2000–2018, desktop), EMF/WMF, raster formats, PSD export and PSD/PSB placement (merged image), Place and Links, Package, Print, clipboard flavours. Affinity documents (Affinity 1–3) open natively as editable art, checked against 51 pinned public files (33 current `.af`), with selected saved-thumbnail comparisons and every-board native/SVG/PDF/PSD checks for current files; Affinity export is missing and effects, adjustments and brushes don't import. Affinity isn't an Illustrator feature, so this doesn't raise the parity score. The editing copy inside an Illustrator EPS or `.ai` is read for its structure: layers, groups, names, hidden objects and type, colours, gradients, transparency, images and every artboard (see Out of scope for what it doesn't read yet); DWG has no open spec | | **Bundled content:** brush, symbol, style and swatch libraries | ~30% | Ours are original and generated in code, and far fewer than Illustrator ships; brush and symbol libraries are still missing | | **Performance** | unverified | Multithreaded, off-thread rendering and caches are in place. The last budget run (2026-10-01, loaded machine) measured 290 ms for a 50k-path fit against a 16 ms budget. Re-run `vectorcraft-cli perf` on an idle machine | | **Robustness** | good, new | ~2,840 tests, property tests, no panics in shipped code (lints, the `guard` safety net, import fuzzing), Data Recovery. Missing: a corpus of real-world files, Windows/Linux/browser QA |@@ -851,6 +851,7 @@ Evidence for the shared light Color: geometry tests in `tests_lighting`, effect  | Date | Change | Summary | |---|---|---|+| 2026-10-20 | minor | DWG export (2000–2018, desktop) converts the DXF output with the acadrust crate, #3068; opening a DWG still answers an error; scores unchanged | | 2026-10-19 | minor | Plug-ins: live effects are given their object's fills and strokes, read only, and the item they sit on (G17); engine and unit regressions. Scores and hours unchanged. | | 2026-10-19 | minor | Plug-ins: parameters with labels, units, help, groups and conditions in their dialogs, `string` and `numberList` types, up to 256 per plug-in (G17); manifest and kittest dialog regressions. Scores and hours unchanged. | | 2026-10-19 | minor | Plug-ins: exporter plug-ins add write-only file formats to Export As and `document.export` (G17); engine, ABI and headless Export As regressions. Scores and hours unchanged. |diff --git a/xtask/src/layers.rs b/xtask/src/layers.rs--- a/xtask/src/layers.rs+++ b/xtask/src/layers.rs@@ -63,6 +63,7 @@ pub const TABLE: &[(&str, Class)] = &[     ("plugins", Class::Layer(2)),     ("affinity", Class::Standalone),     ("scriptcheck", Class::Standalone),+    ("dwg", Class::Standalone), ];  /// Explicit orderings *within* a layer (earlier may be used by later).