mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 14:41:52 +00:00
.ai import: hidden ruler guides open as ruler guides with their own state
A straight `(N) *` guide path that runs across the canvas or an artboard opens as a ruler guide, but a hidden one (`1 Xw`) stayed a guide path. It now opens as a ruler guide too, and every ruler guide made from such a path keeps the path's hide, lock and name, so its Layers row shows them. Before, a locked guide opened unlocked and a named one lost its name. Hidden or locked guide paths that are short, slanted, grouped or `(S) *` stay guide paths, and commented copies (`%_`) are still left out. Docs: docs/target-app-parity.md, docs/file-format-parity.md.
This commit is contained in:
@@ -8,7 +8,8 @@
|
||||
//! next layers: a clipping group in each layer, read by [`masks`].
|
||||
//! - Ruler guides: a straight `(N) *` guide path, directly on a layer, that runs across the whole
|
||||
//! canvas (16,383 points around the template box's centre) or from edge to edge of one artboard
|
||||
//! is a ruler guide on that layer ([`Document::guides`]); other guide paths stay paths.
|
||||
//! is a ruler guide on that layer ([`Document::guides`]), shown or hidden, with the path's name,
|
||||
//! lock and visibility; other guide paths stay paths.
|
||||
//! - Groups `u` … `U`, compound paths `*u` … `*U`, clipping groups `q` … `Q` (the path painted
|
||||
//! with `W` clips); a group's transparency and name follow its end. A wraparound group `*w` …
|
||||
//! `*W` (version 7 specification §15.4.2) is a group whose objects other than its type wrap
|
||||
|
||||
@@ -234,11 +234,10 @@ impl<'a> Reader<'a> {
|
||||
}
|
||||
let kind = NodeKind::Path { path, rule: gs.rule, live: None, clipping: clip, guide };
|
||||
let mut node = self.new_node(kind, &gs);
|
||||
// A ruler guide is written as a straight `(N) *` guide path on its layer.
|
||||
// A ruler guide is written as a straight `(N) *` guide path on its layer, shown or hidden.
|
||||
if guide
|
||||
&& op == "N"
|
||||
&& !hidden
|
||||
&& !gs.hidden
|
||||
&& !clip
|
||||
&& self.pattern.is_none()
|
||||
&& matches!(self.frames.last(), Some(Frame { kind: Kind::Layer(_), .. }))
|
||||
|
||||
@@ -1443,7 +1443,8 @@ fn ruler_guide(a: Point, b: Point, centre: Option<(f64, f64)>, boards: &[Rect])
|
||||
}
|
||||
|
||||
/// Makes the children of the layers and sublayers of `nodes` that `pick` makes a guide of into
|
||||
/// ruler guide nodes with the same ids, moved to the top of their layer in their order.
|
||||
/// ruler guide nodes with the same ids, names, locks and visibility, moved to the top of their layer
|
||||
/// in their order.
|
||||
fn take_ruler_guides(nodes: &mut [Arc<Node>], pick: &dyn Fn(NodeId) -> Option<Guide>, depth: usize) {
|
||||
if depth > MAX_DEPTH {
|
||||
return;
|
||||
@@ -1456,7 +1457,11 @@ fn take_ruler_guides(nodes: &mut [Arc<Node>], pick: &dyn Fn(NodeId) -> Option<Gu
|
||||
let mut guides = vec![];
|
||||
kids.retain(|k| match pick(k.id) {
|
||||
Some(g) => {
|
||||
guides.push(Arc::new(Node::new(k.id, NodeKind::RulerGuide { vertical: g.vertical, pos: g.pos, artboard: g.artboard })));
|
||||
let mut n = Node::new(k.id, NodeKind::RulerGuide { vertical: g.vertical, pos: g.pos, artboard: g.artboard });
|
||||
n.name = k.name.clone();
|
||||
n.locked = k.locked;
|
||||
n.visible = k.visible;
|
||||
guides.push(Arc::new(n));
|
||||
false
|
||||
}
|
||||
None => true,
|
||||
|
||||
@@ -8786,7 +8786,8 @@ fn other_guide_paths_stay_guide_paths() {
|
||||
guide_line(0.0, 30.0, 200.0, 30.0, "S"),
|
||||
guide_line(-7885.0, -7796.0, 8498.0, 8587.0, "N"),
|
||||
format!("u\n{}U\n", guide_line(-7885.0, 40.0, 8498.0, 40.0, "N")),
|
||||
format!("1 Xw\n{}0 Xw\n", guide_line(-7885.0, 40.0, 8498.0, 40.0, "N")),
|
||||
format!("1 Xw\n{}0 Xw\n", guide_line(20.0, 40.0, 80.0, 40.0, "N")),
|
||||
format!("1 A\n{}0 A\n", guide_line(-7885.0, 40.0, 8498.0, 40.0, "S")),
|
||||
"-7885 40 m\n306 40 L\n8498 40 L\n(N) *\n".to_string(),
|
||||
];
|
||||
for body in cases {
|
||||
@@ -8796,6 +8797,35 @@ fn other_guide_paths_stay_guide_paths() {
|
||||
}
|
||||
}
|
||||
|
||||
/// A hidden (`1 Xw`), locked (`1 A`) or named ruler guide path is a ruler guide that keeps its own
|
||||
/// visibility, lock and name, across the canvas or an artboard, on a shown layer.
|
||||
#[test]
|
||||
fn hidden_locked_and_named_guide_paths_are_ruler_guides() {
|
||||
let body = format!(
|
||||
"1 Xw\n{}0 Xw\n1 A\n{}0 A\n{}%_/ArtDictionary :\n%_(Mark) /String (AIArtName) ,\n%_;\n%_\n1 Xw\n{}0 Xw\n",
|
||||
guide_line(-7885.0, 40.0, 8498.0, 40.0, "N"),
|
||||
guide_line(25.0, 8587.0, 25.0, -7796.0, "N"),
|
||||
guide_line(-7885.0, 90.0, 8498.0, 90.0, "N"),
|
||||
guide_line(0.0, 30.0, 200.0, 30.0, "N"),
|
||||
);
|
||||
let doc = read(canvas_stream(&layer(&body)).as_bytes()).unwrap().doc;
|
||||
let top = &doc.layers[0];
|
||||
assert!(top.visible);
|
||||
let kids = top.children().unwrap();
|
||||
assert_eq!(kids.len(), 4);
|
||||
assert!(kids.iter().all(|c| c.is_ruler_guide()), "no guide path is left");
|
||||
let got: Vec<_> = doc.ruler_guides().into_iter().map(|g| (g.vertical, g.pos, g.artboard, g.layer, g.hidden, g.locked, g.name)).collect();
|
||||
let want = [
|
||||
(false, 60.0, None, Some(top.id), true, false, None),
|
||||
(true, 25.0, None, Some(top.id), false, true, None),
|
||||
(false, 10.0, None, Some(top.id), false, false, Some("Mark".to_string())),
|
||||
(false, 70.0, Some(1), Some(top.id), true, false, None),
|
||||
];
|
||||
assert_eq!(got, want);
|
||||
// The nodes themselves carry the state, so the Layers rows show it.
|
||||
assert_eq!(kids.iter().map(|k| (k.visible, k.locked)).collect::<Vec<_>>(), [(false, false), (true, true), (true, false), (false, false)]);
|
||||
}
|
||||
|
||||
/// The layer whose `Lb` has `ops` as its options (the first one in the file).
|
||||
fn layer_with(ops: &str) -> Node {
|
||||
let doc = read_ok(&format!("%AI5_BeginLayer\n{ops} Lb\n(L) Ln\nLB\n%AI5_EndLayer--\n")).doc;
|
||||
|
||||
@@ -190,7 +190,7 @@ private editing data. VectorCraft:
|
||||
editing data is read whole (#758), live effects that are not read or have settings that are not read, and
|
||||
brushes come in as their drawn look (#637; pattern fills are read since #1025, and symbols and their instances
|
||||
open live, as do the symbols of a symbol library without layers); legacy `.ai` without the prolog opens since #1027, its legacy type (`To` … `TO`) since #868; guides
|
||||
open as ruler guides and guide paths since #5057 (#779).
|
||||
open as ruler guides and guide paths since #5057 (#779), hidden, locked and named ruler guides with their state.
|
||||
Type under a Warp effect is compared with the art saved with it: type that draws as that art stays editable with
|
||||
the effect live, and type that draws differently, or that the file's limit of checks has no check left for, opens
|
||||
as that art, with one note that counts them. Type stays live without a comparison when the saved art paints
|
||||
|
||||
@@ -1677,6 +1677,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
In a `.ai` or EPS opened from its editing data, and in a legacy `.ai`, each anchor is smooth or a corner as the case of the segment operator ending on it says (`l`, `c`, `v`, `y` smooth; `L`, `C`, `V`, `Y` corner), and a closed path's first anchor takes the case of the segment back to it; points and handles stay where they are. Paths, compound paths, clipping paths, guide paths, area type frames and type-on-a-path paths all keep it (`crates/eps/src/import/ai/tests.rs`, `crates/eps/src/legacy_ai/tests.rs`).
|
||||
In `.ai` and EPS editing-data import, a clipping group, layer or opacity mask's art with several clipping paths (`W`) clips to their intersection: each clipping path after the first clips a clipping group nested inside the one before it, and each keeps its own stroke and transparency. A compound path of clipping paths (`*u` … `*U`) is still one clipping path, and clipping paths that would nest the art past the document's 32 levels stop the editing data from being read (`crates/eps/src/import/ai/tests.rs`).
|
||||
`.ai` and EPS editing-data import keeps template layers, and opens canvas and artboard ruler guides as ruler guides on their layers; guide objects stay editable guide paths (#779; `crates/eps/src/import/ai/tests.rs`, `crates/eps/src/import/tests_native.rs`).
|
||||
A hidden (`1 Xw`) ruler guide path in `.ai` and EPS editing data opens as a hidden ruler guide rather than a guide path, and every ruler guide keeps its path's hide, lock and name, so its Layers row shows them (`hidden_locked_and_named_guide_paths_are_ruler_guides`).
|
||||
`.ai` and EPS editing-data import keeps each path's Attributes panel note (`%AI3_Note:`, `%%Note:`, more lines in `%AI5_NoteMore:`, up to 1,024 characters) and Show Center (`1 Ap`); a note or Show Center applies to every path after it until the next one, and a note before a group or compound path is not theirs (`crates/eps/src/import/ai/tests.rs`, `crates/eps/src/import/tests_illustrator.rs`).
|
||||
`.ai` and EPS editing-data import reads object tags (`/identifier (string) XT`) on the object before them, whatever kind of object it is: `AdobeURL` becomes the object's URL, and every other tag is kept in the object's data in order, up to 64 per object; a tag longer than 64 KiB, and tags in a pattern's or brush's art, are ignored. A tag on art that shows no longer stops the editing data from being read, and brush art that holds tags is read (`crates/eps/src/import/ai/tests.rs`, `crates/eps/src/import/tests_illustrator.rs`).
|
||||
A `.ai` saved without PDF compatibility (its PDF part only a placeholder page) is placed and pasted from its editing data, as it opens: File › Place places the art of the layers that show on the first artboard (`file.place`'s `page` places another one), and Paste of it as PDF data (`clipboard.importPdf`) takes every object. `document.open` with `pages` naming every page of such a file opens it the same way (`crates/engine/src/cmd/fileio/tests_aiimport.rs`).
|
||||
|
||||
Reference in New Issue
Block a user