From ccc7e44078eee7796882858d378b30f75026ad1b Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Mon, 2 Nov 2026 11:25:25 +0700 Subject: [PATCH] .ai import: multi-layer masks open as a clipping group in each layer Squashed from #5117 onto main. --- crates/engine/tests/import_fuzz.rs | 49 +++++ crates/eps/src/import/ai/read.rs | 9 + crates/eps/src/import/ai/read/containers.rs | 1 + crates/eps/src/import/ai/read/directives.rs | 5 +- crates/eps/src/import/ai/read/masks.rs | 185 ++++++++++++++++ crates/eps/src/import/ai/read/ops.rs | 3 +- crates/eps/src/import/ai/tests.rs | 221 ++++++++++++++++++++ crates/eps/src/import/tests_illustrator.rs | 19 ++ crates/ui-egui/src/i18n/ca.tsv | 5 + crates/ui-egui/src/i18n/cs.tsv | 5 + crates/ui-egui/src/i18n/de.tsv | 5 + crates/ui-egui/src/i18n/el.tsv | 5 + crates/ui-egui/src/i18n/es.tsv | 5 + crates/ui-egui/src/i18n/fi.tsv | 5 + crates/ui-egui/src/i18n/fr.tsv | 5 + crates/ui-egui/src/i18n/hu.tsv | 5 + crates/ui-egui/src/i18n/it.tsv | 5 + crates/ui-egui/src/i18n/ja.tsv | 5 + crates/ui-egui/src/i18n/ka.tsv | 5 + crates/ui-egui/src/i18n/ko.tsv | 5 + crates/ui-egui/src/i18n/pl.tsv | 5 + crates/ui-egui/src/i18n/pt-br.tsv | 5 + crates/ui-egui/src/i18n/ru.tsv | 5 + crates/ui-egui/src/i18n/sv.tsv | 5 + crates/ui-egui/src/i18n/tr.tsv | 5 + crates/ui-egui/src/i18n/uk.tsv | 5 + crates/ui-egui/src/i18n/vi.tsv | 5 + crates/ui-egui/src/i18n/zh-hans.tsv | 5 + crates/ui-egui/src/i18n/zh-hant.tsv | 5 + docs/file-format-parity.md | 2 + 30 files changed, 597 insertions(+), 2 deletions(-) create mode 100644 crates/eps/src/import/ai/read/masks.rs diff --git a/crates/engine/tests/import_fuzz.rs b/crates/engine/tests/import_fuzz.rs index 34bb1a436..9adbd2971 100644 --- a/crates/engine/tests/import_fuzz.rs +++ b/crates/engine/tests/import_fuzz.rs @@ -3322,6 +3322,48 @@ proptest! { let text = mutate_text(NAMED_PROLOG_AI, cut, &edits); survive("mutated PostScript-form .ai", || vectorcraft_engine::cmd::fileio::load("x.ai", text.as_bytes()).ok().map(|l| l.doc))?; } + + /// Multi-layer masks (`Mb` `W n` `Md` … `MB`) over any number of layers, with tokens + /// dropped or put anywhere (the mask operators, layer ends, groups, clipping paths) and the + /// data cut short. + #[test] + fn ai_multi_layer_masks_never_panic( + layers in 1usize..5, + aliases in any::(), + edits in prop::collection::vec((0usize..64, prop::sample::select(vec!["", "Mb", "Md", "MB", "Mi", "LB", "u", "U", "q", "Q", "W n", "h W n"])), 0..4), + cut in prop::option::of(0usize..64), + ) { + use vectorcraft_testkit::ai; + let (begin, define) = if aliases { ("Mv", "ME") } else { ("Mb", "Md") }; + let mut tokens: Vec = vec![]; + for i in 0..layers { + if i > 0 { + tokens.push(format!("LB\n%AI5_EndLayer--\n%AI5_BeginLayer\n1 1 1 1 0 1 1 79 128 255 Lb\n(L{i}) Ln\n")); + } else { + tokens.extend([begin.to_string(), "40 40 m 120 40 L 120 120 L 40 120 L h W n".into(), define.to_string()]); + } + let x = 10 * i; + tokens.push(format!("0.5 0 0 0 k {x} {x} m {} {x} L {} {} L f", x + 140, x + 140, x + 140)); + } + tokens.extend(["MB".to_string(), "150 150 m 190 150 L 190 190 L f".into()]); + for (at, with) in &edits { + let at = at % (tokens.len() + 1); + if with.is_empty() { + if at < tokens.len() { + tokens.remove(at); + } + } else { + tokens.insert(at, with.to_string()); + } + } + if let Some(cut) = cut { + tokens.truncate(cut); + } + let data = ai::editing_data(200.0, 100.0, &format!("%AI5_BeginLayer\n1 1 1 1 0 1 1 79 128 255 Lb\n(L0) Ln\n{}\nLB\n", tokens.join("\n"))); + survive("multi-layer masks", || Some(vectorcraft_eps::layered_ai(data.as_bytes(), Document::new(200.0, 100.0), vec![], false, false, 1.0).0))?; + let eps = ai::eps(data.as_bytes(), ai::page_ps()); + survive("multi-layer masks in an EPS", || vectorcraft_engine::cmd::fileio::load("x.eps", &eps).ok().map(|l| l.doc))?; + } } /// An operand of a layer's options (`Lb`): a flag, a colour index or component, a percentage, or @@ -3390,6 +3432,13 @@ fn arb_ai_token() -> impl Strategy { "Lb", "Ln", "(name) Ln", + "\n%AI5_BeginLayer\n1 1 1 1 0 1 1 79 128 255 Lb\n", + "Mb", + "Mv", + "Md", + "ME", + "Mi", + "MB", "g", "G", "k", diff --git a/crates/eps/src/import/ai/read.rs b/crates/eps/src/import/ai/read.rs index 0202b43c2..c3b4915ae 100644 --- a/crates/eps/src/import/ai/read.rs +++ b/crates/eps/src/import/ai/read.rs @@ -4,6 +4,8 @@ //! - Layers: `%AI5_BeginLayer`, `visible preview unlocked printing dimmed … colorIndex r g b //! template dimPercent … Lb` (ten operands in the legacy format, without the one before the //! colour index; see `Reader::layer_attrs`), `(name) Ln`, its art, `LB`. +//! - Multi-layer masks `Mb` `W n` `Md` … `MB`, whose objects can go on past `LB` into the +//! next layers: a clipping group in each layer, read by [`masks`]. //! - Ruler guides: a straight `(N) *` guide path, directly on a layer, that runs across the whole //! canvas (16,383 points around the template box's centre) or from edge to edge of one artboard //! is a ruler guide on that layer ([`Document::guides`]); other guide paths stay paths. @@ -87,6 +89,7 @@ mod art; mod containers; mod directives; mod document; +mod masks; mod on_path; mod ops; mod types; @@ -308,6 +311,8 @@ struct Reader<'a> { type_state: legacy_text::TypeState, /// The graph being read (see [`Reader::graph_record`]). graph: Option, + /// The multi-layer mask being read (`Mb` … `MB`), which goes on past a layer's end. + multi_mask: Option, } /// A section comment's marker without its `AI_` prefix and value (`AI14_BeginSymbol` → @@ -406,6 +411,7 @@ impl<'a> Reader<'a> { text: None, type_state: legacy_text::TypeState::default(), graph: None, + multi_mask: None, } } @@ -436,6 +442,9 @@ impl<'a> Reader<'a> { if self.frames.iter().any(|f| matches!(f.kind, Kind::Layer(_))) { return Err("it ends inside a layer".into()); } + if self.multi_mask.is_some() { + return Err("it ends inside a multi-layer mask".into()); + } Ok(()) } } diff --git a/crates/eps/src/import/ai/read/containers.rs b/crates/eps/src/import/ai/read/containers.rs index 3638a5189..173e22ed0 100644 --- a/crates/eps/src/import/ai/read/containers.rs +++ b/crates/eps/src/import/ai/read/containers.rs @@ -75,6 +75,7 @@ impl<'a> Reader<'a> { } pub(super) fn end_container(&mut self, what: &str, hidden: bool) -> Result<(), String> { + self.multi_mask_container_ends()?; let f = match what { "U" => self.close(what, |k| matches!(k, Kind::Group))?, "*U" => self.close(what, |k| matches!(k, Kind::Compound))?, diff --git a/crates/eps/src/import/ai/read/directives.rs b/crates/eps/src/import/ai/read/directives.rs index 4814b8704..2d930b31a 100644 --- a/crates/eps/src/import/ai/read/directives.rs +++ b/crates/eps/src/import/ai/read/directives.rs @@ -130,7 +130,10 @@ impl<'a> Reader<'a> { _ => {} } if line.starts_with("AI5_BeginLayer") { - return self.open(Kind::Layer(Box::default()), false); + self.multi_mask_layer_starts()?; + self.open(Kind::Layer(Box::default()), false)?; + self.multi_mask_go_on(); + return Ok(()); } if line.starts_with("AI5_BeginRaster") { self.raster = Some(Raster::default()); diff --git a/crates/eps/src/import/ai/read/masks.rs b/crates/eps/src/import/ai/read/masks.rs new file mode 100644 index 000000000..c07ab5868 --- /dev/null +++ b/crates/eps/src/import/ai/read/masks.rs @@ -0,0 +1,185 @@ +//! Multi-layer masks in the legacy format (version 7 specification §10): `Mb`, the mask's path +//! painted with `W`, `Md`, the masked objects (in that layer and, past its `LB`, in the layers +//! after it), `MB`. The objects of each layer the mask covers become one clipping group in that +//! layer, its clipping path a copy of the mask's. + +use super::types::{Frame, Kind, MAX_NODES}; +use super::{Arc, Node, NodeKind, Reader}; + +/// The note for a file with a multi-layer mask. +pub(crate) const PER_LAYER: &str = "a mask that covers several layers comes in as a clipping group in each layer it covers"; + +/// A multi-layer mask being read. +#[derive(Debug)] +pub(super) struct MultiMask { + /// The mask's clipping path, once `Md` has defined it. + mask: Option, + /// What each copy of the mask adds to the objects read: its objects and their points. + weight: usize, + /// How many frames were open when the masked objects started in the frame they go in; 0 + /// outside the layers, until a layer starts. + depth: usize, + /// Where the masked objects start among that frame's children. + start: usize, +} + +impl Reader<'_> { + /// A multi-layer mask's operator `w`: `Mb` (or `Mv`) starts the mask, `Md` (or `ME`) defines + /// it, `MB` ends it, and `Mi` changes nothing here. + pub(super) fn multi_mask_op(&mut self, w: &str) -> Result<(), String> { + match w { + "Mb" | "Mv" => self.begin_multi_mask(), + "Md" | "ME" => self.define_multi_mask(), + "MB" => self.end_multi_mask(), + _ => Ok(()), + } + } + + /// `LB`: the layer ends, and a multi-layer mask goes on in the frame around it. + pub(super) fn end_layer_in_mask(&mut self) -> Result<(), String> { + self.multi_mask_layer_ends()?; + self.end_layer()?; + self.multi_mask_go_on(); + Ok(()) + } + + /// `Mb`: a multi-layer mask starts. + fn begin_multi_mask(&mut self) -> Result<(), String> { + if self.multi_mask.is_some() { + return Err("a multi-layer mask's operators are out of order".into()); + } + self.end_path_quietly(); + let start = self.frames.last().map_or(0, |f| f.children.len()); + self.multi_mask = Some(MultiMask { mask: None, weight: 0, depth: self.frames.len(), start }); + Ok(()) + } + + /// `Md`: the clipping path painted last since `Mb`, in the same group or layer, is the mask. + fn define_multi_mask(&mut self) -> Result<(), String> { + let depth = self.frames.len(); + let Some(m) = self.multi_mask.as_mut() else { return Err("a multi-layer mask's operators are out of order".into()) }; + if m.mask.is_some() || depth == 0 { + return Err("a multi-layer mask's operators are out of order".into()); + } + if m.depth != depth { + return Err("a group crosses a multi-layer mask".into()); + } + let Some(f) = self.frames.last_mut() else { return Err("a multi-layer mask's operators are out of order".into()) }; + let Some(at) = f.clips.iter().copied().filter(|i| *i >= m.start && *i < f.children.len()).max() else { + return Err("a multi-layer mask needs one clipping path".into()); + }; + let mask = f.children.remove(at); + f.clips.retain(|i| *i != at); + for i in &mut f.clips { + if *i > at { + *i -= 1; + } + } + let mut weight = mask.count_with_masks(); + mask.walk(&mut |n| { + if let NodeKind::Path { path, .. } = &n.kind { + weight = weight.saturating_add(path.anchor_count()); + } + }); + m.weight = weight; + m.mask = Some(mask); + self.after_object = false; + Ok(()) + } + + /// `MB`: the masked objects end. + fn end_multi_mask(&mut self) -> Result<(), String> { + if !self.multi_mask.as_ref().is_some_and(|m| m.mask.is_some()) { + return Err("a multi-layer mask's operators are out of order".into()); + } + self.wrap_masked()?; + self.multi_mask = None; + Ok(()) + } + + /// `%AI5_BeginLayer` in a multi-layer mask, before the layer opens: the objects masked so far + /// in the frame around it are grouped, and the mask goes on in the new layer. + pub(super) fn multi_mask_layer_starts(&mut self) -> Result<(), String> { + let Some(m) = self.multi_mask.as_ref() else { return Ok(()) }; + if m.depth != 0 { + if m.mask.is_none() { + return Err("a multi-layer mask's operators are out of order".into()); + } + self.wrap_masked()?; + } + Ok(()) + } + + /// A layer opened or closed in a multi-layer mask: the masked objects go on in the frame now + /// innermost. + pub(super) fn multi_mask_go_on(&mut self) { + let (depth, start) = (self.frames.len(), self.frames.last().map_or(0, |f| f.children.len())); + if let Some(m) = self.multi_mask.as_mut() { + (m.depth, m.start) = (depth, start); + } + } + + /// `LB` in a multi-layer mask, before the layer closes: its masked objects are grouped. + fn multi_mask_layer_ends(&mut self) -> Result<(), String> { + let Some(m) = self.multi_mask.as_ref() else { return Ok(()) }; + if m.mask.is_none() { + return Err("a multi-layer mask's operators are out of order".into()); + } + if !matches!(self.frames.last(), Some(Frame { kind: Kind::Layer(_), .. })) { + return Err("a group crosses a multi-layer mask".into()); + } + self.wrap_masked() + } + + /// A group, compound path or clipping group closing: it must have opened inside the mask. + pub(super) fn multi_mask_container_ends(&self) -> Result<(), String> { + match &self.multi_mask { + Some(m) if m.depth == self.frames.len() => Err("a group crosses a multi-layer mask".into()), + _ => Ok(()), + } + } + + /// The masked objects in the innermost frame become a clipping group, a copy of the mask first. + fn wrap_masked(&mut self) -> Result<(), String> { + let Some(m) = self.multi_mask.as_ref() else { return Ok(()) }; + if m.depth == 0 { + return Ok(()); + } + if m.depth != self.frames.len() { + return Err("a group crosses a multi-layer mask".into()); + } + let Some(mut mask) = m.mask.clone() else { return Err("a multi-layer mask's operators are out of order".into()) }; + let (start, weight) = (m.start, m.weight); + let Some(f) = self.frames.last_mut() else { return Ok(()) }; + if start >= f.children.len() { + return Ok(()); + } + // A clipping path painted among the masked objects clips the whole layer or group, as + // outside a mask: it stays in the frame, outside the masked objects' group. + let painted: std::collections::HashSet = f.clips.iter().filter_map(|i| i.checked_sub(start)).collect(); + let (clips, kids): (Vec<_>, Vec<_>) = f.children.split_off(start).into_iter().enumerate().partition(|(i, _)| painted.contains(i)); + let (clips, kids): (Vec, Vec) = (clips.into_iter().map(|(_, k)| k).collect(), kids.into_iter().map(|(_, k)| k).collect()); + f.clips.retain(|i| *i < start); + if !kids.is_empty() { + self.nodes = self.nodes.saturating_add(weight).saturating_add(1); + if self.nodes > MAX_NODES { + return Err(format!("it has more than {MAX_NODES} objects")); + } + self.reid(&mut mask); + let children = std::iter::once(mask).chain(kids).map(Arc::new).collect(); + let group = Node::new(self.doc.alloc_id(), NodeKind::Group { children, clip: true }); + if let Some(f) = self.frames.last_mut() { + f.children.push(group); + } + self.warn(PER_LAYER); + } + if let Some(f) = self.frames.last_mut() { + for c in clips { + f.clips.push(f.children.len()); + f.children.push(c); + } + } + self.after_object = false; + Ok(()) + } +} diff --git a/crates/eps/src/import/ai/read/ops.rs b/crates/eps/src/import/ai/read/ops.rs index 215db7340..0846c51a8 100644 --- a/crates/eps/src/import/ai/read/ops.rs +++ b/crates/eps/src/import/ai/read/ops.rs @@ -154,7 +154,7 @@ impl<'a> Reader<'a> { self.graph = None; self.warn(GRAPH_UNENDED); } - self.end_layer()? + self.end_layer_in_mask()? } // Paths. "m" | "l" | "L" | "c" | "C" | "v" | "V" | "y" | "Y" => self.path_op(w, &vals)?, @@ -162,6 +162,7 @@ impl<'a> Reader<'a> { // Ends a path that stays open. "H" => {} "W" => self.clip_next = true, + "Mb" | "Mv" | "Md" | "ME" | "MB" | "Mi" => self.multi_mask_op(w)?, // A text object's frame or path. "N" | "n" | "F" | "f" | "S" | "s" | "B" | "b" if self.in_text_path() => self.text_path(), "N" | "n" | "F" | "f" | "S" | "s" | "B" | "b" => self.paint(w, hidden, false)?, diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index 9c5af3a32..f13787604 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -3953,3 +3953,224 @@ fn a_graphs_records_take_their_operands() { assert_eq!(paths(&a[0]), 1, "the empty groups hold no art"); assert_eq!(r.warnings, [GRAPH_PLAIN]); } + +/// Editing data in the legacy format (`%AI5_FileFormat 2.0`, ten `Lb` operands), then `body`. +fn mask_stream7(body: &str) -> String { + format!( + "%!PS-Adobe-3.0\n%%Creator: test\n%%BoundingBox: 0 0 200 200\n%AI5_FileFormat 2.0\n%AI5_ArtSize: 200 200\n\ + %AI3_TemplateBox: 100 100 100 100\n%%EndComments\n%%BeginProlog\n%%EndProlog\n%%BeginSetup\n%%EndSetup\n{body}\n\ + %%PageTrailer\ngsave annotatepage grestore showpage\n%%Trailer\n%%EOF\n" + ) +} + +/// The start of a legacy layer named `name`, `masks` its multi-layer mask flag (`Lb`'s sixth operand). +fn begin7(name: &str, masks: u8) -> String { + format!("%AI5_BeginLayer\n1 1 1 1 0 {masks} 1 79 128 255 Lb\n({name}) Ln\n") +} + +const END7: &str = "LB\n%AI5_EndLayer--\n"; + +/// `Mb`, an 80 by 80 point mask path painted `W n`, `Md` (the version 7 specification's §10.4 order). +fn mask_start() -> String { + format!("Mb\n{}h\nW\nn\nMd\n", rect_path(40.0, 40.0, 120.0, 120.0)) +} + +/// A filled square at `x`. +fn masked(x: f64) -> String { + format!("0.5 0 0 0 k\n{}f\n", rect_path(x, x, x + 140.0, x + 140.0)) +} + +/// A layer's objects as a tree: `p` a path, `c` a clipping path, `o` a compound path, `g[…]` a +/// group, `k[…]` a clipping group, `l[…]` a sublayer. +fn mask_tree(nodes: &[Arc]) -> String { + let one = |n: &Arc| match &n.kind { + NodeKind::Layer { children, .. } => format!("l[{}]", mask_tree(children)), + NodeKind::Group { children, clip: false } => format!("g[{}]", mask_tree(children)), + NodeKind::Group { children, clip: true } => format!("k[{}]", mask_tree(children)), + NodeKind::Path { clipping: true, .. } => "c".into(), + NodeKind::Path { .. } => "p".into(), + NodeKind::Compound { .. } => "o".into(), + k => format!("{k:?}"), + }; + nodes.iter().map(one).collect::>().join(" ") +} + +fn read7(body: &str) -> Structure { + match read(mask_stream7(body).as_bytes()) { + Ok(r) => r, + Err(e) => panic!("{e}"), + } +} + +/// Each layer's tree, and whether the layer itself clips. +fn layer_trees(doc: &Document) -> Vec<(String, String, bool)> { + doc.layers + .iter() + .map(|l| { + let NodeKind::Layer { children, clip, .. } = &l.kind else { panic!("a layer") }; + (l.name.clone().unwrap_or_default(), mask_tree(children), *clip) + }) + .collect() +} + +/// A multi-layer mask (version 7 specification §10.4: `Mb`, the mask path, `Md`, objects in two +/// layers, `MB`) comes in as a clipping group in each layer it covers, each clipped by a copy of +/// the mask's path; what follows `MB` isn't clipped. +#[test] +fn a_multi_layer_mask_clips_each_layer_it_covers() { + let body = format!("{}{}{}{END7}{}{}MB\n{}{END7}", begin7("A", 1), mask_start(), masked(15.0), begin7("B", 1), masked(30.0), masked(150.0)); + let r = read7(&body); + assert_eq!(layer_trees(&r.doc), [("A".to_string(), "k[c p]".to_string(), false), ("B".to_string(), "k[c p] p".to_string(), false)]); + let mask = |l: usize| r.doc.layers[l].children().unwrap()[0].children().unwrap()[0].clone(); + let (a, b) = (mask(0), mask(1)); + assert_ne!(a.id, b.id, "each layer has a copy of its own"); + assert_eq!(a.geometric_bounds(), b.geometric_bounds()); + let bounds = a.geometric_bounds().unwrap(); + assert_eq!((bounds.width(), bounds.height()), (80.0, 80.0)); + assert!(r.warnings.iter().any(|w| w.contains("clipping group in each layer")), "{:?}", r.warnings); + // The layers' flag saying they have multi-layer masks changes nothing. + let unflagged = read7(&body.replace(&begin7("A", 1), &begin7("A", 0)).replace(&begin7("B", 1), &begin7("B", 0))); + assert_eq!(format!("{:?}", unflagged.doc.layers), format!("{:?}", r.doc.layers)); +} + +#[test] +fn a_multi_layer_mask_in_one_layer_or_over_three() { + let one = format!("{}{}{}MB\n{}{END7}", begin7("A", 1), mask_start(), masked(15.0), masked(150.0)); + assert_eq!(layer_trees(&read7(&one).doc), [("A".to_string(), "k[c p] p".to_string(), false)]); + let three = format!( + "{}{}{}{END7}{}{}{END7}{}{}MB\n{END7}", + begin7("A", 1), + mask_start(), + masked(15.0), + begin7("B", 1), + masked(30.0), + begin7("C", 1), + masked(45.0) + ); + let trees: Vec<_> = layer_trees(&read7(&three).doc).into_iter().map(|(_, t, clip)| (t, clip)).collect(); + assert_eq!(trees, vec![("k[c p]".to_string(), false); 3]); + // Objects before the mask's path are masked too; a layer between with nothing in it stays empty. + let before = format!( + "{}Mb\n{}{}h\nW\nn\nMd\n{END7}{}{END7}{}{}MB\n{END7}", + begin7("A", 1), + masked(15.0), + rect_path(40.0, 40.0, 120.0, 120.0), + begin7("B", 1), + begin7("C", 1), + masked(45.0) + ); + let trees: Vec<_> = layer_trees(&read7(&before).doc).into_iter().map(|(_, t, _)| t).collect(); + assert_eq!(trees, ["k[c p]", "", "k[c p]"]); +} + +#[test] +fn a_multi_layer_mask_in_a_group_or_sublayer() { + // Begun and ended in a group of one layer. + let grouped = format!("{}u\n{}{}MB\n{}U\n{END7}", begin7("A", 1), mask_start(), masked(15.0), masked(150.0)); + assert_eq!(layer_trees(&read7(&grouped).doc)[0].1, "g[k[c p] p]"); + // A sublayer in the mask: what the mask covers in the layer around it and in the sublayer. + let nested = format!("{}{}{}{}{}{END7}{}MB\n{END7}", begin7("A", 1), mask_start(), masked(15.0), begin7("S", 1), masked(30.0), masked(45.0)); + assert_eq!(layer_trees(&read7(&nested).doc)[0].1, "k[c p] l[k[c p]] k[c p]"); +} + +#[test] +fn a_multi_layer_masks_path_can_be_painted_or_compound() { + // `W f`: the path also paints, under what the mask covers. + let painted = format!("{}Mb\n0 0 0 1 k\n{}h\nW\nf\nMd\n{}MB\n{END7}", begin7("A", 1), rect_path(40.0, 40.0, 120.0, 120.0), masked(15.0)); + let doc = read7(&painted).doc; + assert_eq!(layer_trees(&doc)[0].1, "k[c p p]"); + let group = doc.layers[0].children().unwrap()[0].clone(); + let kids = group.children().unwrap(); + assert_eq!(fill(&kids[1]).color(), Some(Color::cmyk(0.0, 0.0, 0.0, 1.0)), "the painted path first"); + // A compound path of clipping paths. + let compound = format!( + "{}Mb\n*u\n{}h\nW\nn\n{}h\nW\nn\n*U\nMd\n{}MB\n{END7}", + begin7("A", 1), + rect_path(40.0, 40.0, 80.0, 80.0), + rect_path(90.0, 90.0, 120.0, 120.0), + masked(15.0) + ); + assert_eq!(layer_trees(&read7(&compound).doc)[0].1, "k[o p]"); +} + +/// `Mv` starts a mask as `Mb` does, `ME` defines one as `Md` does, and `Mi` changes nothing. +#[test] +fn other_multi_layer_mask_operators() { + let body = format!("{}{}{}{END7}{}{}MB\n{}{END7}", begin7("A", 1), mask_start(), masked(15.0), begin7("B", 1), masked(30.0), masked(150.0)); + let plain = format!("{:?}", read7(&body).doc.layers); + let aliases = body.replace("Mb\n", "Mv\n").replace("Md\n", "ME\n"); + assert_eq!(format!("{:?}", read7(&aliases).doc.layers), plain); + let with_mi = body.replace("Md\n", "Md\nMi\n").replace("MB\n", "Mi\nMB\n"); + assert_eq!(format!("{:?}", read7(&with_mi).doc.layers), plain); +} + +#[test] +fn a_malformed_multi_layer_mask_is_an_error() { + let path = format!("{}h\nW\nn\n", rect_path(40.0, 40.0, 120.0, 120.0)); + let sq = masked(15.0); + let cases = [ + ("an end without a start", format!("{sq}MB\n")), + ("a definition without a start", format!("{path}Md\n{sq}")), + ("no mask path", format!("Mb\n{sq}Md\n{sq}MB\n")), + ("a start inside another", format!("Mb\nMb\n{path}Md\n{sq}MB\n")), + ("an end before the definition", format!("Mb\n{path}MB\n")), + ("a definition in another group", format!("Mb\nu\n{path}Md\n{sq}MB\nU\n")), + ("a group crossing the end", format!("{}u\n{sq}MB\nU\n", mask_start())), + ("a group crossing the start", format!("u\n{}{sq}U\nMB\n", mask_start())), + ]; + for (why, art) in cases { + let body = format!("{}{art}{END7}", begin7("A", 1)); + let e = read(mask_stream7(&body).as_bytes()).unwrap_err(); + assert!(e.contains("mask"), "{why}: {e}"); + } + let layered = [ + ("a layer ends before the definition", format!("{}Mb\n{sq}{END7}{}{path}Md\nMB\n{END7}", begin7("A", 1), begin7("B", 1))), + ("a group open at the layer's end", format!("{}{}u\n{sq}{END7}{}U\nMB\n{END7}", begin7("A", 1), mask_start(), begin7("B", 1))), + ("the end of the file inside the mask", format!("{}{}{sq}{END7}", begin7("A", 1), mask_start())), + ]; + for (why, body) in layered { + let e = read(mask_stream7(&body).as_bytes()).unwrap_err(); + assert!(e.contains("mask"), "{why}: {e}"); + } + // Outside the layers, `Md` has nowhere to take its path from. + let outside = format!("Mb\nMd\n{}{path}{sq}MB\n{END7}", begin7("A", 1)); + let e = read(mask_stream7(&outside).as_bytes()).unwrap_err(); + assert!(e.contains("mask"), "{e}"); +} + +/// A clipping path painted among the masked objects clips its whole layer, as outside a mask. +#[test] +fn a_layers_clipping_path_in_a_multi_layer_mask_clips_the_layer() { + let path = format!("{}h\nW\nn\n", rect_path(10.0, 10.0, 190.0, 190.0)); + let body = format!("{}{}{}{path}{}MB\n{END7}", begin7("A", 1), mask_start(), masked(15.0), masked(30.0)); + assert_eq!(layer_trees(&read7(&body).doc), [("A".to_string(), "c k[c p p]".to_string(), true)]); +} + +/// `Mb` before the first layer it covers and `MB` after the last one's `LB`. +#[test] +fn a_multi_layer_mask_begun_or_ended_between_layers() { + let body = format!( + "Mb\n{}{}h\nW\nn\nMd\n{}{END7}{}{}{END7}MB\n{}{}{END7}", + begin7("A", 1), + rect_path(40.0, 40.0, 120.0, 120.0), + masked(15.0), + begin7("B", 1), + masked(30.0), + begin7("C", 1), + masked(150.0) + ); + let trees: Vec<_> = layer_trees(&read7(&body).doc).into_iter().map(|(_, t, _)| t).collect(); + assert_eq!(trees, ["k[c p]", "k[c p]", "p"]); +} + +/// Each copy of the mask counts toward the most objects read, with its points. +#[test] +fn copies_of_a_multi_layer_mask_count_toward_the_limit() { + let points: String = (0..2_000_001).map(|i| format!("{} 40 L\n", 40 + i % 80)).collect(); + let mask = format!("Mb\n40 40 m\n{points}h\nW\nn\nMd\n"); + let one = format!("{}{mask}{}MB\n{END7}", begin7("A", 1), masked(15.0)); + assert_eq!(layer_trees(&read7(&one).doc)[0].1, "k[c p]"); + let two = format!("{}{mask}{}{END7}{}{}MB\n{END7}", begin7("A", 1), masked(15.0), begin7("B", 1), masked(30.0)); + let e = read(mask_stream7(&two).as_bytes()).unwrap_err(); + assert!(e.contains("objects"), "{e}"); +} diff --git a/crates/eps/src/import/tests_illustrator.rs b/crates/eps/src/import/tests_illustrator.rs index d5261cc4b..f8b92b4bb 100644 --- a/crates/eps/src/import/tests_illustrator.rs +++ b/crates/eps/src/import/tests_illustrator.rs @@ -163,6 +163,25 @@ fn a_postscript_ai_whose_layers_cant_be_read_says_why() { assert!(e.contains("`Adobe_packedarray`") && e.contains("its layers couldn't be read either") && e.contains("`frobnicate`"), "{e}"); } +/// A `.ai` in PostScript form with a multi-layer mask (`Mb`, its path, `Md`, objects in both +/// layers, `MB`) opens through its layers: a clipping group in each, and what follows `MB` unclipped. +#[test] +fn a_postscript_ai_with_a_multi_layer_mask_opens_with_its_layers() { + let src = NAMED_PROLOG + .replace("0 D\n10 10 m", "0 D\nMb\n20 20 m\n180 20 L\n180 80 L\n20 80 L\nh\nW\nn\nMd\n10 10 m") + .replace("190 90 L\nS\nLB", "190 90 L\nS\nMB\n150 50 m\n195 95 L\nS\nLB"); + let r = import(src.as_bytes()).unwrap(); + assert!(!r.preview, "read through the layers, not a fallback preview"); + let d = &r.document; + let names: Vec<_> = d.layers.iter().map(|l| l.name.clone().unwrap_or_default()).collect(); + assert_eq!(names, ["SECTION::Cut", "SECTION::Hatch"]); + assert_eq!(tree(d.layers[0].children().unwrap()), "k[c p]"); + assert_eq!(tree(d.layers[1].children().unwrap()), "k[c p] p"); + let mask = d.layers[1].children().unwrap()[0].children().unwrap()[0].clone(); + assert_eq!(mask.geometric_bounds(), Some(Rect::new(20.0, 20.0, 180.0, 80.0))); + assert!(r.warnings.iter().any(|w| w.contains("clipping group in each layer")), "{:?}", r.warnings); +} + /// A `.ai` in PostScript form (read through its layers) whose text carries hyphenation and /// tab-ruler attributes: `XL` (hyphenation language), `Xb`/`XB` (a tab definition) and `Th` /// (hyphen control). These keep no geometry, so the reader drops them rather than failing the diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 423990a6d..9774c74f6 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -4814,6 +4814,7 @@ digits Malayalam Malaiàlam @msg `{to}` is not an available name `{to}` no és un nom disponible @msg `{what}` closes nothing `{what}` no tanca res @msg `{what}` closes something it didn't open `{what}` tanca una cosa que no havia obert +@msg a mask that covers several layers comes in as a clipping group in each layer it covers una màscara que cobreix diverses capes entra com un grup de retall a cada capa que cobreix @msg a graph's records don't end where they should: its art was read as it is els registres d’un gràfic no acaben on haurien: la seva il·lustració s’ha llegit tal com és @msg `{w}` takes no operands `{w}` no admet operands @msg a wraparound group is inside a compound path un grup d’ajustament del text és dins d’un traçat compost @@ -5050,6 +5051,10 @@ digits Malayalam Malaiàlam @msg is not an image VectorCraft reads no és una imatge que VectorCraft pugui llegir @msg it decompresses to more than {_1} MB es descomprimeix fins a més de {_1} MB @msg it ends inside a layer acaba dins d’una capa +@msg it ends inside a multi-layer mask acaba dins d’una màscara multicapa +@msg a multi-layer mask's operators are out of order els operadors d’una màscara multicapa estan desordenats +@msg a multi-layer mask needs one clipping path una màscara multicapa necessita un traçat de retall +@msg a group crosses a multi-layer mask un grup travessa una màscara multicapa @msg it has art outside its layers té art fora de les seves capes @msg it has more than one artboard té més d’un tauler @msg it has more than {MAX_NODES} objects té més de {MAX_NODES} objectes diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 0e064c772..d9d8877da 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -4872,11 +4872,16 @@ imageTrace Outline Obrys @msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead nejde o logo s plnými barvami ({_1} % jeho plných ploch odpovídá několika plným barvám), takže režim Ploché logo by z jeho přechodů udělal skvrny: použijte místo toho režim Barva @msg `{what}` closes nothing `{what}` nic neuzavírá @msg `{what}` closes something it didn't open `{what}` uzavírá něco, co neotevřel +@msg a mask that covers several layers comes in as a clipping group in each layer it covers maska, která pokrývá několik vrstev, se načte jako ořezová skupina v každé vrstvě, kterou pokrývá @msg a graph's records don't end where they should: its art was read as it is záznamy grafu nekončí tam, kde mají: jeho kresba byla načtena tak, jak je @msg `{w}` takes no operands `{w}` nepřijímá žádné operandy @msg a wraparound group is inside a compound path skupina obtékání textu je uvnitř složené cesty @msg a path has too many points cesta má příliš mnoho bodů @msg it ends inside a layer končí uvnitř vrstvy +@msg it ends inside a multi-layer mask končí uvnitř masky přes více vrstev +@msg a multi-layer mask's operators are out of order operátory masky přes více vrstev jsou ve špatném pořadí +@msg a multi-layer mask needs one clipping path maska přes více vrstev potřebuje jednu ořezovou cestu +@msg a group crosses a multi-layer mask skupina přesahuje masku přes více vrstev @msg it has art outside its layers má kresbu mimo své vrstvy @msg it has more than {MAX_NODES} objects má více než {MAX_NODES} objektů @msg it has no layers nemá žádné vrstvy diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 1c1005fc7..4130d11b5 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4481,11 +4481,16 @@ theme Appearance Erscheinungsbild @msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead dies ist kein Logo aus flächigen Farben ({_1} % seiner einfarbigen Bereiche entsprechen wenigen flächigen Farben), daher würde der Modus „Flaches Logo“ seine Verläufe in Flecken zerlegen: Verwenden Sie stattdessen den Modus „Farbe“ @msg `{what}` closes nothing `{what}` schließt nichts @msg `{what}` closes something it didn't open `{what}` schließt etwas, das es nicht geöffnet hat +@msg a mask that covers several layers comes in as a clipping group in each layer it covers eine Maske über mehrere Ebenen wird in jeder Ebene, die sie abdeckt, zu einer Schnittgruppe @msg a graph's records don't end where they should: its art was read as it is die Datensätze eines Diagramms enden nicht dort, wo sie sollten: seine Grafik wurde so gelesen, wie sie ist @msg `{w}` takes no operands `{w}` nimmt keine Operanden @msg a wraparound group is inside a compound path eine Textumfluss-Gruppe liegt in einem zusammengesetzten Pfad @msg a path has too many points ein Pfad hat zu viele Punkte @msg it ends inside a layer es endet innerhalb einer Ebene +@msg it ends inside a multi-layer mask es endet innerhalb einer ebenenübergreifenden Maske +@msg a multi-layer mask's operators are out of order die Operatoren einer ebenenübergreifenden Maske stehen in falscher Reihenfolge +@msg a multi-layer mask needs one clipping path eine ebenenübergreifende Maske braucht genau einen Schnittpfad +@msg a group crosses a multi-layer mask eine Gruppe überschneidet eine ebenenübergreifende Maske @msg it has art outside its layers es enthält Grafik außerhalb seiner Ebenen @msg it has more than {MAX_NODES} objects es enthält mehr als {MAX_NODES} Objekte @msg it has no layers es enthält keine Ebenen diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 7c0d512fa..f5beeb0ed 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -4514,11 +4514,16 @@ digits Malayalam Μαλαγιάλαμ @msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead αυτό δεν είναι λογότυπο επίπεδων χρωμάτων ({_1}% των συμπαγών περιοχών του αντιστοιχούν σε λίγα χρώματα), επομένως η λειτουργία Επίπεδο λογότυπο θα μετατρέψει τα ντεγκραντέ σε κηλίδες: χρησιμοποιήστε τη λειτουργία Χρώμα @msg `{what}` closes nothing το `{what}` δεν κλείνει κανένα στοιχείο @msg `{what}` closes something it didn't open το `{what}` κλείνει στοιχείο που δεν άνοιξε +@msg a mask that covers several layers comes in as a clipping group in each layer it covers μια μάσκα που καλύπτει πολλά επίπεδα εισάγεται ως ομάδα αποκοπής σε κάθε επίπεδο που καλύπτει @msg a graph's records don't end where they should: its art was read as it is οι εγγραφές ενός γραφήματος δεν τελειώνουν εκεί που πρέπει: το έργο του διαβάστηκε όπως είναι @msg `{w}` takes no operands το `{w}` δεν δέχεται τελεστέους @msg a wraparound group is inside a compound path μια ομάδα αναδίπλωσης κειμένου βρίσκεται μέσα σε σύνθετη διαδρομή @msg a path has too many points η διαδρομή έχει υπερβολικά πολλά σημεία @msg it ends inside a layer τερματίζει μέσα σε επίπεδο +@msg it ends inside a multi-layer mask τερματίζει μέσα σε μάσκα πολλών επιπέδων +@msg a multi-layer mask's operators are out of order οι τελεστές μιας μάσκας πολλών επιπέδων είναι σε λάθος σειρά +@msg a multi-layer mask needs one clipping path μια μάσκα πολλών επιπέδων χρειάζεται μία διαδρομή αποκοπής +@msg a group crosses a multi-layer mask μια ομάδα διασχίζει μια μάσκα πολλών επιπέδων @msg it has art outside its layers περιέχει σχέδιο εκτός των επιπέδων του @msg it has more than {MAX_NODES} objects περιέχει περισσότερα από {MAX_NODES} αντικείμενα @msg it has no layers δεν περιέχει επίπεδα diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 2f3ef3207..4e2577feb 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -4566,11 +4566,16 @@ axis Both Ambos @msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead esto no es un logotipo de colores planos ({_1}% de sus zonas sólidas coinciden con unos pocos colores planos), así que el modo Logotipo plano convertiría sus degradados en manchas: usa el modo Color @msg `{what}` closes nothing `{what}` no cierra nada @msg `{what}` closes something it didn't open `{what}` cierra algo que no abrió +@msg a mask that covers several layers comes in as a clipping group in each layer it covers una máscara que cubre varias capas entra como un grupo de recorte en cada capa que cubre @msg a graph's records don't end where they should: its art was read as it is los registros de un gráfico no terminan donde deberían: su ilustración se leyó tal cual @msg `{w}` takes no operands `{w}` no admite operandos @msg a wraparound group is inside a compound path un grupo de ceñir texto está dentro de un trazado compuesto @msg a path has too many points un trazado tiene demasiados puntos @msg it ends inside a layer terminan dentro de una capa +@msg it ends inside a multi-layer mask terminan dentro de una máscara de varias capas +@msg a multi-layer mask's operators are out of order los operadores de una máscara de varias capas están desordenados +@msg a multi-layer mask needs one clipping path una máscara de varias capas necesita un trazado de recorte +@msg a group crosses a multi-layer mask un grupo atraviesa una máscara de varias capas @msg it has art outside its layers tienen ilustraciones fuera de sus capas @msg it has more than {MAX_NODES} objects tienen más de {MAX_NODES} objetos @msg it has no layers no tienen capas diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index e544b5461..78755f5da 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -4774,11 +4774,16 @@ imageTrace Outline Ääriviiva @msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead tämä ei ole tasavärinen logo ({_1} % sen yhtenäisistä alueista vastaa muutamaa tasaväriä), joten Tasavärilogo-tila muuttaisi sen liukuvärit laikuiksi: käytä sen sijaan Väri-tilaa @msg `{what}` closes nothing `{what}` ei sulje mitään @msg `{what}` closes something it didn't open `{what}` sulkee jotain, mitä se ei avannut +@msg a mask that covers several layers comes in as a clipping group in each layer it covers useita tasoja peittävä maski tuodaan leikkausryhmänä jokaiseen tasoon, jonka se peittää @msg a graph's records don't end where they should: its art was read as it is kaavion tietueet eivät pääty siihen, mihin niiden pitäisi: sen piirros luettiin sellaisenaan @msg `{w}` takes no operands `{w}` ei ota operandeja @msg a wraparound group is inside a compound path tekstin rivitysryhmä on yhdistelmäpolun sisällä @msg a path has too many points polulla on liian monta pistettä @msg it ends inside a layer se päättyy tason sisään +@msg it ends inside a multi-layer mask se päättyy monitasoisen maskin sisään +@msg a multi-layer mask's operators are out of order monitasoisen maskin operaattorit ovat väärässä järjestyksessä +@msg a multi-layer mask needs one clipping path monitasoinen maski tarvitsee yhden leikkauspolun +@msg a group crosses a multi-layer mask ryhmä ylittää monitasoisen maskin rajan @msg it has art outside its layers siinä on kuvitusta tasojensa ulkopuolella @msg it has more than {MAX_NODES} objects siinä on yli {MAX_NODES} objektia @msg it has no layers siinä ei ole tasoja diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index c6a0b0bf5..7166f58d1 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -4556,11 +4556,16 @@ axis Both Les deux @msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead ce n’est pas un logo en aplats ({_1} % de ses zones pleines correspondent à quelques couleurs unies), donc le mode Logo en aplats transformerait ses dégradés en taches : utilisez le mode Couleur @msg `{what}` closes nothing `{what}` ne ferme rien @msg `{what}` closes something it didn't open `{what}` ferme quelque chose qu’il n’a pas ouvert +@msg a mask that covers several layers comes in as a clipping group in each layer it covers un masque qui couvre plusieurs calques est importé comme un groupe de découpe dans chaque calque qu’il couvre @msg a graph's records don't end where they should: its art was read as it is les enregistrements d’un graphe ne se terminent pas là où ils le devraient : son illustration a été lue telle quelle @msg `{w}` takes no operands `{w}` n’accepte aucun opérande @msg a wraparound group is inside a compound path un groupe d’habillage de texte se trouve dans un tracé transparent @msg a path has too many points un tracé a trop de points @msg it ends inside a layer elles se terminent à l’intérieur d’un calque +@msg it ends inside a multi-layer mask elles se terminent à l’intérieur d’un masque multicalque +@msg a multi-layer mask's operators are out of order les opérateurs d’un masque multicalque sont dans le désordre +@msg a multi-layer mask needs one clipping path un masque multicalque nécessite un seul tracé d’écrêtage +@msg a group crosses a multi-layer mask un groupe chevauche un masque multicalque @msg it has art outside its layers elles contiennent des illustrations hors de leurs calques @msg it has more than {MAX_NODES} objects elles contiennent plus de {MAX_NODES} objets @msg it has no layers elles n’ont aucun calque diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 3da51358e..9448d6bc8 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4557,6 +4557,7 @@ imageTrace Outline Körvonal @msg `{to}` is not an available name ez a név nem használható: `{to}` @msg `{what}` closes nothing `{what}` semmit sem zár le @msg `{what}` closes something it didn't open `{what}` olyat zár le, amit nem nyitott meg +@msg a mask that covers several layers comes in as a clipping group in each layer it covers a több réteget lefedő maszk minden általa lefedett rétegben vágócsoportként jön be @msg a graph's records don't end where they should: its art was read as it is egy grafikon rekordjai nem ott érnek véget, ahol kellene: a grafikája úgy lett beolvasva, ahogy van @msg `{w}` takes no operands `{w}` nem fogad operandusokat @msg a wraparound group is inside a compound path egy szövegkörbefuttatási csoport összetett útvonalon belül van @@ -4772,6 +4773,10 @@ imageTrace Outline Körvonal @msg invalid restored panel group érvénytelen visszaállított panelcsoport @msg is not an image VectorCraft reads nem olyan kép, amelyet a VectorCraft olvas @msg it ends inside a layer egy rétegen belül ér véget +@msg it ends inside a multi-layer mask egy többrétegű maszkon belül ér véget +@msg a multi-layer mask's operators are out of order egy többrétegű maszk operátorai rossz sorrendben vannak +@msg a multi-layer mask needs one clipping path egy többrétegű maszkhoz egy vágóútvonal kell +@msg a group crosses a multi-layer mask egy csoport átnyúlik egy többrétegű maszkon @msg it has art outside its layers a rétegein kívül is van grafikája @msg it has more than one artboard egynél több rajztáblája van @msg it has more than {MAX_NODES} objects túl sok objektumot tartalmaz (legfeljebb {MAX_NODES}) diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index bf4cac799..4900c22ea 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4607,11 +4607,16 @@ axis Both Entrambi @msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead questo non è un logo a colori piatti ({_1}% delle sue aree piene corrisponde a pochi colori piatti), quindi la modalità Logo piatto trasformerebbe i suoi sfumati in macchie: usa la modalità Colore @msg `{what}` closes nothing `{what}` non chiude nulla @msg `{what}` closes something it didn't open `{what}` chiude qualcosa che non ha aperto +@msg a mask that covers several layers comes in as a clipping group in each layer it covers una maschera che copre più livelli viene importata come gruppo di ritaglio in ogni livello che copre @msg a graph's records don't end where they should: its art was read as it is i record di un grafico non finiscono dove dovrebbero: la sua grafica è stata letta così com’è @msg `{w}` takes no operands `{w}` non accetta operandi @msg a wraparound group is inside a compound path un gruppo di testo attorno si trova in un tracciato composto @msg a path has too many points un tracciato ha troppi punti @msg it ends inside a layer terminano all'interno di un livello +@msg it ends inside a multi-layer mask terminano all'interno di una maschera multilivello +@msg a multi-layer mask's operators are out of order gli operatori di una maschera multilivello non sono in ordine +@msg a multi-layer mask needs one clipping path una maschera multilivello richiede un solo tracciato di ritaglio +@msg a group crosses a multi-layer mask un gruppo attraversa una maschera multilivello @msg it has art outside its layers contengono grafica al di fuori dei loro livelli @msg it has more than {MAX_NODES} objects contengono più di {MAX_NODES} oggetti @msg it has no layers non hanno livelli diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 9cfbf8cfd..c41088fdc 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4311,11 +4311,16 @@ swatch Bright Violet ビビッドバイオレット @msg unknown command `{0}`; did you mean `{1}`? `{0}` は不明なコマンドです。`{1}` のことですか? @msg `{what}` closes nothing `{what}` に対応する開始部分がありません @msg `{what}` closes something it didn't open `{what}` が対応しない開始部分を閉じています +@msg a mask that covers several layers comes in as a clipping group in each layer it covers 複数のレイヤーにまたがるマスクは、覆う各レイヤーでクリップグループとして読み込まれます @msg a graph's records don't end where they should: its art was read as it is グラフのレコードが正しい位置で終わっていません: アートはそのまま読み込まれました @msg `{w}` takes no operands `{w}` はオペランドを取りません @msg a wraparound group is inside a compound path テキストの回り込みグループが複合パスの中にあります @msg a path has too many points ポイントが多すぎるパスがあります @msg it ends inside a layer レイヤーの途中で終わっています +@msg it ends inside a multi-layer mask 複数レイヤーにまたがるマスクの途中で終わっています +@msg a multi-layer mask's operators are out of order 複数レイヤーにまたがるマスクの演算子の順序が正しくありません +@msg a multi-layer mask needs one clipping path 複数レイヤーにまたがるマスクにはクリッピングパスが 1 つ必要です +@msg a group crosses a multi-layer mask グループが複数レイヤーにまたがるマスクと交差しています @msg it has art outside its layers レイヤーの外にアートがあります @msg it has more than {MAX_NODES} objects オブジェクトが {MAX_NODES} 個を超えています @msg it has no layers レイヤーがありません diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 9b2a0b0f6..94bbef591 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -4305,11 +4305,16 @@ font-technology Variable ცვლადი @msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead ეს ერთფეროვანი ლოგო არ არის (მისი მყარი არეების {_1}% რამდენიმე ერთგვაროვან ფერს ემთხვევა), ამიტომ Flat Logo რეჟიმი მის გრადიენტებს ლაქებად აქცევდა: გამოიყენეთ Color რეჟიმი @msg `{what}` closes nothing `{what}` არაფერს ხურავს @msg `{what}` closes something it didn't open `{what}` ხურავს იმას, რაც არ გაუხსნია +@msg a mask that covers several layers comes in as a clipping group in each layer it covers რამდენიმე ფენის დამფარავი ნიღაბი შემოდის როგორც ჩამოჭრის ჯგუფი თითოეულ ფენაში, რომელსაც ფარავს @msg a graph's records don't end where they should: its art was read as it is დიაგრამის ჩანაწერები იქ არ მთავრდება, სადაც უნდა: მისი გრაფიკა წაკითხულია ისე, როგორც არის @msg `{w}` takes no operands `{w}` ოპერანდებს არ იღებს @msg a wraparound group is inside a compound path ტექსტის გარსშემოვლების ჯგუფი შედგენილი ტრაექტორიის შიგნითაა @msg a path has too many points ტრაექტორიას ზედმეტად ბევრი წერტილი აქვს @msg it ends inside a layer ის შრის შიგნით მთავრდება +@msg it ends inside a multi-layer mask ის მრავალშრიანი ნიღბის შიგნით მთავრდება +@msg a multi-layer mask's operators are out of order მრავალშრიანი ნიღბის ოპერატორები არასწორი თანმიმდევრობითაა +@msg a multi-layer mask needs one clipping path მრავალშრიან ნიღაბს ერთი მომჭრელი ტრაექტორია სჭირდება +@msg a group crosses a multi-layer mask ჯგუფი მრავალშრიან ნიღაბს კვეთს @msg it has art outside its layers მას შრეების გარეთ ნახატი აქვს @msg it has more than {MAX_NODES} objects მას {MAX_NODES}-ზე მეტი ობიექტი აქვს @msg it has no layers მას შრეები არ აქვს diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 7ce3bacc4..00fffc7d8 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4238,11 +4238,16 @@ theme Appearance 외관 @msg unknown command `{0}`; did you mean `{1}`? 알 수 없는 명령 `{0}`: `{1}`을(를) 의미하셨습니까? @msg `{what}` closes nothing `{what}`이(가) 닫는 항목이 없습니다 @msg `{what}` closes something it didn't open `{what}`이(가) 열지 않은 항목을 닫습니다 +@msg a mask that covers several layers comes in as a clipping group in each layer it covers 여러 레이어에 걸친 마스크는 덮는 각 레이어에서 클리핑 그룹으로 들어옵니다 @msg a graph's records don't end where they should: its art was read as it is 그래프의 레코드가 끝나야 할 곳에서 끝나지 않습니다: 아트는 있는 그대로 읽었습니다 @msg `{w}` takes no operands `{w}`은(는) 피연산자를 받지 않습니다 @msg a wraparound group is inside a compound path 문자 감싸기 그룹이 컴파운드 패스 안에 있습니다 @msg a path has too many points 패스의 점이 너무 많습니다 @msg it ends inside a layer 레이어 안에서 끝납니다 +@msg it ends inside a multi-layer mask 여러 레이어 마스크 안에서 끝납니다 +@msg a multi-layer mask's operators are out of order 여러 레이어 마스크의 연산자 순서가 잘못되었습니다 +@msg a multi-layer mask needs one clipping path 여러 레이어 마스크에는 클리핑 패스가 하나 필요합니다 +@msg a group crosses a multi-layer mask 그룹이 여러 레이어 마스크에 걸쳐 있습니다 @msg it has art outside its layers 레이어 밖에 아트웍이 있습니다 @msg it has more than {MAX_NODES} objects 개체가 {MAX_NODES}개를 넘습니다 @msg it has no layers 레이어가 없습니다 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index acfee2fba..903e6a086 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -4698,11 +4698,16 @@ imageTrace Outline Kontur @msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead to nie jest logo z płaskimi kolorami ({_1}% jego jednolitych obszarów pasuje do kilku płaskich kolorów), więc tryb Płaskie logo zamieniłby jego gradienty w plamy: użyj trybu Kolor @msg `{what}` closes nothing `{what}` niczego nie zamyka @msg `{what}` closes something it didn't open `{what}` zamyka coś, czego nie otworzył +@msg a mask that covers several layers comes in as a clipping group in each layer it covers maska obejmująca kilka warstw jest wczytywana jako grupa przycinająca w każdej obejmowanej warstwie @msg a graph's records don't end where they should: its art was read as it is rekordy wykresu nie kończą się tam, gdzie powinny: jego grafika została wczytana tak, jak jest @msg `{w}` takes no operands `{w}` nie przyjmuje operandów @msg a wraparound group is inside a compound path grupa oblewania tekstem jest wewnątrz ścieżki złożonej @msg a path has too many points ścieżka ma zbyt wiele punktów @msg it ends inside a layer kończy się wewnątrz warstwy +@msg it ends inside a multi-layer mask kończy się wewnątrz maski wielowarstwowej +@msg a multi-layer mask's operators are out of order operatory maski wielowarstwowej są w złej kolejności +@msg a multi-layer mask needs one clipping path maska wielowarstwowa wymaga jednej ścieżki przycinającej +@msg a group crosses a multi-layer mask grupa przecina maskę wielowarstwową @msg it has art outside its layers ma kompozycję poza warstwami @msg it has more than {MAX_NODES} objects ma ponad {MAX_NODES} obiektów @msg it has no layers nie ma warstw diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index f65b54563..c9bd0d107 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -4148,11 +4148,16 @@ axis Both Ambos @msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead este não é um logotipo de cores chapadas ({_1}% de suas áreas sólidas correspondem a poucas cores chapadas), então o modo Logotipo plano transformaria seus gradientes em manchas: use o modo Cor @msg `{what}` closes nothing `{what}` não fecha nada @msg `{what}` closes something it didn't open `{what}` fecha algo que não abriu +@msg a mask that covers several layers comes in as a clipping group in each layer it covers uma máscara que cobre várias camadas entra como um grupo de recorte em cada camada que cobre @msg a graph's records don't end where they should: its art was read as it is os registros de um gráfico não terminam onde deveriam: sua arte foi lida como está @msg `{w}` takes no operands `{w}` não aceita operandos @msg a wraparound group is inside a compound path um grupo de quebra de texto está dentro de um traçado composto @msg a path has too many points um traçado tem pontos demais @msg it ends inside a layer termina dentro de uma camada +@msg it ends inside a multi-layer mask termina dentro de uma máscara de várias camadas +@msg a multi-layer mask's operators are out of order os operadores de uma máscara de várias camadas estão fora de ordem +@msg a multi-layer mask needs one clipping path uma máscara de várias camadas precisa de um caminho de recorte +@msg a group crosses a multi-layer mask um grupo atravessa uma máscara de várias camadas @msg it has art outside its layers tem arte fora de suas camadas @msg it has more than {MAX_NODES} objects tem mais de {MAX_NODES} objetos @msg it has no layers não tem camadas diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 1195322cf..1ac514dd7 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -4602,11 +4602,16 @@ axis Both Оба @msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead это не логотип из плоских цветов ({_1}% его сплошных областей совпадает с несколькими плоскими цветами), поэтому режим «Плоский логотип» превратил бы градиенты в пятна: используйте режим «Цвет» @msg `{what}` closes nothing `{what}` ничего не закрывает @msg `{what}` closes something it didn't open `{what}` закрывает то, что не открывал +@msg a mask that covers several layers comes in as a clipping group in each layer it covers маска, охватывающая несколько слоёв, переносится как обтравочная группа в каждый слой, который она охватывает @msg a graph's records don't end where they should: its art was read as it is записи диаграммы не заканчиваются там, где должны: её графика прочитана как есть @msg `{w}` takes no operands `{w}` не принимает операндов @msg a wraparound group is inside a compound path группа обтекания текстом находится внутри составного контура @msg a path has too many points в контуре слишком много точек @msg it ends inside a layer они заканчиваются внутри слоя +@msg it ends inside a multi-layer mask они заканчиваются внутри многослойной маски +@msg a multi-layer mask's operators are out of order операторы многослойной маски идут в неправильном порядке +@msg a multi-layer mask needs one clipping path многослойной маске нужен один контур обрезки +@msg a group crosses a multi-layer mask группа пересекает многослойную маску @msg it has art outside its layers в них есть графика вне слоёв @msg it has more than {MAX_NODES} objects в них больше {MAX_NODES} объектов @msg it has no layers в них нет слоёв diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index f7d8b5bfb..c73dc281f 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -4465,11 +4465,16 @@ axis Both Båda @msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead det här är ingen logotyp i platta färger ({_1} % av dess enfärgade ytor matchar några få platta färger), så läget Platt logotyp skulle göra om dess gradienter till fläckar: använd läget Färg i stället @msg `{what}` closes nothing `{what}` stänger ingenting @msg `{what}` closes something it didn't open `{what}` stänger något som den inte öppnade +@msg a mask that covers several layers comes in as a clipping group in each layer it covers en mask som täcker flera lager kommer in som en urklippsgrupp i varje lager den täcker @msg a graph's records don't end where they should: its art was read as it is ett diagrams poster slutar inte där de ska: dess grafik lästes som den är @msg `{w}` takes no operands `{w}` tar inga operander @msg a wraparound group is inside a compound path en textombrytningsgrupp ligger inuti en sammansatt bana @msg a path has too many points en bana har för många punkter @msg it ends inside a layer den slutar inuti ett lager +@msg it ends inside a multi-layer mask den slutar inuti en flerlagersmask +@msg a multi-layer mask's operators are out of order operatorerna för en flerlagersmask står i fel ordning +@msg a multi-layer mask needs one clipping path en flerlagersmask behöver en klippbana +@msg a group crosses a multi-layer mask en grupp korsar en flerlagersmask @msg it has art outside its layers den har grafik utanför sina lager @msg it has more than {MAX_NODES} objects den har fler än {MAX_NODES} objekt @msg it has no layers den har inga lager diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 91d6c39b5..776d2cb80 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -4303,11 +4303,16 @@ axis Both Her İkisi @msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead bu düz renkli bir logo değildir (düz alanların %{_1}'si birkaç düz renkle eşleşir), dolayısıyla Düz Logo modu degradelerini yamalara dönüştürür: bunun yerine Renk modunu kullanın @msg `{what}` closes nothing `{what}` hiçbir şeyi kapatmaz @msg `{what}` closes something it didn't open `{what}` açmadığı bir şeyi kapatıyor +@msg a mask that covers several layers comes in as a clipping group in each layer it covers birkaç katmanı kaplayan bir maske, kapladığı her katmana bir kırpma grubu olarak gelir @msg a graph's records don't end where they should: its art was read as it is bir grafiğin kayıtları bitmesi gereken yerde bitmiyor: resmi olduğu gibi okundu @msg `{w}` takes no operands `{w}` işlenen almaz @msg a wraparound group is inside a compound path bir metin kaydırma grubu bileşik bir yolun içinde @msg a path has too many points bir yolun çok fazla noktası var @msg it ends inside a layer bir katmanın içinde bitiyor +@msg it ends inside a multi-layer mask çok katmanlı bir maskenin içinde bitiyor +@msg a multi-layer mask's operators are out of order çok katmanlı bir maskenin işleçleri yanlış sırada +@msg a multi-layer mask needs one clipping path çok katmanlı bir maskenin bir kırpma yoluna ihtiyacı var +@msg a group crosses a multi-layer mask bir grup çok katmanlı bir maskeyi kesiyor @msg it has art outside its layers katmanlarının dışında sanat var @msg it has more than {MAX_NODES} objects {MAX_NODES}'den daha fazla nesneye sahip @msg it has no layers katmanı yok diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 72eafbe9a..7e11d6daf 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4591,11 +4591,16 @@ axis Both Обидві @msg this isn't a flat-colour logo ({_1}% of its solid areas match a few flat colours), so Flat Logo mode would turn its gradients into patches: use Color mode instead це не логотип із плоских кольорів ({_1}% його суцільних областей збігається з кількома плоскими кольорами), тому режим «Плоский логотип» перетворив би градієнти на плями: використовуйте режим «Колір» @msg `{what}` closes nothing `{what}` нічого не закриває @msg `{what}` closes something it didn't open `{what}` закриває те, що не відкривав +@msg a mask that covers several layers comes in as a clipping group in each layer it covers маску, що охоплює кілька шарів, перенесено як групу відсікання в кожен шар, який вона охоплює @msg a graph's records don't end where they should: its art was read as it is записи діаграми не закінчуються там, де мають: її графіку прочитано як є @msg `{w}` takes no operands `{w}` не приймає операндів @msg a wraparound group is inside a compound path група обтікання текстом розташована всередині складеного контуру @msg a path has too many points у контурі занадто багато точок @msg it ends inside a layer вони закінчуються всередині шару +@msg it ends inside a multi-layer mask вони закінчуються всередині багатошарової маски +@msg a multi-layer mask's operators are out of order оператори багатошарової маски йдуть у неправильному порядку +@msg a multi-layer mask needs one clipping path багатошаровій масці потрібен один відсічний контур +@msg a group crosses a multi-layer mask група перетинає багатошарову маску @msg it has art outside its layers у них є графіка поза шарами @msg it has more than {MAX_NODES} objects в них більше {MAX_NODES} об’єктів @msg it has no layers у них немає шарів diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 906d3727b..f217da5be 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4209,6 +4209,7 @@ theme Appearance Giao diện @msg `{to}` is not an available name `{to}` không phải là tên khả dụng @msg `{what}` closes nothing `{what}` không đóng gì @msg `{what}` closes something it didn't open `{what}` đóng thứ nó chưa mở +@msg a mask that covers several layers comes in as a clipping group in each layer it covers một mặt nạ phủ nhiều lớp được nhập thành một nhóm cắt xén trong mỗi lớp mà nó phủ @msg a graph's records don't end where they should: its art was read as it is các bản ghi của một biểu đồ không kết thúc đúng chỗ: hình vẽ của nó được đọc nguyên trạng @msg `{w}` takes no operands `{w}` không nhận toán hạng @msg a wraparound group is inside a compound path một nhóm bao văn bản nằm trong một đường dẫn phức @@ -4422,6 +4423,10 @@ theme Appearance Giao diện @msg invalid restored panel group nhóm bảng được phục hồi không hợp lệ @msg is not an image VectorCraft reads không phải ảnh mà VectorCraft đọc được @msg it ends inside a layer nó kết thúc bên trong một lớp +@msg it ends inside a multi-layer mask nó kết thúc bên trong một mặt nạ nhiều lớp +@msg a multi-layer mask's operators are out of order các toán tử của mặt nạ nhiều lớp không đúng thứ tự +@msg a multi-layer mask needs one clipping path mặt nạ nhiều lớp cần một đường dẫn cắt +@msg a group crosses a multi-layer mask một nhóm vắt ngang mặt nạ nhiều lớp @msg it has art outside its layers nó có tác phẩm ngoài các lớp @msg it has more than one artboard nó có nhiều hơn một bảng vẽ @msg it has more than {MAX_NODES} objects nó có hơn {MAX_NODES} đối tượng diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index ea2563e81..3be04c6db 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -4036,11 +4036,16 @@ imageTrace Outline 轮廓 @msg unknown command `{0}`; did you mean `{1}`? 未知命令 `{0}`;您是否想输入 `{1}`? @msg `{what}` closes nothing `{what}` 没有可结束的内容 @msg `{what}` closes something it didn't open `{what}` 结束了并非由它开始的内容 +@msg a mask that covers several layers comes in as a clipping group in each layer it covers 覆盖多个图层的蒙版会在其覆盖的每个图层中作为剪切组导入 @msg a graph's records don't end where they should: its art was read as it is 图表的记录没有在应有的位置结束:其图稿按原样读取 @msg `{w}` takes no operands `{w}` 不接受操作数 @msg a wraparound group is inside a compound path 文本绕排组位于复合路径内 @msg a path has too many points 某条路径的点过多 @msg it ends inside a layer 它在图层内部结束 +@msg it ends inside a multi-layer mask 它在跨图层蒙版内部结束 +@msg a multi-layer mask's operators are out of order 跨图层蒙版的运算符顺序错误 +@msg a multi-layer mask needs one clipping path 跨图层蒙版需要一条剪切路径 +@msg a group crosses a multi-layer mask 编组跨越了跨图层蒙版 @msg it has art outside its layers 它有位于图层之外的图稿 @msg it has more than {MAX_NODES} objects 它包含超过 {MAX_NODES} 个对象 @msg it has no layers 它没有图层 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index efe0ca5d4..a046234af 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1958,11 +1958,16 @@ file-filter Templates 範本 @msg unknown command `{0}`; did you mean `{1}`? 未知的指令 `{0}`;您是指 `{1}` 嗎? @msg `{what}` closes nothing `{what}` 沒有可結束的內容 @msg `{what}` closes something it didn't open `{what}` 結束了並非由它開始的內容 +@msg a mask that covers several layers comes in as a clipping group in each layer it covers 覆蓋多個圖層的遮色片會在其覆蓋的每個圖層中作為剪裁群組匯入 @msg a graph's records don't end where they should: its art was read as it is 圖表的記錄沒有在應有的位置結束:其圖稿按原樣讀取 @msg `{w}` takes no operands `{w}` 不接受運算元 @msg a wraparound group is inside a compound path 文字繞圖群組位於複合路徑內 @msg a path has too many points 有一條路徑的點過多 @msg it ends inside a layer 它在圖層內部結束 +@msg it ends inside a multi-layer mask 它在跨圖層遮色片內部結束 +@msg a multi-layer mask's operators are out of order 跨圖層遮色片的運算子順序錯誤 +@msg a multi-layer mask needs one clipping path 跨圖層遮色片需要一條剪裁路徑 +@msg a group crosses a multi-layer mask 群組跨越了跨圖層遮色片 @msg it has art outside its layers 它有位於圖層之外的圖稿 @msg it has more than {MAX_NODES} objects 它有超過 {MAX_NODES} 個物件 @msg it has no layers 它沒有圖層 diff --git a/docs/file-format-parity.md b/docs/file-format-parity.md index 27ef5b5e8..106c4086e 100644 --- a/docs/file-format-parity.md +++ b/docs/file-format-parity.md @@ -43,6 +43,8 @@ private editing data. VectorCraft: write) is its own editing data: it opens through the layers its program has, compared with its page when the program runs, from the layers alone when it names its prolog without including it (#1027), and with its art on one layer when it has no layers (Illustrator 3, and image editors' exported paths: storytold/photocraft#6036). + A multi-layer mask (`Mb` … `Md` … `MB`) comes in as a clipping group in each layer it covers, each + clipped by a copy of the mask's path. RGB in the PDF page's `ICCBased` spaces keeps the file's numbers in a `.ai` (the `uncalibrated_rgb` import option, on for editor documents, off for PDFs), so `.ai` files tagged with a non-sRGB RGB profile open with different, unconverted RGB colours than before.