vectorcraft/crates/doc/src/inks.rs

158 lines
5.6 KiB
Rust

//! Printing inks: the plates of a document (four process inks and its spot inks), separating a
//! colour into them ([`inks`]), and visiting every colour of the art with its swatch link (the
//! Separations Preview recolours the art with these; print separates it into plates).
use std::sync::Arc;
use vectorcraft_color::cms::{Cms, Intent, PROCESS_PLATES};
use vectorcraft_color::swatch::REGISTRATION;
use vectorcraft_color::{Color, Paint};
use crate::{AppearanceItem, Document, Node, NodeKind};
/// Ink coverage of one paint colour.
#[derive(Clone, Debug, PartialEq)]
pub struct Inks {
/// Process inks C, M, Y, K (0..1).
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,
}
}
/// The ink this colour prints on `plate` (a process plate name or a spot swatch name).
pub fn on_plate(&self, plate: &str) -> f32 {
match PROCESS_PLATES.iter().position(|p| *p == plate) {
Some(i) => self.cmyk[i],
None => self.spot_tint(plate),
}
}
}
/// A printing plate.
#[derive(Clone, Debug, PartialEq)]
pub struct Plate {
pub name: String,
pub spot: bool,
/// Display colour of the ink (for the panel swatch).
pub rgb: [f32; 3],
}
/// The plates of `doc`: the four process plates plus one per spot swatch.
pub fn plates(doc: &Document) -> Vec<Plate> {
let c = vectorcraft_color::cms::active();
let mut v: Vec<Plate> = PROCESS_PLATES
.iter()
.enumerate()
.map(|(i, n)| {
let mut ink = [0.0; 4];
ink[i] = 1.0;
Plate { name: (*n).into(), spot: false, rgb: c.cmyk_to_srgb(ink, false) }
})
.collect();
for sw in doc.swatches_iter() {
if let (true, Paint::Solid { color, .. }) = (sw.spot, &sw.paint)
&& !v.iter().any(|p| p.name == sw.name)
{
v.push(Plate { name: sw.name.clone(), spot: true, rgb: c.display_rgb(&doc.linked_color(*color, true)) });
}
}
v
}
/// The ink colour of spot swatch `name` as it shows ([`Document::linked_color`]).
pub fn spot_color(doc: &Document, name: &str) -> Option<Color> {
let s = doc.swatch(name).filter(|s| s.spot)?;
s.paint.color().map(|c| doc.linked_color(c, true))
}
/// A colour's swatch link and tint (`(swatch name, tint 0..1)`), as the colour visitors pass it.
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; 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
&& doc.swatch(name).is_some_and(|s| s.spot && s.paint.color().is_some())
{
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, registration: false }
}
/// Visits one colour: the colour, its swatch link and its tint, all of which it may change.
pub type ColorVisitor<'a> = dyn FnMut(&mut Color, &mut Option<String>, &mut f32) + 'a;
fn visit_paint(p: &mut Paint, f: &mut ColorVisitor) {
match p {
Paint::Solid { color, swatch, tint } => f(color, swatch, tint),
Paint::Gradient(g) => {
for s in &mut g.gradient.stops {
f(&mut s.color, &mut s.swatch, &mut s.tint);
}
}
_ => {}
}
}
/// Visit every colour of a node tree (fills, strokes, gradient stops, text runs, mesh points, not
/// opacity masks) with its swatch link and tint. Mesh points have no link: changes to it are
/// dropped.
pub fn visit_node_colors(n: &mut Node, f: &mut ColorVisitor) {
for it in &mut n.appearance.items {
match it {
AppearanceItem::Fill(l) => visit_paint(&mut l.paint, f),
AppearanceItem::Stroke(l) => visit_paint(&mut l.paint, f),
}
}
match &mut n.kind {
NodeKind::Text(t) => {
for r in &mut t.runs {
visit_paint(&mut r.style.fill, f);
visit_paint(&mut r.style.stroke, f);
}
}
NodeKind::Mesh(m) => {
for p in &mut m.points {
f(&mut p.color, &mut None, &mut 1.0);
}
}
_ => {}
}
if let Some(ch) = n.children_mut() {
for c in ch.iter_mut() {
visit_node_colors(Arc::make_mut(c), f);
}
}
}
/// Apply `f` to every colour of a node tree (fills, strokes, gradient stops, text runs, mesh
/// points) given its swatch link and tint, keeping the links.
pub fn map_node_colors(n: &mut Node, f: &mut dyn FnMut(&Color, Link) -> Color) {
visit_node_colors(n, &mut |c, swatch, tint| *c = f(c, swatch.as_deref().map(|s| (s, *tint))));
}
/// Apply `f` to every colour in the document's art and symbol definitions.
pub fn map_document_colors(doc: &mut Document, f: &mut dyn FnMut(&Color, Link) -> Color) {
for l in &mut doc.layers {
map_node_colors(Arc::make_mut(l), f);
}
for s in &mut doc.symbols {
map_node_colors(Arc::make_mut(&mut s.art), f);
}
}