mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-31 13:17:19 -06:00
M3.77: [Registration] swatch on every plate, trim marks on every artboard, Japanese marks and live Crop Marks
[Registration] is a built-in swatch that prints 100% on every plate: Separations
Preview shows it on each process and spot plate and PDF export writes it as
/Separation /All. It can't be edited, moved, duplicated, merged or deleted;
swatch.list shows it after None and the Swatches panel applies it as a swatch.
Object > Create Trim Marks strokes the marks in Registration and marks every
artboard when nothing is selected ({style?, allArtboards?}). The preference
japaneseCropMarks (General) switches to Japanese marks: double lines at the
trim and bleed edges plus centre marks. Effect > Crop Marks (cropMarks) is a
live effect whose marks follow the object; rendering, SVG/PDF export and
Expand Appearance draw them. The mark geometry lives in doc::marks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4591c85845
commit
c1df6f0621
@@ -48,10 +48,10 @@ pub(crate) fn has_fx(n: &Node) -> bool {
|
||||
}
|
||||
|
||||
/// Does `n` carry visible object-level effects that only apply through its art (type, images,
|
||||
/// symbol instances, live objects), or is it a group or layer with an appearance of its own (see
|
||||
/// [`Renderer::draw_object_fx`])?
|
||||
/// symbol instances, live objects; Crop Marks on anything), or is it a group or layer with an
|
||||
/// appearance of its own (see [`Renderer::draw_object_fx`])?
|
||||
pub(crate) fn has_object_fx(n: &Node) -> bool {
|
||||
(effects::needs_outline(n) && visible(&n.appearance.effects)) || effects::has_container_appearance(n)
|
||||
(effects::needs_outline(n) && visible(&n.appearance.effects)) || effects::has_container_appearance(n) || effects::has_crop_marks(n)
|
||||
}
|
||||
|
||||
/// Is `n` a group or layer (whose evaluated art keeps its knockout setting)?
|
||||
@@ -136,6 +136,8 @@ pub(crate) fn visual_bounds(n: &Node) -> Option<Rect> {
|
||||
/// the subtree (a shadow can reach the view while its object is outside it).
|
||||
pub(crate) fn cull_bounds(n: &Node) -> Option<Rect> {
|
||||
match &n.kind {
|
||||
// The object and its crop marks.
|
||||
_ if effects::has_crop_marks(n) => effects::crop_marks_art(n).and_then(|art| cull_bounds(&art)),
|
||||
// The evaluated art (its fills, strokes and geometry effects) and the raster effects' reach.
|
||||
_ if effects::has_container_appearance(n) => {
|
||||
let art = effects::evaluate_container(n);
|
||||
@@ -378,7 +380,8 @@ impl Renderer {
|
||||
/// What [`Self::draw_object_fx`] draws inside the object's transparency group.
|
||||
fn draw_object_fx_art(&mut self, ctx: &mut RenderContext, f: &Frame, a: &Arc<Node>, cache: bool) {
|
||||
let art = self.fx_art(f.doc, a, cache);
|
||||
let rfx = effects::raster_effects(&a.appearance.effects);
|
||||
// With Crop Marks, the object inside the art paints its own raster effects.
|
||||
let rfx = if effects::has_crop_marks(a) { vec![] } else { effects::raster_effects(&a.appearance.effects) };
|
||||
match cull_bounds(&art) {
|
||||
Some(reach) if !f.opts.outline && !rfx.is_empty() => {
|
||||
// Feather and Inner Glow clip to the art's outline.
|
||||
@@ -413,7 +416,7 @@ impl Renderer {
|
||||
_ => None,
|
||||
};
|
||||
let container = is_container(a);
|
||||
let art = match effects::reshape(a, symbol.as_ref()) {
|
||||
let art = match effects::crop_marks_art(a).or_else(|| effects::reshape(a, symbol.as_ref())) {
|
||||
Some(r) => r,
|
||||
None if container => effects::evaluate_container(a).unwrap_or_else(|| (**a).clone()),
|
||||
None if matches!(a.kind, NodeKind::Text(_) | NodeKind::Image(_)) => (**a).clone(),
|
||||
|
||||
+32
-12
@@ -6,7 +6,8 @@
|
||||
//! into process inks (colour-managed), or a spot ink at the colour's tint when it's linked to a
|
||||
//! spot swatch (solid colours and gradient stops alike). With one plate visible the plate
|
||||
//! renders as greyscale ink coverage (black = 100%); with several, the visible inks are
|
||||
//! composited through the proof profile, spots multiplied on top.
|
||||
//! composited through the proof profile, spots multiplied on top. Colours linked to the
|
||||
//! Registration swatch print their tint on every plate, spot plates included.
|
||||
//! Placed raster images are not separated (limitation).
|
||||
//! * **Overprint Preview** (and Separations Preview, which implies it): fills and strokes that
|
||||
//! overprint ([`vectorcraft_doc::FillLayer::overprint`], characters' too) are drawn with
|
||||
@@ -21,6 +22,7 @@ use std::sync::{Arc, RwLock};
|
||||
|
||||
use vectorcraft_color::cms::{self, Cms, PROCESS_PLATES};
|
||||
pub use vectorcraft_color::cms::{Intent, ProofSetup, ProofTarget};
|
||||
use vectorcraft_color::swatch::REGISTRATION;
|
||||
use vectorcraft_color::{BlendMode, Color, Paint};
|
||||
use vectorcraft_doc::{AppearanceItem, Document, Node, NodeKind};
|
||||
|
||||
@@ -33,6 +35,19 @@ pub struct Inks {
|
||||
pub cmyk: [f32; 4],
|
||||
/// Spot ink (swatch name, tint 0..1).
|
||||
pub spot: Option<(String, f32)>,
|
||||
/// Registration: the tint (every process ink's) prints on every spot plate too.
|
||||
pub registration: bool,
|
||||
}
|
||||
|
||||
impl Inks {
|
||||
/// The tint this colour prints on spot plate `name`.
|
||||
pub fn spot_tint(&self, name: &str) -> f32 {
|
||||
match &self.spot {
|
||||
_ if self.registration => self.cmyk[0],
|
||||
Some((n, t)) if n == name => *t,
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A printing plate.
|
||||
@@ -77,14 +92,17 @@ fn spot_swatch<'a>(doc: &'a Document, name: &str) -> Option<&'a Color> {
|
||||
pub type Link<'a> = Option<(&'a str, f32)>;
|
||||
|
||||
/// Separate one colour into inks. A colour linked to a spot swatch prints on that plate only, at
|
||||
/// its tint.
|
||||
/// its tint; one linked to the Registration swatch prints its tint on every plate.
|
||||
pub fn inks(doc: &Document, c: &Cms, color: &Color, link: Link, intent: Intent) -> Inks {
|
||||
if let Some((REGISTRATION, tint)) = link {
|
||||
return Inks { cmyk: [tint.clamp(0.0, 1.0); 4], spot: None, registration: true };
|
||||
}
|
||||
if let Some((name, tint)) = link
|
||||
&& spot_swatch(doc, name).is_some()
|
||||
{
|
||||
return Inks { cmyk: [0.0; 4], spot: Some((name.to_string(), tint.clamp(0.0, 1.0))) };
|
||||
return Inks { cmyk: [0.0; 4], spot: Some((name.to_string(), tint.clamp(0.0, 1.0))), registration: false };
|
||||
}
|
||||
Inks { cmyk: c.to_cmyk(color, intent), spot: None }
|
||||
Inks { cmyk: c.to_cmyk(color, intent), spot: None, registration: false }
|
||||
}
|
||||
|
||||
fn map_paint(p: &mut Paint, f: &mut dyn FnMut(&Color, Link) -> Color) {
|
||||
@@ -169,7 +187,7 @@ fn plate_color(doc: &Document, c: &Cms, proof: &ProofSetup, visible: &[String],
|
||||
let name = &visible[0];
|
||||
let v = match PROCESS_PLATES.iter().position(|p| p == name) {
|
||||
Some(i) => ink.cmyk[i],
|
||||
None => ink.spot.as_ref().filter(|(n, _)| n == name).map_or(0.0, |s| s.1),
|
||||
None => ink.spot_tint(name),
|
||||
};
|
||||
[1.0 - v; 3]
|
||||
} else {
|
||||
@@ -180,13 +198,15 @@ fn plate_color(doc: &Document, c: &Cms, proof: &ProofSetup, visible: &[String],
|
||||
}
|
||||
}
|
||||
let mut rgb = c.proof_cmyk_to_srgb(cmyk, proof);
|
||||
if let Some((name, t)) = &ink.spot
|
||||
&& visible.contains(name)
|
||||
&& let Some(sc) = spot_swatch(doc, name)
|
||||
{
|
||||
let s = c.display_rgb(sc);
|
||||
for i in 0..3 {
|
||||
rgb[i] *= 1.0 - t * (1.0 - s[i]);
|
||||
for name in visible.iter().filter(|v| !PROCESS_PLATES.contains(&v.as_str())) {
|
||||
let t = ink.spot_tint(name);
|
||||
if t > 0.0
|
||||
&& let Some(sc) = spot_swatch(doc, name)
|
||||
{
|
||||
let s = c.display_rgb(sc);
|
||||
for i in 0..3 {
|
||||
rgb[i] *= 1.0 - t * (1.0 - s[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
rgb
|
||||
|
||||
Reference in New Issue
Block a user