diff --git a/crates/engine/tests/import_fuzz.rs b/crates/engine/tests/import_fuzz.rs index 3d4498c07..bba6247c7 100644 --- a/crates/engine/tests/import_fuzz.rs +++ b/crates/engine/tests/import_fuzz.rs @@ -5175,6 +5175,9 @@ fn arb_ai_token() -> impl Strategy { "-9 /Real (CornerRadius) ,", "1 /Bool (Absolute) ,", "-0.5 /Real ,", + "0.5 /Real ,", + "1 /Real ,", + "1e308 /Real ,", "1.5 /Real ,", "-2 /Real ,", "; (BSLeftWidthsArray) ,", diff --git a/crates/eps/src/import/ai/read/document.rs b/crates/eps/src/import/ai/read/document.rs index c70486915..1b962064c 100644 --- a/crates/eps/src/import/ai/read/document.rs +++ b/crates/eps/src/import/ai/read/document.rs @@ -1308,7 +1308,7 @@ impl<'a> Reader<'a> { self.drawn_looks += self.pattern_looks.values().sum::(); if self.drawn_looks > 0 { warnings.push(format!( - "objects with several fills or strokes, effects or brushes came in as their drawn look ({}): their appearance isn't editable yet", + "objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({}): their appearance isn't editable yet", self.drawn_looks )); } diff --git a/crates/eps/src/import/ai/read/ops.rs b/crates/eps/src/import/ai/read/ops.rs index 7605f3453..f584f26c2 100644 --- a/crates/eps/src/import/ai/read/ops.rs +++ b/crates/eps/src/import/ai/read/ops.rs @@ -752,6 +752,24 @@ impl<'a> Reader<'a> { { return; } + // A variable-width stroke's look that draws nothing but copies read from `%_` lines: an + // empty group, which [`styled`] takes the stroke for. + let drawn; + let look = if !hidden + && slot_of(object.name.as_deref()).is_none() + && matches!(look.kind, NodeKind::Group { .. }) + && self.styles.get(&style).is_some_and(|st| st.uneven) + && !draws(look, &self.hidden_ids) + { + let mut copy = look.clone(); + if let Some(children) = copy.children_mut() { + children.clear(); + } + drawn = copy; + &drawn + } else { + look + }; // A style read here: the object, painted as it says. if let Some(mut appearance) = self.styles.get(&style).and_then(|st| { styled( @@ -1067,7 +1085,8 @@ fn styled(object: &Node, look: &Node, st: &crate::import::ai::style::ArtStyle, p // A symbol instance draws its symbol's art: it keeps its drawn look. if !st.plain || matches!(object.kind, NodeKind::SymbolInstance { .. }) - || (!slot && !st.simple && !stacked) + // A look with no outline in it to keep takes a variable-width stroke as the object's own. + || (!slot && !st.simple && !stacked && (!st.uneven || draws(look, &HashSet::new()))) || (object.is_container() && !slot && !matches!(object.kind, NodeKind::Compound { .. }) && !st.items.is_empty()) || (st.pathfinder && (slot || !st.items.is_empty() || object.is_compound_shape() || !matches!(object.kind, NodeKind::Group { clip: false, .. }))) @@ -1275,6 +1294,21 @@ fn strip(n: &mut Node, hidden: &HashSet) { } } +/// Whether `n` holds anything that draws, apart from the children read from `%_` lines (`hidden`): +/// a path or a compound path with a paint of its own, or a node other than a group or those. +fn draws(n: &Node, hidden: &HashSet) -> bool { + if hidden.contains(&n.id) { + return false; + } + let paints = n.appearance.items.iter().any(|i| i.visible() && !i.paint().is_none()); + match (&n.kind, n.children()) { + (NodeKind::Compound { .. }, Some(children)) => paints && children.iter().any(|c| c.visible && !hidden.contains(&c.id)), + (NodeKind::Group { .. }, Some(children)) => children.iter().any(|c| c.visible && draws(c, hidden)), + (NodeKind::Path { .. }, _) => paints, + _ => true, + } +} + /// Whether ASCII85 data in comment lines, up to its `~>`, follows the entry keyed `key` of a /// dictionary of type `ty`: the `/ASCII85Decode` of a `/Binary` dictionary or a text document, or a /// non-native art object's (`/ForeignObject`'s) `/Data`. diff --git a/crates/eps/src/import/ai/style.rs b/crates/eps/src/import/ai/style.rs index 9e2bfbaa8..f15744f30 100644 --- a/crates/eps/src/import/ai/style.rs +++ b/crates/eps/src/import/ai/style.rs @@ -174,13 +174,16 @@ const PATHFINDER_OPTIONS: [(&str, Kind, f64); 12] = [ pub(super) struct ArtStyle { /// The style reads as [`Self::items`] and [`Self::effects`]. pub plain: bool, - /// It is one fill and one stroke at most, in solid colours or the object's own gradient, with + /// It is one fill and one stroke at most, none of them [`Self::uneven`], in solid colours or the object's own gradient, with /// no transparency or effects of their own, and only Drop Shadow, Inner Glow, Outer Glow, /// Feather, Round Corners, Scribble, Convert to Shape, the Path effects (Offset Path, Outline Object, Outline Stroke), /// Bas Relief read from its settings block and some Distort & Transform effects ([`any_object_takes`]) on the object /// (and Pathfinder effects when it has no fills or strokes): what any object is read with. Type takes the other plain styles too (its drawn look /// can't be retyped), paths and compound paths [`Self::stacked`] ones; other objects keep their drawn look, which is the app's own rendering. pub simple: bool, + /// It would be [`Self::simple`] but for a Stroke Style filter's variable width written as one + /// centered array ([`centered_widths`]): an object whose drawn look is empty takes it with its profile. + pub uneven: bool, /// It is several fills and strokes in solid colours or gradients (strokes without a brush), /// each with its own visibility, opacity and blend mode but no effects of its own, under only /// the effects any object takes that don't move the outline: what a path or compound path is @@ -1601,7 +1604,7 @@ fn style(filters: &[Filter]) -> ArtStyle { st.plain = b.plain; let bas_relief = bas_relief_read(filters); let count = |stroke: bool| st.items.iter().filter(|i| matches!(i, StyleItem::Stroke { .. }) == stroke).count(); - st.simple = st.plain + let simple = st.plain && count(true) <= 1 && count(false) <= 1 && st.items.iter().all(|i| matches!(i, StyleItem::Fill(_) | StyleItem::Stroke { .. } | StyleItem::Gradient { .. })) @@ -1612,7 +1615,11 @@ fn style(filters: &[Filter]) -> ArtStyle { && (!st.moves_outline() || st.effects.len() == 1) // Pathfinder effects on a group with no fills or strokes of its own (`styled` checks the group). && (!st.pathfinder || st.items.is_empty()); - st.stacked = st.plain + // A stroke with one centered width array comes in as the drawn look, which holds the outline as the file draws it. + let centered = filters.iter().any(centered_widths); + (st.simple, st.uneven) = (simple && !centered, simple && centered); + st.stacked = !centered + && st.plain && !st.pathfinder && !st.moves_outline() && st.items.iter().all(|i| matches!(i, StyleItem::Fill(_) | StyleItem::Gradient { .. } | StyleItem::Stroke { brush: None, .. })) @@ -1621,6 +1628,17 @@ fn style(filters: &[Filter]) -> ArtStyle { st } +/// Whether `f` is a painted Stroke Style filter with a variable width written as one centered array, +/// `BSCenteredWidthsArray`. A Stroke Offset filter's profile and one written as each side's widths +/// (`BSLeftWidthsArray` and `BSRightWidthsArray`, as VectorCraft saves it) are not. +fn centered_widths(f: &Filter) -> bool { + let each_side = f + .arrays + .get("BSVariableWidthTValuesArray") + .is_some_and(|at| ["BSLeftWidthsArray", "BSRightWidthsArray"].iter().all(|key| f.arrays.get(*key).is_some_and(|w| w.len() == at.len()))); + f.name == STROKE && f.paints.0 && !each_side && width_profile(&f.arrays).is_some() +} + mod adapter; #[cfg(test)] mod tests; diff --git a/crates/eps/src/import/ai/style/tests.rs b/crates/eps/src/import/ai/style/tests.rs index 2222eb0cc..d39a87b5c 100644 --- a/crates/eps/src/import/ai/style/tests.rs +++ b/crates/eps/src/import/ai/style/tests.rs @@ -5,20 +5,59 @@ fn paint_op(f: &mut Filter, op: &str, vals: &[V]) { super::paint_op(f, op, vals, &mut Names::default()); } -#[test] -fn a_variable_width_stroke_keeps_its_profile() { - let data = b"/KnownStyle :\n(Tapered) /Name ,\n/ActiveStyle :\n/CompoundFilter :\n(Chain Style Filter) 0 0 /Filter ,\n1 /Visible ,\n\ +/// The style `Tapered`: a 30 pt stroke whose beautiful-strokes dictionary holds `arrays`. +fn width_style(arrays: &str) -> ArtStyle { + let data = format!( + "/KnownStyle :\n(Tapered) /Name ,\n/ActiveStyle :\n/CompoundFilter :\n(Chain Style Filter) 0 0 /Filter ,\n1 /Visible ,\n\ /CompoundFilter :\n(Stack Style Filter) 0 0 /Filter ,\n1 /Visible ,\n/BasicFilter :\n(Stroke Style Filter) 0 0 /Filter ,\n1 /Visible ,\n\ 2 /FillOrStroke ,\n/Dictionary : /NotRecorded ,\n/StrokeStyle : 0 R\n0 0 0 0 0 0 1 XA\n0 J 0 j 30 w 10 M []0 d\n0 XR\n1 0 Xd\n/Def ;\n (StrokeStyle) ,\n\ - /Dictionary : /NotRecorded ,\n2 /Int (BSWidthSource) ,\n/Array :\n0 /Real ,\n1 /Real ,\n; (BSVariableWidthTValuesArray) ,\n\ - /Array :\n1 /Real ,\n0.1 /Real ,\n; (BSCenteredWidthsArray) ,\n; (kAIBeautifulStrokesParamsDictKey) ,\n; /Dict ;\n /Part ,\n\ - /BasicFilter :\n(Conduit Filter) 0 0 /Filter ,\n1 /Visible ,\n;\n /Part ,\n;\n"; - let styles = art_styles(data); - let style = &styles["Tapered"]; - assert!(style.plain); - let [StyleItem::Stroke { width, profile, .. }] = style.items.as_slice() else { panic!("{:?}", style.items) }; - assert_eq!(*width, 30.0); - assert_eq!(profile.as_ref().map(|p| p.points.clone()), Some(vec![(0.0, 1.0, 1.0), (1.0, 0.1, 0.1)])); + /Dictionary : /NotRecorded ,\n2 /Int (BSWidthSource) ,\n{arrays}; (kAIBeautifulStrokesParamsDictKey) ,\n; /Dict ;\n /Part ,\n\ + /BasicFilter :\n(Conduit Filter) 0 0 /Filter ,\n1 /Visible ,\n;\n /Part ,\n;\n" + ); + art_styles(data.as_bytes()).remove("Tapered").unwrap_or_default() +} + +/// A beautiful-strokes array `key` of `values`. +fn bs_array(key: &str, values: &[&str]) -> String { + let items: String = values.iter().map(|v| format!("{v} /Real ,\n")).collect(); + format!("/Array :\n{items}; ({key}) ,\n") +} + +#[test] +fn a_centered_width_array_reads_as_uneven_with_its_profile() { + // GUARDS: the style counted as `simple`, and any object read with it dropped its drawn look. + let t = |v: &[&str]| bs_array("BSVariableWidthTValuesArray", v); + let w = |v: &[&str]| bs_array("BSCenteredWidthsArray", v); + for (arrays, points) in [ + (t(&["0", "0.5", "1"]) + &w(&["0", "1", "0"]), vec![(0.0, 0.0, 0.0), (0.5, 1.0, 1.0), (1.0, 0.0, 0.0)]), + (t(&["0", "1"]) + &w(&["1", "0.1"]), vec![(0.0, 1.0, 1.0), (1.0, 0.1, 0.1)]), + ] { + let style = width_style(&arrays); + assert!(style.plain && !style.simple && style.uneven, "{arrays:?}: {style:?}"); + let [StyleItem::Stroke { width, profile, .. }] = style.items.as_slice() else { panic!("{:?}", style.items) }; + assert_eq!((*width, profile.as_ref().map(|p| p.points.clone())), (30.0, Some(points)), "{arrays:?}"); + } +} + +#[test] +fn a_uniform_or_unreadable_width_profile_stays_a_live_stroke() { + let t = |v: &[&str]| bs_array("BSVariableWidthTValuesArray", v); + let w = |v: &[&str]| bs_array("BSCenteredWidthsArray", v); + for arrays in [ + String::new(), + t(&["0", "1"]) + &w(&["1", "1"]), + // No widths, widths of another length, widths that aren't numbers. + t(&["0", "0.5", "1"]), + t(&["0", "0.5", "1"]) + &w(&["0", "1"]), + t(&["0", "1"]) + "/Array :\n(x) /String ,\n(y) /String ,\n; (BSCenteredWidthsArray) ,\n", + // Positions that aren't numbers. + t(&["nan", "nan"]) + &w(&["0", "1"]), + ] { + let style = width_style(&arrays); + assert!(style.plain && style.simple, "{arrays:?}: {style:?}"); + let [StyleItem::Stroke { width, profile, .. }] = style.items.as_slice() else { panic!("{arrays:?}: {:?}", style.items) }; + assert_eq!((*width, profile), (30.0, &None), "{arrays:?}"); + } } #[test] diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index 14064cec5..2d6aa9967 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -6452,6 +6452,31 @@ fn an_object_with_a_feather_style_comes_in_with_a_live_effect() { assert!(!r.warnings.iter().any(|w| w.contains("drawn look")), "{:?}", r.warnings); } +/// The art style `Tapered`: a 4 pt stroke with width points `widths` (position, width) along it, +/// their widths written as one centered array. +fn width_profile_style(widths: &[(f64, f64)]) -> String { + width_arrays_style(widths, &["BSCenteredWidthsArray"]) +} + +/// The art style `Tapered`: a 4 pt stroke with width points `widths` (position, width) along it, +/// their widths written under each of `keys`. +fn width_arrays_style(widths: &[(f64, f64)], keys: &[&str]) -> String { + let array = |values: Vec| values.iter().map(|v| format!("{v} /Real ,\n")).collect::(); + let width_arrays: String = keys.iter().map(|key| format!("/Array :\n{}; ({key}) ,\n", array(widths.iter().map(|w| w.1).collect()))).collect(); + format!( + "%AI9_BeginArtStyles\n\ + /KnownStyle :\n(Tapered) /Name ,\n/ActiveStyle :\n/CompoundFilter :\n(Chain Style Filter) 0 0 /Filter ,\n1 /Visible ,\n\ + /CompoundFilter :\n(Stack Style Filter) 0 0 /Filter ,\n1 /Visible ,\n\ + /BasicFilter :\n(Stroke Style Filter) 0 0 /Filter ,\n1 /Visible ,\n2 /FillOrStroke ,\n/Dictionary : /NotRecorded ,\n\ + /StrokeStyle : 0 R\n0 0 0 0 0 0 1 XA\n0 J 0 j 4 w 10 M []0 d\n0 XR\n1 0 Xd\n/Def ;\n (StrokeStyle) ,\n\ + /Dictionary : /NotRecorded ,\n2 /Int (BSWidthSource) ,\n/Array :\n{}; (BSVariableWidthTValuesArray) ,\n{width_arrays}\ + ; (kAIBeautifulStrokesParamsDictKey) ,\n; /Dict ;\n /Part ,\n\ + /BasicFilter :\n(Conduit Filter) 0 0 /Filter ,\n1 /Visible ,\n;\n /Part ,\n;\n /Part ,\n;\n\ + %AI9_EndArtStyles\n", + array(widths.iter().map(|w| w.0).collect()), + ) +} + /// An art style section with style `PF`: an Effect › Pathfinder filter with `Command` `command` /// and `Mix` `mix` in the object's chain, over a fill and stroke that paint nothing. fn pathfinder_styles(command: u32, mix: f64) -> String { @@ -6482,6 +6507,73 @@ fn pathfinder_group() -> String { ) } +#[test] +fn a_variable_width_stroke_comes_in_as_its_drawn_outline() { + // GUARDS: the object came in as a live stroke drawn from its profile, and the outline in the file was dropped. + let hidden_line = "0 5 m\n10 5 L\n".replace('\n', "\n%_"); + let fills = format!("{RED}{}f\n", rect_path(30.0, 0.0, 40.0, 10.0)); + let art_ = format!("{fills}0 Ae\nu\n{RED}{}f\nU\n%_{hidden_line}S\n1 (Tapered) XW\n{fills}", rect_path(0.0, 3.0, 10.0, 7.0)); + let lens = width_profile_style(&[(0.0, 0.0), (0.5, 1.0), (1.0, 0.0)]); + let r = read_ok(&format!("{lens}{}", layer(&art_))); + let a = art(&r.doc); + assert_eq!(a.len(), 3, "{a:#?}"); + let look = &a[1]; + assert!(matches!(look.kind, NodeKind::Group { .. }), "{look:#?}"); + let [outline] = look.children().unwrap().as_slice() else { panic!("{look:#?}") }; + assert!(matches!(outline.kind, NodeKind::Path { .. }) && outline.appearance.fill().is_some(), "{outline:#?}"); + assert!(outline.appearance.stroke().is_none_or(|s| s.profile.is_none()), "{outline:#?}"); + assert!([&a[0], &a[2]].iter().all(|n| matches!(n.kind, NodeKind::Path { .. })), "the objects beside it stay in place"); + assert!(r.warnings.iter().any(|w| w.contains("drawn look (1)")), "{:?}", r.warnings); + + // A uniform profile is a plain stroke: the object comes in as itself. + let uniform = width_profile_style(&[(0.0, 1.0), (1.0, 1.0)]); + let r = read_ok(&format!("{uniform}{}", layer(&art_))); + let a = art(&r.doc); + assert_eq!(a.len(), 3, "{a:#?}"); + assert!(matches!(a[1].kind, NodeKind::Path { .. }), "{:#?}", a[1]); + assert_eq!(a[1].appearance.stroke().map(|s| (s.width, s.profile.is_none())), Some((4.0, true)), "{:#?}", a[1]); + assert!(!r.warnings.iter().any(|w| w.contains("drawn look")), "{:?}", r.warnings); +} + +/// Only a Stroke Style filter's widths written as one centered array come in as the drawn look. +/// A Stroke Offset stroke and widths written for each side, as VectorCraft saves them, stay live. +#[test] +fn only_centered_widths_on_a_plain_stroke_come_in_as_the_drawn_outline() { + // GUARDS: every stroke with a profile went to its drawn look, so a Stroke Offset stroke and the + // app's own files lost their live profile. + let hidden_line = "0 5 m\n10 5 L\n".replace('\n', "\n%_"); + let art_ = format!("0 Ae\nu\n{RED}{}f\nU\n%_{hidden_line}S\n1 (Tapered) XW\n", rect_path(0.0, 3.0, 10.0, 7.0)); + let lens = [(0.0, 0.0), (0.5, 1.0), (1.0, 0.0)]; + + let r = read_ok(&format!("{}{}", width_profile_style(&lens), layer(&art_))); + let a = art(&r.doc); + let [look] = a.as_slice() else { panic!("{a:#?}") }; + assert!(matches!(look.kind, NodeKind::Group { .. }), "{look:#?}"); + assert!(r.warnings.iter().any(|w| w.contains("drawn look (1)")), "{:?}", r.warnings); + + // One side's array alone doesn't replace the centered one, so the widths are still centered. + let r = read_ok(&format!("{}{}", width_arrays_style(&lens, &["BSCenteredWidthsArray", "BSLeftWidthsArray"]), layer(&art_))); + let a = art(&r.doc); + let [look] = a.as_slice() else { panic!("{a:#?}") }; + assert!(matches!(look.kind, NodeKind::Group { .. }), "{look:#?}"); + + let sides = width_arrays_style(&lens, &["BSLeftWidthsArray", "BSRightWidthsArray"]); + let profile = "/Dictionary : /NotRecorded ,\n/Array :\n0 /Real ,\n0.5 /Real ,\n1 /Real ,\n; (BSVariableWidthTValuesArray) ,\n\ + /Array :\n0 /Real ,\n1 /Real ,\n0 /Real ,\n; (BSCenteredWidthsArray) ,\n; (kAIBeautifulStrokesParamsDictKey) ,\n"; + let offset = format!( + "%AI9_BeginArtStyles\n{}%AI9_EndArtStyles\n", + offset_stroke_style_with("Tapered", &format!("0 /Bool (StrokeOffsetInside) ,\n{profile}"), OFFSET_STROKE) + ); + for (why, style) in [("each side's widths", sides), ("a Stroke Offset stroke", offset)] { + let r = read_ok(&format!("{style}{}", layer(&art_))); + let a = art(&r.doc); + let [object] = a.as_slice() else { panic!("{why}: {a:#?}") }; + assert!(matches!(object.kind, NodeKind::Path { .. }), "{why}: {object:#?}"); + assert!(object.appearance.stroke().is_some_and(|s| s.profile.is_some()), "{why}: {object:#?}"); + assert!(!r.warnings.iter().any(|w| w.contains("drawn look")), "{why}: {:?}", r.warnings); + } +} + #[test] fn a_group_with_a_pathfinder_effect_keeps_its_members_and_the_effect() { // GUARDS: the style was not read, and the group came in as its drawn look: one path, with no effect. @@ -6498,6 +6590,27 @@ fn a_group_with_a_pathfinder_effect_keeps_its_members_and_the_effect() { assert!(!r.warnings.iter().any(|w| w.contains("drawn look")), "{:?}", r.warnings); } +#[test] +fn a_variable_width_stroke_with_an_empty_look_comes_in_as_itself() { + // GUARDS: with no outline in its look to keep, the object was dropped. + let hidden_line = "0 5 m\n10 5 L\n".replace('\n', "\n%_"); + let hidden_fill = format!("{}f\n", rect_path(0.0, 3.0, 10.0, 7.0)).replace('\n', "\n%_"); + let lens = width_profile_style(&[(0.0, 0.0), (0.5, 1.0), (1.0, 0.0)]); + // An empty look, ones whose group or compound path holds only a copy read from `%_` lines, and + // ones that hold only a path painted with nothing or a hidden painted path. + let unpainted = format!("u\n{}n\nU\n", rect_path(0.0, 3.0, 10.0, 7.0)); + let invisible = format!("u\n1 Xw\n{RED}{}f\n0 Xw\nU\n", rect_path(0.0, 3.0, 10.0, 7.0)); + for look in ["u\nU\n".to_string(), format!("u\nu\n%_{hidden_fill}\nU\nU\n"), format!("u\n*u\n%_{hidden_fill}\n*U\nU\n"), unpainted, invisible] { + let art_ = format!("{look}%_{hidden_line}S\n1 (Tapered) XW\n"); + let r = read_ok(&format!("{lens}{}", layer(&art_))); + let a = art(&r.doc); + let [object] = a.as_slice() else { panic!("{look:?}: {a:#?}") }; + assert!(matches!(object.kind, NodeKind::Path { .. }), "{look:?}: {object:#?}"); + assert_eq!(object.appearance.stroke().map(|s| (s.width, s.profile.is_some())), Some((4.0, true)), "{look:?}: {object:#?}"); + assert!(!r.warnings.iter().any(|w| w.contains("drawn look")), "{look:?}: {:?}", r.warnings); + } +} + #[test] fn each_pathfinder_operation_on_a_group_draws_as_vectorcraft_draws_it() { // GUARDS: only Add (`Command` 0) was read; the other operations came in as the drawn look's one path. diff --git a/crates/eps/src/import/tests_native.rs b/crates/eps/src/import/tests_native.rs index e6e750207..a9bb8cf27 100644 --- a/crates/eps/src/import/tests_native.rs +++ b/crates/eps/src/import/tests_native.rs @@ -5774,6 +5774,36 @@ fn type_with_convert_to_shape_comes_in_as_type_with_it_live() { ); } +#[test] +fn type_with_a_variable_width_stroke_keeps_it_live() { + // GUARDS: other objects with a variable-width stroke come in as their drawn look; type keeps the + // stroke, since its drawn look can't be retyped. + use vectorcraft_doc::AppearanceItem; + let doc = text_document(&[("Bang\r", 0, (8200.0, 8180.0), (0.0, 0.0), 0.0)]); + let style = "%AI9_BeginArtStyles\n/KnownStyle :\n(Tapered) /Name ,\n/ActiveStyle :\n/CompoundFilter :\n(Chain Style Filter) 0 0 /Filter ,\n1 /Visible ,\n\ + /CompoundFilter :\n(Stack Style Filter) 0 0 /Filter ,\n1 /Visible ,\n/BasicFilter :\n(Stroke Style Filter) 0 0 /Filter ,\n1 /Visible ,\n\ + 2 /FillOrStroke ,\n/Dictionary : /NotRecorded ,\n/StrokeStyle : 0 R\n0 0 0 1 0 0 0 XA\n0 J 0 j 2 w 10 M []0 d\n0 XR\n1 0 Xd\n/Def ;\n (StrokeStyle) ,\n\ + /Dictionary : /NotRecorded ,\n/Array :\n0 /Real ,\n0.5 /Real ,\n1 /Real ,\n; (BSVariableWidthTValuesArray) ,\n\ + /Array :\n0 /Real ,\n1 /Real ,\n0 /Real ,\n; (BSCenteredWidthsArray) ,\n; (kAIBeautifulStrokesParamsDictKey) ,\n; /Dict ;\n /Part ,\n\ + /BasicFilter :\n(Conduit Filter) 0 0 /Filter ,\n1 /Visible ,\n;\n /Part ,\n;\n /Part ,\n;\n%AI9_EndArtStyles\n"; + let drawing = "0 0 0 1 k\n54 56 m\n100 56 L\n100 80 L\n54 80 L\nf"; + let look = format!("u\n/AI11Text :\n1 /FreeUndo ,\n0 /FrameIndex ,\n0 /StoryIndex ,\n;\n{drawing}\nU\n9 () XW\n"); + let own: String = text_object(0).lines().map(|l| format!("%_{l}\n")).collect(); + let art = native_with_text(&(style.to_string() + &layer("Art", true, &format!("{look}{own}1 (Tapered) XW\n"))), &doc); + let (d, notes) = crate::ai_alone(&private(&art)).unwrap(); + let mut texts = vec![]; + d.walk(|n| { + if let NodeKind::Text(t) = &n.kind { + texts.push((t.plain_text(), n.appearance.clone())); + } + }); + let [(text, a)] = texts.as_slice() else { panic!("{texts:?} {notes:?}") }; + assert_eq!(text, "Bang"); + let [AppearanceItem::Stroke(stroke)] = a.items.as_slice() else { panic!("{a:?}") }; + assert_eq!(stroke.profile.as_ref().map(|p| p.points.clone()), Some(vec![(0.0, 0.0, 0.0), (0.5, 1.0, 1.0), (1.0, 0.0, 0.0)])); + assert!(!notes.iter().any(|w| w.contains("drawn look")), "{notes:?}"); +} + #[test] fn point_type_without_a_matrix_is_placed_by_its_layout() { // GUARDS: Illustrator 24 leaves an unturned, unscaled point type's matrix out of its frame; such diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs index 910dd8e49..9bbc26d4f 100644 --- a/crates/ui-egui/src/app.rs +++ b/crates/ui-egui/src/app.rs @@ -561,7 +561,8 @@ impl VectorcraftApp { tools_float::clear_canvas_dock_zone(&ctx); } // Beside the canvas, docked Tools draws inside the canvas panel (`toolbar::show_beside_canvas`). - if self.ui.toolbar && self.ui.screen_mode < 3 && (!self.ui.tools_beside_canvas || self.ui.toolbar_pos.is_some()) { + // So does Tools docked on the right, left of the panels. + if self.ui.toolbar && self.ui.screen_mode < 3 && ((!self.ui.tools_beside_canvas && !self.ui.toolbar_right) || self.ui.toolbar_pos.is_some()) { toolbar::show(self, ui); } else if !self.ui.toolbar && self.ui.screen_mode < 3 && self.ui.tab_hid.is_none() { // Window › Tools hides the Tools panel alone; Tab hides the toolbars made with New diff --git a/crates/ui-egui/src/dialogs/new_document.rs b/crates/ui-egui/src/dialogs/new_document.rs index 1a4c39389..4c977ed6a 100644 --- a/crates/ui-egui/src/dialogs/new_document.rs +++ b/crates/ui-egui/src/dialogs/new_document.rs @@ -358,6 +358,7 @@ fn details(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog, b: &mut let top = ui.cursor().top(); ui.label(egui::RichText::new(tl!("PRESET DETAILS")).font(theme::semibold(11.0)).color(t.text_dim)); ui.add_space(8.0); + form::caption(ui, tl!("Document Name")); ui.horizontal(|ui| { form::text(ui, d, "name", DETAILS - 50.0); save_button(app, ui, d); @@ -876,6 +877,44 @@ mod tests { assert!(selected(&out), "the turned preset stays chosen: {:?}", field(&app, "preset")); } + /// The document's name field has a caption of its own under the Preset Details heading, and + /// the column stays inside the dialog's height with Save Preset and Advanced Options open. + #[test] + fn the_name_field_is_captioned_and_the_column_fits() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.run("file.newDialog", json!({})).unwrap(); + set(&mut app, "advanced", json!(true)); + set(&mut app, super::SAVING, json!(true)); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let mut time = 0.0; + let mut run = |app: &mut VectorcraftApp| { + time += 0.5; + let screen_rect = Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1400.0, 1000.0))); + let raw = egui::RawInput { screen_rect, time: Some(time), ..Default::default() }; + let mut out = ctx.run_ui(raw, |ui| crate::dialogs::show(app, ui.ctx())); + out.textures_delta.clear(); + out + }; + run(&mut app); + let out = run(&mut app); + fn walk(s: &egui::Shape, out: &mut Vec<(String, egui::Rect)>) { + match s { + egui::Shape::Text(t) => out.push((t.galley.text().to_string(), t.visual_bounding_rect())), + egui::Shape::Vec(v) => v.iter().for_each(|s| walk(s, out)), + _ => {} + } + } + let mut texts = vec![]; + out.shapes.iter().for_each(|c| walk(&c.shape, &mut texts)); + let at = |name: &str| texts.iter().find(|(t, _)| t == name).map(|(_, r)| *r).unwrap_or_else(|| panic!("{name} is missing: {texts:?}")); + let (heading, caption, value) = (at("PRESET DETAILS"), at("Document Name"), at("Untitled-1")); + assert!(heading.bottom() <= caption.top() && caption.bottom() <= value.top(), "{heading:?} {caption:?} {value:?}"); + assert!(value.bottom() <= at("Preset Name").top(), "the preset's own name field follows"); + let used = at("Create").bottom() - heading.top(); + assert!(used <= super::HEIGHT, "the column used {used}"); + } + #[test] fn a_preset_fills_the_details_and_create_makes_the_document() { let mut app = VectorcraftApp::new(Session::new(), Default::default()); diff --git a/crates/ui-egui/src/edit_toolbar.rs b/crates/ui-egui/src/edit_toolbar.rs index 1a4c54989..2d6fede3b 100644 --- a/crates/ui-egui/src/edit_toolbar.rs +++ b/crates/ui-egui/src/edit_toolbar.rs @@ -467,7 +467,7 @@ pub(crate) fn drawer(app: &mut VectorcraftApp, ctx: &egui::Context) { let screen = ctx.content_rect(); let id = drawer_id(); let size = ctx.memory(|m| m.area_rect(id)).map_or(vec2(WIDTH, 200.0), |r| r.size()); - let x = (bar.right() + GAP).min(screen.right() - size.x - GAP).max(screen.left()); + let x = crate::toolbar::beside(bar, size.x, GAP, crate::toolbar::room(app, ctx).shrink(GAP)); // Never above the toolbar's top: higher up are the window's title bar (and on macOS its // buttons) and the bars under it. let top = bar.top().max(screen.top() + GAP); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 99d07134f..ceb10bb4d 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -3928,7 +3928,7 @@ axis Both И двете Dock to Bottom Закотвяне долу Dock Control Bar Закотвяне на контролната лента Drag to float the toolbar Плъзнете, за да направите лентата с инструменти плаваща - Drag to the window's left edge to dock Плъзнете до левия ръб на прозореца за закотвяне + Drag to the window's left or right edge to dock Плъзнете до левия или десния ръб на прозореца за закотвяне Float Tool Group Плаваща група инструменти @msg no library `{id}` няма библиотека `{id}` @msg select some type first първо изберете текст @@ -4618,7 +4618,7 @@ axis Both И двете @msg its objects are nested too deeply обектите му са вложени твърде дълбоко @msg its section ending with `%{end}` doesn't end разделът му, завършващ на `%{end}`, няма край @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) четките, които VectorCraft все още не може да прочете, бяха пропуснати от панела „Четки“ ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet обектите с множество запълвания или щрихове, ефекти или четки се появиха във вид на изрисувания си изглед ({_1}): тяхната поява все още не подлежи на редактиране +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet обектите с множество запълвания или щрихове, щрихове с променлива ширина, ефекти или четки се появиха във вид на изрисувания си изглед ({_1}): тяхната поява все още не подлежи на редактиране @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding името `{name}` не е в UTF-8 и е прочетено като {label}: ако изглежда грешно, отворете файла с друго legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding името `{name}` не е в UTF-8, а части от него не са валидни и в {label}: отворете файла с друго legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) още имена, които не са в UTF-8, са прочетени като {label} ({count}) @@ -6426,3 +6426,4 @@ breakText Glyphs Глифи # Align to Pixel Grid Align to Pixel Grid Подравняване към пикселната мрежа +@msg side is left or right, not `{s}` side е left или right, а не `{s}` diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 8e931292f..95f92e8bf 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -1269,7 +1269,7 @@ lighting Light Svjetlo X: X: Y: Y: Drag to float the toolbar Prevucite da odvojite traku s alatima - Drag to the window's left edge to dock Prevucite do lijeve ivice prozora za sidrenje + Drag to the window's left or right edge to dock Prevucite do lijeve ili desne ivice prozora za sidrenje Crystallize Kristalizuj Add Anchor Point Tool Alat za dodavanje sidrene tačke Anchor Point Tool Alat sidrena tačka @@ -5014,7 +5014,7 @@ imageTrace Outline Obris @msg its objects are nested too deeply njeni objekti su previše duboko ugniježđeni @msg its section ending with `%{end}` doesn't end njen odjeljak koji završava s `%{end}` ne završava @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) četkice koje VectorCraft još ne može pročitati izostavljene su iz panela Četkice ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objekti s više ispuna ili poteza, efekata ili četkica stigli su kao njihov iscrtani izgled ({_1}): njihov izgled se još ne može uređivati +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objekti s više ispuna ili poteza, poteza promjenljive širine, efekata ili četkica stigli su kao njihov iscrtani izgled ({_1}): njihov izgled se još ne može uređivati @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding naziv `{name}` nije u UTF-8 i pročitan je kao {label}: ako izgleda pogrešno, otvorite datoteku s drugim legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding naziv `{name}` nije u UTF-8, a dijelovi nisu ispravni ni u {label}: otvorite datoteku s drugim legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) još naziva koji nisu u UTF-8 pročitano je kao {label} ({count}) @@ -6475,3 +6475,4 @@ shape Open Otvoren # Align to Pixel Grid Align to Pixel Grid Poravnaj s mrežom piksela +@msg side is left or right, not `{s}` side je left ili right, a ne `{s}` diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 88c5819a1..f4ff29ef0 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -1253,7 +1253,7 @@ antiAlias Sharp Nítid Drag to float as a toolbar Arrossega per surar com a barra d’eines Drag to float the toolbar Arrossega per fer surar la barra d’eines Drag to move Arrossega per moure - Drag to the window's left edge to dock Arrossega fins a la vora esquerra de la finestra per acoblar + Drag to the window's left or right edge to dock Arrossega fins a la vora esquerra o dreta de la finestra per acoblar Drag values or labels. Double-click a value to type. Arrossega els valors o les etiquetes. Fes doble clic en un valor per escriure’l. Draw Dibuixa Draw Behind Dibuixa al darrere @@ -5507,7 +5507,7 @@ digits Malayalam Malaiàlam @msg objects can't be nested more than 32 deep els objectes no es poden niar més de 32 nivells @msg objects inside a blend, envelope, repeat or Intertwine group can't be attached to a path els objectes dins d’una fusió, un embolcall, una repetició o un grup Intertwine no es poden adjuntar a un traçat @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) els pinzells que VectorCraft encara no pot llegir s'han deixat fora del tauler Pinzells ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet els objectes amb diversos farciments o traços, efectes o pinzells han arribat amb el seu aspecte dibuixat ({_1}): la seva aparença encara no és editable +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet els objectes amb diversos farciments o traços, traços d’amplada variable, efectes o pinzells han arribat amb el seu aspecte dibuixat ({_1}): la seva aparença encara no és editable @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding el nom `{name}` no és UTF-8 i s’ha llegit com a {label}: si sembla incorrecte, obriu el fitxer amb un altre legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding el nom `{name}` no és UTF-8, i algunes parts tampoc no són {label} vàlid: obriu el fitxer amb un altre legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) s’han llegit com a {label} més noms que no són UTF-8 ({count}) @@ -6637,3 +6637,4 @@ breakText Glyphs Glifs # Align to Pixel Grid Align to Pixel Grid Alinea a la quadrícula de píxels @msg only paths and compound paths can be aligned to the pixel grid només els traçats i els traçats compostos es poden alinear a la quadrícula de píxels +@msg side is left or right, not `{s}` side ha de ser left o right, no `{s}` diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 9af7d8e50..517560adc 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1289,7 +1289,7 @@ lighting Light Světlo X: X: Y: Y: Drag to float the toolbar Přetažením vytvoříte plovoucí panel nástrojů - Drag to the window's left edge to dock Přetažením k levému okraji okna panel ukotvíte + Drag to the window's left or right edge to dock Přetažením k levému nebo pravému okraji okna panel ukotvíte # Tools Crystallize Krystalizace @@ -5089,7 +5089,7 @@ imageTrace Outline Obrys @msg its objects are nested too deeply jeho objekty jsou vnořené příliš hluboko @msg its section ending with `%{end}` doesn't end jeho oddíl končící `%{end}` nemá konec @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) štětce, které VectorCraft zatím neumí načíst, byly vynechány z panelu Štětce ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objekty s více výplněmi nebo tahy, efekty či štětci byly načteny jako vykreslený vzhled ({_1}): jejich vzhled zatím nelze upravovat +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objekty s více výplněmi nebo tahy, tahy s proměnnou šířkou, efekty či štětci byly načteny jako vykreslený vzhled ({_1}): jejich vzhled zatím nelze upravovat @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding název `{name}` není v UTF-8 a byl načten jako {label}: pokud vypadá špatně, otevřete soubor s jiným legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding název `{name}` není v UTF-8 a jeho části nejsou platné ani v {label}: otevřete soubor s jiným legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) další názvy, které nejsou v UTF-8, byly načteny jako {label} ({count}) @@ -6656,3 +6656,4 @@ breakText Glyphs Glyfy # Align to Pixel Grid Align to Pixel Grid Zarovnat na mřížku obrazových bodů @msg only paths and compound paths can be aligned to the pixel grid na mřížku obrazových bodů lze zarovnat jen cesty a složené cesty +@msg side is left or right, not `{s}` side je left nebo right, ne `{s}` diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 5c6cac613..395176d3d 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1204,7 +1204,7 @@ lighting Light Lys X: X: Y: Y: Drag to float the toolbar Træk for at frigøre værktøjslinjen - Drag to the window's left edge to dock Træk til vinduets venstre kant for at fastgøre + Drag to the window's left or right edge to dock Træk til vinduets venstre eller højre kant for at fastgøre Crystallize Krystallisér Add Anchor Point Tool Tilføj ankerpunkt-værktøj Anchor Point Tool Ankerpunktværktøj @@ -4706,7 +4706,7 @@ imageTrace Outline Kontur @msg its objects are nested too deeply dens objekter er indlejret for dybt @msg its section ending with `%{end}` doesn't end dens sektion, der ender med `%{end}`, ender ikke @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) pensler, som VectorCraft endnu ikke kan læse, blev udeladt af panelet Pensler ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objekter med flere fyld eller streger, effekter eller pensler kom ind, som de er tegnet ({_1}): deres udseende er endnu ikke redigerbart +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objekter med flere fyld eller streger, streger med variabel bredde, effekter eller pensler kom ind, som de er tegnet ({_1}): deres udseende er endnu ikke redigerbart @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding navnet `{name}` er ikke UTF-8 og blev læst som {label}: hvis det ser forkert ud, så åbn filen med en anden legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding navnet `{name}` er ikke UTF-8, og dele af det er heller ikke gyldig {label}: åbn filen med en anden legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) flere navne, der ikke er UTF-8, blev læst som {label} ({count}) @@ -6440,3 +6440,4 @@ breakText Glyphs Glyffer # Align to Pixel Grid Align to Pixel Grid Juster efter pixelgitter +@msg side is left or right, not `{s}` side er left eller right, ikke `{s}` diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 0c494018e..00aaf39b3 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -3937,7 +3937,7 @@ theme Appearance Erscheinungsbild Dock to Bottom Unten andocken Dock Control Bar Steuerungsleiste andocken Drag to float the toolbar Ziehen, um die Werkzeugleiste abzulösen - Drag to the window's left edge to dock Zum Andocken an den linken Fensterrand ziehen + Drag to the window's left or right edge to dock Zum Andocken an den linken oder rechten Fensterrand ziehen Float Tool Group Werkzeuggruppe ablösen # Status and error messages (`@msg`), translated where the status bar shows them; the message itself @@ -4697,7 +4697,7 @@ theme Appearance Erscheinungsbild @msg its objects are nested too deeply seine Objekte sind zu tief verschachtelt @msg its section ending with `%{end}` doesn't end sein Abschnitt, der mit `%{end}` enden soll, endet nicht @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) Pinsel, die VectorCraft noch nicht lesen kann, wurden im Bedienfeld „Pinsel“ ausgelassen ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet Objekte mit mehreren Flächen oder Konturen, Effekten oder Pinseln wurden so übernommen, wie sie gezeichnet aussehen ({_1}): Ihr Aussehen ist noch nicht bearbeitbar +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet Objekte mit mehreren Flächen oder Konturen, Konturen mit variabler Breite, Effekten oder Pinseln wurden so übernommen, wie sie gezeichnet aussehen ({_1}): Ihr Aussehen ist noch nicht bearbeitbar @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding Der Name `{name}` ist kein UTF-8 und wurde als {label} gelesen: Wenn er falsch aussieht, öffnen Sie die Datei mit einer anderen legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding Der Name `{name}` ist kein UTF-8, und Teile davon sind auch kein gültiges {label}: Öffnen Sie die Datei mit einer anderen legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) Weitere Namen, die kein UTF-8 sind, wurden als {label} gelesen ({count}) @@ -6243,3 +6243,4 @@ breakText Glyphs Glyphen # Align to Pixel Grid Align to Pixel Grid Am Pixelraster ausrichten @msg only paths and compound paths can be aligned to the pixel grid nur Pfade und zusammengesetzte Pfade können am Pixelraster ausgerichtet werden +@msg side is left or right, not `{s}` side ist left oder right, nicht `{s}` diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index cc38cad02..57a32d3f2 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -4145,7 +4145,7 @@ digits Malayalam Μαλαγιάλαμ Drag to float as a toolbar Σύρετε για αποδέσμευση ως γραμμή εργαλείων Put back in the toolbar Επαναφορά στη γραμμή εργαλείων Drag to float the toolbar Σύρετε για αποδέσμευση της γραμμής εργαλείων - Drag to the window's left edge to dock Σύρετε στο αριστερό άκρο του παραθύρου για σύνδεση + Drag to the window's left or right edge to dock Σύρετε στο αριστερό ή στο δεξί άκρο του παραθύρου για σύνδεση Float Tool Group Αποδέσμευση ομάδας εργαλείων @msg a PostScript error ({name} in `{op}`) σφάλμα PostScript ({name} στην εντολή `{op}`) @msg a PostScript error ({name} in `{op}`), in {procs} σφάλμα PostScript ({name} στην εντολή `{op}`), στις διαδικασίες {procs} @@ -4729,7 +4729,7 @@ digits Malayalam Μαλαγιάλαμ @msg its objects are nested too deeply τα αντικείμενά του έχουν υπερβολικά βαθιά ένθεση @msg its section ending with `%{end}` doesn't end η ενότητά του που τελειώνει σε `%{end}` δεν τερματίζεται @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) τα πινέλα που το VectorCraft δεν μπορεί ακόμη να διαβάσει παραλείφθηκαν από τον πίνακα Πινέλα ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet τα αντικείμενα με πολλαπλά γεμίσματα, περιγράμματα, εφέ ή πινέλα εισήχθησαν ως απεικονίσεις ({_1}): η εμφάνισή τους δεν είναι ακόμη επεξεργάσιμη +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet τα αντικείμενα με πολλαπλά γεμίσματα, περιγράμματα, περιγράμματα μεταβλητού πλάτους, εφέ ή πινέλα εισήχθησαν ως απεικονίσεις ({_1}): η εμφάνισή τους δεν είναι ακόμη επεξεργάσιμη @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding το όνομα `{name}` δεν είναι UTF-8 και διαβάστηκε ως {label}: αν φαίνεται λάθος, ανοίξτε το αρχείο με άλλο legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding το όνομα `{name}` δεν είναι UTF-8 και τμήματά του δεν είναι έγκυρα ούτε σε {label}: ανοίξτε το αρχείο με άλλο legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) περισσότερα ονόματα που δεν είναι UTF-8 διαβάστηκαν ως {label} ({count}) @@ -6608,3 +6608,4 @@ breakText Glyphs Γλύφοι # Align to Pixel Grid Align to Pixel Grid Στοίχιση στο πλέγμα εικονοστοιχείων @msg only paths and compound paths can be aligned to the pixel grid μόνο διαδρομές και σύνθετες διαδρομές μπορούν να στοιχιστούν στο πλέγμα εικονοστοιχείων +@msg side is left or right, not `{s}` το side είναι left ή right, όχι `{s}` diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index fa3f9290d..ea39770da 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -4022,7 +4022,7 @@ axis Both Ambos Dock to Bottom Anclar abajo Dock Control Bar Anclar barra de control Drag to float the toolbar Arrastrar para hacer flotar la barra de herramientas - Drag to the window's left edge to dock Arrastrar al borde izquierdo de la ventana para anclar + Drag to the window's left or right edge to dock Arrastrar al borde izquierdo o derecho de la ventana para anclar Float Tool Group Hacer flotar el grupo de herramientas # Status and error messages (`@msg`), translated where the status bar shows them; the message itself @@ -4781,7 +4781,7 @@ axis Both Ambos @msg its objects are nested too deeply sus objetos están anidados a demasiada profundidad @msg its section ending with `%{end}` doesn't end su sección que termina con `%{end}` no termina @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) los pinceles que VectorCraft aún no puede leer se omitieron del panel Pinceles ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet los objetos con varios rellenos o trazos, efectos o pinceles se importaron con su aspecto dibujado ({_1}): su aspecto aún no se puede editar +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet los objetos con varios rellenos o trazos, trazos de ancho variable, efectos o pinceles se importaron con su aspecto dibujado ({_1}): su aspecto aún no se puede editar @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding el nombre `{name}` no está en UTF-8 y se leyó como {label}: si no se ve bien, abre el archivo con otro legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding el nombre `{name}` no está en UTF-8 y partes de él tampoco son {label} válido: abre el archivo con otro legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) se leyeron como {label} más nombres que no están en UTF-8 ({count}) @@ -6599,3 +6599,4 @@ breakText Glyphs Glifos # Align to Pixel Grid Align to Pixel Grid Alinear con cuadrícula de píxeles @msg only paths and compound paths can be aligned to the pixel grid solo los trazados y los trazados compuestos se pueden alinear con la cuadrícula de píxeles +@msg side is left or right, not `{s}` side es left o right, no `{s}` diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index eaf0c103d..57b40495b 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -1267,7 +1267,7 @@ lighting Light Valo X: X: Y: Y: Drag to float the toolbar Kelluta työkaluriviä vetämällä - Drag to the window's left edge to dock Telakoi vetämällä ikkunan vasempaan reunaan + Drag to the window's left or right edge to dock Telakoi vetämällä ikkunan vasempaan tai oikeaan reunaan Crystallize Kiteytä Add Anchor Point Tool Lisää ankkuripiste -työkalu Anchor Point Tool Ankkuripistetyökalu @@ -4991,7 +4991,7 @@ imageTrace Outline Ääriviiva @msg its objects are nested too deeply sen objektit ovat liian syvällä sisäkkäin @msg its section ending with `%{end}` doesn't end sen `%{end}`-merkintään päättyvä osio ei pääty @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) siveltimet, joita VectorCraft ei vielä osaa lukea, jätettiin pois Siveltimet-paneelista ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objektit, joilla on useita täyttöjä tai viivoja, tehosteita tai siveltimiä, tuotiin piirretyn näköisinä ({_1}): niiden ulkoasua ei voi vielä muokata +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objektit, joilla on useita täyttöjä tai viivoja, vaihtelevan levyisiä viivoja, tehosteita tai siveltimiä, tuotiin piirretyn näköisinä ({_1}): niiden ulkoasua ei voi vielä muokata @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding nimi `{name}` ei ole UTF-8:aa, ja se luettiin koodauksella {label}: jos se näyttää väärältä, avaa tiedosto toisella legacyNameEncoding-arvolla @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding nimi `{name}` ei ole UTF-8:aa, eivätkä kaikki sen osat ole kelvollista {label}-koodausta: avaa tiedosto toisella legacyNameEncoding-arvolla @msg more names that aren't UTF-8 were read as {label} ({count}) muita nimiä, jotka eivät ole UTF-8:aa, luettiin koodauksella {label} ({count}) @@ -6691,3 +6691,4 @@ breakText Glyphs Merkit # Align to Pixel Grid Align to Pixel Grid Tasaa pikseliruudukkoon @msg only paths and compound paths can be aligned to the pixel grid vain polut ja yhdistelmäpolut voi tasata pikseliruudukkoon +@msg side is left or right, not `{s}` side on left tai right, ei `{s}` diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index b6d9da760..6af933fed 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -4011,7 +4011,7 @@ axis Both Les deux Dock to Bottom Ancrer en bas Dock Control Bar Ancrer la barre de contrôle Drag to float the toolbar Faire glisser pour rendre la barre d’outils flottante - Drag to the window's left edge to dock Faire glisser vers le bord gauche de la fenêtre pour ancrer + Drag to the window's left or right edge to dock Faire glisser vers le bord gauche ou droit de la fenêtre pour ancrer Float Tool Group Rendre le groupe d’outils flottant # Status and error messages (`@msg`), translated where the status bar shows them; the message itself @@ -4768,7 +4768,7 @@ axis Both Les deux @msg its objects are nested too deeply leurs objets sont imbriqués trop profondément @msg its section ending with `%{end}` doesn't end leur section qui se termine par `%{end}` ne se termine pas @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) les formes que VectorCraft ne sait pas encore lire ont été omises du panneau Formes ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet les objets à plusieurs fonds ou contours, effets ou formes ont été importés tels qu’ils sont dessinés ({_1}) : leur aspect n’est pas encore modifiable +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet les objets à plusieurs fonds ou contours, contours de largeur variable, effets ou formes ont été importés tels qu’ils sont dessinés ({_1}) : leur aspect n’est pas encore modifiable @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding le nom `{name}` n’est pas en UTF-8 et a été lu en {label} : s’il semble incorrect, ouvrez le fichier avec un autre legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding le nom `{name}` n’est pas en UTF-8, et certaines parties ne sont pas non plus du {label} valide : ouvrez le fichier avec un autre legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) d’autres noms qui ne sont pas en UTF-8 ont été lus en {label} ({count}) @@ -6651,3 +6651,4 @@ breakText Glyphs Glyphes # Align to Pixel Grid Align to Pixel Grid Aligner sur la grille de pixels @msg only paths and compound paths can be aligned to the pixel grid seuls les tracés et les tracés transparents peuvent être alignés sur la grille de pixels +@msg side is left or right, not `{s}` side vaut left ou right, pas `{s}` diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index c972e3e79..9332208ca 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -1384,7 +1384,7 @@ antiAlias Sharp शार्प Drag to float as a toolbar एक टूलबार के रूप में तैरने के लिए खींचें Drag to float the toolbar टूलबार को तैरने के लिए खींचें Drag to move आगे बढ़ने के लिए - Drag to the window's left edge to dock खिड़की के बाएं किनारे पर डॉक करने के लिए खींचें + Drag to the window's left or right edge to dock खिड़की के बाएं या दाएं किनारे पर डॉक करने के लिए खींचें Drag values or labels. Double-click a value to type. मूल्यों या लेबल खींचें। टाइप करने के लिए एक मान को डबल क्लिक करें। Draw ड्रा Draw Behind पीछे खींचना @@ -6998,7 +6998,7 @@ antiAlias Sharp शार्प @msg warped text objects that came in as drawn art, not as editable type: {_1} मोड़े गए टेक्स्ट ऑब्जेक्ट जो संपादन योग्य टेक्स्ट के बजाय कला के रूप में आए: {_1} @msg objects that drew differently from the file's own page came in as its art: {_1} उन ऑब्जेक्ट्स को जो फ़ाइल के स्वयं के पृष्ठ से अलग-अलग तरीके से आकर्षित होते हैं, उनकी कला के रूप में आया: {_1} @msg objects were left out ऑब्जेक्ट्स को छोड़ दिया गया था -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet कई भराव या स्ट्रोक, इफ़ेक्ट या ब्रश के साथ ऑब्जेक्टएं उनके ड्रॉइंग लुक ({_1}) के रूप में आईं: उनकी उपस्थिति अभी तक संपादन योग्य नहीं है +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet कई भराव या स्ट्रोक, परिवर्तनीय चौड़ाई वाले स्ट्रोक, इफ़ेक्ट या ब्रश के साथ ऑब्जेक्टएं उनके ड्रॉइंग लुक ({_1}) के रूप में आईं: उनकी उपस्थिति अभी तक संपादन योग्य नहीं है @msg offset out of range सीमा से बाहर ऑफसेट @msg Offset Path: {e} ऑफसेट पाथ: {e} @msg Offset Path: select paths ऑफसेट पाथ: सिलेक्शन पाथ @@ -8098,3 +8098,4 @@ theme Appearance अपीयरेंस @msg the file is larger than the {MAX_INPUT_BYTES} bytes an importer reads फ़ाइल उन {MAX_INPUT_BYTES} बाइट्स से बड़ी है जो इंपोर्टर पढ़ता है @msg an importer declares 1 to {MAX_EXTENSIONS} `extensions` इंपोर्टर 1 से {MAX_EXTENSIONS} `extensions` घोषित करता है @msg an importer always gets an import's budget: leave `budget` out इंपोर्टर को हमेशा इंपोर्ट का बजट मिलता है: `budget` छोड़ दें +@msg side is left or right, not `{s}` side left या right होना चाहिए, `{s}` नहीं diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 82de11af4..169985e6f 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1161,7 +1161,7 @@ font-technology Variable Változó Drag to float as a toolbar Húzd el, hogy önálló eszköztárként lebegjen Drag to float the toolbar Húzd el az eszköztár lebegtetéséhez Drag to move Húzással áthelyezhető - Drag to the window's left edge to dock Dokkoláshoz húzd az ablak bal szélére + Drag to the window's left or right edge to dock Dokkoláshoz húzd az ablak bal vagy jobb szélére Step up or down Léptetés fel vagy le Drag values or labels. Double-click a value to type. Húzd az értékeket vagy a címkéket. Gépeléshez kattints duplán egy értékre. Draw Rajzolás @@ -5169,7 +5169,7 @@ imageTrace Outline Körvonal @msg object {id} is not a path az objektum nem útvonal: {id} @msg object {id} is not a selected top-level object az objektum nem kijelölt legfelső szintű objektum: {id} @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) a VectorCraft által még nem olvasható ecsetek kimaradtak az Ecsetek panelről ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet a több kitöltésű vagy körvonalú, effektusos vagy ecsetes objektumok a megrajzolt kinézetükkel kerültek be ({_1}): a megjelenésük még nem szerkeszthető +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet a több kitöltésű vagy körvonalú, változó szélességű körvonalú, effektusos vagy ecsetes objektumok a megrajzolt kinézetükkel kerültek be ({_1}): a megjelenésük még nem szerkeszthető @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding a(z) `{name}` név nem UTF-8, ezért {label} kódolással lett beolvasva: ha hibásnak tűnik, nyissa meg a fájlt másik legacyNameEncoding értékkel @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding a(z) `{name}` név nem UTF-8, és egyes részei {label} kódolásban sem érvényesek: nyissa meg a fájlt másik legacyNameEncoding értékkel @msg more names that aren't UTF-8 were read as {label} ({count}) további nem UTF-8 nevek lettek {label} kódolással beolvasva ({count}) @@ -6640,3 +6640,4 @@ breakText Glyphs Glifák # Align to Pixel Grid Align to Pixel Grid Igazítás a pixelrácshoz @msg only paths and compound paths can be aligned to the pixel grid csak útvonalak és összetett útvonalak igazíthatók a pixelrácshoz +@msg side is left or right, not `{s}` a side értéke left vagy right lehet, nem `{s}` diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 00b343156..d7a17ce50 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1204,7 +1204,7 @@ lighting Light Cahaya X: X: Y: Y: Drag to float the toolbar Seret untuk mengambangkan bilah alat - Drag to the window's left edge to dock Seret ke tepi kiri jendela untuk memasang + Drag to the window's left or right edge to dock Seret ke tepi kiri atau kanan jendela untuk memasang Crystallize Kristalisasi Add Anchor Point Tool Alat tambah titik jangkar Anchor Point Tool Alat titik jangkar @@ -4706,7 +4706,7 @@ imageTrace Outline Kontur @msg its objects are nested too deeply objeknya bersarang terlalu dalam @msg its section ending with `%{end}` doesn't end bagian yang berakhir dengan `%{end}` tidak berakhir @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) kuas yang belum dapat dibaca VectorCraft tidak dimasukkan ke panel Kuas ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objek dengan beberapa isian atau goresan, efek, atau kuas masuk sebagai tampilan gambarnya ({_1}): penampilannya belum dapat diedit +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objek dengan beberapa isian atau goresan, goresan dengan lebar bervariasi, efek, atau kuas masuk sebagai tampilan gambarnya ({_1}): penampilannya belum dapat diedit @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding nama `{name}` bukan UTF-8 dan dibaca sebagai {label}: jika terlihat salah, buka file dengan legacyNameEncoding lain @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding nama `{name}` bukan UTF-8, dan sebagiannya juga bukan {label} yang valid: buka file dengan legacyNameEncoding lain @msg more names that aren't UTF-8 were read as {label} ({count}) nama lain yang bukan UTF-8 dibaca sebagai {label} ({count}) @@ -6440,3 +6440,4 @@ breakText Glyphs Glif # Align to Pixel Grid Align to Pixel Grid Sejajarkan ke grid piksel +@msg side is left or right, not `{s}` side adalah left atau right, bukan `{s}` diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 1610413fb..1b8b82076 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4064,7 +4064,7 @@ axis Both Entrambi Dock to Bottom Ancora in basso Dock Control Bar Ancora barra di controllo Drag to float the toolbar Trascina per rendere mobile la barra degli strumenti - Drag to the window's left edge to dock Trascina sul bordo sinistro della finestra per ancorare + Drag to the window's left or right edge to dock Trascina sul bordo sinistro o destro della finestra per ancorare Float Tool Group Rendi mobile gruppo strumenti # Status and error messages (`@msg`), translated where the status bar shows them; the message itself @@ -4823,7 +4823,7 @@ axis Both Entrambi @msg its objects are nested too deeply i loro oggetti sono annidati troppo in profondità @msg its section ending with `%{end}` doesn't end la loro sezione che termina con `%{end}` non termina @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) i pennelli che VectorCraft non sa ancora leggere sono stati esclusi dal pannello Pennelli ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet gli oggetti con più riempimenti o tratti, effetti o pennelli sono stati importati come appaiono disegnati ({_1}): il loro aspetto non è ancora modificabile +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet gli oggetti con più riempimenti o tratti, tratti a larghezza variabile, effetti o pennelli sono stati importati come appaiono disegnati ({_1}): il loro aspetto non è ancora modificabile @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding il nome `{name}` non è in UTF-8 ed è stato letto come {label}: se sembra errato, apri il file con un altro legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding il nome `{name}` non è in UTF-8 e alcune sue parti non sono valide nemmeno in {label}: apri il file con un altro legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) altri nomi non in UTF-8 sono stati letti come {label} ({count}) @@ -6277,3 +6277,4 @@ breakText Glyphs Glifi # Align to Pixel Grid Align to Pixel Grid Allinea alla griglia pixel @msg only paths and compound paths can be aligned to the pixel grid solo i tracciati e i tracciati composti possono essere allineati alla griglia pixel +@msg side is left or right, not `{s}` side è left o right, non `{s}` diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 1e76e79a8..5a1224b21 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4006,7 +4006,7 @@ axis Both 両方 Dock to Bottom 下部にドッキング Dock Control Bar コントロールバーをドッキング Drag to float the toolbar ドラッグしてツールバーをフローティング - Drag to the window's left edge to dock ウィンドウの左端までドラッグしてドッキング + Drag to the window's left or right edge to dock ウィンドウの左端または右端までドラッグしてドッキング Float Tool Group ツールグループをフローティング # The macOS menu bar (native_menu): the app and Window menus' system items, Settings pages. @@ -4514,7 +4514,7 @@ swatch Bright Violet ビビッドバイオレット @msg area type in frames too short for a line of the font it shows in got taller frames ({_1}): their type wouldn't show otherwise 表示に使われるフォントの 1 行に対して低すぎるフレーム内のエリア内文字は、フレームを高くしました ({_1}): そうしないと文字が表示されません @msg gradient “{name}” isn't defined in the file: a plain colour stands in for it グラデーション「{name}」はファイル内で定義されていません: 代わりに単色を使用しています @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) VectorCraft がまだ読み込めないブラシ {_1} 個はブラシパネルに追加されませんでした -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet 塗りや線が複数ある、または効果やブラシを適用したオブジェクト {_1} 個は見た目のまま読み込まれました: アピアランスはまだ編集できません +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet 塗りや線が複数ある、可変幅の線がある、または効果やブラシを適用したオブジェクト {_1} 個は見た目のまま読み込まれました: アピアランスはまだ編集できません @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding 名前 `{name}` は UTF-8 ではないため {label} として読み込みました: 正しくない場合は別の legacyNameEncoding でファイルを開いてください @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding 名前 `{name}` は UTF-8 ではなく、一部は {label} としても無効です: 別の legacyNameEncoding でファイルを開いてください @msg more names that aren't UTF-8 were read as {label} ({count}) UTF-8 ではないほかの名前を {label} として読み込みました ({count}) @@ -6342,3 +6342,4 @@ breakText Glyphs 字形 # Align to Pixel Grid Align to Pixel Grid ピクセルグリッドに整合 @msg only paths and compound paths can be aligned to the pixel grid ピクセルグリッドに整合できるのはパスと複合パスのみです +@msg side is left or right, not `{s}` side は left または right です(`{s}` ではありません) diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index bbde307d1..63a75daa4 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -1144,7 +1144,7 @@ lighting Light სინათლე Toggle single/double column ერთი/ორი სვეტის გადართვა W: სიგ.: Drag to float the toolbar გადაათრიეთ ხელსაწყოთა ზოლის მცურავად გადასაქცევად - Drag to the window's left edge to dock მისამაგრებლად გადაათრიეთ ფანჯრის მარცხენა კიდესთან + Drag to the window's left or right edge to dock მისამაგრებლად გადაათრიეთ ფანჯრის მარცხენა ან მარჯვენა კიდესთან Crystallize კრისტალიზაცია Add Anchor Point Tool საყრდენი წერტილის დამატების ხელსაწყო Anchor Point Tool საყრდენი წერტილის ხელსაწყო @@ -4515,7 +4515,7 @@ font-technology Variable ცვლადი @msg its objects are nested too deeply მისი ობიექტები ზედმეტად ღრმადაა ჩადგმული @msg its section ending with `%{end}` doesn't end მისი სექცია, რომელიც `%{end}`-ით მთავრდება, არ სრულდება @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) ფუნჯები, რომლებსაც VectorCraft ჯერ ვერ კითხულობს, ფუნჯების პანელში არ დაემატა ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet რამდენიმე შევსების ან კონტურის, ეფექტის ან ფუნჯის მქონე ობიექტები შემოვიდა დახატული სახით ({_1}): მათი იერსახე ჯერ არ რედაქტირდება +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet რამდენიმე შევსების ან კონტურის, ცვლადი სისქის კონტურის, ეფექტის ან ფუნჯის მქონე ობიექტები შემოვიდა დახატული სახით ({_1}): მათი იერსახე ჯერ არ რედაქტირდება @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding სახელი `{name}` არ არის UTF-8 და წაკითხულია როგორც {label}: თუ არასწორად გამოიყურება, გახსენით ფაილი სხვა legacyNameEncoding-ით @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding სახელი `{name}` არ არის UTF-8 და მისი ნაწილები არც {label}-ში არის სწორი: გახსენით ფაილი სხვა legacyNameEncoding-ით @msg more names that aren't UTF-8 were read as {label} ({count}) სხვა სახელები, რომლებიც არ არის UTF-8, წაკითხულია როგორც {label} ({count}) @@ -6717,3 +6717,4 @@ font-script Japanese იაპონური # Align to Pixel Grid Align to Pixel Grid პიქსელურ ბადეზე გასწორება @msg only paths and compound paths can be aligned to the pixel grid პიქსელურ ბადეზე მხოლოდ ტრაექტორიების და შედგენილი ტრაექტორიების გასწორებაა შესაძლებელი +@msg side is left or right, not `{s}` side არის left ან right და არა `{s}` diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index c1da3da08..931a3fe26 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -3990,7 +3990,7 @@ axis Both 양쪽 Dock to Bottom 아래쪽에 고정 Dock Control Bar 컨트롤 막대 고정 Drag to float the toolbar 드래그하여 도구 모음 분리 - Drag to the window's left edge to dock 창 왼쪽 가장자리로 드래그하여 고정 + Drag to the window's left or right edge to dock 창 왼쪽 또는 오른쪽 가장자리로 드래그하여 고정 Float Tool Group 도구 그룹 분리 Services 서비스 Hide VectorCraft VectorCraft 숨기기 @@ -4441,7 +4441,7 @@ theme Appearance 외관 @msg area type in frames too short for a line of the font it shows in got taller frames ({_1}): their type wouldn't show otherwise 표시되는 글꼴의 한 줄보다 낮은 프레임의 영역 문자는 프레임을 더 높였습니다({_1}). 그렇지 않으면 문자가 표시되지 않습니다 @msg gradient “{name}” isn't defined in the file: a plain colour stands in for it 그레이디언트 “{name}”이(가) 파일에 정의되어 있지 않아 단색으로 대체했습니다 @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) VectorCraft에서 아직 읽을 수 없는 브러시는 브러시 패널에서 제외되었습니다({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet 칠이나 선이 여러 개인 개체, 효과, 브러시가 있는 개체는 그려진 모양({_1})으로 가져왔습니다: 모양새는 아직 편집할 수 없습니다 +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet 칠이나 선이 여러 개인 개체, 폭이 변하는 선, 효과, 브러시가 있는 개체는 그려진 모양({_1})으로 가져왔습니다: 모양새는 아직 편집할 수 없습니다 @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding 이름 `{name}`은(는) UTF-8이 아니어서 {label}(으)로 읽었습니다: 잘못 보이면 다른 legacyNameEncoding으로 파일을 여세요 @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding 이름 `{name}`은(는) UTF-8이 아니고 일부는 {label}에서도 올바르지 않습니다: 다른 legacyNameEncoding으로 파일을 여세요 @msg more names that aren't UTF-8 were read as {label} ({count}) UTF-8이 아닌 다른 이름도 {label}(으)로 읽었습니다({count}) @@ -6238,3 +6238,4 @@ breakText Glyphs 글리프 # Align to Pixel Grid Align to Pixel Grid 픽셀 격자에 정렬 @msg only paths and compound paths can be aligned to the pixel grid 패스와 컴파운드 패스만 픽셀 격자에 정렬할 수 있습니다 +@msg side is left or right, not `{s}` side는 left 또는 right입니다(`{s}` 아님) diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index b5d8f750a..ddc5d2186 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1218,7 +1218,7 @@ lighting Light Licht X: X: Y: Y: Drag to float the toolbar Sleep om de werkbalk zwevend te maken - Drag to the window's left edge to dock Sleep naar de linkerrand van het venster om te dokken + Drag to the window's left or right edge to dock Sleep naar de linker- of rechterrand van het venster om te dokken Crystallize Kristalliseren Add Anchor Point Tool Ankerpunt toevoegen Anchor Point Tool Ankerpunt-gereedschap @@ -4718,7 +4718,7 @@ imageTrace Outline Contour @msg its objects are nested too deeply de objecten ervan zijn te diep genest @msg its section ending with `%{end}` doesn't end zijn sectie die op `%{end}` eindigt, eindigt niet @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) penselen die VectorCraft nog niet kan lezen, zijn weggelaten uit het deelvenster Penselen ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objecten met meerdere vullingen of lijnen, effecten of penselen zijn als hun getekende uiterlijk binnengekomen ({_1}): hun vormgeving is nog niet bewerkbaar +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objecten met meerdere vullingen of lijnen, lijnen met variabele breedte, effecten of penselen zijn als hun getekende uiterlijk binnengekomen ({_1}): hun vormgeving is nog niet bewerkbaar @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding de naam `{name}` is geen UTF-8 en is gelezen als {label}: als die er verkeerd uitziet, open het bestand dan met een andere legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding de naam `{name}` is geen UTF-8, en delen ervan zijn ook geen geldige {label}: open het bestand met een andere legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) meer namen die geen UTF-8 zijn, zijn gelezen als {label} ({count}) @@ -6491,3 +6491,4 @@ breakText Glyphs Glyphs # Align to Pixel Grid Align to Pixel Grid Uitlijnen op pixelraster +@msg side is left or right, not `{s}` side is left of right, niet `{s}` diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index a78055e25..6df898022 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1217,7 +1217,7 @@ lighting Light Światło Toggle single/double column Przełącz jedną/dwie kolumny W: S: Drag to float the toolbar Przeciągnij, aby odpiąć pasek narzędzi - Drag to the window's left edge to dock Przeciągnij do lewej krawędzi okna, aby zadokować + Drag to the window's left or right edge to dock Przeciągnij do lewej lub prawej krawędzi okna, aby zadokować Crystallize Krystalizacja Add Anchor Point Tool Narzędzie Dodaj punkt kontrolny Anchor Point Tool Narzędzie Punkt kontrolny @@ -4915,7 +4915,7 @@ imageTrace Outline Kontur @msg its objects are nested too deeply jego obiekty są zagnieżdżone zbyt głęboko @msg its section ending with `%{end}` doesn't end jego sekcja kończąca się na `%{end}` nie ma końca @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) pędzle, których VectorCraft jeszcze nie odczytuje, pominięto w panelu Pędzle ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet obiekty z kilkoma wypełnieniami lub obrysami, efektami albo pędzlami wczytano jako ich narysowany wygląd ({_1}): ich wyglądu nie można jeszcze edytować +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet obiekty z kilkoma wypełnieniami lub obrysami, obrysami o zmiennej szerokości, efektami albo pędzlami wczytano jako ich narysowany wygląd ({_1}): ich wyglądu nie można jeszcze edytować @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding nazwa `{name}` nie jest w UTF-8 i została odczytana jako {label}: jeśli wygląda źle, otwórz plik z innym legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding nazwa `{name}` nie jest w UTF-8, a jej części nie są poprawne również w {label}: otwórz plik z innym legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) kolejne nazwy spoza UTF-8 odczytano jako {label} ({count}) @@ -6512,3 +6512,4 @@ breakText Glyphs Glify # Align to Pixel Grid Align to Pixel Grid Wyrównaj do siatki pikseli @msg only paths and compound paths can be aligned to the pixel grid do siatki pikseli można wyrównać tylko ścieżki i ścieżki złożone +@msg side is left or right, not `{s}` side to left lub right, a nie `{s}` diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index f0e40aeb9..c0bef943f 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -3610,7 +3610,7 @@ axis Horizontal Horizontal axis Vertical Vertical axis Both Ambos Drag to float the toolbar Arraste para flutuar a barra de ferramentas - Drag to the window's left edge to dock Arraste até a borda esquerda da janela para encaixar + Drag to the window's left or right edge to dock Arraste até a borda esquerda ou direita da janela para encaixar @msg no library `{id}` nenhuma biblioteca `{id}` @msg select some type first selecione algum texto primeiro @msg kerning must be a number, "auto", or "optical" O kerning deve ser um número, “auto” ou “optical” @@ -4350,7 +4350,7 @@ axis Both Ambos @msg its objects are nested too deeply seus objetos estão aninhados com profundidade excessiva @msg its section ending with `%{end}` doesn't end sua seção que termina com `%{end}` não termina @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) os pincéis que o VectorCraft ainda não consegue ler ficaram fora do painel Pincéis ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objetos com vários preenchimentos ou traços, efeitos ou pincéis entraram com a aparência desenhada ({_1}): sua aparência ainda não é editável +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objetos com vários preenchimentos ou traços, traços de largura variável, efeitos ou pincéis entraram com a aparência desenhada ({_1}): sua aparência ainda não é editável @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding o nome `{name}` não está em UTF-8 e foi lido como {label}: se parecer errado, abra o arquivo com outro legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding o nome `{name}` não está em UTF-8, e partes dele também não são {label} válido: abra o arquivo com outro legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) mais nomes que não estão em UTF-8 foram lidos como {label} ({count}) @@ -6729,3 +6729,4 @@ swatch Yellow Green Verde-amarelado # Align to Pixel Grid Align to Pixel Grid Alinhar à Grade de Pixels @msg only paths and compound paths can be aligned to the pixel grid somente caminhos e caminhos compostos podem ser alinhados à grade de pixels +@msg side is left or right, not `{s}` side é left ou right, não `{s}` diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 9f5f728b0..64fc2d64c 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -4058,7 +4058,7 @@ axis Both Оба Dock to Bottom Прикрепить снизу Dock Control Bar Прикрепить панель управления Drag to float the toolbar Перетащите, чтобы сделать панель инструментов плавающей - Drag to the window's left edge to dock Перетащите к левому краю окна для прикрепления + Drag to the window's left or right edge to dock Перетащите к левому или правому краю окна для прикрепления Float Tool Group Сделать группу инструментов плавающей # Status and error messages (`@msg`), translated where the status bar shows them; the message itself @@ -4817,7 +4817,7 @@ axis Both Оба @msg its objects are nested too deeply объекты в них вложены слишком глубоко @msg its section ending with `%{end}` doesn't end раздел, заканчивающийся на `%{end}`, не заканчивается @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) кисти, которые VectorCraft пока не умеет читать, не добавлены в панель «Кисти» ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet объекты с несколькими заливками или обводками, эффектами или кистями импортированы в нарисованном виде ({_1}): их оформление пока нельзя редактировать +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet объекты с несколькими заливками или обводками, обводками переменной ширины, эффектами или кистями импортированы в нарисованном виде ({_1}): их оформление пока нельзя редактировать @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding имя `{name}` не в UTF-8 и прочитано как {label}: если оно выглядит неверно, откройте файл с другим legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding имя `{name}` не в UTF-8, и его части недопустимы и в {label}: откройте файл с другим legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) другие имена не в UTF-8 прочитаны как {label} ({count}) @@ -6745,3 +6745,4 @@ shape Open Открытый # Align to Pixel Grid Align to Pixel Grid Выровнять по пиксельной сетке @msg only paths and compound paths can be aligned to the pixel grid по пиксельной сетке можно выровнять только контуры и составные контуры +@msg side is left or right, not `{s}` side — это left или right, а не `{s}` diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 7ef0f5e8d..821495b43 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -3938,7 +3938,7 @@ axis Both Båda Dock to Bottom Docka nederst Dock Control Bar Docka kontrollfältet Drag to float the toolbar Dra för att göra verktygsfältet flytande - Drag to the window's left edge to dock Dra till fönstrets vänsterkant för att docka + Drag to the window's left or right edge to dock Dra till fönstrets vänster- eller högerkant för att docka Float Tool Group Gör verktygsgruppen flytande @msg no library `{id}` inget bibliotek `{id}` @msg select some type first markera någon text först @@ -4679,7 +4679,7 @@ axis Both Båda @msg its objects are nested too deeply dess objekt är för djupt kapslade @msg its section ending with `%{end}` doesn't end dess avsnitt som slutar med `%{end}` avslutas aldrig @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) penslar som VectorCraft ännu inte kan läsa utelämnades från panelen Penslar ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objekt med flera fyllningar eller konturer, effekter eller penslar importerades som sitt ritade utseende ({_1}): deras utseende går inte att redigera än +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet objekt med flera fyllningar eller konturer, konturer med variabel bredd, effekter eller penslar importerades som sitt ritade utseende ({_1}): deras utseende går inte att redigera än @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding namnet `{name}` är inte UTF-8 och lästes som {label}: om det ser fel ut, öppna filen med en annan legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding namnet `{name}` är inte UTF-8, och delar av det är inte heller giltig {label}: öppna filen med en annan legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) fler namn som inte är UTF-8 lästes som {label} ({count}) @@ -8141,3 +8141,4 @@ digits Hindi Hindi # Align to Pixel Grid Align to Pixel Grid Justera mot pixelrutnät @msg only paths and compound paths can be aligned to the pixel grid bara banor och sammansatta banor kan justeras mot pixelrutnätet +@msg side is left or right, not `{s}` side är left eller right, inte `{s}` diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 26264b594..c4fbd4ffc 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -3824,7 +3824,7 @@ axis Both Her İkisi Dock to Bottom Alta yerleştir Dock Control Bar Kontrol çubuğunu yerleştir Drag to float the toolbar Araç çubuğunu ayırmak için sürükleyin - Drag to the window's left edge to dock Sabitlemek için pencerenin sol kenarına sürükleyin + Drag to the window's left or right edge to dock Sabitlemek için pencerenin sol veya sağ kenarına sürükleyin Float Tool Group Araç Grubunu Yüzer Yap @msg no library `{id}` `{id}` kütüphanesi yok @msg select some type first önce bir tür seçin @@ -4516,7 +4516,7 @@ axis Both Her İkisi @msg its objects are nested too deeply nesneleri çok derin bir şekilde iç içe geçmiş @msg its section ending with `%{end}` doesn't end `%{end}` ile biten bölümü bitmiyor @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) VectorCraft'ın henüz okuyamadığı fırçalar Fırçalar panelinin dışında bırakıldı ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet çeşitli dolgulara veya konturlara, efektlere veya fırçalara sahip nesneler çizilmiş görünümleri olarak geldi ({_1}): görünümleri henüz düzenlenemez +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet çeşitli dolgulara veya konturlara, değişken genişlikli konturlara, efektlere veya fırçalara sahip nesneler çizilmiş görünümleri olarak geldi ({_1}): görünümleri henüz düzenlenemez @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding `{name}` adı UTF-8 değil ve {label} olarak okundu: yanlış görünüyorsa dosyayı başka bir legacyNameEncoding ile açın @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding `{name}` adı UTF-8 değil ve bazı bölümleri {label} olarak da geçerli değil: dosyayı başka bir legacyNameEncoding ile açın @msg more names that aren't UTF-8 were read as {label} ({count}) UTF-8 olmayan diğer adlar {label} olarak okundu ({count}) @@ -6612,3 +6612,4 @@ breakText Glyphs Glifler # Align to Pixel Grid Align to Pixel Grid Piksel Izgarasına Hizala @msg only paths and compound paths can be aligned to the pixel grid piksel ızgarasına yalnızca yollar ve bileşik yollar hizalanabilir +@msg side is left or right, not `{s}` side left veya right olmalıdır, `{s}` değil diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 824475c52..64664ee78 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4053,7 +4053,7 @@ axis Both Обидві Dock to Bottom Прикріпити внизу Dock Control Bar Прикріпити панель керування Drag to float the toolbar Перетягніть, щоб від’єднати панель інструментів - Drag to the window's left edge to dock Перетягніть до лівого краю вікна для прикріплення + Drag to the window's left or right edge to dock Перетягніть до лівого або правого краю вікна для прикріплення Float Tool Group Від’єднати групу інструментів @msg no library `{id}` немає бібліотеки `{id}` @msg select some type first спочатку виділіть текст @@ -4807,7 +4807,7 @@ axis Both Обидві @msg its objects are nested too deeply об’єкти в них вкладені надто глибоко @msg its section ending with `%{end}` doesn't end розділ, що закінчується на `%{end}`, не закінчується @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) пензлі, які VectorCraft поки не вміє читати, не додано до панелі «Пензлі» ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet об’єкти з кількома заливками або обведеннями, ефектами або пензлями імпортовані в намальованому вигляді ({_1}): їх оформлення поки що не можна редагувати +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet об’єкти з кількома заливками або обведеннями, обведеннями змінної ширини, ефектами або пензлями імпортовані в намальованому вигляді ({_1}): їх оформлення поки що не можна редагувати @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding назва `{name}` не в UTF-8 і прочитана як {label}: якщо вона виглядає неправильно, відкрийте файл з іншим legacyNameEncoding @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding назва `{name}` не в UTF-8, і її частини недійсні також у {label}: відкрийте файл з іншим legacyNameEncoding @msg more names that aren't UTF-8 were read as {label} ({count}) інші назви не в UTF-8 прочитано як {label} ({count}) @@ -6257,3 +6257,4 @@ breakText Glyphs Гліфи # Align to Pixel Grid Align to Pixel Grid Вирівняти за піксельною сіткою @msg only paths and compound paths can be aligned to the pixel grid за піксельною сіткою можна вирівняти лише контури і складені контури +@msg side is left or right, not `{s}` side — це left або right, а не `{s}` diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 6e2a9e33f..8f7783047 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1045,7 +1045,7 @@ font-technology Variable Biến thiên Drag to float as a toolbar Kéo để thả nổi thanh công cụ Drag to float the toolbar Kéo để thả nổi thanh công cụ Drag to move Kéo để di chuyển - Drag to the window's left edge to dock Kéo đến cạnh trái cửa sổ để neo + Drag to the window's left or right edge to dock Kéo đến cạnh trái hoặc phải cửa sổ để neo Step up or down Tăng hoặc giảm một bước Drag values or labels. Double-click a value to type. Kéo giá trị hoặc nhãn. Bấm đúp vào giá trị để nhập. Draw Vẽ @@ -4824,7 +4824,7 @@ theme Appearance Giao diện @msg object {id} is not a path đối tượng {id} không phải là đường dẫn @msg object {id} is not a selected top-level object đối tượng {id} không phải là đối tượng cấp cao nhất đã chọn @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) các cọ mà VectorCraft chưa đọc được đã bị bỏ khỏi bảng Cọ ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet đối tượng có nhiều tô hoặc nét, hiệu ứng hoặc cọ đã vào dưới dạng bề ngoài ({_1}): diện mạo chưa thể chỉnh sửa +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet đối tượng có nhiều tô hoặc nét, nét có độ rộng thay đổi, hiệu ứng hoặc cọ đã vào dưới dạng bề ngoài ({_1}): diện mạo chưa thể chỉnh sửa @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding tên `{name}` không phải UTF-8 và đã được đọc theo {label}: nếu trông sai, hãy mở tệp với legacyNameEncoding khác @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding tên `{name}` không phải UTF-8, và một số phần cũng không hợp lệ trong {label}: hãy mở tệp với legacyNameEncoding khác @msg more names that aren't UTF-8 were read as {label} ({count}) thêm các tên không phải UTF-8 đã được đọc theo {label} ({count}) @@ -6258,3 +6258,4 @@ breakText Glyphs Glyph # Align to Pixel Grid Align to Pixel Grid Căn theo lưới pixel @msg only paths and compound paths can be aligned to the pixel grid chỉ đường dẫn và đường dẫn phức mới căn theo lưới pixel được +@msg side is left or right, not `{s}` side là left hoặc right, không phải `{s}` diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 792919c6a..ec8b6f5b3 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -4227,7 +4227,7 @@ imageTrace Outline 轮廓 @msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers 为保留图层,{_1} × {_2} 像素的嵌入图像已缩小为 {_3} × {_4} 像素 @msg gradient “{name}” isn't defined in the file: a plain colour stands in for it 文件中未定义渐变“{name}”:已改用纯色代替 @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) VectorCraft 尚无法读取的画笔未加入“画笔”面板 ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet 带有多个填色或描边、效果或画笔的对象已按其绘制外观导入 ({_1}):其外观尚不可编辑 +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet 带有多个填色或描边、可变宽度描边、效果或画笔的对象已按其绘制外观导入 ({_1}):其外观尚不可编辑 @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding 名称 `{name}` 不是 UTF-8,已按 {label} 读取:如果显示不正确,请使用其他 legacyNameEncoding 打开文件 @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding 名称 `{name}` 不是 UTF-8,且部分内容在 {label} 中也无效:请使用其他 legacyNameEncoding 打开文件 @msg more names that aren't UTF-8 were read as {label} ({count}) 其他非 UTF-8 名称已按 {label} 读取 ({count}) @@ -5146,7 +5146,7 @@ axis Both 两者 Dock to Bottom 停放到底部 Dock Control Bar 停放控制栏 Drag to float the toolbar 拖动以浮动工具栏 - Drag to the window's left edge to dock 拖到窗口左边缘以停放 + Drag to the window's left or right edge to dock 拖到窗口左边缘或右边缘以停放 Float Tool Group 浮动工具组 # The macOS menu bar (native_menu): the app and Window menus' system items, Settings pages. @@ -6497,3 +6497,4 @@ breakText Glyphs 字形 # Align to Pixel Grid Align to Pixel Grid 对齐像素网格 @msg only paths and compound paths can be aligned to the pixel grid 只有路径和复合路径可以对齐像素网格 +@msg side is left or right, not `{s}` side 应为 left 或 right,而不是 `{s}` diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 569c72914..d21d46212 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2068,7 +2068,7 @@ file-filter Templates 範本 @msg an embedded image of {_1} × {_2} pixels was reduced to {_3} × {_4} pixels to keep the layers 為保留圖層,{_1} × {_2} 像素的內嵌影像已縮小為 {_3} × {_4} 像素 @msg gradient “{name}” isn't defined in the file: a plain colour stands in for it 檔案中未定義漸層「{name}」:已改用單色替代 @msg brushes VectorCraft can't read yet were left out of the Brushes panel ({_1}) VectorCraft 尚無法讀取的筆刷未加入「筆刷」面板 ({_1}) -@msg objects with several fills or strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet 具有多個填色或筆畫、效果或筆刷的物件已依其繪製外觀匯入 ({_1}):其外觀目前尚無法編輯 +@msg objects with several fills or strokes, variable-width strokes, effects or brushes came in as their drawn look ({_1}): their appearance isn't editable yet 具有多個填色或筆畫、可變寬度筆畫、效果或筆刷的物件已依其繪製外觀匯入 ({_1}):其外觀目前尚無法編輯 @msg the name `{name}` isn't UTF-8 and was read as {label}: if it looks wrong, open the file with another legacyNameEncoding 名稱 `{name}` 不是 UTF-8,已依 {label} 讀取:若顯示不正確,請以其他 legacyNameEncoding 開啟檔案 @msg the name `{name}` isn't UTF-8, and parts of it aren't valid {label} either: open the file with another legacyNameEncoding 名稱 `{name}` 不是 UTF-8,且部分內容在 {label} 中也無效:請以其他 legacyNameEncoding 開啟檔案 @msg more names that aren't UTF-8 were read as {label} ({count}) 其他非 UTF-8 名稱已依 {label} 讀取 ({count}) @@ -4969,7 +4969,7 @@ axis Both 兩者 Dock to Bottom 停駐到底部 Dock Control Bar 停駐控制列 Drag to float the toolbar 拖曳以浮動工具列 - Drag to the window's left edge to dock 拖曳到視窗左側邊緣以停駐 + Drag to the window's left or right edge to dock 拖曳到視窗左側或右側邊緣以停駐 Float Tool Group 浮動工具組 # The macOS menu bar (native_menu): the app and Window menus' system items, Settings pages. @@ -6295,3 +6295,4 @@ breakText Glyphs 字符 # Align to Pixel Grid Align to Pixel Grid 對齊像素格點 @msg only paths and compound paths can be aligned to the pixel grid 只有路徑和複合路徑可以對齊像素格點 +@msg side is left or right, not `{s}` side 應為 left 或 right,而不是 `{s}` diff --git a/crates/ui-egui/src/menus/ui_commands.rs b/crates/ui-egui/src/menus/ui_commands.rs index 81ae58673..c3af44d3d 100644 --- a/crates/ui-egui/src/menus/ui_commands.rs +++ b/crates/ui-egui/src/menus/ui_commands.rs @@ -615,7 +615,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "window.panel.dock", "Dock Panel", "", - "{panel: id or \"tools\", anchor?: id, zone?: tab|center|left|right|top|bottom|above|below, before?: id, group?: bool, column?: bool or n, side?: Start|End, onto?: id, above?: id, below?: id, icon?: bool, collapsed?: bool} dock a panel (with `group`, its whole group): with no destination a floating panel goes back where it floated from (else joins the first dock group) and a hidden one shows where it was; with `anchor` (or `onto`) in the group holding that panel, `zone` as for window.panel.move; with `side`, as a new innermost column of that dock (`column: true` then moves its whole dock column or floating window); `column: n` with no other destination docks its group, or the whole floating window it floats in, as a new dock column at that place (1: next to the icon column, counting inwards; `true`: innermost); `icon: true` puts it in a row of its own at the end of the icon column; `above` / `below` as a group of its own above or below the group holding that panel. `collapsed` then minimizes or restores its group. \"tools\" docks the Tools panel at the left edge of the window, or beside the canvas inside any left-side dock while window.toolsBesideCanvas is on. Returns {layout, closed, floating}", + "{panel: id or \"tools\", anchor?: id, zone?: tab|center|left|right|top|bottom|above|below, before?: id, group?: bool, column?: bool or n, side?: Start|End, onto?: id, above?: id, below?: id, icon?: bool, collapsed?: bool} dock a panel (with `group`, its whole group): with no destination a floating panel goes back where it floated from (else joins the first dock group) and a hidden one shows where it was; with `anchor` (or `onto`) in the group holding that panel, `zone` as for window.panel.move; with `side`, as a new innermost column of that dock (`column: true` then moves its whole dock column or floating window); `column: n` with no other destination docks its group, or the whole floating window it floats in, as a new dock column at that place (1: next to the icon column, counting inwards; `true`: innermost); `icon: true` puts it in a row of its own at the end of the icon column; `above` / `below` as a group of its own above or below the group holding that panel. `collapsed` then minimizes or restores its group. \"tools\" docks the Tools panel at the left edge of the window, or beside the canvas inside any left-side dock while window.toolsBesideCanvas is on; with side: right (or End) on the right, between the canvas and the panels, as dropping it there or on the window's right edge does (default: the side it docked at last; left or Start docks it on the left), returning {panel, floating, pos, side}. Returns {layout, closed, floating}", ), ( "window.collapseDock", diff --git a/crates/ui-egui/src/panel_docking.rs b/crates/ui-egui/src/panel_docking.rs index 9f75168ec..270c46a11 100644 --- a/crates/ui-egui/src/panel_docking.rs +++ b/crates/ui-egui/src/panel_docking.rs @@ -1418,13 +1418,53 @@ impl craft_ui::docking::DockContent for PanelContent<'_> { crate::panels::panel_menu_items(self.app, ui, id); } - fn group_header_width(&self, _panels: &[String], _active: &String, _floating: bool, _strip_width: f32) -> f32 { - 28.0 + fn group_header_width(&self, panels: &[String], _active: &String, floating: bool, _strip_width: f32) -> f32 { + let flyout = self.app.ui.open_panel.as_ref().is_some_and(|panel| panels.contains(panel)); + if floating || flyout { 52.0 } else { 28.0 } } - fn group_header(&mut self, ui: &mut egui::Ui, _panels: &[String], active: &String, _floating: bool) { - let rect = egui::Rect::from_center_size(ui.max_rect().center(), egui::vec2(16.0, 16.0)); - crate::panels::panel_menu(self.app, ui, active, rect); + fn group_header(&mut self, ui: &mut egui::Ui, panels: &[String], active: &String, floating: bool) { + let header = ui.max_rect(); + let flyout = self.app.ui.open_panel.clone().filter(|panel| panels.contains(panel)); + let menu_cell = if floating || flyout.is_some() { + let split = (header.right() - 28.0).max(header.left()); + let cell = egui::Rect::from_min_max(header.min, egui::pos2(split, header.bottom())); + if cell.is_positive() { + let rect = egui::Rect::from_center_size(cell.center(), egui::vec2(24.0_f32.min(cell.width()), 24.0_f32.min(cell.height()))); + let tokens = crate::theme::Tokens::get(ui.ctx()); + let response = ui.interact(rect, ui.scope_id().with(("collapse-to-icons", active)), egui::Sense::click()); + response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, ui.is_enabled(), tl!("Collapse to Icons"))); + let color = if response.hovered() || response.has_focus() { tokens.text_strong } else { tokens.text_dim }; + ui.painter().with_clip_rect(rect.intersect(ui.clip_rect())).text( + rect.center(), + egui::Align2::CENTER_CENTER, + "»", + egui::TextStyle::Button.resolve(ui.style()), + color, + ); + if response.on_hover_text(tl!("Collapse to Icons")).clicked() { + let result = if let Some(panel) = flyout { + // The opening icon can differ from the currently active tab. + self.app.run("window.panel", json!({"panel": panel})) + } else { + self.app.run("window.panel.dock", json!({"panel": active, "icon": true, "group": true})) + }; + match result { + // Dock commands reveal their destination. This control leaves the group collapsed. + Ok(_) => self.app.ui.open_panel = None, + Err(error) => self.app.ui.status = error, + } + } + } + egui::Rect::from_min_max(egui::pos2(split, header.top()), header.max) + } else { + header + }; + if menu_cell.is_positive() { + let rect = + egui::Rect::from_center_size(menu_cell.center(), egui::vec2(16.0_f32.min(menu_cell.width()), 16.0_f32.min(menu_cell.height()))); + crate::panels::panel_menu(self.app, ui, active, rect); + } } } diff --git a/crates/ui-egui/src/panel_docking/native_tests.rs b/crates/ui-egui/src/panel_docking/native_tests.rs index 5e72e61af..26614634a 100644 --- a/crates/ui-egui/src/panel_docking/native_tests.rs +++ b/crates/ui-egui/src/panel_docking/native_tests.rs @@ -25,6 +25,7 @@ struct Window { frames: usize, /// The texts its last frame drew, with their rects in the window. texts: Vec<(String, Rect)>, + accessibility: Vec, } struct Host { @@ -82,7 +83,14 @@ impl Host { let mut host = host.borrow_mut(); let builder = &viewport.builder; let rect = Rect::from_min_size(builder.position.unwrap_or_default(), builder.inner_size.unwrap_or(vec2(300.0, 200.0))); - host.windows.entry(id).or_insert_with(|| Window { rect, events: Vec::new(), dropped: Vec::new(), frames: 0, texts: Vec::new() }); + host.windows.entry(id).or_insert_with(|| Window { + rect, + events: Vec::new(), + dropped: Vec::new(), + frames: 0, + texts: Vec::new(), + accessibility: Vec::new(), + }); let viewports = host.infos(); let (time, focused) = (host.time, host.focus == id); let Some(window) = host.windows.get_mut(&id) else { return }; @@ -171,10 +179,13 @@ impl Host { } let mut shown = Vec::new(); full.shapes.iter().for_each(|c| texts(&c.shape, &mut shown)); + let accessibility = + full.platform_output.accesskit_update.take().map(|update| update.nodes.into_iter().map(|(_, node)| node).collect()).unwrap_or_default(); let mut host = host.borrow_mut(); host.child_layer_order.insert(id, layer_order); if let Some(window) = host.windows.get_mut(&id) { window.texts = shown; + window.accessibility = accessibility; } host.apply(&full); } @@ -349,6 +360,22 @@ fn open_button_menu(h: &mut Harness, viewport: ViewportId, side: f32, rank: impl menu } +/// Click a named button, independent of other controls sharing its size or position. +fn open_named_button_menu(h: &mut Harness, viewport: ViewportId, name: &str) -> Pos2 { + let buttons: Vec = h.host.borrow().windows[&viewport] + .accessibility + .iter() + .filter(|node| node.role() == egui::accesskit::Role::Button && node.label() == Some(name)) + .filter_map(|node| node.bounds()) + .map(|bounds| Rect::from_min_max(pos2(bounds.x0 as f32, bounds.y0 as f32), pos2(bounds.x1 as f32, bounds.y1 as f32))) + .collect(); + assert_eq!(buttons.len(), 1, "one visible button named {name}"); + let at = buttons[0].center(); + h.host.borrow_mut().windows.get_mut(&viewport).unwrap().events.extend([Event::PointerMoved(at), button(at, true), button(at, false)]); + h.frames(8); + at +} + /// A click on the ≡ button in the OS window `viewport`, the 16 pt square at the right end of the /// panel's tab strip. Returns where the button is. fn open_panel_menu(h: &mut Harness, viewport: ViewportId) -> Pos2 { @@ -421,14 +448,18 @@ fn the_library_submenu_scrolls_inside_a_short_native_window() { assert_submenu_reaches_its_end(&mut h, "swatches", 240.0, "Open Swatch Library", "Default Swatches", "Other Library…"); } -/// The Layers panel's Filter popup, under the topmost 24 pt button: the wheel brings its last row +/// The Layers panel's named Filter button opens its popup: the wheel brings its last row /// into a short window. #[test] fn the_layers_filter_popup_scrolls_inside_a_short_native_window() { let mut h = Harness::new(); + h.ctx.enable_accesskit(); let viewport = h.float("layers"); resize_window(&mut h, viewport, 160.0); - open_button_menu(&mut h, viewport, 24.0, |at| -at.y); + // The header's collapse control is also 24 pt square and sits above Filter. + assert!(text_rect(&h, viewport, "»").is_some(), "keep the competing header control in this regression"); + open_named_button_menu(&mut h, viewport, "Filter"); + assert!(floating_window(&layout_of(&h.app.ui), "layers").is_some(), "Filter must not collapse the panel"); let over = text_rect(&h, viewport, "By Object").expect("the popup's By Object heading"); assert!(text_rect(&h, viewport, "Hidden").is_none_or(|end| end.bottom() > 160.0), "the popup opens at the top of its list"); wheel_down(&mut h, viewport, over.center(), 5000.0); @@ -1542,3 +1573,31 @@ fn wheel_without_a_move_uses_the_floating_viewports_pointer() { h.frame(vec![]); assert_ne!(h.app.run("text.fonts", json!({})).unwrap(), before); } + +#[test] +fn floating_collapse_arrow_in_a_native_window_moves_the_group_to_the_rail() { + let mut h = Harness::new(); + h.app.run("window.panel.float", json!({"panel": "layers", "group": true})).unwrap(); + h.app.run("window.panel.activate", json!({"panel": "layers"})).unwrap(); + h.frames(3); + let viewport = h.viewport("layers"); + h.host.borrow_mut().focus = viewport; + let arrow = h.host.borrow().windows[&viewport] + .texts + .iter() + .find(|(text, _)| text == "»") + .map(|(_, rect)| rect.center()) + .expect("the native floating header paints a collapse arrow"); + for pressed in [true, false] { + h.host.borrow_mut().windows.get_mut(&viewport).unwrap().events.extend([Event::PointerMoved(arrow), button(arrow, pressed)]); + h.frames(1); + } + h.frames(3); + let layout = layout_of(&h.app.ui); + assert!(floating_window(&layout, "layers").is_none()); + assert_eq!(group_of(&layout, &"layers".into()), ["properties", "layers", "libraries"]); + assert!(["properties", "layers", "libraries"].iter().all(|p| in_iconic_column(&layout, p))); + let frames = h.host.borrow().windows[&viewport].frames; + h.frames(3); + assert_eq!(h.host.borrow().windows[&viewport].frames, frames, "the emptied palette is no longer rendered"); +} diff --git a/crates/ui-egui/src/panel_docking/tests.rs b/crates/ui-egui/src/panel_docking/tests.rs index 98c625743..afae95ca0 100644 --- a/crates/ui-egui/src/panel_docking/tests.rs +++ b/crates/ui-egui/src/panel_docking/tests.rs @@ -284,6 +284,45 @@ fn the_tools_panel_floats_by_its_title_bar_and_docks_on_the_left_edge() { assert_eq!(h.app.ui.toolbar_pos, None, "dropped on the left edge, it docks"); } +/// The Tools panel also docks on the right, left of the panels: dropped on the window's right edge +/// it stands between the canvas and the panels, its flyouts open toward the canvas, the side is +/// kept with the preferences, and it drags back to the left edge. +#[test] +fn the_tools_panel_docks_on_the_right_left_of_the_panels() { + let mut h = Harness::new(); + let zone = docked_tools(&h); + h.drag(zone.min + vec2(20.0, 12.0), pos2(600.0, 400.0)); + let [x, y] = h.app.ui.toolbar_pos.expect("the Tools panel floats"); + h.drag(pos2(x + 20.0, y + 7.0), pos2(SCREEN.right() - 10.0, 500.0)); + assert_eq!((h.app.ui.toolbar_pos, h.app.ui.toolbar_right), (None, true), "dropped on the right edge, it docks on the right"); + settle(&mut h); + let bar = docked_tools(&h); + let canvas = h.app.canvas_rect.expect("the canvas"); + let layers = h.tab("layers").expect("the Layers panel, docked right"); + assert!(canvas.right() <= bar.left() + 1.0 && bar.right() <= layers.left() + 1.0, "canvas {canvas:?}, Tools {bar:?}, Layers {layers:?}"); + assert!(SCREEN.right() - bar.right() > 100.0, "the panels keep the window's right edge: {bar:?}"); + // Its flyouts and drawer open toward the canvas, not over the panels. + let room = crate::toolbar::room(&h.app, &h.ctx); + assert!(room.right() <= bar.right() + 0.5, "{room:?}"); + let left = crate::toolbar::beside(bar, 160.0, 8.0, room); + assert!(left + 160.0 <= bar.left() - 7.0, "left of the toolbar: {left}"); + let prefs: crate::state::UiState = serde_json::from_slice(&serde_json::to_vec(&h.app.ui).unwrap()).unwrap(); + assert!(prefs.toolbar_right, "kept with the preferences"); + // Dragged back to the window's left edge, it docks there again. + h.drag(bar.min + vec2(20.0, 12.0), pos2(600.0, 400.0)); + let [x, y] = h.app.ui.toolbar_pos.expect("floats again"); + h.drag(pos2(x + 20.0, y + 7.0), pos2(12.0, 500.0)); + assert_eq!((h.app.ui.toolbar_pos, h.app.ui.toolbar_right), (None, false)); + settle(&mut h); + assert!(docked_tools(&h).left() < 10.0, "at the window's left edge again"); + // Agents: window.panel.dock takes the side (left/right, or Start/End as panels do). + let r = h.app.run("window.panel.dock", json!({"panel": "tools", "side": "End"})).unwrap(); + assert_eq!((r["side"].as_str(), h.app.ui.toolbar_right), (Some("right"), true)); + h.app.run("window.panel.float", json!({"panel": "tools"})).unwrap(); + assert_eq!(h.app.run("window.panel.dock", json!({"panel": "tools"})).unwrap()["side"], "right", "back on the side it docked at last"); + assert!(h.app.run("window.panel.dock", json!({"panel": "tools", "side": "top"})).is_err()); +} + /// The docked Tools panel as last drawn. fn docked_tools(h: &Harness) -> Rect { h.ctx.data(|d| d.get_temp::(crate::toolbar::docked_rect_id())).expect("the docked Tools panel") @@ -997,3 +1036,118 @@ fn other_function_keys_and_keyboard_guards_keep_their_routes() { assert_eq!(saved.session.doc().unwrap().doc.node_count(), 2, "F12 reloads the saved document"); std::fs::remove_dir_all(dir).unwrap(); } + +fn collapse_to_icons_button(h: &Harness, panel: &str) -> Option { + let panel = panel.to_string(); + h.ctx.read_response(area_id().with(("header", &panel)).with(("collapse-to-icons", &panel))).map(|r| r.rect) +} + +#[test] +fn floating_collapse_arrow_moves_the_whole_group_into_the_icon_rail() { + let mut h = Harness::new(); + assert!(collapse_to_icons_button(&h, "properties").is_none(), "docked headers have no extra button"); + h.app.run("window.panel.float", json!({"panel": "layers", "group": true})).unwrap(); + h.app.run("window.panel.activate", json!({"panel": "layers"})).unwrap(); + for _ in 0..3 { + h.frame(vec![]); + } + let before = h.layout(); + let members = group_of(&before, &"layers".into()); + let columns = end_columns(&before).len(); + let arrow = collapse_to_icons_button(&h, "layers").expect("floating header offers collapse to icons"); + assert!(arrow.width() <= 24.0 && arrow.height() <= 24.0, "a compact header control: {arrow:?}"); + h.click(arrow.center()); + let layout = h.layout(); + assert!(floating_window(&layout, "layers").is_none(), "the old floating window is removed"); + assert_eq!(group_of(&layout, &"layers".into()), members, "all tabs move together"); + assert!(members.iter().all(|p| in_iconic_column(&layout, p))); + assert_eq!(end_columns(&layout).len(), columns, "reuse the existing icon rail"); + assert!(h.app.ui.open_panel.is_none(), "the group stays collapsed until its icon is clicked"); + let icon = h.hit(&Hit::Icon("layers".into())).expect("Layers now has a rail icon"); + h.click(icon.center()); + assert_eq!(h.app.ui.open_panel.as_deref(), Some("layers")); + assert!(h.tab("layers").is_some(), "the collapsed group can reopen as a flyout"); + assert!(collapse_to_icons_button(&h, "layers").is_some(), "the reopened flyout also offers collapse to icons"); +} + +#[test] +fn floating_collapse_arrow_leaves_other_floating_groups_alone() { + let mut h = Harness::new(); + h.app.run("window.panel.float", json!({"panel": "layers"})).unwrap(); + h.app.run("window.panel.float", json!({"panel": "color", "below": "layers"})).unwrap(); + for _ in 0..3 { + h.frame(vec![]); + } + let arrow = collapse_to_icons_button(&h, "layers").expect("floating group has its own arrow"); + h.click(arrow.center()); + let layout = h.layout(); + assert!(in_iconic_column(&layout, "layers")); + assert!(floating_window(&layout, "color").is_some(), "another group in the same window stays floating"); + assert!(!in_iconic_column(&layout, "color")); + assert_eq!(main_panels(&layout), ["properties", "libraries"]); +} + +#[test] +fn floating_collapse_arrow_creates_a_rail_when_none_exists_and_keeps_the_menu() { + let mut h = Harness::new(); + h.app.run("window.panel.float", json!({"panel": "layers"})).unwrap(); + let mut layout = h.layout(); + if let Some(Node::Split { second, .. }) = layout.root.as_mut() + && let Node::Dock { columns, .. } = second.as_mut() + { + columns.retain(|column| !column.iconic); + } + assert!(icon_column(&end_columns(&layout)).is_none()); + h.app.ui.docking = Some(layout); + for _ in 0..3 { + h.frame(vec![]); + } + let panel = "layers".to_string(); + let menu = + h.ctx.read_response(area_id().with(("header", &panel)).with(("panel-menu", &panel))).expect("the existing panel menu remains available").rect; + let arrow = collapse_to_icons_button(&h, "layers").expect("floating group offers collapse"); + assert!(!menu.intersects(arrow), "menu and arrow have separate hit targets"); + h.click(menu.center()); + assert!(egui::Popup::is_any_open(&h.ctx), "the existing menu still opens"); + h.press(egui::Key::Escape, egui::Modifiers::NONE); + h.click(arrow.center()); + let layout = h.layout(); + assert!(in_iconic_column(&layout, "layers")); + assert!(floating_window(&layout, "layers").is_none()); + assert!(h.app.ui.open_panel.is_none(), "collapse does not immediately open a flyout"); + assert_eq!(main_panels(&layout), ["properties", "libraries"]); +} + +#[test] +fn rail_flyout_collapse_arrow_folds_the_panel_back_without_moving_it() { + let mut h = Harness::new(); + let icon = h.hit(&Hit::Icon("swatches".into())).unwrap(); + h.click(icon.center()); + let before = h.layout(); + let arrow = collapse_to_icons_button(&h, "swatches").expect("opened rail panels offer collapse to icons"); + h.click(arrow.center()); + assert!(h.app.ui.open_panel.is_none()); + assert!(craft_ui::docking::flyout::(&h.ctx, area_id()).is_none()); + assert_eq!(h.layout(), before, "folding a flyout leaves the dock layout intact"); + let icon = h.hit(&Hit::Icon("swatches".into())).expect("the rail icon stays available"); + h.click(icon.center()); + assert_eq!(h.app.ui.open_panel.as_deref(), Some("swatches")); + assert!(h.tab("swatches").is_some()); +} + +#[test] +fn rail_flyout_collapse_arrow_closes_the_opening_icon_after_switching_tabs() { + let mut h = Harness::new(); + h.app.ui.docking = Some(h.layout()); + let icon = h.hit(&Hit::Icon("swatches".into())).unwrap(); + h.click(icon.center()); + let brushes = h.tab("brushes").unwrap(); + h.click(brushes.center()); + assert_eq!(h.app.ui.open_panel.as_deref(), Some("swatches")); + let before = h.layout(); + let arrow = collapse_to_icons_button(&h, "brushes").expect("the active tab has a collapse arrow"); + h.click(arrow.center()); + assert!(h.app.ui.open_panel.is_none()); + assert!(craft_ui::docking::flyout::(&h.ctx, area_id()).is_none()); + assert_eq!(h.layout(), before, "all tabs and their active state stay in the rail"); +} diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index 53046a5a8..95ffcd9cd 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -473,6 +473,11 @@ pub struct UiState { /// `window.toolsBesideCanvas`). Off by default, and off in state saved without it. #[serde(default)] pub tools_beside_canvas: bool, + /// The docked Tools panel stands on the right, between the canvas and the panels, instead of on + /// the left (dropped there, or `window.panel.dock {panel: "tools", side: "right"}`). Off by + /// default, and off in state saved without it. + #[serde(default)] + pub toolbar_right: bool, pub status_bar: bool, pub help_bar: bool, pub dock: bool, @@ -1010,6 +1015,7 @@ impl Default for UiState { dock_width: None, toolbar_pos: None, tools_beside_canvas: false, + toolbar_right: false, status_bar: true, help_bar: true, dock: true, diff --git a/crates/ui-egui/src/toolbar.rs b/crates/ui-egui/src/toolbar.rs index dce5912ba..6a90cdfac 100644 --- a/crates/ui-egui/src/toolbar.rs +++ b/crates/ui-egui/src/toolbar.rs @@ -16,7 +16,7 @@ use crate::theme::{self, Tokens}; use crate::{VectorcraftApp, icons, widgets}; pub(crate) const PITCH: f32 = 30.0; -const WIDTH: f32 = 48.0; +pub(crate) const WIDTH: f32 = 48.0; /// Seconds a press on a tool group's button is held before its flyout opens. const LONG_PRESS: f64 = 0.35; /// Width of the grab bar down a flyout's right side: dragging it tears the flyout off. @@ -105,21 +105,39 @@ fn panel_width(ui: &crate::state::UiState) -> f32 { } /// Space reserved inside the protected canvas panel for a visible Tools panel docked beside the -/// canvas (`UiState::tools_beside_canvas`); none while Tools docks at the window's edge. +/// canvas (`UiState::tools_beside_canvas`) or on the canvas's right (`UiState::toolbar_right`); +/// none while Tools docks at the window's left edge. pub(crate) fn docked_width(ui: &crate::state::UiState) -> f32 { - if ui.tools_beside_canvas && ui.toolbar && ui.screen_mode < 3 && ui.toolbar_pos.is_none() { panel_width(ui) + 6.0 } else { 0.0 } + if (ui.tools_beside_canvas || ui.toolbar_right) && ui.toolbar && ui.screen_mode < 3 && ui.toolbar_pos.is_none() { + panel_width(ui) + 6.0 + } else { + 0.0 + } } /// With `UiState::tools_beside_canvas` on, the canvas-edge redocking target, registered even /// while Tools floats, and the docked panel, which follows the canvas's left edge as dock columns /// resize, collapse or hide. With it off, `app.rs` draws Tools before the docking layout. +/// +/// The canvas's right edge, left of the panels, is a redocking target too, and Tools docked there +/// (`UiState::toolbar_right`) is drawn at it whichever way the setting is. pub(crate) fn show_beside_canvas(app: &mut VectorcraftApp, ui: &mut Ui) { - if app.ui.tools_beside_canvas && app.ui.toolbar && app.ui.screen_mode < 3 { - crate::tools_float::set_dock_zones(ui.ctx(), ui.available_rect_before_wrap(), WIDTH); - if app.ui.toolbar_pos.is_none() { - show(app, ui); - } + if !app.ui.toolbar || app.ui.screen_mode >= 3 { + return; } + let canvas = ui.available_rect_before_wrap(); + crate::tools_float::set_right_dock_zone(ui.ctx(), canvas, WIDTH); + if app.ui.tools_beside_canvas { + crate::tools_float::set_dock_zones(ui.ctx(), canvas, WIDTH); + } + if app.ui.toolbar_pos.is_none() && (app.ui.tools_beside_canvas || app.ui.toolbar_right) { + show(app, ui); + } +} + +/// Is the Tools panel docked on the right, between the canvas and the panels? +pub(crate) fn docked_right(ui: &crate::state::UiState) -> bool { + ui.toolbar_right && ui.toolbar_pos.is_none() } pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { @@ -131,8 +149,13 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { let w = panel_width(&app.ui); match app.ui.toolbar_pos { None => { - let margin = egui::Margin { left: 6, right: 0, top: 6, bottom: 6 }; - let docked = egui::Panel::left("toolbar").resizable(false).exact_size(w + 6.0).frame(egui::Frame::NONE).show(ui, |ui| { + // The card's outer gap faces the edge it docks at. + let (panel, margin) = if app.ui.toolbar_right { + (egui::Panel::right("toolbar"), egui::Margin { left: 0, right: 6, top: 6, bottom: 6 }) + } else { + (egui::Panel::left("toolbar"), egui::Margin { left: 6, right: 0, top: 6, bottom: 6 }) + }; + let docked = panel.resizable(false).exact_size(w + 6.0).frame(egui::Frame::NONE).show(ui, |ui| { crate::theme::card(ui, &t, margin, |ui| body(app, ui, cols, None)); }); ui.ctx().data_mut(|d| d.insert_temp(docked_rect_id(), docked.response.rect)); @@ -211,7 +234,7 @@ fn body(app: &mut VectorcraftApp, ui: &mut Ui, cols: usize, floating: Option Option { ctx.input(|i| i.pointer.press_origin().zip(i.pointer.interact_pos()).map(|(a, b)| b - a)) } +/// Where the toolbar's flyouts and drawer may open: the window, or with the toolbar docked right +/// the part left of it, so they open toward the canvas instead of over the panels. +pub(crate) fn room(app: &VectorcraftApp, ctx: &egui::Context) -> egui::Rect { + let mut r = ctx.content_rect(); + if docked_right(&app.ui) + && let Some(x) = ctx.data(|d| d.get_temp::(docked_rect_id())).map(|bar| bar.right()) + { + r.max.x = r.max.x.min(x); + } + r +} + +/// The left edge of something `width` wide opened beside `anchor` (a tool, the toolbar), `gap` +/// from it: on its right when it fits in `screen`, else on its left (a toolbar docked right), +/// kept inside the screen either way. +pub(crate) fn beside(anchor: egui::Rect, width: f32, gap: f32, screen: egui::Rect) -> f32 { + let right = anchor.right() + gap; + let x = if right + width <= screen.right() { right } else { anchor.left() - gap - width }; + x.min(screen.right() - width).max(screen.left()) +} + fn flyout(app: &mut VectorcraftApp, ctx: &egui::Context) { if app.ui.flyout.is_none() { return; @@ -865,12 +909,13 @@ fn flyout(app: &mut VectorcraftApp, ctx: &egui::Context) { let active = app.session.tool_id(); let toolbar = ctx.data(|d| d.get_temp::>(flyout_toolbar_id())).flatten(); let mut input = FlyoutInput::default(); - let resp = egui::Area::new(egui::Id::unique("tool-flyout")).order(egui::Order::Foreground).fixed_pos(anchor.right_top() + vec2(8.0, -1.0)).show( - ctx, - |ui| { + // Beside the tool, on the side with room for it: toward the canvas from a toolbar docked right. + let size = ctx.memory(|m| m.area_rect(egui::Id::unique("tool-flyout"))).map_or(vec2(160.0, 0.0), |r| r.size()); + let x = beside(anchor, size.x, 8.0, room(app, ctx)); + let resp = + egui::Area::new(egui::Id::unique("tool-flyout")).order(egui::Order::Foreground).fixed_pos(pos2(x, anchor.top() - 1.0)).show(ctx, |ui| { flyout_frame(&t, false).show(ui, |ui| input = flyout_body(ui, &t, active, &tools, false, toolbar.as_deref())); - }, - ); + }); // Dragging the bar tears the flyout off, and so does releasing a press over it (a click, or the // end of the long press that opened the flyout): it floats where the pointer is. let at = ctx.input(|i| i.pointer.interact_pos()); diff --git a/crates/ui-egui/src/tools_float.rs b/crates/ui-egui/src/tools_float.rs index 13108368f..2781e4232 100644 --- a/crates/ui-egui/src/tools_float.rs +++ b/crates/ui-egui/src/tools_float.rs @@ -3,11 +3,12 @@ //! docked panel floats it under the pointer, a floating panel's title bar moves it, and a release //! over the strip along the window's left edge (lit up) docks it there. With Keep Tools Beside the //! Canvas on (`UiState::tools_beside_canvas`), a release over the canvas's left edge docks it too, -//! and either release docks it beside the canvas. Escape or losing the window's focus puts it back -//! where the drag began. +//! and either release docks it beside the canvas. A release over the canvas's right edge (left of +//! the panels) or the window's right edge docks it on the right, between the canvas and the panels. +//! Escape or losing the window's focus puts it back where the drag began. //! //! `window.panel.float {panel: "tools"}` / `window.panel.dock {panel: "tools"}` do the same for -//! agents. Its position is kept with the preferences and in user workspaces, and a position saved +//! agents (`side: right` docks it on the right). Its position and side are kept with the preferences and in user workspaces, and a position saved //! on a bigger window is clamped into this one when drawn. use egui::{CornerRadius, Id, Key, Pos2, Rect, Stroke, Vec2, pos2, vec2}; @@ -29,6 +30,8 @@ struct Move { grab: Vec2, from: Pos2, original: Option<[f32; 2]>, + /// The side it was docked on before the drag. + right: bool, } fn move_id() -> Id { @@ -49,6 +52,18 @@ pub(crate) fn tools_zone_id() -> Id { Id::unique("tools-float-zone") } +/// The strip along the canvas's right edge, left of the panels, where the Tools panel docks on the +/// right, as last laid out. +pub(crate) fn tools_zone_right_id() -> Id { + Id::unique("tools-float-zone-right") +} + +/// Accept a drop left of the panels docked right: the canvas's right edge. +pub(crate) fn set_right_dock_zone(ctx: &egui::Context, canvas: Rect, width: f32) { + let zone = Rect::from_min_max(pos2(canvas.right() - width, canvas.top()), canvas.max); + ctx.data_mut(|d| d.insert_temp(tools_zone_right_id(), zone)); +} + fn window_zone_id() -> Id { Id::unique("tools-float-window-zone") } @@ -93,7 +108,7 @@ pub(crate) fn cancel_move_on_escape(app: &mut VectorcraftApp, ctx: &egui::Contex fn start(app: &VectorcraftApp, ctx: &egui::Context, grab: Vec2) { let Some(from) = ctx.input(|i| i.pointer.press_origin().or(i.pointer.interact_pos())) else { return }; - ctx.data_mut(|d| d.insert_temp(move_id(), Move { grab, from, original: app.ui.toolbar_pos })); + ctx.data_mut(|d| d.insert_temp(move_id(), Move { grab, from, original: app.ui.toolbar_pos, right: app.ui.toolbar_right })); } /// The Tools panel's title bar was pressed (`title`, the panel's bounds `bounds`): docked, dragging @@ -116,13 +131,23 @@ pub(crate) fn drag_tools(app: &mut VectorcraftApp, ctx: &egui::Context, title: & } } -/// The docking strip under `at`, if any: the window's left edge, and with `beside_canvas` the -/// canvas's left edge as well. -fn drop_zone(ctx: &egui::Context, at: Pos2, beside_canvas: bool) -> Option { - ctx.data(|d| { +/// The docking strip under `at`, if any, and whether it docks the panel on the right: the window's +/// left edge, with `beside_canvas` the canvas's left edge as well, and on the right the canvas's +/// right edge or anywhere along the window's right edge (which lights up the strip left of the +/// panels, where it docks). +fn drop_zone(ctx: &egui::Context, at: Pos2, beside_canvas: bool) -> Option<(Rect, bool)> { + let (left, right) = ctx.data(|d| { let canvas = d.get_temp::(tools_zone_id()).filter(|_| beside_canvas); - [canvas, d.get_temp::(window_zone_id())].into_iter().flatten().find(|r| r.contains(at)) - }) + let left = [canvas, d.get_temp::(window_zone_id())].into_iter().flatten().find(|r| r.contains(at)); + (left, d.get_temp::(tools_zone_right_id())) + }); + if let Some(r) = left { + return Some((r, false)); + } + let r = right?; + // Read outside the data lock above: egui's context locks are not re-entrant. + let at_edge = at.x >= ctx.content_rect().right() - crate::toolbar::WIDTH && r.y_range().contains(at.y); + (r.contains(at) || at_edge).then_some((r, true)) } fn highlight(ctx: &egui::Context, zone: Rect) { @@ -138,6 +163,7 @@ pub fn track(app: &mut VectorcraftApp, ctx: &egui::Context) { let Some(m) = ctx.data(|d| d.get_temp::(move_id())) else { return }; if !gesture_allowed(ctx) { app.ui.toolbar_pos = m.original; + app.ui.toolbar_right = m.right; ctx.data_mut(|d| d.remove::(move_id())); if ctx.input(|i| i.key_pressed(Key::Escape)) { let frame = ctx.cumulative_frame_nr_for(ctx.viewport_id()); @@ -155,14 +181,15 @@ pub fn track(app: &mut VectorcraftApp, ctx: &egui::Context) { { app.ui.toolbar_pos = Some([p.x, p.y]); } - if let Some(zone) = zone { + if let Some((zone, _)) = zone { highlight(ctx, zone); } return; } ctx.data_mut(|d| d.remove::(move_id())); - if zone.is_some() { + if let Some((_, right)) = zone { app.ui.toolbar_pos = None; + app.ui.toolbar_right = right; } } @@ -191,9 +218,22 @@ pub(crate) fn command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option Ok(app.ui.toolbar_right), + // `Start` and `End`, as panels take them, too. + Some(s) if s.eq_ignore_ascii_case("left") || s.eq_ignore_ascii_case("start") => Ok(false), + Some(s) if s.eq_ignore_ascii_case("right") || s.eq_ignore_ascii_case("end") => Ok(true), + Some(s) => Err(format!("side is left or right, not `{s}`")), + }; + Some(position(p).and_then(|at| { + let right = right?; app.ui.toolbar_pos = float.then(|| at.map_or(TOOLS_AT, |p| [p.x, p.y])); app.ui.toolbar |= float; - json!({ "panel": "tools", "floating": float, "pos": app.ui.toolbar_pos }) + if !float { + app.ui.toolbar_right = right; + } + let side = if app.ui.toolbar_right { "right" } else { "left" }; + Ok(json!({ "panel": "tools", "floating": float, "pos": app.ui.toolbar_pos, "side": side })) })) } diff --git a/crates/ui-egui/src/workspaces.rs b/crates/ui-egui/src/workspaces.rs index fa7f70d96..7e3c9acc4 100644 --- a/crates/ui-egui/src/workspaces.rs +++ b/crates/ui-egui/src/workspaces.rs @@ -61,6 +61,9 @@ pub struct Workspace { /// The docked Tools panel sits beside the canvas, inside any left-side panels (built-in /// workspaces and workspaces saved without it: at the window's left edge). pub tools_beside_canvas: bool, + /// The docked Tools panel stands on the right, left of the panels (built-in workspaces and + /// workspaces saved without it: on the left). + pub toolbar_right: bool, /// Main dock column width in screen points while `docking` is none (#1753). #[serde(default, skip_serializing_if = "Option::is_none")] pub dock_width: Option, @@ -203,6 +206,7 @@ pub fn capture(ui: &UiState, name: &str) -> Workspace { docking_collapsed: Default::default(), toolbar_pos: ui.toolbar_pos, tools_beside_canvas: ui.tools_beside_canvas, + toolbar_right: ui.toolbar_right, dock_width: ui.dock_width, toolbars: ui.custom_toolbars.clone(), } @@ -231,6 +235,7 @@ pub fn apply(ui: &mut UiState, w: &Workspace) { ui.docking_hidden_groups = w.docking_hidden_groups.clone(); ui.toolbar_pos = w.toolbar_pos; ui.tools_beside_canvas = w.tools_beside_canvas; + ui.toolbar_right = w.toolbar_right; ui.dock_width = w.dock_width; crate::state::sanitize_toolbar_pos(&mut ui.toolbar_pos); ui.custom_toolbars = w.toolbars.clone(); diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 71f595508..ab0629430 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -229,7 +229,7 @@ private editing data. VectorCraft: ### Illustrator's private editing data -the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPrivateData` streams, a PDF's `AIPDFPrivateData` streams when it was saved with the editing data (#868), Zstandard or zlib, the EPS's in ASCII85) is not documented as a whole. VectorCraft reads its structure: layers and sublayers (names, order, visibility, lock, printing, preview, dimming, colour), groups as they were nested, compound paths (painted by their last path, or the last painted one, with the paths of groups inside them kept as their paths: #1410), clipping groups and layer clipping (several clipping paths in a clipping group, a layer or an opacity mask's art clip to their intersection, and a compound path of clipping paths stays one clip; a filled clipping path keeps its fill, which paints under the art it clips, except the path of a mask that covers several layers, whose fill is read as a path of its own in the layer that holds the mask's path, and a compound clipping path whose subpaths paint differently, whose filled subpaths are read as a compound path of their own under the art it clips; clipping paths that would nest the art past the document's 32 levels stop the editing data from being read: #5789), object names, hidden and locked objects, fills and strokes (grey, CMYK, RGB, named and spot colours, spot colours defined in Lab with their Lab values (#1032), global process colours as global swatches at their tint; a stroke of width 0, `0 w`, on a path, a gradient-filled path, legacy type or in an art style comes in as a 0.25 pt hairline, as from PDF: #1671), linear and radial gradients, pattern fills with their patterns as pattern swatches (#1025), gradient and pattern names in the file up to their first newline (a name that another swatch, or a gradient or pattern defined earlier, already has is numbered "Sky 2"; a name without a newline is kept, and when another swatch has it, the pattern's swatch, or a named color sharing a gradient or pattern swatch's name, is numbered the same way), opacity, blend modes, isolation and knockout, embedded images where their matrix puts them, rotated ones too (#2269), with their alpha channel (CMYK images keep their ink amounts, as they do when a `.ai` opens from its PDF part, and an alpha channel that hides part of a CMYK image becomes a grayscale opacity mask), placed (linked) files as linked images in their place, each showing the low-resolution preview the file keeps of it until its file is found or relinked in the Links panel (#1264), with the folder the file keeps relative to the document's, where the link is looked for when its path is gone, placed PDF and `.ai` pages as placed documents of the [page and Crop to box](https://helpx.adobe.com/illustrator/desktop/add-and-import-files/import-other-file-types/adobe-pdf-placement-options.html) their dictionary names (`AI11 PlacedPDFPageIndex`, `AI11 PlacedPDFCropTo`; a value VectorCraft doesn't read, or a binding to an image variable, leaves a linked image), guides, and every artboard where it is, with its center mark, cross hairs, video safe areas and video ruler pixel aspect ratio. VectorCraft reads `CropAreaActive`, an `/Int` in the dictionary that holds the artboard array, as the index of the active artboard in that array, and the file opens with that artboard active; without it, or when it names no artboard that opens, the first artboard is active. It also reads the Document Raster Effects Settings (resolution, color model, background, anti-alias, clipping mask, Add Around Object and Preserve Spot Colors), the author from the header's `%%For` (an EPS's own header when the editing data's has none), swatches for the header's `%%CMYKCustomColor` and `%%RGBCustomColor` colors that the document has none of, and `%%TemplateBox` in older files; a setting with a value of another type or out of range keeps its default. Objects on Path groups open live, with their path, their objects in order along it, Pivot and Rotate; one with a dragged spacing or other options opens as a plain group. A gradient with stops in the middle runs its whole ramp across its shape, and a gradient object does not pass its fill to the next path (#1390). An object whose art style is one fill and one stroke at most (solid colours, its own gradient, a variable-width profile) with Drop Shadow, Inner Glow, Outer Glow, Feather, Round Corners, Scribble, Offset Path, Outline Object, Outline Stroke, Convert to Shape, Tweak (only when it moves nothing), Pucker & Bloat at 0 or Bas Relief (read from its settings block: [raster effects reference](https://helpx.adobe.com/illustrator/desktop/special-effects-styles/apply-filter-effects/raster-effects-reference.html), [Photoshop File Formats Specification](https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/), [scripting the filter gallery](https://community.adobe.com/questions-712/scripting-support-for-the-filter-gallery-1064646)) comes in as itself with them and their saved settings. A path, compound path or group (not a clipping group) whose only effect is Transform (not with copies or Random) or a Free Distort whose corners make a parallelogram comes in as itself with it too, when neither it nor anything in it paints with a gradient or pattern and everything in it is a path, compound path or group with no opacity mask and no effects of its own; Transform that scales, rotates or reflects needs nothing in it to be stroked. Twist as the only effect is read the same way on an unstroked path or compound path whose segments are all straight. One with more, other effects (Zig Zag, a Pucker & Bloat of another amount, a Free Distort with other corners), an effect setting that is not read (an unknown key, a missing or out-of-range value, a Scribble with a color of its own), dashes aligned to corners, a brush other than a Calligraphic or Art one, a Calligraphic brush with settings of its own or an angle that follows the path, or an Art brush other than the ones described next comes in as its drawn look (a group named after it). Objects in a symbol defined before the file's art styles section, as newer files write it, take the same styles as the layers' objects. A path with a Calligraphic brush otherwise comes in as the path, the brush on its stroke at the stroke's weight, which scales the brush (#372), and so does a path with an Art brush, whose weight scales the art across the path, when all of these hold: the brush stretches its art along the path, unflipped, at 100% in Tints; the path's settings are the brush's own, with no entry that isn't read, no variable width and no effects; its stroke isn't a swatch; and the art is paths, compound paths and groups in solid fills, with no strokes, masks or effects, and has some length along the path. A stroke takes the brush the Brushes panel keeps under its name, and one whose name the panel doesn't keep comes in as its drawn look. Type with an Art brush keeps its drawn look under the type. A Calligraphic brush's Pressure, Tilt, Bearing and Rotation come in as those channels (a 6D Art Pen's brushes vary with the pressure, the bearing and the barrel's rotation); its Stylus Wheel varies at random. The file's Calligraphic, Scatter, Art and Bristle brushes replace the default ones in the Brushes panel, in the file's order, also when the file comes in as its PDF part (a brush library has no layers); pattern brushes and brushes it can't read are left out with a note. When the file opens from its layers, the spot colors in the art of each brush that comes in become swatches, and that art's colors count toward the document's color mode, as a pattern's do. The swatches take at most 20,000 color names in all across the brush art, and each brush-art section keeps at most 20,000 names. Each name keeps its first definition, with the color operators' names before the gradient stops' names. A spot color past those limits makes no swatch, and its color still counts toward the color mode. A spot color that the page's art, a pattern or a symbol also defines keeps the value defined there. Type keeps its art style as a live appearance whatever its stack (an Art brush aside), so it can be retyped: each fill and stroke of the style's stack (solid colours, gradients and pattern fills, placed as the drawn look places them) with its own opacity, blend mode and effects, the effects on the whole object, and where the characters paint among them; the effects read are Warp, Offset Path, Outline Object, Outline Stroke, Transform (with copies, Transform Objects, Transform Patterns and Scale Strokes & Effects; not with Random), Free Distort, Pucker & Bloat, Twist, Zig Zag, Tweak (only when it moves nothing, the file holding none of its random outline), Drop Shadow, Inner Glow, Outer Glow, Feather, Round Corners, Scribble, Gaussian Blur, Bas Relief (its detail, smoothness and light direction from its settings block, or the effect's defaults when the style holds no settings block; a settings block that isn't read keeps the drawn look) and Convert to Shape, and a style with others, or with an effect setting that isn't read, keeps the type's drawn look. Convert to Shape keeps its Rectangle, Rounded Rectangle or Ellipse, its Absolute or Relative size (a relative size in the file is added to each side) and its corner radius; a relative Ellipse, or a setting that isn't read, keeps the drawn look. The page's drawing of such a style isn't held to look like VectorCraft's where its drawn look lay. Gradients take their matrix (`Xm`, radials and skewed ones too), focal point and the order of stops sharing a ramp point; gradient meshes, a clipping path's own fill and its stroke, and the artboards' names are read too. `editingData: "always"` (`convert --editing-data`) opens a `.ai` or an Illustrator EPS from its editing data even where it draws differently from its page, which can be older, with a note. Type is made from the file's text document, whole and editable, shown or not: point type (its frame's matrix, which newer files leave out when it is neither turned nor scaled, read as none), area type (a story in several frames as threaded type) and type on a path, with its characters, fonts, size, leading, tracking, scaling, baseline shift, fill and stroke (grey, RGB or CMYK), alignment, indents and paragraph spacing (a character attribute a run doesn't set comes from its character style, then the style that one is based on, then the document's defaults); area type keeps its frame's Area Type Options rows and columns, the gutter between rows and the gutter between columns, and its first baseline (Fixed, Cap Height, Leading or x Height) with its minimum; type on a path keeps its frame's Type on a Path effect, Align to Path and Flip (a frame whose options set key 6 to true keeps their defaults), and the first frame's Spacing is fitted from the file's layout, not read from the dialog's value; the distance from the path under Align to Path comes from VectorCraft's font metrics, and on Arial at 24 pt it differs from the distance the file keeps by 1.1 pt (Ascender), 0.3 pt (Center) and 2.7 pt (Descender); the fit under Gravity does not reproduce the file's layout, and its glyphs can lie up to 7.4 pt from the positions the file keeps; the page's drawing of it (often in pieces) is left out. Vertical type keeps its orientation, its columns from right to left, and a run set to Tate-chu-yoko keeps that setting. When it makes the type of every text object that shows, the page's type is only compared with it, each text object keeps its own group, frame and layer, and a line break inside a paragraph stays one. Where it can't make a text object's type (a frame of another kind), that text object takes the page's type where the text document puts its lines, or else what is left of the page's type in painting order (no group around it); the page's type where made type draws stays that type's, and hidden type is left out with a note. Text wrap is read too: an object whose dictionary has the wrap options (offset and Invert Wrap), or any graphic of a legacy wraparound group (`*w` … `*W`, before or after its type, with no offset), is a wrap object, and area type below it in its layer flows around it when the file opens, threaded type included (#2763). Kerning, underline and the other character and paragraph options aren't read yet. A `.ai` saved without PDF compatibility (a placeholder page) opens from its editing data alone, its point type, area type and type on a path made from the text document. A `.ai` whose PDF part shows only text isn't taken for that placeholder when its page states an art, trim or bleed box: it opens from its editing data alone where that reads, else from its PDF part (#1387). A `.ai` whose PDF part draws nothing at all (an empty page, with or without those boxes) opens from its editing data alone where that reads too: there is no page art to check its layers against, and the empty page would lose all of it. Text objects with an appearance keep their own text and frame, type of hidden layers stays hidden, and type on later artboards lands on its text objects (#1391); area type whose frame is too short for a line of the fallback font shows its first line (the frame is made taller, with a warning in the open result) (#1392). In a PDF saved with some of its artboards, the type on each page after the first goes to an artboard of that page's size after the previous page's artboard, and only to one that leaves an artboard for each later page; the first page's artboard also leaves one for each later page. When several artboards fit a later page, VectorCraft takes the one whose art draws most like the page, and compares at most 16 artboards for all the later pages together. Symbols are read into the Symbols panel, in the file's symbol list order, with their art, their registration point and the instances in their art, and their instances come in live with their matrix, name, hide, lock, opacity, blending mode, isolation, knockout and opacity mask; a symbol whose art isn't read whole (type, placed files, an image it leaves out or anything else it doesn't read) isn't kept. A file that comes in as its page or PDF part because its editing data has no layers (a symbol library), or because its layers can't be read or used (any of the cases below), gets the symbols its editing data defines before its first layer, in the symbol list's order, while its page art stays as the page draws it. Instances of symbols it can't read, instances with appearance changes of their own (a dynamic symbol's), pattern fills whose pattern it can't read, placed files the data keeps no preview of and anything else it doesn't read make the file come in as its page (on a layer that doesn't show, they are left out of that layer), with a warning saying what. An unread object on a shown layer whose box can be found instead comes in as a group of the page's objects inside that box, named after it, with the rest of the layers and groups kept and a warning listing what came in as the page's art; the file still comes in as its page when live art or another unread object shares that page art, when the object spans two artboards or lies on an artboard that has no page, when no page art lies inside its box, when that page art is inside a page group with a clip, mask, transparency or appearance, when the object holds a frame of threaded type, when the page art uses a pattern, image or swatch that the page lacks or that differs from the editing data's, or when the page art doesn't carry its transparency (#4806). The layers' art is also compared with the page (by lightness, so colours written as their CMYK equivalents compare alike, and by whether a pixel is inked at all); the page is used when they differ by more than 5% (away from type, when all the type is made from the text document), where one draws an object the other doesn't (away from type, whose lines may be laid out afresh), and when an EPS whose page is its art's box has art printed outside it (#735). A `.ai` is not compared with its page, and its PDF part is not imported, when its text document makes the type of every text object (at least one shows, and none is drawn by the appearance of the layer or group it is in), no object is unread, no placed file has a second placement, no live envelope keeps its drawn art, it declares no large canvas, and its artboards are its pages' trim boxes one for one, in points or in the first page's `UserUnit`: it opens from its layers, with the bleed of its pages' bleed boxes. When they differ because some objects draw differently (an object in another color, or one that only one of them draws), each top-level object of the layers that holds such a difference comes in as a group of the page's objects inside its box and the layers stay, with a warning that names the layer or sublayer of each such object as the Layers panel shows it (`` for one without a name) and how many came in from it; an export or Save As to the file's own path then needs `acknowledgeLoss: true`, and a wrap object replaced this way keeps its wrap options. The file still comes in as its page when only edges or type differ, when a difference lies outside every top-level object, when more than half of the top-level objects or more than 64 hold one, when such an object is or holds type or holds a wrap object inside a group, when the page's type of a text object that stays lies inside such an object's box, when the page's art can't replace such an object for one of the reasons above for an unread object, when matching the differences to the objects would take too much work, or when the result still differs from the page. `editingData: "always"` keeps the objects as read, and placed files at the other point their data gives are tried as well. An EPS whose page is its art's box keeps that page when it comes in through its layers, not the editing data's artboard, and a text object counts as drawn by the page by its colours, not only its lightness, so a red stroke stacked over a dark red one stays (#1102). `textAs: "outlines"` opens a `.ai` whose type shows from its PDF part, which has the outlines; `editingData: false` opens any of them as its page or PDF part only, for print pipelines (#735). A file whose import left something out (hidden text, art or layers) is never written over by an export or Save As to its own path unless `acknowledgeLoss: true` (`convert --replace-lossy`), so the original keeps what the document lacks. Where a `.ai` can't be read that way (or its layers are turned off in the import options), we read its PDF-compatible part, so Illustrator-only live objects arrive as appearance, and plain groups arrive ungrouped: that part draws each object on its own and marks only layers, clipping groups and groups with opacity, blending or a mask (those come back). Art outside the artboards isn't in that part at all (Illustrator's PDF part leaves it out), so it doesn't open; the import says so in a note (#472). Its pages still go where the editing data puts the artboards (a grid stays a grid), with the art on each, when it has as many artboards as pages, of the same sizes (#1068); the page of the artboard `CropAreaActive` names is then the active artboard. A `.ai` whose editing data has artboards but no layers also opens on that page under the same check, and its pages stay where they are. A `.ai` whose PDF part's pages are cropped to its art (each page the box of all the art, which is the editing data's bounding box) instead of its artboards opens from its layers, with its own artboards, where they draw as that page does. Its pages are laid out so that art one of them draws past its artboard (notes beside a design, which the part doesn't clip) doesn't lie over the page before it in the row, where it was taken for that page's art: the check against the editing data then found art its layers don't have there and opened the file as its PDF part, without the art outside the artboards. That part also has no paragraphs: area type is written line by line, and comes back as area type only where its font is installed and lays the lines out with the same breaks, else as a point type object per line (#508). Art off the page that a PDF does draw opens on the pasteboard. +the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPrivateData` streams, a PDF's `AIPDFPrivateData` streams when it was saved with the editing data (#868), Zstandard or zlib, the EPS's in ASCII85) is not documented as a whole. VectorCraft reads its structure: layers and sublayers (names, order, visibility, lock, printing, preview, dimming, colour), groups as they were nested, compound paths (painted by their last path, or the last painted one, with the paths of groups inside them kept as their paths: #1410), clipping groups and layer clipping (several clipping paths in a clipping group, a layer or an opacity mask's art clip to their intersection, and a compound path of clipping paths stays one clip; a filled clipping path keeps its fill, which paints under the art it clips, except the path of a mask that covers several layers, whose fill is read as a path of its own in the layer that holds the mask's path, and a compound clipping path whose subpaths paint differently, whose filled subpaths are read as a compound path of their own under the art it clips; clipping paths that would nest the art past the document's 32 levels stop the editing data from being read: #5789), object names, hidden and locked objects, fills and strokes (grey, CMYK, RGB, named and spot colours, spot colours defined in Lab with their Lab values (#1032), global process colours as global swatches at their tint; a stroke of width 0, `0 w`, on a path, a gradient-filled path, legacy type or in an art style comes in as a 0.25 pt hairline, as from PDF: #1671), linear and radial gradients, pattern fills with their patterns as pattern swatches (#1025), gradient and pattern names in the file up to their first newline (a name that another swatch, or a gradient or pattern defined earlier, already has is numbered "Sky 2"; a name without a newline is kept, and when another swatch has it, the pattern's swatch, or a named color sharing a gradient or pattern swatch's name, is numbered the same way), opacity, blend modes, isolation and knockout, embedded images where their matrix puts them, rotated ones too (#2269), with their alpha channel (CMYK images keep their ink amounts, as they do when a `.ai` opens from its PDF part, and an alpha channel that hides part of a CMYK image becomes a grayscale opacity mask), placed (linked) files as linked images in their place, each showing the low-resolution preview the file keeps of it until its file is found or relinked in the Links panel (#1264), with the folder the file keeps relative to the document's, where the link is looked for when its path is gone, placed PDF and `.ai` pages as placed documents of the [page and Crop to box](https://helpx.adobe.com/illustrator/desktop/add-and-import-files/import-other-file-types/adobe-pdf-placement-options.html) their dictionary names (`AI11 PlacedPDFPageIndex`, `AI11 PlacedPDFCropTo`; a value VectorCraft doesn't read, or a binding to an image variable, leaves a linked image), guides, and every artboard where it is, with its center mark, cross hairs, video safe areas and video ruler pixel aspect ratio. VectorCraft reads `CropAreaActive`, an `/Int` in the dictionary that holds the artboard array, as the index of the active artboard in that array, and the file opens with that artboard active; without it, or when it names no artboard that opens, the first artboard is active. It also reads the Document Raster Effects Settings (resolution, color model, background, anti-alias, clipping mask, Add Around Object and Preserve Spot Colors), the author from the header's `%%For` (an EPS's own header when the editing data's has none), swatches for the header's `%%CMYKCustomColor` and `%%RGBCustomColor` colors that the document has none of, and `%%TemplateBox` in older files; a setting with a value of another type or out of range keeps its default. Objects on Path groups open live, with their path, their objects in order along it, Pivot and Rotate; one with a dragged spacing or other options opens as a plain group. A gradient with stops in the middle runs its whole ramp across its shape, and a gradient object does not pass its fill to the next path (#1390). An object whose art style is one fill and one stroke at most (solid colours, its own gradient, a variable-width profile on a Stroke Offset stroke or written as each side's widths) with Drop Shadow, Inner Glow, Outer Glow, Feather, Round Corners, Scribble, Offset Path, Outline Object, Outline Stroke, Convert to Shape, Tweak (only when it moves nothing), Pucker & Bloat at 0 or Bas Relief (read from its settings block: [raster effects reference](https://helpx.adobe.com/illustrator/desktop/special-effects-styles/apply-filter-effects/raster-effects-reference.html), [Photoshop File Formats Specification](https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/), [scripting the filter gallery](https://community.adobe.com/questions-712/scripting-support-for-the-filter-gallery-1064646)) comes in as itself with them and their saved settings. A path, compound path or group (not a clipping group) whose only effect is Transform (not with copies or Random) or a Free Distort whose corners make a parallelogram comes in as itself with it too, when neither it nor anything in it paints with a gradient or pattern and everything in it is a path, compound path or group with no opacity mask and no effects of its own; Transform that scales, rotates or reflects needs nothing in it to be stroked. Twist as the only effect is read the same way on an unstroked path or compound path whose segments are all straight. One with more, other effects (Zig Zag, a Pucker & Bloat of another amount, a Free Distort with other corners), an effect setting that is not read (an unknown key, a missing or out-of-range value, a Scribble with a color of its own), a variable-width stroke with one centered width array, dashes aligned to corners, a brush other than a Calligraphic or Art one, a Calligraphic brush with settings of its own or an angle that follows the path, or an Art brush other than the ones described next comes in as its drawn look (a group named after it). Objects in a symbol defined before the file's art styles section, as newer files write it, take the same styles as the layers' objects. A path with a Calligraphic brush otherwise comes in as the path, the brush on its stroke at the stroke's weight, which scales the brush (#372), and so does a path with an Art brush, whose weight scales the art across the path, when all of these hold: the brush stretches its art along the path, unflipped, at 100% in Tints; the path's settings are the brush's own, with no entry that isn't read, no variable width and no effects; its stroke isn't a swatch; and the art is paths, compound paths and groups in solid fills, with no strokes, masks or effects, and has some length along the path. A stroke takes the brush the Brushes panel keeps under its name, and one whose name the panel doesn't keep comes in as its drawn look. Type with an Art brush keeps its drawn look under the type. A Calligraphic brush's Pressure, Tilt, Bearing and Rotation come in as those channels (a 6D Art Pen's brushes vary with the pressure, the bearing and the barrel's rotation); its Stylus Wheel varies at random. The file's Calligraphic, Scatter, Art and Bristle brushes replace the default ones in the Brushes panel, in the file's order, also when the file comes in as its PDF part (a brush library has no layers); pattern brushes and brushes it can't read are left out with a note. When the file opens from its layers, the spot colors in the art of each brush that comes in become swatches, and that art's colors count toward the document's color mode, as a pattern's do. The swatches take at most 20,000 color names in all across the brush art, and each brush-art section keeps at most 20,000 names. Each name keeps its first definition, with the color operators' names before the gradient stops' names. A spot color past those limits makes no swatch, and its color still counts toward the color mode. A spot color that the page's art, a pattern or a symbol also defines keeps the value defined there. Type keeps its art style as a live appearance whatever its stack (an Art brush aside), so it can be retyped: each fill and stroke of the style's stack (solid colours, gradients and pattern fills, placed as the drawn look places them) with its own opacity, blend mode and effects, the effects on the whole object, and where the characters paint among them; the effects read are Warp, Offset Path, Outline Object, Outline Stroke, Transform (with copies, Transform Objects, Transform Patterns and Scale Strokes & Effects; not with Random), Free Distort, Pucker & Bloat, Twist, Zig Zag, Tweak (only when it moves nothing, the file holding none of its random outline), Drop Shadow, Inner Glow, Outer Glow, Feather, Round Corners, Scribble, Gaussian Blur, Bas Relief (its detail, smoothness and light direction from its settings block, or the effect's defaults when the style holds no settings block; a settings block that isn't read keeps the drawn look) and Convert to Shape, and a style with others, or with an effect setting that isn't read, keeps the type's drawn look. Convert to Shape keeps its Rectangle, Rounded Rectangle or Ellipse, its Absolute or Relative size (a relative size in the file is added to each side) and its corner radius; a relative Ellipse, or a setting that isn't read, keeps the drawn look. The page's drawing of such a style isn't held to look like VectorCraft's where its drawn look lay. Gradients take their matrix (`Xm`, radials and skewed ones too), focal point and the order of stops sharing a ramp point; gradient meshes, a clipping path's own fill and its stroke, and the artboards' names are read too. `editingData: "always"` (`convert --editing-data`) opens a `.ai` or an Illustrator EPS from its editing data even where it draws differently from its page, which can be older, with a note. Type is made from the file's text document, whole and editable, shown or not: point type (its frame's matrix, which newer files leave out when it is neither turned nor scaled, read as none), area type (a story in several frames as threaded type) and type on a path, with its characters, fonts, size, leading, tracking, scaling, baseline shift, fill and stroke (grey, RGB or CMYK), alignment, indents and paragraph spacing (a character attribute a run doesn't set comes from its character style, then the style that one is based on, then the document's defaults); area type keeps its frame's Area Type Options rows and columns, the gutter between rows and the gutter between columns, and its first baseline (Fixed, Cap Height, Leading or x Height) with its minimum; type on a path keeps its frame's Type on a Path effect, Align to Path and Flip (a frame whose options set key 6 to true keeps their defaults), and the first frame's Spacing is fitted from the file's layout, not read from the dialog's value; the distance from the path under Align to Path comes from VectorCraft's font metrics, and on Arial at 24 pt it differs from the distance the file keeps by 1.1 pt (Ascender), 0.3 pt (Center) and 2.7 pt (Descender); the fit under Gravity does not reproduce the file's layout, and its glyphs can lie up to 7.4 pt from the positions the file keeps; the page's drawing of it (often in pieces) is left out. Vertical type keeps its orientation, its columns from right to left, and a run set to Tate-chu-yoko keeps that setting. When it makes the type of every text object that shows, the page's type is only compared with it, each text object keeps its own group, frame and layer, and a line break inside a paragraph stays one. Where it can't make a text object's type (a frame of another kind), that text object takes the page's type where the text document puts its lines, or else what is left of the page's type in painting order (no group around it); the page's type where made type draws stays that type's, and hidden type is left out with a note. Text wrap is read too: an object whose dictionary has the wrap options (offset and Invert Wrap), or any graphic of a legacy wraparound group (`*w` … `*W`, before or after its type, with no offset), is a wrap object, and area type below it in its layer flows around it when the file opens, threaded type included (#2763). Kerning, underline and the other character and paragraph options aren't read yet. A `.ai` saved without PDF compatibility (a placeholder page) opens from its editing data alone, its point type, area type and type on a path made from the text document. A `.ai` whose PDF part shows only text isn't taken for that placeholder when its page states an art, trim or bleed box: it opens from its editing data alone where that reads, else from its PDF part (#1387). A `.ai` whose PDF part draws nothing at all (an empty page, with or without those boxes) opens from its editing data alone where that reads too: there is no page art to check its layers against, and the empty page would lose all of it. Text objects with an appearance keep their own text and frame, type of hidden layers stays hidden, and type on later artboards lands on its text objects (#1391); area type whose frame is too short for a line of the fallback font shows its first line (the frame is made taller, with a warning in the open result) (#1392). In a PDF saved with some of its artboards, the type on each page after the first goes to an artboard of that page's size after the previous page's artboard, and only to one that leaves an artboard for each later page; the first page's artboard also leaves one for each later page. When several artboards fit a later page, VectorCraft takes the one whose art draws most like the page, and compares at most 16 artboards for all the later pages together. Symbols are read into the Symbols panel, in the file's symbol list order, with their art, their registration point and the instances in their art, and their instances come in live with their matrix, name, hide, lock, opacity, blending mode, isolation, knockout and opacity mask; a symbol whose art isn't read whole (type, placed files, an image it leaves out or anything else it doesn't read) isn't kept. A file that comes in as its page or PDF part because its editing data has no layers (a symbol library), or because its layers can't be read or used (any of the cases below), gets the symbols its editing data defines before its first layer, in the symbol list's order, while its page art stays as the page draws it. Instances of symbols it can't read, instances with appearance changes of their own (a dynamic symbol's), pattern fills whose pattern it can't read, placed files the data keeps no preview of and anything else it doesn't read make the file come in as its page (on a layer that doesn't show, they are left out of that layer), with a warning saying what. An unread object on a shown layer whose box can be found instead comes in as a group of the page's objects inside that box, named after it, with the rest of the layers and groups kept and a warning listing what came in as the page's art; the file still comes in as its page when live art or another unread object shares that page art, when the object spans two artboards or lies on an artboard that has no page, when no page art lies inside its box, when that page art is inside a page group with a clip, mask, transparency or appearance, when the object holds a frame of threaded type, when the page art uses a pattern, image or swatch that the page lacks or that differs from the editing data's, or when the page art doesn't carry its transparency (#4806). The layers' art is also compared with the page (by lightness, so colours written as their CMYK equivalents compare alike, and by whether a pixel is inked at all); the page is used when they differ by more than 5% (away from type, when all the type is made from the text document), where one draws an object the other doesn't (away from type, whose lines may be laid out afresh), and when an EPS whose page is its art's box has art printed outside it (#735). A `.ai` is not compared with its page, and its PDF part is not imported, when its text document makes the type of every text object (at least one shows, and none is drawn by the appearance of the layer or group it is in), no object is unread, no placed file has a second placement, no live envelope keeps its drawn art, it declares no large canvas, and its artboards are its pages' trim boxes one for one, in points or in the first page's `UserUnit`: it opens from its layers, with the bleed of its pages' bleed boxes. When they differ because some objects draw differently (an object in another color, or one that only one of them draws), each top-level object of the layers that holds such a difference comes in as a group of the page's objects inside its box and the layers stay, with a warning that names the layer or sublayer of each such object as the Layers panel shows it (`` for one without a name) and how many came in from it; an export or Save As to the file's own path then needs `acknowledgeLoss: true`, and a wrap object replaced this way keeps its wrap options. The file still comes in as its page when only edges or type differ, when a difference lies outside every top-level object, when more than half of the top-level objects or more than 64 hold one, when such an object is or holds type or holds a wrap object inside a group, when the page's type of a text object that stays lies inside such an object's box, when the page's art can't replace such an object for one of the reasons above for an unread object, when matching the differences to the objects would take too much work, or when the result still differs from the page. `editingData: "always"` keeps the objects as read, and placed files at the other point their data gives are tried as well. An EPS whose page is its art's box keeps that page when it comes in through its layers, not the editing data's artboard, and a text object counts as drawn by the page by its colours, not only its lightness, so a red stroke stacked over a dark red one stays (#1102). `textAs: "outlines"` opens a `.ai` whose type shows from its PDF part, which has the outlines; `editingData: false` opens any of them as its page or PDF part only, for print pipelines (#735). A file whose import left something out (hidden text, art or layers) is never written over by an export or Save As to its own path unless `acknowledgeLoss: true` (`convert --replace-lossy`), so the original keeps what the document lacks. Where a `.ai` can't be read that way (or its layers are turned off in the import options), we read its PDF-compatible part, so Illustrator-only live objects arrive as appearance, and plain groups arrive ungrouped: that part draws each object on its own and marks only layers, clipping groups and groups with opacity, blending or a mask (those come back). Art outside the artboards isn't in that part at all (Illustrator's PDF part leaves it out), so it doesn't open; the import says so in a note (#472). Its pages still go where the editing data puts the artboards (a grid stays a grid), with the art on each, when it has as many artboards as pages, of the same sizes (#1068); the page of the artboard `CropAreaActive` names is then the active artboard. A `.ai` whose editing data has artboards but no layers also opens on that page under the same check, and its pages stay where they are. A `.ai` whose PDF part's pages are cropped to its art (each page the box of all the art, which is the editing data's bounding box) instead of its artboards opens from its layers, with its own artboards, where they draw as that page does. Its pages are laid out so that art one of them draws past its artboard (notes beside a design, which the part doesn't clip) doesn't lie over the page before it in the row, where it was taken for that page's art: the check against the editing data then found art its layers don't have there and opened the file as its PDF part, without the art outside the artboards. That part also has no paragraphs: area type is written line by line, and comes back as area type only where its font is installed and lays the lines out with the same breaks, else as a point type object per line (#508). Art off the page that a PDF does draw opens on the pasteboard. the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPrivateData` streams, a PDF's `AIPDFPrivateData` streams when it was saved with the editing data (#868), Zstandard or zlib, the EPS's in ASCII85) is not documented as a whole. VectorCraft reads its structure: layers and sublayers (names, order, visibility, lock, printing, preview, dimming, colour), groups as they were nested, compound paths (painted by their last path, or the last painted one, with the paths of groups inside them kept as their paths: #1410), clipping groups and layer clipping (several clipping paths in a clipping group, a layer or an opacity mask's art clip to their intersection, and a compound path of clipping paths stays one clip; a filled clipping path keeps its fill, which paints under the art it clips, except the path of a mask that covers several layers, whose fill is read as a path of its own in the layer that holds the mask's path, and a compound clipping path whose subpaths paint differently, whose filled subpaths are read as a compound path of their own under the art it clips; clipping paths that would nest the art past the document's 32 levels stop the editing data from being read: #5789), object names, hidden and locked objects, fills and strokes (grey, CMYK, RGB, named and spot colours, spot colours defined in Lab with their Lab values (#1032), global process colours as global swatches at their tint; a stroke of width 0, `0 w`, on a path, a gradient-filled path, legacy type or in an art style comes in as a 0.25 pt hairline, as from PDF: #1671), linear and radial gradients, pattern fills with their patterns as pattern swatches (#1025), gradient and pattern names in the file up to their first newline (a name that another swatch, or a gradient or pattern defined earlier, already has is numbered "Sky 2"; a name without a newline is kept, and when another swatch has it, the pattern's swatch, or a named color sharing a gradient or pattern swatch's name, is numbered the same way), opacity, blend modes, isolation and knockout, embedded images where their matrix puts them, rotated ones too (#2269), with their alpha channel (CMYK images keep their ink amounts, as they do when a `.ai` opens from its PDF part, and an alpha channel that hides part of a CMYK image becomes a grayscale opacity mask), placed (linked) files as linked images in their place, each showing the low-resolution preview the file keeps of it until its file is found or relinked in the Links panel (#1264), with the folder the file keeps relative to the document's, where the link is looked for when its path is gone, guides, and every artboard where it is, with its center mark, cross hairs, video safe areas and video ruler pixel aspect ratio. VectorCraft reads `CropAreaActive`, an `/Int` in the dictionary that holds the artboard array, as the index of the active artboard in that array, and the file opens with that artboard active; without it, or when it names no artboard that opens, the first artboard is active. It also reads the Document Raster Effects Settings (resolution, color model, background, anti-alias, clipping mask, Add Around Object and Preserve Spot Colors), the author from the header's `%%For` (an EPS's own header when the editing data's has none), swatches for the header's `%%CMYKCustomColor` and `%%RGBCustomColor` colors that the document has none of, and `%%TemplateBox` in older files; a setting with a value of another type or out of range keeps its default. Objects on Path groups open live, with their path, their objects in order along it, Pivot and Rotate; one with a dragged spacing or other options opens as a plain group. A gradient with stops in the middle runs its whole ramp across its shape, and a gradient object does not pass its fill to the next path (#1390). An object whose art style is one fill and one stroke at most (solid colours, its own gradient, a variable-width profile) with Drop Shadow, Inner Glow, Outer Glow, Feather, Round Corners, Scribble, Offset Path, Outline Object, Outline Stroke, Convert to Shape, Tweak (only when it moves nothing) or Pucker & Bloat at 0 comes in as itself with them and their saved settings. A path, compound path or group (not a clipping group) whose only effect is Transform (not with copies or Random) or a Free Distort whose corners make a parallelogram comes in as itself with it too, when neither it nor anything in it paints with a gradient or pattern and everything in it is a path, compound path or group with no opacity mask and no effects of its own; Transform that scales, rotates or reflects needs nothing in it to be stroked. Twist as the only effect is read the same way on an unstroked path or compound path whose segments are all straight. One with more, other effects (Zig Zag, a Pucker & Bloat of another amount, a Free Distort with other corners), an effect setting that is not read (an unknown key, a missing or out-of-range value, a Scribble with a color of its own), dashes aligned to corners, a brush other than a Calligraphic or Art one, a Calligraphic brush with settings of its own or an angle that follows the path, or an Art brush other than the ones described next comes in as its drawn look (a group named after it). Objects in a symbol defined before the file's art styles section, as newer files write it, take the same styles as the layers' objects. A path with a Calligraphic brush otherwise comes in as the path, the brush on its stroke at the stroke's weight, which scales the brush (#372), and so does a path with an Art brush, whose weight scales the art across the path, when all of these hold: the brush stretches its art along the path, unflipped, at 100% in Tints; the path's settings are the brush's own, with no entry that isn't read, no variable width and no effects; its stroke isn't a swatch; and the art is paths, compound paths and groups in solid fills, with no strokes, masks or effects, and has some length along the path. A stroke takes the brush the Brushes panel keeps under its name, and one whose name the panel doesn't keep comes in as its drawn look. Type with an Art brush keeps its drawn look under the type. A Calligraphic brush's Pressure, Tilt, Bearing and Rotation come in as those channels (a 6D Art Pen's brushes vary with the pressure, the bearing and the barrel's rotation); its Stylus Wheel varies at random. The file's Calligraphic, Scatter, Art and Bristle brushes replace the default ones in the Brushes panel, in the file's order, also when the file comes in as its PDF part (a brush library has no layers); pattern brushes and brushes it can't read are left out with a note. When the file opens from its layers, the spot colors in the art of each brush that comes in become swatches, and that art's colors count toward the document's color mode, as a pattern's do. The swatches take at most 20,000 color names in all across the brush art, and each brush-art section keeps at most 20,000 names. Each name keeps its first definition, with the color operators' names before the gradient stops' names. A spot color past those limits makes no swatch, and its color still counts toward the color mode. A spot color that the page's art, a pattern or a symbol also defines keeps the value defined there. Type keeps its art style as a live appearance whatever its stack (an Art brush aside), so it can be retyped: each fill and stroke of the style's stack (solid colours, gradients and pattern fills, placed as the drawn look places them) with its own opacity, blend mode and effects, the effects on the whole object, and where the characters paint among them; the effects read are Warp, Offset Path, Outline Object, Outline Stroke, Transform (with copies, Transform Objects, Transform Patterns and Scale Strokes & Effects; not with Random), Free Distort, Pucker & Bloat, Twist, Zig Zag, Tweak (only when it moves nothing, the file holding none of its random outline), Drop Shadow, Inner Glow, Outer Glow, Feather, Round Corners, Scribble, Gaussian Blur, Bas Relief (its settings at their defaults) and Convert to Shape, and a style with others, or with an effect setting that isn't read, keeps the type's drawn look. Convert to Shape keeps its Rectangle, Rounded Rectangle or Ellipse, its Absolute or Relative size (a relative size in the file is added to each side) and its corner radius; a relative Ellipse, or a setting that isn't read, keeps the drawn look. The page's drawing of such a style isn't held to look like VectorCraft's where its drawn look lay. Gradients take their matrix (`Xm`, radials and skewed ones too), focal point and the order of stops sharing a ramp point; gradient meshes, a clipping path's own fill and its stroke, and the artboards' names are read too. `editingData: "always"` (`convert --editing-data`) opens a `.ai` or an Illustrator EPS from its editing data even where it draws differently from its page, which can be older, with a note. Type is made from the file's text document, whole and editable, shown or not: point type (its frame's matrix, which newer files leave out when it is neither turned nor scaled, read as none), area type (a story in several frames as threaded type) and type on a path, with its characters, fonts, size, leading, tracking, scaling, baseline shift, fill and stroke (grey, RGB or CMYK), alignment, indents and paragraph spacing (a character attribute a run doesn't set comes from its character style, then the style that one is based on, then the document's defaults); area type keeps its frame's Area Type Options rows and columns, the gutter between rows and the gutter between columns, and its first baseline (Fixed, Cap Height, Leading or x Height) with its minimum; type on a path keeps its frame's Type on a Path effect, Align to Path and Flip (a frame whose options set key 6 to true keeps their defaults), and the first frame's Spacing is fitted from the file's layout, not read from the dialog's value; the distance from the path under Align to Path is measured for each character at its size and vertical scale (in vertical type, its horizontal scale), from the top of its font's em box (Ascender), half the height of the top of its font's H (Center) or the bottom of its font's bounding box (Descender); the fit under Gravity does not reproduce the file's layout, and its glyphs can lie up to 7.4 pt from the positions the file keeps; the page's drawing of it (often in pieces) is left out. Vertical type keeps its orientation, its columns from right to left, and a run set to Tate-chu-yoko keeps that setting. When it makes the type of every text object that shows, the page's type is only compared with it, each text object keeps its own group, frame and layer, and a line break inside a paragraph stays one. Where it can't make a text object's type (a frame of another kind), that text object takes the page's type where the text document puts its lines, or else what is left of the page's type in painting order (no group around it); the page's type where made type draws stays that type's, and hidden type is left out with a note. Text wrap is read too: an object whose dictionary has the wrap options (offset and Invert Wrap), or any graphic of a legacy wraparound group (`*w` … `*W`, before or after its type, with no offset), is a wrap object, and area type below it in its layer flows around it when the file opens, threaded type included (#2763). Kerning, underline and the other character and paragraph options aren't read yet. A `.ai` saved without PDF compatibility (a placeholder page) opens from its editing data alone, its point type, area type and type on a path made from the text document. A `.ai` whose PDF part shows only text isn't taken for that placeholder when its page states an art, trim or bleed box: it opens from its editing data alone where that reads, else from its PDF part (#1387). A `.ai` whose PDF part draws nothing at all (an empty page, with or without those boxes) opens from its editing data alone where that reads too: there is no page art to check its layers against, and the empty page would lose all of it. Text objects with an appearance keep their own text and frame, type of hidden layers stays hidden, and type on later artboards lands on its text objects (#1391); area type whose frame is too short for a line of the fallback font shows its first line (the frame is made taller, with a warning in the open result) (#1392). In a PDF saved with some of its artboards, the type on each page after the first goes to an artboard of that page's size after the previous page's artboard, and only to one that leaves an artboard for each later page; the first page's artboard also leaves one for each later page. When several artboards fit a later page, VectorCraft takes the one whose art draws most like the page, and compares at most 16 artboards for all the later pages together. Symbols are read into the Symbols panel, in the file's symbol list order, with their art, their registration point and the instances in their art, and their instances come in live with their matrix, name, hide, lock, opacity, blending mode, isolation, knockout and opacity mask; a symbol whose art isn't read whole (type, placed files, an image it leaves out or anything else it doesn't read) isn't kept. A file that comes in as its page or PDF part because its editing data has no layers (a symbol library), or because its layers can't be read or used (any of the cases below), gets the symbols its editing data defines before its first layer, in the symbol list's order, while its page art stays as the page draws it. Instances of symbols it can't read, instances with appearance changes of their own (a dynamic symbol's), pattern fills whose pattern it can't read, placed files the data keeps no preview of and anything else it doesn't read make the file come in as its page (on a layer that doesn't show, they are left out of that layer), with a warning saying what. An unread object on a shown layer whose box can be found instead comes in as a group of the page's objects inside that box, named after it, with the rest of the layers and groups kept and a warning listing what came in as the page's art; the file still comes in as its page when live art or another unread object shares that page art, when the object spans two artboards or lies on an artboard that has no page, when no page art lies inside its box, when that page art is inside a page group with a clip, mask, transparency or appearance, when the object holds a frame of threaded type, when the page art uses a pattern, image or swatch that the page lacks or that differs from the editing data's, or when the page art doesn't carry its transparency (#4806). The layers' art is also compared with the page (by lightness, so colours written as their CMYK equivalents compare alike, and by whether a pixel is inked at all); the page is used when they differ by more than 5% (away from type, when all the type is made from the text document), where one draws an object the other doesn't (away from type, whose lines may be laid out afresh), and when an EPS whose page is its art's box has art printed outside it (#735). A `.ai` is not compared with its page, and its PDF part is not imported, when its text document makes the type of every text object (at least one shows, and none is drawn by the appearance of the layer or group it is in), no object is unread, no placed file has a second placement, no live envelope keeps its drawn art, it declares no large canvas, and its artboards are its pages' trim boxes one for one, in points or in the first page's `UserUnit`: it opens from its layers, with the bleed of its pages' bleed boxes. When they differ because some objects draw differently (an object in another color, or one that only one of them draws), each top-level object of the layers that holds such a difference comes in as a group of the page's objects inside its box and the layers stay, with a warning that names the layer or sublayer of each such object as the Layers panel shows it (`` for one without a name) and how many came in from it; an export or Save As to the file's own path then needs `acknowledgeLoss: true`, and a wrap object replaced this way keeps its wrap options. The file still comes in as its page when only edges or type differ, when a difference lies outside every top-level object, when more than half of the top-level objects or more than 64 hold one, when such an object is or holds type or holds a wrap object inside a group, when the page's type of a text object that stays lies inside such an object's box, when the page's art can't replace such an object for one of the reasons above for an unread object, when matching the differences to the objects would take too much work, or when the result still differs from the page. `editingData: "always"` keeps the objects as read, and placed files at the other point their data gives are tried as well. An EPS whose page is its art's box keeps that page when it comes in through its layers, not the editing data's artboard, and a text object counts as drawn by the page by its colours, not only its lightness, so a red stroke stacked over a dark red one stays (#1102). `textAs: "outlines"` opens a `.ai` whose type shows from its PDF part, which has the outlines; `editingData: false` opens any of them as its page or PDF part only, for print pipelines (#735). A file whose import left something out (hidden text, art or layers) is never written over by an export or Save As to its own path unless `acknowledgeLoss: true` (`convert --replace-lossy`), so the original keeps what the document lacks. Where a `.ai` can't be read that way (or its layers are turned off in the import options), we read its PDF-compatible part, so Illustrator-only live objects arrive as appearance, and plain groups arrive ungrouped: that part draws each object on its own and marks only layers, clipping groups and groups with opacity, blending or a mask (those come back). Art outside the artboards isn't in that part at all (Illustrator's PDF part leaves it out), so it doesn't open; the import says so in a note (#472). Its pages still go where the editing data puts the artboards (a grid stays a grid), with the art on each, when it has as many artboards as pages, of the same sizes (#1068); the page of the artboard `CropAreaActive` names is then the active artboard. A `.ai` whose editing data has artboards but no layers also opens on that page under the same check, and its pages stay where they are. A `.ai` whose PDF part's pages are cropped to its art (each page the box of all the art, which is the editing data's bounding box) instead of its artboards opens from its layers, with its own artboards, where they draw as that page does. Its pages are laid out so that art one of them draws past its artboard (notes beside a design, which the part doesn't clip) doesn't lie over the page before it in the row, where it was taken for that page's art: the check against the editing data then found art its layers don't have there and opened the file as its PDF part, without the art outside the artboards. That part also has no paragraphs: area type is written line by line, and comes back as area type only where its font is installed and lays the lines out with the same breaks, else as a point type object per line (#508). Art off the page that a PDF does draw opens on the pasteboard. the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPrivateData` streams, a PDF's `AIPDFPrivateData` streams when it was saved with the editing data (#868), Zstandard or zlib, the EPS's in ASCII85) is not documented as a whole. VectorCraft reads its structure: layers and sublayers (names, order, visibility, lock, printing, preview, dimming, colour), groups as they were nested, compound paths (painted by their last path, or the last painted one, with the paths of groups inside them kept as their paths: #1410), clipping groups and layer clipping (several clipping paths in a clipping group, a layer or an opacity mask's art clip to their intersection, and a compound path of clipping paths stays one clip; clipping paths that would nest the art past the document's 32 levels stop the editing data from being read: #5789), object names, hidden and locked objects, fills and strokes (grey, CMYK, RGB, named and spot colours, spot colours defined in Lab with their Lab values (#1032), global process colours as global swatches at their tint; a stroke of width 0, `0 w`, on a path, a gradient-filled path, legacy type or in an art style comes in as a 0.25 pt hairline, as from PDF: #1671), linear and radial gradients, pattern fills with their patterns as pattern swatches (#1025), gradient and pattern names in the file up to their first newline (a name that another swatch, or a gradient or pattern defined earlier, already has is numbered "Sky 2"; a name without a newline is kept, and when another swatch has it, the pattern's swatch, or a named color sharing a gradient or pattern swatch's name, is numbered the same way), opacity, blend modes, isolation and knockout, embedded images where their matrix puts them, rotated ones too (#2269), with their alpha channel (CMYK images keep their ink amounts, as they do when a `.ai` opens from its PDF part, and an alpha channel that hides part of a CMYK image becomes a grayscale opacity mask), placed (linked) files as linked images in their place, each showing the low-resolution preview the file keeps of it until its file is found or relinked in the Links panel (#1264), with the folder the file keeps relative to the document's, where the link is looked for when its path is gone, guides, and every artboard where it is, with its center mark, cross hairs, video safe areas and video ruler pixel aspect ratio. VectorCraft reads `CropAreaActive`, an `/Int` in the dictionary that holds the artboard array, as the index of the active artboard in that array, and the file opens with that artboard active; without it, or when it names no artboard that opens, the first artboard is active. It also reads the Document Raster Effects Settings (resolution, color model, background, anti-alias, clipping mask, Add Around Object and Preserve Spot Colors), the author from the header's `%%For` (an EPS's own header when the editing data's has none), swatches for the header's `%%CMYKCustomColor` and `%%RGBCustomColor` colors that the document has none of, and `%%TemplateBox` in older files; a setting with a value of another type or out of range keeps its default. Objects on Path groups open live, with their path, their objects in order along it, Pivot and Rotate; one with a dragged spacing or other options opens as a plain group. A gradient with stops in the middle runs its whole ramp across its shape, and a gradient object does not pass its fill to the next path (#1390). An object whose art style is one fill and one stroke at most (solid colours, its own gradient, a variable-width profile) with Drop Shadow, Inner Glow, Outer Glow, Feather, Round Corners, Scribble, Offset Path, Outline Object, Outline Stroke, Convert to Shape, Tweak (only when it moves nothing) or Pucker & Bloat at 0 comes in as itself with them and their saved settings. A path, compound path or group (not a clipping group) whose only effect is Transform (not with copies or Random) or a Free Distort whose corners make a parallelogram comes in as itself with it too, when neither it nor anything in it paints with a gradient or pattern and everything in it is a path, compound path or group with no opacity mask and no effects of its own; Transform that scales, rotates or reflects needs nothing in it to be stroked. Twist as the only effect is read the same way on an unstroked path or compound path whose segments are all straight. One with more, other effects (Zig Zag, a Pucker & Bloat of another amount, a Free Distort with other corners), an effect setting that is not read (an unknown key, a missing or out-of-range value, a Scribble with a color of its own), dashes aligned to corners, a brush other than a Calligraphic one, or a Calligraphic brush with settings of its own or an angle that follows the path comes in as its drawn look (a group named after it). Objects in a symbol defined before the file's art styles section, as newer files write it, take the same styles as the layers' objects. A path with a Calligraphic brush otherwise comes in as the path, the brush on its stroke at the stroke's weight, which scales the brush (#372). A Calligraphic brush's Pressure, Tilt, Bearing and Rotation come in as those channels (a 6D Art Pen's brushes vary with the pressure, the bearing and the barrel's rotation); its Stylus Wheel varies at random. The file's Calligraphic, Scatter, Art and Bristle brushes replace the default ones in the Brushes panel, in the file's order, also when the file comes in as its PDF part (a brush library has no layers); pattern brushes and brushes it can't read are left out with a note. Type keeps its art style as a live appearance whatever its stack, so it can be retyped: each fill and stroke of the style's stack (solid colours, gradients and pattern fills, placed as the drawn look places them) with its own opacity, blend mode and effects, the effects on the whole object, and where the characters paint among them; the effects read are Warp, Offset Path, Outline Object, Outline Stroke, Transform (with copies, Transform Objects, Transform Patterns and Scale Strokes & Effects; not with Random), Free Distort, Pucker & Bloat, Twist, Zig Zag, Tweak (only when it moves nothing, the file holding none of its random outline), Drop Shadow, Inner Glow, Outer Glow, Feather, Round Corners, Scribble, Gaussian Blur, Bas Relief (its settings at their defaults) and Convert to Shape, and a style with others, or with an effect setting that isn't read, keeps the type's drawn look. Convert to Shape keeps its Rectangle, Rounded Rectangle or Ellipse, its Absolute or Relative size (a relative size in the file is added to each side) and its corner radius; a relative Ellipse, or a setting that isn't read, keeps the drawn look. The page's drawing of such a style isn't held to look like VectorCraft's where its drawn look lay. Gradients take their matrix (`Xm`, radials and skewed ones too), focal point and the order of stops sharing a ramp point; gradient meshes, a clipping path's own fill and its stroke, and the artboards' names are read too. `editingData: "always"` (`convert --editing-data`) opens a `.ai` or an Illustrator EPS from its editing data even where it draws differently from its page, which can be older, with a note. Type is made from the file's text document, whole and editable, shown or not: point type (its frame's matrix, which newer files leave out when it is neither turned nor scaled, read as none), area type (a story in several frames as threaded type) and type on a path, with its characters, fonts, size, leading, tracking, scaling, baseline shift, fill and stroke (grey, RGB or CMYK), alignment, indents and paragraph spacing (a character attribute a run doesn't set comes from its character style, then the style that one is based on, then the document's defaults); area type keeps its frame's Area Type Options rows and columns, the gutter between rows and the gutter between columns, and its first baseline (Fixed, Cap Height, Leading or x Height) with its minimum; the page's drawing of it (often in pieces) is left out. Vertical type keeps its orientation, its columns from right to left, and a run set to Tate-chu-yoko keeps that setting. When it makes the type of every text object that shows, the page's type is only compared with it, each text object keeps its own group, frame and layer, and a line break inside a paragraph stays one. Where it can't make a text object's type (a frame of another kind), that text object takes the page's type where the text document puts its lines, or else what is left of the page's type in painting order (no group around it); the page's type where made type draws stays that type's, and hidden type is left out with a note. Text wrap is read too: an object whose dictionary has the wrap options (offset and Invert Wrap), or any graphic of a legacy wraparound group (`*w` … `*W`, before or after its type, with no offset), is a wrap object, and area type below it in its layer flows around it when the file opens, threaded type included (#2763). Kerning, underline and the other character and paragraph options aren't read yet. A `.ai` saved without PDF compatibility (a placeholder page) opens from its editing data alone, its point type, area type and type on a path made from the text document. A `.ai` whose PDF part shows only text isn't taken for that placeholder when its page states an art, trim or bleed box: it opens from its editing data alone where that reads, else from its PDF part (#1387). A `.ai` whose PDF part draws nothing at all (an empty page, with or without those boxes) opens from its editing data alone where that reads too: there is no page art to check its layers against, and the empty page would lose all of it. Text objects with an appearance keep their own text and frame, type of hidden layers stays hidden, and type on later artboards lands on its text objects (#1391); area type whose frame is too short for a line of the fallback font shows its first line (the frame is made taller, with a warning in the open result) (#1392). In a PDF saved with some of its artboards, the type on each page after the first goes to an artboard of that page's size after the previous page's artboard, and only to one that leaves an artboard for each later page; the first page's artboard also leaves one for each later page. When several artboards fit a later page, VectorCraft takes the one whose art draws most like the page, and compares at most 16 artboards for all the later pages together. Symbols are read into the Symbols panel, in the file's symbol list order, with their art, their registration point and the instances in their art, and their instances come in live with their matrix, name, hide, lock, opacity, blending mode, isolation, knockout and opacity mask; a symbol whose art isn't read whole (type, placed files, an image it leaves out or anything else it doesn't read) isn't kept. A file that comes in as its page or PDF part because its editing data has no layers (a symbol library), or because its layers can't be read or used (any of the cases below), gets the symbols its editing data defines before its first layer, in the symbol list's order, while its page art stays as the page draws it. Instances of symbols it can't read, instances with appearance changes of their own (a dynamic symbol's), pattern fills whose pattern it can't read, placed files the data keeps no preview of and anything else it doesn't read make the file come in as its page (on a layer that doesn't show, they are left out of that layer), with a warning saying what. An unread object on a shown layer whose box can be found instead comes in as a group of the page's objects inside that box, named after it, with the rest of the layers and groups kept and a warning listing what came in as the page's art; the file still comes in as its page when live art or another unread object shares that page art, when the object spans two artboards or lies on an artboard that has no page, when no page art lies inside its box, when that page art is inside a page group with a clip, mask, transparency or appearance, when the object holds a frame of threaded type, when the page art uses a pattern, image or swatch that the page lacks or that differs from the editing data's, or when the page art doesn't carry its transparency (#4806). The layers' art is also compared with the page (by lightness, so colours written as their CMYK equivalents compare alike, and by whether a pixel is inked at all); the page is used when they differ by more than 5% (away from type, when all the type is made from the text document), where one draws an object the other doesn't (away from type, whose lines may be laid out afresh), and when an EPS whose page is its art's box has art printed outside it (#735). A `.ai` is not compared with its page, and its PDF part is not imported, when its text document makes the type of every text object (at least one shows, and none is drawn by the appearance of the layer or group it is in), no object is unread, no placed file has a second placement, no live envelope keeps its drawn art, it declares no large canvas, and its artboards are its pages' trim boxes one for one, in points or in the first page's `UserUnit`: it opens from its layers, with the bleed of its pages' bleed boxes. When they differ because some objects draw differently (an object in another color, or one that only one of them draws), each top-level object of the layers that holds such a difference comes in as a group of the page's objects inside its box and the layers stay, with a warning that names the layer or sublayer of each such object as the Layers panel shows it (`` for one without a name) and how many came in from it; an export or Save As to the file's own path then needs `acknowledgeLoss: true`, and a wrap object replaced this way keeps its wrap options. The file still comes in as its page when only edges or type differ, when a difference lies outside every top-level object, when more than half of the top-level objects or more than 64 hold one, when such an object is or holds type or holds a wrap object inside a group, when the page's type of a text object that stays lies inside such an object's box, when the page's art can't replace such an object for one of the reasons above for an unread object, when matching the differences to the objects would take too much work, or when the result still differs from the page. `editingData: "always"` keeps the objects as read, and placed files at the other point their data gives are tried as well. An EPS whose page is its art's box keeps that page when it comes in through its layers, not the editing data's artboard, and a text object counts as drawn by the page by its colours, not only its lightness, so a red stroke stacked over a dark red one stays (#1102). `textAs: "outlines"` opens a `.ai` whose type shows from its PDF part, which has the outlines; `editingData: false` opens any of them as its page or PDF part only, for print pipelines (#735). A file whose import left something out (hidden text, art or layers) is never written over by an export or Save As to its own path unless `acknowledgeLoss: true` (`convert --replace-lossy`), so the original keeps what the document lacks. Where a `.ai` can't be read that way (or its layers are turned off in the import options), we read its PDF-compatible part, so Illustrator-only live objects arrive as appearance, and plain groups arrive ungrouped: that part draws each object on its own and marks only layers, clipping groups and groups with opacity, blending or a mask (those come back). Art outside the artboards isn't in that part at all (Illustrator's PDF part leaves it out), so it doesn't open; the import says so in a note (#472). Its pages still go where the editing data puts the artboards (a grid stays a grid), with the art on each, when it has as many artboards as pages, of the same sizes (#1068); the page of the artboard `CropAreaActive` names is then the active artboard. A `.ai` whose editing data has artboards but no layers also opens on that page under the same check, and its pages stay where they are. A `.ai` whose PDF part's pages are cropped to its art (each page the box of all the art, which is the editing data's bounding box) instead of its artboards opens from its layers, with its own artboards, where they draw as that page does. Its pages are laid out so that art one of them draws past its artboard (notes beside a design, which the part doesn't clip) doesn't lie over the page before it in the row, where it was taken for that page's art: the check against the editing data then found art its layers don't have there and opened the file as its PDF part, without the art outside the artboards. That part also has no paragraphs: area type is written line by line, and comes back as area type only where its font is installed and lays the lines out with the same breaks, else as a point type object per line (#508). Art off the page that a PDF does draw opens on the pasteboard. the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPrivateData` streams, a PDF's `AIPDFPrivateData` streams when it was saved with the editing data (#868), Zstandard or zlib, the EPS's in ASCII85) is not documented as a whole. VectorCraft reads its structure: layers and sublayers (names, order, visibility, lock, printing, preview, dimming, colour), groups as they were nested, compound paths (painted by their last path, or the last painted one, with the paths of groups inside them kept as their paths: #1410), clipping groups and layer clipping (several clipping paths in a clipping group, a layer or an opacity mask's art clip to their intersection, and a compound path of clipping paths stays one clip; clipping paths that would nest the art past the document's 32 levels stop the editing data from being read: #5789), object names, hidden and locked objects, fills and strokes (grey, CMYK, RGB, named and spot colours, spot colours defined in Lab with their Lab values (#1032), global process colours as global swatches at their tint; a stroke of width 0, `0 w`, on a path, a gradient-filled path, legacy type or in an art style comes in as a 0.25 pt hairline, as from PDF: #1671), linear and radial gradients, pattern fills with their patterns as pattern swatches (#1025), gradient and pattern names in the file up to their first newline (a name that another swatch, or a gradient or pattern defined earlier, already has is numbered "Sky 2"; a name without a newline is kept, and when another swatch has it, the pattern's swatch, or a named color sharing a gradient or pattern swatch's name, is numbered the same way), opacity, blend modes, isolation and knockout, embedded images where their matrix puts them, rotated ones too (#2269), with their alpha channel (CMYK images keep their ink amounts, as they do when a `.ai` opens from its PDF part, and an alpha channel that hides part of a CMYK image becomes a grayscale opacity mask), placed (linked) files as linked images in their place, each showing the low-resolution preview the file keeps of it until its file is found or relinked in the Links panel (#1264), with the folder the file keeps relative to the document's, where the link is looked for when its path is gone, guides, and every artboard where it is, with its center mark, cross hairs, video safe areas and video ruler pixel aspect ratio. VectorCraft reads `CropAreaActive`, an `/Int` in the dictionary that holds the artboard array, as the index of the active artboard in that array, and the file opens with that artboard active; without it, or when it names no artboard that opens, the first artboard is active. It also reads the Document Raster Effects Settings (resolution, color model, background, anti-alias, clipping mask, Add Around Object and Preserve Spot Colors), the author from the header's `%%For` (an EPS's own header when the editing data's has none), swatches for the header's `%%CMYKCustomColor` and `%%RGBCustomColor` colors that the document has none of, and `%%TemplateBox` in older files; a setting with a value of another type or out of range keeps its default. Objects on Path groups open live, with their path, their objects in order along it, Pivot and Rotate; one with a dragged spacing or other options opens as a plain group. A gradient with stops in the middle runs its whole ramp across its shape, and a gradient object does not pass its fill to the next path (#1390). An object whose art style is one fill and one stroke at most (solid colours, its own gradient, a variable-width profile) with Drop Shadow, Inner Glow, Outer Glow, Feather, Round Corners, Scribble, Offset Path, Outline Object, Outline Stroke, Convert to Shape, Tweak (only when it moves nothing) or Pucker & Bloat at 0 comes in as itself with them and their saved settings. A path, compound path or group (not a clipping group) whose only effect is Transform (not with copies or Random) or a Free Distort whose corners make a parallelogram comes in as itself with it too, when neither it nor anything in it paints with a gradient or pattern and everything in it is a path, compound path or group with no opacity mask and no effects of its own; Transform that scales, rotates or reflects needs nothing in it to be stroked. Twist as the only effect is read the same way on an unstroked path or compound path whose segments are all straight. One with more, other effects (Zig Zag, a Pucker & Bloat of another amount, a Free Distort with other corners), an effect setting that is not read (an unknown key, a missing or out-of-range value, a Scribble with a color of its own), dashes aligned to corners, a brush other than a Calligraphic or Art one, a Calligraphic brush with settings of its own or an angle that follows the path, or an Art brush other than the ones described next comes in as its drawn look (a group named after it). A path with a Calligraphic brush otherwise comes in as the path, the brush on its stroke at the stroke's weight, which scales the brush (#372), and so does a path with an Art brush, whose weight scales the art across the path, when all of these hold: the brush stretches its art along the path, unflipped, at 100% in Tints; the path's settings are the brush's own, with no entry that isn't read, no variable width and no effects; its stroke isn't a swatch; and the art is paths, compound paths and groups in solid fills, with no strokes, masks or effects, and has some length along the path. A stroke takes the brush the Brushes panel keeps under its name, and one whose name the panel doesn't keep comes in as its drawn look. Type with an Art brush keeps its drawn look under the type. A Calligraphic brush's Pressure, Tilt, Bearing and Rotation come in as those channels (a 6D Art Pen's brushes vary with the pressure, the bearing and the barrel's rotation); its Stylus Wheel varies at random. The file's Calligraphic, Scatter, Art and Bristle brushes replace the default ones in the Brushes panel, in the file's order, also when the file comes in as its PDF part (a brush library has no layers); pattern brushes and brushes it can't read are left out with a note. Type keeps its art style as a live appearance whatever its stack (an Art brush aside), so it can be retyped: each fill and stroke of the style's stack (solid colours, gradients and pattern fills, placed as the drawn look places them) with its own opacity, blend mode and effects, the effects on the whole object, and where the characters paint among them; the effects read are Warp, Offset Path, Outline Object, Outline Stroke, Transform (with copies, Transform Objects, Transform Patterns and Scale Strokes & Effects; not with Random), Free Distort, Pucker & Bloat, Twist, Zig Zag, Tweak (only when it moves nothing, the file holding none of its random outline), Drop Shadow, Inner Glow, Outer Glow, Feather, Round Corners, Scribble, Gaussian Blur, Bas Relief (its settings at their defaults) and Convert to Shape, and a style with others, or with an effect setting that isn't read, keeps the type's drawn look. Convert to Shape keeps its Rectangle, Rounded Rectangle or Ellipse, its Absolute or Relative size (a relative size in the file is added to each side) and its corner radius; a relative Ellipse, or a setting that isn't read, keeps the drawn look. The page's drawing of such a style isn't held to look like VectorCraft's where its drawn look lay. Gradients take their matrix (`Xm`, radials and skewed ones too), focal point and the order of stops sharing a ramp point; gradient meshes, a clipping path's own fill and its stroke, and the artboards' names are read too. `editingData: "always"` (`convert --editing-data`) opens a `.ai` or an Illustrator EPS from its editing data even where it draws differently from its page, which can be older, with a note. Type is made from the file's text document, whole and editable, shown or not: point type (its frame's matrix, which newer files leave out when it is neither turned nor scaled, read as none), area type (a story in several frames as threaded type) and type on a path, with its characters, fonts, size, leading, tracking, scaling, baseline shift, fill and stroke (grey, RGB or CMYK), alignment, indents and paragraph spacing (a character attribute a run doesn't set comes from its character style, then the style that one is based on, then the document's defaults); area type keeps its frame's Area Type Options rows and columns, the gutter between rows and the gutter between columns, and its first baseline (Fixed, Cap Height, Leading or x Height) with its minimum; the page's drawing of it (often in pieces) is left out. Vertical type keeps its orientation, its columns from right to left, and a run set to Tate-chu-yoko keeps that setting. When it makes the type of every text object that shows, the page's type is only compared with it, each text object keeps its own group, frame and layer, and a line break inside a paragraph stays one. Where it can't make a text object's type (a frame of another kind), that text object takes the page's type where the text document puts its lines, or else what is left of the page's type in painting order (no group around it); the page's type where made type draws stays that type's, and hidden type is left out with a note. Text wrap is read too: an object whose dictionary has the wrap options (offset and Invert Wrap), or any graphic of a legacy wraparound group (`*w` … `*W`, before or after its type, with no offset), is a wrap object, and area type below it in its layer flows around it when the file opens, threaded type included (#2763). Kerning, underline and the other character and paragraph options aren't read yet. A `.ai` saved without PDF compatibility (a placeholder page) opens from its editing data alone, its point type, area type and type on a path made from the text document. A `.ai` whose PDF part shows only text isn't taken for that placeholder when its page states an art, trim or bleed box: it opens from its editing data alone where that reads, else from its PDF part (#1387). A `.ai` whose PDF part draws nothing at all (an empty page, with or without those boxes) opens from its editing data alone where that reads too: there is no page art to check its layers against, and the empty page would lose all of it. Text objects with an appearance keep their own text and frame, type of hidden layers stays hidden, and type on later artboards lands on its text objects (#1391); area type whose frame is too short for a line of the fallback font shows its first line (the frame is made taller, with a warning in the open result) (#1392). In a PDF saved with some of its artboards, the type on each page after the first goes to an artboard of that page's size after the previous page's artboard, and only to one that leaves an artboard for each later page; the first page's artboard also leaves one for each later page. When several artboards fit a later page, VectorCraft takes the one whose art draws most like the page, and compares at most 16 artboards for all the later pages together. Symbols are read into the Symbols panel, in the file's symbol list order, with their art, their registration point and the instances in their art, and their instances come in live with their matrix, name, hide, lock, opacity, blending mode, isolation, knockout and opacity mask; a symbol whose art isn't read whole (type, placed files, an image it leaves out or anything else it doesn't read) isn't kept. A file that comes in as its page or PDF part because its editing data has no layers (a symbol library), or because its layers can't be read or used (any of the cases below), gets the symbols its editing data defines before its first layer, in the symbol list's order, while its page art stays as the page draws it. Instances of symbols it can't read, instances with appearance changes of their own (a dynamic symbol's), pattern fills whose pattern it can't read, placed files the data keeps no preview of and anything else it doesn't read make the file come in as its page (on a layer that doesn't show, they are left out of that layer), with a warning saying what. An unread object on a shown layer whose box can be found instead comes in as a group of the page's objects inside that box, named after it, with the rest of the layers and groups kept and a warning listing what came in as the page's art; the file still comes in as its page when live art or another unread object shares that page art, when the object spans two artboards or lies on an artboard that has no page, when no page art lies inside its box, when that page art is inside a page group with a clip, mask, transparency or appearance, when the object holds a frame of threaded type, when the page art uses a pattern, image or swatch that the page lacks or that differs from the editing data's, or when the page art doesn't carry its transparency (#4806). The layers' art is also compared with the page (by lightness, so colours written as their CMYK equivalents compare alike, and by whether a pixel is inked at all); the page is used when they differ by more than 5% (away from type, when all the type is made from the text document), where one draws an object the other doesn't (away from type, whose lines may be laid out afresh), and when an EPS whose page is its art's box has art printed outside it (#735). A `.ai` is not compared with its page, and its PDF part is not imported, when its text document makes the type of every text object (at least one shows, and none is drawn by the appearance of the layer or group it is in), no object is unread, no placed file has a second placement, no live envelope keeps its drawn art, it declares no large canvas, and its artboards are its pages' trim boxes one for one, in points or in the first page's `UserUnit`: it opens from its layers, with the bleed of its pages' bleed boxes. When they differ because some objects draw differently (an object in another color, or one that only one of them draws), each top-level object of the layers that holds such a difference comes in as a group of the page's objects inside its box and the layers stay, with a warning that names the layer or sublayer of each such object as the Layers panel shows it (`` for one without a name) and how many came in from it; an export or Save As to the file's own path then needs `acknowledgeLoss: true`, and a wrap object replaced this way keeps its wrap options. The file still comes in as its page when only edges or type differ, when a difference lies outside every top-level object, when more than half of the top-level objects or more than 64 hold one, when such an object is or holds type or holds a wrap object inside a group, when the page's type of a text object that stays lies inside such an object's box, when the page's art can't replace such an object for one of the reasons above for an unread object, when matching the differences to the objects would take too much work, or when the result still differs from the page. `editingData: "always"` keeps the objects as read, and placed files at the other point their data gives are tried as well. An EPS whose page is its art's box keeps that page when it comes in through its layers, not the editing data's artboard, and a text object counts as drawn by the page by its colours, not only its lightness, so a red stroke stacked over a dark red one stays (#1102). `textAs: "outlines"` opens a `.ai` whose type shows from its PDF part, which has the outlines; `editingData: false` opens any of them as its page or PDF part only, for print pipelines (#735). A file whose import left something out (hidden text, art or layers) is never written over by an export or Save As to its own path unless `acknowledgeLoss: true` (`convert --replace-lossy`), so the original keeps what the document lacks. Where a `.ai` can't be read that way (or its layers are turned off in the import options), we read its PDF-compatible part, so Illustrator-only live objects arrive as appearance, and plain groups arrive ungrouped: that part draws each object on its own and marks only layers, clipping groups and groups with opacity, blending or a mask (those come back). Art outside the artboards isn't in that part at all (Illustrator's PDF part leaves it out), so it doesn't open; the import says so in a note (#472). Its pages still go where the editing data puts the artboards (a grid stays a grid), with the art on each, when it has as many artboards as pages, of the same sizes (#1068); the page of the artboard `CropAreaActive` names is then the active artboard. A `.ai` whose editing data has artboards but no layers also opens on that page under the same check, and its pages stay where they are. A `.ai` whose PDF part's pages are cropped to its art (each page the box of all the art, which is the editing data's bounding box) instead of its artboards opens from its layers, with its own artboards, where they draw as that page does. Its pages are laid out so that art one of them draws past its artboard (notes beside a design, which the part doesn't clip) doesn't lie over the page before it in the row, where it was taken for that page's art: the check against the editing data then found art its layers don't have there and opened the file as its PDF part, without the art outside the artboards. That part also has no paragraphs: area type is written line by line, and comes back as area type only where its font is installed and lays the lines out with the same breaks, else as a point type object per line (#508). Art off the page that a PDF does draw opens on the pasteboard. diff --git a/docs/gaps.md b/docs/gaps.md index 5575b9a6c..37c1445a6 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Gaps -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: G9: EPS gradients drawn as abutting strips import as one gradient, without seams; Tools stays beside the canvas with left-side panels; new gradients follow document color mode; Selection QoL: marquee modes and visible feedback, #5313; Indian-language hyphenation review and license allowlist, #4947; opening a slow file shows a progress window, part of #5930; Hindi catalog and contributor loanword preferences; agent wording pass complete; wider native proofreading welcome; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window; scores and estimates unchanged; large images in CMYK documents are shrunk before they are separated into ink planes (#4155); scores and estimates unchanged; `.ai` paths with a Calligraphic brush open brushed, with the 6D Art Pen's channels, and Windows reads the pen's tilt and barrel rotation, #372; scores and estimates unchanged; Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; G9: EPS gradients drawn as abutting strips import as one gradient, without seams; Tools stays beside the canvas with left-side panels; new gradients follow document color mode; Selection QoL: marquee modes and visible feedback, #5313; Indian-language hyphenation review and license allowlist, #4947; opening a slow file shows a progress window, part of #5930; Hindi catalog and contributor loanword preferences; agent wording pass complete; wider native proofreading welcome; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window; scores and estimates unchanged; large images in CMYK documents are shrunk before they are separated into ink planes (#4155); scores and estimates unchanged; `.ai` paths with a Calligraphic brush open brushed, with the 6D Art Pen's channels, and Windows reads the pen's tilt and barrel rotation, #372; scores and estimates unchanged; Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -113,6 +113,8 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall ## G1 Interaction fidelity +- **Panel collapse controls (#5925, in part):** The small » in a floating tab-group header moves that group into the side icon rail, reusing the existing rail or creating one. An opened rail panel has the same control to fold its flyout back into the rail, including after a tab switch. Both controls preserve the tab group and its menu; other floating groups and full-width docked headers stay as they were. Pointer regressions in `crates/ui-egui/src/panel_docking/tests.rs` cover grouped tabs, reopening, separate groups, a missing rail, the menu and flyout closure after switching tabs; `native_tests.rs` covers the native-window click and removal of the empty palette. Scores and estimates unchanged. + - **Done:** Distance Guides ([public page](https://helpx.adobe.com/illustrator/desktop/measure-and-align/grids-and-guides/use-distance-guides-for-accurate-placement.html)): with Smart Guides on and something selected, the Selection tool with Alt/Option held over another object shows the horizontal and vertical distances between them, and over an empty part of an artboard the selection's distances from its four edges, labelled in the General unit; Preferences › Smart Guides › Invoke Distance Guides turns them off. Hover only, as documented: not while moving or nudging. No entry closed; estimates unchanged. - **Done (part of #4028):** Recolor Artwork › Assign: double-clicking a row's new color opens the Color Picker, as the [public page](https://helpx.adobe.com/illustrator/desktop/manage-colors/modify-colors/assign-new-colors-to-selected-artwork.html) describes. OK sets the row's new color in the model it was picked in, so a CMYK mix is no longer a hex value converted back; Cancel leaves the row. A Lab new color is kept, but can't be typed yet: the picker's Lab fields work in RGB, as for fill and stroke. **Missing:** Lab entry in the picker, swatch libraries in the picker and the row's right-click menu (Pick from Swatches, Limit to Library). @@ -833,6 +835,7 @@ trap, and the defaults (0.25 pt, 100%, 40%) are VectorCraft's own, the pages giv | Date | Change | Summary | |---|---|---| | 2026-11-08 | minor | G4 type: the spell check engine is done (`text.spelling.check`, `text.spelling.suggest` on craft-proof, #4936); the Check Spelling dialog, Auto Spell Check and the custom dictionary remain; scores and estimates unchanged. | +| 2026-11-08 | minor | Floating tab groups collapse into the side icon rail and opened rail panels fold back into it (#5925, in part); tab groups remain intact. Scores and estimates unchanged. | | 2026-11-06 | minor | Hindi (`hi`): 7,877-row machine-assisted catalog, zero/one singular plurals, desktop font fallback and optional Devanagari craft-fonts fallback; all 55 translation tests pass; native review and a web font remain; scores and estimates unchanged | | 2026-11-05 | minor | G15: nearby hits on plain long strokes and outlines check their candidate segments directly; a 200,000-anchor middle hit took about 11–13 ms before and 1.0–1.4 ms after in paired runs. An inside-bounds miss fell from 18.7 ms to 1.6 ms. Estimate unchanged; idle budget run still open. | | 2026-11-05 | minor | G15: an image zoomed in on is separated, colorized or proofed only for the tiles in view (#4155); 100% zoom on that file 8.6 s → 0.05 s. Estimate unchanged. | diff --git a/docs/mcp.md b/docs/mcp.md index e414ff8a5..cbe9d39ea 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -463,7 +463,6 @@ opens the picker through `run_command` with the remote backend. | `open_panel` | `{panel}` | Remote only. `panel` is a panel id (`layers`, `swatches`, `colorGuide`, …, as `window.panel` takes) or its display label (`"Color Guide"`), in any case. | | `screenshot` | `{path?, scale?, artboard?, window?, max_side?, traceViews?}` | Returns MCP image content (`image/png`, base64) plus a text block. Renders the artboard with the pixels of a PNG `export` with `background: "white"` at the same scale; `traceViews:true` draws Image Trace objects in the view set with `imageTrace.setView`, as in the app's window; `window:true` captures the app window (remote only), as `ui_screenshot` does, and `max_side` (1–4096, window captures only) scales the returned image down as described for `ui_screenshot`. With `path` the backend (the headless session or the app) writes the PNG there too; a window capture written there keeps its full size with `max_side`. | | `open_file` | `{path, importer?, params?}` | Opens any readable file as a new active document: `.vectorcraft`/`.drawcraft`, `.vctemplate`, `.svg`/`.svgz`, `.pdf`/`.ai`, `.ait`, `.eps`/`.epsf`, `.ps`, `.dxf`, `.emf`, `.wmf`, PNG (`.png`/`.pns`), JPEG, GIF, WebP, TIFF, BMP (`.bmp`/`.dib`/`.rle`), Targa (`.tga`/`.vda`/`.icb`/`.vst`), PCX, Pixar (`.pxr`), JPEG 2000 (`.jpf`/`.jpx`/`.jp2`/`.j2k`/`.j2c`/`.jpc`), HEIC/HEIF (`.heic`/`.heif`), JPEG XL (`.jxl`), PSD (`.psd`/`.pdd`)/PSB (an image opens as a document of its pixel size; a Photoshop file as its merged image; a JPEG XL file as its first frame). Templates (`.vctemplate`, `.ait`) open as a new untitled document. PDF, `.ai` and SVG files saved with Preserve Editing reopen as the document they carry. The reply is `document.open`'s: its `warnings` say what didn't come in as it was (an EPS whose PostScript can't be read opens as its preview, and the warning names the PostScript error, the operator and the procedure). A file of an extension only an installed importer plug-in lists opens with that importer ([Plug-ins](#plug-ins)); `importer: "plugin."` opens the file with the importer it names, which must list the file's extension, also one a built-in format reads (in the app, as File › Open With does), and `params` gives that importer's parameters (their defaults when omitted). `run_command document.formats` lists the formats. | -| `save_file` | `{path?, compress?, version?, preview?, precision?, options?}` | Runs `document.save`: the document's own file in its own format (native `.vectorcraft` unless it was opened from or saved as SVG, PDF or a restorable `.ai`; then `warnings` say what that format loses). A path's extension picks the format (`.vectorcraft`, `.vctemplate`, `.pdf`, `.svg`, `.svgz`, `.ai`: a PDF carrying the native document, which reopens editable). For a native file, `compress: true` gzips it (default: the `useCompression` preference, Preferences › File Handling › Use Compression), `version` (1 to 4) writes it for an older VectorCraft (1 and 2 are never compressed, and `version: 2` with `compress: true` is an error), `preview: true` embeds a PNG of the first artboard, and `precision` (0 to 9) rounds path anchors and handles to that many decimal places for that save only (colors, transforms and type stay exact). `options` passes the format's save options as `run_command document.formats` lists them. When `compress`, `version`, `preview` or `precision` is given both at the top level and in `options` with different values, the call is an error (`` `compress: true` and `options.compress: false` disagree: give the option once ``) and nothing is written. The save drops each option the written format doesn't read. The reply's `warnings` then name each such option given (for example `` `compress` was not used: SVG files don't read it `` for an `.svg` path, and `` `options.preview` was not used: … `` for a key in `options`; a `.ai` file reads `compress` but not `version`, `preview` or `precision`). `run_command document.save` and the other save commands refuse any option given beside the path and in `options` with different values, and give the same warning for a top-level `compress`, `version`, `preview` or `precision`. The headless session and the app receive the same `app.save` params, and the app is also sent `wait: true`: the call returns once the file is written, or with the error (such as a path outside the write root), even with Background Save on. When the save asks first (the question before replacing a file that opened with losses, or on macOS and Linux the file dialog for an untitled document without `path`), the reply is `{pending}` or `"cancelled"` as before, and the save that runs after the answer uses the Background Save preference. | | `save_file` | `{path?, compress?, version?, preview?, precision?, options?}` | Runs `document.save`: the document's own file in its own format (native `.vectorcraft` unless it was opened from or saved as SVG, PDF or a restorable `.ai`; then `warnings` say what that format loses). A path's extension picks the format (`.vectorcraft`, `.vctemplate`, `.pdf`, `.svg`, `.svgz`, `.ai`: a PDF carrying the native document, which reopens editable). For a native file, `compress: true` gzips it (default: the `useCompression` preference, Preferences › File Handling › Use Compression), `version` (1 to 4) writes it for an older VectorCraft (1 and 2 are never compressed, and `version: 2` with `compress: true` is an error), `preview: true` embeds a PNG of the first artboard, and `precision` (0 to 9) rounds path anchors and handles to that many decimal places for that save only (colors, transforms and type stay exact). `options` passes the format's save options as `run_command document.formats` lists them. When `compress`, `version`, `preview` or `precision` is given both at the top level and in `options` with different values, the call is an error (`` `compress: true` and `options.compress: false` disagree: give the option once ``) and nothing is written. A key in `options` that no save format takes is an error, and nothing is written (see Parameter keys). The save drops each option the written format doesn't read. The reply's `warnings` then name each such option given (for example `` `compress` was not used: SVG files don't read it `` for an `.svg` path, and `` `options.preview` was not used: … `` for a key in `options`; a `.ai` file reads `compress` but not `version`, `preview` or `precision`). For a `.ai` file, these warnings name the kind of `.ai` written: a legacy .ai (version 7) for `aiVersion: "7"`, a non-PDF-compatible .ai for `pdfCompatible: false`, and a PDF-compatible .ai otherwise, including a save that reuses the options the document was last saved with. `run_command document.save` and the other save commands refuse any option given beside the path and in `options` with different values, and give the same warnings for a top-level `compress`, `version`, `preview` or `precision` and for each key in `options` that the format doesn't read. The headless session and the app receive the same `app.save` params, and the app is also sent `wait: true`: the call returns once the file is written, or with the error (such as a path outside the write root), even with Background Save on. When the save asks first (the question before replacing a file that opened with losses, or on macOS and Linux the file dialog for an untitled document without `path`), the reply is `{pending}` or `"cancelled"` as before, and the save that runs after the answer uses the Background Save preference. | | `export` | `{path?, format?, scale?, artboard?, range?, selection?, outlineText?, options?}` | `svg`, `svgz`, `pdf`, `ai` (the PDF-compatible `.ai` that Save writes, which carries the document when every artboard is written in order), `eps`, `dxf`, `emf`, `wmf`, `png`, `jpg`, `webp`, `gif`, `png8` (an indexed `.png`), `tiff`, `bmp`, `tga`, `psd` (layered), `txt` (the document's text), `vectorcraft` or `template` (a native template). The tool's `format` enum and `document.formats` list them, generated from the engine's format table. When `format` is omitted, it comes from the path's extension. PDF and `.ai` write one page per artboard: all of them, or `artboard` (0-based) / `range` (`"1-3, 5"`, 1-based). SVG and SVGZ write one file per chosen artboard. EPS writes one file of the visible art; with `artboard`, `range`, `options.artboards` or `options: {"useArtboards": true}` it writes one file per chosen artboard, after a central file of the visible art when there are two or more, and `useArtboards: false` keeps the one file. Rasters, DXF, EMF and WMF write one artboard, or one file per chosen artboard with `options: {"useArtboards": true}`. Rasters also take `options: {"rect": [x, y, width, height]}` (page points, y down): that area of the page at the given `ppi`/`scale`, without an artboard and without changing the document; it is refused with `artboard`, `artboards`, `range` or `useArtboards`, with a non-positive or non-finite size, and for non-raster formats. `options` carries more format options (e.g. `{"quality": 80}` for JPEG). `selection: true` exports the selected objects cropped to their bounds (the reply adds their `bounds`, and reports `format` and the encoder's `warnings` as a whole-document export does); `outlineText: true` writes SVG text as paths. Template layers are left out, live effects are kept, and exporting `vectorcraft` never changes the document's path. Without `path` the bytes come back as `dataBase64`. Both backends run the same `document.export` call. | | `add_text` | `{text, x?, y?, width?, height?, fit?, fitMinPercent?, path?, mode?, pathEffect?, size?, font?, color?, justify?, vertical?}` | Point type at (x, y) (`justify`: `left`, `center` or `right` starts, centres or ends each line on x); area type with `width`/`height` (`fit`: `none`, `autoHeight`, `shrinkText`, as `text.areaOptions`); or `path` + `mode` (`area`/`onPath`) to flow text in or along a path, with `pathEffect` (`rainbow`, `skew`, `3dRibbon`, `stairStep`, `gravity`; SVG export writes type on a path with any effect other than `rainbow` as outlines, with a warning); `justify` (also `auto`, `justifyAll`) aligns the paragraphs of every kind, as `text.setStyle` does later. `vertical: true` uses the existing vertical writing mode for every kind (default `false`, horizontal). Returns `{id}`, with `overflow: true` when the text doesn't fit its frame or path. | diff --git a/docs/plugins.md b/docs/plugins.md index bb6ec8dbb..c5423e511 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -527,7 +527,11 @@ In this example the character and paragraph styles are cut short; a filter gets `pixelsOmitted: false`. A CMYK image's pixels are its inks through the working CMYK profile. The pixels of all the images in one input take at most 16 MiB in base64. A larger image, one whose pixels don't fit what the images before it in paint order left, and a linked image (`linked: true`) have - `pixelsOmitted: true` and no pixels; the host checks an image's size before it decodes the image. The host never + `pixelsOmitted: true` and no pixels; the host checks an image's size before it decodes the image. An image of any + size also has `pixelsOmitted: true` when its data is missing from the document, when its width or height is 0, when + its data cannot be decoded (or needs more than 64 MiB of decoder memory, or is a TIFF the host rejects as too + large), or when the dimensions in its data differ from `pixelSize`. A plug-in checks `pixelsOmitted` before it + reads `pixelsBase64`, which is present only when `pixelsOmitted` is `false`. The host never reads a linked image's file for a filter and never gives its path. - A symbol instance (`"symbolInstance"`): `symbolId` is the name of the symbol it shows, with `missing: true` when the document has no symbol of that name. `overrides` is `{}`. diff --git a/docs/roadmap.md b/docs/roadmap.md index 985b8f866..110685567 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,6 +1,6 @@ # Roadmap: milestones and what's next -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: M4/M7: `.ai`/EPS type keeps its tab stops, and right-aligned lines with tabs lay out as the file's page, #6147; M4/M7: `.ai`/EPS type from the text document lays its lines out closer to the file's page, within limits, #6137; numeric fields step on wheel hover without taking keyboard focus, #5949) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; M4/M7: `.ai`/EPS type keeps its tab stops, and right-aligned lines with tabs lay out as the file's page, #6147; M4/M7: `.ai`/EPS type from the text document lays its lines out closer to the file's page, within limits, #6137; numeric fields step on wheel hover without taking keyboard focus, #5949) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -153,7 +153,7 @@ native wording review, layout checks and a web Devanagari font remain. [Notes](l | M12 | Advanced color & art (CMYK/ICC, separations, Gradient Mesh, Live Paint, Image Trace, Graphs) | 🟡 Gradient Mesh, Live Paint, Image Trace (junction-returning pieces retain their positioned boundaries; a simpler contour walk with identical output; an edge's sub-pixel shift is the same from either side; notches read only their own piece; collapsed loops keep few segments; large images trace with a warning; Auto Color keeps rare distinct colours; quick presets trace at once and the Image Trace button opens the adjustment controls; Properties reopens them; tiny fitted contours retain islands and holes; Create Strokes: exact crop EDT, Guo–Hall thinning and maximal-disc graph simplification; 15 presets, the 14 documented ones each with an enhanced version; Ignore Color, Shapes, Auto Grouping and Gradients options; measured in `image-trace-quality.md`; closed outlines retain their interior after straightening; the Flat Logo mode places edges to a fraction of a pixel, restores sharp corners, snaps lines and circles, and refuses gradients; live trace result bounds and selection highlights exclude the retained bitmap except in source-image views), Recolor Artwork, colour management (ICC, soft proofing, separations preview), Graphs (all 9 tools, Graph Data/Type, group-selected series colours persist through regenerate/save/open) done; Graph Design and Marker designs, Transpose and Switch x/y, `\|` label line breaks, the pie options, drop shadows, the legend across the top, overlapping widths, First Row/Column in Front, filled lines, kept label formatting and the Graph Data worksheet done; graph Column designs pending | 6–10 | | M13 | Automation (Actions ✅ record/playback, persisted; variables, scripting, batch) | 🟡 MCP `add_text` exposes existing vertical point, area and path type; broader scripting and batch work remains | 18–27 | | M13 | Automation (Actions ✅ record/playback, persisted; variables, scripting, batch) | 🟡 | 18–27 | -| M14 | 1.0 polish (preferences, shortcut editor, workspaces, accessibility, packaging for all OSes) (status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; sample widths cached by language/unit/DPI; regression-tested and contributor-confirmed 2026-10-19; estimates unchanged) | 🟡 Dock groups collapse to their tabs by a double-click on a tab (UI.8); Preferences › General › Constrain Angle acts for the arrow keys, Shift drags and new objects (#394, 2026-10-21); numeric-field pointer selection and focus regression-tested (#1326, in part; short movements still select all); RPM runtime capabilities and glibc floor checked (#1275; real-distro install/start-up pending), Eyedropper cursor with precise-setting checks (#1269), Preferences, shortcut editor (chords with Shift and a punctuation key fire as typed; other apps' text shortcut sets imported), workspaces (each keeps its layout changes until Reset , UI.7), Window › Help Bar shown or hidden apart from the status bar and kept in saved workspaces, panels docked by dragging into any dock group (as tabs or a top/bottom split) or into the icon column, and closed from a dock tab's context menu (UI.6), a custom title bar on Windows/Linux with a Home button, content-sized dialogs, menus that scroll when longer than the window and macOS native menu dismissal done (M14.8), and native macOS save sheets with a Format menu (#866); titled PDF exports set `DisplayDocTitle` for viewers and accessibility checkers (#3646); accessibility and broader Windows/Linux packaging/QA pending | 18–28 | +| M14 | 1.0 polish (preferences, shortcut editor, workspaces, accessibility, packaging for all OSes) (status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates; sample widths cached by language/unit/DPI; regression-tested and contributor-confirmed 2026-10-19; estimates unchanged) | 🟡 Dock groups collapse to their tabs by a double-click on a tab (UI.8); Preferences › General › Constrain Angle acts for the arrow keys, Shift drags and new objects (#394, 2026-10-21); numeric-field pointer selection and focus regression-tested (#1326, in part; short movements still select all); RPM runtime capabilities and glibc floor checked (#1275; real-distro install/start-up pending), Eyedropper cursor with precise-setting checks (#1269), Preferences, shortcut editor (chords with Shift and a punctuation key fire as typed; other apps' text shortcut sets imported), workspaces (each keeps its layout changes until Reset , UI.7), Window › Help Bar shown or hidden apart from the status bar and kept in saved workspaces, panels docked by dragging into any dock group (as tabs or a top/bottom split) or into the icon column, and closed from a dock tab's context menu (UI.6), a custom title bar on Windows/Linux with a Home button, content-sized dialogs, menus that scroll when longer than the window and macOS native menu dismissal done (M14.8), and native macOS save sheets with a Format menu (#866); titled PDF exports set `DisplayDocTitle` for viewers and accessibility checkers (#3646); accessibility and broader Windows/Linux packaging/QA pending; floating tab groups collapse into the side icon rail and opened rail panels fold back into it (#5925, in part), with pointer and native-window regression coverage; estimates unchanged | 18–28 | | — | Interaction fidelity pass (every tool's modifiers, Properties panel per context, isolation, nuance; width-point creation and adjoining adjustment regression-tested; dialog layout reviewed) | ⬜ (new ruler-guide Alt orientation implemented; existing-guide Escape cancellation regression-tested; broader pass remains) | 60–90 | | — | Hardening at scale (big-file corpus, fuzzing, cross-platform + browser QA) | 🟡 PngSuite integrity and bounded fetching verified (#1729); real-world corpus/manual QA remain | 50–80 | | M14 follow-up | Color Picker layout (#2008) | ✅ Compact controls, centered header, stacked actions, aligned fields and screen sampling; narrow windows scroll vertically | Included above | @@ -191,6 +191,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development | Date | Change | Summary | |---|---|---| | 2026-11-08 | minor | M7: spell check engine, `text.spelling.check` and `text.spelling.suggest` on craft-proof (#4936); the dialog, Auto Spell Check and the custom dictionary remain; scores and estimates unchanged. | +| 2026-11-08 | minor | Floating tab groups collapse into the side icon rail and opened rail panels fold back into it (#5925, in part); tab groups remain intact. Scores and estimates unchanged. | | 2026-11-09 | minor | Numeric fields step on wheel hover without taking focus; keyboard focus and existing rounding rules remain (#5949). Scores and estimates unchanged. | | 2026-11-07 | minor | M2: with the Pen or Curvature tool, Edit › Copy and Cut take the anchors and segments a Cmd/Ctrl-lent Direct Selection picked, not the whole path (#6123); estimates unchanged | | 2026-11-06 | minor | M2: a shape just drawn moves by its centre point (Show Center) and turns from the rotate zone just outside a bounding-box corner with its tool still active, as the Selection tool's gestures do (#5477); estimates unchanged. | diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index e67ad71fb..523184e3f 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1,6 +1,6 @@ # Parity with Adobe Illustrator -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: `.ai`/EPS type keeps its tab stops, and right-aligned lines with tabs lay out as the file's page, #6147; `.ai`/EPS type from the text document lays its lines out closer to the file's page, within limits, #6137; `.ai` and EPS import: older Art brush objects read; numeric fields step on wheel hover without taking keyboard focus, and a panel wheel turn cannot seed a canvas zoom glide, #5949) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (spell check engine: `text.spelling.check` and `text.spelling.suggest` on craft-proof, #4936; previously minor: floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; `.ai`/EPS type keeps its tab stops, and right-aligned lines with tabs lay out as the file's page, #6147; `.ai`/EPS type from the text document lays its lines out closer to the file's page, within limits, #6137; `.ai` and EPS import: older Art brush objects read; numeric fields step on wheel hover without taking keyboard focus, and a panel wheel turn cannot seed a canvas zoom glide, #5949) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence @@ -233,7 +233,7 @@ dominate; 3D, graphs and automation are specialist). They sum to 105. Feature de | Export for Screens, Asset Export, slices, Save for Web | 3 | 85% | anti-aliasing ghost borders (#983); TIFF choices and settings are implemented | 2–4 | estimated | | Print, colour management, separations, flattener | 3 | 80% | print preview fidelity | 2–4 | estimated | | Automation (Actions, Variables, scripting, batch) | 3 | 60% | a scripting surface (Illustrator: JavaScript/ExtendScript), Batch, image and graph variables, dataset import/export | 8–14 | estimated; MCP `add_text` exposes existing vertical type; score and hours unchanged | -| UI chrome (panels, Properties, workspaces, preferences) | 6 | 80% | Properties per context, 3 Preferences options stored but unused (#394, audited 2026-10-30: Open Documents As Tabs, Scratch Disks, legacy vector clipboard), SVG Interactivity, context menus beyond the canvas, accessibility | 13–20 | estimated; floating panels in OS windows of their own on the desktop (#4135), score and hours unchanged; macOS save Format menu implemented (#866), score and hours unchanged; Eyedropper cursor and precise setting checked (#1269), OS visual QA unverified; numeric-field focus and normal text drags regression-tested (#1326, in part); movements below the click threshold still select all; artboard navigation synchronizes the tool selection before destructive actions; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates (sample widths cached by language/unit/DPI; regression and contributor original-file evidence below; scores/hours unchanged); effect dialogs' colour fields show a swatch that opens the Color Picker (#5365), score and hours unchanged | +| UI chrome (panels, Properties, workspaces, preferences) | 6 | 80% | Properties per context, 3 Preferences options stored but unused (#394, audited 2026-10-30: Open Documents As Tabs, Scratch Disks, legacy vector clipboard), SVG Interactivity, context menus beyond the canvas, accessibility | 13–20 | estimated; floating panels in OS windows of their own on the desktop (#4135), score and hours unchanged; macOS save Format menu implemented (#866), score and hours unchanged; Eyedropper cursor and precise setting checked (#1269), OS visual QA unverified; numeric-field focus and normal text drags regression-tested (#1326, in part); movements below the click threshold still select all; artboard navigation synchronizes the tool selection before destructive actions; status messages and readouts use stable responsive allocations sized for localized labels and ordinary coordinates (sample widths cached by language/unit/DPI; regression and contributor original-file evidence below; scores/hours unchanged); effect dialogs' colour fields show a swatch that opens the Color Picker (#5365), score and hours unchanged; floating tab groups collapse into the side icon rail and opened rail panels fold back into it (#5925, in part), scores and hours unchanged | | Libraries, Links, Package | 2 | 82% | shared libraries, linked graphics, gradients and patterns as library items (no cloud by design) | 2–3 | estimated | | Generative AI (Text to Vector, Generative Recolor, Shape Fill, Concept to Vector, Prompt to Edit, Rewrite Text) | 2 | 0% | a pluggable provider and the panels; needs an owner decision on models | 30–60 | measured (absent) | | **Feature depth** | **105** | **~75%** | | **~250–420** | | @@ -539,6 +539,8 @@ since 2026-10-01. ## Evidence: what ships today, by area +- **Panel collapse controls (#5925, in part):** The small » in a floating tab-group header moves that group into the side icon rail, reusing the existing rail or creating one. An opened rail panel has the same control to fold its flyout back into the rail, including after a tab switch. Both controls preserve the tab group and its menu; other floating groups and full-width docked headers stay as they were. Pointer regressions in `crates/ui-egui/src/panel_docking/tests.rs` cover grouped tabs, reopening, separate groups, a missing rail, the menu and flyout closure after switching tabs; `native_tests.rs` covers the native-window click and removal of the empty palette. Scores and estimates unchanged. + - 3D lighting presets: `crates/effects/src/lighting_presets.rs`, `crates/engine/src/cmd/effectcmd/lighting_presets_tests.rs` and the pointer tests in `crates/ui-egui/src/dialogs/revolve/tests.rs` cover recognition, atomic targeting, native round trips, preview, Cancel, reset and Undo. - Window › 3D and Materials panel: `crates/ui-egui/src/panels/three_d.rs` applies a 3D effect by object type (Plane, Extrude, Revolve, Inflate), changes an applied one in place (`effect.setParams` `threeDType`) and edits it with the options dialog's Object, Rotation and Lighting controls, one undo step per change; `crates/engine/src/cmd/effectcmd/three_d_type_tests.rs` and `crates/ui-egui/src/panels/tests_three_d.rs` cover it. No Materials tab. @@ -1891,6 +1893,7 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s | Date | Change | Summary | |---|---|---| | 2026-11-08 | minor | Type advanced: spell check engine, `text.spelling.check` lists unknown words per run language at `text.editRange` byte offsets and `text.spelling.suggest` gives suggestions, from craft-proof (#4936); evidence added; the dialog, Auto Spell Check and the custom dictionary remain; scores and hours unchanged. | +| 2026-11-08 | minor | Floating tab groups collapse into the side icon rail and opened rail panels fold back into it (#5925, in part); tab groups remain intact. Scores and estimates unchanged. | | 2026-11-07 | minor | File formats and Type core: `.ai`/EPS type keeps its paragraphs' tab stops, and right-aligned area type sets the text after a line's last tab against the right edge, the text before it at its stops (#6147); evidence added; scores unchanged. | | 2026-11-07 | minor | File formats and Type core: `.ai`/EPS type from the text document lays its lines out closer to the file's page: auto leading per line at the paragraph's share, the paragraph's hyphenation options, possibly more of the last lines the file sets in an area frame of its own, and a line a soft return ends justified (#6137); evidence added; scores unchanged. | | 2026-11-06 | minor | `.ai` and EPS import: Art brushes in the older form of their objects come into the Brushes panel; evidence updated; scores unchanged. | diff --git a/docs/type-parity.md b/docs/type-parity.md index a99814940..d79bf9cd2 100644 --- a/docs/type-parity.md +++ b/docs/type-parity.md @@ -68,8 +68,8 @@ against Illustrator's public documentation; **estimated** unless noted. | CJK: Standard Vertical Roman Alignment | done | Character panel menu › Standard Vertical Roman Alignment (`text.setFormat` / `text.setRangeStyle {standardVerticalRoman}`), with Show East Asian Options, off by default: in vertical type the marked half-width characters (Latin letters, digits and other characters that would lie on their side) stand upright one below the other, each in its upright cell; spaces keep their width and tate-chu-yoko blocks stay across the column; horizontal type keeps the mark and does not change. Character Style Options has its own checkbox for it. Evidence: `tests_vertical::standard_vertical_roman_alignment_stands_half_width_characters_upright`, `typecmd::standard_vertical_roman_is_a_character_attribute_saved_only_when_on` | | CJK: composite fonts | partial | Type › Composite Fonts… (`type.compositeFonts.set`), with Show East Asian Options: a font made of a font per class of character (Kanji, Kana, Punctuation, Symbols, Roman, Numerals), each with its own style, size and baseline in percent of the type's size, Kana, Roman and Numerals with horizontal and vertical scale, and Kana with Scale at Center; New, Delete Font, Import… and Export… (`.vccomposite`, `type.compositeFonts.import` and `.export`) ([Creating composite fonts](https://help.adobe.com/archive/en_US/illustrator/cs3/illustrator_cs3_help.pdf#page=347)). Composite fonts are listed first in the font menus, saved with the document, and their type is laid out per class; SVG and legacy `.ai` write it as outlines. Not yet: custom character sets, the Units menu, Show Sample. Evidence: `text::composite::tests::characters_fall_in_their_classes`, `tests::composite::composite_type_draws_each_class_in_its_font`, `compositefonts::tests`, `dialogs::composite_fonts::tests` | | CJK: ruby | partial | ruby (ふりがな) for live text (`type.ruby`): a reading over a byte range, above (right of the column in vertical type) or below, start/centre/end alignment, size, overhang or squeeze; saved natively, laid out with its line growing, drawn on the canvas, in SVG and PDF (as outlines where ruby is present), in EPS, EMF/WMF and legacy `.ai` (legacy `.ai` writes type with ruby as outlines), and kept by Create Outlines, Expand Appearance and flattening, each glyph where the layout puts it (`typecmd::ruby_tests::create_outlines_keeps_the_ruby_where_it_is_laid_out`) | #633 | -| Language per character (Character panel › Language, character styles), text shaped in it | done | `text.setFormat` / `text.setRangeStyle {language}` by name or BCP 47 tag, one of Document Setup's languages (Hindi, Marathi, Gujarati, Bengali, Punjabi, Tamil, Telugu, Kannada, Malayalam, Odia and Urdu added, #4943; Assamese and Sanskrit added, #4947; Serbian: Cyrillic, Serbian: Latin and Croatian); the shaper gets the tag, so the font's language forms apply (OpenType `locl`: Turkish keeps `fi` apart and the dot on its i, #4940); new type takes the document's language; saved as the tag only when set, carried by character styles (#4946). English and eleven Indian languages hyphenate by run language; Sanskrit automatic hyphenation awaits allowlisted patterns (#4947). Not yet: spelling by it, `.ai`/PDF import of a text's language, paragraph styles. Evidence: `features::tests::language_picks_the_fonts_forms`, `features::tests::serbian_gets_the_fonts_serbian_forms`, `typecmd::area_tests::language_is_a_character_attribute_saved_only_when_set`, `tests::typo::indic_language_runs_hyphenate_with_both_composers`, `hyphen::tests::sanskrit_without_allowlisted_patterns_uses_only_exceptions`; spelling is checked in each run's Language (`text.spelling.check`, #4936) | -| Language per character (Character panel › Language, character styles), text shaped in it | done | `text.setFormat` / `text.setRangeStyle {language}` by name or BCP 47 tag, one of Document Setup's languages (Hindi, Marathi, Gujarati, Bengali, Punjabi, Tamil, Telugu, Kannada, Malayalam, Odia and Urdu added, #4943; Serbian: Cyrillic, Serbian: Latin and Croatian; Bulgarian, #5945); the shaper gets the tag, so the font's language forms apply (OpenType `locl`: Turkish keeps `fi` apart and the dot on its i, #4940); new type takes the document's language; saved as the tag only when set, carried by character styles (#4946). Not yet: hyphenation and spelling by it, `.ai`/PDF import of a text's language, paragraph styles. Evidence: `features::tests::language_picks_the_fonts_forms`, `features::tests::serbian_gets_the_fonts_serbian_forms`, `typecmd::area_tests::language_is_a_character_attribute_saved_only_when_set`; spelling is checked in each run's Language (`text.spelling.check`, #4936) | +| Language per character (Character panel › Language, character styles), text shaped in it | done | `text.setFormat` / `text.setRangeStyle {language}` by name or BCP 47 tag, one of Document Setup's languages (Hindi, Marathi, Gujarati, Bengali, Punjabi, Tamil, Telugu, Kannada, Malayalam, Odia and Urdu added, #4943; Assamese and Sanskrit added, #4947; Serbian: Cyrillic, Serbian: Latin and Croatian); the shaper gets the tag, so the font's language forms apply (OpenType `locl`: Turkish keeps `fi` apart and the dot on its i, #4940); new type takes the document's language; saved as the tag only when set, carried by character styles (#4946). English and eleven Indian languages hyphenate by run language; Sanskrit automatic hyphenation awaits allowlisted patterns (#4947). Not yet: `.ai`/PDF import of a text's language, paragraph styles. Evidence: `features::tests::language_picks_the_fonts_forms`, `features::tests::serbian_gets_the_fonts_serbian_forms`, `typecmd::area_tests::language_is_a_character_attribute_saved_only_when_set`, `tests::typo::indic_language_runs_hyphenate_with_both_composers`, `hyphen::tests::sanskrit_without_allowlisted_patterns_uses_only_exceptions`; spelling is checked in each run's Language (`text.spelling.check`, #4936) | +| Language per character (Character panel › Language, character styles), text shaped in it | done | `text.setFormat` / `text.setRangeStyle {language}` by name or BCP 47 tag, one of Document Setup's languages (Hindi, Marathi, Gujarati, Bengali, Punjabi, Tamil, Telugu, Kannada, Malayalam, Odia and Urdu added, #4943; Serbian: Cyrillic, Serbian: Latin and Croatian; Bulgarian, #5945); the shaper gets the tag, so the font's language forms apply (OpenType `locl`: Turkish keeps `fi` apart and the dot on its i, #4940); new type takes the document's language; saved as the tag only when set, carried by character styles (#4946). Not yet: hyphenation by it in languages other than English and the eleven Indian languages above, `.ai`/PDF import of a text's language, paragraph styles. Evidence: `features::tests::language_picks_the_fonts_forms`, `features::tests::serbian_gets_the_fonts_serbian_forms`, `typecmd::area_tests::language_is_a_character_attribute_saved_only_when_set`; spelling is checked in each run's Language (`text.spelling.check`, #4936) | | No Break | done | Character panel menu (`text.setFormat` / `text.setRangeStyle {noBreak}`): the marked characters stay on one line, including across a space; a run that does not fit after earlier text moves together, and a run longer than the line stays together. Evidence: `layout::wrapping_tests::no_break_keeps_marked_characters_on_one_line`, `tests::no_break_keeps_marked_words_on_one_line`, `typecmd::no_break_is_a_character_attribute_saved_only_when_on` | | Inline symbols in text | beyond Illustrator | not scored | | Align Stroke (inside, outside) on live type's character stroke | beyond Illustrator | not scored; `stroke.set {align}` and `text.setRangeStyle {strokeOptions: {align}}`; saved natively, drawn around the glyphs on the canvas and in PDF, EPS and SVG (live text, stroke on the glyph outlines); visual bounds grow with it (#2294) | @@ -81,7 +81,7 @@ Character Styles menu New/Redefine and footer New capture the first selected cha | Date | Change | Summary | |---|---|---| -| 2026-11-08 | minor | Spell check engine (#4936): `text.spelling.check` lists unknown words per run language at `text.editRange` byte offsets and `text.spelling.suggest` gives suggestions, from craft-proof; the dialog, Auto Spell Check and the custom dictionary remain. Spelling noted in the Language per character rows. Scores and estimates unchanged. | +| 2026-11-08 | minor | Spell check engine (#4936): `text.spelling.check` lists unknown words per run language at `text.editRange` byte offsets and `text.spelling.suggest` gives suggestions, from craft-proof; the dialog, Auto Spell Check and the custom dictionary remain. Spelling noted in the Language per character rows, which no longer list it as not yet done; their hyphenation note names the languages that still lack it. Scores and estimates unchanged. | | 2026-11-07 | minor | Tabs: in right-aligned area type, the text before a line's last tab sits at its stops, measured from the left edge, and the text after it (none, when the tab ends the line) against the right edge; the line was aligned as a whole, which moved its start off the left edge. Illustrator's own layout in a user's `.ai` (used locally, not committed) sets its price lists so. Centred and justified lines keep their alignment as a whole. Scores and estimates unchanged. | | 2026-11-07 | minor | Soft return: in Justify Left, Center or Right, the line a soft return ends is justified as a wrapped line is; only the paragraph's last line keeps its alignment. Illustrator's own layout in a user's `.ai` (used locally, not committed) justifies those lines. Scores and estimates unchanged. | | 2026-11-08 | minor | Style panels: a double-click on a user character style's row opens its Character Style Options (`ui.charStyleOptions`), and on a paragraph style's row its Paragraph Style Options (rename); the Character Style Options dialog keeps a fixed width and scrolls its sections in a shorter window, so OK and Cancel stay on screen. Tests `panels::text_styles::tests::double_clicking_a_character_style_row_opens_its_options` and `dialogs::char_style_options::tests::the_sections_scroll_in_a_window_shorter_than_the_general_list`. Scores and estimates unchanged. | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 5cfffecc6..72f12328e 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI and interaction parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (numeric fields step on wheel hover without taking keyboard focus, #5949) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; numeric fields step on wheel hover without taking keyboard focus, #5949) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) ## Feature submission evidence @@ -341,6 +341,8 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po ## Panels, Control bar, Properties, context menus +- **Panel collapse controls (#5925, in part):** The small » in a floating tab-group header moves that group into the side icon rail, reusing the existing rail or creating one. An opened rail panel has the same control to fold its flyout back into the rail, including after a tab switch. Both controls preserve the tab group and its menu; other floating groups and full-width docked headers stay as they were. Pointer regressions in `crates/ui-egui/src/panel_docking/tests.rs` cover grouped tabs, reopening, separate groups, a missing rail, the menu and flyout closure after switching tabs; `native_tests.rs` covers the native-window click and removal of the empty palette. Scores and estimates unchanged. + | Interaction | Status | Evidence | |---|---|---| | Selection artwork drag hovers a visible document tab to activate it, then drops editable artwork on its canvas | done | `tests_document_drag`, `edit.dropSelection`, lossless resource and one-step Undo regressions; overflow and cross-window drag remain separate | @@ -488,6 +490,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development | Date | Change | Summary | |---|---|---| +| 2026-11-08 | minor | Floating tab groups collapse into the side icon rail and opened rail panels fold back into it (#5925, in part); tab groups remain intact. Scores and estimates unchanged. | | 2026-11-09 | minor | Numeric fields step on wheel hover without taking focus; keyboard focus and existing rounding rules remain (#5949). Scores and estimates unchanged. | | 2026-11-07 | minor | Pen and Curvature tool with Cmd/Ctrl held: Edit › Copy and Cut take the anchors and segments the lent Direct Selection picked, as with Direct Selection chosen, not the whole path; with the Pen's own last anchor selected Copy still takes the whole path (#6123); scores and estimates unchanged | | 2026-11-07 | minor | Live Corners: with Direct Selection, while every corner of a path is selected, a Shift-click on a corner widget picks that corner alone instead of deselecting it (#5188); adding a hidden corner's widget by Shift-click stays open (#1232); scores and estimates unchanged |