diff --git a/crates/eps/src/ai_editing.rs b/crates/eps/src/ai_editing.rs index 43b82f127..36fcf2b07 100644 --- a/crates/eps/src/ai_editing.rs +++ b/crates/eps/src/ai_editing.rs @@ -32,8 +32,8 @@ //! global process color `… (name) tint model Xk`; Registration `1 1 1 1 ([Registration]) tint //! Xs`. Upper case paints the stroke. //! - State before each object: locked `A`, hidden `Xw` (`1` or `0`). An object's name follows it as -//! an `/ArtDictionary` whose `AI10_ArtUID` is the name as an XML id. Guides are `(N) *`, the -//! path, `(N) *` (`(n) *` when closed). +//! an `/ArtDictionary` whose `AI10_ArtUID` is the name as an XML id. Guides are the path +//! followed by `(N) *` (`(n) *` when closed), without the `(N) *` that §6 also puts before it. //! - Transparency: `mode opacity isolate knockout shape Xy` (the blending mode's number, Knockout //! Group 0 off, 1 on, 2 neutral), written when it changes: in a path's paint after its colors, //! before a guide, and after a group's end (`U` or `Q`) followed by `0 0 Xd` and `6 () XW`; a diff --git a/crates/eps/src/ai_editing/tests.rs b/crates/eps/src/ai_editing/tests.rs index dcf928df5..8aa1fa627 100644 --- a/crates/eps/src/ai_editing/tests.rs +++ b/crates/eps/src/ai_editing/tests.rs @@ -978,6 +978,24 @@ fn artboard_display_marks_are_written() { assert!(ab.show_center_mark && !ab.show_cross_hairs && ab.show_video_safe_areas); } +/// A guide is its path followed by `(n) *` (closed) or `(N) *` (open), with no `(N) *` before +/// it, and reads back as the same guide. +#[test] +fn guides_are_their_path_then_the_guide_operator() { + let mut doc = Document::new(200.0, 100.0); + let layer = doc.layers[0].id; + let line = shapes::line(vectorcraft_geom::Point::new(10.0, 80.0), vectorcraft_geom::Point::new(150.0, 80.0)); + for path in [shapes::rectangle(Rect::new(10.0, 10.0, 50.0, 40.0)), line] { + let g = Node::new(doc.alloc_id(), NodeKind::Path { path, rule: FillRule::NonZero, live: None, clipping: false, guide: true }); + doc.insert(Some(layer), usize::MAX, g).unwrap(); + } + let p = program(&doc); + let (closed, open) = (p.matches("\n(n) *\n").count(), p.matches("\n(N) *\n").count()); + assert_eq!((closed, open), (1, 1), "{p}"); + assert!(p.contains("0 Xw\n10 -10 m\n") && p.contains("10 -10 L\n(n) *\n10 -80 m\n150 -80 L\n(N) *\nLB\n"), "{p}"); + assert_eq!(lines(&read_back(&doc)), lines(&doc)); +} + #[test] fn effects_on_a_fill_are_reported_left_out() { let mut doc = Document::new(200.0, 100.0); diff --git a/crates/eps/src/ai_ops.rs b/crates/eps/src/ai_ops.rs index cfa3928ec..a22c94de5 100644 --- a/crates/eps/src/ai_ops.rs +++ b/crates/eps/src/ai_ops.rs @@ -49,13 +49,16 @@ pub(crate) fn subpath(s: &mut String, xf: Affine, sp: &SubPath) { } } -/// A guide (§6): for each subpath, `(N) *`, its points, `(N) *`; with `closing`, a closed -/// subpath ends `(n) *` (the painting operator that closes it first). -pub(crate) fn guide(s: &mut String, xf: Affine, path: &PathData, closing: bool) { +/// A guide (§6): for each subpath, `(N) *`, its points, `(N) *`. In `editing` data (see +/// [`crate::ai_editing`]) a subpath is its points and `(N) *` alone, `(n) *` when closed (the +/// painting operator that closes it first). +pub(crate) fn guide(s: &mut String, xf: Affine, path: &PathData, editing: bool) { for sp in path.subpaths.iter().filter(|sp| !sp.anchors.is_empty()) { - s.push_str("(N) *\n"); + if !editing { + s.push_str("(N) *\n"); + } subpath(s, xf, sp); - s.push_str(if closing && sp.closed { "(n) *\n" } else { "(N) *\n" }); + s.push_str(if editing && sp.closed { "(n) *\n" } else { "(N) *\n" }); } }