mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-31 13:17:19 -06:00
Merge pull request #4771 from storytold/feat/addon-path-effects
Add-ons: Path Precision Tools — live path effects
This commit is contained in:
@@ -6,7 +6,10 @@
|
||||
//! with one to four inks.
|
||||
//! - [`halftone`]: Color Effects' live Vector Halftone, and the screens Object › Vector Halftone
|
||||
//! builds its dots with.
|
||||
//! - [`path`]: Path Precision Tools' live Path Effects, geometry effects built on the add-on's path
|
||||
//! kernels.
|
||||
|
||||
pub mod color;
|
||||
pub mod duotone;
|
||||
pub mod halftone;
|
||||
pub mod path;
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
//! Path Effects (Path Precision Tools): live geometry effects that rewrite an object's path with the
|
||||
//! add-on's path kernels (Effect › Path Effects). Each is evaluated with the other geometry effects
|
||||
//! ([`crate::apply_geometry`]), so it draws, measures, exports and expands as they do; the geometry
|
||||
//! is written only by Expand Appearance.
|
||||
//!
|
||||
//! Parameters come from documents and scripts, so each is clamped to its documented range, and a
|
||||
//! path with a coordinate past [`MAX_COORDINATE`] or not finite is left as it is.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::Dash;
|
||||
use vectorcraft_geom::{PathData, Point, SubPath};
|
||||
use vectorcraft_pathops::addons::pathtools::{self as tools, AnchorSpacing, CloseMode, ExtendMode, SMOOTH_RATIO, SegmentKind};
|
||||
use vectorcraft_pathops::addons::{PT_PER_TOLERANCE, retract_tiny_handles, simplify_keep_shape};
|
||||
|
||||
use crate::catalog::{EffectInfo, Lengths};
|
||||
use crate::stroke;
|
||||
use crate::util::{flag, num, text};
|
||||
|
||||
/// What every Path Effect's id starts with.
|
||||
pub const PREFIX: &str = "addon.path.";
|
||||
pub const DASHES: &str = "addon.path.dashes";
|
||||
pub const ADD_ANCHORS: &str = "addon.path.addAnchors";
|
||||
pub const EXTEND: &str = "addon.path.extend";
|
||||
pub const CLOSE: &str = "addon.path.close";
|
||||
pub const SMOOTH: &str = "addon.path.smooth";
|
||||
pub const RETRACT: &str = "addon.path.retract";
|
||||
pub const REVERSE: &str = "addon.path.reverse";
|
||||
pub const SIMPLIFY: &str = "addon.path.simplify";
|
||||
pub const SPLIT: &str = "addon.path.split";
|
||||
|
||||
const MENU: &[&str] = &["Effect", "Path Effects"];
|
||||
|
||||
/// The dash and gap parameters of [`DASHES`], pair by pair.
|
||||
const DASH_PAIRS: [(&str, &str); 5] = [("dash1", "gap1"), ("dash2", "gap2"), ("dash3", "gap3"), ("dash4", "gap4"), ("dash5", "gap5")];
|
||||
/// Longest dash or gap, and the farthest offset (pt).
|
||||
const MAX_DASH: f64 = 10_000.0;
|
||||
/// Longest stretch [`EXTEND`] adds or takes off an end, in points and in percent of the path.
|
||||
const MAX_EXTEND: f64 = 100_000.0;
|
||||
const MAX_EXTEND_PERCENT: f64 = 1000.0;
|
||||
/// Most anchors [`ADD_ANCHORS`] adds to one segment.
|
||||
const MAX_PER_SEGMENT: f64 = 1000.0;
|
||||
/// Closest and farthest apart [`ADD_ANCHORS`]' anchors are (pt).
|
||||
const DISTANCE_RANGE: (f64, f64) = (0.01, 100_000.0);
|
||||
/// The range of [`SMOOTH`]'s ratio.
|
||||
const RATIO_RANGE: (f64, f64) = (0.01, 0.5);
|
||||
/// Longest handle length [`RETRACT`] takes (pt).
|
||||
const MAX_HANDLE: f64 = 100_000.0;
|
||||
/// Highest tolerance of [`SIMPLIFY`].
|
||||
const MAX_TOLERANCE: f64 = 200.0;
|
||||
// shortcut: simplifying measures every anchor on every draw, so a larger path draws as it is; lift
|
||||
// this if the kernel gets an incremental pass.
|
||||
/// Most anchors [`SIMPLIFY`] takes in one path.
|
||||
const MAX_SIMPLIFY_ANCHORS: usize = 50_000;
|
||||
/// A path with a coordinate past this, or one that isn't finite, is left as it is: lengths measured
|
||||
/// on it would overflow.
|
||||
const MAX_COORDINATE: f64 = 1e12;
|
||||
|
||||
/// The effects' catalogue entries.
|
||||
pub(crate) fn catalog() -> Vec<EffectInfo> {
|
||||
let entry = |id, label, params, defaults, always| EffectInfo {
|
||||
id,
|
||||
label,
|
||||
menu: MENU,
|
||||
params,
|
||||
defaults,
|
||||
raster: false,
|
||||
lengths: Lengths { always, absolute: &[] },
|
||||
};
|
||||
vec![
|
||||
entry(
|
||||
DASHES,
|
||||
"Dashes to Geometry…",
|
||||
"{dash1: pt 0..10000 (12), gap1: pt 0..10000 (6), dash2: pt 0..10000 (0), gap2: pt 0..10000 (0), dash3: pt 0..10000 (0), gap3: pt 0..10000 (0), dash4: pt 0..10000 (0), gap4: pt 0..10000 (0), dash5: pt 0..10000 (0), gap5: pt 0..10000 (0), offset: pt -10000..10000 (0; how far into the pattern the path starts; not with alignCorners), alignCorners: bool (false; every run between corners and path ends holds whole periods, stretched or squeezed to fit, with a dash centred on each corner and end)} cuts the path into its dashes, open subpaths along it that the stroke paints as it paints any path. A dash of 0 is a point its stroke's round or projecting cap paints; a pair of two zeros is left out, and the pattern repeats. A pattern of nothing, or of more than 200000 dashes, leaves the path as it is; a dash longer than a closed subpath keeps it closed",
|
||||
json!({"dash1": 12.0, "gap1": 6.0, "dash2": 0.0, "gap2": 0.0, "dash3": 0.0, "gap3": 0.0, "dash4": 0.0, "gap4": 0.0, "dash5": 0.0, "gap5": 0.0, "offset": 0.0, "alignCorners": false}),
|
||||
&["dash1", "gap1", "dash2", "gap2", "dash3", "gap3", "dash4", "gap4", "dash5", "gap5", "offset"],
|
||||
),
|
||||
entry(
|
||||
ADD_ANCHORS,
|
||||
"Add Anchors…",
|
||||
"{target: \"all\"|\"curved\"|\"straight\" (all; the segments that get anchors), spacing: \"count\"|\"distance\" (count), count: 1..1000 (1; anchors on each segment, evenly spaced by length), distance: pt 0.01..100000 (20; an anchor every so far along each segment from its start), maxCount: 1..1000 (100; most anchors added to one segment)} adds anchors without changing the shape; past 2 million anchors in all the path stays as it is",
|
||||
json!({"target": "all", "spacing": "count", "count": 1, "distance": 20.0, "maxCount": 100}),
|
||||
&["distance"],
|
||||
),
|
||||
entry(
|
||||
EXTEND,
|
||||
"Extend Path…",
|
||||
"{startLength: pt -100000..100000 (10), startPercent: % of the path's length -1000..1000 (0), startShape: \"straight\"|\"arc\" (straight), endLength: pt -100000..100000 (10), endPercent: % of the path's length -1000..1000 (0), endShape: \"straight\"|\"arc\" (straight)} lengthens each end of an open subpath by its length and percent together, or shortens it back along itself when they come to less than 0. Straight runs on along the end's direction; arc follows the circle of the end's curvature, at most half way round. Closed subpaths stay as they are, and so does an end that would be shortened to nothing",
|
||||
json!({"startLength": 10.0, "startPercent": 0.0, "startShape": "straight", "endLength": 10.0, "endPercent": 0.0, "endShape": "straight"}),
|
||||
&["startLength", "endLength"],
|
||||
),
|
||||
entry(
|
||||
CLOSE,
|
||||
"Close Path…",
|
||||
"{mode: \"straight\"|\"smooth\" (straight; smooth: the closing segment leaves both ends smoothly)} closes every open subpath of two anchors or more",
|
||||
json!({"mode": "straight"}),
|
||||
&[],
|
||||
),
|
||||
entry(
|
||||
SMOOTH,
|
||||
"Smooth Anchors…",
|
||||
"{ratio: 0.01..0.5 (0.3905; a handle's length as a share of the distance to the nearer neighbour; 0.3905 makes a square's corners a circle)} makes every anchor smooth: a lone handle is mirrored, two are brought into line keeping their lengths, and an anchor without handles gets them at right angles to the bisector of its neighbours",
|
||||
json!({"ratio": SMOOTH_RATIO}),
|
||||
&[],
|
||||
),
|
||||
entry(
|
||||
RETRACT,
|
||||
"Retract Handles…",
|
||||
"{handles: \"all\"|\"shorter\" (all; shorter: only those shorter than length), length: pt 0..100000 (5)} takes the handles back to their anchors; an anchor that loses one becomes a corner",
|
||||
json!({"handles": "all", "length": 5.0}),
|
||||
&["length"],
|
||||
),
|
||||
entry(
|
||||
REVERSE,
|
||||
"Reverse Direction",
|
||||
"{} runs every subpath the other way: the shape stays, and arrowheads, dashes and the effects after it swap ends",
|
||||
json!({}),
|
||||
&[],
|
||||
),
|
||||
entry(
|
||||
SIMPLIFY,
|
||||
"Simplify Anchors…",
|
||||
"{tolerance: 0..200 (10; higher removes more anchors and lets the outline move further, up to tolerance × 0.02 pt), protectCorners: bool (true; anchors where the path turns by more than 30° stay)} removes the anchors a path can do without, refitting the handles beside each; open ends stay; a path of more than 50000 anchors stays as it is",
|
||||
json!({"tolerance": 10.0, "protectCorners": true}),
|
||||
&[],
|
||||
),
|
||||
entry(
|
||||
SPLIT,
|
||||
"Split at Anchors…",
|
||||
"{anchors: \"corner\"|\"smooth\"|\"both\" (both; smooth: where the handles stand in line, corner: the others)} cuts subpaths into open pieces at those anchors; the shape stays, and an open path's ends aren't cuts",
|
||||
json!({"anchors": "both"}),
|
||||
&[],
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
/// Path Effect `id` with parameters `p` on `path`, which keeps to `max` anchors where anchors are
|
||||
/// added one by one. `path` as it is when `id` is no such effect or the path can't be measured.
|
||||
pub(crate) fn apply(id: &str, p: &Value, path: &PathData, max: usize) -> PathData {
|
||||
if !measurable(path) {
|
||||
return path.clone();
|
||||
}
|
||||
let edit = |change: &dyn Fn(&mut SubPath)| {
|
||||
let mut out = path.clone();
|
||||
out.subpaths.iter_mut().for_each(change);
|
||||
out
|
||||
};
|
||||
match id {
|
||||
DASHES => dashes(path, p),
|
||||
ADD_ANCHORS => add_anchors(path, p, max).unwrap_or_else(|| path.clone()),
|
||||
EXTEND => PathData::new(path.subpaths.iter().map(|sp| extend(sp, p)).collect()),
|
||||
CLOSE => {
|
||||
let mode = if text(p, "mode", "straight") == "smooth" { CloseMode::Smooth } else { CloseMode::Straight };
|
||||
edit(&|sp| {
|
||||
tools::close(sp, mode, SMOOTH_RATIO);
|
||||
})
|
||||
}
|
||||
SMOOTH => {
|
||||
let ratio = num(p, "ratio", SMOOTH_RATIO).clamp(RATIO_RANGE.0, RATIO_RANGE.1);
|
||||
edit(&|sp| tools::smooth_anchors(sp, ratio))
|
||||
}
|
||||
RETRACT if text(p, "handles", "all") == "shorter" => {
|
||||
let length = num(p, "length", 5.0).clamp(0.0, MAX_HANDLE);
|
||||
edit(&|sp| {
|
||||
retract_tiny_handles(sp, length);
|
||||
})
|
||||
}
|
||||
RETRACT => edit(&|sp| sp.anchors.iter_mut().for_each(|a| a.retract())),
|
||||
REVERSE => edit(&SubPath::reverse),
|
||||
SIMPLIFY if path.anchor_count() <= MAX_SIMPLIFY_ANCHORS => {
|
||||
let tolerance = num(p, "tolerance", 10.0).clamp(0.0, MAX_TOLERANCE) * PT_PER_TOLERANCE;
|
||||
simplify_keep_shape(path, tolerance, flag(p, "protectCorners", true))
|
||||
}
|
||||
SPLIT => {
|
||||
let at = text(p, "anchors", "both");
|
||||
let (corners, smooth) = (at != "smooth", at != "corner");
|
||||
PathData::new(path.subpaths.iter().flat_map(|sp| tools::split_at_anchors(sp, corners, smooth)).collect())
|
||||
}
|
||||
_ => path.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Is every coordinate of `path` within [`MAX_COORDINATE`]?
|
||||
fn measurable(path: &PathData) -> bool {
|
||||
let near = |q: Point| q.x.abs() <= MAX_COORDINATE && q.y.abs() <= MAX_COORDINATE;
|
||||
path.anchors().all(|(_, _, a)| near(a.p) && near(a.h_in) && near(a.h_out))
|
||||
}
|
||||
|
||||
/// [`DASHES`]: the dashes of `path` as open subpaths, those of no length as subpaths that stay on
|
||||
/// their point; `path` as it is when the pattern draws no dashes.
|
||||
fn dashes(path: &PathData, p: &Value) -> PathData {
|
||||
let length = |key| num(p, key, 0.0).clamp(0.0, MAX_DASH);
|
||||
let pairs = DASH_PAIRS.iter().map(|(dash, gap)| (length(dash), length(gap)));
|
||||
let pattern = pairs.filter(|(d, g)| *d > 0.0 || *g > 0.0).flat_map(|(d, g)| [d, g]).collect();
|
||||
let dash = Dash { pattern, offset: num(p, "offset", 0.0).clamp(-MAX_DASH, MAX_DASH), align_corners: flag(p, "alignCorners", false) };
|
||||
let Some(dashed) = stroke::dash(&path.to_bezpath(), &dash) else { return path.clone() };
|
||||
let mut out = PathData::from_bezpath(&dashed.path);
|
||||
out.subpaths.extend(dashed.dots.iter().map(|d| SubPath::polyline(&[d.at, d.at], false)));
|
||||
out
|
||||
}
|
||||
|
||||
/// [`ADD_ANCHORS`] within `max` anchors in all; `None` past them.
|
||||
fn add_anchors(path: &PathData, p: &Value, max: usize) -> Option<PathData> {
|
||||
let which = match text(p, "target", "all") {
|
||||
"curved" => SegmentKind::Curved,
|
||||
"straight" => SegmentKind::Straight,
|
||||
_ => SegmentKind::All,
|
||||
};
|
||||
let spacing = match text(p, "spacing", "count") {
|
||||
"distance" => AnchorSpacing::Distance(num(p, "distance", 20.0).clamp(DISTANCE_RANGE.0, DISTANCE_RANGE.1)),
|
||||
_ => AnchorSpacing::Count(num(p, "count", 1.0).clamp(1.0, MAX_PER_SEGMENT) as usize),
|
||||
};
|
||||
let cap = num(p, "maxCount", 100.0).clamp(1.0, MAX_PER_SEGMENT) as usize;
|
||||
let mut room = max.saturating_sub(path.anchor_count());
|
||||
let mut out = Vec::with_capacity(path.subpaths.len());
|
||||
for sp in &path.subpaths {
|
||||
let added = tools::add_anchors(sp, which, spacing, cap, room)?;
|
||||
room = room.saturating_sub(added.anchors.len().saturating_sub(sp.anchors.len()));
|
||||
out.push(added);
|
||||
}
|
||||
Some(PathData::new(out))
|
||||
}
|
||||
|
||||
/// [`EXTEND`] on one subpath, the start then the end; what can't be done stays as it is.
|
||||
fn extend(sp: &SubPath, p: &Value) -> SubPath {
|
||||
let total = sp.anchor_arc_positions().last().copied().unwrap_or(0.0);
|
||||
let by = |length, percent| {
|
||||
num(p, length, 0.0).clamp(-MAX_EXTEND, MAX_EXTEND) + num(p, percent, 0.0).clamp(-MAX_EXTEND_PERCENT, MAX_EXTEND_PERCENT) / 100.0 * total
|
||||
};
|
||||
let shape = |key| if text(p, key, "straight") == "arc" { ExtendMode::Arc } else { ExtendMode::Straight };
|
||||
let ends = [(true, by("startLength", "startPercent"), shape("startShape")), (false, by("endLength", "endPercent"), shape("endShape"))];
|
||||
let mut out = sp.clone();
|
||||
for (at_start, by, shape) in ends {
|
||||
if by != 0.0
|
||||
&& let Some(changed) = tools::extend_open(&out, at_start, by, shape)
|
||||
{
|
||||
out = changed;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -0,0 +1,278 @@
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::Effect;
|
||||
use vectorcraft_geom::{Anchor, PathData, Point, Rect, SubPath};
|
||||
|
||||
use super::*;
|
||||
use crate::{apply_geometry, effect_info, is_geometry, is_length, is_raster, length_range};
|
||||
|
||||
fn pt(x: f64, y: f64) -> Point {
|
||||
Point::new(x, y)
|
||||
}
|
||||
|
||||
fn line(pts: &[(f64, f64)], closed: bool) -> PathData {
|
||||
PathData::single(SubPath::polyline(&pts.iter().map(|&(x, y)| pt(x, y)).collect::<Vec<_>>(), closed))
|
||||
}
|
||||
|
||||
fn square() -> PathData {
|
||||
line(&[(0.0, 0.0), (100.0, 0.0), (100.0, 100.0), (0.0, 100.0)], true)
|
||||
}
|
||||
|
||||
/// A curve: a smooth anchor between two ends.
|
||||
fn curve() -> PathData {
|
||||
let mid = Anchor::smooth(pt(50.0, -30.0), pt(70.0, -30.0));
|
||||
PathData::single(SubPath::new(
|
||||
vec![
|
||||
Anchor { h_out: pt(10.0, -20.0), ..Anchor::corner(pt(0.0, 0.0)) },
|
||||
mid,
|
||||
Anchor { h_in: pt(90.0, -20.0), ..Anchor::corner(pt(100.0, 0.0)) },
|
||||
],
|
||||
false,
|
||||
))
|
||||
}
|
||||
|
||||
fn run(id: &str, params: Value, path: &PathData) -> PathData {
|
||||
let e = Effect { id: id.into(), params, visible: true };
|
||||
apply_geometry(&[e], path, path.bounds().unwrap_or(Rect::ZERO))
|
||||
}
|
||||
|
||||
fn near(a: f64, b: f64) -> bool {
|
||||
(a - b).abs() < 1e-3
|
||||
}
|
||||
|
||||
const ALL: [&str; 9] = [DASHES, ADD_ANCHORS, EXTEND, CLOSE, SMOOTH, RETRACT, REVERSE, SIMPLIFY, SPLIT];
|
||||
|
||||
#[test]
|
||||
fn the_effects_are_geometry_effects_of_the_path_effects_menu() {
|
||||
for id in ALL {
|
||||
let info = effect_info(id).unwrap();
|
||||
assert!(is_geometry(id) && !is_raster(id), "{id}");
|
||||
assert_eq!(info.menu, ["Effect", "Path Effects"], "{id}");
|
||||
assert!(id.starts_with(PREFIX));
|
||||
}
|
||||
assert!(is_length(DASHES, "gap5", false) && is_length(EXTEND, "endLength", false) && !is_length(EXTEND, "endPercent", false));
|
||||
assert_eq!(length_range(DASHES, "offset"), Some(-10_000.0..=10_000.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dashes_become_open_subpaths_along_the_path() {
|
||||
let out = run(DASHES, json!({"dash1": 10, "gap1": 5}), &line(&[(0.0, 0.0), (100.0, 0.0)], false));
|
||||
assert_eq!(out.subpaths.len(), 7, "periods of 15 on 100");
|
||||
assert!(out.subpaths.iter().all(|s| !s.closed));
|
||||
assert!(near(out.subpaths[1].anchors[0].p.x, 15.0) && near(out.subpaths[1].anchors[1].p.x, 25.0));
|
||||
// Two pairs, and a pair of zeros between them left out.
|
||||
let out = run(DASHES, json!({"dash1": 10, "gap1": 5, "dash3": 20, "gap3": 5}), &line(&[(0.0, 0.0), (100.0, 0.0)], false));
|
||||
assert!(near(out.subpaths[1].anchors[0].p.x, 15.0) && near(out.subpaths[1].anchors[1].p.x, 35.0));
|
||||
assert!(near(out.subpaths[2].anchors[0].p.x, 40.0));
|
||||
// The offset starts part way into the pattern.
|
||||
let out = run(DASHES, json!({"dash1": 10, "gap1": 5, "offset": 5}), &line(&[(0.0, 0.0), (100.0, 0.0)], false));
|
||||
assert!(near(out.subpaths[0].anchors[1].p.x, 5.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aligned_dashes_centre_on_corners_and_ends() {
|
||||
let open = line(&[(0.0, 0.0), (100.0, 0.0), (100.0, 100.0)], false);
|
||||
let out = run(DASHES, json!({"dash1": 10, "gap1": 10, "alignCorners": true}), &open);
|
||||
let on = |p: Point| out.subpaths.iter().any(|s| (0..s.segment_count()).any(|i| s.anchors[i].p == p || s.anchors[i + 1].p == p));
|
||||
assert!(on(pt(0.0, 0.0)) && on(pt(100.0, 100.0)), "a dash on each end");
|
||||
let corner = out.subpaths.iter().find(|s| s.anchors.iter().any(|a| a.p == pt(100.0, 0.0))).expect("a dash over the corner");
|
||||
assert!(near(corner.anchors[0].p.distance(pt(100.0, 0.0)), corner.anchors[2].p.distance(pt(100.0, 0.0))), "centred: {corner:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_dashes_are_points_and_patterns_of_nothing_leave_the_path() {
|
||||
let path = line(&[(0.0, 0.0), (100.0, 0.0)], false);
|
||||
let dots = run(DASHES, json!({"dash1": 0, "gap1": 25}), &path);
|
||||
assert_eq!(dots.subpaths.len(), 5);
|
||||
assert!(dots.subpaths.iter().all(|s| s.anchors.len() == 2 && s.anchors[0].p == s.anchors[1].p));
|
||||
for params in [
|
||||
json!({"dash1": 0, "gap1": 0}),
|
||||
json!({"dash1": -5, "gap1": -5}),
|
||||
json!({"dash1": 0.0001, "gap1": 0.0001}),
|
||||
json!({"dash1": "x", "gap1": null}),
|
||||
] {
|
||||
assert_eq!(run(DASHES, params.clone(), &path), path, "{params}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_anchors_spaces_them_and_keeps_to_the_caps() {
|
||||
let square = square();
|
||||
assert_eq!(run(ADD_ANCHORS, json!({}), &square).anchor_count(), 8, "one on each segment");
|
||||
assert_eq!(run(ADD_ANCHORS, json!({"count": 3}), &square).anchor_count(), 16);
|
||||
assert_eq!(run(ADD_ANCHORS, json!({"count": 3, "maxCount": 2}), &square).anchor_count(), 12);
|
||||
let out = run(ADD_ANCHORS, json!({"spacing": "distance", "distance": 30}), &square);
|
||||
assert_eq!(out.anchor_count(), 16, "at 30, 60 and 90 on each side");
|
||||
assert!(near(out.subpaths[0].anchors[2].p.x, 60.0));
|
||||
assert_eq!(run(ADD_ANCHORS, json!({"spacing": "distance", "distance": 25}), &square).anchor_count(), 16, "none on the segment's end");
|
||||
assert_eq!(run(ADD_ANCHORS, json!({"spacing": "distance", "distance": 1e-9, "maxCount": 2}), &square).anchor_count(), 12);
|
||||
// Only the segments asked for.
|
||||
let mixed = PathData::new(vec![curve().subpaths[0].clone(), square.subpaths[0].clone()]);
|
||||
assert_eq!(run(ADD_ANCHORS, json!({"target": "curved"}), &mixed).anchor_count(), 7 + 2);
|
||||
assert_eq!(run(ADD_ANCHORS, json!({"target": "straight"}), &mixed).anchor_count(), 7 + 4);
|
||||
// The shape stays.
|
||||
let out = run(ADD_ANCHORS, json!({"count": 4}), &curve());
|
||||
assert_eq!(out.anchor_count(), 3 + 8);
|
||||
assert!(near(out.length(), curve().length()));
|
||||
assert_eq!(out.bounds().map(|b| (b.y0 * 100.0).round()), curve().bounds().map(|b| (b.y0 * 100.0).round()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_anchors_past_the_budget_leaves_the_path() {
|
||||
let path = square();
|
||||
let most = json!({"count": 1000, "maxCount": 1000});
|
||||
assert_eq!(super::apply(ADD_ANCHORS, &most, &path, 4003), path);
|
||||
assert_eq!(super::apply(ADD_ANCHORS, &most, &path, 4004).anchor_count(), 4004);
|
||||
// Many segments, each with the most anchors: refused before they are built.
|
||||
let long = line(&(0..4000).map(|i| (i as f64, 0.0)).collect::<Vec<_>>(), false);
|
||||
assert_eq!(run(ADD_ANCHORS, json!({"count": 1000, "maxCount": 1000}), &long).anchor_count(), 4000, "4 million would pass the budget");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extend_lengthens_and_shortens_each_end() {
|
||||
let path = line(&[(0.0, 0.0), (100.0, 0.0)], false);
|
||||
let out = run(EXTEND, json!({"startLength": 20, "endLength": 10}), &path);
|
||||
let xs: Vec<f64> = out.subpaths[0].anchors.iter().map(|a| a.p.x).collect();
|
||||
assert_eq!(xs, [-20.0, 0.0, 100.0, 110.0]);
|
||||
// Percent of the path's length, adding to the length; negative shortens.
|
||||
let out = run(EXTEND, json!({"startLength": 0, "endLength": 5, "endPercent": 10}), &path);
|
||||
assert!(out.subpaths[0].anchors.last().is_some_and(|a| near(a.p.x, 115.0)));
|
||||
let out = run(EXTEND, json!({"startLength": -10, "endLength": 0, "endPercent": -50}), &path);
|
||||
let xs: Vec<f64> = out.subpaths[0].anchors.iter().map(|a| a.p.x).collect();
|
||||
assert!(near(xs[0], 10.0) && near(xs[1], 50.0), "{xs:?}");
|
||||
// An arc carries on round the end's curve.
|
||||
let arc = run(EXTEND, json!({"startLength": 0, "endLength": 40, "endShape": "arc"}), &curve());
|
||||
let straight = run(EXTEND, json!({"startLength": 0, "endLength": 40}), &curve());
|
||||
assert!(arc.anchor_count() >= straight.anchor_count());
|
||||
assert!((arc.length() - straight.length()).abs() < 0.1);
|
||||
assert_ne!(arc.subpaths[0].anchors.last().map(|a| a.p), straight.subpaths[0].anchors.last().map(|a| a.p));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extend_leaves_what_it_cannot_change() {
|
||||
let closed = square();
|
||||
assert_eq!(run(EXTEND, json!({"endLength": 50}), &closed), closed);
|
||||
let path = line(&[(0.0, 0.0), (100.0, 0.0)], false);
|
||||
assert_eq!(run(EXTEND, json!({"startLength": 0, "endLength": -500}), &path), path, "nothing would be left");
|
||||
assert_eq!(run(EXTEND, json!({"startLength": 0, "endLength": 0, "endPercent": -100}), &path), path);
|
||||
let one = PathData::single(SubPath::polyline(&[pt(1.0, 1.0)], false));
|
||||
assert_eq!(run(EXTEND, json!({}), &one), one);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_joins_open_subpaths_straight_or_smooth() {
|
||||
let open = line(&[(0.0, 0.0), (100.0, 0.0), (100.0, 100.0)], false);
|
||||
let out = run(CLOSE, json!({}), &open);
|
||||
assert!(out.subpaths[0].closed && out.subpaths[0].segment_is_line(2));
|
||||
let out = run(CLOSE, json!({"mode": "smooth"}), &open);
|
||||
assert!(out.subpaths[0].closed && !out.subpaths[0].segment_is_line(2));
|
||||
assert_eq!(run(CLOSE, json!({}), &square()), square());
|
||||
let one = PathData::single(SubPath::polyline(&[pt(1.0, 1.0)], false));
|
||||
assert_eq!(run(CLOSE, json!({}), &one), one);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smooth_makes_a_square_round_and_clamps_its_ratio() {
|
||||
let out = run(SMOOTH, json!({}), &square());
|
||||
assert!(out.subpaths[0].anchors.iter().all(|a| near(a.h_out.distance(a.p), 39.05) && a.has_in()));
|
||||
let at = |ratio: f64| run(SMOOTH, json!({ "ratio": ratio }), &square()).subpaths[0].anchors[0];
|
||||
assert!(near(at(9.0).h_out.distance(pt(0.0, 0.0)), 50.0));
|
||||
assert!(near(at(-9.0).h_out.distance(pt(0.0, 0.0)), 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retract_takes_all_handles_or_the_short_ones() {
|
||||
let out = run(RETRACT, json!({}), &curve());
|
||||
assert!(out.subpaths[0].anchors.iter().all(|a| !a.has_in() && !a.has_out()));
|
||||
// The ends' handles are about 22 pt long and the middle anchor's 20.
|
||||
let out = run(RETRACT, json!({"handles": "shorter", "length": 21}), &curve());
|
||||
let a = &out.subpaths[0].anchors;
|
||||
assert!(!a[1].has_in() && !a[1].has_out() && a[0].has_out() && a[2].has_in());
|
||||
assert_eq!(run(RETRACT, json!({"handles": "shorter", "length": 1}), &curve()), curve());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reverse_runs_the_other_way_and_twice_is_as_it_was() {
|
||||
let out = run(REVERSE, json!({}), &curve());
|
||||
assert_eq!(out.subpaths[0].anchors[0].p, pt(100.0, 0.0));
|
||||
assert_eq!(run(REVERSE, json!({}), &out), curve());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simplify_takes_anchors_within_the_tolerance_and_stops_at_its_cap() {
|
||||
let pts: Vec<(f64, f64)> = (0..=40).map(|i| (i as f64, (i as f64 / 40.0 * std::f64::consts::PI).sin() * 3.0)).collect();
|
||||
let path = line(&pts, false);
|
||||
let out = run(SIMPLIFY, json!({"tolerance": 200}), &path);
|
||||
assert!(out.anchor_count() < 20, "{}", out.anchor_count());
|
||||
assert_eq!(run(SIMPLIFY, json!({"tolerance": 0}), &path), path);
|
||||
// Corners stay unless told otherwise.
|
||||
let corner = line(&[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0)], false);
|
||||
assert_eq!(run(SIMPLIFY, json!({"tolerance": 200}), &corner), corner);
|
||||
assert_eq!(run(SIMPLIFY, json!({"tolerance": 200, "protectCorners": false}), &corner).anchor_count(), 2);
|
||||
let long = line(&(0..=MAX_SIMPLIFY_ANCHORS).map(|i| (i as f64, 0.0)).collect::<Vec<_>>(), false);
|
||||
assert_eq!(run(SIMPLIFY, json!({}), &long).anchor_count(), long.anchor_count());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn split_cuts_at_the_anchors_asked_for() {
|
||||
let mixed = PathData::single(SubPath::new(
|
||||
vec![
|
||||
Anchor::corner(pt(0.0, 0.0)),
|
||||
Anchor::smooth(pt(50.0, 0.0), pt(60.0, 0.0)),
|
||||
Anchor::corner(pt(100.0, 0.0)),
|
||||
Anchor::corner(pt(100.0, 50.0)),
|
||||
],
|
||||
false,
|
||||
));
|
||||
assert_eq!(run(SPLIT, json!({}), &mixed).subpaths.len(), 3);
|
||||
assert_eq!(run(SPLIT, json!({"anchors": "smooth"}), &mixed).subpaths.len(), 2);
|
||||
assert_eq!(run(SPLIT, json!({"anchors": "corner"}), &mixed).subpaths.len(), 2);
|
||||
let out = run(SPLIT, json!({}), &square());
|
||||
assert_eq!(out.subpaths.len(), 4);
|
||||
assert!(out.subpaths.iter().all(|s| !s.closed && s.anchors.len() == 2));
|
||||
assert!(near(out.length(), 400.0));
|
||||
assert_eq!(run(SPLIT, json!({"anchors": "smooth"}), &square()), square(), "no smooth anchors");
|
||||
}
|
||||
|
||||
/// Whatever a file holds: every effect takes wrong types, huge numbers and paths of nothing, of one
|
||||
/// anchor or past measuring, and keeps to the budget.
|
||||
#[test]
|
||||
fn junk_params_and_paths_are_harmless() {
|
||||
let junk = [
|
||||
json!({}),
|
||||
json!(null),
|
||||
json!([1, 2]),
|
||||
json!("x"),
|
||||
json!({"dash1": 1e308, "gap1": 1e308, "dash2": -1e308, "gap2": "x", "offset": 1e308, "alignCorners": "yes"}),
|
||||
json!({"dash1": 1e-300, "gap1": 1e-300, "offset": -1e308, "alignCorners": true}),
|
||||
json!({"target": 3, "spacing": ["distance"], "count": 1e308, "distance": 1e-308, "maxCount": -1e308}),
|
||||
json!({"spacing": "distance", "distance": -1e308, "maxCount": 1e308}),
|
||||
json!({"startLength": 1e308, "startPercent": 1e308, "startShape": 7, "endLength": -1e308, "endPercent": -1e308, "endShape": "arc"}),
|
||||
json!({"mode": {}, "ratio": 1e308, "handles": "shorter", "length": 1e308, "tolerance": 1e308, "protectCorners": "no", "anchors": 9}),
|
||||
json!({"ratio": -1e308, "length": -1e308, "tolerance": -1e308, "anchors": "none"}),
|
||||
];
|
||||
let far = line(&[(1e308, -1e308), (-1e308, 1e308), (1e300, 0.0)], false);
|
||||
let nan = line(&[(f64::NAN, 0.0), (f64::INFINITY, 1.0)], false);
|
||||
let tiny = line(&[(0.0, 0.0), (1e-300, 0.0), (1e-300, 1e-300)], true);
|
||||
let same = line(&[(5.0, 5.0), (5.0, 5.0), (5.0, 5.0)], true);
|
||||
let empty = PathData::single(SubPath::default());
|
||||
let one = PathData::single(SubPath::polyline(&[pt(1.0, 1.0)], true));
|
||||
let paths = [square(), curve(), far.clone(), nan.clone(), tiny, same, empty, one, PathData::default()];
|
||||
for id in ALL {
|
||||
for params in &junk {
|
||||
for path in &paths {
|
||||
let out = super::apply(id, &crate::merged_params(id, params), path, 100_000);
|
||||
assert!(out.anchor_count() <= 100_000.max(path.anchor_count()) + 8 * path.subpaths.len(), "{id} {params}");
|
||||
}
|
||||
}
|
||||
// Compared as text: NaN never equals itself.
|
||||
assert_eq!(format!("{:?}", super::apply(id, &json!({}), &nan, 100)), format!("{nan:?}"), "{id}");
|
||||
assert_eq!(super::apply(id, &json!({}), &far, 100), far, "{id}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The dash generator's limit holds: a pattern too fine for the path leaves it solid.
|
||||
#[test]
|
||||
fn too_many_dashes_leave_the_path() {
|
||||
let long = line(&[(0.0, 0.0), (1e9, 0.0)], false);
|
||||
assert_eq!(run(DASHES, json!({"dash1": 1, "gap1": 1}), &long), long);
|
||||
}
|
||||
@@ -793,6 +793,7 @@ pub fn effect_catalog() -> Vec<EffectInfo> {
|
||||
}
|
||||
v.extend(crate::addons::color::catalog());
|
||||
v.extend(crate::addons::halftone::catalog());
|
||||
v.extend(crate::addons::path::catalog());
|
||||
v.push(g(TRAP, "Trap…", PATHFINDER, trap::EFFECT_PARAMS_DOC, trap::effect_defaults()));
|
||||
v.push(g(
|
||||
CROP_MARKS,
|
||||
|
||||
@@ -265,6 +265,7 @@ fn apply_unchecked(id: &str, p: &Value, path: &PathData, b: Rect, ctx: &GeomCont
|
||||
"convertToShape.rectangle" | "convertToShape.roundedRectangle" | "convertToShape.ellipse" => stylize::convert_to_shape(id, b, p),
|
||||
"stylize.roundCorners" => stylize::round_corners(path, num(p, "radius", 10.0)),
|
||||
"stylize.scribble" => stylize::scribble(path, b, p),
|
||||
_ if id.starts_with(crate::addons::path::PREFIX) => crate::addons::path::apply(id, p, path, max),
|
||||
_ => match id.strip_prefix("warp.").and_then(WarpStyle::from_id) {
|
||||
Some(style) => warp::warp(path, b, style, p, max),
|
||||
None => path.clone(),
|
||||
|
||||
@@ -18,9 +18,8 @@ use super::super::edit::selected_roots;
|
||||
use super::super::{CommandSpec, bad, checked_ids_or_selection, cmd, has_doc, has_selection, number_or, switch_or, text_param};
|
||||
use crate::{EngineError, Result, Session};
|
||||
|
||||
/// Simplify Anchors' tolerance by default, and the drift (pt) each step of it allows.
|
||||
/// Simplify Anchors' tolerance by default.
|
||||
const TOLERANCE: f64 = 20.0;
|
||||
pub(super) const PT_PER_TOLERANCE: f64 = 0.02;
|
||||
/// Rejoin Path Pieces' gap by default, and the widest (pt).
|
||||
const GAP: f64 = 0.1;
|
||||
const MAX_GAP: f64 = 6.0;
|
||||
@@ -123,7 +122,7 @@ fn simplify(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let changed: Vec<(NodeId, PathData)> = paths
|
||||
.iter()
|
||||
.filter_map(|(id, path)| {
|
||||
let np = po::simplify_keep_shape(path, tolerance * PT_PER_TOLERANCE, protect);
|
||||
let np = po::simplify_keep_shape(path, tolerance * po::PT_PER_TOLERANCE, protect);
|
||||
(np != **path).then_some((*id, np))
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
//! and the UI's.
|
||||
|
||||
mod corners;
|
||||
#[cfg(test)]
|
||||
mod effect_tests;
|
||||
mod shape;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -22,7 +24,6 @@ use super::super::pathops::Beside;
|
||||
use super::super::{
|
||||
CommandSpec, bad, checked_id, checked_ids_or_selection, cmd, finite_numbers, has_doc, has_selection, number_or, switch_or, text_param,
|
||||
};
|
||||
use super::cleanup::PT_PER_TOLERANCE;
|
||||
use crate::{EngineError, Result, Session};
|
||||
|
||||
/// The Anchor Eraser Brush's tolerance by default (the scale Simplify Anchors (Keep Shape) uses).
|
||||
@@ -256,7 +257,7 @@ fn erase(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let ids = paths_under(d, &roots);
|
||||
let paths: Vec<(NodeId, &PathData)> = ids.iter().filter_map(|id| Some((*id, d.node(*id)?.path_data()?))).collect();
|
||||
check_size(paths.iter().map(|(_, p)| *p), C)?;
|
||||
let tol = if ignore { f64::INFINITY } else { tolerance * PT_PER_TOLERANCE };
|
||||
let tol = if ignore { f64::INFINITY } else { tolerance * po::PT_PER_TOLERANCE };
|
||||
let mut removed = 0;
|
||||
let mut changed = vec![];
|
||||
for (id, path) in paths {
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
//! Path Effects (`addon.path.*`) through the session: live on the canvas, the document's path left
|
||||
//! alone, and Expand Appearance and a save keeping what they draw.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::{Document, Effect, Node, NodeId};
|
||||
use vectorcraft_geom::{BezPath, Rect};
|
||||
use vectorcraft_render::Renderer;
|
||||
use vectorcraft_render::effects::{GeomContext, apply_geometry_bez, effect_catalog, new_effect, preview, scale_effect};
|
||||
|
||||
use crate::Session;
|
||||
|
||||
/// A black 4 pt stroke along `anchors`, unfilled.
|
||||
fn stroked(anchors: Value, closed: bool) -> (Session, NodeId) {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
|
||||
let id = NodeId(s.execute("path.create", &json!({"anchors": anchors, "closed": closed})).unwrap()["id"].as_u64().unwrap());
|
||||
s.select(|_, sel| sel.set([id])).unwrap();
|
||||
s.execute("paint.setFill", &json!({"none": true})).unwrap();
|
||||
s.execute("paint.setStroke", &json!({"color": "#000000"})).unwrap();
|
||||
s.execute("stroke.set", &json!({"weight": 4})).unwrap();
|
||||
(s, id)
|
||||
}
|
||||
|
||||
fn wave() -> (Session, NodeId) {
|
||||
let anchors = json!([
|
||||
{"x": 20, "y": 100},
|
||||
{"x": 60, "y": 60, "in": [40, 60], "out": [80, 60]},
|
||||
{"x": 100, "y": 100},
|
||||
{"x": 140, "y": 140},
|
||||
{"x": 180, "y": 100}
|
||||
]);
|
||||
stroked(anchors, false)
|
||||
}
|
||||
|
||||
/// Every Path Effect, with parameters that change the wave.
|
||||
fn effects() -> Vec<(&'static str, Value)> {
|
||||
vec![
|
||||
("addon.path.dashes", json!({"dash1": 14, "gap1": 6, "dash2": 3, "gap2": 6})),
|
||||
("addon.path.dashes", json!({"dash1": 14, "gap1": 6, "alignCorners": true})),
|
||||
("addon.path.addAnchors", json!({"count": 3})),
|
||||
("addon.path.addAnchors", json!({"spacing": "distance", "distance": 15, "target": "curved"})),
|
||||
("addon.path.extend", json!({"startLength": 10, "endPercent": 10, "endShape": "arc"})),
|
||||
("addon.path.extend", json!({"startLength": -10, "endLength": -20})),
|
||||
("addon.path.close", json!({})),
|
||||
("addon.path.close", json!({"mode": "smooth"})),
|
||||
("addon.path.smooth", json!({"ratio": 0.3})),
|
||||
("addon.path.retract", json!({})),
|
||||
("addon.path.reverse", json!({})),
|
||||
("addon.path.simplify", json!({"tolerance": 200, "protectCorners": false})),
|
||||
("addon.path.split", json!({"anchors": "both"})),
|
||||
]
|
||||
}
|
||||
|
||||
fn node(s: &Session, id: NodeId) -> Node {
|
||||
s.doc().unwrap().doc.node(id).cloned().unwrap()
|
||||
}
|
||||
|
||||
fn picture_of(d: &Document) -> Vec<u8> {
|
||||
let r = Renderer::new().render_region(d, Rect::new(0.0, 0.0, 200.0, 200.0), 1.0, true);
|
||||
(0..200).flat_map(|y| (0..200).map(move |x| (x, y))).flat_map(|(x, y)| r.pixel(x, y)).collect()
|
||||
}
|
||||
|
||||
fn picture(s: &Session) -> Vec<u8> {
|
||||
picture_of(&s.doc().unwrap().doc)
|
||||
}
|
||||
|
||||
/// The outline of `n`: its path, or those of the compound path it became.
|
||||
fn outline(n: &Node) -> BezPath {
|
||||
let mut out = n.path_data().map(|p| p.to_bezpath()).unwrap_or_default();
|
||||
n.children().into_iter().flatten().for_each(|c| out.extend(outline(c)));
|
||||
out
|
||||
}
|
||||
|
||||
/// How many bytes of two pictures differ by more than antialiasing noise.
|
||||
fn differing(a: &[u8], b: &[u8]) -> usize {
|
||||
a.iter().zip(b).filter(|(x, y)| x.abs_diff(**y) > 8).count()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_path_effect_draws_live_and_leaves_the_document_path() {
|
||||
let (mut s, id) = stroked(json!([{"x": 10, "y": 50}, {"x": 190, "y": 50}]), false);
|
||||
let solid = picture(&s);
|
||||
let path = node(&s, id).path_data().cloned();
|
||||
s.execute("effect.apply", &json!({"effect": "addon.path.dashes", "params": {"dash1": 20, "gap1": 20}})).unwrap();
|
||||
assert_eq!(node(&s, id).path_data().cloned(), path, "only the effect holds the geometry");
|
||||
let dashed = picture(&s);
|
||||
let at = |pic: &[u8], x: usize| pic[(50 * 200 + x) * 4];
|
||||
assert!(at(&solid, 45) < 40 && at(&dashed, 45) > 200, "a gap: {}", at(&dashed, 45));
|
||||
assert!(at(&dashed, 15) < 40, "a dash");
|
||||
// Hiding it draws the path again.
|
||||
s.execute("effect.setParams", &json!({"index": 0, "visible": false})).unwrap();
|
||||
assert_eq!(picture(&s), solid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_path_effect_moves_the_bounds_the_object_is_measured_by() {
|
||||
let (mut s, id) = stroked(json!([{"x": 50, "y": 50}, {"x": 150, "y": 50}]), false);
|
||||
s.execute("effect.apply", &json!({"effect": "addon.path.extend", "params": {"startLength": 0, "endLength": 30}})).unwrap();
|
||||
let drawn = preview::bounds_of(&s.doc().unwrap().doc, &[id], true).unwrap();
|
||||
assert!((drawn.x1 - 180.0).abs() < 3.0, "{drawn:?}");
|
||||
let pic = picture(&s);
|
||||
assert!(pic[(50 * 200 + 170) * 4] < 40, "and draws there");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_appearance_writes_the_outline_the_effect_draws() {
|
||||
for (effect, params) in effects() {
|
||||
let (mut s, id) = wave();
|
||||
s.execute("effect.apply", &json!({"effect": effect, "params": params})).unwrap();
|
||||
let n = node(&s, id);
|
||||
let before = n.path_data().cloned().unwrap();
|
||||
let live = apply_geometry_bez(&n.appearance.effects, &before.to_bezpath(), before.bounds().unwrap(), &GeomContext::of(&n));
|
||||
let pic = picture(&s);
|
||||
s.execute("effect.expandAppearance", &json!({})).unwrap();
|
||||
let expanded = node(&s, id);
|
||||
assert!(expanded.appearance.effects.is_empty(), "{effect} {params}");
|
||||
let (out, want) = (outline(&expanded), live);
|
||||
assert_eq!(out.elements().len(), want.elements().len(), "{effect} {params}");
|
||||
for (a, b) in out.elements().iter().zip(want.elements()) {
|
||||
assert!(a.is_finite() && format!("{a:.4?}") == format!("{b:.4?}"), "{effect} {params}: {a:?} {b:?}");
|
||||
}
|
||||
assert_eq!(differing(&pic, &picture(&s)), 0, "{effect} {params}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_effects_round_trip_through_a_save_and_scale_their_distances() {
|
||||
for (effect, params) in effects() {
|
||||
let (mut s, id) = wave();
|
||||
s.execute("effect.apply", &json!({"effect": effect, "params": params})).unwrap();
|
||||
let restored = vectorcraft_format::load(&vectorcraft_format::save(&s.doc().unwrap().doc, false)).unwrap();
|
||||
assert_eq!(restored.node(id).unwrap().appearance.effects, node(&s, id).appearance.effects, "{effect} {params}");
|
||||
assert_eq!(picture_of(&restored), picture(&s), "{effect} {params}");
|
||||
}
|
||||
let mut e = new_effect("addon.path.dashes", &json!({"dash1": 10})).unwrap();
|
||||
scale_effect(&mut e, 2.0);
|
||||
assert_eq!((e.params["dash1"].as_f64(), e.params["gap1"].as_f64()), (Some(20.0), Some(12.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undo_takes_a_path_effect_off_in_one_step() {
|
||||
let (mut s, id) = wave();
|
||||
s.execute("effect.apply", &json!({"effect": "addon.path.reverse"})).unwrap();
|
||||
assert_eq!(node(&s, id).appearance.effects.len(), 1);
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert!(node(&s, id).appearance.effects.is_empty());
|
||||
}
|
||||
|
||||
/// A file's effects skip the checks `effect.apply` makes: whatever they hold, the object draws and
|
||||
/// expands.
|
||||
#[test]
|
||||
fn path_effects_with_junk_params_from_a_file_draw_and_expand() {
|
||||
let junk = [
|
||||
json!(null),
|
||||
json!("x"),
|
||||
json!({"dash1": 1e308, "gap1": -1e308, "offset": "x", "alignCorners": 3}),
|
||||
json!({"count": 1e308, "maxCount": 1e308, "distance": -1e308, "spacing": "distance", "target": []}),
|
||||
json!({"startLength": 1e308, "endPercent": -1e308, "endShape": {}, "mode": 1, "ratio": "x", "handles": "shorter", "length": 1e308}),
|
||||
json!({"tolerance": 1e308, "protectCorners": "no", "anchors": null, "startPercent": 1e308, "startShape": "arc"}),
|
||||
];
|
||||
let ids = effect_catalog().into_iter().map(|e| e.id).filter(|id| id.starts_with("addon.path."));
|
||||
let stack: Vec<Effect> =
|
||||
ids.flat_map(|id| junk.iter().map(move |params| Effect { id: id.into(), params: params.clone(), visible: true })).collect();
|
||||
let (mut s, id) = wave();
|
||||
s.edit("Junk", |d, _| {
|
||||
if let Some(n) = d.node_mut(id) {
|
||||
n.appearance.effects = stack;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
let saved = vectorcraft_format::save(&s.doc().unwrap().doc, false);
|
||||
picture_of(&vectorcraft_format::load(&saved).unwrap());
|
||||
s.execute("effect.expandAppearance", &json!({})).unwrap();
|
||||
assert!(node(&s, id).appearance.effects.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exports_write_the_dashes_as_paths() {
|
||||
let (mut s, _) = wave();
|
||||
s.execute("effect.apply", &json!({"effect": "addon.path.dashes", "params": {"dash1": 10, "gap1": 10}})).unwrap();
|
||||
let v = s.execute("document.export", &json!({"format": "svg"})).unwrap();
|
||||
let svg = vectorcraft_format::base64_decode(v["dataBase64"].as_str().unwrap()).unwrap();
|
||||
let moves = svg.iter().filter(|c| **c == b'M').count();
|
||||
assert!(moves > 5, "the exported art holds the dashes: {moves}");
|
||||
}
|
||||
@@ -419,6 +419,18 @@ fn structured_junk() {
|
||||
"addon.color.filter",
|
||||
json!({"effect": "addon.color.duotone", "params": {"tones": [{"color": "#ff0000", "swatch": "[Registration]"}], "skipSpot": true}}),
|
||||
),
|
||||
("effect.apply", json!({"effect": "addon.path.dashes", "params": {"dash1": 1e308, "gap1": 1e308, "offset": -1e308, "alignCorners": true}})),
|
||||
("effect.apply", json!({"effect": "addon.path.dashes", "params": {"dash1": -1e308, "gap1": 1e-308, "dash5": 1e-9, "gap5": 0}})),
|
||||
("effect.apply", json!({"effect": "addon.path.addAnchors", "params": {"target": "x", "spacing": "x", "count": 1e308, "maxCount": 1e308}})),
|
||||
("effect.apply", json!({"effect": "addon.path.addAnchors", "params": {"spacing": "distance", "distance": 1e-308, "maxCount": -1e308}})),
|
||||
("effect.apply", json!({"effect": "addon.path.extend", "params": {"startLength": 1e308, "endLength": -1e308, "startPercent": -1e308}})),
|
||||
("effect.apply", json!({"effect": "addon.path.extend", "params": {"endPercent": 1e308, "endShape": "arc", "startShape": "x"}})),
|
||||
("effect.apply", json!({"effect": "addon.path.close", "params": {"mode": "x"}})),
|
||||
("effect.apply", json!({"effect": "addon.path.smooth", "params": {"ratio": 1e308}})),
|
||||
("effect.apply", json!({"effect": "addon.path.retract", "params": {"handles": "shorter", "length": 1e308}})),
|
||||
("effect.apply", json!({"effect": "addon.path.reverse"})),
|
||||
("effect.apply", json!({"effect": "addon.path.simplify", "params": {"tolerance": 1e308, "protectCorners": false}})),
|
||||
("effect.apply", json!({"effect": "addon.path.split", "params": {"anchors": "x"}})),
|
||||
("addon.color.printFixes", json!({"inkLimit": 1e308, "overprintBlack": -1e308})),
|
||||
("addon.color.printFixes", json!({"inkLimit": 1, "overprintBlack": 1, "removeWhiteOverprint": true, "scope": "document", "textOnly": true})),
|
||||
("addon.color.printFixes", json!({"inkLimit": [300], "scope": 3, "ids": "x"})),
|
||||
|
||||
@@ -171,6 +171,27 @@ fn rich_document_roundtrips() {
|
||||
assert_eq!(v["version"], vectorcraft_format::VERSION);
|
||||
}
|
||||
|
||||
/// A file's Path Effects are its own business: whatever their parameters hold, they load and save
|
||||
/// as they are (the engine's tests draw them).
|
||||
#[test]
|
||||
fn path_effects_with_junk_params_round_trip() {
|
||||
let mut d = rich_doc();
|
||||
let mut id = None;
|
||||
d.walk(|n| id = id.or(n.path_data().map(|_| n.id)));
|
||||
let id = id.unwrap();
|
||||
let effects: Vec<vectorcraft_doc::Effect> = [
|
||||
("addon.path.dashes", json!({"dash1": 1e308, "gap1": -1e308, "offset": "x", "alignCorners": 3})),
|
||||
("addon.path.addAnchors", json!({"count": 1e308, "maxCount": 1e308, "distance": -1e308, "spacing": "distance", "target": []})),
|
||||
("addon.path.extend", json!(null)),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(id, params)| vectorcraft_doc::Effect { id: id.into(), params, visible: true })
|
||||
.collect();
|
||||
d.node_mut(id).unwrap().appearance.effects = effects.clone();
|
||||
let back = load(&save(&d, true)).unwrap();
|
||||
assert_eq!(back.node(id).unwrap().appearance.effects, effects);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sniff_distinguishes_formats() {
|
||||
assert!(sniff(&save(&Document::new(1.0, 1.0), false)));
|
||||
|
||||
@@ -20,5 +20,5 @@ pub mod segment_reshape;
|
||||
pub use cleanup::{
|
||||
Rejoined, close_nearly_closed, merge_duplicate_anchors, near_point_groups, rejoin, remove_collinear_anchors, retract_tiny_handles,
|
||||
};
|
||||
pub use keep_shape::{SHARP_CORNER_DEG, remove_anchor_keep_shape, simplify_anchors_keep_shape, simplify_keep_shape};
|
||||
pub use keep_shape::{PT_PER_TOLERANCE, SHARP_CORNER_DEG, remove_anchor_keep_shape, simplify_anchors_keep_shape, simplify_keep_shape};
|
||||
pub use segment_reshape::{ReshapeMode, reshape_segment};
|
||||
|
||||
@@ -13,6 +13,9 @@ use crate::fit::{end_tangent, start_tangent};
|
||||
/// The turn (degrees) at an anchor above which `protect_corners` keeps it.
|
||||
pub const SHARP_CORNER_DEG: f64 = 30.0;
|
||||
|
||||
/// The drift (pt) each step of the tolerance the add-ons offer allows.
|
||||
pub const PT_PER_TOLERANCE: f64 = 0.02;
|
||||
|
||||
/// Points sampled along each curve to measure how far two outlines are apart.
|
||||
const SAMPLES: usize = 8;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Path Precision Tools' geometry: anchor edits for the Anchor Inspector (smooth by the auto-smooth
|
||||
//! rule, equalise handles, add evenly spaced anchors, close), the readouts the Measure & Inspect
|
||||
//! rule, equalise handles, add evenly spaced anchors, close), what its live path effects add to them
|
||||
//! ([`add_anchors`], [`smooth_anchors`], [`split_at_anchors`]), the readouts the Measure & Inspect
|
||||
//! tool shows over a path, open paths lengthened or shortened at an end ([`extend`]), the places
|
||||
//! where paths cross ([`crossings`]), anchors slid along their path ([`slide`]) and the Parametric
|
||||
//! Shape Tool's shapes ([`shapes`]).
|
||||
@@ -14,7 +15,7 @@ mod tests;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use kurbo::{CubicBez, ParamCurve, ParamCurveArclen, ParamCurveNearest, Point, Vec2};
|
||||
use vectorcraft_geom::{AnchorKind, PathData, SubPath};
|
||||
use vectorcraft_geom::{Anchor, AnchorKind, PathData, SubPath};
|
||||
|
||||
pub use crossings::{MAX_CROSSING_SEGMENTS, crossings, insert_at};
|
||||
pub use extend::{ExtendMode, extend_length_to, extend_open};
|
||||
@@ -151,6 +152,69 @@ pub fn add_points(sp: &SubPath, segments: Option<&BTreeSet<usize>>, count: usize
|
||||
insert_at(sp, &hits)
|
||||
}
|
||||
|
||||
/// The segments [`add_anchors`] adds to.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum SegmentKind {
|
||||
All,
|
||||
Curved,
|
||||
Straight,
|
||||
}
|
||||
|
||||
/// How [`add_anchors`] spaces its anchors along a segment.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum AnchorSpacing {
|
||||
/// This many, evenly spaced by length.
|
||||
Count(usize),
|
||||
/// One every this many points from the segment's start, none on its end.
|
||||
Distance(f64),
|
||||
}
|
||||
|
||||
/// `sp` with anchors added on the segments `which` picks, spaced as `spacing` says and at most `cap`
|
||||
/// on one segment; the shape doesn't change. `None` when that would add more than `room` anchors.
|
||||
pub fn add_anchors(sp: &SubPath, which: SegmentKind, spacing: AnchorSpacing, cap: usize, mut room: usize) -> Option<SubPath> {
|
||||
let mut hits = vec![];
|
||||
for seg in 0..sp.segment_count() {
|
||||
let picked = match which {
|
||||
SegmentKind::All => true,
|
||||
SegmentKind::Curved => !sp.segment_is_line(seg),
|
||||
SegmentKind::Straight => sp.segment_is_line(seg),
|
||||
};
|
||||
let c = sp.segment(seg);
|
||||
let len = c.arclen(ACCURACY);
|
||||
if !picked || !(len > TINY && len.is_finite()) {
|
||||
continue;
|
||||
}
|
||||
let (count, step) = match spacing {
|
||||
AnchorSpacing::Count(n) => (n, len / (n as f64 + 1.0)),
|
||||
AnchorSpacing::Distance(d) if d > 0.0 => (((len / d).ceil() as usize).saturating_sub(1), d),
|
||||
AnchorSpacing::Distance(_) => (0, 0.0),
|
||||
};
|
||||
let count = count.min(cap);
|
||||
room = room.checked_sub(count)?;
|
||||
hits.extend((1..=count).map(|k| (seg, c.inv_arclen(step * k as f64, ACCURACY))));
|
||||
}
|
||||
Some(insert_at(sp, &hits).0)
|
||||
}
|
||||
|
||||
/// Make every anchor of `sp` smooth by the auto-smooth rule ([`auto_smooth`]). An open path's ends
|
||||
/// come last, so each aims at the handle its neighbour ended up with, whichever way the path runs.
|
||||
pub fn smooth_anchors(sp: &mut SubPath, ratio: f64) {
|
||||
let (n, closed) = (sp.anchors.len(), sp.closed);
|
||||
let ends = if closed { vec![] } else { vec![0, n.saturating_sub(1)] };
|
||||
for i in (0..n).filter(|&i| closed || (i > 0 && i + 1 < n)).chain(ends) {
|
||||
auto_smooth(sp, i, ratio);
|
||||
}
|
||||
}
|
||||
|
||||
/// `sp` cut at its `corners`, its `smooth` anchors, or both ([`SubPath::cut_at`]); the shape doesn't
|
||||
/// change. An anchor is smooth when its handles stand in line, as the outline shows it, whatever
|
||||
/// kind it was given.
|
||||
pub fn split_at_anchors(sp: &SubPath, corners: bool, smooth: bool) -> Vec<SubPath> {
|
||||
let is_smooth = |a: &Anchor| Anchor::with_handles(a.p, a.h_in, a.h_out).kind == AnchorKind::Smooth;
|
||||
let at = sp.anchors.iter().enumerate().filter(|(_, a)| if is_smooth(a) { smooth } else { corners }).map(|(i, _)| i).collect();
|
||||
sp.cut_at(&at)
|
||||
}
|
||||
|
||||
/// How [`close`] joins an open subpath's ends.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum CloseMode {
|
||||
|
||||
@@ -359,3 +359,78 @@ fn the_slide_a_pointer_asks_for_and_where_the_tangent_stops() {
|
||||
assert_eq!(stops.len(), 4, "{stops:?}");
|
||||
assert!(stops.windows(2).all(|w| w[0] < w[1]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_anchors_spaces_them_by_count_or_distance() {
|
||||
let sq = line(&[(0.0, 0.0), (90.0, 0.0), (90.0, 90.0), (0.0, 90.0)], true);
|
||||
let out = add_anchors(&sq, SegmentKind::All, AnchorSpacing::Count(2), 100, 100).unwrap();
|
||||
assert_eq!(out.anchors.len(), 12);
|
||||
assert!(near(out.anchors[1].p.x, 30.0, 1e-6) && near(out.anchors[2].p.x, 60.0, 1e-6));
|
||||
assert!(out.closed);
|
||||
let out = add_anchors(&sq, SegmentKind::All, AnchorSpacing::Distance(40.0), 100, 100).unwrap();
|
||||
assert_eq!(out.anchors.len(), 12, "at 40 and 80, none past the segment");
|
||||
assert!(near(out.anchors[2].p.x, 80.0, 1e-6));
|
||||
assert_eq!(add_anchors(&sq, SegmentKind::All, AnchorSpacing::Distance(1.0), 3, 100).unwrap().anchors.len(), 16, "the cap");
|
||||
assert_eq!(add_anchors(&sq, SegmentKind::All, AnchorSpacing::Distance(0.0), 3, 100).unwrap(), sq);
|
||||
assert_eq!(add_anchors(&sq, SegmentKind::Curved, AnchorSpacing::Count(1), 3, 100).unwrap(), sq, "no curves");
|
||||
assert_eq!(add_anchors(&sq, SegmentKind::All, AnchorSpacing::Count(3), 9, 12).unwrap().anchors.len(), 16, "room for all of them");
|
||||
assert!(add_anchors(&sq, SegmentKind::All, AnchorSpacing::Count(3), 9, 11).is_none(), "no room");
|
||||
// A curve keeps its shape, and a zero-length segment gets nothing.
|
||||
let bent = SubPath::new(
|
||||
vec![
|
||||
Anchor { h_out: pt(0.0, 50.0), ..Anchor::corner(pt(0.0, 0.0)) },
|
||||
Anchor { h_in: pt(100.0, 50.0), ..Anchor::corner(pt(100.0, 0.0)) },
|
||||
Anchor::corner(pt(100.0, 0.0)),
|
||||
],
|
||||
false,
|
||||
);
|
||||
let out = add_anchors(&bent, SegmentKind::Curved, AnchorSpacing::Count(3), 9, 9).unwrap();
|
||||
assert_eq!(out.anchors.len(), 6);
|
||||
assert!(near(length(&out), length(&bent), 1e-4));
|
||||
assert_eq!(add_anchors(&bent, SegmentKind::Straight, AnchorSpacing::Count(3), 9, 9).unwrap(), bent);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoothing_every_anchor_reads_the_same_either_way_along_the_path() {
|
||||
let zig = line(&[(0.0, 0.0), (30.0, 40.0), (70.0, 0.0), (100.0, 50.0)], false);
|
||||
let mut there = zig.clone();
|
||||
smooth_anchors(&mut there, SMOOTH_RATIO);
|
||||
let mut back = zig.clone();
|
||||
back.reverse();
|
||||
smooth_anchors(&mut back, SMOOTH_RATIO);
|
||||
back.reverse();
|
||||
for (a, b) in there.anchors.iter().zip(&back.anchors) {
|
||||
assert!(a.h_in.distance(b.h_in) < 1e-9 && a.h_out.distance(b.h_out) < 1e-9, "{a:?} {b:?}");
|
||||
}
|
||||
assert!(there.anchors[1].kind == AnchorKind::Smooth && there.anchors[0].has_out() && there.anchors[3].has_in());
|
||||
// Nothing to smooth.
|
||||
for mut sp in [SubPath::default(), line(&[(1.0, 1.0)], false), line(&[(1.0, 1.0)], true)] {
|
||||
let before = sp.clone();
|
||||
smooth_anchors(&mut sp, SMOOTH_RATIO);
|
||||
assert_eq!(sp, before);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn splitting_reads_an_anchors_kind_from_its_handles() {
|
||||
// Smooth by its handles though it is called a corner, as a path read back from an outline is.
|
||||
let run = SubPath::new(
|
||||
vec![
|
||||
Anchor::corner(pt(0.0, 0.0)),
|
||||
Anchor { h_in: pt(40.0, 0.0), h_out: pt(60.0, 0.0), ..Anchor::corner(pt(50.0, 0.0)) },
|
||||
Anchor::corner(pt(100.0, 0.0)),
|
||||
Anchor::corner(pt(100.0, 50.0)),
|
||||
],
|
||||
false,
|
||||
);
|
||||
assert_eq!(split_at_anchors(&run, false, true).len(), 2);
|
||||
assert_eq!(split_at_anchors(&run, true, false).len(), 2);
|
||||
assert_eq!(split_at_anchors(&run, true, true).len(), 3);
|
||||
assert_eq!(split_at_anchors(&run, false, false), std::slice::from_ref(&run));
|
||||
let pieces = split_at_anchors(&run, true, true);
|
||||
assert!(near(pieces.iter().map(length).sum::<f64>(), length(&run), 1e-6), "the shape stays");
|
||||
// A closed subpath opens at its first cut.
|
||||
let sq = line(&[(0.0, 0.0), (10.0, 0.0), (10.0, 10.0)], true);
|
||||
assert_eq!(split_at_anchors(&sq, true, false).len(), 3);
|
||||
assert!(split_at_anchors(&sq, true, false).iter().all(|p| !p.closed));
|
||||
}
|
||||
|
||||
@@ -30,6 +30,20 @@ pub const KIND: &str = "pathToolOptions";
|
||||
const CLOSE_MODES: [(&str, &str); 3] = [("handles", "Keep Handles"), ("straight", "Straight"), ("smooth", "Smooth")];
|
||||
const LABEL_W: f32 = 64.0;
|
||||
|
||||
/// The parameters of the add-on's Path Effects that pick one of some values, as (label, value).
|
||||
pub(crate) fn effect_choices(effect: &str, key: &str) -> Option<&'static [(&'static str, &'static str)]> {
|
||||
use vectorcraft_effects::addons::path as fx;
|
||||
match (effect, key) {
|
||||
(fx::ADD_ANCHORS, "target") => Some(&[("All", "all"), ("Curved", "curved"), ("Straight", "straight")]),
|
||||
(fx::ADD_ANCHORS, "spacing") => Some(&[("By Count", "count"), ("By Distance", "distance")]),
|
||||
(fx::EXTEND, "startShape" | "endShape") => Some(&[("Straight", "straight"), ("Arc", "arc")]),
|
||||
(fx::CLOSE, "mode") => Some(&[("Straight", "straight"), ("Smooth", "smooth")]),
|
||||
(fx::RETRACT, "handles") => Some(&[("All", "all"), ("Shorter Than Length", "shorter")]),
|
||||
(fx::SPLIT, "anchors") => Some(&[("Corner", "corner"), ("Smooth", "smooth"), ("Both", "both")]),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Object › Plug-ins › Path Precision Tools.
|
||||
pub fn menu() -> Vec<Item> {
|
||||
let tool = |label, id: &str| Item::Cmd(label, "tool.select", json!({ "tool": id }));
|
||||
|
||||
@@ -161,3 +161,51 @@ fn the_corner_section_shows_the_tools_values_and_applies_them_to_the_selection()
|
||||
assert_eq!(app.session.doc().unwrap().doc.node(id).unwrap().path_data().unwrap().anchor_count(), 6);
|
||||
assert!(painted_text(&mut app, show).contains("Corners selected: 2"), "still selected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effect_path_effects_lists_the_live_effects_and_their_dialogs_pick_from_their_values() {
|
||||
let mut app = app();
|
||||
let entries = menus::menu_entries(&app);
|
||||
let at = |label: &str| entries.iter().find(|e| e.path == ["Effect", "Path Effects"] && e.label == label);
|
||||
for label in [
|
||||
"Dashes to Geometry…",
|
||||
"Add Anchors…",
|
||||
"Extend Path…",
|
||||
"Close Path…",
|
||||
"Smooth Anchors…",
|
||||
"Retract Handles…",
|
||||
"Simplify Anchors…",
|
||||
"Split at Anchors…",
|
||||
] {
|
||||
assert_eq!(at(label).expect(label).command.as_deref(), Some("effect.dialog"), "{label}");
|
||||
}
|
||||
// No options: applied at once.
|
||||
assert_eq!(at("Reverse Direction").expect("listed").command.as_deref(), Some("effect.apply"));
|
||||
assert_eq!(entries.iter().filter(|e| e.path == ["Effect", "Path Effects"]).count(), 9);
|
||||
// The dialog opens with the effect's parameters, each choice on a list.
|
||||
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 40, "height": 40})).unwrap();
|
||||
menus::invoke(&mut app, "effect.dialog", json!({"effect": "addon.path.addAnchors"}));
|
||||
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some("effect"));
|
||||
let text = painted_text(&mut app, |app, ui| crate::dialogs::show(app, ui.ctx()));
|
||||
assert!(text.contains("Add Anchors") && text.contains("Max Count:") && text.contains("All"), "{text}");
|
||||
app.ui.dialog = None;
|
||||
let _ = app.session.cancel_interaction();
|
||||
menus::invoke(&mut app, "effect.dialog", json!({"effect": "addon.path.dashes"}));
|
||||
let text = painted_text(&mut app, |app, ui| crate::dialogs::show(app, ui.ctx()));
|
||||
assert!(text.contains("Dash 1:") && text.contains("Gap 5:") && text.contains("Align to Corners and Ends:"), "{text}");
|
||||
for (effect, key) in [
|
||||
("addon.path.addAnchors", "target"),
|
||||
("addon.path.addAnchors", "spacing"),
|
||||
("addon.path.extend", "startShape"),
|
||||
("addon.path.extend", "endShape"),
|
||||
("addon.path.close", "mode"),
|
||||
("addon.path.retract", "handles"),
|
||||
("addon.path.split", "anchors"),
|
||||
] {
|
||||
let doc = vectorcraft_effects::effect_info(effect).unwrap();
|
||||
let options = super::effect_choices(effect, key).expect(key);
|
||||
let default = doc.defaults[key].as_str().unwrap();
|
||||
assert!(options.iter().any(|(_, value)| *value == default), "{effect} {key}");
|
||||
assert!(options.iter().all(|(_, value)| doc.params.contains(&format!("\"{value}\""))), "{effect} {key}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ fn choices(effect: &str, key: &str) -> Option<form::Choices> {
|
||||
("stylize.dropShadow", "shade") => Some(SHADES),
|
||||
("path.offsetPath", "joins") => Some(&[("Miter", "miter"), ("Round", "round"), ("Bevel", "bevel")]),
|
||||
("distort.roughen" | "distort.zigZag", "points") => Some(&[("Smooth", "smooth"), ("Corner", "corner")]),
|
||||
_ => None,
|
||||
_ => crate::addons::pathtools::effect_choices(effect, key),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -647,6 +647,17 @@ pub(crate) fn humanize(k: &str) -> String {
|
||||
"Sy" => "Vertical %".into(),
|
||||
"Radius1" => "Radius 1".into(),
|
||||
"Radius2" => "Radius 2".into(),
|
||||
"Dash1" => "Dash 1:".into(),
|
||||
"Dash2" => "Dash 2:".into(),
|
||||
"Dash3" => "Dash 3:".into(),
|
||||
"Dash4" => "Dash 4:".into(),
|
||||
"Dash5" => "Dash 5:".into(),
|
||||
"Gap1" => "Gap 1:".into(),
|
||||
"Gap2" => "Gap 2:".into(),
|
||||
"Gap3" => "Gap 3:".into(),
|
||||
"Gap4" => "Gap 4:".into(),
|
||||
"Gap5" => "Gap 5:".into(),
|
||||
"Align Corners" => "Align to Corners and Ends:".into(),
|
||||
"Max Radius" => "Max. Radius:".into(),
|
||||
"Blur Mode" => "Mode:".into(),
|
||||
"Channel1" => "Channel 1:".into(),
|
||||
|
||||
@@ -5828,3 +5828,44 @@ breakText Glyphs Глифи
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` трябва да е lines, paragraphs, words или glyphs, а не {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` трябва да е "line" или "paragraph", а не {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` трябва да е "left", "center" или "right", а не {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Mode Режим
|
||||
Path Effects Ефекти на пътя
|
||||
Reverse Direction Обръщане на посоката
|
||||
Align to Corners and Ends Подравняване към ъглите и краищата
|
||||
Count Брой
|
||||
Max Count Максимален брой
|
||||
Start Length Дължина в началото
|
||||
Start Percent Процент в началото
|
||||
Start Shape Форма в началото
|
||||
End Length Дължина в края
|
||||
End Percent Процент в края
|
||||
End Shape Форма в края
|
||||
By Count По брой
|
||||
By Distance По разстояние
|
||||
Both И двете
|
||||
Shorter Than Length По-къси от дължината
|
||||
Dashes to Geometry Тирета в геометрия
|
||||
Dashes to Geometry… Тирета в геометрия…
|
||||
Add Anchors Добавяне на опорни точки
|
||||
Add Anchors… Добавяне на опорни точки…
|
||||
Smooth Anchors Заглаждане на опорните точки
|
||||
Smooth Anchors… Заглаждане на опорните точки…
|
||||
Simplify Anchors Опростяване на опорните точки
|
||||
Simplify Anchors… Опростяване на опорните точки…
|
||||
Split at Anchors Разделяне в опорните точки
|
||||
Split at Anchors… Разделяне в опорните точки…
|
||||
Extend Path… Удължаване на път…
|
||||
Close Path… Затваряне на пътя…
|
||||
Retract Handles… Прибиране на дръжките…
|
||||
Dash 1 Тире 1
|
||||
Gap 1 Разстояние 1
|
||||
Dash 2 Тире 2
|
||||
Gap 2 Разстояние 2
|
||||
Dash 3 Тире 3
|
||||
Gap 3 Разстояние 3
|
||||
Dash 4 Тире 4
|
||||
Gap 4 Разстояние 4
|
||||
Dash 5 Тире 5
|
||||
Gap 5 Разстояние 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5852,6 +5852,48 @@ swatch Yellow Green Žuto-zelena
|
||||
Position: Položaj:
|
||||
Font Size: Veličina fonta:
|
||||
shape Open Otvoren
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Retract Handles Uvuci ručke
|
||||
Curved Zakrivljeno
|
||||
Path Effects Efekti putanje
|
||||
Reverse Direction Obrni smjer
|
||||
Align to Corners and Ends Poravnaj s uglovima i krajevima
|
||||
Count Broj
|
||||
Max Count Najveći broj
|
||||
Start Length Dužina na početku
|
||||
Start Percent Postotak na početku
|
||||
Start Shape Oblik na početku
|
||||
End Length Dužina na kraju
|
||||
End Percent Postotak na kraju
|
||||
End Shape Oblik na kraju
|
||||
By Count Po broju
|
||||
By Distance Po udaljenosti
|
||||
Both Oba
|
||||
Shorter Than Length Kraće od dužine
|
||||
Dashes to Geometry Crtice u geometriju
|
||||
Dashes to Geometry… Crtice u geometriju…
|
||||
Add Anchors Dodaj sidra
|
||||
Add Anchors… Dodaj sidra…
|
||||
Smooth Anchors Zagladi sidra
|
||||
Smooth Anchors… Zagladi sidra…
|
||||
Simplify Anchors Pojednostavi sidra
|
||||
Simplify Anchors… Pojednostavi sidra…
|
||||
Split at Anchors Razdvoji na sidrima
|
||||
Split at Anchors… Razdvoji na sidrima…
|
||||
Extend Path… Produži putanju…
|
||||
Close Path… Zatvori putanju…
|
||||
Retract Handles… Uvuci ručke…
|
||||
Dash 1 Crtica 1
|
||||
Gap 1 Razmak 1
|
||||
Dash 2 Crtica 2
|
||||
Gap 2 Razmak 2
|
||||
Dash 3 Crtica 3
|
||||
Gap 3 Razmak 3
|
||||
Dash 4 Crtica 4
|
||||
Gap 4 Razmak 4
|
||||
Dash 5 Crtica 5
|
||||
Gap 5 Razmak 5
|
||||
# Path Precision Tools add-on: the Corner Tool and Slide Anchor Along Path.
|
||||
Corner Tool Alat za uglove
|
||||
Slide Anchor Along Path Klizanje sidra po putanji
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5929,3 +5929,43 @@ breakText Glyphs Glifs
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` ha de ser lines, paragraphs, words o glyphs, no {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` ha de ser "line" o "paragraph", no {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` ha de ser "left", "center" o "right", no {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Efectes de traçat
|
||||
Reverse Direction Inverteix la direcció
|
||||
Align to Corners and Ends Alinea a les cantonades i als extrems
|
||||
Count Nombre
|
||||
Max Count Nombre màxim
|
||||
Start Length Longitud a l’inici
|
||||
Start Percent Percentatge a l’inici
|
||||
Start Shape Forma a l’inici
|
||||
End Length Longitud al final
|
||||
End Percent Percentatge al final
|
||||
End Shape Forma al final
|
||||
By Count Per nombre
|
||||
By Distance Per distància
|
||||
Both Tots dos
|
||||
Shorter Than Length Més curts que la longitud
|
||||
Dashes to Geometry Traços a geometria
|
||||
Dashes to Geometry… Traços a geometria…
|
||||
Add Anchors Afegeix punts d’ancoratge
|
||||
Add Anchors… Afegeix punts d’ancoratge…
|
||||
Smooth Anchors Suavitza els punts d’ancoratge
|
||||
Smooth Anchors… Suavitza els punts d’ancoratge…
|
||||
Simplify Anchors Simplifica els punts d’ancoratge
|
||||
Simplify Anchors… Simplifica els punts d’ancoratge…
|
||||
Split at Anchors Divideix als punts d’ancoratge
|
||||
Split at Anchors… Divideix als punts d’ancoratge…
|
||||
Extend Path… Allarga el traçat…
|
||||
Close Path… Tanca el traçat…
|
||||
Retract Handles… Retrau les nanses…
|
||||
Dash 1 Guió 1
|
||||
Gap 1 Separació 1
|
||||
Dash 2 Guió 2
|
||||
Gap 2 Separació 2
|
||||
Dash 3 Guió 3
|
||||
Gap 3 Separació 3
|
||||
Dash 4 Guió 4
|
||||
Gap 4 Separació 4
|
||||
Dash 5 Guió 5
|
||||
Gap 5 Separació 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5938,3 +5938,43 @@ breakText Glyphs Glyfy
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` musí být lines, paragraphs, words nebo glyphs, ne {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` musí být "line" nebo "paragraph", ne {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` musí být "left", "center" nebo "right", ne {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Efekty cesty
|
||||
Reverse Direction Obrátit směr
|
||||
Align to Corners and Ends Zarovnat k rohům a koncům
|
||||
Count Počet
|
||||
Max Count Maximální počet
|
||||
Start Length Délka na začátku
|
||||
Start Percent Procenta na začátku
|
||||
Start Shape Tvar na začátku
|
||||
End Length Délka na konci
|
||||
End Percent Procenta na konci
|
||||
End Shape Tvar na konci
|
||||
By Count Podle počtu
|
||||
By Distance Podle vzdálenosti
|
||||
Both Obojí
|
||||
Shorter Than Length Kratší než délka
|
||||
Dashes to Geometry Čárky na geometrii
|
||||
Dashes to Geometry… Čárky na geometrii…
|
||||
Add Anchors Přidat kotevní body
|
||||
Add Anchors… Přidat kotevní body…
|
||||
Smooth Anchors Vyhladit kotevní body
|
||||
Smooth Anchors… Vyhladit kotevní body…
|
||||
Simplify Anchors Zjednodušit kotevní body
|
||||
Simplify Anchors… Zjednodušit kotevní body…
|
||||
Split at Anchors Rozdělit v kotevních bodech
|
||||
Split at Anchors… Rozdělit v kotevních bodech…
|
||||
Extend Path… Prodloužit cestu…
|
||||
Close Path… Uzavřít cestu…
|
||||
Retract Handles… Zasunout úchyty…
|
||||
Dash 1 Čárka 1
|
||||
Gap 1 Mezera 1
|
||||
Dash 2 Čárka 2
|
||||
Gap 2 Mezera 2
|
||||
Dash 3 Čárka 3
|
||||
Gap 3 Mezera 3
|
||||
Dash 4 Čárka 4
|
||||
Gap 4 Mezera 4
|
||||
Dash 5 Čárka 5
|
||||
Gap 5 Mezera 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5843,3 +5843,43 @@ breakText Glyphs Glyffer
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` skal være lines, paragraphs, words eller glyphs, ikke {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` skal være "line" eller "paragraph", ikke {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` skal være "left", "center" eller "right", ikke {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Kurveeffekter
|
||||
Reverse Direction Omvend retning
|
||||
Align to Corners and Ends Juster til hjørner og ender
|
||||
Count Antal
|
||||
Max Count Største antal
|
||||
Start Length Startlængde
|
||||
Start Percent Startprocent
|
||||
Start Shape Startform
|
||||
End Length Slutlængde
|
||||
End Percent Slutprocent
|
||||
End Shape Slutform
|
||||
By Count Efter antal
|
||||
By Distance Efter afstand
|
||||
Both Begge
|
||||
Shorter Than Length Kortere end længden
|
||||
Dashes to Geometry Streger til geometri
|
||||
Dashes to Geometry… Streger til geometri…
|
||||
Add Anchors Tilføj ankerpunkter
|
||||
Add Anchors… Tilføj ankerpunkter…
|
||||
Smooth Anchors Udglat ankerpunkter
|
||||
Smooth Anchors… Udglat ankerpunkter…
|
||||
Simplify Anchors Forenkl ankerpunkter
|
||||
Simplify Anchors… Forenkl ankerpunkter…
|
||||
Split at Anchors Opdel ved ankerpunkter
|
||||
Split at Anchors… Opdel ved ankerpunkter…
|
||||
Extend Path… Forlæng sti…
|
||||
Close Path… Luk sti…
|
||||
Retract Handles… Træk håndtag ind…
|
||||
Dash 1 Streg 1
|
||||
Gap 1 Mellemrum 1
|
||||
Dash 2 Streg 2
|
||||
Gap 2 Mellemrum 2
|
||||
Dash 3 Streg 3
|
||||
Gap 3 Mellemrum 3
|
||||
Dash 4 Streg 4
|
||||
Gap 4 Mellemrum 4
|
||||
Dash 5 Streg 5
|
||||
Gap 5 Mellemrum 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5523,3 +5523,44 @@ breakText Glyphs Glyphen
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` muss lines, paragraphs, words oder glyphs sein, nicht {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` muss "line" oder "paragraph" sein, nicht {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` muss "left", "center" oder "right" sein, nicht {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Pfadeffekte
|
||||
Reverse Direction Richtung umkehren
|
||||
Align to Corners and Ends An Ecken und Enden ausrichten
|
||||
Count Anzahl
|
||||
Max Count Höchstanzahl
|
||||
Start Length Startlänge
|
||||
Start Percent Start in Prozent
|
||||
Start Shape Startform
|
||||
End Length Endlänge
|
||||
End Percent Ende in Prozent
|
||||
End Shape Endform
|
||||
By Count Nach Anzahl
|
||||
By Distance Nach Abstand
|
||||
Both Beide
|
||||
Shorter Than Length Kürzer als Länge
|
||||
Anchors Ankerpunkte
|
||||
Dashes to Geometry Striche in Geometrie
|
||||
Dashes to Geometry… Striche in Geometrie…
|
||||
Add Anchors Ankerpunkte hinzufügen
|
||||
Add Anchors… Ankerpunkte hinzufügen…
|
||||
Smooth Anchors Ankerpunkte glätten
|
||||
Smooth Anchors… Ankerpunkte glätten…
|
||||
Simplify Anchors Ankerpunkte vereinfachen
|
||||
Simplify Anchors… Ankerpunkte vereinfachen…
|
||||
Split at Anchors An Ankerpunkten teilen
|
||||
Split at Anchors… An Ankerpunkten teilen…
|
||||
Extend Path… Pfad verlängern…
|
||||
Close Path… Pfad schließen…
|
||||
Retract Handles… Griffe einziehen…
|
||||
Dash 1 Strich 1
|
||||
Gap 1 Lücke 1
|
||||
Dash 2 Strich 2
|
||||
Gap 2 Lücke 2
|
||||
Dash 3 Strich 3
|
||||
Gap 3 Lücke 3
|
||||
Dash 4 Strich 4
|
||||
Gap 4 Lücke 4
|
||||
Dash 5 Strich 5
|
||||
Gap 5 Lücke 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5890,3 +5890,44 @@ breakText Glyphs Γλύφοι
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} το `by` πρέπει να είναι lines, paragraphs, words ή glyphs, όχι {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} το `kind` πρέπει να είναι "line" ή "paragraph", όχι {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} το `align` πρέπει να είναι "left", "center" ή "right", όχι {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Mode Λειτουργία
|
||||
Path Effects Εφέ διαδρομής
|
||||
Reverse Direction Αντιστροφή κατεύθυνσης
|
||||
Align to Corners and Ends Στοίχιση σε γωνίες και άκρα
|
||||
Count Πλήθος
|
||||
Max Count Μέγιστο πλήθος
|
||||
Start Length Μήκος αρχής
|
||||
Start Percent Ποσοστό αρχής
|
||||
Start Shape Σχήμα αρχής
|
||||
End Length Μήκος τέλους
|
||||
End Percent Ποσοστό τέλους
|
||||
End Shape Σχήμα τέλους
|
||||
By Count Κατά πλήθος
|
||||
By Distance Κατά απόσταση
|
||||
Both Και τα δύο
|
||||
Shorter Than Length Μικρότερες από το μήκος
|
||||
Dashes to Geometry Παύλες σε γεωμετρία
|
||||
Dashes to Geometry… Παύλες σε γεωμετρία…
|
||||
Add Anchors Προσθήκη σημείων αγκύρωσης
|
||||
Add Anchors… Προσθήκη σημείων αγκύρωσης…
|
||||
Smooth Anchors Εξομάλυνση σημείων αγκύρωσης
|
||||
Smooth Anchors… Εξομάλυνση σημείων αγκύρωσης…
|
||||
Simplify Anchors Απλοποίηση σημείων αγκύρωσης
|
||||
Simplify Anchors… Απλοποίηση σημείων αγκύρωσης…
|
||||
Split at Anchors Διαχωρισμός στα σημεία αγκύρωσης
|
||||
Split at Anchors… Διαχωρισμός στα σημεία αγκύρωσης…
|
||||
Extend Path… Επέκταση διαδρομής…
|
||||
Close Path… Κλείσιμο διαδρομής…
|
||||
Retract Handles… Σύμπτυξη λαβών…
|
||||
Dash 1 Παύλα 1
|
||||
Gap 1 Διάκενο 1
|
||||
Dash 2 Παύλα 2
|
||||
Gap 2 Διάκενο 2
|
||||
Dash 3 Παύλα 3
|
||||
Gap 3 Διάκενο 3
|
||||
Dash 4 Παύλα 4
|
||||
Gap 4 Διάκενο 4
|
||||
Dash 5 Παύλα 5
|
||||
Gap 5 Διάκενο 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 164 and column 5.
|
@@ -5881,3 +5881,43 @@ breakText Glyphs Glifos
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` debe ser lines, paragraphs, words o glyphs, no {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` debe ser "line" o "paragraph", no {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` debe ser "left", "center" o "right", no {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Efectos de trazado
|
||||
Reverse Direction Invertir dirección
|
||||
Align to Corners and Ends Alinear con esquinas y extremos
|
||||
Count Cantidad
|
||||
Max Count Cantidad máxima
|
||||
Start Length Longitud inicial
|
||||
Start Percent Porcentaje inicial
|
||||
Start Shape Forma inicial
|
||||
End Length Longitud final
|
||||
End Percent Porcentaje final
|
||||
End Shape Forma final
|
||||
By Count Por cantidad
|
||||
By Distance Por distancia
|
||||
Both Ambos
|
||||
Shorter Than Length Más cortos que la longitud
|
||||
Dashes to Geometry Guiones a geometría
|
||||
Dashes to Geometry… Guiones a geometría…
|
||||
Add Anchors Agregar puntos de ancla
|
||||
Add Anchors… Agregar puntos de ancla…
|
||||
Smooth Anchors Suavizar puntos de ancla
|
||||
Smooth Anchors… Suavizar puntos de ancla…
|
||||
Simplify Anchors Simplificar puntos de ancla
|
||||
Simplify Anchors… Simplificar puntos de ancla…
|
||||
Split at Anchors Dividir en puntos de ancla
|
||||
Split at Anchors… Dividir en puntos de ancla…
|
||||
Extend Path… Prolongar trazado…
|
||||
Close Path… Cerrar trazado…
|
||||
Retract Handles… Retraer manejadores…
|
||||
Dash 1 Guion 1
|
||||
Gap 1 Hueco 1
|
||||
Dash 2 Guion 2
|
||||
Gap 2 Hueco 2
|
||||
Dash 3 Guion 3
|
||||
Gap 3 Hueco 3
|
||||
Dash 4 Guion 4
|
||||
Gap 4 Hueco 4
|
||||
Dash 5 Guion 5
|
||||
Gap 5 Hueco 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5969,3 +5969,43 @@ breakText Glyphs Merkit
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` on oltava lines, paragraphs, words tai glyphs, ei {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` on oltava "line" tai "paragraph", ei {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` on oltava "left", "center" tai "right", ei {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Polkutehosteet
|
||||
Reverse Direction Käännä suunta
|
||||
Align to Corners and Ends Tasaa kulmiin ja päihin
|
||||
Count Määrä
|
||||
Max Count Enimmäismäärä
|
||||
Start Length Alun pituus
|
||||
Start Percent Alun prosentti
|
||||
Start Shape Alun muoto
|
||||
End Length Lopun pituus
|
||||
End Percent Lopun prosentti
|
||||
End Shape Lopun muoto
|
||||
By Count Määrän mukaan
|
||||
By Distance Etäisyyden mukaan
|
||||
Both Molemmat
|
||||
Shorter Than Length Pituutta lyhyemmät
|
||||
Dashes to Geometry Katkoviivat geometriaksi
|
||||
Dashes to Geometry… Katkoviivat geometriaksi…
|
||||
Add Anchors Lisää ankkuripisteitä
|
||||
Add Anchors… Lisää ankkuripisteitä…
|
||||
Smooth Anchors Pyöristä ankkuripisteet
|
||||
Smooth Anchors… Pyöristä ankkuripisteet…
|
||||
Simplify Anchors Yksinkertaista ankkuripisteet
|
||||
Simplify Anchors… Yksinkertaista ankkuripisteet…
|
||||
Split at Anchors Jaa ankkuripisteistä
|
||||
Split at Anchors… Jaa ankkuripisteistä…
|
||||
Extend Path… Jatka polkua…
|
||||
Close Path… Sulje polku…
|
||||
Retract Handles… Vedä kahvat sisään…
|
||||
Dash 1 Viiva 1
|
||||
Gap 1 Väli 1
|
||||
Dash 2 Viiva 2
|
||||
Gap 2 Väli 2
|
||||
Dash 3 Viiva 3
|
||||
Gap 3 Väli 3
|
||||
Dash 4 Viiva 4
|
||||
Gap 4 Väli 4
|
||||
Dash 5 Viiva 5
|
||||
Gap 5 Väli 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5937,3 +5937,43 @@ breakText Glyphs Glyphes
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` doit valoir lines, paragraphs, words ou glyphs, et non {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` doit valoir "line" ou "paragraph", et non {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` doit valoir "left", "center" ou "right", et non {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Effets de tracé
|
||||
Reverse Direction Inverser le sens
|
||||
Align to Corners and Ends Aligner sur les angles et extrémités
|
||||
Count Nombre
|
||||
Max Count Nombre maximal
|
||||
Start Length Longueur au début
|
||||
Start Percent Pourcentage au début
|
||||
Start Shape Forme au début
|
||||
End Length Longueur à la fin
|
||||
End Percent Pourcentage à la fin
|
||||
End Shape Forme à la fin
|
||||
By Count Par nombre
|
||||
By Distance Par distance
|
||||
Both Les deux
|
||||
Shorter Than Length Plus courtes que la longueur
|
||||
Dashes to Geometry Tirets en géométrie
|
||||
Dashes to Geometry… Tirets en géométrie…
|
||||
Add Anchors Ajouter des points d’ancrage
|
||||
Add Anchors… Ajouter des points d’ancrage…
|
||||
Smooth Anchors Lisser les points d’ancrage
|
||||
Smooth Anchors… Lisser les points d’ancrage…
|
||||
Simplify Anchors Simplifier les points d’ancrage
|
||||
Simplify Anchors… Simplifier les points d’ancrage…
|
||||
Split at Anchors Scinder aux points d’ancrage
|
||||
Split at Anchors… Scinder aux points d’ancrage…
|
||||
Extend Path… Prolonger le tracé…
|
||||
Close Path… Fermer le tracé…
|
||||
Retract Handles… Rétracter les poignées…
|
||||
Dash 1 Tiret 1
|
||||
Gap 1 Écart 1
|
||||
Dash 2 Tiret 2
|
||||
Gap 2 Écart 2
|
||||
Dash 3 Tiret 3
|
||||
Gap 3 Écart 3
|
||||
Dash 4 Tiret 4
|
||||
Gap 4 Écart 4
|
||||
Dash 5 Tiret 5
|
||||
Gap 5 Écart 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5925,3 +5925,43 @@ breakText Glyphs Glifák
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} a `by` értéke lines, paragraphs, words vagy glyphs lehet, nem {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} a `kind` értéke "line" vagy "paragraph" lehet, nem {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} az `align` értéke "left", "center" vagy "right" lehet, nem {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Útvonaleffektusok
|
||||
Reverse Direction Irány megfordítása
|
||||
Align to Corners and Ends Igazítás a sarkokhoz és végekhez
|
||||
Count Darabszám
|
||||
Max Count Legnagyobb darabszám
|
||||
Start Length Kezdő hossz
|
||||
Start Percent Kezdő százalék
|
||||
Start Shape Kezdő alak
|
||||
End Length Záró hossz
|
||||
End Percent Záró százalék
|
||||
End Shape Záró alak
|
||||
By Count Darabszám szerint
|
||||
By Distance Távolság szerint
|
||||
Both Mindkettő
|
||||
Shorter Than Length A hossznál rövidebbek
|
||||
Dashes to Geometry Vonalak geometriává
|
||||
Dashes to Geometry… Vonalak geometriává…
|
||||
Add Anchors Horgonypontok hozzáadása
|
||||
Add Anchors… Horgonypontok hozzáadása…
|
||||
Smooth Anchors Horgonypontok simítása
|
||||
Smooth Anchors… Horgonypontok simítása…
|
||||
Simplify Anchors Horgonypontok egyszerűsítése
|
||||
Simplify Anchors… Horgonypontok egyszerűsítése…
|
||||
Split at Anchors Felosztás a horgonypontoknál
|
||||
Split at Anchors… Felosztás a horgonypontoknál…
|
||||
Extend Path… Útvonal meghosszabbítása…
|
||||
Close Path… Útvonal lezárása…
|
||||
Retract Handles… Fogantyúk behúzása…
|
||||
Dash 1 Vonal 1
|
||||
Gap 1 Hézag 1
|
||||
Dash 2 Vonal 2
|
||||
Gap 2 Hézag 2
|
||||
Dash 3 Vonal 3
|
||||
Gap 3 Hézag 3
|
||||
Dash 4 Vonal 4
|
||||
Gap 4 Hézag 4
|
||||
Dash 5 Vonal 5
|
||||
Gap 5 Hézag 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5843,3 +5843,43 @@ breakText Glyphs Glif
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` harus lines, paragraphs, words, atau glyphs, bukan {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` harus "line" atau "paragraph", bukan {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` harus "left", "center", atau "right", bukan {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Efek jalur
|
||||
Reverse Direction Balikkan arah
|
||||
Align to Corners and Ends Sejajarkan ke sudut dan ujung
|
||||
Count Jumlah
|
||||
Max Count Jumlah maksimum
|
||||
Start Length Panjang awal
|
||||
Start Percent Persen awal
|
||||
Start Shape Bentuk awal
|
||||
End Length Panjang akhir
|
||||
End Percent Persen akhir
|
||||
End Shape Bentuk akhir
|
||||
By Count Menurut jumlah
|
||||
By Distance Menurut jarak
|
||||
Both Keduanya
|
||||
Shorter Than Length Lebih pendek dari panjang
|
||||
Dashes to Geometry Garis putus ke geometri
|
||||
Dashes to Geometry… Garis putus ke geometri…
|
||||
Add Anchors Tambahkan titik jangkar
|
||||
Add Anchors… Tambahkan titik jangkar…
|
||||
Smooth Anchors Haluskan titik jangkar
|
||||
Smooth Anchors… Haluskan titik jangkar…
|
||||
Simplify Anchors Sederhanakan titik jangkar
|
||||
Simplify Anchors… Sederhanakan titik jangkar…
|
||||
Split at Anchors Pisahkan di titik jangkar
|
||||
Split at Anchors… Pisahkan di titik jangkar…
|
||||
Extend Path… Perpanjang jalur…
|
||||
Close Path… Tutup jalur…
|
||||
Retract Handles… Tarik masuk pegangan…
|
||||
Dash 1 Garis 1
|
||||
Gap 1 Jarak 1
|
||||
Dash 2 Garis 2
|
||||
Gap 2 Jarak 2
|
||||
Dash 3 Garis 3
|
||||
Gap 3 Jarak 3
|
||||
Dash 4 Garis 4
|
||||
Gap 4 Jarak 4
|
||||
Dash 5 Garis 5
|
||||
Gap 5 Jarak 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2169 and column 5.
|
@@ -5557,3 +5557,44 @@ breakText Glyphs Glifi
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` deve essere lines, paragraphs, words o glyphs, non {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` deve essere "line" o "paragraph", non {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` deve essere "left", "center" o "right", non {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Effetti tracciato
|
||||
Reverse Direction Inverti direzione
|
||||
Align to Corners and Ends Allinea ad angoli ed estremità
|
||||
Count Numero
|
||||
Max Count Numero massimo
|
||||
Start Length Lunghezza iniziale
|
||||
Start Percent Percentuale iniziale
|
||||
Start Shape Forma iniziale
|
||||
End Length Lunghezza finale
|
||||
End Percent Percentuale finale
|
||||
End Shape Forma finale
|
||||
By Count Per numero
|
||||
By Distance Per distanza
|
||||
Both Entrambi
|
||||
Shorter Than Length Più corte della lunghezza
|
||||
Anchors Punti di ancoraggio
|
||||
Dashes to Geometry Tratti in geometria
|
||||
Dashes to Geometry… Tratti in geometria…
|
||||
Add Anchors Aggiungi punti di ancoraggio
|
||||
Add Anchors… Aggiungi punti di ancoraggio…
|
||||
Smooth Anchors Smussa punti di ancoraggio
|
||||
Smooth Anchors… Smussa punti di ancoraggio…
|
||||
Simplify Anchors Semplifica punti di ancoraggio
|
||||
Simplify Anchors… Semplifica punti di ancoraggio…
|
||||
Split at Anchors Dividi ai punti di ancoraggio
|
||||
Split at Anchors… Dividi ai punti di ancoraggio…
|
||||
Extend Path… Estendi tracciato…
|
||||
Close Path… Chiudi tracciato…
|
||||
Retract Handles… Ritira maniglie…
|
||||
Dash 1 Tratto 1
|
||||
Gap 1 Spazio 1
|
||||
Dash 2 Tratto 2
|
||||
Gap 2 Spazio 2
|
||||
Dash 3 Tratto 3
|
||||
Gap 3 Spazio 3
|
||||
Dash 4 Tratto 4
|
||||
Gap 4 Spazio 4
|
||||
Dash 5 Tratto 5
|
||||
Gap 5 Spazio 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5626,3 +5626,44 @@ breakText Glyphs 字形
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` は lines、paragraphs、words、glyphs のいずれかにしてください({other} は使用できません)
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` は "line" または "paragraph" にしてください({other} は使用できません)
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` は "left"、"center"、"right" のいずれかにしてください({other} は使用できません)
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects パス効果
|
||||
Reverse Direction 方向を反転
|
||||
Align to Corners and Ends コーナーと先端に整列
|
||||
Count 個数
|
||||
Max Count 最大個数
|
||||
Start Length 始点の長さ
|
||||
Start Percent 始点のパーセント
|
||||
Start Shape 始点の形状
|
||||
End Length 終点の長さ
|
||||
End Percent 終点のパーセント
|
||||
End Shape 終点の形状
|
||||
By Count 個数で指定
|
||||
By Distance 距離で指定
|
||||
Both 両方
|
||||
Shorter Than Length 長さより短いもの
|
||||
Anchors アンカーポイント
|
||||
Dashes to Geometry 破線をジオメトリに
|
||||
Dashes to Geometry… 破線をジオメトリに…
|
||||
Add Anchors アンカーポイントを追加
|
||||
Add Anchors… アンカーポイントを追加…
|
||||
Smooth Anchors アンカーポイントをスムーズに
|
||||
Smooth Anchors… アンカーポイントをスムーズに…
|
||||
Simplify Anchors アンカーポイントを単純化
|
||||
Simplify Anchors… アンカーポイントを単純化…
|
||||
Split at Anchors アンカーポイントで分割
|
||||
Split at Anchors… アンカーポイントで分割…
|
||||
Extend Path… パスを延長…
|
||||
Close Path… パスを閉じる…
|
||||
Retract Handles… ハンドルを収める…
|
||||
Dash 1 線分 1
|
||||
Gap 1 間隔 1
|
||||
Dash 2 線分 2
|
||||
Gap 2 間隔 2
|
||||
Dash 3 線分 3
|
||||
Gap 3 間隔 3
|
||||
Dash 4 線分 4
|
||||
Gap 4 間隔 4
|
||||
Dash 5 線分 5
|
||||
Gap 5 間隔 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5928,3 +5928,43 @@ breakText Glyphs გლიფები
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` უნდა იყოს lines, paragraphs, words ან glyphs და არა {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` უნდა იყოს "line" ან "paragraph" და არა {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` უნდა იყოს "left", "center" ან "right" და არა {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects ტრაექტორიის ეფექტები
|
||||
Reverse Direction მიმართულების შებრუნება
|
||||
Align to Corners and Ends კუთხეებთან და ბოლოებთან სწორება
|
||||
Count რაოდენობა
|
||||
Max Count მაქსიმალური რაოდენობა
|
||||
Start Length საწყისი სიგრძე
|
||||
Start Percent საწყისი პროცენტი
|
||||
Start Shape საწყისი ფორმა
|
||||
End Length საბოლოო სიგრძე
|
||||
End Percent საბოლოო პროცენტი
|
||||
End Shape საბოლოო ფორმა
|
||||
By Count რაოდენობით
|
||||
By Distance მანძილით
|
||||
Both ორივე
|
||||
Shorter Than Length სიგრძეზე მოკლე
|
||||
Dashes to Geometry ტირეები გეომეტრიად
|
||||
Dashes to Geometry… ტირეები გეომეტრიად…
|
||||
Add Anchors საყრდენი წერტილების დამატება
|
||||
Add Anchors… საყრდენი წერტილების დამატება…
|
||||
Smooth Anchors საყრდენი წერტილების მოგლუვება
|
||||
Smooth Anchors… საყრდენი წერტილების მოგლუვება…
|
||||
Simplify Anchors საყრდენი წერტილების გამარტივება
|
||||
Simplify Anchors… საყრდენი წერტილების გამარტივება…
|
||||
Split at Anchors დაყოფა საყრდენ წერტილებზე
|
||||
Split at Anchors… დაყოფა საყრდენ წერტილებზე…
|
||||
Extend Path… ტრაექტორიის გაგრძელება…
|
||||
Close Path… ტრაექტორიის შეკვრა…
|
||||
Retract Handles… სახელურების შეკეცვა…
|
||||
Dash 1 ტირე 1
|
||||
Gap 1 შუალედი 1
|
||||
Dash 2 ტირე 2
|
||||
Gap 2 შუალედი 2
|
||||
Dash 3 ტირე 3
|
||||
Gap 3 შუალედი 3
|
||||
Dash 4 ტირე 4
|
||||
Gap 4 შუალედი 4
|
||||
Dash 5 ტირე 5
|
||||
Gap 5 შუალედი 5
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -5520,3 +5520,44 @@ breakText Glyphs 글리프
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by`는 lines, paragraphs, words 또는 glyphs여야 합니다({other}는 사용할 수 없음)
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind`는 "line" 또는 "paragraph"여야 합니다({other}는 사용할 수 없음)
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align`은 "left", "center" 또는 "right"여야 합니다({other}는 사용할 수 없음)
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects 패스 효과
|
||||
Reverse Direction 방향 반전
|
||||
Align to Corners and Ends 모퉁이와 끝에 정렬
|
||||
Count 개수
|
||||
Max Count 최대 개수
|
||||
Start Length 시작 길이
|
||||
Start Percent 시작 퍼센트
|
||||
Start Shape 시작 모양
|
||||
End Length 끝 길이
|
||||
End Percent 끝 퍼센트
|
||||
End Shape 끝 모양
|
||||
By Count 개수 기준
|
||||
By Distance 거리 기준
|
||||
Both 둘 다
|
||||
Shorter Than Length 길이보다 짧은 핸들
|
||||
Anchors 앵커 포인트
|
||||
Dashes to Geometry 대시를 도형으로
|
||||
Dashes to Geometry… 대시를 도형으로…
|
||||
Add Anchors 앵커 포인트 추가
|
||||
Add Anchors… 앵커 포인트 추가…
|
||||
Smooth Anchors 앵커 포인트 매끄럽게
|
||||
Smooth Anchors… 앵커 포인트 매끄럽게…
|
||||
Simplify Anchors 앵커 포인트 단순화
|
||||
Simplify Anchors… 앵커 포인트 단순화…
|
||||
Split at Anchors 앵커 포인트에서 분할
|
||||
Split at Anchors… 앵커 포인트에서 분할…
|
||||
Extend Path… 패스 연장…
|
||||
Close Path… 패스 닫기…
|
||||
Retract Handles… 핸들 접기…
|
||||
Dash 1 대시 1
|
||||
Gap 1 간격 1
|
||||
Dash 2 대시 2
|
||||
Gap 2 간격 2
|
||||
Dash 3 대시 3
|
||||
Gap 3 간격 3
|
||||
Dash 4 대시 4
|
||||
Gap 4 간격 4
|
||||
Dash 5 대시 5
|
||||
Gap 5 간격 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5887,3 +5887,43 @@ breakText Glyphs Glyphs
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` moet lines, paragraphs, words of glyphs zijn, niet {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` moet "line" of "paragraph" zijn, niet {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` moet "left", "center" of "right" zijn, niet {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Padeffecten
|
||||
Reverse Direction Richting omkeren
|
||||
Align to Corners and Ends Uitlijnen op hoeken en uiteinden
|
||||
Count Aantal
|
||||
Max Count Maximumaantal
|
||||
Start Length Beginlengte
|
||||
Start Percent Beginpercentage
|
||||
Start Shape Beginvorm
|
||||
End Length Eindlengte
|
||||
End Percent Eindpercentage
|
||||
End Shape Eindvorm
|
||||
By Count Op aantal
|
||||
By Distance Op afstand
|
||||
Both Beide
|
||||
Shorter Than Length Korter dan lengte
|
||||
Dashes to Geometry Streepjes naar geometrie
|
||||
Dashes to Geometry… Streepjes naar geometrie…
|
||||
Add Anchors Ankerpunten toevoegen
|
||||
Add Anchors… Ankerpunten toevoegen…
|
||||
Smooth Anchors Ankerpunten gladmaken
|
||||
Smooth Anchors… Ankerpunten gladmaken…
|
||||
Simplify Anchors Ankerpunten vereenvoudigen
|
||||
Simplify Anchors… Ankerpunten vereenvoudigen…
|
||||
Split at Anchors Splitsen bij ankerpunten
|
||||
Split at Anchors… Splitsen bij ankerpunten…
|
||||
Extend Path… Pad verlengen…
|
||||
Close Path… Pad sluiten…
|
||||
Retract Handles… Grepen intrekken…
|
||||
Dash 1 Streepje 1
|
||||
Gap 1 Tussenruimte 1
|
||||
Dash 2 Streepje 2
|
||||
Gap 2 Tussenruimte 2
|
||||
Dash 3 Streepje 3
|
||||
Gap 3 Tussenruimte 3
|
||||
Dash 4 Streepje 4
|
||||
Gap 4 Tussenruimte 4
|
||||
Dash 5 Streepje 5
|
||||
Gap 5 Tussenruimte 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5793,3 +5793,44 @@ breakText Glyphs Glify
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` musi mieć wartość lines, paragraphs, words lub glyphs, a nie {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` musi mieć wartość "line" lub "paragraph", a nie {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` musi mieć wartość "left", "center" lub "right", a nie {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Efekty ścieżki
|
||||
Reverse Direction Odwróć kierunek
|
||||
Align to Corners and Ends Wyrównaj do narożników i końców
|
||||
Count Liczba
|
||||
Max Count Maksymalna liczba
|
||||
Start Length Długość na początku
|
||||
Start Percent Procent na początku
|
||||
Start Shape Kształt na początku
|
||||
End Length Długość na końcu
|
||||
End Percent Procent na końcu
|
||||
End Shape Kształt na końcu
|
||||
By Count Według liczby
|
||||
By Distance Według odległości
|
||||
Both Oba
|
||||
Shorter Than Length Krótsze niż długość
|
||||
Anchors Punkty kontrolne
|
||||
Dashes to Geometry Kreski jako geometria
|
||||
Dashes to Geometry… Kreski jako geometria…
|
||||
Add Anchors Dodaj punkty kontrolne
|
||||
Add Anchors… Dodaj punkty kontrolne…
|
||||
Smooth Anchors Wygładź punkty kontrolne
|
||||
Smooth Anchors… Wygładź punkty kontrolne…
|
||||
Simplify Anchors Uprość punkty kontrolne
|
||||
Simplify Anchors… Uprość punkty kontrolne…
|
||||
Split at Anchors Podziel w punktach kontrolnych
|
||||
Split at Anchors… Podziel w punktach kontrolnych…
|
||||
Extend Path… Przedłuż ścieżkę…
|
||||
Close Path… Zamknij ścieżkę…
|
||||
Retract Handles… Schowaj uchwyty…
|
||||
Dash 1 Kreska 1
|
||||
Gap 1 Odstęp 1
|
||||
Dash 2 Kreska 2
|
||||
Gap 2 Odstęp 2
|
||||
Dash 3 Kreska 3
|
||||
Gap 3 Odstęp 3
|
||||
Dash 4 Kreska 4
|
||||
Gap 4 Odstęp 4
|
||||
Dash 5 Kreska 5
|
||||
Gap 5 Odstęp 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5856,3 +5856,45 @@ breakText Glyphs Glifos
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` deve ser lines, paragraphs, words ou glyphs, não {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` deve ser "line" ou "paragraph", não {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` deve ser "left", "center" ou "right", não {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Distance Distância
|
||||
Mode Modo
|
||||
Path Effects Efeitos de Caminho
|
||||
Reverse Direction Inverter Direção
|
||||
Align to Corners and Ends Alinhar aos Cantos e Extremidades
|
||||
Count Quantidade
|
||||
Max Count Quantidade Máxima
|
||||
Start Length Comprimento Inicial
|
||||
Start Percent Porcentagem Inicial
|
||||
Start Shape Forma Inicial
|
||||
End Length Comprimento Final
|
||||
End Percent Porcentagem Final
|
||||
End Shape Forma Final
|
||||
By Count Por Quantidade
|
||||
By Distance Por Distância
|
||||
Both Ambos
|
||||
Shorter Than Length Mais Curtas que o Comprimento
|
||||
Dashes to Geometry Traços em Geometria
|
||||
Dashes to Geometry… Traços em Geometria…
|
||||
Add Anchors Adicionar Pontos de Ancoragem
|
||||
Add Anchors… Adicionar Pontos de Ancoragem…
|
||||
Smooth Anchors Suavizar Pontos de Ancoragem
|
||||
Smooth Anchors… Suavizar Pontos de Ancoragem…
|
||||
Simplify Anchors Simplificar Pontos de Ancoragem
|
||||
Simplify Anchors… Simplificar Pontos de Ancoragem…
|
||||
Split at Anchors Dividir nos Pontos de Ancoragem
|
||||
Split at Anchors… Dividir nos Pontos de Ancoragem…
|
||||
Extend Path… Estender Caminho…
|
||||
Close Path… Fechar Caminho…
|
||||
Retract Handles… Recolher Alças…
|
||||
Dash 1 Traço 1
|
||||
Gap 1 Lacuna 1
|
||||
Dash 2 Traço 2
|
||||
Gap 2 Lacuna 2
|
||||
Dash 3 Traço 3
|
||||
Gap 3 Lacuna 3
|
||||
Dash 4 Traço 4
|
||||
Gap 4 Lacuna 4
|
||||
Dash 5 Traço 5
|
||||
Gap 5 Lacuna 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -5916,3 +5916,43 @@ breakText Glyphs Глифы
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` должен быть lines, paragraphs, words или glyphs, а не {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` должен быть "line" или "paragraph", а не {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` должен быть "left", "center" или "right", а не {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Эффекты контура
|
||||
Reverse Direction Обратить направление
|
||||
Align to Corners and Ends Выровнять по углам и концам
|
||||
Count Количество
|
||||
Max Count Наибольшее количество
|
||||
Start Length Длина в начале
|
||||
Start Percent Проценты в начале
|
||||
Start Shape Форма в начале
|
||||
End Length Длина в конце
|
||||
End Percent Проценты в конце
|
||||
End Shape Форма в конце
|
||||
By Count По количеству
|
||||
By Distance По расстоянию
|
||||
Both Оба
|
||||
Shorter Than Length Короче длины
|
||||
Dashes to Geometry Штрихи в геометрию
|
||||
Dashes to Geometry… Штрихи в геометрию…
|
||||
Add Anchors Добавить опорные точки
|
||||
Add Anchors… Добавить опорные точки…
|
||||
Smooth Anchors Сгладить опорные точки
|
||||
Smooth Anchors… Сгладить опорные точки…
|
||||
Simplify Anchors Упростить опорные точки
|
||||
Simplify Anchors… Упростить опорные точки…
|
||||
Split at Anchors Разрезать в опорных точках
|
||||
Split at Anchors… Разрезать в опорных точках…
|
||||
Extend Path… Продлить контур…
|
||||
Close Path… Замкнуть контур…
|
||||
Retract Handles… Убрать маркеры…
|
||||
Dash 1 Штрих 1
|
||||
Gap 1 Зазор 1
|
||||
Dash 2 Штрих 2
|
||||
Gap 2 Зазор 2
|
||||
Dash 3 Штрих 3
|
||||
Gap 3 Зазор 3
|
||||
Dash 4 Штрих 4
|
||||
Gap 4 Зазор 4
|
||||
Dash 5 Штрих 5
|
||||
Gap 5 Зазор 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5994,3 +5994,43 @@ breakText Glyphs Glyfer
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` måste vara lines, paragraphs, words eller glyphs, inte {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` måste vara "line" eller "paragraph", inte {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` måste vara "left", "center" eller "right", inte {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Baneffekter
|
||||
Reverse Direction Vänd riktning
|
||||
Align to Corners and Ends Justera mot hörn och ändar
|
||||
Count Antal
|
||||
Max Count Högsta antal
|
||||
Start Length Startlängd
|
||||
Start Percent Startprocent
|
||||
Start Shape Startform
|
||||
End Length Slutlängd
|
||||
End Percent Slutprocent
|
||||
End Shape Slutform
|
||||
By Count Efter antal
|
||||
By Distance Efter avstånd
|
||||
Both Båda
|
||||
Shorter Than Length Kortare än längden
|
||||
Dashes to Geometry Streck till geometri
|
||||
Dashes to Geometry… Streck till geometri…
|
||||
Add Anchors Lägg till ankarpunkter
|
||||
Add Anchors… Lägg till ankarpunkter…
|
||||
Smooth Anchors Jämna ut ankarpunkter
|
||||
Smooth Anchors… Jämna ut ankarpunkter…
|
||||
Simplify Anchors Förenkla ankarpunkter
|
||||
Simplify Anchors… Förenkla ankarpunkter…
|
||||
Split at Anchors Dela vid ankarpunkter
|
||||
Split at Anchors… Dela vid ankarpunkter…
|
||||
Extend Path… Förläng bana…
|
||||
Close Path… Stäng bana…
|
||||
Retract Handles… Dra in handtag…
|
||||
Dash 1 Streck 1
|
||||
Gap 1 Mellanrum 1
|
||||
Dash 2 Streck 2
|
||||
Gap 2 Mellanrum 2
|
||||
Dash 3 Streck 3
|
||||
Gap 3 Mellanrum 3
|
||||
Dash 4 Streck 4
|
||||
Gap 4 Mellanrum 4
|
||||
Dash 5 Streck 5
|
||||
Gap 5 Mellanrum 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -5896,3 +5896,43 @@ breakText Glyphs Glifler
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by`, lines, paragraphs, words veya glyphs olmalı, {other} değil
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind`, "line" veya "paragraph" olmalı, {other} değil
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align`, "left", "center" veya "right" olmalı, {other} değil
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Yol Efektleri
|
||||
Reverse Direction Yönü Ters Çevir
|
||||
Align to Corners and Ends Köşelere ve Uçlara Hizala
|
||||
Count Sayı
|
||||
Max Count En Fazla Sayı
|
||||
Start Length Başlangıç Uzunluğu
|
||||
Start Percent Başlangıç Yüzdesi
|
||||
Start Shape Başlangıç Şekli
|
||||
End Length Bitiş Uzunluğu
|
||||
End Percent Bitiş Yüzdesi
|
||||
End Shape Bitiş Şekli
|
||||
By Count Sayıya Göre
|
||||
By Distance Mesafeye Göre
|
||||
Both Her İkisi
|
||||
Shorter Than Length Uzunluktan Kısa Olanlar
|
||||
Dashes to Geometry Çizgileri Geometriye Dönüştür
|
||||
Dashes to Geometry… Çizgileri Geometriye Dönüştür…
|
||||
Add Anchors Bağlantı Noktaları Ekle
|
||||
Add Anchors… Bağlantı Noktaları Ekle…
|
||||
Smooth Anchors Bağlantı Noktalarını Yumuşat
|
||||
Smooth Anchors… Bağlantı Noktalarını Yumuşat…
|
||||
Simplify Anchors Bağlantı Noktalarını Basitleştir
|
||||
Simplify Anchors… Bağlantı Noktalarını Basitleştir…
|
||||
Split at Anchors Bağlantı Noktalarından Böl
|
||||
Split at Anchors… Bağlantı Noktalarından Böl…
|
||||
Extend Path… Yolu Uzat…
|
||||
Close Path… Yolu Kapat…
|
||||
Retract Handles… Kolları Geri Çek…
|
||||
Dash 1 Çizgi 1
|
||||
Gap 1 Boşluk 1
|
||||
Dash 2 Çizgi 2
|
||||
Gap 2 Boşluk 2
|
||||
Dash 3 Çizgi 3
|
||||
Gap 3 Boşluk 3
|
||||
Dash 4 Çizgi 4
|
||||
Gap 4 Boşluk 4
|
||||
Dash 5 Çizgi 5
|
||||
Gap 5 Boşluk 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -5537,3 +5537,44 @@ breakText Glyphs Гліфи
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` має бути lines, paragraphs, words або glyphs, а не {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` має бути "line" або "paragraph", а не {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` має бути "left", "center" або "right", а не {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Ефекти контуру
|
||||
Reverse Direction Змінити напрям
|
||||
Align to Corners and Ends Вирівняти за кутами й кінцями
|
||||
Count Кількість
|
||||
Max Count Найбільша кількість
|
||||
Start Length Довжина на початку
|
||||
Start Percent Відсотки на початку
|
||||
Start Shape Форма на початку
|
||||
End Length Довжина в кінці
|
||||
End Percent Відсотки в кінці
|
||||
End Shape Форма в кінці
|
||||
By Count За кількістю
|
||||
By Distance За відстанню
|
||||
Both Обидва
|
||||
Shorter Than Length Коротші за довжину
|
||||
Anchors Опорні точки
|
||||
Dashes to Geometry Штрихи в геометрію
|
||||
Dashes to Geometry… Штрихи в геометрію…
|
||||
Add Anchors Додати опорні точки
|
||||
Add Anchors… Додати опорні точки…
|
||||
Smooth Anchors Згладити опорні точки
|
||||
Smooth Anchors… Згладити опорні точки…
|
||||
Simplify Anchors Спростити опорні точки
|
||||
Simplify Anchors… Спростити опорні точки…
|
||||
Split at Anchors Розрізати в опорних точках
|
||||
Split at Anchors… Розрізати в опорних точках…
|
||||
Extend Path… Подовжити контур…
|
||||
Close Path… Замкнути контур…
|
||||
Retract Handles… Прибрати маркери…
|
||||
Dash 1 Штрих 1
|
||||
Gap 1 Проміжок 1
|
||||
Dash 2 Штрих 2
|
||||
Gap 2 Проміжок 2
|
||||
Dash 3 Штрих 3
|
||||
Gap 3 Проміжок 3
|
||||
Dash 4 Штрих 4
|
||||
Gap 4 Проміжок 4
|
||||
Dash 5 Штрих 5
|
||||
Gap 5 Проміжок 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 178 and column 5.
|
@@ -5539,3 +5539,44 @@ breakText Glyphs Glyph
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` phải là lines, paragraphs, words hoặc glyphs, không phải {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` phải là "line" hoặc "paragraph", không phải {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` phải là "left", "center" hoặc "right", không phải {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects Hiệu ứng đường dẫn
|
||||
Reverse Direction Đảo ngược hướng
|
||||
Align to Corners and Ends Căn theo góc và đầu mút
|
||||
Count Số lượng
|
||||
Max Count Số lượng tối đa
|
||||
Start Length Độ dài đầu
|
||||
Start Percent Phần trăm đầu
|
||||
Start Shape Hình dạng đầu
|
||||
End Length Độ dài cuối
|
||||
End Percent Phần trăm cuối
|
||||
End Shape Hình dạng cuối
|
||||
By Count Theo số lượng
|
||||
By Distance Theo khoảng cách
|
||||
Both Cả hai
|
||||
Shorter Than Length Ngắn hơn độ dài
|
||||
Anchors Điểm neo
|
||||
Dashes to Geometry Nét đứt thành hình học
|
||||
Dashes to Geometry… Nét đứt thành hình học…
|
||||
Add Anchors Thêm các điểm neo
|
||||
Add Anchors… Thêm các điểm neo…
|
||||
Smooth Anchors Làm mượt điểm neo
|
||||
Smooth Anchors… Làm mượt điểm neo…
|
||||
Simplify Anchors Đơn giản hóa điểm neo
|
||||
Simplify Anchors… Đơn giản hóa điểm neo…
|
||||
Split at Anchors Tách tại điểm neo
|
||||
Split at Anchors… Tách tại điểm neo…
|
||||
Extend Path… Kéo dài đường dẫn…
|
||||
Close Path… Đóng đường dẫn…
|
||||
Retract Handles… Thu tay nắm…
|
||||
Dash 1 Nét 1
|
||||
Gap 1 Khoảng hở 1
|
||||
Dash 2 Nét 2
|
||||
Gap 2 Khoảng hở 2
|
||||
Dash 3 Nét 3
|
||||
Gap 3 Khoảng hở 3
|
||||
Dash 4 Nét 4
|
||||
Gap 4 Khoảng hở 4
|
||||
Dash 5 Nét 5
|
||||
Gap 5 Khoảng hở 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 200 and column 5.
|
@@ -5780,3 +5780,43 @@ breakText Glyphs 字形
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` 必须是 lines、paragraphs、words 或 glyphs,而不是 {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` 必须是 "line" 或 "paragraph",而不是 {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` 必须是 "left"、"center" 或 "right",而不是 {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects 路径效果
|
||||
Reverse Direction 反转方向
|
||||
Align to Corners and Ends 对齐转角和端点
|
||||
Count 数量
|
||||
Max Count 最大数量
|
||||
Start Length 起点长度
|
||||
Start Percent 起点百分比
|
||||
Start Shape 起点形状
|
||||
End Length 终点长度
|
||||
End Percent 终点百分比
|
||||
End Shape 终点形状
|
||||
By Count 按数量
|
||||
By Distance 按距离
|
||||
Both 两者
|
||||
Shorter Than Length 短于长度
|
||||
Dashes to Geometry 虚线转为几何图形
|
||||
Dashes to Geometry… 虚线转为几何图形…
|
||||
Add Anchors 添加锚点
|
||||
Add Anchors… 添加锚点…
|
||||
Smooth Anchors 平滑锚点
|
||||
Smooth Anchors… 平滑锚点…
|
||||
Simplify Anchors 简化锚点
|
||||
Simplify Anchors… 简化锚点…
|
||||
Split at Anchors 在锚点处分割
|
||||
Split at Anchors… 在锚点处分割…
|
||||
Extend Path… 延长路径…
|
||||
Close Path… 闭合路径…
|
||||
Retract Handles… 收回手柄…
|
||||
Dash 1 虚线 1
|
||||
Gap 1 间隔 1
|
||||
Dash 2 虚线 2
|
||||
Gap 2 间隔 2
|
||||
Dash 3 虚线 3
|
||||
Gap 3 间隔 3
|
||||
Dash 4 虚线 4
|
||||
Gap 4 间隔 4
|
||||
Dash 5 虚线 5
|
||||
Gap 5 间隔 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5576,3 +5576,44 @@ breakText Glyphs 字符
|
||||
@msg `by` must be lines, paragraphs, words or glyphs, not {other} `by` 必須是 lines、paragraphs、words 或 glyphs,而不是 {other}
|
||||
@msg `kind` must be "line" or "paragraph", not {other} `kind` 必須是 "line" 或 "paragraph",而不是 {other}
|
||||
@msg `align` must be "left", "center" or "right", not {other} `align` 必須是 "left"、"center" 或 "right",而不是 {other}
|
||||
|
||||
# Path Precision Tools add-on: Path Effects (Effect › Path Effects).
|
||||
Path Effects 路徑效果
|
||||
Reverse Direction 反轉方向
|
||||
Align to Corners and Ends 對齊轉角和端點
|
||||
Count 數量
|
||||
Max Count 最大數量
|
||||
Start Length 起點長度
|
||||
Start Percent 起點百分比
|
||||
Start Shape 起點形狀
|
||||
End Length 終點長度
|
||||
End Percent 終點百分比
|
||||
End Shape 終點形狀
|
||||
By Count 依數量
|
||||
By Distance 依距離
|
||||
Both 兩者
|
||||
Shorter Than Length 短於長度
|
||||
Anchors 錨點
|
||||
Dashes to Geometry 虛線轉為幾何圖形
|
||||
Dashes to Geometry… 虛線轉為幾何圖形…
|
||||
Add Anchors 增加錨點
|
||||
Add Anchors… 增加錨點…
|
||||
Smooth Anchors 平滑錨點
|
||||
Smooth Anchors… 平滑錨點…
|
||||
Simplify Anchors 簡化錨點
|
||||
Simplify Anchors… 簡化錨點…
|
||||
Split at Anchors 在錨點處分割
|
||||
Split at Anchors… 在錨點處分割…
|
||||
Extend Path… 延長路徑…
|
||||
Close Path… 封閉路徑…
|
||||
Retract Handles… 收回控制把手…
|
||||
Dash 1 虛線 1
|
||||
Gap 1 間隙 1
|
||||
Dash 2 虛線 2
|
||||
Gap 2 間隙 2
|
||||
Dash 3 虛線 3
|
||||
Gap 3 間隙 3
|
||||
Dash 4 虛線 4
|
||||
Gap 4 間隙 4
|
||||
Dash 5 虛線 5
|
||||
Gap 5 間隙 5
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -139,6 +139,7 @@ pub(super) fn effect_menu() -> Vec<Item> {
|
||||
"Crop Marks",
|
||||
"Distort & Transform",
|
||||
"Path",
|
||||
"Path Effects",
|
||||
"Pathfinder",
|
||||
"Rasterize…",
|
||||
"Stylize",
|
||||
|
||||
+31
@@ -5903,6 +5903,37 @@ plain path from then on.
|
||||
{"name":"run_command","arguments":{"command":"addon.pathtools.shape.set","params":{"id":14,"kind":"gear","count":16,"radius":2}}}
|
||||
```
|
||||
|
||||
### Path Effects (live)
|
||||
|
||||
Effect › Path Effects holds the add-on's live geometry effects, applied with `effect.apply {effect, params}` like any
|
||||
other effect, with no shortcuts. Each draws, measures, exports and stacks as the other geometry effects do; the object's
|
||||
own path stays as it is until Expand Appearance (`effect.expandAppearance`) writes the result. `effect.list` documents
|
||||
every parameter. Out-of-range values are clamped, and an effect whose result would pass the geometry effects' budget
|
||||
(2 million anchors) leaves the path as it is.
|
||||
|
||||
| Id | Menu item | Parameters |
|
||||
| --- | --- | --- |
|
||||
| `addon.path.dashes` | Dashes to Geometry… | `dash1`…`dash5`, `gap1`…`gap5` (pt 0 to 10000; 12 and 6, then 0), `offset` (pt), `alignCorners` (bool, false) |
|
||||
| `addon.path.addAnchors` | Add Anchors… | `target` (`all`, `curved`, `straight`), `spacing` (`count`, `distance`), `count` (1 to 1000, 1), `distance` (pt, 20), `maxCount` (1 to 1000, 100) |
|
||||
| `addon.path.extend` | Extend Path… | `startLength`, `endLength` (pt, 10; negative shortens), `startPercent`, `endPercent` (% of the path's length, 0), `startShape`, `endShape` (`straight`, `arc`) |
|
||||
| `addon.path.close` | Close Path… | `mode` (`straight`, `smooth`) |
|
||||
| `addon.path.smooth` | Smooth Anchors… | `ratio` (0.01 to 0.5, 0.3905) |
|
||||
| `addon.path.retract` | Retract Handles… | `handles` (`all`, `shorter`), `length` (pt, 5) |
|
||||
| `addon.path.reverse` | Reverse Direction | none |
|
||||
| `addon.path.simplify` | Simplify Anchors… | `tolerance` (0 to 200, 10; each step lets the outline drift 0.02 pt), `protectCorners` (bool, true) |
|
||||
| `addon.path.split` | Split at Anchors… | `anchors` (`corner`, `smooth`, `both`) |
|
||||
|
||||
Dashes to Geometry turns the path into open subpaths, one per dash; with `alignCorners`, every run between corners and
|
||||
ends holds whole periods with a dash centred on each corner and end, and the offset doesn't apply. A dash of 0 becomes a
|
||||
point that a round or projecting cap paints. Extend Path adds an end's length and percent together; closed subpaths stay
|
||||
as they are, and so does an end that would be shortened to nothing. An arc follows the end's curvature for at most half
|
||||
a circle. Split at Anchors tells smooth anchors by their handles standing in line. Simplify Anchors leaves a path of more
|
||||
than 50000 anchors as it is.
|
||||
|
||||
```json
|
||||
{"id": 1, "method": "engine.execute", "params": {"command": "effect.apply", "params": {"effect": "addon.path.dashes", "params": {"dash1": 8, "gap1": 4, "dash2": 2, "gap2": 4, "alignCorners": true}}}}
|
||||
```
|
||||
|
||||
## Smart Pen add-on
|
||||
|
||||
Smart Pen is a built-in add-on: one tool that draws paths and edits their points, handles and segments without
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Parity with Adobe Illustrator
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (Path Precision Tools add-on: Corner Tool and Slide Anchor Along Path; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (Path Precision Tools add-on: live Path Effects; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -1251,6 +1251,16 @@ Kernels in `vectorcraft_pathops::addons::pathtools`. Tests: `addons::pathtools`
|
||||
undo step, bad params refused, the brush's stroke one step) and `edit_toolbar::tests::addon_tools_are_in_the_drawer_not_the_toolbars`.
|
||||
Commands: `docs/mcp.md` › Path Precision Tools add-on.
|
||||
|
||||
The add-on's live Path Effects (2026-10-31, Effect › Path Effects, `addon.path.*`) are geometry effects, so they draw,
|
||||
measure, export and expand as the others do: Dashes to Geometry (up to five dash and gap pairs, offset, fitted to corners
|
||||
and ends), Add Anchors (all, curved or straight segments, by count or distance, capped per segment), Extend Path (each
|
||||
end by length and percent, shortening when negative, straight or arc), Close Path (straight or smooth), Smooth Anchors,
|
||||
Retract Handles (all or the short ones), Reverse Direction, Simplify Anchors and Split at Anchors. Code in
|
||||
`vectorcraft_effects::addons::path` on the kernels above. Tests: `addons::path::tests` in `vectorcraft-effects` (geometry,
|
||||
caps, junk params and paths), `cmd::addons::pathtools::effect_tests` in `vectorcraft-engine` (drawn live, Expand
|
||||
Appearance matches the outline and the pixels, saves keep it, a file's junk params), `addons::pathtools::tests` in
|
||||
`vectorcraft-pathops` and `vectorcraft-ui-egui`, and the command sweep's junk rows.
|
||||
|
||||
The add-on's Parametric Shape Tool (2026-10-30) draws ten kinds of shape that stay editable as numbers (rectangle,
|
||||
ellipse, polygon and star with 3 to 1,000 sides or points, ring, arrow, cross, heart, gear, speech bubble; corner radius
|
||||
limited to the middle of a side): a drag from the centre sizes and turns one, the arrow keys change its count and values
|
||||
@@ -1328,6 +1338,7 @@ add-on.
|
||||
| 2026-10-30 | minor | File formats: `.ai`/EPS editing data: a blend takes the transparency (opacity, blend mode, knockout) in its state block after its record, so its steps knock each other out as the page draws them; a user's file comes in 0.6% from its page instead of 1.3%. Scores and estimates unchanged. |
|
||||
| 2026-10-31 | minor | Path Precision Tools add-on: the Corner Tool and Slide Anchor Along Path; evidence added; scores unchanged. |
|
||||
| 2026-10-30 | minor | File formats: `.ai`/EPS editing-data fidelity from an audit of a user's files: All Caps and Small Caps (#4708); placed files at their box's corner, kept where they draw closer to the page (#4664, #4723, #4742); pages cropped to the art, with `UserUnit` (#4658, #4727); gradient mesh corners with handle weights of 0 and the state written after a mesh (#4738, #4744, #4748); type on a path spaced as the file lays it out (#4743); synthetic regressions in `crates/eps`; evidence added; scores unchanged. |
|
||||
| 2026-10-31 | minor | Path Precision Tools add-on (a VectorCraft addition): live Path Effects; evidence added; scores unchanged. |
|
||||
| 2026-10-31 | minor | Artwork Cleanup add-on (a VectorCraft addition): text tools; evidence added; scores unchanged. |
|
||||
| 2026-10-30 | minor | Color Effects add-on: live Duotone (one to four inks, spot tones kept) and Print Ink Fixes; scores and estimates unchanged |
|
||||
| 2026-10-30 | minor | 3D Extrude & Bevel, Inflate and Rotate apply to type, which stays editable (#4715); scores and estimates unchanged |
|
||||
|
||||
+4
-1
@@ -1,6 +1,6 @@
|
||||
# UI and interaction parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (Path Precision Tools add-on: Corner Tool and Slide Anchor Along Path; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-31 · **Change:** minor (Path Precision Tools add-on: live Path Effects; scores unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -326,6 +326,8 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po
|
||||
| Tabs panel: a stop drags from where the press landed, a stop just added included | done | #1458 |
|
||||
| Outline view keeps template layers in Preview, dimmed | done | #1486; a picture placed to be traced stays visible under the outlines |
|
||||
| Smart Pen add-on (VectorCraft addition): Object › Plug-ins › Smart Pen selects the Smart Pen Tool, which the All Tools drawer lists under its own heading but neither toolbar shows as shipped (no shortcut), and opens the Smart Pen panel (next segment straight or curved; Remove Points Keep Shape, Make Smooth, Make Corner, Retract Handles, Close or Join). The tool draws (click: straight or curved segment, a curve after a straight one tangent; drag: smooth anchor, Alt breaks the handle, Shift 45°, Space moves it; Tab switches a segment; rubber band; close, join, continue) and edits without switching tools (click selects a point, drag moves it, Alt-click removes it keeping the shape, Shift+Alt-click deletes it, double-click toggles smooth/corner; handle drags with Shift and Alt; segment drags reshape, Alt-click adds a point) | done | `addon.pen.*`, `crates/tools/src/addons/smart_pen.rs`, `crates/ui-egui/src/addons/pen.rs` and their tests; not an Illustrator feature, so it is not scored |
|
||||
| Path Precision Tools add-on (VectorCraft addition): Effect › Path Effects lists its live effects (Dashes to Geometry, Add Anchors, Extend Path, Close Path, Smooth Anchors, Retract Handles, Reverse Direction, Simplify Anchors, Split at Anchors), each with the effect dialog's live Preview except Reverse Direction, which has no options, and listed in the Appearance panel like any effect | done | `addon.path.*`, `crates/effects/src/addons/path.rs`, `crates/ui-egui/src/addons/pathtools.rs` and their tests; not an Illustrator feature, so it is not scored |
|
||||
| Path Precision Tools add-on (VectorCraft addition): Object › Plug-ins › Path Precision Tools selects its four tools (Anchor Eraser Brush, Extend Path, Measure & Inspect, Parametric Shape), which the All Tools drawer lists under their own heading but neither toolbar shows as shipped, opens the Anchor Inspector panel (the selected anchor's X/Y and handle lengths and angles as fields; Make Smooth, Make Corner, Equalize Handles, Remove Points Keep Shape, Add Points, the three ways to close) and the Parametric Shape panel (kind, count, W/H, rotation, the kind's own values and corner radius of the selected parametric shape, or of the tool's next one), and runs Add Points at / Split at Crossings and Convert to Parametric Shape; the Parametric Shape Tool draws ten re-editable kinds from the centre (↑/↓ and ←/→ change their numbers while dragging) and edits a selected one with handles; a double-click on the brush or Extend Path tool opens its options | done | `addon.pathtools.*`, `crates/tools/src/addons/`, `crates/ui-egui/src/addons/pathtools.rs`, `crates/ui-egui/src/addons/pathtools/shape.rs` and their tests; not an Illustrator feature, so it is not scored |
|
||||
| Path Precision Tools add-on (VectorCraft addition): Object › Plug-ins › Path Precision Tools selects its six tools (Anchor Eraser Brush, Extend Path, Measure & Inspect, Parametric Shape, Corner Tool, Slide Anchor Along Path), which the All Tools drawer lists under their own heading but neither toolbar shows as shipped, opens the Anchor Inspector panel (the selected anchor's X/Y and handle lengths and angles as fields; Make Smooth, Make Corner, Equalize Handles, Remove Points Keep Shape, Add Points, the three ways to close) and the Parametric Shape panel (kind, count, W/H, rotation, the kind's own values and corner radius of the selected parametric shape, or of the tool's next one), and runs Add Points at / Split at Crossings and Convert to Parametric Shape; the Parametric Shape Tool draws ten re-editable kinds from the centre (↑/↓ and ←/→ change their numbers while dragging) and edits a selected one with handles; the Corner Tool cuts editable corners on any path (hover shows the anchor, a click uses its radius and kind, a drag sizes the corner, Shift whole values; a corner's marker selects it and loads its values, drags to resize, to 0 or double-clicked removes it; never past what fits; Alt-marquee cuts every anchor inside) with a Corners section in the Anchor Inspector (radius, kind, selected count, Apply to Selected Anchors); Slide Anchor Along Path drags an anchor along its curve with the segments beside it refitted, Shift snapping where the tangent is a multiple of 45°; a double-click on the brush or Extend Path tool opens its options | done | `addon.pathtools.*`, `crates/tools/src/addons/`, `crates/ui-egui/src/addons/pathtools.rs`, `crates/ui-egui/src/addons/pathtools/` and their tests; not an Illustrator feature, so it is not scored |
|
||||
| Smart Pen add-on (VectorCraft addition): Object › Plug-ins › Smart Pen selects the Smart Pen Tool, which the All Tools drawer lists under its own heading but neither toolbar shows as shipped (no shortcut), and opens the Smart Pen panel (next segment straight or curved; Remove Points Keep Shape, Make Smooth, Make Corner, Retract Handles, Close or Join). The tool draws (click: straight or curved segment, a curve after a straight one tangent; drag: smooth anchor, Alt breaks the handle, Shift 45°, Space moves it; Tab switches a segment; rubber band; close, join, continue) and edits without switching tools (click selects a point, drag moves it, Alt-click removes it keeping the shape, Shift+Alt-click deletes it, double-click toggles smooth/corner; handle drags with Shift and Alt; segment drags reshape, Alt-click adds a point); an Alt-drag away from paths selects the points inside a marquee | done | `addon.pen.*`, `crates/tools/src/addons/smart_pen.rs`, `crates/ui-egui/src/addons/pen.rs` and their tests; not an Illustrator feature, so it is not scored |
|
||||
| Path Precision Tools add-on (VectorCraft addition): Object › Plug-ins › Path Precision Tools selects its three tools (Anchor Eraser Brush, Extend Path, Measure & Inspect), which the All Tools drawer lists under their own heading but neither toolbar shows as shipped, opens the Anchor Inspector panel (the selected anchor's X/Y and handle lengths and angles as fields; Make Smooth, Make Corner, Equalize Handles, Remove Points Keep Shape, Add Points, the three ways to close) and runs Add Points at / Split at Crossings; a double-click on the brush or Extend Path tool opens its options | done | `addon.pathtools.*`, `crates/tools/src/addons/`, `crates/ui-egui/src/addons/pathtools.rs` and their tests; not an Illustrator feature, so it is not scored |
|
||||
@@ -372,6 +374,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-31 | minor | Path Precision Tools add-on: live Path Effects in Effect › Path Effects; scores and estimates unchanged |
|
||||
| 2026-10-31 | minor | Path Precision Tools add-on: the Corner Tool (editable corners, panel section) and Slide Anchor Along Path; scores and estimates unchanged |
|
||||
| 2026-10-31 | minor | Artwork Cleanup add-on (a VectorCraft addition): its text tools in the panel and Object › Plug-ins › Artwork Cleanup; scores and estimates unchanged |
|
||||
| 2026-10-30 | minor | Color Effects add-on: live Duotone (one to four inks, spot tones kept) and Print Ink Fixes; scores and estimates unchanged |
|
||||
|
||||
Reference in New Issue
Block a user