mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 13:47:21 -06:00
Fix #4937: artboards can have a background fill, included in exports and print
This commit is contained in:
@@ -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>,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))?;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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"]));
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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
|
||||
|
||||
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
Reference in New Issue
Block a user