mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 17:56:50 -06:00
Merge pull request #3702 from Taha-Mahmoodi/fix/3233-dimension-cmyk
Fix #3233: in a CMYK document dimension lines and labels are CMYK, black on the K plate only
This commit is contained in:
@@ -4,7 +4,7 @@ use serde_json::{Value, json};
|
||||
use std::sync::Arc;
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
use vectorcraft_doc::dimension::{ArrowStyle, Dimension, DimensionMode, DimensionOptions, DimensionSource, LineType, TextAlignment, TextPosition};
|
||||
use vectorcraft_doc::{Appearance, Arrowhead, CharStyle, Dash, Document, Node, NodeId, NodeKind, TextObject};
|
||||
use vectorcraft_doc::{Appearance, Arrowhead, CharStyle, ColorMode, Dash, Document, Node, NodeId, NodeKind, TextObject};
|
||||
use vectorcraft_geom::{Affine, BezPath, PathData, Point, Vec2};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
@@ -234,8 +234,19 @@ pub(crate) fn refresh(before: &Document, doc: &mut Document) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn paint(c: [u8; 3]) -> Paint {
|
||||
Paint::solid(Color::rgb8(c[0], c[1], c[2]))
|
||||
/// Option colour `c` as a paint in the document's colour model: in a CMYK document black is K
|
||||
/// only (a rich four-ink black would print a black dimension in every plate) and other colours
|
||||
/// convert through the active colour settings.
|
||||
fn paint(c: [u8; 3], mode: ColorMode) -> Paint {
|
||||
let rgb = Color::rgb8(c[0], c[1], c[2]);
|
||||
Paint::solid(match mode {
|
||||
ColorMode::Rgb => rgb,
|
||||
ColorMode::Cmyk if c == [0, 0, 0] => Color::cmyk(0.0, 0.0, 0.0, 1.0),
|
||||
ColorMode::Cmyk => {
|
||||
let cms = vectorcraft_color::cms::active();
|
||||
cms.convert(&rgb, vectorcraft_color::cms::Model::Cmyk, cms.settings().intent)
|
||||
}
|
||||
})
|
||||
}
|
||||
fn line(a: Point, b: Point) -> BezPath {
|
||||
let mut p = BezPath::new();
|
||||
@@ -244,10 +255,10 @@ fn line(a: Point, b: Point) -> BezPath {
|
||||
p
|
||||
}
|
||||
|
||||
fn appearance(o: &DimensionOptions, ext: bool, arrows: bool) -> Appearance {
|
||||
fn appearance(o: &DimensionOptions, mode: ColorMode, ext: bool, arrows: bool) -> Appearance {
|
||||
let (color, width, kind) =
|
||||
if ext { (o.extension_color, o.extension_weight, o.extension_type) } else { (o.line_color, o.line_weight, o.line_type) };
|
||||
let mut a = Appearance::basic(Paint::None, paint(color), width);
|
||||
let mut a = Appearance::basic(Paint::None, paint(color, mode), width);
|
||||
if let Some(st) = a.stroke_mut() {
|
||||
st.dash = match kind {
|
||||
LineType::Dashed => Some(Dash { pattern: vec![width * 5.0, width * 3.0], ..Default::default() }),
|
||||
@@ -285,6 +296,7 @@ fn along(o: &DimensionOptions) -> f64 {
|
||||
fn artwork(doc: &mut Document, d: &Dimension, old: &[NodeId]) -> Result<Vec<Arc<Node>>> {
|
||||
d.validate().map_err(error)?;
|
||||
let o = &d.options;
|
||||
let mode = doc.color_mode;
|
||||
let label = d.label().map_err(error)?;
|
||||
let mut paths: Vec<(BezPath, Appearance)> = Vec::new();
|
||||
let mut angle = 0.0;
|
||||
@@ -301,11 +313,11 @@ fn artwork(doc: &mut Document, d: &Dimension, old: &[NodeId]) -> Result<Vec<Arc<
|
||||
for i in 1..=64 {
|
||||
arc.line_to(on(i as f64 / 64.0));
|
||||
}
|
||||
paths.push((arc, appearance(o, false, true)));
|
||||
paths.push((arc, appearance(o, mode, false, true)));
|
||||
if !o.hide_extensions {
|
||||
for p in [d.a, d.b] {
|
||||
let dir = (p - d.vertex) / (p - d.vertex).hypot();
|
||||
paths.push((line(d.vertex + dir * o.extension_offset, d.vertex + dir * (r + o.extension_length)), appearance(o, true, false)));
|
||||
paths.push((line(d.vertex + dir * o.extension_offset, d.vertex + dir * (r + o.extension_length)), appearance(o, mode, true, false)));
|
||||
}
|
||||
}
|
||||
let dir = Vec2::from_angle(start + sweep * along(o));
|
||||
@@ -333,7 +345,7 @@ fn artwork(doc: &mut Document, d: &Dimension, old: &[NodeId]) -> Result<Vec<Arc<
|
||||
};
|
||||
if d.mode == DimensionMode::Linear && !o.hide_extensions {
|
||||
for (p, q) in [(d.a, a), (d.b, b)] {
|
||||
paths.push((line(p + outward * o.extension_offset, q + outward * o.extension_length), appearance(o, true, false)));
|
||||
paths.push((line(p + outward * o.extension_offset, q + outward * o.extension_length), appearance(o, mode, true, false)));
|
||||
}
|
||||
}
|
||||
angle = u.atan2();
|
||||
@@ -342,13 +354,13 @@ fn artwork(doc: &mut Document, d: &Dimension, old: &[NodeId]) -> Result<Vec<Arc<
|
||||
}
|
||||
let shift = o.text_offset + o.font_size * 0.5;
|
||||
text_at = a.lerp(b, along(o)) + outward * if away { shift } else { -shift };
|
||||
paths.push((line(a, b), appearance(o, false, true)));
|
||||
paths.push((line(a, b), appearance(o, mode, false, true)));
|
||||
}
|
||||
let style = CharStyle {
|
||||
font_family: o.font_family.clone(),
|
||||
font_style: o.font_style.clone(),
|
||||
size: o.font_size,
|
||||
fill: paint(o.text_color),
|
||||
fill: paint(o.text_color, mode),
|
||||
..Default::default()
|
||||
};
|
||||
let mut text = TextObject::point(Point::ZERO, &label, style);
|
||||
@@ -366,7 +378,7 @@ fn artwork(doc: &mut Document, d: &Dimension, old: &[NodeId]) -> Result<Vec<Arc<
|
||||
}
|
||||
let id = old.get(children.len()).copied().unwrap_or_else(|| doc.alloc_id());
|
||||
let mut node = Node::new(id, NodeKind::Text(Box::new(text)));
|
||||
node.appearance = Appearance::basic(paint(o.text_color), Paint::None, 0.0).into();
|
||||
node.appearance = Appearance::basic(paint(o.text_color, mode), Paint::None, 0.0).into();
|
||||
children.push(Arc::new(node));
|
||||
Ok(children)
|
||||
}
|
||||
|
||||
@@ -452,3 +452,57 @@ fn both_points_snap_onto_anchors_with_a_visible_indicator() {
|
||||
run(&mut s, "object.scale", json!({"sx":200,"sy":100}));
|
||||
assert_eq!(text(&s, id), "144.00 pt", "linked to both anchors");
|
||||
}
|
||||
|
||||
/// The page content streams of exported PDF `v`, inflated.
|
||||
fn pdf_content(v: &Value) -> String {
|
||||
let bytes = vectorcraft_format::base64_decode(v["dataBase64"].as_str().unwrap()).unwrap();
|
||||
let mut out = String::new();
|
||||
let mut rest = &bytes[..];
|
||||
while let Some(at) = rest.windows(7).position(|w| w == b"stream\n" || w == b"stream\r") {
|
||||
let body = &rest[at + 7..];
|
||||
let body = body.strip_prefix(b"\n").unwrap_or(body);
|
||||
let end = body.windows(9).position(|w| w == b"endstream").unwrap_or(body.len());
|
||||
let mut raw = Vec::new();
|
||||
if std::io::Read::read_to_end(&mut flate2::read::ZlibDecoder::new(&body[..end]), &mut raw).is_ok() {
|
||||
out.push_str(&String::from_utf8_lossy(&raw));
|
||||
}
|
||||
rest = &body[(end + 9).min(body.len())..];
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// #3233: in a CMYK document a dimension's black lines and label print on the black plate only
|
||||
/// (`0 0 0 1 K`), not as a four-ink rich black; other colours convert to CMYK; RGB documents keep
|
||||
/// RGB.
|
||||
#[test]
|
||||
fn dimension_colours_follow_the_document_colour_model() {
|
||||
let mut s = Session::new();
|
||||
run(&mut s, "file.new", json!({"width":600,"height":500,"colorMode":"cmyk"}));
|
||||
let line = edge(&mut s);
|
||||
run(&mut s, "object.hide", json!({"ids":[line]}));
|
||||
let id = dim(&mut s, line);
|
||||
let colours = |s: &Session, id: u64| {
|
||||
let mut v = Vec::new();
|
||||
s.doc().unwrap().doc.node(NodeId(id)).unwrap().walk(&mut |n| {
|
||||
v.extend([n.appearance.fill_paint(), n.appearance.stroke_paint()].iter().filter_map(|p| p.color()));
|
||||
});
|
||||
v
|
||||
};
|
||||
let black = colours(&s, id);
|
||||
assert!(!black.is_empty() && black.iter().all(|c| *c == vectorcraft_color::Color::cmyk(0.0, 0.0, 0.0, 1.0)), "{black:?}");
|
||||
let pdf = pdf_content(&run(&mut s, "document.exportPdf", json!({"preset":"PDF/X-4:2010"})));
|
||||
// PDF/X-4 paints in the output intent's CMYK space: `/c0 CS 0 0 0 1 SCN` strokes, `scn` fills.
|
||||
let operands = |op: &str| pdf.match_indices(op).map(|(i, _)| pdf[i.saturating_sub(7)..i].to_string()).collect::<Vec<_>>();
|
||||
let (strokes, fills) = (operands(" SCN"), operands(" scn"));
|
||||
assert!(!strokes.is_empty() && !fills.is_empty(), "{pdf}");
|
||||
assert!(strokes.iter().chain(&fills).all(|v| v == "0 0 0 1"), "K-only colours: {strokes:?} {fills:?}");
|
||||
run(&mut s, "dimension.update", json!({"ids":[id],"options":{"lineColor":[255,0,0]}}));
|
||||
let red = colours(&s, id);
|
||||
assert!(red.iter().any(|c| matches!(c, vectorcraft_color::Color::Cmyk { m, y, .. } if *m > 0.5 && *y > 0.5)), "{red:?}");
|
||||
assert!(red.iter().all(|c| matches!(c, vectorcraft_color::Color::Cmyk { .. })), "{red:?}");
|
||||
// RGB documents are unchanged.
|
||||
let mut s = session();
|
||||
let line = edge(&mut s);
|
||||
let id = dim(&mut s, line);
|
||||
assert!(colours(&s, id).iter().all(|c| *c == vectorcraft_color::Color::rgb8(0, 0, 0)));
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user