Merge pull request #5770 from dotCooCoo/fix/ai-editing-guides

.ai save: guides in the editing copy end with the guide operator only
This commit is contained in:
BFlat
2026-11-05 15:38:20 +07:00
committed by GitHub
3 changed files with 28 additions and 7 deletions
+2 -2
View File
@@ -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
+18
View File
@@ -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);
+8 -5
View File
@@ -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" });
}
}