diff --git a/crates/effects/src/group.rs b/crates/effects/src/group.rs index fa82303a7..5463857ed 100644 --- a/crates/effects/src/group.rs +++ b/crates/effects/src/group.rs @@ -4,7 +4,8 @@ //! children are the Pathfinder stack (back → front). In the shape modes (Add, Subtract, Intersect, //! Exclude, Minus Back) a child group acts as one shape — the union of its leaves, styled like its //! top leaf — as in the Pathfinder panel. The result replaces the children at render time; each -//! result keeps the appearance of the object whose paint it takes. +//! result keeps the appearance of the object whose paint it takes. Trap ([`crate::trap`]) keeps the +//! children and adds its traps above them. //! //! **Container appearance** ([`evaluate_container`]): a group's or layer's own fills and strokes //! paint every member's geometry, below or above the members as its contents slot says @@ -197,6 +198,25 @@ pub fn pathfinder_children(group: &Node, hook: Option) -> Option] = group.children()?; let mut stack: Vec> = source.to_vec(); for e in group.appearance.effects.iter().filter(|e| e.visible) { + if e.id == crate::trap::TRAP { + let mut lv = vec![]; + for c in &stack { + leaves(c, hook, &mut lv); + } + if !within_budget(&lv) { + return Some(source.to_vec()); + } + let (ids, srcs): (Vec, Vec) = lv + .iter() + .filter_map(|l| { + let (path, rule) = geometry(l)?; + Some((l.id, crate::trap::TrapSource { path, rule, fill: l.appearance.fill_paint() })) + }) + .unzip(); + let made = crate::trap::traps(&srcs, &crate::trap::TrapOptions::from_params(&e.params)); + stack.extend(made.into_iter().map(|p| Arc::new(crate::trap::trap_node(ids.get(p.from).copied().unwrap_or(NodeId(0)), p)))); + continue; + } let Some(op) = op_of(&e.id) else { continue }; let shape_mode = matches!(op, PathfinderOp::Unite | PathfinderOp::MinusFront | PathfinderOp::Intersect | PathfinderOp::Exclude | PathfinderOp::MinusBack); @@ -519,6 +539,25 @@ mod tests { ) } + /// Effect › Pathfinder › Trap keeps the members and adds an overprinting strip of the lighter + /// colour inside the darker one along the edge where they meet. + #[test] + fn trap_effect_adds_an_overprinting_strip_above_the_members() { + let yellow = Color::cmyk(0.0, 0.0, 1.0, 0.0); + let blue = Color::cmyk(1.0, 0.6, 0.0, 0.0); + let mut g = Node::group(NodeId(10), vec![sq(1, 0.0, yellow), sq(2, 10.0, blue)]); + let params = serde_json::json!({"thickness": 1.0, "tintReduction": 100}); + g.appearance.effects.push(Effect { id: crate::trap::TRAP.into(), params, visible: true }); + let out = pathfinder_children(&g, None).unwrap(); + assert_eq!(out.len(), 3, "both squares and one trap"); + assert_eq!((out[0].id, out[1].id), (NodeId(1), NodeId(2))); + let b = out[2].geometric_bounds().unwrap(); + assert!((b.x0 - 10.0).abs() < 0.02 && (b.x1 - 11.0).abs() < 0.02 && b.y0.abs() < 0.02 && (b.y1 - 10.0).abs() < 0.02, "{b:?}"); + let fill = out[2].appearance.fill().unwrap(); + assert!(fill.overprint); + assert_eq!(fill.paint.color(), Some(yellow)); + } + #[test] fn hard_mix_takes_the_highest_of_each_cmyk_channel_and_drops_strokes() { let mut g = two_overlapping_cmyk_squares(); diff --git a/crates/effects/src/lib.rs b/crates/effects/src/lib.rs index 23d839726..ce1b1d58d 100644 --- a/crates/effects/src/lib.rs +++ b/crates/effects/src/lib.rs @@ -51,6 +51,7 @@ mod revolve; mod solids; pub mod stroke; mod stylize; +pub mod trap; mod util; mod warp; @@ -89,6 +90,7 @@ pub use solids::{ EXTRUDE, INFLATE, ROTATE, ROTATION_PRESETS, SOLIDS, extrude_options, has_solid, inflate_options, is_three_d, solid_art, validate_solid, validate_three_d, view_options, }; +pub use trap::TRAP; pub use warp::{WarpStyle, warp_point}; /// Catalogue entry for one effect. @@ -169,6 +171,7 @@ fn lengths_of(id: &str) -> Lengths { "sketch.notePaper" => always(&["relief"]), "sketch.tornEdges" => always(&["smoothness"]), "sketch.waterPaper" => always(&["fiberLength"]), + TRAP => always(&["thickness", "precision"]), RASTERIZE => always(&["addAround"]), _ => Lengths::default(), } @@ -850,6 +853,7 @@ pub fn effect_catalog() -> Vec { v.push(g(id, label, PATHFINDER, "{} (groups and layers: live Pathfinder over the members)", json!({}))); } } + v.push(g(TRAP, "Trap…", PATHFINDER, trap::EFFECT_PARAMS_DOC, trap::effect_defaults())); v.push(g( CROP_MARKS, "Crop Marks", diff --git a/crates/effects/src/trap.rs b/crates/effects/src/trap.rs new file mode 100644 index 000000000..a0a26bbf8 --- /dev/null +++ b/crates/effects/src/trap.rs @@ -0,0 +1,387 @@ +//! Pathfinder › Trap, shared by the Pathfinder panel's Trap command (`object.pathfinder.trap`) and +//! the live effect Effect › Pathfinder › Trap ([`TRAP`]). +//! +//! Where two colours printed from separate plates meet, a press that registers the plates slightly +//! off leaves a sliver of paper showing between them. A trap closes that gap: a thin extra shape +//! along the edge where the colours meet, lying over the darker colour, painted with the lighter +//! colour and set to overprint, so the lighter colour seems to spread into the darker one (a +//! lighter object on a darker background) or to choke it (a lighter background around a darker +//! object). The options: +//! +//! - **Thickness** (0.01–5000 pt): how far the lighter colour reaches into the darker one across +//! vertical edges. +//! - **Height/Width** (%): the reach across horizontal edges as a percentage of the vertical one +//! (100%: the same everywhere), for paper that stretches more one way on press. +//! - **Tint Reduction** (%): the trap paints the lighter colour at this percentage of its tint +//! (100: at full strength), so two light colours don't print a dark border where they overlap. +//! - **Traps with Process Color**: a trap of a swatch colour (a spot colour) is painted with the +//! equivalent CMYK process colour instead of the swatch. +//! - **Reverse Traps**: the darker colour spreads into the lighter one instead; not when the +//! darker colour is a rich black (100% K with other inks), which stays where it is. +//! - **Precision** and **Remove Redundant Points** (the effect only): the tolerance in points the +//! trap's outline is refitted to, and whether anchors that don't change its shape are dropped. +//! +//! Only objects filled with a solid colour take part; each object's visible part (what the objects +//! above leave of it) is what traps, so a trap follows the edges that actually show. Two objects +//! filled with the same colour need no trap. The lighter colour is the one with the higher CIE +//! lightness (L*); between colours of equal lightness, the one with less ink. +//! +//! Behaviour from the public descriptions only: +//! and +//! (feature names and +//! option ranges; the defaults of 0.25 pt, 100% and 40% are VectorCraft's own choices, the pages +//! give none). + +use serde_json::{Value, json}; +use vectorcraft_doc::color::{Color, Paint}; +use vectorcraft_doc::{Appearance, AppearanceItem, FillLayer, Node, NodeId}; +use vectorcraft_geom::{FillRule, PathData}; +use vectorcraft_pathops as po; + +use crate::util::{flag, num}; + +/// Effect id of Effect › Pathfinder › Trap. +pub const TRAP: &str = "pathfinder.trap"; + +/// Undo label and name of the Pathfinder panel's Trap. +pub const TRAP_LABEL: &str = "Trap"; + +/// Parameter documentation of the effect (the panel command `object.pathfinder.trap` documents the +/// same keys but precision and removeRedundantPoints). +pub const EFFECT_PARAMS_DOC: &str = "{thickness?: pt 0.01..5000 (0.25; how far the lighter colour reaches into the darker one across vertical edges), heightWidth?: % 1..1000 (100; the reach across horizontal edges as a percentage of the vertical one), tintReduction?: % 0..100 (40; the trap paints the lighter colour at this percentage of its tint), trapsWithProcessColor?: bool (false; a swatch (spot) colour's trap is painted with its CMYK process equivalent), reverseTraps?: bool (false; the darker colour spreads into the lighter one, except a rich black), precision?: pt 0.001..10 (0.01; the tolerance the trap's outline is refitted to), removeRedundantPoints?: bool (true)} adds overprinting traps where the members' visible areas of different solid colours meet (groups and layers: live Pathfinder over the members)"; + +/// The options of a trap; [`TrapOptions::from_params`] reads and bounds them. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct TrapOptions { + /// Reach across vertical edges, in points (0.01..=5000). + pub thickness: f64, + /// Reach across horizontal edges as a fraction of `thickness` (0.01..=10). + pub height_width: f64, + /// The fraction of the lighter colour's tint the trap paints with (0..=1). + pub tint: f32, + /// Paint traps of swatch colours with their CMYK equivalent. + pub process: bool, + /// Spread the darker colour into the lighter one. + pub reverse: bool, + /// Refit tolerance of the trap outline, in points (0.001..=10). + pub precision: f64, + /// Drop anchors that don't change the trap's shape. + pub remove_redundant: bool, +} + +impl Default for TrapOptions { + fn default() -> Self { + TrapOptions { + thickness: 0.25, + height_width: 1.0, + tint: 0.4, + process: false, + reverse: false, + precision: po::DEFAULT_PRECISION, + remove_redundant: true, + } + } +} + +impl TrapOptions { + /// The options in `p` (keys of [`EFFECT_PARAMS_DOC`]): a missing or non-numeric value takes its + /// default and a value out of range its nearest bound, so a live effect always evaluates. + pub fn from_params(p: &Value) -> Self { + let d = TrapOptions::default(); + let (hw_min, hw_max) = po::TRAP_HEIGHT_WIDTH_RANGE; + TrapOptions { + thickness: num(p, "thickness", d.thickness).clamp(po::TRAP_MIN_WIDTH, po::TRAP_MAX_WIDTH), + height_width: (num(p, "heightWidth", d.height_width * 100.0) / 100.0).clamp(hw_min, hw_max), + tint: (num(p, "tintReduction", f64::from(d.tint) * 100.0) / 100.0).clamp(0.0, 1.0) as f32, + process: flag(p, "trapsWithProcessColor", d.process), + reverse: flag(p, "reverseTraps", d.reverse), + precision: num(p, "precision", d.precision).clamp(0.001, 10.0), + remove_redundant: flag(p, "removeRedundantPoints", d.remove_redundant), + } + } +} + +/// The Pathfinder panel Trap dialog's fields, at their defaults. +pub fn command_defaults() -> Value { + let d = TrapOptions::default(); + json!({ + "thickness": d.thickness, + "heightWidth": d.height_width * 100.0, + "tintReduction": (f64::from(d.tint) * 100.0).round(), + "trapsWithProcessColor": d.process, + "reverseTraps": d.reverse, + }) +} + +/// The Effect › Pathfinder › Trap dialog's fields, at their defaults. +pub fn effect_defaults() -> Value { + let d = TrapOptions::default(); + let mut v = command_defaults(); + if let Some(o) = v.as_object_mut() { + o.insert("precision".into(), json!(d.precision)); + o.insert("removeRedundantPoints".into(), json!(d.remove_redundant)); + } + v +} + +/// One object taking part in a trap, back to front: its outline and its fill. +#[derive(Clone, Debug)] +pub struct TrapSource { + pub path: PathData, + pub rule: FillRule, + pub fill: Paint, +} + +/// One trap: its outline, its paint (the spreading colour, tinted) and the sources it spreads +/// from and into (indices into the sources given to [`traps`]). +#[derive(Clone, Debug)] +pub struct TrapPiece { + pub path: PathData, + pub paint: Paint, + pub from: usize, + pub into: usize, +} + +/// The solid colour a source traps with: none for no fill, gradients, patterns and Registration. +fn trap_color(p: &Paint) -> Option { + if p.is_registration() { None } else { p.color() } +} + +fn finite(p: &PathData) -> bool { + p.subpaths.iter().flat_map(|sp| sp.anchors.iter()).all(|a| a.p.x.is_finite() && a.p.y.is_finite()) +} + +/// Is `a` lighter than `b`? Higher L*; at equal lightness, less ink. +fn lighter(a: Color, b: Color) -> bool { + let (la, lb) = (a.to_lab().l, b.to_lab().l); + if (la - lb).abs() > 1e-3 { + return la > lb; + } + let ink = |c: Color| c.to_cmyk().iter().sum::(); + ink(a) < ink(b) +} + +/// 100% black with other inks under it. +fn rich_black(c: Color) -> bool { + matches!(c, Color::Cmyk { c, m, y, k } if k >= 0.995 && c + m + y > 0.005) +} + +/// Do `a` and `b` print the same (no trap needed)? +fn same_color(a: &Paint, b: &Paint, ca: Color, cb: Color) -> bool { + let link = |p: &Paint| match p { + Paint::Solid { swatch, .. } => swatch.clone(), + _ => None, + }; + link(a) == link(b) && ca.to_cmyk().iter().zip(cb.to_cmyk()).all(|(x, y)| (x - y).abs() < 1e-4) +} + +/// The paint of a trap spreading `p` at tint `t`. +fn trap_paint(p: &Paint, color: Color, o: &TrapOptions) -> Paint { + // A solid colour shows its swatch at `tint` already, so tinting it again gives the swatch at + // `tint × t` ([`Color::tinted`] multiplies in every model). + let shown = color.tinted(o.tint); + match p { + Paint::Solid { swatch: Some(_), .. } if o.process => { + let [c, m, y, k] = shown.to_cmyk(); + Paint::solid(Color::cmyk(c, m, y, k)) + } + Paint::Solid { swatch: Some(s), tint, .. } => Paint::Solid { color: shown, swatch: Some(s.clone()), tint: tint * o.tint }, + _ => Paint::solid(shown), + } +} + +/// The visible part of each source: the faces of their arrangement that it is the top one of. +/// Sources that paint no fill hide nothing and have none. +fn visible(sources: &[TrapSource]) -> Vec { + let mut shapes = vec![]; + for (i, s) in sources.iter().enumerate() { + if !s.fill.is_none() && !s.path.is_empty() && finite(&s.path) { + shapes.push(po::Shape::new(s.path.clone(), s.rule, i as u64)); + } + } + let mut faces: Vec> = vec![vec![]; sources.len()]; + for r in po::regions(&shapes) { + if let Some(face) = r.sources.last().and_then(|&si| shapes.get(si)).and_then(|s| faces.get_mut(s.key as usize)) { + face.push(r.path); + } + } + faces + .into_iter() + .map(|mut f| match f.len() { + 0 => PathData::default(), + 1 => f.pop().unwrap_or_default(), + _ => po::unite_all(&f.iter().map(|p| (p, FillRule::NonZero)).collect::>()), + }) + .collect() +} + +/// The traps between `sources` (back to front) with options `o`: for each two sources of +/// different solid colours whose visible parts meet, the strip of the darker one's visible part +/// next to the lighter one's (the other way round with [`TrapOptions::reverse`]), painted per +/// [`trap_paint`]. Empty when no visible edges of different colours meet. +pub fn traps(sources: &[TrapSource], o: &TrapOptions) -> Vec { + let colors: Vec> = sources.iter().map(|s| trap_color(&s.fill)).collect(); + if colors.iter().flatten().count() < 2 { + return vec![]; + } + let vis = visible(sources); + let mut out = vec![]; + for i in 0..sources.len() { + for j in i + 1..sources.len() { + let (Some(ci), Some(cj)) = (colors[i], colors[j]) else { continue }; + if vis[i].is_empty() || vis[j].is_empty() || same_color(&sources[i].fill, &sources[j].fill, ci, cj) { + continue; + } + let (mut from, mut into) = if lighter(ci, cj) { (i, j) } else { (j, i) }; + if o.reverse && colors[into].is_some_and(|c| !rich_black(c)) { + (from, into) = (into, from); + } + let mut band = po::trap_band(&vis[from], FillRule::NonZero, &vis[into], FillRule::NonZero, o.thickness, o.height_width, o.precision); + if o.remove_redundant { + band = po::remove_redundant_points(&band, o.precision); + } + if band.is_empty() || po::area(&band, FillRule::NonZero) <= 1e-9 { + continue; + } + let Some(color) = colors[from] else { continue }; + out.push(TrapPiece { path: band, paint: trap_paint(&sources[from].fill, color, o), from, into }); + } + } + out +} + +/// A trap's appearance: one overprinting fill of `paint`, no stroke. +pub fn trap_appearance(paint: Paint) -> Appearance { + Appearance { items: vec![AppearanceItem::Fill(FillLayer { overprint: true, ..FillLayer::new(paint) })], ..Default::default() } +} + +/// A trap as a path object with id `id`. +pub fn trap_node(id: NodeId, piece: TrapPiece) -> Node { + Node::path(id, piece.path, trap_appearance(piece.paint)) +} + +#[cfg(test)] +mod tests { + use vectorcraft_geom::Rect; + use vectorcraft_geom::shapes::rectangle; + + use super::*; + + fn src(r: Rect, c: Color) -> TrapSource { + TrapSource { path: rectangle(r), rule: FillRule::NonZero, fill: Paint::solid(c) } + } + + fn yellow() -> Color { + Color::cmyk(0.0, 0.0, 1.0, 0.0) + } + + fn blue() -> Color { + Color::cmyk(1.0, 0.6, 0.0, 0.0) + } + + fn near(a: f64, b: f64) -> bool { + (a - b).abs() < 0.02 + } + + fn abutting() -> Vec { + vec![src(Rect::new(0.0, 0.0, 50.0, 40.0), yellow()), src(Rect::new(50.0, 0.0, 100.0, 40.0), blue())] + } + + #[test] + fn the_lighter_colour_spreads_into_the_darker_along_the_shared_edge() { + let o = TrapOptions { thickness: 3.0, tint: 1.0, ..Default::default() }; + let t = traps(&abutting(), &o); + assert_eq!(t.len(), 1); + let b = t[0].path.bounds().unwrap(); + assert!(near(b.x0, 50.0) && near(b.x1, 53.0) && near(b.y0, 0.0) && near(b.y1, 40.0), "inside the blue, 3 pt wide: {b:?}"); + assert_eq!((t[0].from, t[0].into), (0, 1)); + assert_eq!(t[0].paint.color(), Some(yellow())); + let n = trap_node(NodeId(7), t[0].clone()); + assert!(n.appearance.fill().is_some_and(|f| f.overprint), "the trap overprints"); + assert!(n.appearance.stroke_paint().is_none()); + } + + #[test] + fn reverse_traps_spread_the_darker_colour() { + let o = TrapOptions { thickness: 2.0, reverse: true, tint: 1.0, ..Default::default() }; + let t = traps(&abutting(), &o); + assert_eq!(t.len(), 1); + let b = t[0].path.bounds().unwrap(); + assert!(near(b.x0, 48.0) && near(b.x1, 50.0), "inside the yellow: {b:?}"); + assert_eq!(t[0].paint.color(), Some(blue())); + } + + #[test] + fn reverse_traps_leave_rich_black_alone() { + let rich = Color::cmyk(0.6, 0.4, 0.4, 1.0); + let s = vec![src(Rect::new(0.0, 0.0, 50.0, 40.0), yellow()), src(Rect::new(50.0, 0.0, 100.0, 40.0), rich)]; + let t = traps(&s, &TrapOptions { reverse: true, ..Default::default() }); + assert_eq!(t[0].from, 0, "the yellow still spreads into the rich black"); + } + + #[test] + fn tint_reduction_tints_the_trap_and_keeps_a_swatch_unless_process_colour() { + let mut s = abutting(); + s[0].fill = Paint::Solid { color: yellow(), swatch: Some("PANTONE Yellow".into()), tint: 1.0 }; + let t = traps(&s, &TrapOptions { tint: 0.5, ..Default::default() }); + match &t[0].paint { + Paint::Solid { color, swatch, tint } => { + assert_eq!(swatch.as_deref(), Some("PANTONE Yellow")); + assert!((tint - 0.5).abs() < 1e-6); + assert!((color.to_cmyk()[2] - 0.5).abs() < 1e-4, "{color:?}"); + } + p => panic!("{p:?}"), + } + let t = traps(&s, &TrapOptions { tint: 0.5, process: true, ..Default::default() }); + match &t[0].paint { + Paint::Solid { color, swatch: None, .. } => assert!(matches!(color, Color::Cmyk { .. })), + p => panic!("{p:?}"), + } + } + + #[test] + fn a_lighter_object_on_a_darker_background_spreads_all_round() { + // Yellow square on top of a blue background: a ring around the yellow, inside the blue. + let s = vec![src(Rect::new(0.0, 0.0, 100.0, 100.0), blue()), src(Rect::new(40.0, 40.0, 60.0, 60.0), yellow())]; + let t = traps(&s, &TrapOptions { thickness: 1.0, ..Default::default() }); + assert_eq!(t.len(), 1); + let b = t[0].path.bounds().unwrap(); + assert!(near(b.x0, 39.0) && near(b.x1, 61.0) && near(b.y0, 39.0) && near(b.y1, 61.0), "{b:?}"); + assert!(near(po::area(&t[0].path, FillRule::NonZero), 22.0 * 22.0 - 400.0)); + } + + #[test] + fn no_trap_without_adjoining_edges_of_different_colours() { + let apart = vec![src(Rect::new(0.0, 0.0, 10.0, 10.0), yellow()), src(Rect::new(30.0, 0.0, 40.0, 10.0), blue())]; + assert!(traps(&apart, &TrapOptions::default()).is_empty()); + let same = vec![src(Rect::new(0.0, 0.0, 10.0, 10.0), blue()), src(Rect::new(10.0, 0.0, 20.0, 10.0), blue())]; + assert!(traps(&same, &TrapOptions::default()).is_empty()); + let mut unfilled = abutting(); + unfilled[1].fill = Paint::None; + assert!(traps(&unfilled, &TrapOptions::default()).is_empty()); + } + + #[test] + fn options_are_bounded_and_bad_numbers_take_defaults() { + let o = TrapOptions::from_params(&json!({"thickness": 1e300, "heightWidth": -5, "tintReduction": 400, "precision": "x"})); + assert_eq!(o.thickness, po::TRAP_MAX_WIDTH); + assert_eq!(o.height_width, po::TRAP_HEIGHT_WIDTH_RANGE.0); + assert_eq!(o.tint, 1.0); + assert_eq!(o.precision, po::DEFAULT_PRECISION); + let o = TrapOptions::from_params(&json!({"thickness": "NaN"})); + assert_eq!(o.thickness, TrapOptions::default().thickness); + // The widest trap still stays inside the darker shape. + let t = traps(&abutting(), &TrapOptions { thickness: f64::NAN, ..Default::default() }); + assert!(t.is_empty(), "a NaN thickness traps nothing"); + let t = traps(&abutting(), &TrapOptions { thickness: po::TRAP_MAX_WIDTH, ..Default::default() }); + let b = t[0].path.bounds().unwrap(); + assert!(near(b.x0, 50.0) && near(b.x1, 100.0), "{b:?}"); + } + + #[test] + fn nan_coordinates_are_left_out() { + let mut s = abutting(); + s[1].path.subpaths[0].anchors[0].p.x = f64::NAN; + assert!(traps(&s, &TrapOptions::default()).is_empty()); + } +} diff --git a/crates/engine/src/cmd/pathops.rs b/crates/engine/src/cmd/pathops.rs index 6bb1c3620..f898ff40e 100644 --- a/crates/engine/src/cmd/pathops.rs +++ b/crates/engine/src/cmd/pathops.rs @@ -108,6 +108,15 @@ pub fn specs() -> Vec { has_selection, |s, _| run_pf(s, PathfinderOp::MinusBack) ), + cmd!( + "object.pathfinder.trap", + "Trap…", + ["Window", "Pathfinder"], + None, + "{ids?: [id…] (selected first; default: the selection), thickness?: pt 0.01..5000 (0.25; how far the lighter colour reaches into the darker one across vertical edges), heightWidth?: % 1..1000 (100; the reach across horizontal edges as a percentage of the vertical one), tintReduction?: % 0..100 (40; the trap paints the lighter colour at this percentage of its tint), trapsWithProcessColor?: bool (false; a swatch (spot) colour's trap is painted with its CMYK process equivalent), reverseTraps?: bool (false; the darker colour spreads into the lighter one, except a rich black)} adds overprinting traps where the selected objects' visible areas of different solid colours meet, grouped directly above the topmost selected object and selected; the objects stay as they are → {ids}", + has_selection, + trap + ), cmd!( "object.path.outlineStroke", "Outline Stroke", @@ -500,6 +509,68 @@ fn run_pf(s: &mut Session, op: PathfinderOp) -> Result { Ok(ids_json(&ids)) } +// ---------- Trap ---------- + +/// Pathfinder panel › Trap ([`vectorcraft_render::effects::trap`]): the traps between the selected +/// objects' leaf shapes, as one group of overprinting paths above the topmost selected object. +fn trap(s: &mut Session, p: &Value) -> Result { + use vectorcraft_render::effects::trap::{TRAP_LABEL, TrapOptions, TrapSource, trap_appearance, traps}; + const ID: &str = "object.pathfinder.trap"; + let mut q = p.clone(); + if let Some(t) = len_param(p, "thickness") { + if !(po::TRAP_MIN_WIDTH..=po::TRAP_MAX_WIDTH).contains(&t) { + return Err(bad(ID, "thickness must be from 0.01 to 5000 pt")); + } + q["thickness"] = json!(t); + } + if num_param(p, "heightWidth").is_some_and(|v| !(1.0..=1000.0).contains(&v)) { + return Err(bad(ID, "heightWidth must be from 1 to 1000 %")); + } + if num_param(p, "tintReduction").is_some_and(|v| !(0.0..=100.0).contains(&v)) { + return Err(bad(ID, "tintReduction must be from 0 to 100 %")); + } + // The effect-only settings stay at their defaults. + if let Some(o) = q.as_object_mut() { + o.remove("precision"); + o.remove("removeRedundantPoints"); + } + let options = TrapOptions::from_params(&q); + let roots = selected_roots(s)?; + if roots.is_empty() { + return Err(EngineError::Other("nothing selected".into())); + } + let ids = s.edit(TRAP_LABEL, |d, sel| { + let mut sources = vec![]; + for id in &roots { + let n = d.node(*id).ok_or(EngineError::NoNode(*id))?; + each_leaf(n, &mut |l| { + if let Some((path, rule)) = node_path(l) { + sources.push(TrapSource { path, rule, fill: style_of(l).appearance.fill_paint() }); + } + }); + } + if sources.iter().filter(|s| s.fill.color().is_some()).count() < 2 { + return Err(EngineError::Other("Trap: select two or more objects filled with solid colours".into())); + } + let made = traps(&sources, &options); + if made.is_empty() { + return Err(EngineError::Other("Trap: no edges of different colours meet among the selected objects".into())); + } + let mut children = Vec::with_capacity(made.len()); + for piece in made { + let mut n = shape_node(d, piece.path, None); + n.appearance = trap_appearance(piece.paint).into(); + children.push(Arc::new(n)); + } + let gid = d.alloc_id(); + let top = *roots.last().ok_or_else(|| EngineError::Other("nothing selected".into()))?; + d.insert_beside(vec![(top, vec![Node::group(gid, children)])], false)?; + sel.set([gid]); + Ok(vec![gid]) + })?; + Ok(ids_json(&ids)) +} + // ---------- Offset Path ---------- fn join_param(p: &Value, key: &str) -> po::Join { diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 7726abd56..9d8187052 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -2620,6 +2620,8 @@ mod tests_transformfx; #[cfg(test)] mod tests_transparencygrid; #[cfg(test)] +mod tests_trap; +#[cfg(test)] mod tests_typearea; #[cfg(test)] mod tests_typescale; @@ -2664,6 +2666,7 @@ pub(crate) const SELECTS_GIVEN_IDS: &[&str] = &[ "object.pathfinder.minusBack", "object.pathfinder.minusFront", "object.pathfinder.outline", + "object.pathfinder.trap", "object.pathfinder.trim", "object.pathfinder.unite", "object.reflect", diff --git a/crates/engine/src/tests_trap.rs b/crates/engine/src/tests_trap.rs new file mode 100644 index 000000000..fd7619329 --- /dev/null +++ b/crates/engine/src/tests_trap.rs @@ -0,0 +1,148 @@ +//! Tests for Pathfinder panel › Trap (`object.pathfinder.trap`). + +use serde_json::{Value, json}; +use vectorcraft_color::{Color, Paint}; +use vectorcraft_doc::{Node, NodeKind}; +use vectorcraft_geom::Rect; + +use super::*; + +const YELLOW: Color = Color::Cmyk { c: 0.0, m: 0.0, y: 1.0, k: 0.0 }; +const BLUE: Color = Color::Cmyk { c: 1.0, m: 0.6, y: 0.0, k: 0.0 }; + +/// A yellow rectangle (0..50) abutting a blue one (50..100), both 40 pt tall, both selected. +fn abutting() -> (Session, NodeId, NodeId) { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + let mut rect = |x: f64, c: Color| { + let id = NodeId(s.execute("shape.rectangle", &json!({"x": x, "y": 0, "width": 50, "height": 40})).unwrap()["id"].as_u64().unwrap()); + s.edit("fill", |d, _| { + if let Some(n) = d.node_mut(id) { + n.appearance.set_fill(Paint::solid(c)); + } + Ok(()) + }) + .unwrap(); + id + }; + let (a, b) = (rect(0.0, YELLOW), rect(50.0, BLUE)); + s.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap(); + (s, a, b) +} + +fn ids(v: &Value) -> Vec { + v["ids"].as_array().unwrap().iter().map(|x| NodeId(x.as_u64().unwrap())).collect() +} + +fn node(s: &Session, id: NodeId) -> Node { + s.doc().unwrap().doc.node(id).cloned().unwrap() +} + +fn near(a: f64, b: f64) -> bool { + (a - b).abs() < 0.02 +} + +/// The traps of the trap group the command made. +fn traps(s: &Session, v: &Value) -> Vec { + let g = node(s, ids(v)[0]); + assert!(matches!(g.kind, NodeKind::Group { .. })); + g.children().unwrap().iter().map(|c| (**c).clone()).collect() +} + +#[test] +fn trap_spreads_the_lighter_colour_into_the_darker_along_the_shared_edge() { + let (mut s, a, b) = abutting(); + let before = s.doc().unwrap().doc.clone(); + let undo_len = s.doc().unwrap().history.undo.len(); + let r = s.execute("object.pathfinder.trap", &json!({"thickness": 2, "tintReduction": 100})).unwrap(); + let t = traps(&s, &r); + assert_eq!(t.len(), 1); + let bb = t[0].geometric_bounds().unwrap(); + assert!(near(bb.x0, 50.0) && near(bb.x1, 52.0) && near(bb.y0, 0.0) && near(bb.y1, 40.0), "2 pt into the blue: {bb:?}"); + let fill = t[0].appearance.fill().unwrap(); + assert_eq!(fill.paint.color(), Some(YELLOW), "filled with the lighter colour"); + assert!(fill.overprint, "set to overprint"); + assert!(t[0].appearance.stroke_paint().is_none()); + // The originals stay; the traps sit directly above the topmost one and are selected. + let st = s.doc().unwrap(); + assert_eq!(node(&s, a), *before.node(a).unwrap()); + assert_eq!(node(&s, b), *before.node(b).unwrap()); + let (parent, ib, _) = st.doc.position(b).unwrap(); + let (gparent, ig, _) = st.doc.position(ids(&r)[0]).unwrap(); + assert_eq!((gparent, ig), (parent, ib + 1)); + assert_eq!(st.selection.objects, ids(&r)); + // One undo step. + assert_eq!(st.history.undo.len(), undo_len + 1); + s.execute("edit.undo", &json!({})).unwrap(); + assert_eq!(s.doc().unwrap().doc, before); +} + +#[test] +fn height_width_and_tint_reduction_shape_and_tint_the_trap() { + let (mut s, _, _) = abutting(); + let r = s.execute("object.pathfinder.trap", &json!({"thickness": "1 pt", "tintReduction": 25})).unwrap(); + let t = traps(&s, &r); + let [_, _, y, _] = t[0].appearance.fill_paint().color().unwrap().to_cmyk(); + assert!((y - 0.25).abs() < 1e-4, "yellow at 25%: {y}"); + let bb = t[0].geometric_bounds().unwrap(); + assert!(near(bb.x1, 51.0), "{bb:?}"); +} + +#[test] +fn reverse_traps_spread_the_darker_colour_into_the_lighter() { + let (mut s, _, _) = abutting(); + let r = s.execute("object.pathfinder.trap", &json!({"thickness": 2, "tintReduction": 100, "reverseTraps": true})).unwrap(); + let t = traps(&s, &r); + let bb = t[0].geometric_bounds().unwrap(); + assert!(near(bb.x0, 48.0) && near(bb.x1, 50.0), "2 pt into the yellow: {bb:?}"); + assert_eq!(t[0].appearance.fill_paint().color(), Some(BLUE)); +} + +#[test] +fn no_adjoining_edges_is_a_clear_error_that_changes_nothing() { + let (mut s, a, b) = abutting(); + s.edit("apart", |d, _| { + if let Some(n) = d.node_mut(b) { + n.kind = NodeKind::Path { + path: vectorcraft_geom::shapes::rectangle(Rect::new(200.0, 0.0, 250.0, 40.0)), + rule: vectorcraft_geom::FillRule::NonZero, + live: None, + clipping: false, + guide: false, + }; + } + Ok(()) + }) + .unwrap(); + s.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap(); + let before = s.doc().unwrap().doc.clone(); + let undo_len = s.doc().unwrap().history.undo.len(); + let e = s.execute("object.pathfinder.trap", &json!({})).unwrap_err().to_string(); + assert!(e.contains("no edges of different colours meet"), "{e}"); + assert_eq!(s.doc().unwrap().doc, before); + assert_eq!(s.doc().unwrap().history.undo.len(), undo_len); + // One object alone has nothing to trap with. + s.execute("select.set", &json!({"ids": [a.0]})).unwrap(); + let e = s.execute("object.pathfinder.trap", &json!({})).unwrap_err().to_string(); + assert!(e.contains("two or more objects"), "{e}"); +} + +#[test] +fn out_of_range_and_unreadable_numbers_are_refused_or_defaulted() { + let (mut s, _, _) = abutting(); + for p in [json!({"thickness": 1e300}), json!({"thickness": 0}), json!({"heightWidth": 0}), json!({"tintReduction": 101})] { + assert!(s.execute("object.pathfinder.trap", &p).is_err(), "{p}"); + } + // A thickness that isn't a number falls back to the default. + let r = s.execute("object.pathfinder.trap", &json!({"thickness": "NaN", "tintReduction": 100})).unwrap(); + let bb = traps(&s, &r)[0].geometric_bounds().unwrap(); + assert!(near(bb.x1, 50.25), "{bb:?}"); +} + +#[test] +fn ids_name_the_objects_to_trap() { + let (mut s, a, b) = abutting(); + s.execute("select.set", &json!({"ids": []})).unwrap(); + let r = s.execute("object.pathfinder.trap", &json!({"ids": [a.0, b.0]})).unwrap(); + assert_eq!(traps(&s, &r).len(), 1); +} diff --git a/crates/pathops/src/lib.rs b/crates/pathops/src/lib.rs index 62d3338d8..12fafac2b 100644 --- a/crates/pathops/src/lib.rs +++ b/crates/pathops/src/lib.rs @@ -12,6 +12,7 @@ mod fit; mod offset; mod pathfinder; mod planar; +mod trap; pub use boolean::{BoolOp, DEFAULT_PRECISION, area, boolean, boolean_n, normalize, try_boolean, try_normalize, unite_all}; pub use edit::{ @@ -24,6 +25,7 @@ pub use planar::{ BuilderArrangement, SHAPE_BUILDER_MAX_GAP, SHAPE_BUILDER_MAX_SEGMENTS, cut_out, encloses_area, find_gaps, has_open_path, interior_point, live_paint, live_paint_with_gaps, run_around, shape_builder, shape_builder_with_gap, }; +pub use trap::{TRAP_HEIGHT_WIDTH_RANGE, TRAP_MAX_WIDTH, TRAP_MIN_WIDTH, trap_band}; /// Errors from fallible operations. #[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)] diff --git a/crates/pathops/src/trap.rs b/crates/pathops/src/trap.rs new file mode 100644 index 000000000..ab5c31571 --- /dev/null +++ b/crates/pathops/src/trap.rs @@ -0,0 +1,124 @@ +//! The geometry of a trap (Pathfinder › Trap): the thin strip where one colour is spread into the +//! area of an adjoining colour, so a slight misregistration on press leaves no paper gap between +//! them. See `vectorcraft_effects::trap` for which colour spreads and how the strip is painted. + +use kurbo::Affine; +use vectorcraft_geom::{FillRule, PathData}; + +use crate::boolean::{BoolOp, try_boolean, try_normalize}; +use crate::offset::{Cap, Join, outline_stroke}; + +/// Largest trap width, in points, as the public Trap options allow (0.01–5000 pt). +pub const TRAP_MAX_WIDTH: f64 = 5000.0; +/// Smallest trap width, in points. +pub const TRAP_MIN_WIDTH: f64 = 0.01; +/// Height/Width range (horizontal edges' trap as a fraction of vertical edges'). +pub const TRAP_HEIGHT_WIDTH_RANGE: (f64, f64) = (0.01, 10.0); + +/// Does every anchor and handle of `p` have finite coordinates? +fn finite(p: &PathData) -> bool { + p.subpaths.iter().flat_map(|sp| sp.anchors.iter()).all(|a| [a.p, a.h_in, a.h_out].iter().all(|q| q.x.is_finite() && q.y.is_finite())) +} + +/// The strip of `into` (with `into_rule`) within `width` of the edge of `spread` (with +/// `spread_rule`): where `spread`'s colour reaches when it grows by `width` across vertical edges +/// and by `width × height_width` across horizontal edges. Where the shapes overlap, the strip +/// also covers the overlap, so callers pass areas that don't overlap (the visible parts of two +/// objects). `precision` is the refit tolerance of the boolean that cuts the strip, in points. +/// +/// Empty when the shapes don't come within `width` of each other, when either is empty or has a +/// coordinate that isn't finite, or when `width` or `height_width` isn't a finite positive number. +/// `width` is capped at [`TRAP_MAX_WIDTH`] and `height_width` at [`TRAP_HEIGHT_WIDTH_RANGE`]. +pub fn trap_band( + spread: &PathData, + spread_rule: FillRule, + into: &PathData, + into_rule: FillRule, + width: f64, + height_width: f64, + precision: f64, +) -> PathData { + if !(width.is_finite() && width > 0.0 && height_width.is_finite() && height_width > 0.0) || spread.is_empty() || into.is_empty() { + return PathData::default(); + } + if !finite(spread) || !finite(into) { + return PathData::default(); + } + let w = width.clamp(TRAP_MIN_WIDTH, TRAP_MAX_WIDTH); + let r = height_width.clamp(TRAP_HEIGHT_WIDTH_RANGE.0, TRAP_HEIGHT_WIDTH_RANGE.1); + let precision = if precision.is_finite() && precision > 0.0 { precision.min(10.0) } else { crate::boolean::DEFAULT_PRECISION }; + // Quick reject: bounds farther apart than the trap reaches. + let (Some(sb), Some(ib)) = (spread.bounds(), into.bounds()) else { return PathData::default() }; + let reach = sb.inflate(w, w * r); + if reach.x1 < ib.x0 || ib.x1 < reach.x0 || reach.y1 < ib.y0 || ib.y1 < reach.y0 { + return PathData::default(); + } + // Grow uniformly in a space squashed vertically by `r`, then stretch back: horizontal edges' + // strip comes out `r` times as tall as vertical edges' is wide. + let Ok(outline) = try_normalize(spread, spread_rule) else { return PathData::default() }; + let squash = Affine::scale_non_uniform(1.0, 1.0 / r); + let ring = outline_stroke(&outline.transformed(squash), 2.0 * w, Cap::Butt, Join::Miter, 4.0); + if ring.is_empty() { + return PathData::default(); + } + let ring = ring.transformed(Affine::scale_non_uniform(1.0, r)); + try_boolean(&ring, FillRule::NonZero, into, into_rule, BoolOp::Intersect, precision).unwrap_or_default() +} + +#[cfg(test)] +mod tests { + use vectorcraft_geom::Rect; + use vectorcraft_geom::shapes::rectangle; + + use super::*; + + fn bounds(p: &PathData) -> Rect { + p.bounds().unwrap_or_default() + } + + fn near(a: f64, b: f64) -> bool { + (a - b).abs() < 0.02 + } + + #[test] + fn abutting_rectangles_trap_along_their_shared_edge() { + let light = rectangle(Rect::new(0.0, 0.0, 50.0, 40.0)); + let dark = rectangle(Rect::new(50.0, 0.0, 100.0, 40.0)); + let band = trap_band(&light, FillRule::NonZero, &dark, FillRule::NonZero, 2.0, 1.0, 0.01); + let b = bounds(&band); + assert!(near(b.x0, 50.0) && near(b.x1, 52.0) && near(b.y0, 0.0) && near(b.y1, 40.0), "{b:?}"); + assert!(near(crate::area(&band, FillRule::NonZero), 80.0)); + } + + #[test] + fn height_width_scales_the_trap_on_horizontal_edges() { + let light = rectangle(Rect::new(0.0, 0.0, 40.0, 50.0)); + let dark = rectangle(Rect::new(0.0, 50.0, 40.0, 100.0)); + let band = trap_band(&light, FillRule::NonZero, &dark, FillRule::NonZero, 2.0, 2.0, 0.01); + let b = bounds(&band); + assert!(near(b.y0, 50.0) && near(b.y1, 54.0), "a horizontal edge's trap is twice the width: {b:?}"); + } + + #[test] + fn far_apart_shapes_and_bad_numbers_give_nothing() { + let a = rectangle(Rect::new(0.0, 0.0, 10.0, 10.0)); + let b = rectangle(Rect::new(30.0, 0.0, 40.0, 10.0)); + assert!(trap_band(&a, FillRule::NonZero, &b, FillRule::NonZero, 2.0, 1.0, 0.01).is_empty()); + let c = rectangle(Rect::new(10.0, 0.0, 20.0, 10.0)); + for (w, r) in [(f64::NAN, 1.0), (f64::INFINITY, 1.0), (-1.0, 1.0), (0.0, 1.0), (1.0, f64::NAN), (1.0, 0.0)] { + assert!(trap_band(&a, FillRule::NonZero, &c, FillRule::NonZero, w, r, 0.01).is_empty(), "{w} {r}"); + } + let mut bad = a.clone(); + bad.subpaths[0].anchors[0].p.x = f64::NAN; + assert!(trap_band(&bad, FillRule::NonZero, &c, FillRule::NonZero, 1.0, 1.0, 0.01).is_empty()); + } + + #[test] + fn a_huge_width_is_capped_and_stays_inside_the_darker_shape() { + let a = rectangle(Rect::new(0.0, 0.0, 10.0, 10.0)); + let c = rectangle(Rect::new(10.0, 0.0, 20.0, 10.0)); + let band = trap_band(&a, FillRule::NonZero, &c, FillRule::NonZero, 1e300, 1e300, f64::NAN); + let b = bounds(&band); + assert!(near(b.x0, 10.0) && near(b.x1, 20.0), "the whole darker square at most: {b:?}"); + } +} diff --git a/crates/ui-egui/src/dialogs/command.rs b/crates/ui-egui/src/dialogs/command.rs index 59309c5e9..e4237ca27 100644 --- a/crates/ui-egui/src/dialogs/command.rs +++ b/crates/ui-egui/src/dialogs/command.rs @@ -13,8 +13,9 @@ pub(super) const SPEC: DialogSpec = DialogSpec { heading: |d| tl!(&d.str("__label")).to_string(), body: |app, ui, d| { let command = d.str("__command"); - let lengths = lengths(&command); - form::param_fields(ui, d, &|k| lengths.contains(&k), &|k| choices(&command, k), &|_| 0, app.session.general_unit()); + let (lengths, order) = (lengths(&command), order(&command)); + let rank = |k: &str| order.iter().position(|o| *o == k).unwrap_or(order.len()); + form::param_fields(ui, d, &|k| lengths.contains(&k), &|k| choices(&command, k), &rank, app.session.general_unit()); false }, confirm, @@ -30,6 +31,17 @@ fn lengths(command: &str) -> &'static [&'static str] { "object.repeat.options" => &["radius", "hSpacing", "vSpacing"], "text.areaOptions" => &["width", "height", "gutter", "inset", "firstBaselineMin"], "type.pathOptions" => &["spacing"], + "object.pathfinder.trap" => &["thickness"], + _ => &[], + } +} + +/// The order the fields of the commands this dialog edits come in, where the order of their +/// names isn't it (the rest follow, by name). +fn order(command: &str) -> &'static [&'static str] { + match command { + // The public Trap options' order. + "object.pathfinder.trap" => &["thickness", "heightWidth", "tintReduction", "trapsWithProcessColor", "reverseTraps"], _ => &[], } } diff --git a/crates/ui-egui/src/dialogs/form.rs b/crates/ui-egui/src/dialogs/form.rs index 5a149b7ec..b715ff98a 100644 --- a/crates/ui-egui/src/dialogs/form.rs +++ b/crates/ui-egui/src/dialogs/form.rs @@ -591,6 +591,8 @@ pub(crate) fn humanize(k: &str) -> String { "Align To Path" => "Align to Path:".into(), "Overlap Variation" | "Curviness Variation" | "Spacing Variation" => "Variation:".into(), "Create" => "Create New Fields by:".into(), + "Height Width" => "Height/Width:".into(), + "Traps With Process Color" => "Traps with Process Color:".into(), _ => format!("{s}:"), } } diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 3750e45fc..bb6c7a388 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -3293,6 +3293,13 @@ font-technology Variable Променливи Transparent Background Прозрачен фон Transverse (the page a quarter turn on the paper) Напречно (страницата е завъртена на четвърт оборот върху хартията) Trap… Застъпване… + Trap Застъпване + Height/Width Височина/ширина + Tint Reduction Намаляване на тона + Traps with Process Color Застъпвания с триадни цветове + Reverse Traps Обратни застъпвания + Precision Точност + Remove Redundant Points Премахване на излишните точки Trim Изрязване Trim mark weight: Дебелина на маркировките за изрязване: Trim marks Маркировки за изрязване @@ -5416,6 +5423,11 @@ axis Y Axis Y ос The plug-in is computing… Приставката изчислява… Inspect Plug-in Проверка на приставка Cancel Plug-in Run Отказ от изпълнението на приставката +@msg Trap: select two or more objects filled with solid colours Застъпване: изберете два или повече обекта, запълнени с плътни цветове +@msg Trap: no edges of different colours meet among the selected objects Застъпване: сред избраните обекти няма допиращи се ръбове с различни цветове +@msg thickness must be from 0.01 to 5000 pt thickness трябва да е от 0,01 до 5000 pt +@msg heightWidth must be from 1 to 1000 % heightWidth трябва да е от 1 до 1000 % +@msg tintReduction must be from 0 to 100 % tintReduction трябва да е от 0 до 100 % Rectangular Grid Tool Options Опции на инструмента „Правоъгълна мрежа“ Polar Grid Tool Options Опции на инструмента „Полярна мрежа“ Fill Line Запълване на линия diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 6391ac812..7a3668251 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1792,6 +1792,13 @@ lighting Light Světlo Repeat Pathfinder Zopakovat operaci s cestami Repeat {name} Zopakovat {name} Trap… Přesah barev… + Trap Přesah barev + Height/Width Výška/šířka + Tint Reduction Snížení tónu + Traps with Process Color Přesahy s procesními barvami + Reverse Traps Obrácené přesahy + Precision Přesnost + Remove Redundant Points Odstranit nadbytečné body Bottom in Front Spodní vpředu Brick by Column Cihly po sloupcích Brick by Row Cihly po řádcích @@ -5467,6 +5474,11 @@ axis Y Axis osa Y @msg a live effect runs while the document is drawn, so it can't be `heavy` živý efekt běží během vykreslování dokumentu, takže nemůže být `heavy` @msg an exporter always gets an export's budget: leave `budget` out exportní zásuvný modul dostává vždy rozpočet exportu: klíč `budget` vynechte @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees „{_1}“ žádá o velký výpočetní rozpočet (každé spuštění až {minutes} minut a {times}krát víc instrukcí než obvykle): nainstalujte ho s `allowHeavy: true`, jakmile uživatel souhlasí +@msg Trap: select two or more objects filled with solid colours Přesah barev: vyberte dva nebo více objektů vyplněných plnými barvami +@msg Trap: no edges of different colours meet among the selected objects Přesah barev: mezi vybranými objekty se nestýkají žádné okraje různých barev +@msg thickness must be from 0.01 to 5000 pt thickness musí být od 0,01 do 5000 pt +@msg heightWidth must be from 1 to 1000 % heightWidth musí být od 1 do 1000 % +@msg tintReduction must be from 0 to 100 % tintReduction musí být od 0 do 100 % Rectangular Grid Tool Options Možnosti nástroje Obdélníková mřížka Polar Grid Tool Options Možnosti nástroje Polární mřížka Fill Line Výplň úsečky diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 37d4aadc2..55382c56e 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1609,6 +1609,13 @@ lighting Light Lys Repeat Pathfinder Gentag Pathfinder Repeat {name} Gentag {name} Trap… Trap… + Trap Trap + Height/Width Højde/bredde + Tint Reduction Tonereduktion + Traps with Process Color Traps med procesfarver + Reverse Traps Omvendte traps + Precision Præcision + Remove Redundant Points Fjern overflødige punkter Bottom in Front Bund forrest Brick by Column Mursten efter kolonne Brick by Row Mursten efter række @@ -5432,6 +5439,11 @@ axis Y Axis Y-akse The plug-in is computing… Plug-in'en regner… Inspect Plug-in Undersøg plug-in Cancel Plug-in Run Annullér plug-in-kørsel +@msg Trap: select two or more objects filled with solid colours Trap: Vælg to eller flere objekter udfyldt med ensfarvede farver +@msg Trap: no edges of different colours meet among the selected objects Trap: Ingen kanter i forskellige farver mødes blandt de valgte objekter +@msg thickness must be from 0.01 to 5000 pt thickness skal være fra 0,01 til 5000 pt +@msg heightWidth must be from 1 to 1000 % heightWidth skal være fra 1 til 1000 % +@msg tintReduction must be from 0 to 100 % tintReduction skal være fra 0 til 100 % Rectangular Grid Tool Options Indstillinger for rektangulært gitterværktøj Polar Grid Tool Options Indstillinger for polært gitterværktøj Fill Line Udfyld linje diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 309b3d91d..143205d2d 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -3315,6 +3315,13 @@ effect Points Punkte Transparent Background Transparenter Hintergrund Transverse (the page a quarter turn on the paper) Quer (die Seite um eine Vierteldrehung auf dem Papier) Trap… Überfüllen… + Trap Überfüllen + Height/Width Höhe/Breite + Tint Reduction Farbtonreduzierung + Traps with Process Color Überfüllungen mit Prozessfarben + Reverse Traps Umgekehrte Überfüllungen + Precision Genauigkeit + Remove Redundant Points Überflüssige Punkte entfernen Trim Überlappungsbereich entfernen Trim mark weight: Stärke der Schnittmarken: Trim marks Schnittmarken @@ -5050,6 +5057,11 @@ axis Y Axis Y-Achse @msg a live effect runs while the document is drawn, so it can't be `heavy` ein Live-Effekt läuft, während das Dokument gezeichnet wird, daher kann er nicht `heavy` sein @msg an exporter always gets an export's budget: leave `budget` out ein Export-Plug-in erhält immer das Budget eines Exports: Lassen Sie `budget` weg @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees „{_1}“ verlangt ein großes Rechenbudget (jede Ausführung bis zu {minutes} Minuten und {times}-mal so viele Anweisungen wie üblich): Installieren Sie es mit `allowHeavy: true`, sobald der Benutzer zustimmt +@msg Trap: select two or more objects filled with solid colours Überfüllen: Wählen Sie zwei oder mehr Objekte mit Volltonflächen aus +@msg Trap: no edges of different colours meet among the selected objects Überfüllen: Unter den ausgewählten Objekten treffen keine Kanten verschiedener Farben aufeinander +@msg thickness must be from 0.01 to 5000 pt thickness muss zwischen 0,01 und 5000 pt liegen +@msg heightWidth must be from 1 to 1000 % heightWidth muss zwischen 1 und 1000 % liegen +@msg tintReduction must be from 0 to 100 % tintReduction muss zwischen 0 und 100 % liegen Rectangular Grid Tool Options Optionen für das Rechteckiges-Raster-Werkzeug Polar Grid Tool Options Optionen für das Radiales-Raster-Werkzeug Fill Line Linie füllen diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 00919639f..9145ebd3a 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1935,6 +1935,13 @@ font-technology Variable Μεταβλητές Transparency Διαφάνεια Transparent Διαφανές Trap… Παγίδευση χρωμάτων… + Trap Παγίδευση χρωμάτων + Height/Width Ύψος/Πλάτος + Tint Reduction Μείωση απόχρωσης + Traps with Process Color Παγιδεύσεις με χρώματα τετραχρωμίας + Reverse Traps Αντίστροφες παγιδεύσεις + Precision Ακρίβεια + Remove Redundant Points Αφαίρεση περιττών σημείων Trim Περικοπή περιθωρίων Twist… Συστροφή… Type Κείμενο @@ -5419,6 +5426,11 @@ axis Y Axis Άξονας Y @msg a live effect runs while the document is drawn, so it can't be `heavy` ένα δυναμικό εφέ εκτελείται κατά τη σχεδίαση του εγγράφου, οπότε δεν μπορεί να είναι `heavy` @msg an exporter always gets an export's budget: leave `budget` out ένα πρόσθετο εξαγωγής παίρνει πάντα τον προϋπολογισμό μιας εξαγωγής: παραλείψτε το `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees το «{_1}» ζητά μεγάλο υπολογιστικό προϋπολογισμό (κάθε εκτέλεση έως {minutes} λεπτά και {times} φορές τις συνήθεις εντολές): εγκαταστήστε το με `allowHeavy: true` μόλις συμφωνήσει ο χρήστης +@msg Trap: select two or more objects filled with solid colours Παγίδευση χρωμάτων: επιλέξτε δύο ή περισσότερα αντικείμενα με συμπαγές χρώμα γεμίσματος +@msg Trap: no edges of different colours meet among the selected objects Παγίδευση χρωμάτων: δεν συναντώνται άκρες διαφορετικών χρωμάτων στα επιλεγμένα αντικείμενα +@msg thickness must be from 0.01 to 5000 pt το thickness πρέπει να είναι από 0,01 έως 5000 pt +@msg heightWidth must be from 1 to 1000 % το heightWidth πρέπει να είναι από 1 έως 1000 % +@msg tintReduction must be from 0 to 100 % το tintReduction πρέπει να είναι από 0 έως 100 % Rectangular Grid Tool Options Επιλογές εργαλείου ορθογώνιου πλέγματος Polar Grid Tool Options Επιλογές εργαλείου πολικού πλέγματος Fill Line Γέμισμα γραμμής diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 9daf3c836..b812a44b6 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -3361,6 +3361,13 @@ font-technology Variable Variables Transparent Background Fondo transparente Transverse (the page a quarter turn on the paper) Transversal (la página gira un cuarto de vuelta sobre el papel) Trap… Reventar… + Trap Reventar + Height/Width Altura/anchura + Tint Reduction Reducción de tinta + Traps with Process Color Reventados con colores de cuatricromía + Reverse Traps Invertir reventados + Precision Precisión + Remove Redundant Points Quitar puntos redundantes Trim Cortar Trim mark weight: Grosor de marcas de recorte: Trim marks Marcas de recorte @@ -5409,6 +5416,11 @@ axis Y Axis Eje Y @msg a live effect runs while the document is drawn, so it can't be `heavy` un efecto interactivo se ejecuta mientras se dibuja el documento, así que no puede ser `heavy` @msg an exporter always gets an export's budget: leave `budget` out un plugin de exportación siempre recibe el presupuesto de una exportación: omite `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” pide un presupuesto de cálculo grande (cada ejecución hasta {minutes} minutos y {times} veces las instrucciones habituales): instálalo con `allowHeavy: true` cuando el usuario lo acepte +@msg Trap: select two or more objects filled with solid colours Reventar: seleccione dos o más objetos rellenos con colores sólidos +@msg Trap: no edges of different colours meet among the selected objects Reventar: entre los objetos seleccionados no se tocan bordes de colores distintos +@msg thickness must be from 0.01 to 5000 pt thickness debe estar entre 0,01 y 5000 pt +@msg heightWidth must be from 1 to 1000 % heightWidth debe estar entre 1 y 1000 % +@msg tintReduction must be from 0 to 100 % tintReduction debe estar entre 0 y 100 % Rectangular Grid Tool Options Opciones de la herramienta Cuadrícula rectangular Polar Grid Tool Options Opciones de la herramienta Cuadrícula polar Fill Line Rellenar línea diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 0810d2b3e..86675ad1b 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -1755,6 +1755,13 @@ lighting Light Valo Repeat Pathfinder Toista polunetsin Repeat {name} Toista {name} Trap… Ylitäyttö… + Trap Ylitäyttö + Height/Width Korkeus/leveys + Tint Reduction Sävyn vähennys + Traps with Process Color Ylitäytöt prosessiväreillä + Reverse Traps Käänteiset ylitäytöt + Precision Tarkkuus + Remove Redundant Points Poista tarpeettomat pisteet Bottom in Front Alin edessä Brick by Column Tiili sarakkeittain Brick by Row Tiili riveittäin @@ -5498,6 +5505,11 @@ axis Y Axis Y-akseli @msg a live effect runs while the document is drawn, so it can't be `heavy` live-tehoste suoritetaan asiakirjaa piirrettäessä, joten se ei voi olla `heavy` @msg an exporter always gets an export's budget: leave `budget` out vientilaajennus saa aina viennin budjetin: jätä `budget` pois @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees ”{_1}” pyytää suurta laskentabudjettia (jokainen ajo enintään {minutes} minuuttia ja {times} kertaa tavallinen määrä käskyjä): asenna se asetuksella `allowHeavy: true`, kun käyttäjä hyväksyy +@msg Trap: select two or more objects filled with solid colours Ylitäyttö: valitse vähintään kaksi tasaisella värillä täytettyä objektia +@msg Trap: no edges of different colours meet among the selected objects Ylitäyttö: valituissa objekteissa eriväriset reunat eivät kohtaa +@msg thickness must be from 0.01 to 5000 pt thickness-arvon on oltava 0,01–5000 pt +@msg heightWidth must be from 1 to 1000 % heightWidth-arvon on oltava 1–1000 % +@msg tintReduction must be from 0 to 100 % tintReduction-arvon on oltava 0–100 % Rectangular Grid Tool Options Suorakulmaisen ruudukon työkalun asetukset Polar Grid Tool Options Napakoordinaattiruudukon työkalun asetukset Fill Line Täytä viiva diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index e20adfce0..dfc080388 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -3358,6 +3358,13 @@ font-technology Variable Variables Transparent Background Arrière-plan transparent Transverse (the page a quarter turn on the paper) Transversal (la page tournée d’un quart de tour sur le papier) Trap… Recouvrement… + Trap Recouvrement + Height/Width Hauteur/largeur + Tint Reduction Réduction de la teinte + Traps with Process Color Recouvrements en couleurs quadri + Reverse Traps Inverser les recouvrements + Precision Précision + Remove Redundant Points Supprimer les points superflus Trim Détourage Trim mark weight: Épaisseur des traits de coupe : Trim marks Traits de coupe @@ -5466,6 +5473,11 @@ axis Y Axis Axe Y @msg a live effect runs while the document is drawn, so it can't be `heavy` un effet dynamique s’exécute pendant le tracé du document, il ne peut donc pas être `heavy` @msg an exporter always gets an export's budget: leave `budget` out un module externe d’exportation reçoit toujours le budget d’une exportation : omettez `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees « {_1} » demande un budget de calcul important (chaque exécution jusqu’à {minutes} minutes et {times} fois les instructions habituelles) : installez-le avec `allowHeavy: true` une fois que l’utilisateur l’accepte +@msg Trap: select two or more objects filled with solid colours Recouvrement : sélectionnez au moins deux objets remplis de couleurs unies +@msg Trap: no edges of different colours meet among the selected objects Recouvrement : aucun bord de couleurs différentes ne se touche parmi les objets sélectionnés +@msg thickness must be from 0.01 to 5000 pt thickness doit être compris entre 0,01 et 5000 pt +@msg heightWidth must be from 1 to 1000 % heightWidth doit être compris entre 1 et 1000 % +@msg tintReduction must be from 0 to 100 % tintReduction doit être compris entre 0 et 100 % Rectangular Grid Tool Options Options de l’outil Grille rectangulaire Polar Grid Tool Options Options de l’outil Grille à coordonnées polaires Fill Line Remplir le trait diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 8ae6f4954..e040bfb56 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4055,6 +4055,13 @@ mark-style Japanese Japán Transparent Objects Átlátszó objektumok Transverse (the page a quarter turn on the paper) Keresztirányú (az oldal negyed fordulattal elforgatva a papíron) Trap… Csapdázás… + Trap Csapdázás + Height/Width Magasság/szélesség + Tint Reduction Tónuscsökkentés + Traps with Process Color Csapdázás négyszínnyomó színekkel + Reverse Traps Fordított csapdázás + Precision Pontosság + Remove Redundant Points Felesleges pontok eltávolítása Triad Triád Triad 2 Triád 2 Triad 3 Triád 3 @@ -5453,6 +5460,11 @@ axis Y Axis Y tengely @msg a live effect runs while the document is drawn, so it can't be `heavy` az élő effektus a dokumentum rajzolása közben fut, ezért nem lehet `heavy` @msg an exporter always gets an export's budget: leave `budget` out az exportáló bővítmény mindig az exportálás keretét kapja: hagyd ki a `budget` kulcsot @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees a(z) „{_1}” nagy számítási keretet kér (futásonként legfeljebb {minutes} perc és a szokásos utasításszám {times}-szorosa): telepítsd az `allowHeavy: true` megadásával, miután a felhasználó beleegyezett +@msg Trap: select two or more objects filled with solid colours Csapdázás: jelöljön ki legalább két, egyszínű kitöltésű objektumot +@msg Trap: no edges of different colours meet among the selected objects Csapdázás: a kijelölt objektumok között nem érintkeznek különböző színű élek +@msg thickness must be from 0.01 to 5000 pt a thickness értéke 0,01 és 5000 pt között lehet +@msg heightWidth must be from 1 to 1000 % a heightWidth értéke 1 és 1000 % között lehet +@msg tintReduction must be from 0 to 100 % a tintReduction értéke 0 és 100 % között lehet Rectangular Grid Tool Options Téglalaprács beállításai Polar Grid Tool Options Polárrács beállításai Fill Line Szakasz kitöltése diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index c62dba55e..b7c2726fd 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1609,6 +1609,13 @@ lighting Light Cahaya Repeat Pathfinder Ulangi Pathfinder Repeat {name} Ulangi {name} Trap… Trap… + Trap Trap + Height/Width Tinggi/Lebar + Tint Reduction Pengurangan Tint + Traps with Process Color Trap dengan Warna Proses + Reverse Traps Balikkan Trap + Precision Presisi + Remove Redundant Points Hapus Titik Berlebih Bottom in Front Bawah di depan Brick by Column Bata per kolom Brick by Row Bata per baris @@ -5432,6 +5439,11 @@ axis Y Axis Sumbu Y The plug-in is computing… Plug-in sedang menghitung… Inspect Plug-in Periksa plug-in Cancel Plug-in Run Batalkan jalannya plug-in +@msg Trap: select two or more objects filled with solid colours Trap: pilih dua objek atau lebih yang diisi warna solid +@msg Trap: no edges of different colours meet among the selected objects Trap: tidak ada tepi berwarna berbeda yang bertemu di antara objek yang dipilih +@msg thickness must be from 0.01 to 5000 pt thickness harus dari 0,01 sampai 5000 pt +@msg heightWidth must be from 1 to 1000 % heightWidth harus dari 1 sampai 1000 % +@msg tintReduction must be from 0 to 100 % tintReduction harus dari 0 sampai 100 % Rectangular Grid Tool Options Opsi alat kisi persegi panjang Polar Grid Tool Options Opsi alat kisi polar Fill Line Isi garis diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 48d89d6b3..31027ac52 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -3396,6 +3396,13 @@ mark-style Japanese Giapponese Transparent Background Sfondo trasparente Transverse (the page a quarter turn on the paper) Trasversale (la pagina ruotata di un quarto di giro sul foglio) Trap… Trapping… + Trap Trapping + Height/Width Altezza/larghezza + Tint Reduction Riduzione tinta + Traps with Process Color Trapping con colori di quadricromia + Reverse Traps Trapping inverso + Precision Precisione + Remove Redundant Points Rimuovi punti ridondanti Trim Rifila Trim mark weight: Spessore segni di taglio: Trim marks Segni di taglio @@ -5084,6 +5091,11 @@ axis Y Axis Asse Y @msg a live effect runs while the document is drawn, so it can't be `heavy` un effetto dinamico viene eseguito mentre il documento viene disegnato, quindi non può essere `heavy` @msg an exporter always gets an export's budget: leave `budget` out un plug-in di esportazione riceve sempre il budget di un’esportazione: ometti `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” chiede un budget di calcolo elevato (ogni esecuzione fino a {minutes} minuti e {times} volte le istruzioni consuete): installalo con `allowHeavy: true` quando l’utente acconsente +@msg Trap: select two or more objects filled with solid colours Trapping: selezionate due o più oggetti con riempimento in tinta unita +@msg Trap: no edges of different colours meet among the selected objects Trapping: tra gli oggetti selezionati non si incontrano bordi di colori diversi +@msg thickness must be from 0.01 to 5000 pt thickness deve essere compreso tra 0,01 e 5000 pt +@msg heightWidth must be from 1 to 1000 % heightWidth deve essere compreso tra 1 e 1000 % +@msg tintReduction must be from 0 to 100 % tintReduction deve essere compreso tra 0 e 100 % Rectangular Grid Tool Options Opzioni strumento Griglia rettangolare Polar Grid Tool Options Opzioni strumento Griglia polare Fill Line Riempi linea diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 55c0d1e42..d4edb44c3 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -3074,6 +3074,13 @@ mark-style Japanese 日本式 Transparent Background 背景を透明にする Transverse (the page a quarter turn on the paper) 横向き回転 (用紙上でページを 90 度回転) Trap… トラップ… + Trap トラップ + Height/Width 高さ/幅 + Tint Reduction 色合いの軽減 + Traps with Process Color プロセスカラーでトラップ + Reverse Traps トラップを反転 + Precision 精度 + Remove Redundant Points 余分なポイントを削除 Trim mark weight: トリムマークの線幅: True colour needs version 2004 or later: the nearest of 256 colours is written. True Color にはバージョン 2004 以降が必要です。256 色のうち最も近い色で書き出します。 Turn off Data Recovery for complex documents 複雑なドキュメントではデータ復帰をオフにする @@ -5146,6 +5153,11 @@ axis Y Axis Y 軸 @msg a live effect runs while the document is drawn, so it can't be `heavy` ライブ効果はドキュメントの描画中に実行されるため、`heavy` にはできません @msg an exporter always gets an export's budget: leave `budget` out 書き出しプラグインには常に書き出しの予算が割り当てられます。`budget` は省略してください @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees 「{_1}」は大きな計算予算を求めています (1 回の実行につき最大 {minutes} 分、通常の {times} 倍の命令)。ユーザーが同意したら `allowHeavy: true` を指定してインストールしてください +@msg Trap: select two or more objects filled with solid colours トラップ: 単色で塗りつぶした 2 つ以上のオブジェクトを選択してください +@msg Trap: no edges of different colours meet among the selected objects トラップ: 選択したオブジェクトに異なる色が接する辺がありません +@msg thickness must be from 0.01 to 5000 pt thickness は 0.01~5000 pt にしてください +@msg heightWidth must be from 1 to 1000 % heightWidth は 1~1000 % にしてください +@msg tintReduction must be from 0 to 100 % tintReduction は 0~100 % にしてください Rectangular Grid Tool Options 長方形グリッドツールオプション Polar Grid Tool Options 同心円グリッドツールオプション Fill Line 線を塗りつぶす diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 2e6c235bb..5ecb66e40 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -1510,6 +1510,13 @@ lighting Light სინათლე Repeat Pathfinder ტრაექტორიების ოპერაციის გამეორება Repeat {name} გამეორება: {name} Trap… ტრეპინგი… + Trap ტრეპინგი + Height/Width სიმაღლე/სიგანე + Tint Reduction ტონის შემცირება + Traps with Process Color ტრეპინგი პროცესული ფერებით + Reverse Traps შებრუნებული ტრეპინგი + Precision სიზუსტე + Remove Redundant Points ზედმეტი წერტილების წაშლა Bottom in Front ქვედა წინ Brick by Column აგური სვეტებით Brick by Row აგური სტრიქონებით @@ -5383,6 +5390,11 @@ axis Y Axis Y ღერძი @msg a live effect runs while the document is drawn, so it can't be `heavy` ცოცხალი ეფექტი დოკუმენტის დახატვისას ეშვება, ამიტომ ის ვერ იქნება `heavy` @msg an exporter always gets an export's budget: leave `budget` out ექსპორტის პლაგინი ყოველთვის ექსპორტის ბიუჯეტს იღებს: გამოტოვეთ `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees „{_1}“ დიდ გამოთვლით ბიუჯეტს ითხოვს (თითო გაშვება {minutes} წუთამდე და ჩვეულებრივზე {times}-ჯერ მეტი ინსტრუქცია): მომხმარებლის თანხმობის შემდეგ დააყენეთ ის `allowHeavy: true` პარამეტრით +@msg Trap: select two or more objects filled with solid colours ტრეპინგი: აირჩიეთ ორი ან მეტი ობიექტი, შევსებული ერთგვაროვანი ფერით +@msg Trap: no edges of different colours meet among the selected objects ტრეპინგი: არჩეულ ობიექტებს შორის განსხვავებული ფერის კიდეები არ ეხება ერთმანეთს +@msg thickness must be from 0.01 to 5000 pt thickness უნდა იყოს 0,01-დან 5000 pt-მდე +@msg heightWidth must be from 1 to 1000 % heightWidth უნდა იყოს 1-დან 1000 %-მდე +@msg tintReduction must be from 0 to 100 % tintReduction უნდა იყოს 0-დან 100 %-მდე Rectangular Grid Tool Options მართკუთხა ბადის ხელსაწყოს პარამეტრები Polar Grid Tool Options პოლარული ბადის ხელსაწყოს პარამეტრები Fill Line ხაზის შევსება diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 1afc46aa1..7b8c10640 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -3068,6 +3068,13 @@ mark-style Japanese 일본식 Transparent Background 투명 배경 Transverse (the page a quarter turn on the paper) 가로 방향(용지에서 페이지를 90도 돌림) Trap… 트랩… + Trap 트랩 + Height/Width 높이/폭 + Tint Reduction 색조 감소 + Traps with Process Color 프로세스 색상으로 트랩 + Reverse Traps 트랩 반전 + Precision 정밀도 + Remove Redundant Points 불필요한 점 제거 Trim mark weight: 재단선 두께: True colour needs version 2004 or later: the nearest of 256 colours is written. 트루 컬러는 2004 이상 버전이 필요합니다. 256색 중 가장 가까운 색으로 기록됩니다. Turn off Data Recovery for complex documents 복잡한 문서는 데이터 복구 끄기 @@ -5047,6 +5054,11 @@ axis Y Axis Y축 @msg a live effect runs while the document is drawn, so it can't be `heavy` 라이브 효과는 문서를 그리는 동안 실행되므로 `heavy`일 수 없습니다 @msg an exporter always gets an export's budget: leave `budget` out 내보내기 플러그인은 항상 내보내기 예산을 받습니다. `budget`은 생략하세요 @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}”은(는) 큰 계산 예산을 요청합니다(실행당 최대 {minutes}분, 평소의 {times}배 명령어). 사용자가 동의하면 `allowHeavy: true`로 설치하세요 +@msg Trap: select two or more objects filled with solid colours 트랩: 단색으로 칠한 개체를 두 개 이상 선택하십시오 +@msg Trap: no edges of different colours meet among the selected objects 트랩: 선택한 개체 사이에 서로 다른 색상이 맞닿는 가장자리가 없습니다 +@msg thickness must be from 0.01 to 5000 pt thickness는 0.01~5000pt여야 합니다 +@msg heightWidth must be from 1 to 1000 % heightWidth는 1~1000%여야 합니다 +@msg tintReduction must be from 0 to 100 % tintReduction은 0~100%여야 합니다 Rectangular Grid Tool Options 사각형 격자 도구 옵션 Polar Grid Tool Options 극좌표 격자 도구 옵션 Fill Line 선 채우기 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 3675b4989..e6aaf3366 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1623,6 +1623,13 @@ lighting Light Licht Repeat Pathfinder Pathfinder herhalen Repeat {name} {name} herhalen Trap… Overbrugging… + Trap Overbrugging + Height/Width Hoogte/breedte + Tint Reduction Tintvermindering + Traps with Process Color Overbruggingen met proceskleuren + Reverse Traps Overbruggingen omkeren + Precision Precisie + Remove Redundant Points Overbodige punten verwijderen Bottom in Front Onderkant vooraan Brick by Column Baksteen per kolom Brick by Row Baksteen per rij @@ -5475,6 +5482,11 @@ axis Y Axis Y-as The plug-in is computing… De plug-in is aan het rekenen… Inspect Plug-in Plug-in inspecteren Cancel Plug-in Run Plug-in-run annuleren +@msg Trap: select two or more objects filled with solid colours Overbrugging: selecteer twee of meer objecten met een egale vulkleur +@msg Trap: no edges of different colours meet among the selected objects Overbrugging: tussen de geselecteerde objecten raken geen randen van verschillende kleuren elkaar +@msg thickness must be from 0.01 to 5000 pt thickness moet tussen 0,01 en 5000 pt liggen +@msg heightWidth must be from 1 to 1000 % heightWidth moet tussen 1 en 1000 % liggen +@msg tintReduction must be from 0 to 100 % tintReduction moet tussen 0 en 100 % liggen Rectangular Grid Tool Options Opties rechthoekig-rastergereedschap Polar Grid Tool Options Opties polair-rastergereedschap Fill Line Lijn vullen diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index ac13934d6..85832ce51 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1712,6 +1712,13 @@ lighting Light Światło Repeat Pathfinder Powtórz odnajdywanie ścieżek Repeat {name} Powtórz {name} Trap… Zalewka… + Trap Zalewka + Height/Width Wysokość/szerokość + Tint Reduction Redukcja tinty + Traps with Process Color Zalewki kolorami procesowymi + Reverse Traps Odwrócone zalewki + Precision Dokładność + Remove Redundant Points Usuń zbędne punkty Bottom in Front Dolny na wierzchu Brick by Column Cegły według kolumn Brick by Row Cegły według wierszy @@ -5319,6 +5326,11 @@ axis Y Axis Oś Y @msg a live effect runs while the document is drawn, so it can't be `heavy` efekt na żywo działa podczas rysowania dokumentu, więc nie może być `heavy` @msg an exporter always gets an export's budget: leave `budget` out wtyczka eksportu zawsze dostaje budżet eksportu: pomiń `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees „{_1}” prosi o duży budżet obliczeń (każde uruchomienie do {minutes} minut i {times} razy więcej instrukcji niż zwykle): zainstaluj wtyczkę z `allowHeavy: true`, gdy użytkownik się zgodzi +@msg Trap: select two or more objects filled with solid colours Zalewka: zaznacz co najmniej dwa obiekty wypełnione kolorem jednolitym +@msg Trap: no edges of different colours meet among the selected objects Zalewka: wśród zaznaczonych obiektów nie stykają się krawędzie różnych kolorów +@msg thickness must be from 0.01 to 5000 pt thickness musi wynosić od 0,01 do 5000 pt +@msg heightWidth must be from 1 to 1000 % heightWidth musi wynosić od 1 do 1000 % +@msg tintReduction must be from 0 to 100 % tintReduction musi wynosić od 0 do 100 % Rectangular Grid Tool Options Opcje narzędzia Siatka prostokątna Polar Grid Tool Options Opcje narzędzia Siatka biegunowa Fill Line Wypełnij odcinek diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 5ec517162..dbe925f6c 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -2738,6 +2738,13 @@ Transparent Background Fundo Transparente Transverse (the page a quarter turn on the paper) Transversal (a página um quarto de volta no papel) Trap… Armadilha… + Trap Armadilha + Height/Width Altura/largura + Tint Reduction Redução de matiz + Traps with Process Color Armadilhas com cores de escala + Reverse Traps Inverter armadilhas + Precision Precisão + Remove Redundant Points Remover pontos redundantes Trim Aparar Trim View Modo de Aparar Trim mark weight: Peso da marca de corte: @@ -5384,6 +5391,11 @@ axis Y Axis Eixo Y @msg a live effect runs while the document is drawn, so it can't be `heavy` um efeito dinâmico é executado enquanto o documento é desenhado, então não pode ser `heavy` @msg an exporter always gets an export's budget: leave `budget` out um plug-in de exportação sempre recebe o orçamento de uma exportação: omita `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” pede um orçamento de processamento grande (cada execução até {minutes} minutos e {times} vezes as instruções habituais): instale-o com `allowHeavy: true` depois que o usuário concordar +@msg Trap: select two or more objects filled with solid colours Armadilha: selecione dois ou mais objetos preenchidos com cores sólidas +@msg Trap: no edges of different colours meet among the selected objects Armadilha: nenhuma borda de cores diferentes se encontra entre os objetos selecionados +@msg thickness must be from 0.01 to 5000 pt thickness deve estar entre 0,01 e 5000 pt +@msg heightWidth must be from 1 to 1000 % heightWidth deve estar entre 1 e 1000 % +@msg tintReduction must be from 0 to 100 % tintReduction deve estar entre 0 e 100 % Rectangular Grid Tool Options Opções da Ferramenta Grade Retangular Polar Grid Tool Options Opções da Ferramenta Grade Polar Fill Line Preencher linha diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 337a5a073..909f1e6fc 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -3392,6 +3392,13 @@ mark-style Japanese Японский Transparent Background Прозрачный фон Transverse (the page a quarter turn on the paper) Поперечная (страница повёрнута на четверть оборота на листе) Trap… Треппинг… + Trap Треппинг + Height/Width Высота/ширина + Tint Reduction Снижение оттенка + Traps with Process Color Треппинг триадными цветами + Reverse Traps Обратный треппинг + Precision Точность + Remove Redundant Points Удалить лишние точки Trim Подрезать Trim mark weight: Толщина меток подрезки: Trim marks Метки подрезки @@ -5080,6 +5087,11 @@ axis Y Axis Ось Y @msg a live effect runs while the document is drawn, so it can't be `heavy` динамический эффект выполняется во время отрисовки документа, поэтому он не может быть `heavy` @msg an exporter always gets an export's budget: leave `budget` out модуль экспорта всегда получает бюджет экспорта: не указывайте `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees «{_1}» запрашивает большой вычислительный бюджет (каждый запуск до {minutes} минут и в {times} раз больше инструкций, чем обычно): установите его с `allowHeavy: true`, когда пользователь согласится +@msg Trap: select two or more objects filled with solid colours Треппинг: выберите два или более объекта со сплошной заливкой +@msg Trap: no edges of different colours meet among the selected objects Треппинг: среди выбранных объектов нет соприкасающихся краёв разных цветов +@msg thickness must be from 0.01 to 5000 pt thickness должно быть от 0,01 до 5000 pt +@msg heightWidth must be from 1 to 1000 % heightWidth должно быть от 1 до 1000 % +@msg tintReduction must be from 0 to 100 % tintReduction должно быть от 0 до 100 % Rectangular Grid Tool Options Параметры инструмента «Прямоугольная сетка» Polar Grid Tool Options Параметры инструмента «Полярная сетка» Fill Line Заливка отрезка diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 5b0d5fe53..4850f0972 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -3293,6 +3293,13 @@ font-technology Variable Variabla Transparent Background Transparent bakgrund Transverse (the page a quarter turn on the paper) Tvärställd (sidan vriden ett kvarts varv på pappret) Trap… Överfyllning… + Trap Överfyllning + Height/Width Höjd/bredd + Tint Reduction Tonreducering + Traps with Process Color Överfyllningar med processfärger + Reverse Traps Omvända överfyllningar + Precision Precision + Remove Redundant Points Ta bort överflödiga punkter Trim Trimma Trim mark weight: Tjocklek på skärmärken: Trim marks Skärmärken @@ -5423,6 +5430,11 @@ axis Y Axis Y-axel @msg a live effect runs while the document is drawn, so it can't be `heavy` en live-effekt körs medan dokumentet ritas, så den kan inte vara `heavy` @msg an exporter always gets an export's budget: leave `budget` out ett exportinsticksprogram får alltid en exports budget: utelämna `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees ”{_1}” begär en stor beräkningsbudget (varje körning upp till {minutes} minuter och {times} gånger de vanliga instruktionerna): installera det med `allowHeavy: true` när användaren har godkänt +@msg Trap: select two or more objects filled with solid colours Överfyllning: markera två eller fler objekt fyllda med enfärgade färger +@msg Trap: no edges of different colours meet among the selected objects Överfyllning: inga kanter i olika färger möts bland de markerade objekten +@msg thickness must be from 0.01 to 5000 pt thickness måste vara från 0,01 till 5000 pt +@msg heightWidth must be from 1 to 1000 % heightWidth måste vara från 1 till 1000 % +@msg tintReduction must be from 0 to 100 % tintReduction måste vara från 0 till 100 % Rectangular Grid Tool Options Alternativ för verktyget för rektangulärt rutnät Polar Grid Tool Options Alternativ för verktyget för polärt rutnät Fill Line Fyll linje diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 143cdc486..76b8c12ea 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -3187,6 +3187,13 @@ font-technology Variable Değişken Transparent Background Şeffaf Arka Plan Transverse (the page a quarter turn on the paper) Enine (kağıt üzerinde çeyrek tur dönüş yapan sayfa) Trap… Tuzak… + Trap Tuzak + Height/Width Yükseklik/Genişlik + Tint Reduction Ton Azaltma + Traps with Process Color Proses Renkli Tuzaklar + Reverse Traps Tuzakları Tersine Çevir + Precision Hassasiyet + Remove Redundant Points Gereksiz Noktaları Kaldır Trim Kırpma Trim mark weight: Trim işareti ağırlığı: Trim marks Kırpma işaretleri @@ -5430,6 +5437,11 @@ axis Y Axis Y ekseni @msg a live effect runs while the document is drawn, so it can't be `heavy` canlı efekt belge çizilirken çalışır, bu yüzden `heavy` olamaz @msg an exporter always gets an export's budget: leave `budget` out dışa aktarma eklentisi her zaman bir dışa aktarmanın bütçesini alır: `budget` anahtarını çıkarın @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” büyük bir hesaplama bütçesi istiyor (her çalıştırma en fazla {minutes} dakika ve olağan komutların {times} katı): kullanıcı onayladıktan sonra `allowHeavy: true` ile yükleyin +@msg Trap: select two or more objects filled with solid colours Tuzak: düz renkle doldurulmuş iki veya daha fazla nesne seçin +@msg Trap: no edges of different colours meet among the selected objects Tuzak: seçili nesneler arasında farklı renkli kenarlar birleşmiyor +@msg thickness must be from 0.01 to 5000 pt thickness 0,01 ile 5000 pt arasında olmalıdır +@msg heightWidth must be from 1 to 1000 % heightWidth 1 ile 1000 % arasında olmalıdır +@msg tintReduction must be from 0 to 100 % tintReduction 0 ile 100 % arasında olmalıdır Rectangular Grid Tool Options Dikdörtgen Izgara Aracı Seçenekleri Polar Grid Tool Options Yuvarlak Izgara Aracı Seçenekleri Fill Line Çizgiyi doldur diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 0c03f32c2..851ee85e8 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -3389,6 +3389,13 @@ effect Points Точки Transparent Background Прозоре тло Transverse (the page a quarter turn on the paper) Поперечна (сторінка повернута на чверть оберту на аркуші) Trap… Трепінг… + Trap Трепінг + Height/Width Висота/ширина + Tint Reduction Зменшення відтінку + Traps with Process Color Трепінг тріадними кольорами + Reverse Traps Зворотний трепінг + Precision Точність + Remove Redundant Points Вилучити зайві точки Trim Підрізати Trim mark weight: Товщина міток підрізування: Trim marks Мітки підрізування @@ -5064,6 +5071,11 @@ axis Y Axis Вісь Y @msg a live effect runs while the document is drawn, so it can't be `heavy` динамічний ефект виконується під час відмальовування документа, тому він не може бути `heavy` @msg an exporter always gets an export's budget: leave `budget` out модуль експорту завжди отримує бюджет експорту: не вказуйте `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees «{_1}» запитує великий обчислювальний бюджет (кожен запуск до {minutes} хвилин і в {times} разів більше інструкцій, ніж зазвичай): встановіть його з `allowHeavy: true`, коли користувач погодиться +@msg Trap: select two or more objects filled with solid colours Трепінг: виберіть два або більше об’єктів із суцільною заливкою +@msg Trap: no edges of different colours meet among the selected objects Трепінг: серед вибраних об’єктів немає дотичних країв різних кольорів +@msg thickness must be from 0.01 to 5000 pt thickness має бути від 0,01 до 5000 pt +@msg heightWidth must be from 1 to 1000 % heightWidth має бути від 1 до 1000 % +@msg tintReduction must be from 0 to 100 % tintReduction має бути від 0 до 100 % Rectangular Grid Tool Options Параметри інструменту «Прямокутна сітка» Polar Grid Tool Options Параметри інструменту «Полярна сітка» Fill Line Заливка відрізка diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 7ccb9f072..fe41fa16b 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -3757,6 +3757,13 @@ mark-style Japanese Kiểu Nhật Transpose Chuyển vị Transverse (the page a quarter turn on the paper) Ngang (trang xoay 1/4 vòng trên giấy) Trap… Chồng màu… + Trap Chồng màu + Height/Width Cao/Rộng + Tint Reduction Giảm sắc độ + Traps with Process Color Chồng màu bằng màu process + Reverse Traps Đảo chiều chồng màu + Precision Độ chính xác + Remove Redundant Points Xóa điểm thừa Trim Cắt xén Trim covered geometry on Expand Appearance. Live preview keeps the full surface. Transparent paints and surfaces exceeding visibility limits retain all faces. Cắt bỏ hình học bị che khi Mở rộng diện mạo. Xem trước trực tiếp giữ toàn bộ bề mặt. Sơn trong suốt và bề mặt vượt quá giới hạn hiển thị giữ nguyên tất cả các mặt. Trim mark weight: Độ dày dấu cắt: @@ -5064,6 +5071,11 @@ axis Y Axis Trục Y @msg a live effect runs while the document is drawn, so it can't be `heavy` hiệu ứng trực tiếp chạy trong lúc tài liệu được vẽ nên không thể là `heavy` @msg an exporter always gets an export's budget: leave `budget` out plug-in xuất luôn nhận ngân sách của một lần xuất: hãy bỏ `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}” yêu cầu ngân sách tính toán lớn (mỗi lần chạy tới {minutes} phút và gấp {times} lần số lệnh thông thường): hãy cài đặt với `allowHeavy: true` khi người dùng đồng ý +@msg Trap: select two or more objects filled with solid colours Chồng màu: hãy chọn hai đối tượng trở lên được tô màu đặc +@msg Trap: no edges of different colours meet among the selected objects Chồng màu: không có cạnh khác màu nào tiếp giáp giữa các đối tượng đã chọn +@msg thickness must be from 0.01 to 5000 pt thickness phải từ 0,01 đến 5000 pt +@msg heightWidth must be from 1 to 1000 % heightWidth phải từ 1 đến 1000 % +@msg tintReduction must be from 0 to 100 % tintReduction phải từ 0 đến 100 % Rectangular Grid Tool Options Tùy chọn công cụ Lưới chữ nhật Polar Grid Tool Options Tùy chọn công cụ Lưới cực Fill Line Tô đoạn thẳng diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 54886ff48..f18bdb14e 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -3229,6 +3229,13 @@ font-script Japanese 汉字 Transparent Background 透明背景 Transverse (the page a quarter turn on the paper) 横向 (页面在纸张上转四分之一圈) Trap… 陷印… + Trap 陷印 + Height/Width 高度/宽度 + Tint Reduction 色调减淡 + Traps with Process Color 使用印刷色陷印 + Reverse Traps 反向陷印 + Precision 精度 + Remove Redundant Points 删除冗余点 Trim 修边 Trim mark weight: 剪裁标记宽度: Trim marks 剪裁标记 @@ -5307,6 +5314,11 @@ axis Y Axis Y 轴 @msg a live effect runs while the document is drawn, so it can't be `heavy` 实时效果在绘制文档时运行,因此不能为 `heavy` @msg an exporter always gets an export's budget: leave `budget` out 导出增效工具始终使用导出的预算:请省略 `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees “{_1}”需要大量计算预算(每次运行最多 {minutes} 分钟,指令数为通常的 {times} 倍):请在用户同意后使用 `allowHeavy: true` 安装 +@msg Trap: select two or more objects filled with solid colours 陷印:请选择两个或更多用实色填充的对象 +@msg Trap: no edges of different colours meet among the selected objects 陷印:所选对象中没有不同颜色相接的边缘 +@msg thickness must be from 0.01 to 5000 pt thickness 必须介于 0.01 到 5000 pt 之间 +@msg heightWidth must be from 1 to 1000 % heightWidth 必须介于 1 到 1000 % 之间 +@msg tintReduction must be from 0 to 100 % tintReduction 必须介于 0 到 100 % 之间 Rectangular Grid Tool Options 矩形网格工具选项 Polar Grid Tool Options 极坐标网格工具选项 Fill Line 填充线段 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 0831c9ab6..404752172 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -4156,6 +4156,13 @@ font-script Japanese 漢字 Transparent Background 透明背景 Transverse (the page a quarter turn on the paper) 橫向轉置 (頁面在紙張上轉四分之一圈) Trap… 補漏白… + Trap 補漏白 + Height/Width 高度/寬度 + Tint Reduction 色調減淡 + Traps with Process Color 使用印刷色補漏白 + Reverse Traps 反向補漏白 + Precision 精確度 + Remove Redundant Points 移除多餘的點 Trim 剪裁 Trim mark weight: 剪裁標記寬度: Trim marks 剪裁標記 @@ -5103,6 +5110,11 @@ axis Y Axis Y 軸 @msg a live effect runs while the document is drawn, so it can't be `heavy` 即時效果會在繪製文件時執行,因此不能是 `heavy` @msg an exporter always gets an export's budget: leave `budget` out 匯出增效模組一律使用匯出的預算:請移除 `budget` @msg “{_1}” asks for a heavy budget (each run up to {minutes} minutes and {times} times the usual instructions): install it with `allowHeavy: true` once the user agrees 「{_1}」需要大量運算預算(每次執行最多 {minutes} 分鐘,指令數為一般的 {times} 倍):請在使用者同意後以 `allowHeavy: true` 安裝 +@msg Trap: select two or more objects filled with solid colours 補漏白:請選取兩個或更多以實色填色的物件 +@msg Trap: no edges of different colours meet among the selected objects 補漏白:所選物件中沒有不同顏色相接的邊緣 +@msg thickness must be from 0.01 to 5000 pt thickness 必須介於 0.01 到 5000 pt 之間 +@msg heightWidth must be from 1 to 1000 % heightWidth 必須介於 1 到 1000 % 之間 +@msg tintReduction must be from 0 to 100 % tintReduction 必須介於 0 到 100 % 之間 Rectangular Grid Tool Options 矩形格線工具選項 Polar Grid Tool Options 極座標格點工具選項 Fill Line 填色線段 diff --git a/crates/ui-egui/src/panels/pathfinder.rs b/crates/ui-egui/src/panels/pathfinder.rs index 440b4053b..af7231f4c 100644 --- a/crates/ui-egui/src/panels/pathfinder.rs +++ b/crates/ui-egui/src/panels/pathfinder.rs @@ -80,8 +80,17 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { }); } +/// Pathfinder panel › Trap…: the Trap dialog (`object.pathfinder.trap`'s options) for two or more +/// objects. +pub fn open_trap(app: &mut VectorcraftApp) { + let params = vectorcraft_effects::trap::command_defaults(); + let _ = app.run("ui.paramDialog", json!({"command": "object.pathfinder.trap", "label": "Trap", "params": params})); +} + pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) { - menu_item(ui, tl!("Trap…"), false, false); + if menu_item(ui, tl!("Trap…"), operands(app) >= 2, false) { + open_trap(app); + } let last: Option<(String, String)> = pstate(ui.ctx(), "pf-last"); let label = last.as_ref().map(|(_, l)| crate::i18n::fmt(tl!("Repeat {name}"), &[("name", tl!(l))])).unwrap_or_else(|| tl!("Repeat Pathfinder").into()); @@ -142,4 +151,41 @@ mod tests { assert!(!node.is_disabled(), "{label}"); } } + + /// The panel menu's Trap… is enabled for two objects and opens the Trap options; OK adds the + /// trap above them as one selected group in one undo step. + #[test] + fn trap_opens_its_options_and_ok_traps_the_selection() { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); + app.run("file.new", json!({"width": 200, "height": 200})).unwrap(); + let mut rect = |x: f64, color: &str| { + let id = app.run("shape.rectangle", json!({"x": x, "y": 10, "width": 50, "height": 50})).unwrap()["id"].clone(); + app.run("paint.setFill", json!({"color": color})).unwrap(); + id + }; + let (a, b) = (rect(10.0, "#ffff00"), rect(60.0, "#0000ff")); + app.run("select.set", json!({"ids": [a]})).unwrap(); + // Menu rows pad their label where a check mark would go. + let one = tree(|ui| menu(&mut app, ui)); + assert!(named(&one, " Trap…").is_disabled(), "one object has nothing to trap with"); + app.run("select.set", json!({"ids": [a, b]})).unwrap(); + let two = tree(|ui| menu(&mut app, ui)); + assert!(!named(&two, " Trap…").is_disabled()); + open_trap(&mut app); + let d = app.ui.dialog.as_mut().expect("the Trap options"); + assert_eq!(d.str("__command"), "object.pathfinder.trap"); + for key in ["thickness", "heightWidth", "tintReduction", "trapsWithProcessColor", "reverseTraps"] { + assert!(d.fields.contains_key(key), "{key}"); + } + d.fields.insert("thickness".into(), json!(2)); + let undo = app.session.doc().unwrap().history.undo.len(); + crate::dialogs::confirm(&mut app).unwrap(); + let st = app.session.doc().unwrap(); + assert_eq!(st.history.undo.len(), undo + 1); + let group = st.doc.node(*st.selection.objects.first().unwrap()).unwrap(); + let trap = &group.children().unwrap()[0]; + let bb = trap.geometric_bounds().unwrap(); + assert!((bb.x0 - 60.0).abs() < 0.02 && (bb.x1 - 62.0).abs() < 0.02, "2 pt into the blue: {bb:?}"); + assert!(trap.appearance.fill().is_some_and(|f| f.overprint)); + } }