mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-02 22:50:27 -06:00
Merge branch 'print-color' into print-color-separations
This commit is contained in:
@@ -1006,7 +1006,12 @@ impl Exporter<'_> {
|
||||
for (si, gp) in designcraft_compose::path_glyphs(&cs, ft, &bp, pt) {
|
||||
let st = &cs.styles[si as usize];
|
||||
// A gradient spans the path's box.
|
||||
let fill = designcraft_doc::Fill { swatch: st.fill.clone(), tint: st.fill_tint, ..designcraft_doc::Fill::none() };
|
||||
let fill = designcraft_doc::Fill {
|
||||
swatch: st.fill.clone(),
|
||||
tint: st.fill_tint,
|
||||
overprint: st.overprint_fill,
|
||||
..designcraft_doc::Fill::none()
|
||||
};
|
||||
if let (Some(paint), Some(p)) = (self.fill_paint(&fill, bp.bounding_box()), to_path(&gp)) {
|
||||
let op = self.overprints(&st.fill, st.fill_tint, st.overprint_fill);
|
||||
let op = self.push_overprint(s, op, false);
|
||||
@@ -1103,7 +1108,8 @@ impl Exporter<'_> {
|
||||
let (swatch, tint) = (fill.swatch.as_str(), fill.tint);
|
||||
if let Some(g) = designcraft_color::swatch::resolve_gradient(&self.doc.swatches, swatch) {
|
||||
let kind = g.kind;
|
||||
let stops = match self.ink_gradient_stops(g, swatch) {
|
||||
let overprint = self.overprints(swatch, tint, fill.overprint);
|
||||
let stops = match self.ink_gradient_stops(g, swatch, overprint) {
|
||||
Some(stops) => stops,
|
||||
None => self.device_gradient_stops(g, swatch),
|
||||
};
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
//! Gradients written in their inks. A gradient whose stops name a spot swatch shades in that
|
||||
//! spot: one spot with paper or tints of itself is a Separation shading, a spot with process
|
||||
//! inks or a second spot a DeviceN shading (process colorants in CMYK order, then the spots).
|
||||
//! In print, a process gradient with a `[Paper]` stop shades in DeviceCMYK.
|
||||
//! In print, a process gradient with a `[Paper]` stop shades in DeviceCMYK. An overprinting
|
||||
//! gradient of fewer than four process inks is a DeviceN shading of just those inks: nonzero
|
||||
//! overprint mode doesn't apply to shadings (ISO 32000-2 §8.6.7), so a DeviceCMYK shading's
|
||||
//! zero components would knock out the plates beneath.
|
||||
|
||||
use craft_pdf::write::backend as krilla;
|
||||
use designcraft_color::swatch::{ColorType, SwatchValue, apply_tint};
|
||||
@@ -73,16 +76,18 @@ impl Exporter<'_> {
|
||||
}
|
||||
|
||||
/// The gradient's stops in its inks, or `None` to write it in device colors (no spot, or
|
||||
/// inks that can't share one shading space, which is warned about).
|
||||
pub(crate) fn ink_gradient_stops(&mut self, g: &Gradient, swatch: &str) -> Option<Vec<Stop>> {
|
||||
/// inks that can't share one shading space, which is warned about). `overprint`: the paint
|
||||
/// overprints.
|
||||
pub(crate) fn ink_gradient_stops(&mut self, g: &Gradient, swatch: &str, overprint: bool) -> Option<Vec<Stop>> {
|
||||
let overprint = overprint && !self.rgb_only;
|
||||
// Plain-color stops (all older documents) keep their device colors.
|
||||
if g.stops.iter().all(|s| s.swatch.is_none()) {
|
||||
if !overprint && g.stops.iter().all(|s| s.swatch.is_none()) {
|
||||
return None;
|
||||
}
|
||||
let inks: Vec<Option<Ink>> = g.stops.iter().map(|s| self.stop_ink(s)).collect();
|
||||
let has_spot = inks.iter().any(|i| matches!(i, Some(Ink::Spot { .. })));
|
||||
let on_paper = g.stops.iter().any(|s| s.swatch.as_deref() == Some(designcraft_color::swatch::PAPER));
|
||||
if !has_spot && !(on_paper && self.for_print() && !self.rgb_only) {
|
||||
if !has_spot && !overprint && !(on_paper && self.for_print() && !self.rgb_only) {
|
||||
return None;
|
||||
}
|
||||
let Some(inks) = inks.into_iter().collect::<Option<Vec<Ink>>>() else {
|
||||
@@ -107,6 +112,14 @@ impl Exporter<'_> {
|
||||
}
|
||||
let process: Vec<usize> = (0..4).filter(|j| process.get(*j).copied().unwrap_or(false)).collect();
|
||||
let space = match (spots.as_slice(), process.is_empty()) {
|
||||
// All four inks are painted either way.
|
||||
([], false) if overprint && process.len() < 4 => {
|
||||
let colorants: Vec<Colorant> = process.iter().filter_map(|j| Process::ALL.get(*j).copied().map(Colorant::Process)).collect();
|
||||
match DeviceNSpace::new(colorants) {
|
||||
Some(s) => Some(Space::DeviceN(s)),
|
||||
None => return self.unshaded(swatch, "its inks don't fit one DeviceN space", false),
|
||||
}
|
||||
}
|
||||
([], _) => None,
|
||||
([(name, alternate)], true) => {
|
||||
Some(Space::Separation(SeparationSpace::new(SeparationColorant::Custom(name.clone()), regular(alternate, self.rgb_only))))
|
||||
@@ -167,7 +180,8 @@ impl Exporter<'_> {
|
||||
let color: krilla::color::Color = match &space {
|
||||
Some(Space::Separation(sp)) => separation::Color::new(q(v.first().copied().unwrap_or(0.0)), sp.clone()).into(),
|
||||
Some(Space::DeviceN(sp)) => device_n::Color::new(v.iter().map(|t| q(*t)).collect(), sp.clone())?.into(),
|
||||
// Process inks only (a gradient to [Paper] in print).
|
||||
// Process inks only (a gradient to [Paper] in print, or an overprinting one of
|
||||
// all four inks).
|
||||
None => {
|
||||
let mut cmyk = [0.0f32; 4];
|
||||
for (t, j) in v.iter().zip(&process) {
|
||||
|
||||
@@ -32,10 +32,11 @@ use crate::export::{Exporter, solid_fill, tf, to_path};
|
||||
|
||||
impl Exporter<'_> {
|
||||
/// A text run's paint: solid, or a gradient spanning the frame's box (`area`, text space),
|
||||
/// for glyphs drawn under `origin` (text space ← run space).
|
||||
fn text_paint(&mut self, swatch: &str, tint: f32, origin: Affine, area: Rect) -> Option<Paint> {
|
||||
/// for glyphs drawn under `origin` (text space ← run space). `overprint`: the run's
|
||||
/// overprint attribute for this paint.
|
||||
fn text_paint(&mut self, swatch: &str, tint: f32, overprint: bool, origin: Affine, area: Rect) -> Option<Paint> {
|
||||
if designcraft_color::swatch::resolve_gradient(&self.doc.swatches, swatch).is_some() && origin.determinant().abs() > 1e-12 {
|
||||
let fill = designcraft_doc::Fill { swatch: swatch.into(), tint, ..designcraft_doc::Fill::none() };
|
||||
let fill = designcraft_doc::Fill { swatch: swatch.into(), tint, overprint, ..designcraft_doc::Fill::none() };
|
||||
return self.fill_paint_in(&fill, self.text_area.unwrap_or(area), origin.inverse());
|
||||
}
|
||||
self.swatch_color(swatch, tint).map(Into::into)
|
||||
@@ -474,9 +475,9 @@ impl Exporter<'_> {
|
||||
// Outside a frame (no text area), a gradient spans the run.
|
||||
let end = glyphs.last().map_or(g0.x, |g| g.x + g.adv);
|
||||
let run_area = Rect::new(g0.x, baseline - size, end.max(g0.x + 1.0), baseline + size * 0.25);
|
||||
let fill = self.text_paint(&st.fill, st.fill_tint, origin, run_area);
|
||||
let fill = self.text_paint(&st.fill, st.fill_tint, st.overprint_fill, origin, run_area);
|
||||
let stroke = if st.stroke != designcraft_color::swatch::NONE && st.stroke_weight > 0.0 {
|
||||
self.text_paint(&st.stroke, st.stroke_tint, origin, run_area)
|
||||
self.text_paint(&st.stroke, st.stroke_tint, st.overprint_stroke, origin, run_area)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -19,6 +19,11 @@ fn spot(d: &mut Document, name: &str, color: Color) {
|
||||
/// A document with spot inks Gold and Silver, a 40 % tint of Gold, and one box filled with a gradient on `stops`
|
||||
/// (swatch name or plain color, location).
|
||||
fn document(stops: &[(Result<&str, Color>, f32)], midpoint: f32) -> Document {
|
||||
document_with(stops, midpoint, false)
|
||||
}
|
||||
|
||||
/// [`document`], the box's fill overprinting when `overprint`.
|
||||
fn document_with(stops: &[(Result<&str, Color>, f32)], midpoint: f32, overprint: bool) -> Document {
|
||||
let mut d = Document::new(&NewDocument { width: 120.0, height: 120.0, facing_pages: false, ..Default::default() });
|
||||
spot(&mut d, "Gold", Color::cmyk(0.0, 0.3, 0.9, 0.1));
|
||||
spot(&mut d, "Silver", Color::cmyk(0.2, 0.15, 0.1, 0.3));
|
||||
@@ -35,7 +40,7 @@ fn document(stops: &[(Result<&str, Color>, f32)], midpoint: f32) -> Document {
|
||||
..Swatch::color("Fade", Color::BLACK)
|
||||
});
|
||||
let mut item = Item::new(ItemId(d.alloc()), d.default_layer(), Shape::Rectangle, shapes::rectangle(Rect::new(10.0, 10.0, 110.0, 110.0)));
|
||||
item.fill = Fill::swatch("Fade");
|
||||
item.fill = Fill { overprint, ..Fill::swatch("Fade") };
|
||||
d.insert_item(SpreadRef::Doc(0), item, None).unwrap();
|
||||
d
|
||||
}
|
||||
@@ -169,6 +174,28 @@ fn a_process_gradient_to_paper_prints_in_cmyk() {
|
||||
assert!(close(&shadings[0].start, &[1.0, 0.0, 0.0, 0.0]) && close(&shadings[0].end, &[0.0, 0.0, 0.0, 0.0]));
|
||||
}
|
||||
|
||||
/// Nonzero overprint mode doesn't apply to shadings, so an overprinting process gradient names
|
||||
/// only the inks it uses and leaves the other plates alone.
|
||||
#[test]
|
||||
fn an_overprinting_process_gradient_is_a_device_n_shading_of_its_inks() {
|
||||
for standard in [Standard::None, Standard::PdfX4] {
|
||||
let (shadings, warnings) = export(&document_with(&[(Ok(CYAN), 0.0), (Ok("[Paper]"), 1.0)], 0.5, true), standard);
|
||||
no_conversion(&warnings);
|
||||
let s = &shadings[0];
|
||||
assert_eq!((s.family.as_str(), s.colorants.as_slice()), ("DeviceN", &["Cyan".to_string()][..]), "{standard:?}");
|
||||
assert!(close(&s.start, &[1.0]) && close(&s.end, &[0.0]), "{:?} → {:?}", s.start, s.end);
|
||||
}
|
||||
// Plain-color stops too.
|
||||
let c_to_m = [(Err(Color::cmyk(1.0, 0.0, 0.0, 0.0)), 0.0), (Err(Color::cmyk(0.0, 1.0, 0.0, 0.0)), 1.0)];
|
||||
let (shadings, _) = export(&document_with(&c_to_m, 0.5, true), Standard::PdfX4);
|
||||
let s = &shadings[0];
|
||||
assert_eq!((s.family.as_str(), s.colorants.as_slice()), ("DeviceN", &["Cyan".to_string(), "Magenta".to_string()][..]));
|
||||
assert!(close(&s.start, &[1.0, 0.0]) && close(&s.end, &[0.0, 1.0]), "{:?} → {:?}", s.start, s.end);
|
||||
// Without overprint it stays in DeviceCMYK.
|
||||
let (shadings, _) = export(&document(&c_to_m, 0.5), Standard::PdfX4);
|
||||
assert_eq!(shadings[0].family, "DeviceCMYK");
|
||||
}
|
||||
|
||||
/// PDF/A forbids DeviceN's PostScript tint transform: the gradient goes to process color, with
|
||||
/// a warning, and the export still succeeds.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user