Pathfinder: Trap spreads the lighter colour into the darker as an overprinting strip where solid colours meet

The Pathfinder panel menu's Trap… (object.pathfinder.trap) and Effect › Pathfinder › Trap… (pathfinder.trap, live on groups) share one geometry (vectorcraft_pathops::trap_band, vectorcraft_effects::trap): Thickness, Height/Width, Tint Reduction, Traps with Process Color and Reverse Traps, plus the effect's Precision and Remove Redundant Points. The panel's Trap is one undo step and groups the traps above the topmost selected object. Behaviour from https://helpx.adobe.com/illustrator/using/trapping.html and https://helpx.adobe.com/illustrator/using/pathfinder-panel-overview.html.
This commit is contained in:
Taha-Mahmoodi
2026-10-24 03:09:08 +07:00
committed by bflatastic
parent 422ecc0c77
commit fd345e0c56
35 changed files with 1130 additions and 4 deletions
+40 -1
View File
@@ -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<OutlineHook>) -> Option<Ve
let source: &[Arc<Node>] = group.children()?;
let mut stack: Vec<Arc<Node>> = 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<NodeId>, Vec<crate::trap::TrapSource>) = 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();
+4
View File
@@ -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<EffectInfo> {
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",
+387
View File
@@ -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:
//! <https://helpx.adobe.com/illustrator/using/trapping.html> and
//! <https://helpx.adobe.com/illustrator/using/pathfinder-panel-overview.html> (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<Color> {
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::<f32>();
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<PathData> {
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<PathData>> = 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::<Vec<_>>()),
})
.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<TrapPiece> {
let colors: Vec<Option<Color>> = 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<TrapSource> {
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());
}
}
+71
View File
@@ -108,6 +108,15 @@ pub fn specs() -> Vec<CommandSpec> {
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<Value> {
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<Value> {
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 {
+3
View File
@@ -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",
+148
View File
@@ -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<NodeId> {
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<Node> {
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);
}
+2
View File
@@ -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)]
+124
View File
@@ -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:?}");
}
}
+14 -2
View File
@@ -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"],
_ => &[],
}
}
+2
View File
@@ -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}:"),
}
}
+12
View File
@@ -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 Запълване на линия
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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 Γέμισμα γραμμής
Can't render this file because it contains an unexpected character in line 2059 and column 19.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 3830 and column 19.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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 線を塗りつぶす
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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 ხაზის შევსება
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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 선 채우기
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+12
View File
@@ -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 Заливка отрезка
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 5 and column 25.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 5 and column 25.
+12
View File
@@ -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 Заливка відрізка
Can't render this file because it contains an unexpected character in line 740 and column 40.
+12
View File
@@ -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
Can't render this file because it contains an unexpected character in line 4044 and column 10.
+12
View File
@@ -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 填充线段
Can't render this file because it contains an unexpected character in line 2 and column 73.
+12
View File
@@ -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 填色線段
Can't render this file because it contains an unexpected character in line 6 and column 47.
+47 -1
View File
@@ -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));
}
}