Merge pull request #4991 from storytold/feat/artboard-fill

Fix #4937: artboard background colours are included in exports and print
This commit is contained in:
BFlat
2026-11-01 04:31:59 +07:00
committed by GitHub
47 changed files with 330 additions and 27 deletions
+33 -2
View File
@@ -35,8 +35,8 @@ pub struct Artboard {
pub video_ruler_pixel_aspect_ratio: f64,
/// Background color (Properties › Artboards › Fill, Artboard Options). The canvas draws the
/// artboard's paper in it, multiplied with the Simulate Colored Paper color while that is on;
/// without one the paper is white, or the Simulate Colored Paper color. Exports and print leave
/// it out.
/// without one the paper is white, or the Simulate Colored Paper color. Exports and print draw
/// it behind the art ([`super::Document::with_artboard_fills`]).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub color: Option<vectorcraft_color::Color>,
}
@@ -77,6 +77,37 @@ impl Artboard {
}
}
impl super::Document {
/// A copy for output with the background colors ([`Artboard::color`]) of the artboards at
/// `indices` drawn behind all the art: each a rectangle of its artboard filled with its color
/// (in the color's own model), on a layer of their own (Artboard Fill) below the
/// others. `None` when none of them has a color.
pub fn with_artboard_fills(&self, indices: impl IntoIterator<Item = usize>) -> Option<Self> {
let fills: Vec<(Rect, vectorcraft_color::Color)> = indices
.into_iter()
.filter_map(|i| {
let a = self.artboards.get(i)?;
Some((a.rect, a.color?))
})
.collect();
if fills.is_empty() {
return None;
}
let mut d = self.clone();
let mut layer = Node::layer(d.alloc_id(), "Artboard Fill", super::LayerColor::Preset(0));
let paths: Vec<Arc<Node>> = fills
.into_iter()
.map(|(r, c)| {
let look = super::Appearance::basic(vectorcraft_color::Paint::solid(c), vectorcraft_color::Paint::None, 0.0);
Arc::new(Node::path(d.alloc_id(), vectorcraft_geom::shapes::rectangle(r), look))
})
.collect();
*layer.children_mut()? = paths;
d.layers.insert(0, Arc::new(layer));
Some(d)
}
}
/// A document's locked artboards ([`Document::locked_artboards`]).
pub struct LockedArtboards<'a> {
pub(crate) rects: Vec<Rect>,
+23
View File
@@ -55,6 +55,29 @@ fn doc_with_rects() -> (Document, NodeId, NodeId) {
(d, a, b)
}
/// Output draws the background colors of the artboards asked for, and only theirs, as rectangles
/// of those artboards on a layer below the art; without a color there is nothing to draw.
#[test]
fn artboard_fills_go_below_the_art() {
let (mut d, a, _) = doc_with_rects();
assert!(d.with_artboard_fills([0]).is_none(), "no color, no fill");
let second = Rect::new(700.0, 0.0, 800.0, 100.0);
d.artboards.push(Artboard { id: 2, rect: second, ..d.artboards[0].clone() });
let blue = vectorcraft_color::Color::cmyk(1.0, 0.5, 0.0, 0.0);
d.artboards[1].color = Some(blue);
assert!(d.with_artboard_fills([0]).is_none(), "only the artboards asked for");
let out = d.with_artboard_fills([0, 1]).unwrap();
assert_eq!(out.layers.len(), d.layers.len() + 1);
assert_eq!(out.layers[1..], d.layers[..], "the art stays as it was, above the fills");
let fills = out.layers[0].children().unwrap();
assert_eq!(out.layers[0].display_name(), "Artboard Fill");
assert_eq!(fills.len(), 1);
assert_eq!(fills[0].geometric_bounds(), Some(second));
assert_eq!(fills[0].appearance.fill_paint(), vectorcraft_color::Paint::solid(blue), "in the color's own model");
assert!(out.node(a).is_some() && out.node(fills[0].id).is_some() && fills[0].id != a);
assert_eq!(d.artboards, out.artboards);
}
#[test]
fn new_document_has_layer_and_artboard() {
let d = Document::new(612.0, 792.0);
+3
View File
@@ -57,6 +57,7 @@ pub use dxf::{UNSUPPORTED, Unsupported, unsupported};
pub use encode::{ARTBOARD_PARAMS, ArtboardPick, Encoded, encode, encode_all, encode_with_warnings};
pub(crate) use encode::{anti_alias, background, with_single_artboard};
pub use export::export_source;
pub(crate) use export::with_artboard_fills;
pub(crate) use export::{export_crop, isolated};
pub(crate) use load::check_not_lossy_overwrite;
use load::err;
@@ -132,6 +133,8 @@ mod tests;
#[cfg(test)]
mod tests_affinity;
#[cfg(test)]
mod tests_artboard_fill;
#[cfg(test)]
mod tests_pdf;
#[cfg(test)]
mod tests_pdfcolor;
+3 -3
View File
@@ -36,7 +36,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, after a central {stem}.eps of the whole document when there are two or more and art shows; 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|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), includeArtboardFill?: true (raster, PDF, SVG and EPS draw each exported artboard's background color behind its art, a transparent background too; false leaves it out), 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, after a central {stem}.eps of the whole document when there are two or more and art shows; 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
),
@@ -54,7 +54,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Export for Screens",
["File", "Export"],
None,
"{folder?, zip?: false (one .zip of every file, each deflated or stored, whichever is smaller: no folder → {name, dataBase64, bytes, files: [name…]}; with a folder it is written there → {path, bytes, files}), artboards?: [index…] | range?: \"1-3\" (default all), assets?: [asset id…] (instead of artboards: each asset's art alone, cropped to its bounds and named after the asset; see assets.list), layers?: [layer id…] (instead of artboards: each layer's art alone, sublayers included, named after the layer), selection?: one|each (instead of artboards: the selected art as one file, or a file per selected object, named after it), fullDocument?: false (one file per format instead: a PDF of every artboard, other formats the bounds of all visible art, named after the document), includeBleed?: false (artboards grown by the document's bleed), pdfs?: multiple|single (Export PDFs As: with several artboards, single writes them as the pages of one PDF per PDF row, named after the document), exportPreset?: (a preset of document.exportPresets.list: its settings, under these params), subfolders?: false (each row's files in a sub-folder: its folder, else its size for raster (1x, 2x, 100w…) or its format (SVG, PDF)), folders?: none|rows (as subfolders)|format (a folder per extension: png, svg, pdf…)|item (a folder per artboard, asset, layer or object)|itemFormat (item/extension) (default: rows with subfolders or a preset, else none), name?: (a file name pattern instead of the item's name, with <filename> for the document, <name> for the artboard, asset, layer or object, <index> from 1, #### for the counter zero-padded to the run of #, <format>, <scale>, <suffix> (else the row's suffix ends the name), YYYYMMDD, YYYY-MM-DD, YYYY, <date>, <time>, <YYYY>, <MM>, <DD>, <hh>, <mm> and <ss>; a trailing .ext is dropped, as in `COMPANY-Logo-<filename>-####-YYYYMMDD`; different files it names alike get -2, -3…), counterStart?: 1, counterPer?: item (every file of an item has its number)|file, date?: YYYY-MM-DD (the date the pattern uses; default today, UTC), preset?: mobile (PNG 1x, 2x, 3x) | density (PNG 0.75x–4x in ldpi…xxxhdpi sub-folders) instead of formats (turns subfolders on), formats?: [{format: png|png8|jpg|webp|gif|tiff|bmp|tga|psd|svg|svgz|pdf|eps|ai|vectorcraft, scale?: 1 | \"2x\" | \"100w\" | \"100h\" | \"72ppi\", width?: px | height?: px | ppi? (raster only; width, then height, then ppi win over scale), suffix?: (raster default: @2x, @100w, @100h, none at 1x; vector formats drop size suffixes), folder?: (its sub-folder), quality?: (jpg 0–100), …the format's options (antiAlias, background, preset (pdf)…)}], settings?: {png|png8|jpg|webp|gif|tiff|bmp|tga|psd|svg|svgz|pdf|eps|ai|vectorcraft: {…options for every row of that format}} (rows' own options win), prefix?, antiAlias?: none|art|type (raster rows without their own), openLocation?: (remembered; the app shows the files after exporting)} one file per artboard and format (a PDF holds its artboard alone; artboards with the same name, in any case, get -2, -3…; an unnamed one is Artboard-N) → {files: [path…]}; no folder → {files: [{name, dataBase64}]} (names include sub-folders: 2x/Icon@2x.png); every reply has warnings: [the encoders' warnings for the files, each once: an image that couldn't be decoded, type written in the fallback font…]. eps, ai and vectorcraft rows write a document of the item alone (its one artboard). The params (but zip and assets) are remembered in the document (document.exportSettings; not an undo step)",
"{folder?, zip?: false (one .zip of every file, each deflated or stored, whichever is smaller: no folder → {name, dataBase64, bytes, files: [name…]}; with a folder it is written there → {path, bytes, files}), artboards?: [index…] | range?: \"1-3\" (default all), assets?: [asset id…] (instead of artboards: each asset's art alone, cropped to its bounds and named after the asset; see assets.list), layers?: [layer id…] (instead of artboards: each layer's art alone, sublayers included, named after the layer), selection?: one|each (instead of artboards: the selected art as one file, or a file per selected object, named after it), fullDocument?: false (one file per format instead: a PDF of every artboard, other formats the bounds of all visible art, named after the document), includeBleed?: false (artboards grown by the document's bleed), includeArtboardFill?: true (Include Artboard Fill: each artboard's background color behind its art; false leaves it out), pdfs?: multiple|single (Export PDFs As: with several artboards, single writes them as the pages of one PDF per PDF row, named after the document), exportPreset?: (a preset of document.exportPresets.list: its settings, under these params), subfolders?: false (each row's files in a sub-folder: its folder, else its size for raster (1x, 2x, 100w…) or its format (SVG, PDF)), folders?: none|rows (as subfolders)|format (a folder per extension: png, svg, pdf…)|item (a folder per artboard, asset, layer or object)|itemFormat (item/extension) (default: rows with subfolders or a preset, else none), name?: (a file name pattern instead of the item's name, with <filename> for the document, <name> for the artboard, asset, layer or object, <index> from 1, #### for the counter zero-padded to the run of #, <format>, <scale>, <suffix> (else the row's suffix ends the name), YYYYMMDD, YYYY-MM-DD, YYYY, <date>, <time>, <YYYY>, <MM>, <DD>, <hh>, <mm> and <ss>; a trailing .ext is dropped, as in `COMPANY-Logo-<filename>-####-YYYYMMDD`; different files it names alike get -2, -3…), counterStart?: 1, counterPer?: item (every file of an item has its number)|file, date?: YYYY-MM-DD (the date the pattern uses; default today, UTC), preset?: mobile (PNG 1x, 2x, 3x) | density (PNG 0.75x–4x in ldpi…xxxhdpi sub-folders) instead of formats (turns subfolders on), formats?: [{format: png|png8|jpg|webp|gif|tiff|bmp|tga|psd|svg|svgz|pdf|eps|ai|vectorcraft, scale?: 1 | \"2x\" | \"100w\" | \"100h\" | \"72ppi\", width?: px | height?: px | ppi? (raster only; width, then height, then ppi win over scale), suffix?: (raster default: @2x, @100w, @100h, none at 1x; vector formats drop size suffixes), folder?: (its sub-folder), quality?: (jpg 0–100), …the format's options (antiAlias, background, preset (pdf)…)}], settings?: {png|png8|jpg|webp|gif|tiff|bmp|tga|psd|svg|svgz|pdf|eps|ai|vectorcraft: {…options for every row of that format}} (rows' own options win), prefix?, antiAlias?: none|art|type (raster rows without their own), openLocation?: (remembered; the app shows the files after exporting)} one file per artboard and format (a PDF holds its artboard alone; artboards with the same name, in any case, get -2, -3…; an unnamed one is Artboard-N) → {files: [path…]}; no folder → {files: [{name, dataBase64}]} (names include sub-folders: 2x/Icon@2x.png); every reply has warnings: [the encoders' warnings for the files, each once: an image that couldn't be decoded, type written in the fallback font…]. eps, ai and vectorcraft rows write a document of the item alone (its one artboard). The params (but zip and assets) are remembered in the document (document.exportSettings; not an undo step)",
has_doc,
screens::export_for_screens
),
@@ -72,7 +72,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Save Export Preset",
[],
None,
"{name, settings: {formats, settings, preset, name, prefix, counterStart, counterPer, folders, subfolders, includeBleed, antiAlias, folder, fullDocument} (as document.exportForScreens; other keys are left out)} save (or replace) a batch export preset, kept with the preferences; built-in names are refused → {name, created, settings}",
"{name, settings: {formats, settings, preset, name, prefix, counterStart, counterPer, folders, subfolders, includeBleed, includeArtboardFill, antiAlias, folder, fullDocument} (as document.exportForScreens; other keys are left out)} save (or replace) a batch export preset, kept with the preferences; built-in names are refused → {name, created, settings}",
always,
screens::presets_save
),
+3
View File
@@ -565,6 +565,9 @@ fn encode_files(doc: &Document, f: &Format, p: &Value) -> Result<Encoded> {
(RasterFormat::Jpeg, Some(m)) => MapKind::from_id(m).ok_or_else(|| bad(C, format!("imageMap `{m}`: none, client or server")))?,
_ => MapKind::None,
};
// The artboards' background colors, behind the art (a transparent background too).
let filled = super::export::with_artboard_fills(doc, p, chosen.iter().copied()).map_err(|e| bad(C, e))?;
let doc = &*filled;
let mut enc = Encoded::default();
let mut renderer = vectorcraft_render::Renderer::new();
let total = chosen.len() as u64;
+3
View File
@@ -300,6 +300,9 @@ fn encode_document(doc: &Document, p: &Value, saving: bool) -> Result<Encoded> {
if pages.is_empty() {
return Err(bad(C, "the document has no artboard"));
}
// The artboards' background colors, behind the art (the editing data keeps them as colors).
let filled = super::export::with_artboard_fills(doc, p, pages.iter().filter_map(|(b, ..)| *b)).map_err(|e| bad(C, e))?;
let doc = &*filled;
let regions: Vec<Rect> = pages.iter().map(|(_, region, _)| *region).collect();
let expanded = expand_transparent_tile_images(doc, &set.flatten)?;
let source = expanded.as_ref().unwrap_or(doc);
+15
View File
@@ -146,6 +146,21 @@ pub(super) fn art_over(doc: &Document, rect: vectorcraft_geom::Rect) -> Document
d
}
/// `doc` with the background colors of `artboards` drawn behind its art
/// ([`Document::with_artboard_fills`]), what exports and print draw, unless `p` turns
/// `includeArtboardFill` off (Export for Screens › Include Artboard Fill; on by default).
pub(crate) fn with_artboard_fills<'a>(
doc: &'a Document,
p: &Value,
artboards: impl IntoIterator<Item = usize>,
) -> std::result::Result<Cow<'a, Document>, String> {
let include = match p.get("includeArtboardFill") {
None | Some(Value::Null) => true,
Some(v) => v.as_bool().ok_or("`includeArtboardFill` must be true or false")?,
};
Ok(include.then(|| doc.with_artboard_fills(artboards)).flatten().map_or(Cow::Borrowed(doc), Cow::Owned))
}
/// `p` without its artboard choice, also inside its SVG options (for documents made of one
/// synthetic artboard, and for callers that pick the artboard themselves).
pub(super) fn without_artboards(p: &Value) -> Value {
+4
View File
@@ -284,6 +284,10 @@ fn overflow_warning(cmd: &str, doc: &Document, p: &Value) -> Result<Option<Strin
/// native document with its save options, whose pages may be left blank).
pub(super) fn encode_carrying(cmd: &str, doc: &Document, p: &Value, native: impl FnOnce() -> Result<Vec<u8>>) -> Result<(Vec<u8>, Vec<String>)> {
let mut opts = options(cmd, doc, p)?;
// The pages' background colors, behind the art (the editing data keeps them as colors).
let pages = opts.artboards.clone().unwrap_or_else(|| (0..doc.artboards.len()).collect());
let filled = super::export::with_artboard_fills(doc, p, pages).map_err(|e| bad(cmd, e))?;
let doc = &*filled;
if opts.settings.thumbnails {
opts.thumbnails = thumbnails(doc, &opts).map_err(|e| pdf_error(cmd, e))?;
}
+12 -3
View File
@@ -21,7 +21,7 @@ use super::encode::single_artboard;
use super::export::{export_crop, isolated, selected_artwork, without_artboards};
use super::naming::{self, NameParts};
use super::{
ARTBOARD_PARAMS, ArtboardPick, Format, artboard_file_names, create_dir, distinct_names, encode_with_warnings, unique_file_names,
ARTBOARD_PARAMS, ArtboardPick, Format, artboard_file_names, create_dir, distinct_names, encode_with_warnings, merge, unique_file_names,
with_black_output, writable, write_file,
};
@@ -289,6 +289,8 @@ struct ScreenParams {
zip: bool,
full_document: bool,
include_bleed: bool,
/// Include Artboard Fill: the artboards' background colors behind the art (default on).
include_artboard_fill: Option<bool>,
subfolders: bool,
preset: Option<String>,
prefix: Option<String>,
@@ -579,7 +581,7 @@ pub struct ScreenExportPreset {
/// What a preset keeps: the formats, their settings, the naming and the folders; not which art
/// (it changes with the document) nor where to (unless given).
pub const PRESET_KEYS: [&str; 13] = [
pub const PRESET_KEYS: [&str; 14] = [
"formats",
"settings",
"preset",
@@ -590,6 +592,7 @@ pub const PRESET_KEYS: [&str; 13] = [
"folders",
"subfolders",
"includeBleed",
"includeArtboardFill",
"antiAlias",
"folder",
"fullDocument",
@@ -708,7 +711,9 @@ pub(crate) fn export(s: &mut Session, p: &Value) -> Result<Value> {
let crop = rows.iter().any(|r| r.format.id != "pdf");
let items = items(&doc, &o, selection, &selected, &naming.filename, crop, true)?;
// Appearance of Black › Printing / Exporting for RGB raster rows.
let rows = rows.into_iter().map(|r| Row { options: with_black_output(s, &r.options), ..r }).collect();
// Include Artboard Fill off: the encoders leave the background colors out.
let fill = if o.include_artboard_fill == Some(false) { json!({ "includeArtboardFill": false }) } else { json!({}) };
let rows = rows.into_iter().map(|r| Row { options: merge(with_black_output(s, &r.options), fill.clone()), ..r }).collect();
let export = ScreenExport { cmd: C, o, rows, naming, items };
match &mut s.screens_hand_off {
Some(slot) => {
@@ -830,6 +835,10 @@ fn encode_and_write(export: ScreenExport, writer: &mut dyn FnMut(&str, &[u8]) ->
.entry(b)
.or_insert_with(|| {
let mut d = single_artboard(&item.doc, item.region, &item.name);
// It keeps its background color.
if let (Some(a), Some(from)) = (d.artboards.first_mut(), item.doc.artboards.get(b)) {
a.color = from.color;
}
super::save::keep_art_on(&mut d, item.region);
Arc::new(d)
})
+3 -1
View File
@@ -169,7 +169,9 @@ pub(super) fn encode(doc: &Document, p: &Value, compressed: bool) -> Result<Enco
// Draw linked files as vectors when available, or their previews with a warning. Keep
// that preparation out of the native attachment so missing links remain relinkable.
let (full, warnings) = crate::cmd::place::document::full_documents(doc);
let doc = &*full;
// The artboards' background colors, behind the art (the editing data keeps them as colors).
let filled = super::export::with_artboard_fills(&full, p, boards.iter().flatten().copied())?;
let doc = &*filled;
// A chosen artboard's file holds the art over it; otherwise the file holds all of it.
let regions: Option<Vec<vectorcraft_geom::Rect>> =
chosen.then(|| boards.iter().flatten().filter_map(|b| doc.artboards.get(*b)).map(|a| a.rect).collect());
@@ -0,0 +1,134 @@
//! Artboard background colors in output (#4937): raster, PDF, SVG and EPS exports, Export for
//! Screens and print draw each artboard's color behind its art, inside the artboard;
//! `includeArtboardFill: false` (Include Artboard Fill) leaves it out; the editing data keeps it as
//! the artboard's color, not as art.
use serde_json::{Value, json};
use super::*;
const RED: [u8; 4] = [255, 0, 0, 255];
/// Two 60×40 pt artboards, a 20 pt square (white fill, black stroke) on the first, which is red.
fn session() -> Session {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 60, "height": 40, "artboards": 2})).unwrap();
s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 20, "height": 20})).unwrap();
s.execute("artboard.setProps", &json!({"index": 0, "color": "#ff0000"})).unwrap();
s
}
fn b64(v: &Value) -> Vec<u8> {
vectorcraft_format::base64_decode(v["dataBase64"].as_str().expect("dataBase64")).unwrap()
}
fn with(mut p: Value, more: &Value) -> Value {
if let (Some(o), Some(m)) = (p.as_object_mut(), more.as_object()) {
o.extend(m.iter().map(|(k, v)| (k.clone(), v.clone())));
}
p
}
/// The pixels of the PNG `document.export` writes of the active document with `p`, on transparency.
fn png(s: &mut Session, p: Value) -> image::RgbaImage {
let v = s.execute("document.export", &with(json!({"format": "png", "background": "transparent"}), &p)).unwrap();
image::load_from_memory(&b64(&v)).unwrap().to_rgba8()
}
/// The file `document.export` writes with `p`.
fn file(s: &mut Session, p: Value) -> Vec<u8> {
b64(&s.execute("document.export", &p).unwrap())
}
/// The file `bytes` named `name`, opened in a new session.
fn opened(name: &str, bytes: &[u8]) -> Session {
let mut s = Session::new();
s.execute("document.open", &json!({"name": name, "dataBase64": vectorcraft_format::base64_encode(bytes)})).unwrap();
s
}
/// What the file `bytes` named `name` shows near its top-left corner, opened and drawn.
fn corner(name: &str, bytes: &[u8]) -> [u8; 4] {
png(&mut opened(name, bytes), json!({"artboard": 0})).get_pixel(2, 2).0
}
#[test]
fn raster_exports_paint_the_color_behind_the_art() {
let mut s = session();
let on = png(&mut s, json!({"artboard": 0}));
assert_eq!(on.get_pixel(2, 2).0, RED, "a transparent background still shows the color");
assert_eq!(on.get_pixel(20, 20).0, [255, 255, 255, 255], "the art over it");
assert_eq!(png(&mut s, json!({"artboard": 0, "includeArtboardFill": false})).get_pixel(2, 2)[3], 0, "left out");
assert_eq!(png(&mut s, json!({"artboard": 1})).get_pixel(2, 2)[3], 0, "an artboard without a color");
let jpg = file(&mut s, json!({"format": "jpg", "artboard": 0, "quality": 100}));
let px = image::load_from_memory(&jpg).unwrap().to_rgb8().get_pixel(2, 2).0;
assert!(px[0] > 240 && px[1] < 16 && px[2] < 16, "JPEG too: {px:?}");
for bad in [json!("no"), json!(1)] {
assert!(s.execute("document.export", &json!({"format": "png", "includeArtboardFill": bad})).is_err(), "{bad}");
}
assert_eq!(s.doc().unwrap().doc.layers.len(), 1, "the document gains no layer");
}
#[test]
fn the_color_fills_its_artboard_alone_behind_the_art() {
let s = session();
let doc = &s.doc().unwrap().doc;
let board = doc.artboards[0].rect;
let filled = doc.with_artboard_fills([0, 1]).unwrap();
let img = vectorcraft_render::Renderer::new().render_region(&filled, board.inflate(10.0, 10.0), 1.0, false);
let rgba = img.to_straight();
let px = |x: usize, y: usize| rgba[(y * img.width as usize + x) * 4..][..4].to_vec();
assert_eq!(px(5, 5)[3], 0, "outside the artboard");
assert_eq!(px(15, 15), RED, "inside it");
assert_eq!(px(30, 30), [255, 255, 255, 255], "the art over it");
}
#[test]
fn pdf_and_svg_show_the_color_and_their_editing_data_keeps_it_as_the_artboards() {
let mut s = session();
for (format, name) in [("pdf", "a.pdf"), ("svg", "a.svg")] {
let p = json!({"format": format, "artboard": 0, "preserveEditing": false});
assert_eq!(corner(name, &file(&mut s, p.clone())), RED, "{format}");
assert_eq!(corner(name, &file(&mut s, with(p, &json!({"includeArtboardFill": false}))))[3], 0, "{format}");
}
let reopened = opened("a.pdf", &file(&mut s, json!({"format": "pdf", "preserveEditing": true})));
let (back, doc) = (&reopened.doc().unwrap().doc, &s.doc().unwrap().doc);
assert_eq!(back.artboards[0].color, doc.artboards[0].color);
assert_eq!(back.layers.len(), doc.layers.len(), "no art made of it");
}
#[test]
fn eps_and_postscript_print_draw_the_color() {
let mut s = session();
// The only red the document has, the first artboard's color: RGB, or CMYK PostScript's.
let red = |t: &str| t.contains("1 0 0 rg\n") || t.contains("0 0.9725 1 0 k\n");
let eps = |s: &mut Session, more: Value| {
String::from_utf8_lossy(&file(s, with(json!({"format": "eps", "useArtboards": true, "artboard": 0}), &more))).into_owned()
};
assert!(red(&eps(&mut s, json!({}))), "EPS");
assert!(!red(&eps(&mut s, json!({"includeArtboardFill": false}))), "EPS without it");
let ps = |s: &mut Session, more: Value| {
String::from_utf8_lossy(&b64(&s.execute("file.print", &with(json!({"format": "postscript"}), &more)).unwrap())).into_owned()
};
assert!(red(&ps(&mut s, json!({}))), "print");
assert!(!red(&ps(&mut s, json!({"includeArtboardFill": false}))), "print without it");
}
#[test]
fn export_for_screens_includes_the_fill_unless_unchecked() {
let mut s = session();
let screens = |s: &mut Session, more: Value| {
let p = with(json!({"artboards": [0], "formats": [{"format": "png"}], "settings": {"png": {"background": "transparent"}}}), &more);
s.execute("document.exportForScreens", &p).unwrap()
};
let corner = |r: &Value| image::load_from_memory(&b64(&r["files"][0])).unwrap().to_rgba8().get_pixel(2, 2).0;
assert_eq!(corner(&screens(&mut s, json!({}))), RED, "on by default");
assert_eq!(corner(&screens(&mut s, json!({"includeArtboardFill": false})))[3], 0, "unchecked");
assert_eq!(s.execute("document.exportSettings", &json!({})).unwrap()["settings"]["includeArtboardFill"], false, "remembered");
assert!(s.execute("document.exportForScreens", &json!({"includeArtboardFill": "no", "formats": [{"format": "png"}]})).is_err());
// A document row's artboard keeps its color, as a color.
let r = screens(&mut s, json!({"formats": [{"format": "vectorcraft"}]}));
let back = vectorcraft_format::load(&b64(&r["files"][0])).unwrap();
assert_eq!(back.artboards[0].color, s.doc().unwrap().doc.artboards[0].color);
assert_eq!(back.layers.len(), 1, "not as art");
}
+4 -2
View File
@@ -41,7 +41,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Print",
[],
Some("Cmd+P"),
"{preset?: name (as print.setup's: its settings instead of the document's; the saved settings stay), settings?: {…settings of print.setup} (over the document's or the preset's), path?, format?: pdf (default: a print-ready PDF) | postscript (the default for a .ps path: a PostScript file with DSC comments, %%Pages, a page per sheet with its paper size, marks, separations with their ink and halftone screen, the fixed flatness, negatives), level?: 3|2 (the PostScript language level), flattenerPreset?: \"medium\" (PostScript: the preset transparency is flattened with — high, low or a saved one, see flattener.presets.list), acknowledgeLoss?: bool (with path: write over the file the document was read from although reading it left things out), forPrinter?: bool (the PDF goes to a print queue: a page whose paper is turned to landscape is written upright, the art turned a quarter anticlockwise, as a printer driver turns a landscape job; macOS prints a landscape page as it is, cut off on upright paper; paper wide in Portrait and transverse pages stay as they are)} print the document, one page per sheet of paper (per tile, per ink, per copy) → {pages, format, path, bytes, warnings}; no path → {dataBase64, bytes, pages, format, warnings}. Raster effects print at the document's raster effects resolution; a PDF leaves halftone screens and flatness to the output device",
"{preset?: name (as print.setup's: its settings instead of the document's; the saved settings stay), settings?: {…settings of print.setup} (over the document's or the preset's), includeArtboardFill?: true (each artboard's background color prints behind its art; false leaves it out), path?, format?: pdf (default: a print-ready PDF) | postscript (the default for a .ps path: a PostScript file with DSC comments, %%Pages, a page per sheet with its paper size, marks, separations with their ink and halftone screen, the fixed flatness, negatives), level?: 3|2 (the PostScript language level), flattenerPreset?: \"medium\" (PostScript: the preset transparency is flattened with — high, low or a saved one, see flattener.presets.list), acknowledgeLoss?: bool (with path: write over the file the document was read from although reading it left things out), forPrinter?: bool (the PDF goes to a print queue: a page whose paper is turned to landscape is written upright, the art turned a quarter anticlockwise, as a printer driver turns a landscape job; macOS prints a landscape page as it is, cut off on upright paper; paper wide in Portrait and transverse pages stay as they are)} print the document, one page per sheet of paper (per tile, per ink, per copy) → {pages, format, path, bytes, warnings}; no path → {dataBase64, bytes, pages, format, warnings}. Raster effects print at the document's raster effects resolution; a PDF leaves halftone screens and flatness to the output device",
has_doc,
print
),
@@ -176,7 +176,9 @@ fn print(s: &mut Session, p: &Value) -> Result<Value> {
}
// Placed documents print their files' art.
let (full, _) = super::place::document::full_documents(doc);
let doc = &*full;
// The artboards' background colors print behind the art.
let filled = super::fileio::with_artboard_fills(&full, p, 0..full.artboards.len()).map_err(|e| bad(PRINT, e))?;
let doc = &*filled;
// Raster effects print as images at the document's raster effects resolution.
let flat = super::rasterfx::flatten_raster_effects(doc);
let doc = flat.as_ref().unwrap_or(doc);
+1 -1
View File
@@ -42,7 +42,7 @@ const HOST_COMMANDS: &[(&str, &str, &str)] = &[
(
"file.exportForScreens",
"Export for Screens…",
"{folder?, zip?, artboards? | range? | fullDocument? | assets?, includeBleed?, subfolders?, preset?, formats?, settings?, prefix?} = document.exportForScreens (no folder → the files, or one zip, as dataBase64)",
"{folder?, zip?, artboards? | range? | fullDocument? | assets?, includeBleed?, includeArtboardFill?, subfolders?, preset?, formats?, settings?, prefix?} = document.exportForScreens (no folder → the files, or one zip, as dataBase64)",
),
("tool.select", "Select Tool", "{tool} e.g. selection, directSelection, pen, rectangle, ellipse, polygon, star, lineSegment"),
(
@@ -1,6 +1,6 @@
//! File → Export → Export for Screens, laid out as Illustrator's: on the left the Artboards and
//! Assets tabs (thumbnails with checkboxes, in a grid or a list), Clear Selection and the prefix;
//! on the right Select (All, a range or Full Document, Include Bleed), the destination (folder
//! on the right Select (All, a range or Full Document, Include Bleed, Include Artboard Fill), the destination (folder
//! picker, Open Location after Export, Create Sub-folders by scale or by format; the web downloads
//! instead), Export PDFs As (one file of every artboard or a file each), and the format rows (scale
//! as `2x`, `100w`, `100h` or `72ppi`, suffix, format) with the iOS and Android rows, the format
@@ -16,6 +16,7 @@
//!
//! Fields (`ui.dialog.set`): `tab` (artboards|assets), `select` (all|range|full), `range`,
//! `boards` ([bool] per artboard), `assets` ([asset id] checked on the Assets tab), `includeBleed`,
//! `includeArtboardFill`,
//! `folder`, `openLocation`, `subfolders`, `subfolderBy` (scale|format), `pdfs` (single|multiple),
//! `prefix`, `preset` (""|mobile|density: where the rows came from), `formats` (rows of
//! `document.exportForScreens`, `scale` as text), `settings` ({png|png8|jpg|webp|gif|tiff|svg|pdf:
@@ -164,6 +165,7 @@ pub fn open(app: &mut VectorcraftApp) {
"boards": boards,
"assets": assets,
"includeBleed": flag("includeBleed", false),
"includeArtboardFill": flag("includeArtboardFill", true),
"folder": folder.unwrap_or_default(),
"openLocation": flag("openLocation", true),
"subfolders": subfolders,
@@ -324,6 +326,7 @@ fn selection_params(app: &VectorcraftApp, d: &Dialog) -> Result<(Map<String, Val
let pdf_rows = rows.iter().filter(|r| r["format"].as_str().and_then(fileio::format).is_some_and(|f| f.id == "pdf")).count();
let files = if single { (rows.len() - pdf_rows) * pieces + pdf_rows } else { rows.len() * pieces };
p.insert("includeBleed".into(), json!(d.bool("includeBleed")));
p.insert("includeArtboardFill".into(), json!(d.bool("includeArtboardFill")));
let folders = match (d.bool("subfolders"), d.str("subfolderBy").as_str()) {
(false, _) => "none",
(true, "format") => "format",
@@ -448,7 +451,8 @@ fn boards(app: &VectorcraftApp, d: &Dialog) -> Vec<bool> {
boards
}
/// Select: All or a Range (which checks the artboards it names), Include Bleed and Full Document.
/// Select: All or a Range (which checks the artboards it names), Include Bleed, Include Artboard
/// Fill and Full Document.
fn select_options(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
let n = app.session.active().map_or(0, |st| st.doc.artboards.len());
let select = d.str("select");
@@ -470,7 +474,10 @@ fn select_options(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) {
}
});
});
form::check(ui, d, "includeBleed", tl!("Include Bleed"));
ui.horizontal(|ui| {
form::check(ui, d, "includeBleed", tl!("Include Bleed"));
form::check(ui, d, "includeArtboardFill", tl!("Include Artboard Fill"));
});
if widgets::radio(ui, tl!("Full Document"), d.str("select") == "full", true) {
set_select(d, "full");
}
@@ -740,8 +747,10 @@ pub(super) fn apply_preset(d: &mut Dialog, preset: &Value) {
if let Some(prefix) = st["prefix"].as_str() {
d.fields.insert("prefix".into(), json!(prefix));
}
if let Some(b) = st["includeBleed"].as_bool() {
d.fields.insert("includeBleed".into(), json!(b));
for key in ["includeBleed", "includeArtboardFill"] {
if let Some(b) = st[key].as_bool() {
d.fields.insert(key.into(), json!(b));
}
}
d.fields.insert("exportPreset".into(), json!(preset["name"]));
}
+2 -1
View File
@@ -545,7 +545,8 @@ fn art_texture(app: &mut VectorcraftApp, ctx: &egui::Context, layers: PrintLayer
{
return Some(tex);
}
let mut doc = (*st.doc).clone();
// The artboards' background colors print behind the art.
let mut doc = st.doc.with_artboard_fills(0..st.doc.artboards.len()).unwrap_or_else(|| (*st.doc).clone());
vectorcraft_pdf::keep_layers(&mut doc.layers, layers);
let tex = widgets::region_texture(ctx, &mut app.canvas.renderer, "print-preview-art", &doc, area, px);
ctx.data_mut(|m| m.insert_temp(key, (stamp, tex.clone())));
@@ -65,6 +65,7 @@ fn opens_on_defaults_and_draws_every_view() {
assert_eq!(field(&app, "folder"), json!(fileio::export_folder().unwrap_or_default()), "the Desktop, else home");
assert_eq!((field(&app, "openLocation"), field(&app, "subfolders"), field(&app, "preset")), (json!(true), json!(true), json!("")));
assert_eq!((field(&app, "subfolderBy"), field(&app, "pdfs"), field(&app, "view")), (json!("scale"), json!("multiple"), json!("grid")));
assert_eq!((field(&app, "includeBleed"), field(&app, "includeArtboardFill")), (json!(false), json!(true)));
let settings = field(&app, "settings");
for f in ["png", "png8", "jpg", "webp", "gif", "tiff", "svg", "pdf"] {
assert!(settings[f].is_object(), "{f} has settings");
@@ -450,6 +451,30 @@ fn the_web_downloads_one_zip_or_the_file() {
assert_eq!(image::load_from_memory(&log[0].1).unwrap().to_rgba8().get_pixel(1, 1).0, [0, 0, 0, 255], "the PNG settings' background");
}
/// Include Artboard Fill: checked, the file shows the artboard's background color behind the art
/// (on transparency too); unchecked, it doesn't, and the dialog reopens unchecked.
#[test]
fn include_artboard_fill_reaches_the_files_and_is_remembered() {
let (mut app, log) = app(1, true);
app.run("artboard.setProps", json!({"index": 0, "color": "#ff0000"})).unwrap();
for include in [true, false] {
log.borrow_mut().clear();
open(&mut app);
assert_eq!(field(&app, "includeArtboardFill"), json!(true), "on until unchecked");
set(&mut app, "includeArtboardFill", json!(include));
set(&mut app, "settings", json!({"png": {"background": "transparent"}}));
frame(&mut app);
confirm(&mut app).unwrap();
let px = image::load_from_memory(&log.borrow()[0].1).unwrap().to_rgba8().get_pixel(58, 38).0;
assert_eq!(px, if include { [255, 0, 0, 255] } else { [0, 0, 0, 0] }, "include {include}");
if include {
continue;
}
open(&mut app);
assert_eq!(field(&app, "includeArtboardFill"), json!(false), "remembered");
}
}
#[test]
fn the_status_bar_says_when_an_image_could_not_be_decoded() {
const NOTE: &str = "with 1 note(s): an image couldn't be decoded and is missing from the picture";
@@ -784,6 +809,7 @@ fn the_dialog_is_laid_out_as_illustrators() {
"Select:",
"Full Document",
"Include Bleed",
"Include Artboard Fill",
"Export to:",
"Open Location after Export",
"Create Sub-folders",
+1
View File
@@ -1524,6 +1524,7 @@ font-technology Variable Променливи
In Tangent Handles Входящи управляващи точки
In the selected objects В избраните обекти
Inches Инчове
Include Artboard Fill Включване на запълването на работната област
Include Blacks with CMY: Включване на черни към CMY:
Include Bleed Включване на изтичане
Include Cmy Blacks Включване на CMYK черни
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2221,6 +2221,7 @@ digits Malayalam Malajalam
Compatible Gradient and Gradient Mesh Printing Kompatibilno štampanje gradijenata i gradijentnih mreža
Embed Fonts (for other applications) Ugradi fontove (za druge aplikacije)
EPS Options EPS opcije
Include Artboard Fill Uključi ispunu radne ploče
Include CMYK PostScript in RGB Files Uključi CMYK PostScript u RGB datoteke
Include Document Thumbnails Uključi sličice dokumenta
Include Linked Files Uključi povezane datoteke
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1944,6 +1944,7 @@ digits Malayalam Malaiàlam
In the selected objects Als objectes seleccionats
In VectorCraft A VectorCraft
Inches Polzades
Include Artboard Fill Inclou el farciment del tauler
Include all profiles Inclou tots els perfils
Include All Subdirectories Inclou tots els subdirectoris
Include Blacks with CMY: Inclou negres amb CMY:
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2249,6 +2249,7 @@ digits Malayalam Malajálamské
Compatible Gradient and Gradient Mesh Printing Kompatibilní tisk přechodů a přechodových sítí
Embed Fonts (for other applications) Vložit písma (pro jiné aplikace)
EPS Options Možnosti EPS
Include Artboard Fill Zahrnout výplň pracovní plochy
Include CMYK PostScript in RGB Files Zahrnout CMYK PostScript do souborů RGB
Include Document Thumbnails Zahrnout miniatury dokumentu
Include Linked Files Zahrnout propojené soubory
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2052,6 +2052,7 @@ digits Malayalam Malayalam
Compatible Gradient and Gradient Mesh Printing Kompatibel udskrivning af gradienter og gradientnet
Embed Fonts (for other applications) Indlejr skrifttyper (til andre programmer)
EPS Options EPS-indstillinger
Include Artboard Fill Medtag kunstpladens udfyldning
Include CMYK PostScript in RGB Files Medtag CMYK-PostScript i RGB-filer
Include Document Thumbnails Medtag dokumentminiaturer
Include Linked Files Medtag linkede filer
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1562,6 +1562,7 @@ file-filter Templates Vorlagen
In Tangent Handles Eingehende Tangentengriffe
In the selected objects In den ausgewählten Objekten
Inches Zoll
Include Artboard Fill Zeichenflächenfüllung einschließen
Include Blacks with CMY: Schwarz mit CMY einschließen:
Include Bleed Anschnitt einschließen
Include Cmy Blacks CMY-Schwarz einschließen
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1487,6 +1487,7 @@ font-technology Variable Μεταβλητές
In Tangent Handles Λαβές εφαπτομένης εισόδου
In the selected objects Στα επιλεγμένα αντικείμενα
Inches Ίντσες
Include Artboard Fill Συμπερίληψη γεμίσματος πίνακα σχεδίασης
Include Blacks with CMY: Συμπερίληψη μαύρων με CMY:
Include Bleed Συμπερίληψη ξακρίσματος
Include Cmy Blacks Συμπερίληψη μαύρων CMY
Can't render this file because it contains an unexpected character in line 164 and column 5.
+1
View File
@@ -1562,6 +1562,7 @@ font-technology Variable Variables
In Tangent Handles Manejadores de tangente de entrada
In the selected objects En los objetos seleccionados
Inches Pulgadas
Include Artboard Fill Incluir relleno de la mesa de trabajo
Include Blacks with CMY: Incluir negros con CMY:
Include Bleed Incluir sangrado
Include Cmy Blacks Incluir negros CMY
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2204,6 +2204,7 @@ digits Malayalam Malajalam
Compatible Gradient and Gradient Mesh Printing Yhteensopiva liukuvärin ja liukuväriverkon tulostus
Embed Fonts (for other applications) Upota fontit (muita sovelluksia varten)
EPS Options EPS-asetukset
Include Artboard Fill Sisällytä piirtoalustan täyttö
Include CMYK PostScript in RGB Files Sisällytä CMYK PostScript RGB-tiedostoihin
Include Document Thumbnails Sisällytä asiakirjan pikkukuvat
Include Linked Files Sisällytä linkitetyt tiedostot
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1562,6 +1562,7 @@ font-technology Variable Variables
In Tangent Handles Poignées de tangente entrantes
In the selected objects Dans les objets sélectionnés
Inches Pouces
Include Artboard Fill Inclure le fond du plan de travail
Include Blacks with CMY: Inclure les noirs avec CMJ :
Include Bleed Inclure le fond perdu
Include Cmy Blacks Inclure les noirs CMJ
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1961,6 +1961,7 @@ file-filter Templates Sablonok
In Tangent Handles Bejövő érintőfogantyúk
In the selected objects A kijelölt objektumokban
Inches Hüvelyk
Include Artboard Fill Rajztábla kitöltésével együtt
Include all profiles Összes profil beágyazása
Include Blacks with CMY: CMY-alapú feketék is:
Include Bleed Kifutóval együtt
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2052,6 +2052,7 @@ digits Malayalam Malayalam
Compatible Gradient and Gradient Mesh Printing Pencetakan gradien dan mesh gradien yang kompatibel
Embed Fonts (for other applications) Sematkan font (untuk aplikasi lain)
EPS Options Opsi EPS
Include Artboard Fill Sertakan isian artboard
Include CMYK PostScript in RGB Files Sertakan PostScript CMYK dalam file RGB
Include Document Thumbnails Sertakan miniatur dokumen
Include Linked Files Sertakan file tertaut
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
2052
2053
2054
2055
2056
2057
2058
+1
View File
@@ -1586,6 +1586,7 @@ file-filter Templates Modelli
In Tangent Handles Maniglie tangente in entrata
In the selected objects Negli oggetti selezionati
Inches Pollici
Include Artboard Fill Includi riempimento tavola da disegno
Include Blacks with CMY: Includi neri con CMY:
Include Bleed Includi abbondanza
Include Cmy Blacks Includi neri CMY
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1856,6 +1856,7 @@ digits Malayalam マラヤーラム数字
In Tangent Handles 入る側のハンドル
In the selected objects 選択したオブジェクト内
Inches インチ
Include Artboard Fill アートボードの塗りを含める
Include Bleed 裁ち落としを含める
Include CMYK PostScript in RGB Files RGB ファイルに CMYK PostScript を含める
Include Document Thumbnails ドキュメントのサムネールを含める
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1952,6 +1952,7 @@ digits Malayalam მალაიალამური
Compatible Gradient and Gradient Mesh Printing თავსებადი გრადიენტისა და გრადიენტული ბადის ბეჭდვა
Embed Fonts (for other applications) შრიფტების ჩაშენება (სხვა აპლიკაციებისთვის)
EPS Options EPS-ის პარამეტრები
Include Artboard Fill სამუშაო დაფის შევსების ჩართვა
Include CMYK PostScript in RGB Files CMYK PostScript-ის ჩართვა RGB ფაილებში
Include Document Thumbnails დოკუმენტის ესკიზების ჩართვა
Include Linked Files დაკავშირებული ფაილების ჩართვა
Can't render this file because it is too large.
+1
View File
@@ -1884,6 +1884,7 @@ digits Malayalam 말라얄람어
In Tangent Handles 안쪽 접선 핸들
In the selected objects 선택한 개체 안에서
Inches 인치
Include Artboard Fill 아트보드 칠 포함
Include Bleed 블리드 포함
Include CMYK PostScript in RGB Files RGB 파일에 CMYK PostScript 포함
Include Document Thumbnails 문서 미리 보기 이미지 포함
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2066,6 +2066,7 @@ digits Malayalam Malayalam
Compatible Gradient and Gradient Mesh Printing Compatibel afdrukken van verlopen en verlooprasters
Embed Fonts (for other applications) Lettertypen insluiten (voor andere programma's)
EPS Options EPS-opties
Include Artboard Fill Tekenbordvulling opnemen
Include CMYK PostScript in RGB Files CMYK PostScript opnemen in RGB-bestanden
Include Document Thumbnails Documentminiaturen opnemen
Include Linked Files Gekoppelde bestanden opnemen
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2165,6 +2165,7 @@ digits Malayalam Malajalam
Compatible Gradient and Gradient Mesh Printing Zgodne drukowanie gradientów i siatek gradientowych
Embed Fonts (for other applications) Osadź czcionki (dla innych aplikacji)
EPS Options Opcje EPS
Include Artboard Fill Uwzględnij wypełnienie obszaru kompozycji
Include CMYK PostScript in RGB Files Dołącz CMYK PostScript w plikach RGB
Include Document Thumbnails Dołącz miniatury dokumentu
Include Linked Files Dołącz połączone pliki
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1347,6 +1347,7 @@
In Tangent Handles Alças de Tangente de Entrada
In the selected objects Nos objetos selecionados
Inches Polegadas
Include Artboard Fill Incluir Preenchimento da Prancheta
Include Bleed Incluir Sangria
Include CMYK PostScript in RGB Files Incluir PostScript CMYK em Arquivos RGB
Include Document Thumbnails Incluir Miniaturas do Documento
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -1584,6 +1584,7 @@ file-filter Templates Шаблоны
In Tangent Handles Маркеры входящей касательной
In the selected objects В выделенных объектах
Inches Дюймы
Include Artboard Fill Включать заливку монтажной области
Include Blacks with CMY: Включать чёрные с CMY:
Include Bleed Включать вылет
Include Cmy Blacks Включать чёрные CMY
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1526,6 +1526,7 @@ font-technology Variable Variabla
In Tangent Handles Inkommande tangenthandtag
In the selected objects I de markerade objekten
Inches Tum
Include Artboard Fill Inkludera ritytans fyllning
Include Blacks with CMY: Inkludera svart med CMY:
Include Bleed Inkludera utfall
Include Cmy Blacks Inkludera CMY-svarta
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -1412,6 +1412,7 @@ font-technology Variable Değişken
In Tangent Handles Teğet Tutamaçlarda
In the selected objects Seçilen nesnelerde
Inches İnç
Include Artboard Fill Çalışma Yüzeyi Dolgusunu Dahil Et
Include Blacks with CMY: CMY ile Siyahları Dahil Et:
Include Bleed Taşmayı Dahil Et
Include Cmy Blacks Cmy Black'leri dahil et
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -1580,6 +1580,7 @@ file-filter Templates Шаблони
In Tangent Handles Маркери вхідної дотичної
In the selected objects У виділених об’єктах
Inches Дюйми
Include Artboard Fill Включати заливку монтажної області
Include Blacks with CMY: Включати чорні з CMY:
Include Bleed Включати виліт
Include Cmy Blacks Включати чорні CMY
Can't render this file because it contains an unexpected character in line 178 and column 5.
+1
View File
@@ -1842,6 +1842,7 @@ digits Hindi Hindi
In Tangent Handles Tay nắm tiếp tuyến vào
In the selected objects Trong các đối tượng đã chọn
Inches Inch
Include Artboard Fill Bao gồm màu tô bảng vẽ
Include Blacks with CMY: Bao gồm đen với CMY:
Include Bleed Bao gồm vùng chảy tràn
Include Cmy Blacks Bao gồm đen CMY
Can't render this file because it contains an unexpected character in line 200 and column 5.
+1
View File
@@ -1513,6 +1513,7 @@ file-filter Templates 模板
In Tangent Handles 内侧切线控制手柄
In the selected objects 在所选对象中
Inches 英寸
Include Artboard Fill 包含画板填色
Include Blacks with CMY: 包含 CMY 的黑色:
Include Bleed 包含出血
Include Cmy Blacks 包含 CMY 黑色
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1551,6 +1551,7 @@ file-filter Templates 範本
In Tangent Handles 內側切線控制把手
In the selected objects 在選取的物件中
Inches 英吋
Include Artboard Fill 包含工作區域填色
Include Blacks with CMY: 包含 CMY 的黑色:
Include Bleed 包含出血
Include Cmy Blacks 包含 CMY 黑色
Can't render this file because it contains an unexpected character in line 6 and column 47.
+2 -1
View File
@@ -1,6 +1,6 @@
# File format parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (PDF import: fonts named with hyphens or with a composite font's CMap find the installed family) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (exports draw artboard background colors, #4937) · **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/formats.rs`). Illustrator's list comes from its public
@@ -131,6 +131,7 @@ the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPri
| Date | Change | Summary |
|---|---|---|
| 2026-11-01 | minor | Exports draw each artboard's background color behind its art (#4937): PNG, JPEG, GIF, TIFF, WebP, BMP, Targa and PSD (a layer of its own; on a transparent background too), PDF, SVG and EPS pages, a `.ai` file's PDF pages and PostScript and PDF print; `includeArtboardFill: false` leaves it out. Files with editing data (Preserve Editing, `.ai`, EPS, native) keep it as the artboard's color and reopen without extra art. Not read from the reference app's own `.ai` data, nor written to it. DXF, EMF and WMF leave it out |
| 2026-10-31 | minor | PDF import finds an installed font whose PDF name holds hyphens (`Noto-Sans-Bold` is Noto Sans, Bold: the longest part before a hyphen that names an installed family) and a composite font by its CIDFont's name rather than its base name, which ends in the CMap's (`Name-Identity-H`, [ISO 32000-1 §9.7.6.1](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf)); a missing font is split only before a style part (`Helvetica-BoldOblique`), so it is reported as `MS PGothic`, not `MS`; scores unchanged (estimated) |
| 2026-10-31 | minor | DXF export (2000–2018): the LAYOUT objects wrote the paper image origin as a point at group codes 148 and 158, but PLOTSETTINGS defines 148 and 149 and no 158, so strict readers could reject the file (Autodesk Fusion reported "DXF File failed to insert", #3085). They now write 148/149, the shade plot mode, resolution and DPI (76–78, 2004 and later) and flag 1024 on the Model layout; the plot style ACDBPLACEHOLDER no longer carries a subclass marker. Test `r2000_and_later_files_have_the_structure_cad_apps_require` (required sections and table entries, handles and references, LAYOUT group codes); exports pass ezdxf's recover and audit; not tested in Fusion itself; scores unchanged |
| 2026-10-31 | minor | `.ai`/EPS editing data: an envelope whose contents are being edited is written the other way round from one that isn't: its contents, a group on `%_` lines, then its dictionary, which holds the mesh that shapes them. Those contents were read as art, so the undistorted art drew over the distorted art the page draws. The group on `%_` lines before an envelope's dictionary is now left out whatever it holds (text in it stays as hidden text, as the dictionary's own does), as the envelope's mesh already was (#2405). Test `import::ai::tests::an_envelope_s_contents_being_edited_are_not_read_as_art`; checked locally against a user's file: it opens from its layers, 0.0% from its page (it was 9.7%, over the 5% limit); scores unchanged |
+5 -2
View File
@@ -1,6 +1,6 @@
# Gaps
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (G5: Start Global Edit on groups, #4939; estimate unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (G13: artboard background colors in exports and print, #4937; estimate unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
## Feature submission evidence
@@ -435,7 +435,9 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall
- **Done:** TIFF choices and format settings in Export for Screens and Asset Export (30.2), using the existing encoder; UI-to-file regressions cover selection, scaling, options and persistence.
- **Done:** artboard background colors on the canvas (30.0): Properties › Artboards › Fill and Artboard Options set
each artboard's color, and the canvas draws its paper in it. Exports and print do not draw the color yet.
each artboard's color, and the canvas draws its paper in it. Exports (raster, PDF, SVG, EPS, `.ai` PDF pages,
Export for Screens with Include Artboard Fill) and print draw it behind the art (#4937). Left: the color isn't read
from or written to the reference app's own `.ai` data.
- **Done:** Object › Intertwine (#1763): Make, Edit and Release; the Intertwine tool highlights the overlap area under
the pointer, a click swaps which object is on top there, and a path (Shift: a rectangle) drawn around overlaps
@@ -677,6 +679,7 @@ trap, and the defaults (0.25 pt, 100%, 40%) are VectorCraft's own, the pages giv
| Date | Change | Summary |
|---|---|---|
| 2026-11-01 | minor | G13: Artboard background colors in output (#4937): raster exports (a transparent background too), PDF, SVG and EPS pages, `.ai` files' PDF pages, Export for Screens and print (and its preview) draw each artboard's color behind its art, inside the artboard and in the color's own model; Export for Screens has Include Artboard Fill beside Include Bleed (on by default, remembered; `includeArtboardFill`, also on `document.export` and `file.print`); editing data keeps the color as the artboard's, and Export for Screens' document rows keep it too; estimate unchanged. |
| 2026-11-01 | minor | G5: Select › Start Global Edit on a group takes the groups like it (the same objects at the same places, moved or scaled; Appearance, Size and the artboard options apply) and gives an edit made to an object inside one to the object at its place in each of the others, in proportion, as one undo step (#4939); rotated copies and Shift-click exclusion remain. Estimate unchanged. |
| 2026-10-31 | minor | G4: PDF import finds an installed font whose PDF name holds hyphens (`Noto-Sans-Bold` is Noto Sans, Bold: the longest part before a hyphen that names an installed family) and a composite font by its CIDFont's name rather than its base name, which ends in the CMap's (`Name-Identity-H`, [ISO 32000-1 §9.7.6.1](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf)); a missing font is split only before a style part (`Helvetica-BoldOblique`), so it is reported as `MS PGothic`, not `MS`. Estimate unchanged. |
| 2026-10-31 | minor | G13: Direct Selection snaps the end of an existing straight line, dragged by its end anchor, square to straight paths and grazing curves (Smart Guides › Geometric Guides: Snap to Perpendicular and Snap to Tangent), the case the public Snap to Perpendicular and Snap to Tangent pages add to drawing with the Line Segment and Pen tools (#2530). Scores and estimates unchanged. |
+8 -2
View File
@@ -3586,7 +3586,12 @@ An artboard's background color (Properties › Artboards › Fill, Artboard Opti
with one. The canvas draws that artboard's paper in
the color. With Simulate Colored Paper on, it multiplies the color with the paper color, each sRGB channel as
artboard × paper / 255; artboards without a color show the paper color. The transparency grid still covers it while
it shows. Copies, duplicates and the native format keep the color; exports and print do not draw it. `inspect_document`
it shows. Copies, duplicates and the native format keep the color. Exports draw it behind the art of each artboard
they write (raster formats, a transparent background too; PDF, SVG and EPS pages; `.ai` files' PDF pages; Export for
Screens; `file.print` and its preview), as a rectangle of the artboard filled with the color in its own model, on a
layer of its own below the others; files that carry the editing data (Preserve Editing, `.ai`, EPS, native rows of
Export for Screens) keep it as the artboard's color, not as art. `includeArtboardFill: false` on `document.export`,
`document.serialize`, `document.exportForScreens` (Include Artboard Fill) or `file.print` leaves it out. `inspect_document`
lists it as the artboard's `color` (`#rrggbb`), and `ui.colorPicker {artboard: index}` opens the Color Picker for it.
Artboard Options' Show Center Mark and Show Cross Hairs: `artboard.setProps {index, showCenterMark?: bool,
@@ -4028,7 +4033,8 @@ each file deflated, or stored when deflating does not make it smaller.
A row is `{format, scale?}` where `scale` is a factor (`2`, `"2x"`), a pixel width (`"100w"` or `width: 100`), a
height (`"100h"`) or a resolution (`"72ppi"`); raster rows name their files `@2x`, `@100w`… unless `suffix` says
otherwise. `fullDocument` writes one file per row instead (a PDF of every artboard, other formats the bounds of all
art), `includeBleed` grows each artboard by the document's bleed, `pdfs: "single"` (Export PDFs As › Single file)
art), `includeBleed` grows each artboard by the document's bleed, `includeArtboardFill: false` (Include Artboard
Fill unchecked) leaves the artboards' background colors out of the files, `pdfs: "single"` (Export PDFs As › Single file)
writes the artboards a PDF row exports as the pages of one PDF named after the document, `subfolders` puts each
row's files in a sub-folder (its size or format, or its own `folder`; `folders: "format"` sorts by format instead), `preset: "mobile"` or `"density"` (Android-style ldpi…xxxhdpi
sub-folders) replaces the rows, and `settings: {png: {…}, jpg: {…}, svg: {…}, pdf: {preset}}` gives every row of a
+4 -3
View File
@@ -1,6 +1,6 @@
# Parity with Adobe Illustrator
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (Start Global Edit on groups, #4939; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (artboard background colors in exports and print, #4937; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
## Feature submission evidence
@@ -528,7 +528,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- **A clip group in Properties:** with one clip group selected, the panel has Edit Clipping Path and Edit Contents at its top, under the Clip Group heading, which select the clipping path or the clipped art for the selection tools to transform alone, as Object › Clipping Mask does (<https://www.adobe.com/learn/illustrator/web/crop-using-masks>: with a clip group selected, the tutorial uses Edit Clipping Path at the top of the Properties panel to select the mask for editing, and uses Edit Contents to select the masked art). Edit Contents shows only when the group clips something. Not checked against the reference app: whether Properties keeps the buttons while the clipping path or the contents is selected; the Control bar has no clip-group buttons here. Evidence: `panels::properties::tests::one_clip_group_has_edit_clipping_path_and_edit_contents_at_the_top`.
- **Contextual controls in Properties:** the [Properties panel overview](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/properties-panel-overview.html) lists controls that appear for particular selections and tools; Properties has them, each running the existing command. Quick Actions has Blend Options for one selected blend, with the Blend tool or any other, as the [blending page](https://helpx.adobe.com/illustrator/using/blending-objects.html) describes (#4817) (the Blend Options dialog, OK one undo step) and Merge Live Paint when a Live Paint group is selected with more paths and nothing Live Paint refuses (`livePaint.merge`). Appearance has the stroke's Variable Width Profile list, the Control bar's, with the Width tool or when the stroke has a variable width (`stroke.set {profile}`), and beside fx the Delete icon: with one effect on the one selected object (its own or a fill's or stroke's) its name and an icon that removes it (`effect.remove`), with several an icon that opens the Appearance panel, as the overview describes. With Direct Selection and corners of one path selected, Corner and Corner Radius set the kind and radius of those corners (`object.setLiveShape`); a live rectangle's and polygon's own rows already do. Each edit is one undo step. The overview's Variable Font icon was already in Character. Not checked against the reference app: whether Merge needs two Live Paint groups (the overview speaks of several selected groups; `livePaint.merge` takes a group and paths), and where exactly each control sits in its section. Evidence: `panels::properties::tests::{blend_options_is_a_quick_action_for_one_blend_with_the_blend_tool, merge_live_paint_is_a_quick_action_for_a_live_paint_group_and_more, the_width_profile_shows_for_a_variable_width_or_the_width_tool, direct_selected_corners_show_their_kind_and_radius, one_effect_has_a_delete_icon_in_appearance}`.
- **Edit Artboards in Properties:** the active artboard's name is a field. Typing a new name and pressing Enter or clicking elsewhere renames that artboard in one undo step, as the Artboards panel's rename does (`artboard.setProps {index, name}`); pressing Escape or leaving the field empty keeps the old name. A click on the Artboard Options icon beside the field opens Artboard Options for the active artboard, as double-clicking the Artboard tool's button does (`tool.options {tool: "artboard"}`). Pointer and key tests in `panels/properties.rs` cover both. Public references: [Resize artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/add-edit-artboards/resize-artboards.html) and [Create and add new artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/add-edit-artboards/add-new-artboards.html).
- **Artboard background colors:** each artboard can have a background color (`artboard.setProps {index, color}`, `color: null` removes it). With the Artboard tool, Properties has a Fill row: its list sets None, White or Black, and its chip and its Color Picker… item open the Color Picker for the artboard. Artboard Options has a Background Color row with a list that sets None, White or Black, and a color button. The canvas draws the artboard's paper in the color, multiplied with the paper color while Simulate Colored Paper is on, and the transparency grid still covers it. The native format saves the color, and a document without colors saves as before. Exports and print do not draw the color. Engine, canvas, Properties, Color Picker and Artboard Options tests cover it. Public reference: [Apply colors to artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/add-background-color-to-artboards.html).
- **Artboard background colors:** each artboard can have a background color (`artboard.setProps {index, color}`, `color: null` removes it). With the Artboard tool, Properties has a Fill row: its list sets None, White or Black, and its chip and its Color Picker… item open the Color Picker for the artboard. Artboard Options has a Background Color row with a list that sets None, White or Black, and a color button. The canvas draws the artboard's paper in the color, multiplied with the paper color while Simulate Colored Paper is on, and the transparency grid still covers it. The native format saves the color, and a document without colors saves as before. Exports draw it behind each exported artboard's art, inside the artboard and in the color's own model: raster formats (on a transparent background too), PDF, SVG and EPS pages, `.ai` files' PDF pages, Export for Screens and print, whose preview shows it; files with editing data keep it as the artboard's color, not as art. Export for Screens has Include Artboard Fill beside Include Bleed (on by default and remembered; `includeArtboardFill: false`, which `document.export` and `file.print` take too, leaves it out) (#4937). Not done: `.ai` files the reference app reads keep it only in VectorCraft's editing data, and `.ai`/PDF import doesn't read a background color from the reference app's own data. Engine, canvas, Properties, Color Picker, Artboard Options, export (`fileio::tests_artboard_fill`) and Export for Screens dialog tests cover it. Public reference: [Apply colors to artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/add-background-color-to-artboards.html).
- **Resizing several artboards:** with several artboards selected with the Artboard tool, a new W or H in Properties, the Transform panel or the Control bar, a preset or an orientation in Properties or the Control bar, and a new Width or Height in Artboard Options resize every selected artboard in one undo step (`artboard.setProps {artboards}`); with Scale Artwork with Artboard on, Properties, the Transform panel and the Control bar scale each object once, with the artboard it was inside before the change. The Transform panel, the Control bar and Artboard Options resize each one about its own reference point, a preset sizes each one in its own orientation, and X and Y still move the active artboard alone. Pointer and key tests in `panels/properties.rs`, dialog tests in `dialogs/tests.rs` and an engine test in `tests/xform.rs` cover them. Public references: [Artboard Enhancements In Illustrator CC](https://blog.adobe.com/en/publish/2017/11/10/artboard-enhancements-in-illustrator-cc) (a height and a width entered once resize every selected artboard) and [Using multiple artboards](https://helpx.adobe.com/illustrator/using/using-multiple-artboards.html) (2019).
- **Artboard Escape routing:** Escape leaves Artboard editing for Selection; during a drag it first cancels the gesture. Headless egui key events cover idle Escape, active-drag cancellation and a subsequent Escape in `tests/distortkeys.rs`.
- **Escape in the distort tools:** Escape during a Puppet Warp pin drag or turn, a Perspective Grid tool handle or plane widget drag, a Perspective Selection move, resize or plane widget drag, or a Liquify stroke cancels it: the art goes back to how the press found it, no undo step is added, and the tool stays chosen. Puppet Warp also selects again the pins selected before the press, and Escape during a Liquify Alt-drag puts the brush size back. Regression tests: `escape_cancels_*` in `crates/tools/src/distort/puppet.rs`, `perspective.rs` and `liquify.rs`, and `escape_cancels_a_puppet_warp_perspective_or_liquify_drag` in `tests/distortkeys.rs`.
@@ -550,7 +550,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
- Batch… over data sets names each file after the document and the data set (`action.batch`). It writes `/`, `\`, `:`, `<`, `>`, `"`, `|`, `?`, `*` and control characters in the data set's name as `-`, and adds `-2`, `-3`… to a name an earlier data set used.
- Batch… offers File + Number, File + Data Set Name, and Data Set Name for a Data Sets source. The dialog defaults to File + Number; `action.batch` without `datasetFileNaming` keeps File + Data Set Name. An output already in a destination folder the batch may read and write gets the next free `-2`, `-3` suffix. [Batch options](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/batch-options.html).
- Export for Screens: the dialog's Example line shows the path of the file the export writes first, sub-folder included, named as the export names it, for the first chosen artboard, asset, layer or object (`document.exportForScreens`).
- Export for Screens is laid out as the reference app's dialog: on the left the Artboards and Assets tabs (thumbnails with their checkboxes, number and name, in a grid or a list), Clear Selection and Prefix; on the right Select (All, Range, Include Bleed, Full Document), Export to, Open Location after Export, Create Sub-folders by Scale or by Format (on by default), Export PDFs As (Single File writes the chosen artboards as the pages of one PDF named after the document, `pdfs: "single"`), and the format rows with the iOS and Android buttons, the format presets (☰) and Format Settings; under them, how many items are selected and how many files the export writes. File name patterns, the Layers and Selection sources and per-item folders stay available through `document.exportForScreens` (scripts and MCP). Tests: `dialogs::tests_screens` (layout, iOS/Android, Clear Selection, sub-folders, Single File) and `fileio::tests_screens::single_file_pdfs_hold_every_chosen_artboard`.
- Export for Screens is laid out as the reference app's dialog: on the left the Artboards and Assets tabs (thumbnails with their checkboxes, number and name, in a grid or a list), Clear Selection and Prefix; on the right Select (All, Range, Include Bleed, Include Artboard Fill, Full Document), Export to, Open Location after Export, Create Sub-folders by Scale or by Format (on by default), Export PDFs As (Single File writes the chosen artboards as the pages of one PDF named after the document, `pdfs: "single"`), and the format rows with the iOS and Android buttons, the format presets (☰) and Format Settings; under them, how many items are selected and how many files the export writes. File name patterns, the Layers and Selection sources and per-item folders stay available through `document.exportForScreens` (scripts and MCP). Tests: `dialogs::tests_screens` (layout, iOS/Android, Clear Selection, sub-folders, Single File) and `fileio::tests_screens::single_file_pdfs_hold_every_chosen_artboard`.
- Save for Web: Preview in Browser writes each preview's page and images to a folder of its own inside `VectorCraft Save for Web` in the system's temporary folder. A later preview, from the same app or another one running, does not overwrite an earlier one. Each preview first removes the preview folders there that are an hour old or older and, on Linux, macOS and FreeBSD, belong to the current user; it leaves other files and folders alone. The folders a preview makes there are open to their owner only on those systems. When `VectorCraft Save for Web` is a link or is not a folder, or on those systems belongs to another user or can be written by other users, the preview goes to a new folder of its own in the temporary folder, which a later preview removes once it is an hour old (`file.saveForWeb.browser`).
- Export for Screens, Save for Web (slice images, and files named after the document's title) and the Libraries panel name files with the characters Windows does not allow written as `-` and with a `-` after a Windows device name such as `CON`, on every platform (`document.exportForScreens`, `document.exportForWeb`, `library.create`).
- Variables (data merge): text, visibility and image variables bound to objects (an object may carry several), dataset rows applied in one undo step (`variable.*`, `dataset.*`) from a Variables panel and Window › Variables, with dialogs to define, bind and edit, Make Text/Visibility/Image Dynamic, Capture/Update Data Set, CSV import/export (`dataset.import` / `dataset.export`), and a row in italics when an edit leaves the art not matching it; an image dataset value is a file path and apply keeps the object's bounds; bindings let go of deleted art and locked art is left alone; every command, like the rest, is drivable over MCP and the CLI.
@@ -1419,6 +1419,7 @@ A VectorCraft addition, not a feature of the reference app, so no area score mov
| Date | Change | Summary |
|---|---|---|
| 2026-11-01 | minor | Artboard background colors in output (#4937): raster exports (a transparent background too), PDF, SVG and EPS pages, `.ai` files' PDF pages, Export for Screens and print (and its preview) draw each artboard's color behind its art, inside the artboard and in the color's own model; Export for Screens has Include Artboard Fill beside Include Bleed (on by default, remembered; `includeArtboardFill`, also on `document.export` and `file.print`); editing data keeps the color as the artboard's, and Export for Screens' document rows keep it too; evidence updated; scores unchanged. |
| 2026-11-01 | minor | Selection: Select › Start Global Edit on a group takes the groups like it (the same objects at the same places, moved or scaled; Appearance, Size and the artboard options apply) and gives an edit made to an object inside one to the object at its place in each of the others, in proportion, as one undo step (#4939); Global Edit of rotated copies and Shift-click exclusion remain. Score unchanged at 85% (estimated), hours unchanged. |
| 2026-11-01 | minor | Document Tools add-on (a VectorCraft addition): New Symbols from Layers (#4927); evidence added; scores unchanged. |
| 2026-11-01 | minor | Document Tools add-on (a VectorCraft addition): Save a Timestamped Copy (#4926); evidence added; scores unchanged. |
+2 -1
View File
@@ -1,6 +1,6 @@
# UI and interaction parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (Start Global Edit on groups, #4939) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-01 · **Change:** minor (Export for Screens: Include Artboard Fill, #4937) · **Target:** Adobe Illustrator 2026 (30.x)
## Feature submission evidence
@@ -385,6 +385,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
| Date | Change | Summary |
|---|---|---|
| 2026-11-01 | minor | Export for Screens has Include Artboard Fill beside Include Bleed (on by default, remembered; `includeArtboardFill`): the files draw each artboard's background color behind its art, as exports and print now do, and the Print dialog's preview shows it (#4937); scores and estimates unchanged |
| 2026-11-01 | minor | Select › Start Global Edit on a group takes the groups like it (the same objects at the same places, moved or scaled; Appearance, Size and the artboard options apply) and gives an edit made to an object inside one to the object at its place in each of the others, in proportion, as one undo step (#4939). Scores and estimates unchanged |
| 2026-11-01 | minor | Document Tools add-on (a VectorCraft addition): New Symbols from Layers and Convert Layers to Symbols (#4927); scores and estimates unchanged |
| 2026-11-01 | minor | Document Tools add-on (a VectorCraft addition): Save a Timestamped Copy (#4926); scores and estimates unchanged |