mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-01 22:26:49 -06:00
Print color: inks on craft-prepress's InkSet
The Ink Manager settings stay saved by swatch name; PDF export and the ink commands build a craft_prepress::InkSet from the document's spot swatches and those settings, and ink.options refuses alias loops. The hayro crates come from craft-libs at the craft-pdf revision, which craft-prepress's plate mode needs.
This commit is contained in:
Generated
+30
-6
@@ -816,6 +816,14 @@ dependencies = [
|
||||
"rustix 1.1.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-cms"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/aiongg/craft-libs?rev=0fe6156614de2e8780f34612fa6a577c745f35bc#0fe6156614de2e8780f34612fa6a577c745f35bc"
|
||||
dependencies = [
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-cms"
|
||||
version = "0.1.0"
|
||||
@@ -834,10 +842,24 @@ name = "craft-pdf"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/aiongg/craft-libs?rev=0fe6156614de2e8780f34612fa6a577c745f35bc#0fe6156614de2e8780f34612fa6a577c745f35bc"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"hayro",
|
||||
"hayro-interpret",
|
||||
"hayro-syntax",
|
||||
"krilla",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-prepress"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/aiongg/craft-libs?rev=0fe6156614de2e8780f34612fa6a577c745f35bc#0fe6156614de2e8780f34612fa6a577c745f35bc"
|
||||
dependencies = [
|
||||
"craft-cms 0.1.0 (git+https://github.com/aiongg/craft-libs?rev=0fe6156614de2e8780f34612fa6a577c745f35bc)",
|
||||
"craft-pdf",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-print"
|
||||
version = "0.1.0"
|
||||
@@ -969,7 +991,7 @@ dependencies = [
|
||||
name = "designcraft-color"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"craft-cms",
|
||||
"craft-cms 0.1.0 (git+https://github.com/storytold/craft-libs?rev=e9546e191dd62f2bc1d61eca45dbdc2f1b3c1d8e)",
|
||||
"kurbo",
|
||||
"proptest",
|
||||
"serde",
|
||||
@@ -1016,6 +1038,7 @@ dependencies = [
|
||||
"caseless",
|
||||
"craft-atomic",
|
||||
"craft-docpath",
|
||||
"craft-prepress",
|
||||
"craft-print",
|
||||
"designcraft-color",
|
||||
"designcraft-compose",
|
||||
@@ -1114,7 +1137,7 @@ dependencies = [
|
||||
name = "designcraft-images"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"craft-cms",
|
||||
"craft-cms 0.1.0 (git+https://github.com/storytold/craft-libs?rev=e9546e191dd62f2bc1d61eca45dbdc2f1b3c1d8e)",
|
||||
"designcraft-color",
|
||||
"designcraft-fonts",
|
||||
"image",
|
||||
@@ -1143,6 +1166,7 @@ name = "designcraft-pdf"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"craft-pdf",
|
||||
"craft-prepress",
|
||||
"designcraft-color",
|
||||
"designcraft-compose",
|
||||
"designcraft-doc",
|
||||
@@ -2105,7 +2129,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "hayro"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/storytold/pdfcraft?rev=b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9#b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9"
|
||||
source = "git+https://github.com/aiongg/craft-libs?rev=0fe6156614de2e8780f34612fa6a577c745f35bc#0fe6156614de2e8780f34612fa6a577c745f35bc"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"hayro-interpret",
|
||||
@@ -2135,7 +2159,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "hayro-interpret"
|
||||
version = "0.7.0"
|
||||
source = "git+https://github.com/storytold/pdfcraft?rev=b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9#b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9"
|
||||
source = "git+https://github.com/aiongg/craft-libs?rev=0fe6156614de2e8780f34612fa6a577c745f35bc#0fe6156614de2e8780f34612fa6a577c745f35bc"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"hayro-cmap",
|
||||
@@ -2153,7 +2177,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "hayro-jbig2"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/storytold/pdfcraft?rev=b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9#b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9"
|
||||
source = "git+https://github.com/aiongg/craft-libs?rev=0fe6156614de2e8780f34612fa6a577c745f35bc#0fe6156614de2e8780f34612fa6a577c745f35bc"
|
||||
dependencies = [
|
||||
"fearless_simd",
|
||||
"hayro-ccitt",
|
||||
@@ -2177,7 +2201,7 @@ checksum = "885c5ef0654933139a9b9546fc2c69e18d37f38aa2520f079092b1be18f1fcaa"
|
||||
[[package]]
|
||||
name = "hayro-syntax"
|
||||
version = "0.7.2"
|
||||
source = "git+https://github.com/storytold/pdfcraft?rev=b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9#b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9"
|
||||
source = "git+https://github.com/aiongg/craft-libs?rev=0fe6156614de2e8780f34612fa6a577c745f35bc#0fe6156614de2e8780f34612fa6a577c745f35bc"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"hayro-ccitt",
|
||||
|
||||
+6
-5
@@ -120,8 +120,9 @@ debug = 0
|
||||
[patch.crates-io]
|
||||
winit = { git = "https://github.com/skylartaylor/winit.git", rev = "3d51077fd1a966f84df9ef287839dc84fd5b4ca7" }
|
||||
krilla = { git = "https://github.com/aiongg/craft-libs", rev = "0fe6156614de2e8780f34612fa6a577c745f35bc", version = "=0.8.2" }
|
||||
# The hayro crates from pdfcraft's vendor/ directory, with the patches its README lists.
|
||||
hayro = { git = "https://github.com/storytold/pdfcraft", rev = "b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9" }
|
||||
hayro-interpret = { git = "https://github.com/storytold/pdfcraft", rev = "b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9" }
|
||||
hayro-syntax = { git = "https://github.com/storytold/pdfcraft", rev = "b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9" }
|
||||
hayro-jbig2 = { git = "https://github.com/storytold/pdfcraft", rev = "b5467d93a0ffaa5e5e6658bccdf8655a3ba689c9" }
|
||||
# The hayro crates from craft-libs' vendor/ directory (pdfcraft's, plus the plate mode and decode
|
||||
# limits craft-pdf and craft-prepress need); same revision as craft-pdf.
|
||||
hayro = { git = "https://github.com/aiongg/craft-libs", rev = "0fe6156614de2e8780f34612fa6a577c745f35bc" }
|
||||
hayro-interpret = { git = "https://github.com/aiongg/craft-libs", rev = "0fe6156614de2e8780f34612fa6a577c745f35bc" }
|
||||
hayro-syntax = { git = "https://github.com/aiongg/craft-libs", rev = "0fe6156614de2e8780f34612fa6a577c745f35bc" }
|
||||
hayro-jbig2 = { git = "https://github.com/aiongg/craft-libs", rev = "0fe6156614de2e8780f34612fa6a577c745f35bc" }
|
||||
|
||||
+2
-14
@@ -225,7 +225,8 @@ impl Document {
|
||||
}
|
||||
}
|
||||
|
||||
/// Ink Manager settings for output.
|
||||
/// Ink Manager settings for output, by swatch name. Output applies them through a
|
||||
/// `craft_prepress::InkSet` built from the document (`designcraft_pdf::inks`).
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", default)]
|
||||
pub struct InkManager {
|
||||
@@ -241,19 +242,6 @@ impl InkManager {
|
||||
fn is_default(&self) -> bool {
|
||||
*self == Self::default()
|
||||
}
|
||||
|
||||
/// The ink a spot swatch prints on (after aliases; at most a few hops) and whether it's
|
||||
/// converted to process.
|
||||
pub fn resolve<'a>(&'a self, ink: &'a str) -> (&'a str, bool) {
|
||||
let mut n = ink;
|
||||
for _ in 0..8 {
|
||||
match self.aliases.iter().find(|(a, _)| a == n) {
|
||||
Some((_, to)) if to != n => n = to,
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
(n, self.all_to_process || self.to_process.iter().any(|x| x == n))
|
||||
}
|
||||
}
|
||||
|
||||
/// XML tags and mappings.
|
||||
|
||||
@@ -23,6 +23,7 @@ designcraft-epub = { workspace = true }
|
||||
designcraft-pptx = { workspace = true }
|
||||
designcraft-textimport = { workspace = true }
|
||||
designcraft-pdf = { workspace = true }
|
||||
craft-prepress = { workspace = true }
|
||||
designcraft-tools = { workspace = true }
|
||||
designcraft-idml = { workspace = true }
|
||||
indd = { workspace = true }
|
||||
|
||||
@@ -498,13 +498,14 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
}),
|
||||
cmd!(query "ink.list", "Ink Manager", [], None, "{} → {allToProcess, inks: [{name, process: bool (printed as process), alias?}]} — the spot inks", has_doc, |s, _| {
|
||||
let d = &s.doc()?.doc;
|
||||
let inks = designcraft_pdf::inks::ink_set(d);
|
||||
let inks: Vec<Value> = d
|
||||
.swatches
|
||||
.iter()
|
||||
.filter(|w| matches!(w.value, designcraft_color::swatch::SwatchValue::Color { color_type: designcraft_color::swatch::ColorType::Spot, .. }))
|
||||
.map(|w| {
|
||||
let alias = d.inks.aliases.iter().find(|(a, _)| *a == w.name).map(|(_, t)| t.clone());
|
||||
json!({"name": w.name, "process": d.inks.resolve(&w.name).1, "alias": alias})
|
||||
json!({"name": w.name, "process": designcraft_pdf::inks::spot_output(d, &inks, &w.name).1, "alias": alias})
|
||||
})
|
||||
.collect();
|
||||
Ok(json!({"allToProcess": d.inks.all_to_process, "inks": inks}))
|
||||
@@ -550,6 +551,13 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
}
|
||||
match alias {
|
||||
Some(Value::String(to)) => {
|
||||
let mut set = designcraft_pdf::inks::ink_set(d);
|
||||
let ids = (set.lookup(name), set.lookup(&to));
|
||||
if let (Some(from), Some(target)) = ids
|
||||
&& !set.alias(from, target)
|
||||
{
|
||||
return Err(bad("ink.options", format!("aliasing `{name}` to `{to}` would make a loop")));
|
||||
}
|
||||
d.inks.aliases.retain(|(a, _)| a != name);
|
||||
d.inks.aliases.push((name.clone(), to));
|
||||
}
|
||||
@@ -2789,9 +2797,10 @@ mod ink_tests {
|
||||
let l = s.execute("ink.list", &json!({})).unwrap();
|
||||
assert_eq!(l["inks"][0]["alias"], "Pantone B");
|
||||
let back = designcraft_idml::import_idml(&designcraft_idml::export_idml(&s.doc().unwrap().doc)).unwrap();
|
||||
assert!(back.inks.resolve("Pantone A").1, "IDML ConvertToProcess");
|
||||
assert!(designcraft_pdf::inks::spot_output(&back, &designcraft_pdf::inks::ink_set(&back), "Pantone A").1, "IDML ConvertToProcess");
|
||||
assert!(back.inks.aliases.iter().any(|(a, b)| a == "Pantone A" && b == "Pantone B"), "IDML AliasInkName");
|
||||
assert!(s.execute("ink.options", &json!({"ink": "[Black]", "toProcess": true})).is_err());
|
||||
assert!(s.execute("ink.options", &json!({"ink": "Pantone B", "alias": "Pantone A"})).is_err(), "an alias loop is refused");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ designcraft-doc = { workspace = true }
|
||||
designcraft-fonts = { workspace = true }
|
||||
designcraft-compose = { workspace = true }
|
||||
craft-pdf = { workspace = true, features = ["write-pdf"] }
|
||||
craft-prepress = { workspace = true }
|
||||
krilla-svg = { workspace = true }
|
||||
designcraft-images = { workspace = true }
|
||||
image = { workspace = true }
|
||||
|
||||
@@ -159,6 +159,7 @@ fn export_pdf_with_report_inner(doc: &Document, cache: &Cache, opts: &PdfOptions
|
||||
|
||||
let mut ex = Exporter {
|
||||
drawing_items: Vec::new(),
|
||||
inks: crate::inks::ink_set(doc),
|
||||
doc,
|
||||
cache,
|
||||
opts,
|
||||
@@ -346,6 +347,7 @@ fn export_booklet_inner(doc: &Document, cache: &Cache, opts: &BookletOptions) ->
|
||||
let pdf = writer.document_mut();
|
||||
let mut ex = Exporter {
|
||||
drawing_items: Vec::new(),
|
||||
inks: crate::inks::ink_set(doc),
|
||||
doc,
|
||||
cache,
|
||||
opts: &pdf_opts,
|
||||
@@ -480,6 +482,8 @@ impl std::hash::Hasher for ConstantImageHasher {
|
||||
|
||||
pub(crate) struct Exporter<'a> {
|
||||
pub doc: &'a Document,
|
||||
/// The document's inks with its Ink Manager settings.
|
||||
inks: craft_prepress::InkSet,
|
||||
pub cache: &'a Cache,
|
||||
pub opts: &'a PdfOptions,
|
||||
/// Current sheet's visible area (spread space) for culling.
|
||||
@@ -644,7 +648,7 @@ impl Exporter<'_> {
|
||||
SwatchValue::Registration => return Some(registration(t, self.rgb_only)),
|
||||
SwatchValue::Color { color: _, color_type: ColorType::Spot } => {
|
||||
// Ink Manager: an alias prints on another ink; converted inks go to process.
|
||||
let (ink, process) = self.doc.inks.resolve(&sw.name);
|
||||
let (ink, process) = crate::inks::spot_output(self.doc, &self.inks, &sw.name);
|
||||
let target =
|
||||
self.doc.swatch(ink).filter(|w| matches!(w.value, SwatchValue::Color { color_type: ColorType::Spot, .. })).unwrap_or(sw);
|
||||
let SwatchValue::Color { color, .. } = &target.value else { break };
|
||||
@@ -1800,6 +1804,7 @@ mod image_content_cache_tests {
|
||||
fn exporter<'a>(doc: &'a Document, cache: &'a Cache, opts: &'a PdfOptions) -> Exporter<'a> {
|
||||
Exporter {
|
||||
drawing_items: Vec::new(),
|
||||
inks: crate::inks::ink_set(doc),
|
||||
doc,
|
||||
cache,
|
||||
opts,
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
//! The document's inks as a [`craft_prepress::InkSet`]: the four process inks, each spot swatch
|
||||
//! with its color as the alternate, and the Ink Manager settings (aliases, spots to process).
|
||||
//!
|
||||
//! The document saves the Ink Manager by swatch name ([`designcraft_doc::InkManager`]); the set
|
||||
//! is built from it whenever output needs it, so renaming or editing a swatch never leaves a
|
||||
//! stale copy.
|
||||
|
||||
use craft_prepress::{Alternate, Ink, InkId, InkSet, Spot};
|
||||
use designcraft_color::swatch::{ColorType, Swatch, SwatchValue};
|
||||
use designcraft_doc::Document;
|
||||
|
||||
fn is_spot(w: &Swatch) -> bool {
|
||||
matches!(w.value, SwatchValue::Color { color_type: ColorType::Spot, .. })
|
||||
}
|
||||
|
||||
/// The inks of `doc` with its Ink Manager settings applied. An alias whose target isn't a spot
|
||||
/// swatch, or that would make a loop, is left out: that spot prints on its own plate.
|
||||
pub fn ink_set(doc: &Document) -> InkSet {
|
||||
let mut set = InkSet::new();
|
||||
for w in doc.swatches.iter().filter(|w| is_spot(w)) {
|
||||
let SwatchValue::Color { color, .. } = w.value else { continue };
|
||||
let alternate = match color {
|
||||
designcraft_color::Color::Cmyk { c, m, y, k } => Alternate::Cmyk([c, m, y, k]),
|
||||
other => {
|
||||
let lab = other.to_lab();
|
||||
Alternate::Lab([lab.l, lab.a, lab.b])
|
||||
}
|
||||
};
|
||||
set.add(Ink::Spot(Spot { name: w.name.clone(), alternate, solidity: None }));
|
||||
}
|
||||
let spot_id = |set: &InkSet, name: &str| doc.swatch(name).filter(|w| is_spot(w)).and_then(|_| set.lookup(name));
|
||||
for name in &doc.inks.to_process {
|
||||
if let Some(id) = spot_id(&set, name) {
|
||||
set.set_to_process(id, true);
|
||||
}
|
||||
}
|
||||
for (from, to) in &doc.inks.aliases {
|
||||
if let (Some(from), Some(to)) = (spot_id(&set, from), spot_id(&set, to)) {
|
||||
set.alias(from, to);
|
||||
}
|
||||
}
|
||||
set.set_all_to_process(doc.inks.all_to_process);
|
||||
set
|
||||
}
|
||||
|
||||
/// How spot swatch `name` prints: the spot swatch whose plate (or, converted, whose color) it
|
||||
/// takes after aliases, and whether it prints as process color. A name the set doesn't hold
|
||||
/// prints as itself on its own plate.
|
||||
pub fn spot_output<'a>(doc: &'a Document, set: &InkSet, name: &'a str) -> (&'a str, bool) {
|
||||
let Some(id) = set.lookup(name) else { return (name, false) };
|
||||
let end = std::iter::successors(Some(id), |i| set.alias_of(*i)).take(set.len()).last().unwrap_or(id);
|
||||
let target = if end == id { name } else { swatch_of(doc, set, end).unwrap_or(name) };
|
||||
(target, set.plate_of(id).is_none())
|
||||
}
|
||||
|
||||
/// The first spot swatch that is ink `id`.
|
||||
fn swatch_of<'a>(doc: &'a Document, set: &InkSet, id: InkId) -> Option<&'a str> {
|
||||
doc.swatches.iter().filter(|w| is_spot(w)).find(|w| set.lookup(&w.name) == Some(id)).map(|w| w.name.as_str())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use designcraft_color::Color;
|
||||
use designcraft_doc::build::NewDocument;
|
||||
|
||||
fn spot(d: &mut Document, name: &str, color: Color) {
|
||||
d.swatches.push(Swatch {
|
||||
name: name.into(),
|
||||
value: SwatchValue::Color { color, color_type: ColorType::Spot },
|
||||
locked: false,
|
||||
named: true,
|
||||
hidden: false,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aliases_follow_chains_and_converted_inks_print_as_process() {
|
||||
let mut d = Document::new(&NewDocument::default());
|
||||
spot(&mut d, "Gold", Color::Cmyk { c: 0.0, m: 0.2, y: 0.8, k: 0.1 });
|
||||
spot(&mut d, "Bronze", Color::Rgb { r: 0.6, g: 0.4, b: 0.2 });
|
||||
spot(&mut d, "Copper", Color::Cmyk { c: 0.0, m: 0.5, y: 0.6, k: 0.2 });
|
||||
d.inks.aliases = vec![("Gold".into(), "Bronze".into()), ("Bronze".into(), "Copper".into()), ("Copper".into(), "Gold".into())];
|
||||
d.inks.to_process = vec!["Copper".into()];
|
||||
let set = ink_set(&d);
|
||||
assert_eq!(set.spots().map(|(_, s)| s.name.as_str()).collect::<Vec<_>>(), ["Gold", "Bronze", "Copper"]);
|
||||
// The loop-closing alias is refused, so Gold → Bronze → Copper, which is converted.
|
||||
assert_eq!(spot_output(&d, &set, "Gold"), ("Copper", true));
|
||||
assert_eq!(spot_output(&d, &set, "Copper"), ("Copper", true));
|
||||
d.inks.to_process.clear();
|
||||
let set = ink_set(&d);
|
||||
assert_eq!(spot_output(&d, &set, "Bronze"), ("Copper", false));
|
||||
assert_eq!(spot_output(&d, &set, "Unknown"), ("Unknown", false));
|
||||
d.inks.all_to_process = true;
|
||||
assert!(spot_output(&d, &ink_set(&d), "Gold").1);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod export;
|
||||
pub mod inks;
|
||||
mod links;
|
||||
mod marks;
|
||||
mod text;
|
||||
|
||||
Reference in New Issue
Block a user