From 7f843e1e4a799ddd1e5d575fdecc58a7f22eef4a Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 1 Nov 2026 07:59:14 -0800 Subject: [PATCH 1/7] .ai import: objects with a variable-width stroke come in as their drawn outline The editing data keeps each styled object's drawn look, the outline as the file draws it, next to the editable path. An object whose art style was one stroke with a width profile came in as a live stroke instead, and VectorCraft's width profile does not draw these strokes at the widths the file draws them, so outlines no longer met the fills built against them. Such an object now comes in as its drawn look, as other styles that VectorCraft cannot draw the same way already do. Type keeps the stroke and its profile live, since its drawn look cannot be retyped. The import warning for drawn looks now names variable-width strokes. --- crates/engine/tests/import_fuzz.rs | 3 ++ crates/eps/src/import/ai/read/document.rs | 2 +- crates/eps/src/import/ai/style.rs | 10 ++-- crates/eps/src/import/ai/style/tests.rs | 64 ++++++++++++++++++----- crates/eps/src/import/ai/tests.rs | 47 +++++++++++++++++ crates/eps/src/import/tests_native.rs | 30 +++++++++++ crates/ui-egui/src/i18n/bg.tsv | 2 +- crates/ui-egui/src/i18n/bs.tsv | 2 +- crates/ui-egui/src/i18n/ca.tsv | 2 +- crates/ui-egui/src/i18n/cs.tsv | 2 +- crates/ui-egui/src/i18n/da.tsv | 2 +- crates/ui-egui/src/i18n/de.tsv | 2 +- crates/ui-egui/src/i18n/el.tsv | 2 +- crates/ui-egui/src/i18n/es.tsv | 2 +- crates/ui-egui/src/i18n/fi.tsv | 2 +- crates/ui-egui/src/i18n/fr.tsv | 2 +- crates/ui-egui/src/i18n/hu.tsv | 2 +- crates/ui-egui/src/i18n/id.tsv | 2 +- crates/ui-egui/src/i18n/it.tsv | 2 +- crates/ui-egui/src/i18n/ja.tsv | 2 +- crates/ui-egui/src/i18n/ka.tsv | 2 +- crates/ui-egui/src/i18n/ko.tsv | 2 +- crates/ui-egui/src/i18n/nl.tsv | 2 +- crates/ui-egui/src/i18n/pl.tsv | 2 +- crates/ui-egui/src/i18n/pt-br.tsv | 2 +- crates/ui-egui/src/i18n/ru.tsv | 2 +- crates/ui-egui/src/i18n/sv.tsv | 2 +- crates/ui-egui/src/i18n/tr.tsv | 2 +- crates/ui-egui/src/i18n/uk.tsv | 2 +- crates/ui-egui/src/i18n/vi.tsv | 2 +- crates/ui-egui/src/i18n/zh-hans.tsv | 2 +- crates/ui-egui/src/i18n/zh-hant.tsv | 2 +- docs/file-format-parity.md | 2 +- 33 files changed, 166 insertions(+), 44 deletions(-) diff --git a/crates/engine/tests/import_fuzz.rs b/crates/engine/tests/import_fuzz.rs index 19c6ef93a..ba56718ab 100644 --- a/crates/engine/tests/import_fuzz.rs +++ b/crates/engine/tests/import_fuzz.rs @@ -3401,6 +3401,9 @@ fn arb_ai_token() -> impl Strategy { "/Execution ;", "[5 5] 1e308 d", "-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 0874ec095..4b46f6d2f 100644 --- a/crates/eps/src/import/ai/read/document.rs +++ b/crates/eps/src/import/ai/read/document.rs @@ -485,7 +485,7 @@ impl<'a> Reader<'a> { let mut warnings = self.warnings; 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/style.rs b/crates/eps/src/import/ai/style.rs index ede1ae8e9..6dda903c1 100644 --- a/crates/eps/src/import/ai/style.rs +++ b/crates/eps/src/import/ai/style.rs @@ -93,9 +93,9 @@ pub(super) fn warp_style(n: i64) -> Option<&'static str> { 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 - /// no transparency or effects of their own, and only Outer Glow, Feather and Drop Shadow on the - /// object: what any object is read with. Type takes the other plain styles too (its drawn look + /// It is one fill and one stroke of even width at most, in solid colours or the object's own + /// gradient, with no transparency or effects of their own, and only Outer Glow, Feather and Drop + /// Shadow on the object: what any object is read with. Type takes the other plain styles too (its drawn look /// can't be retyped); other objects keep their drawn look, which is the app's own rendering. pub simple: bool, /// Its fills and strokes, bottom first. @@ -655,7 +655,9 @@ fn style(filters: &[Filter]) -> ArtStyle { st.simple = st.plain && count(true) <= 1 && count(false) <= 1 - && st.items.iter().all(|i| matches!(i, StyleItem::Fill(_) | StyleItem::Stroke { .. } | StyleItem::Gradient { .. })) + // A variable-width stroke curves between its width points, and `WidthProfile` is straight + // between them: the drawn look holds the outline as drawn. + && st.items.iter().all(|i| matches!(i, StyleItem::Fill(_) | StyleItem::Stroke { profile: None, .. } | StyleItem::Gradient { .. })) && st.looks.iter().all(|l| l.visible && l.opacity >= 1.0 && l.blend == BlendMode::Normal && l.effects.is_empty()) && st.effects.iter().all(|e| matches!(e.id.as_str(), "stylize.outerGlow" | "stylize.feather" | "stylize.dropShadow")); st diff --git a/crates/eps/src/import/ai/style/tests.rs b/crates/eps/src/import/ai/style/tests.rs index 964b74f10..e68726afe 100644 --- a/crates/eps/src/import/ai/style/tests.rs +++ b/crates/eps/src/import/ai/style/tests.rs @@ -1,19 +1,59 @@ use super::*; -#[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_variable_width_stroke_keeps_its_profile_for_type_only() { + // GUARDS: the profile is straight between its width points, so other objects read with it drew + // other widths than their drawn look and left gaps next to fills built to meet it. + 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, "{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 a0a24d865..fff315126 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -1869,6 +1869,53 @@ 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. +fn width_profile_style(widths: &[(f64, f64)]) -> String { + let array = |values: Vec| values.iter().map(|v| format!("{v} /Real ,\n")).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/Array :\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", + array(widths.iter().map(|w| w.0).collect()), + array(widths.iter().map(|w| w.1).collect()), + ) +} + +#[test] +fn a_variable_width_stroke_comes_in_as_its_drawn_outline() { + // GUARDS: the object came in as a live stroke whose profile is straight between its width + // points, thinner than the drawn outline, and left gaps next to fills built to meet it. + 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); +} + #[test] fn a_bare_comment_line_in_a_prolog_leaves_its_end_alone() { // GUARDS: a bare `%` line's empty marker matched the empty begin marker of a section that has none. diff --git a/crates/eps/src/import/tests_native.rs b/crates/eps/src/import/tests_native.rs index 94127e8cf..7fef12c65 100644 --- a/crates/eps/src/import/tests_native.rs +++ b/crates/eps/src/import/tests_native.rs @@ -2956,6 +2956,36 @@ fn type_with_a_stacked_art_style_comes_in_as_type_with_that_appearance() { check(&r.document, &r.warnings); } +#[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/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index f3f6cbfbb..29a9648d2 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -4443,7 +4443,7 @@ axis Both И двете @msg it has {_1}, which VectorCraft doesn't read from it yet той съдържа {_1}, който VectorCraft все още не може да разчете @msg its objects are nested too deeply обектите му са вложени твърде дълбоко @msg its section ending with `%{end}` doesn't end разделът му, завършващ на `%{end}`, няма край -@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 gradient “{name}” isn't defined in the file: a plain colour stands in for it градиентът „{name}“ не е дефиниран във файла: заменен е с обикновен цвят @msg an image VectorCraft can't read was left out изображение, което VectorCraft не може да разчете, беше пропуснато @msg an image whose data is cut short was left out изображение с непълни данни беше пропуснато diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index d7efa9c0e..cd0dcbfce 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -4845,7 +4845,7 @@ imageTrace Outline Obris @msg it has {_1}, which VectorCraft doesn't read from it yet sadrži {_1}, što VectorCraft još ne čita iz nje @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 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 gradient “{name}” isn't defined in the file: a plain colour stands in for it gradijent „{name}” nije definisan u datoteci: umjesto njega koristi se obična boja @msg an image VectorCraft can't read was left out izostavljena je slika koju VectorCraft ne može pročitati @msg an image whose data is cut short was left out izostavljena je slika čiji su podaci skraćeni diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index cd6172d6a..8cb87366f 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -5266,7 +5266,7 @@ digits Malayalam Malaiàlam @msg object {id} is not a selected top-level object l’objecte {id} no és un objecte de nivell superior seleccionat @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 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 Offset Path: select paths Desplaça el traçat: selecciona traçats @msg Offset Path: the offset leaves nothing of the selected paths (an inset larger than the shapes) Desplaça el traçat: el desplaçament no deixa res dels traçats seleccionats (una retracció més gran que les formes) @msg only a dock group above or below others collapses, not one beside another només es plega un grup d’acoblament situat a sobre o a sota d’altres, no un al costat d’un altre diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 34c7f4e03..f9a6263a1 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -4885,7 +4885,7 @@ imageTrace Outline Obrys @msg it has {_1}, which VectorCraft doesn't read from it yet obsahuje {_1}, což z něj VectorCraft zatím nečte @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 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 gradient “{name}” isn't defined in the file: a plain colour stands in for it přechod „{name}“ není v souboru definován: zastupuje ho plná barva @msg an image VectorCraft can't read was left out obrázek, který VectorCraft neumí přečíst, byl vynechán @msg an image whose data is cut short was left out obrázek s useknutými daty byl vynechán diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 5a2391342..79fb2d383 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -4537,7 +4537,7 @@ imageTrace Outline Kontur @msg it has {_1}, which VectorCraft doesn't read from it yet den har {_1}, hvilket VectorCraft endnu ikke læser fra den @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 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 gradient “{name}” isn't defined in the file: a plain colour stands in for it gradienten »{name}« er ikke defineret i filen: der bruges en flad farve i stedet @msg an image VectorCraft can't read was left out et billede, som VectorCraft ikke kan læse, blev udeladt @msg an image whose data is cut short was left out et billede, hvis data er afkortet, blev udeladt diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 101bbefac..c23a6f74e 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4494,7 +4494,7 @@ theme Appearance Erscheinungsbild @msg it has {_1}, which VectorCraft doesn't read from it yet es enthält {_1}, was VectorCraft daraus noch nicht liest @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 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 gradient “{name}” isn't defined in the file: a plain colour stands in for it der Verlauf „{name}“ ist in der Datei nicht definiert: Eine einfache Farbe steht an seiner Stelle @msg an image VectorCraft can't read was left out ein Bild, das VectorCraft nicht lesen kann, wurde ausgelassen @msg an image whose data is cut short was left out ein Bild mit abgeschnittenen Daten wurde ausgelassen diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index e704faade..bd8ba5265 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -4527,7 +4527,7 @@ digits Malayalam Μαλαγιάλαμ @msg it has {_1}, which VectorCraft doesn't read from it yet περιέχει {_1}, τα οποία το VectorCraft δεν μπορεί ακόμη να διαβάσει @msg its objects are nested too deeply τα αντικείμενά του έχουν υπερβολικά βαθιά ένθεση @msg its section ending with `%{end}` doesn't end η ενότητά του που τελειώνει σε `%{end}` δεν τερματίζεται -@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 gradient “{name}” isn't defined in the file: a plain colour stands in for it το ντεγκραντέ «{name}» δεν ορίζεται στο αρχείο: χρησιμοποιείται στη θέση του ένα συμπαγές χρώμα @msg an image VectorCraft can't read was left out παραλείφθηκε εικόνα που δεν μπορεί να διαβάσει το VectorCraft @msg an image whose data is cut short was left out παραλείφθηκε εικόνα με περικομμένα δεδομένα diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 4ebddbe9a..9864c746f 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -4579,7 +4579,7 @@ axis Both Ambos @msg it has {_1}, which VectorCraft doesn't read from it yet tienen {_1}, que VectorCraft aún no lee de ellos @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 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 gradient “{name}” isn't defined in the file: a plain colour stands in for it el degradado “{name}” no está definido en el archivo: lo sustituye un color sólido @msg an image VectorCraft can't read was left out se omitió una imagen que VectorCraft no puede leer @msg an image whose data is cut short was left out se omitió una imagen con datos incompletos diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 7042b21f2..59920bf0e 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -4787,7 +4787,7 @@ imageTrace Outline Ääriviiva @msg it has {_1}, which VectorCraft doesn't read from it yet siinä on {_1}, jota VectorCraft ei vielä lue siitä @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 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 gradient “{name}” isn't defined in the file: a plain colour stands in for it liukuväriä “{name}” ei ole määritetty tiedostossa: sen tilalla on tasaväri @msg an image VectorCraft can't read was left out kuva, jota VectorCraft ei voi lukea, jätettiin pois @msg an image whose data is cut short was left out kuva, jonka data on katkennut, jätettiin pois diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 48eb621eb..5a6e28f78 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -4569,7 +4569,7 @@ axis Both Les deux @msg it has {_1}, which VectorCraft doesn't read from it yet elles contiennent {_1}, que VectorCraft n’y lit pas encore @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 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 gradient “{name}” isn't defined in the file: a plain colour stands in for it le dégradé « {name} » n’est pas défini dans le fichier : une couleur unie le remplace @msg an image VectorCraft can't read was left out une image que VectorCraft ne peut pas lire a été ignorée @msg an image whose data is cut short was left out une image aux données tronquées a été ignorée diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 81f948263..f6c92d2ec 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4972,7 +4972,7 @@ imageTrace Outline Körvonal @msg object {0} cannot have children ennek az objektumnak nem lehetnek gyermekei: {0} @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 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 Offset Path: select paths Útvonal eltolása: jelölj ki útvonalakat @msg Offset Path: the offset leaves nothing of the selected paths (an inset larger than the shapes) Útvonal eltolása: az eltolás után semmi sem marad a kijelölt útvonalakból (a befelé eltolás nagyobb az alakzatoknál) @msg onto must name a floating panel az onto egy lebegő panelt kell megnevezzen diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 89633ae99..eeaebb967 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -4537,7 +4537,7 @@ imageTrace Outline Kontur @msg it has {_1}, which VectorCraft doesn't read from it yet memiliki {_1}, yang belum dibaca VectorCraft dari berkas tersebut @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 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 gradient “{name}” isn't defined in the file: a plain colour stands in for it gradien “{name}” tidak didefinisikan dalam berkas: warna polos dipakai sebagai pengganti @msg an image VectorCraft can't read was left out gambar yang tidak dapat dibaca VectorCraft tidak disertakan @msg an image whose data is cut short was left out gambar yang datanya terpotong tidak disertakan diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 6acfaf3c9..dea813c57 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4620,7 +4620,7 @@ axis Both Entrambi @msg it has {_1}, which VectorCraft doesn't read from it yet contengono {_1}, che VectorCraft non vi legge ancora @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 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 gradient “{name}” isn't defined in the file: a plain colour stands in for it la sfumatura “{name}” non è definita nel file: la sostituisce un colore pieno @msg an image VectorCraft can't read was left out un'immagine che VectorCraft non può leggere è stata omessa @msg an image whose data is cut short was left out un'immagine con dati incompleti è stata omessa diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 4bf3cd6bd..4ab5b6b60 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4327,7 +4327,7 @@ swatch Bright Violet ビビッドバイオレット @msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel リンク画像は、元のファイルが見つかるまで、ファイルに保存された低解像度のプレビューで表示されます:リンクパネルで再リンクしてください @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 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 patterns nested too deeply are filled with mid-grey 入れ子が深すぎるパターンは中間のグレーで塗りつぶされています @msg shadings of an unknown type were left out 種類が不明なシェーディングは除外されました @msg shadings whose functions are of an unknown type are filled with their middle colour 関数の種類が不明なシェーディングは中間の色で塗りつぶされています diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index cf411b072..1d478728e 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -4324,7 +4324,7 @@ font-technology Variable ცვლადი @msg it has {_1}, which VectorCraft doesn't read from it yet მას აქვს {_1}, რასაც VectorCraft მისგან ჯერ ვერ კითხულობს @msg its objects are nested too deeply მისი ობიექტები ზედმეტად ღრმადაა ჩადგმული @msg its section ending with `%{end}` doesn't end მისი სექცია, რომელიც `%{end}`-ით მთავრდება, არ სრულდება -@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 gradient “{name}” isn't defined in the file: a plain colour stands in for it გრადიენტი „{name}“ ფაილში განსაზღვრული არ არის: მის ნაცვლად ერთგვაროვანი ფერია @msg an image VectorCraft can't read was left out გამოსახულება, რომელსაც VectorCraft ვერ კითხულობს, გამოტოვებულია @msg an image whose data is cut short was left out გამოსახულება, რომლის მონაცემებიც შეკვეცილია, გამოტოვებულია diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 81f6c600c..8ee22794e 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4254,7 +4254,7 @@ theme Appearance 외관 @msg linked pictures show the low-resolution preview saved in the file until their files are found: relink them in the Links panel 연결된 그림은 해당 파일을 찾을 때까지 파일에 저장된 저해상도 미리 보기로 표시됩니다: 링크 패널에서 다시 연결하세요 @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 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 patterns nested too deeply are filled with mid-grey 너무 깊게 중첩된 패턴은 중간 회색으로 채웁니다 @msg shadings of an unknown type were left out 알 수 없는 종류의 셰이딩은 제외했습니다 @msg shadings whose functions are of an unknown type are filled with their middle colour 함수 종류를 알 수 없는 셰이딩은 중간 색상으로 채웁니다 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 344853683..dd3d5f2e1 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -4549,7 +4549,7 @@ imageTrace Outline Contour @msg it has {_1}, which VectorCraft doesn't read from it yet het bevat {_1}, wat VectorCraft er nog niet uit leest @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 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 gradient “{name}” isn't defined in the file: a plain colour stands in for it verloop „{name}” is niet gedefinieerd in het bestand: er is een effen kleur voor gebruikt @msg an image VectorCraft can't read was left out een afbeelding die VectorCraft niet kan lezen, is weggelaten @msg an image whose data is cut short was left out een afbeelding waarvan de gegevens zijn afgekapt, is weggelaten diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 14825f83c..dd89695e4 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -4711,7 +4711,7 @@ imageTrace Outline Kontur @msg it has {_1}, which VectorCraft doesn't read from it yet zawiera {_1}, czego VectorCraft jeszcze z niego nie odczytuje @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 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 gradient “{name}” isn't defined in the file: a plain colour stands in for it gradient „{name}” nie jest zdefiniowany w pliku: zastąpiono go jednolitym kolorem @msg an image VectorCraft can't read was left out pominięto obraz, którego VectorCraft nie potrafi odczytać @msg an image whose data is cut short was left out pominięto obraz z uciętymi danymi diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 96a31c3e5..ec78a8fc8 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -4150,7 +4150,7 @@ axis Both Ambos @msg it has {_1}, which VectorCraft doesn't read from it yet tem {_1}, que o VectorCraft ainda não lê desse arquivo @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 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 gradient “{name}” isn't defined in the file: a plain colour stands in for it o gradiente “{name}” não está definido no arquivo: uma cor sólida o substitui @msg an image VectorCraft can't read was left out uma imagem que o VectorCraft não consegue ler foi omitida @msg an image whose data is cut short was left out uma imagem com dados incompletos foi omitida diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index fdb51b80d..2c128ea48 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -4615,7 +4615,7 @@ axis Both Оба @msg it has {_1}, which VectorCraft doesn't read from it yet в них есть {_1}, которые VectorCraft пока не читает @msg its objects are nested too deeply объекты в них вложены слишком глубоко @msg its section ending with `%{end}` doesn't end раздел, заканчивающийся на `%{end}`, не заканчивается -@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 gradient “{name}” isn't defined in the file: a plain colour stands in for it градиент «{name}» не определён в файле: вместо него используется сплошной цвет @msg an image VectorCraft can't read was left out изображение, которое VectorCraft не может прочитать, пропущено @msg an image whose data is cut short was left out изображение с обрезанными данными пропущено diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index e2dddf022..16c4a111c 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -4478,7 +4478,7 @@ axis Both Båda @msg it has {_1}, which VectorCraft doesn't read from it yet den innehåller {_1}, som VectorCraft inte kan läsa från den än @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 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 gradient “{name}” isn't defined in the file: a plain colour stands in for it gradienten ”{name}” är inte definierad i filen: en enfärgad fyllning används i stället @msg an image VectorCraft can't read was left out en bild som VectorCraft inte kan läsa utelämnades @msg an image whose data is cut short was left out en bild med avkortade data utelämnades diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 9ecf2ccf9..67dbc32ca 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -4321,7 +4321,7 @@ axis Both Her İkisi @msg it has {_1}, which VectorCraft doesn't read from it yet VectorCraft'ın henüz okumadığı {_1} var @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 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 gradient “{name}” isn't defined in the file: a plain colour stands in for it “{name}” degradesi dosyada tanımlı değil: onun yerine düz bir renk geliyor @msg an image VectorCraft can't read was left out VectorCraft'ın okuyamadığı bir resim dışarıda bırakıldı @msg an image whose data is cut short was left out verileri kısa kesilen bir resim dışarıda bırakıldı diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index d348440a2..4f719e80e 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4604,7 +4604,7 @@ axis Both Обидві @msg it has {_1}, which VectorCraft doesn't read from it yet у них є {_1}, які VectorCraft поки не читає @msg its objects are nested too deeply об’єкти в них вкладені надто глибоко @msg its section ending with `%{end}` doesn't end розділ, що закінчується на `%{end}`, не закінчується -@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 gradient “{name}” isn't defined in the file: a plain colour stands in for it градієнт «{name}» не визначено у файлі: замість нього використовується суцільний колір @msg an image VectorCraft can't read was left out зображення, яке VectorCraft не може прочитати, пропущено @msg an image whose data is cut short was left out зображення з обрізаними даними пропущено diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 09652ede2..28b52b02e 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4626,7 +4626,7 @@ theme Appearance Giao diện @msg object {0} cannot have children đối tượng {0} không thể có phần tử con @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 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 Offset Path: select paths Đường dẫn độ lệch: hãy chọn đường dẫn @msg Offset Path: the offset leaves nothing of the selected paths (an inset larger than the shapes) Đường dẫn độ lệch: độ lệch không để lại gì từ các đường dẫn đã chọn (độ lệch vào trong lớn hơn hình) @msg onto must name a floating panel onto phải đặt tên bảng đang thả nổi diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 841bd4528..29278d6f0 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -4050,7 +4050,7 @@ imageTrace Outline 轮廓 @msg an image VectorCraft can't read was left out 已略去 VectorCraft 无法读取的图像 @msg an image whose data is cut short was left out 已略去数据不完整的图像 @msg gradient “{name}” isn't defined in the file: a plain colour stands in for it 文件中未定义渐变“{name}”:已改用纯色代替 -@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 patterns nested too deeply are filled with mid-grey 嵌套过深的图案以中灰色填充 @msg shadings of an unknown type were left out 已略去未知类型的着色 @msg shadings whose functions are of an unknown type are filled with their middle colour 函数类型未知的着色以其中间颜色填充 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index a515d59a7..f8cb6bacf 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1973,7 +1973,7 @@ file-filter Templates 範本 @msg an image VectorCraft can't read was left out 已略過 VectorCraft 無法讀取的影像 @msg an image whose data is cut short was left out 已略過資料不完整的影像 @msg gradient “{name}” isn't defined in the file: a plain colour stands in for it 檔案中未定義漸層「{name}」:已改用單色替代 -@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 patterns nested too deeply are filled with mid-grey 巢狀層級過深的圖樣以中灰色填滿 @msg shadings of an unknown type were left out 已略過類型未知的著色 @msg shadings whose functions are of an unknown type are filled with their middle colour 函數類型未知的著色以其中間顏色填滿 diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index de7e1cfce..8c666c11a 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -84,7 +84,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, 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), linear and radial gradients, pattern fills with their patterns as pattern swatches (#1025), 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), guides, and every artboard where it is. 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 an Outer Glow or Drop Shadow comes in as itself with them; one with more, other effects, a brush or dashes aligned to corners comes in as its drawn look (a group named after it). 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, Transform (with copies), Drop Shadow, Outer Glow, Feather, Gaussian Blur and Bas Relief (its settings at their defaults), and a style with others keeps the type's 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 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); the page's drawing of it (often in pieces) is left out. 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. 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). Symbols, 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 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). 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). 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, 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), linear and radial gradients, pattern fills with their patterns as pattern swatches (#1025), 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), guides, and every artboard where it is. 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) with an Outer Glow or Drop Shadow comes in as itself with them; one with more, other effects, a brush, dashes aligned to corners or a variable-width stroke comes in as its drawn look (a group named after it). 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, Transform (with copies), Drop Shadow, Outer Glow, Feather, Gaussian Blur and Bas Relief (its settings at their defaults), and a style with others keeps the type's 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 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); the page's drawing of it (often in pieces) is left out. 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. 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). Symbols, 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 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). 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). 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. ## Format by format From aecff872a52eb485abca7970fd5a7f9b25b6c5ee Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 1 Nov 2026 18:50:43 -0800 Subject: [PATCH 2/7] .ai import: a variable-width stroke whose look draws nothing comes in as itself An object with a variable-width stroke whose drawn look holds nothing that draws, apart from copies read from %_ lines, has no outline to keep. It now comes in as the object with its width profile instead of being dropped. --- crates/eps/src/import/ai/read/ops.rs | 33 +++++++++++++++++++++++++++- crates/eps/src/import/ai/style.rs | 13 +++++++---- crates/eps/src/import/ai/tests.rs | 18 +++++++++++++++ 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/crates/eps/src/import/ai/read/ops.rs b/crates/eps/src/import/ai/read/ops.rs index 7e7b79746..08a71b853 100644 --- a/crates/eps/src/import/ai/read/ops.rs +++ b/crates/eps/src/import/ai/read/ops.rs @@ -531,6 +531,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(appearance) = self .styles @@ -658,7 +676,8 @@ fn styled(object: &Node, look: &Node, st: &crate::import::ai::style::ArtStyle, p // A text object's slot (an empty group standing for it) takes the style as the type will. let slot = slot_of(object.name.as_deref()).is_some(); if !st.plain - || (!slot && !st.simple) + // A look with no outline in it to keep takes a variable-width stroke as the object's own. + || (!slot && !st.simple && (!st.uneven || draws(look, &HashSet::new()))) || (object.is_container() && !slot && !matches!(object.kind, NodeKind::Compound { .. }) && !st.items.is_empty()) { return None; @@ -733,3 +752,15 @@ fn strip(n: &mut Node, hidden: &HashSet) { } } } + +/// Whether `n` holds anything that draws, apart from the children read from `%_` lines (`hidden`): +/// a node other than a group or a compound path. +fn draws(n: &Node, hidden: &HashSet) -> bool { + if hidden.contains(&n.id) { + return false; + } + match n.children() { + Some(children) if matches!(n.kind, NodeKind::Group { .. } | NodeKind::Compound { .. }) => children.iter().any(|c| draws(c, hidden)), + _ => true, + } +} diff --git a/crates/eps/src/import/ai/style.rs b/crates/eps/src/import/ai/style.rs index 6dda903c1..7b6c5facc 100644 --- a/crates/eps/src/import/ai/style.rs +++ b/crates/eps/src/import/ai/style.rs @@ -98,6 +98,9 @@ pub(super) struct ArtStyle { /// Shadow on the object: what any object is read with. Type takes the other plain styles too (its drawn look /// can't be retyped); other objects keep their drawn look, which is the app's own rendering. pub simple: bool, + /// It would be [`Self::simple`] but for a variable-width stroke: an object whose drawn look + /// is empty takes it with its profile. + pub uneven: bool, /// Its fills and strokes, bottom first. pub items: Vec, /// Each item's own visibility, transparency and effects, in the order of [`Self::items`]. @@ -652,14 +655,16 @@ fn style(filters: &[Filter]) -> ArtStyle { } st.plain = b.plain; 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 - // A variable-width stroke curves between its width points, and `WidthProfile` is straight - // between them: the drawn look holds the outline as drawn. - && st.items.iter().all(|i| matches!(i, StyleItem::Fill(_) | StyleItem::Stroke { profile: None, .. } | StyleItem::Gradient { .. })) + && st.items.iter().all(|i| matches!(i, StyleItem::Fill(_) | StyleItem::Stroke { .. } | StyleItem::Gradient { .. })) && st.looks.iter().all(|l| l.visible && l.opacity >= 1.0 && l.blend == BlendMode::Normal && l.effects.is_empty()) && st.effects.iter().all(|e| matches!(e.id.as_str(), "stylize.outerGlow" | "stylize.feather" | "stylize.dropShadow")); + // A variable-width stroke curves between its width points, and `WidthProfile` is straight + // between them: the drawn look holds the outline as drawn. + let even = st.items.iter().all(|i| !matches!(i, StyleItem::Stroke { profile: Some(_), .. })); + (st.simple, st.uneven) = (simple && even, simple && !even); st } diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index fff315126..c4786fa0d 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -1916,6 +1916,24 @@ fn a_variable_width_stroke_comes_in_as_its_drawn_outline() { 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, and ones whose group or compound path holds only a copy read from `%_` lines. + 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")] { + 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 a_bare_comment_line_in_a_prolog_leaves_its_end_alone() { // GUARDS: a bare `%` line's empty marker matched the empty begin marker of a section that has none. From 655a16fa674b1d5c2a588907806eed03a6e000ea Mon Sep 17 00:00:00 2001 From: ruchirguitar <113586074+ruchirguitar@users.noreply.github.com> Date: Sun, 8 Nov 2026 17:02:29 +0530 Subject: [PATCH 3/7] #5925: add collapse arrows to floating panels and rail flyouts --- crates/ui-egui/src/panel_docking.rs | 50 +++++++- .../ui-egui/src/panel_docking/native_tests.rs | 28 +++++ crates/ui-egui/src/panel_docking/tests.rs | 115 ++++++++++++++++++ docs/gaps.md | 5 +- docs/roadmap.md | 5 +- docs/target-app-parity.md | 7 +- docs/ui-parity.md | 5 +- 7 files changed, 204 insertions(+), 11 deletions(-) diff --git a/crates/ui-egui/src/panel_docking.rs b/crates/ui-egui/src/panel_docking.rs index ff39d90cf..a479b227f 100644 --- a/crates/ui-egui/src/panel_docking.rs +++ b/crates/ui-egui/src/panel_docking.rs @@ -1414,13 +1414,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 a7c661404..60130db74 100644 --- a/crates/ui-egui/src/panel_docking/native_tests.rs +++ b/crates/ui-egui/src/panel_docking/native_tests.rs @@ -1163,3 +1163,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 60b59a2a8..7543beaca 100644 --- a/crates/ui-egui/src/panel_docking/tests.rs +++ b/crates/ui-egui/src/panel_docking/tests.rs @@ -759,3 +759,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/docs/gaps.md b/docs/gaps.md index 6780782ab..176607212 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -1,6 +1,6 @@ # Gaps -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-07 · **Change:** minor (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-08 · **Change:** minor (floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; 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 @@ -106,6 +106,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). @@ -817,6 +819,7 @@ trap, and the defaults (0.25 pt, 100%, 40%) are VectorCraft's own, the pages giv | 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-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/roadmap.md b/docs/roadmap.md index c4246c711..944925518 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-07 · **Change:** minor (Indian-language hyphenation review and license allowlist, #4947; Hindi catalog and contributor loanword preferences; full wording review in progress; Effect dialogs: a colour swatch beside a hex colour opens the Color Picker, #5365; shape tools move the selection from its centre point and turn it from the zone outside a box corner, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-08 · **Change:** minor (floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; Indian-language hyphenation review and license allowlist, #4947; Hindi catalog and contributor loanword preferences; full wording review in progress; Effect dialogs: a colour swatch beside a hex colour opens the Color Picker, #5365; shape tools move the selection from its centre point and turn it from the zone outside a box corner, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x) ## Feature submission evidence @@ -143,7 +143,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), 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), 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 | @@ -180,6 +180,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-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. | | 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-04 | minor | New Document: the units chosen in Preset Details belong to the chosen preset; Recent and Saved cards keep their own units; scores and estimates unchanged. | diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 1aef04833..7a07ff4bd 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-07 · **Change:** minor (the canvas keeps selection outlines and anchors between frames; Indian-language hyphenation review and license allowlist, #4947; Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-08 · **Change:** minor (floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; the canvas keeps selection outlines and anchors between frames; Indian-language hyphenation review and license allowlist, #4947; Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026) ## Feature submission evidence @@ -212,7 +212,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** | | @@ -517,6 +517,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. - **Status-bar pointer readout (2026-10-18):** uses the same effective ruler origin as the position fields, then formats the relative X/Y in General units. The absolute pointer position, document, selection, Undo/Redo and saved state are preserved. Three `chrome::tests::status_coordinates_*` painted-text regressions cover zero/positive/negative origins, unit conversion, global/artboard mode, active artboard, reset and missing pointer/document. Original headless status-bar captures were reviewed before and after the change. Scores and hours are unchanged. @@ -1729,6 +1731,7 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s | 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-06 | minor | Direct Selection: with Snap to Grid, a dragged straight segment lands its anchors on the grid (#3835); evidence added; scores unchanged. | | 2026-11-06 | minor | Snap to Glyph › Glyph Bounds: the corners of each glyph's box are points a drawn point lands on, also on a curved side (`B`), and the box turns with rotated type, its edges then Angular Guides (#5112; [Snap to Glyph options](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/snap-to-glyph-options.html)); scores and estimates unchanged. | | 2026-11-06 | minor | Drop Shadow: a swatch beside Color opens the Color Picker over the dialog, as the other effect colour fields do (#5364); correction: the #5365 entry listed Drop Shadow, whose Color is drawn outside the shared grid and had no swatch; scores unchanged. | diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 4efbee4aa..85ea0a2f3 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-06 · **Change:** minor (Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; shape tools: a press on the selection's centre point moves it and one in the rotate zone outside a box corner turns it, #5477; scores and estimates unchanged) · **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-08 · **Change:** minor (floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; shape tools: a press on the selection's centre point moves it and one in the rotate zone outside a box corner turns it, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged) ## Feature submission evidence @@ -334,6 +334,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 | @@ -478,6 +480,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-06 | minor | Keyboard shortcuts: Cmd/Ctrl+Alt+. and Cmd/Ctrl+Alt+, (Stroke Weight Up and Down) step each selected stroke's weight, or the next object drawn's (#5797); scores and estimates unchanged | | 2026-11-06 | minor | Properties: Quick Actions has Repeat Options for one selected radial, grid or mirror repeat, opening Object › Repeat › Options with its values (#5181); scores and estimates unchanged | | 2026-11-06 | minor | Control bar: with type selected, a Paragraph: link (the Paragraph panel in a popover) and the Align left, Align center and Align right buttons follow the Character controls; the bar leaves them out from its right end when short of room, after X/Y/W/H and the Align buttons (#5372); scores and estimates unchanged | From a4612ea88b258652b500026341f20a7e27075011 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 8 Nov 2026 20:51:42 -0800 Subject: [PATCH 4/7] Docs: docs/mcp.md lists save_file once The tools table had two save_file rows. The shorter one is out of date, so it is removed and the current row is unchanged. --- docs/mcp.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/mcp.md b/docs/mcp.md index 92bb1957e..04ff9ffe5 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. | From b31c09bc28e5f4d5bbb6b2ce50631eeb9e086928 Mon Sep 17 00:00:00 2001 From: vecteza Date: Mon, 9 Nov 2026 09:46:21 +0500 Subject: [PATCH 5/7] Workspace: the Tools panel docks on the right too, left of the panels The Tools panel docked only on the left (the window's edge, or beside the canvas with Keep Tools Beside the Canvas). Dropped on the canvas's right edge, just left of the panels, or anywhere along the window's right edge, it now docks on the right, between the canvas and the panels: it is drawn inside the canvas panel at its right edge, which reserves room for it, so it follows the panels however they are arranged. Dragged back to the left it docks there again; Escape puts it back on the side it came from. - The side is kept with the preferences and in saved workspaces (built-in workspaces and state saved without it dock it on the left). - Tool flyouts and the Edit Toolbar drawer open toward the canvas from a toolbar docked right (toolbar::room / toolbar::beside). - window.panel.dock {panel: "tools", side?: left|right (or Start|End)}, default the side it docked at last; replies carry the side. The grip's tip names both edges; translated in every catalog. - Test: the_tools_panel_docks_on_the_right_left_of_the_panels. --- crates/ui-egui/src/app.rs | 3 +- crates/ui-egui/src/edit_toolbar.rs | 2 +- crates/ui-egui/src/i18n/bg.tsv | 3 +- crates/ui-egui/src/i18n/bs.tsv | 3 +- crates/ui-egui/src/i18n/ca.tsv | 3 +- crates/ui-egui/src/i18n/cs.tsv | 3 +- crates/ui-egui/src/i18n/da.tsv | 3 +- crates/ui-egui/src/i18n/de.tsv | 3 +- crates/ui-egui/src/i18n/el.tsv | 3 +- crates/ui-egui/src/i18n/es.tsv | 3 +- crates/ui-egui/src/i18n/fi.tsv | 3 +- crates/ui-egui/src/i18n/fr.tsv | 3 +- crates/ui-egui/src/i18n/hi.tsv | 3 +- crates/ui-egui/src/i18n/hu.tsv | 3 +- crates/ui-egui/src/i18n/id.tsv | 3 +- crates/ui-egui/src/i18n/it.tsv | 3 +- crates/ui-egui/src/i18n/ja.tsv | 3 +- crates/ui-egui/src/i18n/ka.tsv | 3 +- crates/ui-egui/src/i18n/ko.tsv | 3 +- crates/ui-egui/src/i18n/nl.tsv | 3 +- crates/ui-egui/src/i18n/pl.tsv | 3 +- crates/ui-egui/src/i18n/pt-br.tsv | 3 +- crates/ui-egui/src/i18n/ru.tsv | 3 +- crates/ui-egui/src/i18n/sv.tsv | 3 +- crates/ui-egui/src/i18n/tr.tsv | 3 +- crates/ui-egui/src/i18n/uk.tsv | 3 +- crates/ui-egui/src/i18n/vi.tsv | 3 +- crates/ui-egui/src/i18n/zh-hans.tsv | 3 +- crates/ui-egui/src/i18n/zh-hant.tsv | 3 +- crates/ui-egui/src/menus/ui_commands.rs | 2 +- crates/ui-egui/src/panel_docking/tests.rs | 39 ++++++++++++ crates/ui-egui/src/state.rs | 6 ++ crates/ui-egui/src/toolbar.rs | 77 ++++++++++++++++++----- crates/ui-egui/src/tools_float.rs | 68 +++++++++++++++----- crates/ui-egui/src/workspaces.rs | 5 ++ 35 files changed, 223 insertions(+), 60 deletions(-) diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs index a75fecde2..6ae2f6812 100644 --- a/crates/ui-egui/src/app.rs +++ b/crates/ui-egui/src/app.rs @@ -558,7 +558,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/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..15374b930 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 първо изберете текст @@ -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..101f5632f 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 @@ -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..0ad7a05b9 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 @@ -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..f4f6a01f8 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 @@ -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..3095b6477 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 @@ -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..0747c8aba 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 @@ -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..fdcebaee2 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} @@ -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..035bdceb6 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 @@ -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..b2104d773 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 @@ -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..5820de83a 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 @@ -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..a6ce918ab 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 पीछे खींचना @@ -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..076565e8c 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 @@ -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..b56e8d7e6 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 @@ -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..9f731a6a9 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 @@ -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..7a943b24c 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. @@ -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..1efebf897 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 საყრდენი წერტილის ხელსაწყო @@ -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..a98e905e1 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 숨기기 @@ -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..93b9371ee 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 @@ -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..61ae817ef 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 @@ -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..922d408ed 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” @@ -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..b3c66d0f8 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 @@ -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..bae0b3f7e 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 @@ -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..6a62f228b 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 @@ -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..f4b29e59b 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 спочатку виділіть текст @@ -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..dadb37d21 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ẽ @@ -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..8b2456f65 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -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..a1da1f933 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -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/tests.rs b/crates/ui-egui/src/panel_docking/tests.rs index 98c625743..5eba37c31 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") diff --git a/crates/ui-egui/src/state.rs b/crates/ui-egui/src/state.rs index d7f3cc579..ccc8fc86c 100644 --- a/crates/ui-egui/src/state.rs +++ b/crates/ui-egui/src/state.rs @@ -472,6 +472,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, @@ -1009,6 +1014,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(); From 2f42feb848b3f76d970c20636df76fd5b01b5c0a Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 8 Nov 2026 21:17:22 -0800 Subject: [PATCH 6/7] Plug-ins: docs say when pixelsOmitted is set An embedded image also comes with pixelsOmitted: true when its data is missing, its width or height is 0, its data cannot be decoded within the decoder's memory limit, or its dimensions differ from pixelSize. The entry tells plug-ins to check the flag before reading pixelsBase64. --- docs/plugins.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 `{}`. From db280aa87eb08699005efd3df570fb15eabfa8e5 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 8 Nov 2026 21:16:50 -0800 Subject: [PATCH 7/7] New Document: the name field is labeled Document Name The name field under Preset Details had no label and sat directly above the Preset Name row, so it read as the name of the preset. It now has a Document Name caption. Every catalog already translates that string. --- crates/ui-egui/src/dialogs/new_document.rs | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) 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());