mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 02:37:01 -06:00
PDF.W1: reconcile shared writer with current main
This commit is contained in:
@@ -107,7 +107,7 @@ impl Renderer {
|
||||
let pieces = match self.brushes.entries.get(&key, stamp) {
|
||||
Some(e) if Arc::ptr_eq(&e.brush, &brush) && e.stroke == *st && e.path == *bp => e.pieces.clone(),
|
||||
_ => {
|
||||
let pieces = Arc::new(vectorcraft_brush::stroke_pieces(&brush, bp, st));
|
||||
let pieces = Arc::new(vectorcraft_brush::stroke_pieces(f.doc, &brush, bp, st));
|
||||
let bytes = size_of::<Entry>() + lru::path_bytes(bp) + pieces.iter().map(lru::node_bytes).sum::<usize>();
|
||||
self.brushes.entries.insert(key, Entry { path: bp.clone(), stroke: st.clone(), brush, pieces: pieces.clone() }, bytes, stamp);
|
||||
pieces
|
||||
|
||||
+66
-16
@@ -366,6 +366,29 @@ fn linear_key(view: Affine) -> [u64; 4] {
|
||||
[c[0].to_bits(), c[1].to_bits(), c[2].to_bits(), c[3].to_bits()]
|
||||
}
|
||||
|
||||
/// The raster a shadow or glow blurred by `blur` device pixels is filtered in, for content
|
||||
/// reaching `reach` (device pixels) in a view of `screen` pixels: (left, top, width, height,
|
||||
/// scale). It covers what of the reach can show in the view, the blur's spread around it
|
||||
/// included, at `scale` raster pixels a device pixel: 1, or less to stay within
|
||||
/// [`PIXEL_BUDGET`]. At a high zoom the spread of a wide blur is tens of thousands of pixels a
|
||||
/// side, whatever the size of the view (#1706). `None`: nothing to draw.
|
||||
fn filter_region(reach: Rect, screen: (u16, u16), blur: f64) -> Option<(f64, f64, u16, u16, f64)> {
|
||||
// 3σ of the Gaussian (σ = blur / 2) in pixels, plus a pixel of antialiasing.
|
||||
let spread = blur.max(0.0) * 1.5 + 2.0;
|
||||
let screen = Rect::new(0.0, 0.0, f64::from(screen.0), f64::from(screen.1)).inflate(spread, spread);
|
||||
let r = reach.inflate(spread, spread).intersect(screen);
|
||||
let (x0, y0) = (r.x0.floor(), r.y0.floor());
|
||||
let (rw, rh) = (r.x1.ceil() - x0, r.y1.ceil() - y0);
|
||||
// (Also false for a reach or a blur that isn't a number.)
|
||||
if !(rw >= 1.0 && rh >= 1.0 && rw.is_finite() && rh.is_finite()) {
|
||||
return None;
|
||||
}
|
||||
let side = f64::from(u16::MAX);
|
||||
let k = (PIXEL_BUDGET / (rw * rh)).sqrt().min(side / rw).min(side / rh).min(1.0);
|
||||
let (w, h) = ((rw * k).floor().clamp(1.0, side), (rh * k).floor().clamp(1.0, side));
|
||||
Some((x0, y0, w as u16, h as u16, k))
|
||||
}
|
||||
|
||||
/// Draw `image` with its top-left at (`x`, `y`) device pixels, `1 / k` device pixels a pixel.
|
||||
fn draw_scaled(ctx: &mut RenderContext, image: std::sync::Arc<vello_cpu::Pixmap>, (x, y): (f64, f64), k: f64) {
|
||||
let (w, h) = (image.width() as f64, image.height() as f64);
|
||||
@@ -509,7 +532,8 @@ impl Drawn {
|
||||
/// objects in it ([`Reads::of`]): which parts of [`Inputs`] can change what its rasters show.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub(crate) struct Reads {
|
||||
/// Pattern paints: the pattern definitions.
|
||||
/// Pattern paints: the pattern definitions. Their art can hold anything, so content painted
|
||||
/// with a pattern reads everything else too.
|
||||
patterns: bool,
|
||||
/// Symbol instances and type with inline graphics: the symbols' art. Symbol art can hold
|
||||
/// anything, so content that shows a symbol reads everything else too.
|
||||
@@ -527,14 +551,25 @@ pub(crate) struct Reads {
|
||||
impl Reads {
|
||||
/// What the content of `n` reads.
|
||||
pub(crate) fn of(n: &Node) -> Self {
|
||||
Self::of_all(std::iter::once(n))
|
||||
}
|
||||
|
||||
/// What the content of `nodes` reads together. A pattern's art can hold anything, as a
|
||||
/// symbol's can, so content painted with a pattern reads everything else too (#1723).
|
||||
pub(crate) fn of_all<'a>(nodes: impl Iterator<Item = &'a Node>) -> Self {
|
||||
let mut reads = Self::default();
|
||||
reads.add(n);
|
||||
if reads.symbols {
|
||||
nodes.for_each(|n| reads.add(n));
|
||||
if reads.symbols || reads.patterns {
|
||||
return Self { patterns: true, symbols: true, images: true, brushes: true, text: true };
|
||||
}
|
||||
reads
|
||||
}
|
||||
|
||||
/// Does the content read anything outside its own nodes?
|
||||
pub(crate) fn any(self) -> bool {
|
||||
self != Self::default()
|
||||
}
|
||||
|
||||
fn add(&mut self, n: &Node) {
|
||||
let pattern = |p: &vectorcraft_color::Paint| matches!(p, vectorcraft_color::Paint::Pattern { .. });
|
||||
n.walk(&mut |c| {
|
||||
@@ -1365,18 +1400,9 @@ impl Renderer {
|
||||
if !f.mt {
|
||||
return self.inside_layer(|r| draw(r, ctx, f, composite));
|
||||
}
|
||||
// 3σ of the Gaussian (σ = blur / 2) in pixels, plus a pixel of antialiasing.
|
||||
let spread = blur.max(0.0) * 1.5 / f.px + 2.0;
|
||||
let screen = Rect::new(0.0, 0.0, ctx.width() as f64, ctx.height() as f64).inflate(spread, spread);
|
||||
let r = f.view.transform_rect_bbox(reach).inflate(spread, spread).intersect(screen);
|
||||
let (x0, y0) = (r.x0.floor(), r.y0.floor());
|
||||
let (w, h) = ((r.x1.ceil() - x0).min(u16::MAX as f64), (r.y1.ceil() - y0).min(u16::MAX as f64));
|
||||
if w < 1.0 || h < 1.0 {
|
||||
return;
|
||||
}
|
||||
let (w, h) = (w as u16, h as u16);
|
||||
let Some((x0, y0, w, h, k)) = filter_region(f.view.transform_rect_bbox(reach), (ctx.width(), ctx.height()), blur / f.px) else { return };
|
||||
let mut off = f.offscreen_context(w, h);
|
||||
let shifted = Frame { mt: false, view: Affine::translate((-x0, -y0)) * f.view, ..*f };
|
||||
let shifted = Frame { mt: false, view: Affine::scale(k) * Affine::translate((-x0, -y0)) * f.view, px: f.px / k, ..*f };
|
||||
self.inside_layer(|r| draw(r, &mut off, &shifted, None));
|
||||
off.flush();
|
||||
let mut pm = vello_cpu::Pixmap::new(w, h);
|
||||
@@ -1386,7 +1412,12 @@ impl Renderer {
|
||||
ctx.set_transform(Affine::IDENTITY);
|
||||
ctx.push_layer(None, Some(blend_mode(m)), Some(o), None, None);
|
||||
}
|
||||
crate::group::draw_pixmap(ctx, std::sync::Arc::new(pm), (x0, y0));
|
||||
if k < 1.0 {
|
||||
draw_scaled(ctx, std::sync::Arc::new(pm), (x0, y0), k);
|
||||
ctx.set_transform(Affine::IDENTITY);
|
||||
} else {
|
||||
crate::group::draw_pixmap(ctx, std::sync::Arc::new(pm), (x0, y0));
|
||||
}
|
||||
if layered {
|
||||
ctx.pop_layer();
|
||||
}
|
||||
@@ -1482,9 +1513,28 @@ mod tests {
|
||||
use vectorcraft_doc::{Appearance, Document, Effect, Node};
|
||||
use vectorcraft_geom::{Affine, Rect, shapes};
|
||||
|
||||
use super::Drawn;
|
||||
use super::{Drawn, PIXEL_BUDGET, filter_region};
|
||||
use crate::{RenderOptions, Rendered, Renderer};
|
||||
|
||||
/// #1706: the raster a shadow or glow is filtered in stays within the pixel budget however
|
||||
/// far the blur spreads at the zoom (it was up to 65535 pixels a side: 16 GB).
|
||||
#[test]
|
||||
fn a_wide_blur_at_a_high_zoom_is_filtered_within_the_pixel_budget() {
|
||||
// A 100 pt square at 3200 % and at 6400 %, blurred by 1000 pt, in a 1600 × 1000 view.
|
||||
for zoom in [32.0, 64.0] {
|
||||
let reach = Rect::new(0.0, 0.0, 100.0 * zoom, 100.0 * zoom);
|
||||
let (x0, y0, w, h, k) = filter_region(reach, (1600, 1000), 1000.0 * zoom).unwrap();
|
||||
assert!(f64::from(w) * f64::from(h) <= PIXEL_BUDGET, "{w} × {h}");
|
||||
assert!(k < 1.0 && x0 < 0.0 && y0 < 0.0);
|
||||
// It still covers the view and the blur's spread around it, at that scale.
|
||||
assert!(f64::from(w) / k >= 1600.0 + 1500.0 * zoom && f64::from(h) / k >= 1000.0 + 1500.0 * zoom);
|
||||
}
|
||||
// A small blur is filtered at full size, cropped to what can show.
|
||||
assert_eq!(filter_region(Rect::new(10.0, 20.0, 110.0, 70.0), (1600, 1000), 4.0), Some((2.0, 12.0, 116, 66, 1.0)));
|
||||
assert_eq!(filter_region(Rect::new(5000.0, 0.0, 5100.0, 100.0), (1600, 1000), 4.0), None, "off the view");
|
||||
assert_eq!(filter_region(Rect::new(0.0, 0.0, 100.0, 100.0), (1600, 1000), f64::NAN).map(|r| r.4), Some(1.0));
|
||||
}
|
||||
|
||||
fn doc_with(r: Rect, fill: Color, effects: Vec<(&str, serde_json::Value)>) -> Document {
|
||||
let mut d = Document::new(100.0, 100.0);
|
||||
let id = d.alloc_id();
|
||||
|
||||
@@ -83,7 +83,8 @@ pub struct RenderOptions {
|
||||
pub artboards: bool,
|
||||
/// Objects not to draw (e.g. the object being edited by a live drag preview).
|
||||
pub hidden: Vec<NodeId>,
|
||||
/// Draw template layers dimmed (50%).
|
||||
/// Draw the layers as their Layer Options show them on screen: Preview off in outline, Dim
|
||||
/// Images fading their images (a template layer's too), a template in Preview in Outline view.
|
||||
pub dim_templates: bool,
|
||||
/// Soft proof / separations preview (see [`proof`]).
|
||||
pub proof: Option<proof::ProofSetup>,
|
||||
@@ -489,6 +490,9 @@ pub struct Renderer {
|
||||
isolation: Option<(u64, usize, NodeId, Vec<NodeId>)>,
|
||||
/// Drawing inside the isolated container or a dimmed object: nothing further dims.
|
||||
isolation_settled: bool,
|
||||
/// The art the last frame drew with Overprint Preview or Separations Preview: the next frame
|
||||
/// draws it again for the objects that have not changed ([`proof::prepare`]).
|
||||
proof_art: Option<proof::Prepared>,
|
||||
}
|
||||
|
||||
/// How deep inline graphics nest (text in a symbol shown inline in text…) before they draw nothing.
|
||||
@@ -591,6 +595,7 @@ impl Renderer {
|
||||
raster: vello_cpu::RasterizerSettings::default(),
|
||||
isolation: None,
|
||||
isolation_settled: false,
|
||||
proof_art: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -615,7 +620,7 @@ impl Renderer {
|
||||
}
|
||||
// The document's own symbols: proofing rewrites the colors and blend modes of their art.
|
||||
self.check_symbols(doc);
|
||||
let prepared = proof::prepare(doc, opts);
|
||||
let prepared = proof::prepare(doc, opts, &mut self.proof_art);
|
||||
let doc: &Document = &prepared;
|
||||
let w = width.clamp(1, u16::MAX as u32) as u16;
|
||||
let h = height.clamp(1, u16::MAX as u32) as u16;
|
||||
@@ -1088,8 +1093,8 @@ impl Renderer {
|
||||
return;
|
||||
}
|
||||
// Layer Options on screen: a layer whose Preview is off draws in outline, and Dim Images
|
||||
// fades its images (exports and thumbnails, which leave templates out, ignore both).
|
||||
// (A template is dimmed as a whole already.)
|
||||
// fades its images, a template's too (exports and thumbnails, which leave templates out,
|
||||
// ignore both).
|
||||
if let NodeKind::Layer { preview, dim_images, template, .. } = &n.kind
|
||||
&& f.opts.dim_templates
|
||||
&& !f.opts.skip_templates
|
||||
@@ -1104,7 +1109,7 @@ impl Renderer {
|
||||
let opts = RenderOptions { outline: true, ..f.opts.clone() };
|
||||
return self.draw_node(ctx, &Frame { opts: &opts, ..*f }, n, force);
|
||||
}
|
||||
if let Some(p) = dim_images.filter(|_| !template) {
|
||||
if let Some(p) = *dim_images {
|
||||
let outer = self.dim_images;
|
||||
let dim = f32::from(p).clamp(0.0, 100.0) / 100.0;
|
||||
self.dim_images = Some(outer.map_or(dim, |o| o.min(dim)));
|
||||
@@ -1162,8 +1167,7 @@ impl Renderer {
|
||||
return self.draw_object_fx(ctx, f, &Arc::new(n.clone()), false);
|
||||
}
|
||||
let outline = f.opts.outline;
|
||||
let template_dim = matches!(n.kind, NodeKind::Layer { template: true, .. }) && f.opts.dim_templates;
|
||||
let opacity = if template_dim { self.opacity_of(n) * 0.5 } else { self.opacity_of(n) };
|
||||
let opacity = self.opacity_of(n);
|
||||
// Whether this container's children knock each other out.
|
||||
let knockout = !outline && n.knocks_out(self.knockout);
|
||||
let enclosing = std::mem::replace(&mut self.knockout, knockout);
|
||||
|
||||
@@ -6,15 +6,19 @@
|
||||
//! with the paint transform mapping pixels → pattern space → document. Tiles are cached per
|
||||
//! pattern name and raster scale (and ink plane, see [`crate::ink`]) within
|
||||
//! [`lru::PATTERN_BUDGET`]. A tile is used while the pattern's definition is the one it was
|
||||
//! rasterized from (art `Arc` identity, tile, tile type, overlap): the pattern's "revision".
|
||||
//! rasterized from (art `Arc` identity, tile, tile type, overlap): the pattern's "revision", and,
|
||||
//! when its art shows more than itself (a symbol, another pattern, an image, type, a brush), while
|
||||
//! what the document holds of those is what it held then ([`Deps`]), and while the color settings
|
||||
//! are the ones it was drawn through.
|
||||
//!
|
||||
//! Pattern editing mode (`Document::pattern_edit`) draws only the temporary tile layer, with
|
||||
//! dimmed copies of it at the neighbouring tile positions and the tile edge.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use vectorcraft_color::cms::Cms;
|
||||
use vectorcraft_doc::pattern::{Overlap, PatternDef, TileType};
|
||||
use vectorcraft_doc::{Document, Node};
|
||||
use vectorcraft_geom::{Affine, Rect, Shape};
|
||||
@@ -32,8 +36,54 @@ const MAX_TILE_PX: f64 = 2048.0;
|
||||
/// ([`raster_scale`]).
|
||||
type TileKey = (String, u64);
|
||||
|
||||
/// What a tile was drawn from besides its pattern's own art, for art that shows more than itself:
|
||||
/// the art of every pattern and symbol and the pixels of every image in the document, the
|
||||
/// document's other data (which holds its brushes), and the fonts. Editing a symbol leaves the art
|
||||
/// of a pattern that shows it as it was, so the art alone kept the tile from before (#1723). Held
|
||||
/// weakly: a weak reference keeps the allocation, so no other value gets one of these addresses
|
||||
/// while the tile is kept.
|
||||
struct Deps {
|
||||
art: Vec<Weak<Node>>,
|
||||
pixels: Vec<Weak<Vec<u8>>>,
|
||||
/// shortcut: any change to the document's other data draws the tile again, not only a brush
|
||||
/// edit; compare the brush library alone if that ever costs a frame.
|
||||
unknown: Weak<vectorcraft_brush::serde_json::Map<String, vectorcraft_brush::serde_json::Value>>,
|
||||
fonts: u64,
|
||||
}
|
||||
|
||||
impl Deps {
|
||||
fn art(doc: &Document) -> impl Iterator<Item = &Arc<Node>> {
|
||||
doc.patterns.iter().flat_map(|p| &p.art).chain(doc.symbols.iter().map(|s| &s.art))
|
||||
}
|
||||
|
||||
fn pixels(doc: &Document) -> impl Iterator<Item = &Arc<Vec<u8>>> {
|
||||
doc.images.values().flat_map(|b| std::iter::once(&b.bytes).chain(&b.proxy))
|
||||
}
|
||||
|
||||
fn of(doc: &Document) -> Self {
|
||||
Self {
|
||||
art: Self::art(doc).map(Arc::downgrade).collect(),
|
||||
pixels: Self::pixels(doc).map(Arc::downgrade).collect(),
|
||||
unknown: vectorcraft_doc::Shared::downgrade(&doc.unknown),
|
||||
fonts: vectorcraft_text::FontDb::global().generation(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Does `doc` hold what the tile was drawn from?
|
||||
fn current(&self, doc: &Document) -> bool {
|
||||
self.fonts == vectorcraft_text::FontDb::global().generation()
|
||||
&& std::ptr::eq(self.unknown.as_ptr(), &*doc.unknown)
|
||||
&& self.art.iter().map(Weak::as_ptr).eq(Self::art(doc).map(Arc::as_ptr))
|
||||
&& self.pixels.iter().map(Weak::as_ptr).eq(Self::pixels(doc).map(Arc::as_ptr))
|
||||
}
|
||||
}
|
||||
|
||||
struct TileEntry {
|
||||
art: Vec<Arc<Node>>,
|
||||
/// `None` for art that shows only itself.
|
||||
deps: Option<Deps>,
|
||||
/// The color settings the tile was drawn through, held weakly as [`Deps`] holds its values.
|
||||
cms: Weak<Cms>,
|
||||
params: (Rect, TileType, Overlap),
|
||||
sx: f64,
|
||||
sy: f64,
|
||||
@@ -159,11 +209,20 @@ pub(crate) fn set_pattern_paint(ctx: &mut RenderContext, name: &str, xf: Affine,
|
||||
let scale = raster_scale(s, (pw, ph));
|
||||
let params = (def.tile, def.tile_type, def.overlap);
|
||||
let key = (name.to_string(), scale.to_bits());
|
||||
let cms = vectorcraft_color::cms::active();
|
||||
let hit = CACHE.with(|c| {
|
||||
let mut c = c.borrow_mut();
|
||||
let tiles = &mut c.tiles[ink as usize];
|
||||
let t = tiles.tick();
|
||||
tiles.get(&key, t).filter(|e| e.params == params && same_art(&e.art, &def.art)).map(|e| (e.pixmap.clone(), e.sx, e.sy))
|
||||
tiles
|
||||
.get(&key, t)
|
||||
.filter(|e| {
|
||||
e.params == params
|
||||
&& same_art(&e.art, &def.art)
|
||||
&& std::ptr::eq(e.cms.as_ptr(), Arc::as_ptr(&cms))
|
||||
&& e.deps.as_ref().is_none_or(|d| d.current(doc))
|
||||
})
|
||||
.map(|e| (e.pixmap.clone(), e.sx, e.sy))
|
||||
});
|
||||
let (pm, sx, sy) = match hit {
|
||||
Some(h) => h,
|
||||
@@ -176,11 +235,12 @@ pub(crate) fn set_pattern_paint(ctx: &mut RenderContext, name: &str, xf: Affine,
|
||||
};
|
||||
let pm = Arc::new(pm);
|
||||
let (sx, sy) = (w as f64 / pw, h as f64 / ph);
|
||||
let deps = crate::fx::Reads::of_all(def.art.iter().map(|a| &**a)).any().then(|| Deps::of(doc));
|
||||
CACHE.with(|c| {
|
||||
let mut c = c.borrow_mut();
|
||||
let tiles = &mut c.tiles[ink as usize];
|
||||
let (t, bytes) = (tiles.tick(), size_of::<TileEntry>() + w as usize * h as usize * 4);
|
||||
tiles.insert(key, TileEntry { art: def.art.clone(), params, sx, sy, pixmap: pm.clone() }, bytes, t);
|
||||
tiles.insert(key, TileEntry { art: def.art.clone(), deps, cms: Arc::downgrade(&cms), params, sx, sy, pixmap: pm.clone() }, bytes, t);
|
||||
});
|
||||
(pm, sx, sy)
|
||||
}
|
||||
|
||||
+157
-21
@@ -18,14 +18,15 @@
|
||||
//! [`crate::RenderOptions`] with [`active_proof`] and [`overprint_preview_on`].
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::sync::RwLock;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock, Weak};
|
||||
|
||||
use vectorcraft_color::Color;
|
||||
use vectorcraft_color::cms::{self, Cms, PROCESS_PLATES};
|
||||
pub use vectorcraft_color::cms::{Intent, ProofSetup, ProofTarget};
|
||||
use vectorcraft_doc::Document;
|
||||
use vectorcraft_color::{Color, Swatch, SwatchGroup};
|
||||
pub use vectorcraft_doc::inks::{Inks, Link, Plate, inks, map_document_colors, map_node_colors, plates, spot_color};
|
||||
use vectorcraft_doc::overprint::multiply_overprints;
|
||||
use vectorcraft_doc::{Document, Node, NodeId, Shared};
|
||||
|
||||
use crate::RenderOptions;
|
||||
|
||||
@@ -63,29 +64,164 @@ fn plate_color(doc: &Document, c: &Cms, proof: &ProofSetup, visible: &[String],
|
||||
Color::Rgb { r, g, b }
|
||||
}
|
||||
|
||||
/// The document as it should be drawn for these options (overprints, separations).
|
||||
pub(crate) fn prepare<'a>(doc: &'a Document, opts: &RenderOptions) -> Cow<'a, Document> {
|
||||
let seps = opts.proof.as_ref().and_then(|p| p.separations.as_ref().map(|s| (p, s)));
|
||||
let overprint = overprints(opts) && (doc.layers.iter().any(|l| l.has_overprint()) || doc.symbols.iter().any(|s| s.art.has_overprint()));
|
||||
if !overprint && seps.is_none() {
|
||||
/// The art a renderer drew with Overprint Preview or Separations Preview, kept for its next frame
|
||||
/// ([`prepare`]): the document's layers and symbol art as they were, each beside the art drawn for
|
||||
/// it, and what that art was made with. The kept nodes keep their addresses: no other node gets
|
||||
/// one of them while they are kept.
|
||||
pub(crate) struct Prepared {
|
||||
inputs: Inputs,
|
||||
layers: (Vec<Arc<Node>>, Vec<Arc<Node>>),
|
||||
art: (Vec<Arc<Node>>, Vec<Arc<Node>>),
|
||||
}
|
||||
|
||||
/// What [`prepare`] reads besides the art.
|
||||
#[derive(PartialEq)]
|
||||
struct Inputs {
|
||||
discard_white: bool,
|
||||
plates: Option<Plates>,
|
||||
}
|
||||
|
||||
/// What the colors of Separations Preview's plates are made from ([`plate_color`]).
|
||||
struct Plates {
|
||||
/// The proof setup, with the visible plates.
|
||||
proof: ProofSetup,
|
||||
/// The document's swatches, swatch groups and Spot Colors option.
|
||||
swatches: Shared<Vec<Swatch>>,
|
||||
groups: Shared<Vec<SwatchGroup>>,
|
||||
spot_use_lab: bool,
|
||||
/// The color settings, held weakly: no other settings get their address while they are kept.
|
||||
cms: Weak<Cms>,
|
||||
/// [`cms::profiles_generation`].
|
||||
profiles: u64,
|
||||
}
|
||||
|
||||
impl PartialEq for Plates {
|
||||
fn eq(&self, o: &Self) -> bool {
|
||||
(&self.proof, &self.swatches, &self.groups, self.spot_use_lab, self.profiles)
|
||||
== (&o.proof, &o.swatches, &o.groups, o.spot_use_lab, o.profiles)
|
||||
&& Weak::ptr_eq(&self.cms, &o.cms)
|
||||
}
|
||||
}
|
||||
|
||||
/// What [`prepare`] does to the fills and strokes of each node: overprinting ones multiply
|
||||
/// ([`multiply_overprints`]), and with Separations Preview every color shows as the visible plates
|
||||
/// print it ([`plate_color`]).
|
||||
struct How<'a> {
|
||||
doc: &'a Document,
|
||||
discard_white: bool,
|
||||
/// Separations Preview: the color settings, the proof setup and the visible plates.
|
||||
plates: Option<(&'a Cms, &'a ProofSetup, &'a [String])>,
|
||||
}
|
||||
|
||||
impl How<'_> {
|
||||
/// Whether [`Self::apply`] can change `n`: with Separations Preview any node, else a node with
|
||||
/// an overprinting fill or stroke.
|
||||
fn changes(&self, n: &Node) -> bool {
|
||||
self.plates.is_some() || n.appearance.items.iter().any(|i| i.overprint())
|
||||
}
|
||||
|
||||
/// Change the fills, strokes and other colors of node `a`, which holds no members.
|
||||
fn apply(&self, a: &mut Arc<Node>) {
|
||||
multiply_overprints(a, self.discard_white);
|
||||
if let Some((c, proof, visible)) = self.plates {
|
||||
map_node_colors(Arc::make_mut(a), &mut |col, sw| plate_color(self.doc, c, proof, visible, col, sw));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The document as it should be drawn for these options (overprints, separations). `kept` holds
|
||||
/// the art of an earlier frame ([`Prepared`]): when that frame's inputs were the same, each layer,
|
||||
/// symbol art and member that is still the node its kept art was made from is drawn from that art,
|
||||
/// with its `Arc`. `kept` then holds this frame's art, or nothing when neither preview is on.
|
||||
pub(crate) fn prepare<'a>(doc: &'a Document, opts: &RenderOptions, kept: &mut Option<Prepared>) -> Cow<'a, Document> {
|
||||
if !overprints(opts) {
|
||||
*kept = None;
|
||||
return Cow::Borrowed(doc);
|
||||
}
|
||||
let mut d = doc.clone();
|
||||
if overprint {
|
||||
let discard_white = doc.setup.discard_white_overprint;
|
||||
for l in &mut d.layers {
|
||||
multiply_overprints(l, discard_white);
|
||||
}
|
||||
for s in &mut d.symbols {
|
||||
multiply_overprints(&mut s.art, discard_white);
|
||||
let seps = opts.proof.as_ref().and_then(|p| p.separations.as_ref().map(|s| (p, s)));
|
||||
let c = cms::active();
|
||||
let inputs = Inputs {
|
||||
discard_white: doc.setup.discard_white_overprint,
|
||||
plates: seps.map(|(proof, _)| Plates {
|
||||
proof: proof.clone(),
|
||||
swatches: doc.swatches.clone(),
|
||||
groups: doc.swatch_groups.clone(),
|
||||
spot_use_lab: doc.spot_use_lab,
|
||||
cms: Arc::downgrade(&c),
|
||||
profiles: cms::profiles_generation(),
|
||||
}),
|
||||
};
|
||||
let earlier = kept.take().filter(|k| k.inputs == inputs);
|
||||
let how = How { doc, discard_white: inputs.discard_white, plates: seps.map(|(proof, visible)| (&*c, proof, visible.as_slice())) };
|
||||
let none = (vec![], vec![]);
|
||||
let (layers, art) = earlier.as_ref().map_or((&none, &none), |k| (&k.layers, &k.art));
|
||||
let made_layers = prepare_list(&doc.layers, &layers.0, &layers.1, &how);
|
||||
let symbol_art: Vec<Arc<Node>> = doc.symbols.iter().map(|s| s.art.clone()).collect();
|
||||
let made_art = prepare_list(&symbol_art, &art.0, &art.1, &how);
|
||||
let same = |made: &[Arc<Node>], from: &[Arc<Node>]| made.iter().zip(from).all(|(a, b)| Arc::ptr_eq(a, b));
|
||||
let out = if same(&made_layers, &doc.layers) && same(&made_art, &symbol_art) {
|
||||
Cow::Borrowed(doc)
|
||||
} else {
|
||||
let mut d = doc.clone();
|
||||
d.layers.clone_from(&made_layers);
|
||||
for (s, a) in d.symbols.iter_mut().zip(&made_art) {
|
||||
s.art = a.clone();
|
||||
}
|
||||
Cow::Owned(d)
|
||||
};
|
||||
*kept = Some(Prepared { inputs, layers: (doc.layers.clone(), made_layers), art: (symbol_art, made_art) });
|
||||
out
|
||||
}
|
||||
|
||||
/// `nodes` as drawn with `how` ([`prepare_node`]), each against its earlier version in `was`: the
|
||||
/// node at its place when that node has its id, else the node with its id. `made` holds the art
|
||||
/// drawn for `was`, place by place.
|
||||
fn prepare_list(nodes: &[Arc<Node>], was: &[Arc<Node>], made: &[Arc<Node>], how: &How) -> Vec<Arc<Node>> {
|
||||
let mut places: Option<HashMap<NodeId, usize>> = None;
|
||||
nodes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, n)| {
|
||||
let at = match was.get(i) {
|
||||
Some(w) if w.id == n.id => Some(i),
|
||||
_ => places.get_or_insert_with(|| was.iter().enumerate().map(|(j, w)| (w.id, j)).collect()).get(&n.id).copied(),
|
||||
};
|
||||
prepare_node(n, at.and_then(|j| Some((was.get(j)?, made.get(j)?))), how)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// `n` as drawn with `how`: the art of `earlier` (a node and the art drawn for it) when that art
|
||||
/// was made from `n` itself; else a copy of `n` with its own fills and strokes changed
|
||||
/// ([`How::apply`]) and its members prepared against those of `earlier` ([`prepare_list`]), or `n`
|
||||
/// itself when neither changes.
|
||||
fn prepare_node(n: &Arc<Node>, earlier: Option<(&Arc<Node>, &Arc<Node>)>, how: &How) -> Arc<Node> {
|
||||
if let Some((was, made)) = earlier
|
||||
&& Arc::ptr_eq(was, n)
|
||||
{
|
||||
return made.clone();
|
||||
}
|
||||
if let Some((proof, visible)) = seps {
|
||||
let c = cms::active();
|
||||
let src = doc.clone();
|
||||
map_document_colors(&mut d, &mut |col, sw| plate_color(&src, &c, proof, visible, col, sw));
|
||||
let members = n.children().map(|ch| {
|
||||
let (was, made) = earlier.and_then(|(w, m)| Some((w.children()?.as_slice(), m.children()?.as_slice()))).unwrap_or_default();
|
||||
prepare_list(ch, was, made, how)
|
||||
});
|
||||
let same_members = match (&members, n.children()) {
|
||||
(Some(m), Some(ch)) => m.iter().zip(ch).all(|(a, b)| Arc::ptr_eq(a, b)),
|
||||
_ => true,
|
||||
};
|
||||
if same_members && !how.changes(n) {
|
||||
return n.clone();
|
||||
}
|
||||
Cow::Owned(d)
|
||||
let mut own = Node::clone(n);
|
||||
if let Some(ch) = own.children_mut() {
|
||||
ch.clear();
|
||||
}
|
||||
let mut a = Arc::new(own);
|
||||
how.apply(&mut a);
|
||||
if let (Some(m), Some(ch)) = (members, Arc::make_mut(&mut a).children_mut()) {
|
||||
*ch = m;
|
||||
}
|
||||
a
|
||||
}
|
||||
|
||||
/// Soft-proof the rendered (premultiplied RGBA8) pixels in place.
|
||||
|
||||
@@ -67,13 +67,37 @@ fn dim_images_fades_only_the_images() {
|
||||
assert_eq!(render(&d, true).pixel(65, 25), [255, 0, 0, 255], "exports are not dimmed");
|
||||
}
|
||||
|
||||
/// #1683: a template layer dims its images by its Dim Images percentage and nothing else: with
|
||||
/// the option off they show at full strength, and the layer's paths are never faded.
|
||||
#[test]
|
||||
fn a_template_layer_dims_its_images_by_dim_images() {
|
||||
let template = |dim: Option<u8>| {
|
||||
let d = doc(|k| {
|
||||
if let NodeKind::Layer { template, dim_images, .. } = k {
|
||||
(*template, *dim_images) = (true, dim);
|
||||
}
|
||||
});
|
||||
let img = render(&d, false);
|
||||
(img.pixel(65, 25), img.pixel(25, 25))
|
||||
};
|
||||
let red = [255, 0, 0, 255];
|
||||
assert_eq!(template(None), (red, red), "Dim Images off: as it is");
|
||||
let (image, path) = template(Some(50));
|
||||
assert!(image[0] == 255 && (120..=135).contains(&image[1]) && image[1] == image[2], "half way to white: {image:?}");
|
||||
assert_eq!(path, red, "vector art stays");
|
||||
let (image, _) = template(Some(20));
|
||||
assert!((198..=210).contains(&image[1]) && image[1] == image[2], "a fifth of its strength: {image:?}");
|
||||
assert_eq!(template(Some(100)), (red, red));
|
||||
}
|
||||
|
||||
/// In Outline view a template layer still draws in Preview, dimmed, so its picture can be traced;
|
||||
/// the layer above it draws in outline (#1486).
|
||||
#[test]
|
||||
fn a_template_layer_stays_in_preview_in_outline_view() {
|
||||
let mut d = doc(|k| {
|
||||
if let NodeKind::Layer { template, preview, .. } = k {
|
||||
*template = true;
|
||||
if let NodeKind::Layer { template, preview, dim_images, .. } = k {
|
||||
// As Layer Options makes a template: its images dimmed to 50 %.
|
||||
(*template, *dim_images) = (true, Some(50));
|
||||
// Preview off doesn't put a template in outline either.
|
||||
*preview = false;
|
||||
}
|
||||
|
||||
@@ -940,6 +940,63 @@ fn rasters_draw_pattern_edits() {
|
||||
});
|
||||
}
|
||||
|
||||
/// #1723: a pattern's tile is drawn again when what its art shows changes while the art itself
|
||||
/// doesn't: a symbol an instance in it shows, another pattern it is painted with, or a brush it is
|
||||
/// stroked with. So is a raster effect on an object painted with such a pattern.
|
||||
#[test]
|
||||
fn pattern_tiles_draw_edits_to_what_their_art_shows() {
|
||||
let _fonts = fonts_steady();
|
||||
use vectorcraft_doc::pattern::{PatternDef, pattern_paint};
|
||||
use vectorcraft_geom::shapes::rectangle;
|
||||
let tile = |name: &str, art: Node| {
|
||||
let mut def = PatternDef::new(name, vec![Arc::new(art)]);
|
||||
def.tile = Rect::new(0.0, 0.0, 20.0, 20.0);
|
||||
def
|
||||
};
|
||||
// Pattern `Stamps` shows symbol `S`; pattern `Outer` is a square painted with pattern `Checks`.
|
||||
let doc = |pattern: &str, effects: Vec<vectorcraft_doc::Effect>| {
|
||||
let mut d = Document::new(200.0, 100.0);
|
||||
let art = symbol_art(&mut d);
|
||||
d.symbols.push(vectorcraft_doc::Symbol { name: "S".into(), art: Arc::new(art) });
|
||||
let shown = Node::new(d.alloc_id(), NodeKind::SymbolInstance { symbol: "S".into(), xf: Affine::translate((10.0, 10.0)) });
|
||||
d.patterns.push(tile("Stamps", shown));
|
||||
d.patterns.push(checks(Color::rgb(1.0, 0.0, 0.0)));
|
||||
let square = filled(&mut d, rectangle(Rect::new(0.0, 0.0, 20.0, 20.0)), pattern_paint("Checks"), vec![]);
|
||||
d.patterns.push(tile("Outer", square));
|
||||
let painted = filled(&mut d, rectangle(Rect::new(10.0, 10.0, 190.0, 90.0)), pattern_paint(pattern), effects);
|
||||
with_art(d, vec![painted])
|
||||
};
|
||||
let new_symbol = |d: &mut Document| {
|
||||
let red = vectorcraft_doc::Appearance::basic(Paint::solid(Color::rgb(1.0, 0.0, 0.0)), Paint::None, 0.0);
|
||||
let bar = Node::path(d.alloc_id(), rectangle(Rect::new(-8.0, -2.0, 8.0, 2.0)), red);
|
||||
d.symbols[0].art = Arc::new(bar);
|
||||
};
|
||||
let new_checks = |d: &mut Document| {
|
||||
if let Some(def) = d.pattern_mut("Checks") {
|
||||
def.art = checks(Color::rgb(0.0, 0.0, 1.0)).art;
|
||||
}
|
||||
};
|
||||
let blur = || vec![effect("blur.gaussian", serde_json::json!({"radius": 2.0}))];
|
||||
edit_draws_as_new(doc("Stamps", vec![]), new_symbol);
|
||||
edit_draws_as_new(doc("Stamps", blur()), new_symbol);
|
||||
edit_draws_as_new(doc("Outer", vec![]), new_checks);
|
||||
edit_draws_as_new(doc("Outer", blur()), new_checks);
|
||||
// Pattern `Brushed` is a line stroked with a calligraphic brush; the brush gets wider.
|
||||
let mut d = Document::new(200.0, 100.0);
|
||||
let mut stroke = vectorcraft_doc::Appearance::basic(Paint::None, Paint::solid(Color::BLACK), 1.0);
|
||||
stroke.stroke_mut().unwrap().brush = Some("3 pt. Round".into());
|
||||
let brushed = Node::path(d.alloc_id(), vectorcraft_geom::shapes::line((2.0, 10.0).into(), (18.0, 10.0).into()), stroke);
|
||||
d.patterns.push(tile("Brushed", brushed));
|
||||
let painted = filled(&mut d, rectangle(Rect::new(10.0, 10.0, 190.0, 90.0)), pattern_paint("Brushed"), vec![]);
|
||||
edit_draws_as_new(with_art(d, vec![painted]), |d| {
|
||||
let mut lib = vectorcraft_brush::library(d);
|
||||
if let vectorcraft_brush::BrushKind::Calligraphic(c) = &mut lib[0].kind {
|
||||
c.size = 12.0;
|
||||
}
|
||||
vectorcraft_brush::store(d, &lib);
|
||||
});
|
||||
}
|
||||
|
||||
/// Raster effects are made again when a symbol shown inside another symbol changes (a group's drop
|
||||
/// shadow around an instance of the other symbol), when a brush definition changes (a brushed
|
||||
/// stroke's drop shadow), and when an image's pixels change under the same key, as when a linked
|
||||
@@ -1093,6 +1150,149 @@ fn rasters_follow_the_view_options() {
|
||||
draws_as_new(&mut d, &isolated, &on_white, unchanged);
|
||||
}
|
||||
|
||||
/// A document for the proof previews: a cyan square, a magenta circle over it whose black stroke
|
||||
/// overprints, a white square over the cyan one that overprints, a blurred rectangle in spot color
|
||||
/// `Spot`, and an instance of symbol `S`, a group holding a yellow square.
|
||||
fn proof_doc() -> Document {
|
||||
use vectorcraft_geom::shapes::{ellipse, rectangle};
|
||||
let mut d = Document::new(200.0, 100.0);
|
||||
let orange = Color::cmyk(0.0, 0.6, 1.0, 0.0);
|
||||
d.swatches.push(vectorcraft_color::Swatch { name: "Spot".into(), paint: Paint::solid(orange), global: true, spot: true });
|
||||
let cyan = filled(&mut d, rectangle(Rect::new(10.0, 10.0, 90.0, 90.0)), Paint::solid(Color::cmyk(1.0, 0.0, 0.0, 0.0)), vec![]);
|
||||
let black = Paint::solid(Color::cmyk(0.0, 0.0, 0.0, 1.0));
|
||||
let ring = vectorcraft_doc::Appearance::basic(Paint::solid(Color::cmyk(0.0, 1.0, 0.0, 0.0)), black, 4.0);
|
||||
let mut circle = Node::path(d.alloc_id(), ellipse(Rect::new(40.0, 20.0, 100.0, 80.0)), ring);
|
||||
circle.appearance.stroke_mut().unwrap().overprint = true;
|
||||
let mut white = filled(&mut d, rectangle(Rect::new(20.0, 30.0, 40.0, 50.0)), Paint::solid(Color::cmyk(0.0, 0.0, 0.0, 0.0)), vec![]);
|
||||
white.appearance.fill_mut().unwrap().overprint = true;
|
||||
let spot = Paint::Solid { color: orange, swatch: Some("Spot".into()), tint: 1.0 };
|
||||
let blur = effect("blur.gaussian", serde_json::json!({"radius": 1.0}));
|
||||
let spot = filled(&mut d, rectangle(Rect::new(110.0, 10.0, 150.0, 50.0)), spot, vec![blur]);
|
||||
let yellow = filled(&mut d, rectangle(Rect::new(-10.0, -10.0, 10.0, 10.0)), Paint::solid(Color::cmyk(0.0, 0.0, 1.0, 0.0)), vec![]);
|
||||
let art = Node::group(d.alloc_id(), vec![Arc::new(yellow)]);
|
||||
d.symbols.push(vectorcraft_doc::Symbol { name: "S".into(), art: Arc::new(art) });
|
||||
let instance = Node::new(d.alloc_id(), NodeKind::SymbolInstance { symbol: "S".into(), xf: Affine::translate((175.0, 75.0)) });
|
||||
with_art(d, vec![cyan, circle, white, spot, instance])
|
||||
}
|
||||
|
||||
/// Overprint Preview, and Separations Preview showing `plates`, which turns Overprint Preview on
|
||||
/// too (as the canvas draws them).
|
||||
fn proof_views(plates: &[&str]) -> (RenderOptions, RenderOptions) {
|
||||
let overprint = RenderOptions { overprint_preview: true, ..Default::default() };
|
||||
let proof = proof::ProofSetup { separations: Some(plates.iter().map(|p| p.to_string()).collect()), ..Default::default() };
|
||||
(overprint.clone(), RenderOptions { proof: Some(proof), ..overprint })
|
||||
}
|
||||
|
||||
/// [`proof_views`] with every plate of [`proof_doc`].
|
||||
fn every_plate() -> (RenderOptions, RenderOptions) {
|
||||
proof_views(&["Cyan", "Magenta", "Yellow", "Black", "Spot"])
|
||||
}
|
||||
|
||||
/// With Overprint Preview or Separations Preview on, a second frame of an unchanged document draws
|
||||
/// the art the first prepared for the preview: it makes no geometry entries. After an object
|
||||
/// moves, a frame makes them for the object and its layer only. A frame drawn with neither preview
|
||||
/// lets go of the art, and each frame drawn with a preview draws what a new renderer draws.
|
||||
#[test]
|
||||
fn proof_previews_keep_their_art_between_frames() {
|
||||
let _fonts = fonts_steady();
|
||||
let d = proof_doc();
|
||||
let (overprint, separations) = every_plate();
|
||||
let frame = |r: &mut Renderer, d: &Document, opts: &RenderOptions| r.render(d, 200, 100, Affine::IDENTITY, opts).pixels;
|
||||
let geometry = |r: &Renderer| r.geom.keys().copied().collect::<std::collections::HashSet<_>>();
|
||||
for opts in [&overprint, &separations] {
|
||||
let mut r = Renderer::new();
|
||||
let first = frame(&mut r, &d, opts);
|
||||
let kept = geometry(&r);
|
||||
assert!(frame(&mut r, &d, opts) == first);
|
||||
assert_eq!(geometry(&r), kept, "the second frame makes no geometry entries");
|
||||
// The cyan square moves 5 pt right.
|
||||
let mut moved = d.clone();
|
||||
let square = moved.layers[0].children().unwrap()[0].id;
|
||||
moved.node_mut(square).unwrap().transform(Affine::translate((5.0, 0.0)), false);
|
||||
assert!(frame(&mut r, &moved, opts) == frame(&mut Renderer::new(), &moved, opts));
|
||||
assert_eq!(geometry(&r).difference(&kept).count(), 2, "the square and its layer");
|
||||
frame(&mut r, &moved, &RenderOptions::default());
|
||||
assert!(r.proof_art.is_none(), "drawn without the previews, the renderer keeps no art for them");
|
||||
}
|
||||
}
|
||||
|
||||
/// With Overprint Preview or Separations Preview on, a renderer draws what a new renderer draws
|
||||
/// after a change to the art or to what the previews read: an object recolored, Discard White
|
||||
/// Overprint turned off, a symbol's art recolored (its instance shows the plates of the new art),
|
||||
/// the spot color redefined, a spot color in a color group redefined, the Spot Colors option set
|
||||
/// to CMYK values, a plate hidden, Simulate Paper Color, another proof profile, and one preview
|
||||
/// after the other.
|
||||
#[test]
|
||||
fn proof_previews_draw_changes_as_a_new_renderer_does() {
|
||||
let _fonts = fonts_steady();
|
||||
let (overprint, separations) = every_plate();
|
||||
let unchanged = |_: &mut Document| {};
|
||||
let recolor_square = |d: &mut Document| {
|
||||
let square = d.layers[0].children().unwrap()[0].id;
|
||||
d.node_mut(square).unwrap().appearance.set_fill(Paint::solid(Color::cmyk(0.0, 0.0, 1.0, 0.0)));
|
||||
};
|
||||
let recolor_symbol = |d: &mut Document| {
|
||||
let mut art = (*d.symbols[0].art).clone();
|
||||
if let Some(square) = art.children_mut().and_then(|c| c.first_mut()) {
|
||||
Arc::make_mut(square).appearance.set_fill(Paint::solid(Color::cmyk(1.0, 0.0, 0.0, 0.0)));
|
||||
}
|
||||
d.symbols[0].art = Arc::new(art);
|
||||
};
|
||||
for opts in [&overprint, &separations] {
|
||||
draws_as_new(&mut proof_doc(), opts, opts, recolor_square);
|
||||
draws_as_new(&mut proof_doc(), opts, opts, |d| d.setup.discard_white_overprint = false);
|
||||
}
|
||||
draws_as_new(&mut proof_doc(), &separations, &separations, recolor_symbol);
|
||||
draws_as_new(&mut proof_doc(), &separations, &separations, |d| {
|
||||
if let Some(s) = d.swatch_mut("Spot") {
|
||||
s.paint = Paint::solid(Color::cmyk(1.0, 0.0, 0.0, 0.0));
|
||||
}
|
||||
});
|
||||
// A square in spot color `Lab Spot`, defined in Lab in a color group, on its own plate.
|
||||
let lab_doc = || {
|
||||
let mut d = proof_doc();
|
||||
let lab = Color::lab(50.0, 60.0, 40.0);
|
||||
let swatch = vectorcraft_color::Swatch { name: "Lab Spot".into(), paint: Paint::solid(lab), global: true, spot: true };
|
||||
d.swatch_groups.push(vectorcraft_color::SwatchGroup { name: "Lab".into(), swatches: vec![swatch] });
|
||||
let fill = Paint::Solid { color: lab, swatch: Some("Lab Spot".into()), tint: 1.0 };
|
||||
let square = filled(&mut d, vectorcraft_geom::shapes::rectangle(Rect::new(115.0, 60.0, 155.0, 95.0)), fill, vec![]);
|
||||
with_art(d, vec![square])
|
||||
};
|
||||
let lab_plate = proof_views(&["Cyan", "Magenta", "Yellow", "Black", "Lab Spot"]).1;
|
||||
draws_as_new(&mut lab_doc(), &lab_plate, &lab_plate, |d| {
|
||||
if let Some(s) = d.swatch_mut("Lab Spot") {
|
||||
s.paint = Paint::solid(Color::lab(70.0, -40.0, 30.0));
|
||||
}
|
||||
});
|
||||
draws_as_new(&mut lab_doc(), &lab_plate, &lab_plate, |d| d.spot_use_lab = false);
|
||||
let hidden = proof_views(&["Magenta", "Yellow", "Black", "Spot"]).1;
|
||||
draws_as_new(&mut proof_doc(), &separations, &hidden, unchanged);
|
||||
let with = |f: fn(&mut proof::ProofSetup)| {
|
||||
let mut opts = separations.clone();
|
||||
if let Some(p) = &mut opts.proof {
|
||||
f(p);
|
||||
}
|
||||
opts
|
||||
};
|
||||
draws_as_new(&mut proof_doc(), &separations, &with(|p| p.simulate_paper = true), unchanged);
|
||||
let device = with(|p| p.target = proof::ProofTarget::Cmyk(vectorcraft_color::cms::DEVICE_CMYK.into()));
|
||||
draws_as_new(&mut proof_doc(), &separations, &device, unchanged);
|
||||
let cyan = proof_views(&["Cyan"]).1;
|
||||
draws_as_new(&mut proof_doc(), &overprint, &cyan, unchanged);
|
||||
draws_as_new(&mut proof_doc(), &cyan, &overprint, unchanged);
|
||||
// On the Yellow plate the instance is solid while its art is yellow, and blank once it is cyan.
|
||||
let yellow = proof_views(&["Yellow"]).1;
|
||||
let mut d = proof_doc();
|
||||
let mut r = Renderer::new();
|
||||
let instance = |r: &mut Renderer, d: &Document| {
|
||||
let pixels = r.render(d, 200, 100, Affine::IDENTITY, &yellow).pixels;
|
||||
Rendered { width: 200, height: 100, pixels }.pixel(175, 75)
|
||||
};
|
||||
assert_eq!(instance(&mut r, &d), [0, 0, 0, 255]);
|
||||
recolor_symbol(&mut d);
|
||||
assert_eq!(instance(&mut r, &d), [255, 255, 255, 255]);
|
||||
}
|
||||
|
||||
/// A thumbnail of an object under a filter that treats color channels apart (Color Halftone) is
|
||||
/// made again when the document's color mode changes: CMYK documents screen ink channels, RGB
|
||||
/// documents light ones.
|
||||
|
||||
Reference in New Issue
Block a user