Merge pull request #4543 from storytold/feat/path-smooth

Path: Object › Path › Smooth with a slider and Auto-Smooth (#4115)
This commit is contained in:
BFlat
2026-10-29 14:27:38 +07:00
committed by GitHub
44 changed files with 583 additions and 93 deletions
+31 -6
View File
@@ -14,7 +14,10 @@ use vectorcraft_pathops as po;
use vectorcraft_pathops::{BoolOp, PathfinderOp};
use super::edit::selected_roots;
use super::{CommandSpec, EngineError, Result, Session, always, bad, bool_or, cmd, has_doc, has_multi, has_selection, str_param};
use super::{
CommandSpec, EngineError, Result, Session, always, bad, bool_or, checked_ids_or_selection, cmd, has_doc, has_multi, has_selection, number_or,
str_param, switch_or,
};
pub fn specs() -> Vec<CommandSpec> {
vec![
@@ -153,6 +156,15 @@ pub fn specs() -> Vec<CommandSpec> {
has_selection,
simplify
),
cmd!(
"object.path.smooth",
"Smooth…",
["Object", "Path"],
None,
"{level?: 0..100 (50; the Smooth slider: 0 minimum smoothing, the path staying within 0.1% of its longer side, 100 maximum: refitted with curves within up to 2.5% of it, corners kept only where the path turns back, never with more anchor points), auto?: bool (Auto-Smooth: level 50), ids?: [id…] (default: the selection)} smooth each path under the objects, compound path members included, as one undo step → {ids, before, after} anchor counts, and the level used",
has_selection,
smooth
),
cmd!("object.path.addAnchorPoints", "Add Anchor Points", ["Object", "Path"], None, "{} → {ids}", has_selection, add_anchor_points),
cmd!(
"object.path.divideObjectsBelow",
@@ -957,20 +969,20 @@ fn path_ids(d: &Document, roots: &[NodeId]) -> Vec<NodeId> {
/// selected segments when they were picked as segments). `None` selects the path whole.
type PartSelection = Option<(BTreeSet<AnchorRef>, Option<BTreeSet<SegmentRef>>)>;
/// Replace each path under the selection ([`path_ids`]) by what `f` makes of it, given its
/// Replace each path under `roots` ([`path_ids`]) by what `f` makes of it, given its
/// direct-selected anchors and picked segments (none when it is selected whole): → (their ids, how
/// many anchors they have before and after). A path that `f` would empty or returns unchanged is
/// left as it is, with its live shape and its direct selection; a replaced path loses its live
/// shape and has the direct selection `f` gives it.
fn edit_paths(
s: &mut Session,
roots: &[NodeId],
label: &str,
f: impl Fn(&PathData, Option<&BTreeSet<AnchorRef>>, Option<&BTreeSet<SegmentRef>>) -> (PathData, PartSelection),
) -> Result<(Vec<NodeId>, usize, usize)> {
let roots = selected_roots(s)?;
let st = s.doc()?;
let (d, sel) = (&st.doc, &st.selection);
let ids = path_ids(d, &roots);
let ids = path_ids(d, roots);
if ids.is_empty() {
return Err(EngineError::Other(format!("{label}: select paths")));
}
@@ -1064,7 +1076,8 @@ fn simplify(s: &mut Session, p: &Value) -> Result<Value> {
let whole = bool_or(p, "wholePaths", false);
// A path with segments direct-selected simplifies only their runs, and keeps those runs (and the
// other anchors it had selected) direct-selected.
let (ids, before, after) = edit_paths(s, "Simplify", |path, anchors, segments| {
let roots = selected_roots(s)?;
let (ids, before, after) = edit_paths(s, &roots, "Simplify", |path, anchors, segments| {
let Some(picked) = selected_segments(path, anchors, segments).filter(|_| !whole) else {
return (po::simplify_with(path, &opts), None);
};
@@ -1084,8 +1097,20 @@ fn simplify(s: &mut Session, p: &Value) -> Result<Value> {
Ok(out)
}
/// Object › Path › Smooth (`object.path.smooth`): [`po::smooth_level`] on each path under the
/// objects given or selected, as one undo step; Auto-Smooth (`auto`) uses [`po::SMOOTH_AUTO_LEVEL`].
fn smooth(s: &mut Session, p: &Value) -> Result<Value> {
const C: &str = "object.path.smooth";
let level = number_or(p, "level", po::SMOOTH_AUTO_LEVEL, C)?.round().clamp(0.0, 100.0);
let level = if switch_or(p, "auto", false, C)? { po::SMOOTH_AUTO_LEVEL } else { level };
let roots = checked_ids_or_selection(s, p, C)?;
let (ids, before, after) = edit_paths(s, &roots, "Smooth", |path, _, _| (po::smooth_level(path, level), None))?;
Ok(json!({ "ids": ids.iter().map(|i| i.0).collect::<Vec<_>>(), "before": before, "after": after, "level": level }))
}
fn add_anchor_points(s: &mut Session, _: &Value) -> Result<Value> {
let (ids, before, after) = edit_paths(s, "Add Anchor Points", |path, _, _| (po::add_anchor_points(path), None))?;
let roots = selected_roots(s)?;
let (ids, before, after) = edit_paths(s, &roots, "Add Anchor Points", |path, _, _| (po::add_anchor_points(path), None))?;
Ok(json!({ "ids": ids.iter().map(|i| i.0).collect::<Vec<_>>(), "before": before, "after": after }))
}
+1 -1
View File
@@ -37,7 +37,7 @@ pub use journal::Journal;
pub use tooling::{UiRequest, ViewInfo};
pub use vectorcraft_doc as doc;
pub use vectorcraft_doc::cancel;
pub use vectorcraft_pathops::{SIMPLIFY_LEVEL, simplify_level_tolerance};
pub use vectorcraft_pathops::{SIMPLIFY_LEVEL, SMOOTH_AUTO_LEVEL, simplify_level_tolerance};
pub use vectorcraft_tools as tools;
/// Coordinates beyond this (points) are rejected: ~1,400 m, far past Illustrator's large canvas.
+1
View File
@@ -140,6 +140,7 @@ mod shortcutset;
mod simplify_part;
mod slices;
mod smartguides;
mod smooth_path;
mod store;
mod strokegeom;
mod strokegradient;
+163
View File
@@ -0,0 +1,163 @@
//! Object › Path › Smooth (`object.path.smooth`): the slider from minimum to maximum smoothing and
//! Auto-Smooth, as the Smooth slider help page describes
//! (https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/refine-path-segments-with-the-smooth-slider.html).
use serde_json::{Value, json};
use vectorcraft_doc::NodeKind;
use vectorcraft_geom::kurbo::ParamCurveNearest;
use vectorcraft_geom::{ParamCurve, PathData, Point};
use crate::*;
fn session() -> Session {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 800, "height": 600})).unwrap();
s
}
fn path_of(s: &Session, id: NodeId) -> PathData {
match &s.doc().unwrap().doc.node(id).unwrap().kind {
NodeKind::Path { path, .. } => path.clone(),
_ => panic!("not a path"),
}
}
/// A path through `points`, selected: its id.
fn polyline(s: &mut Session, points: &[(f64, f64)], closed: bool) -> NodeId {
let anchors: Vec<Value> = points.iter().map(|&(x, y)| json!({"x": x, "y": y})).collect();
NodeId(s.execute("path.create", &json!({"anchors": anchors, "closed": closed})).unwrap()["id"].as_u64().unwrap())
}
/// A sine wave 300 pt long with an anchor every 3 pt, its anchors 1.5 pt above and below it in turn.
fn jagged_wave(s: &mut Session) -> NodeId {
let pts: Vec<(f64, f64)> = (0..=100u32)
.map(|i| (f64::from(i) * 3.0, 200.0 + 40.0 * (f64::from(i) * 3.0 / 50.0).sin() + if i.is_multiple_of(2) { 1.5 } else { -1.5 }))
.collect();
polyline(s, &pts, false)
}
/// A closed ring of radius `r` around (`cx`, 300) with 120 anchors, `jag` pt in and out in turn.
fn jagged_ring(s: &mut Session, cx: f64, r: f64, jag: f64) -> NodeId {
let pts: Vec<(f64, f64)> = (0..120u32)
.map(|i| {
let a = f64::from(i) * std::f64::consts::TAU / 120.0;
let rr = r + if i.is_multiple_of(2) { jag } else { -jag };
(cx + rr * a.cos(), 300.0 + rr * a.sin())
})
.collect();
polyline(s, &pts, true)
}
/// The farthest that 11 points on each segment of `from` lie from the nearest point of `to`.
fn farthest(from: &PathData, to: &PathData) -> f64 {
let off = |p: Point| {
to.subpaths
.iter()
.flat_map(|sp| (0..sp.segment_count()).map(move |i| sp.segment(i).nearest(p, 1e-9).distance_sq.sqrt()))
.fold(f64::INFINITY, f64::min)
};
from.subpaths
.iter()
.flat_map(|sp| (0..sp.segment_count()).flat_map(move |i| (0..=10).map(move |k| sp.segment(i).eval(f64::from(k) / 10.0))))
.map(off)
.fold(0.0, f64::max)
}
fn undo_len(s: &Session) -> usize {
s.doc().unwrap().history.undo.len()
}
#[test]
fn minimum_smoothing_leaves_the_path_where_it_was() {
let mut s = session();
let id = jagged_wave(&mut s);
let before = path_of(&s, id);
let out = s.execute("object.path.smooth", &json!({"level": 0})).unwrap();
assert_eq!(out["level"], json!(0.0));
let after = path_of(&s, id);
// 0.1 % of the longer side (300 pt), both ways.
assert!(farthest(&before, &after) <= 0.3 + 1e-6 && farthest(&after, &before) <= 0.3 + 1e-6);
}
#[test]
fn more_smoothing_removes_the_jags_and_keeps_the_shape() {
let mut s = session();
let id = jagged_wave(&mut s);
let before = path_of(&s, id);
let mut counts = vec![];
for level in [25, 50, 100] {
let out = s.execute("object.path.smooth", &json!({ "level": level })).unwrap();
let after = path_of(&s, id);
assert_eq!(out["after"].as_u64().unwrap() as usize, after.anchor_count());
counts.push(after.anchor_count());
// Within the tolerance (2.5 % of 300 pt at most) plus the jag the refit smooths away.
assert!(farthest(&before, &after) <= 7.5 + 1.5, "level {level}: {}", farthest(&before, &after));
// The end points of an open path stay put.
let (a, b) = (&before.subpaths[0].anchors, &after.subpaths[0].anchors);
assert_eq!((a[0].p, a[a.len() - 1].p), (b[0].p, b[b.len() - 1].p));
assert!(!after.subpaths[0].closed);
s.execute("edit.undo", &json!({})).unwrap();
}
assert!(counts[0] < 101 / 2 && counts[2] <= 10, "{counts:?}");
assert!(counts.windows(2).all(|w| w[0] >= w[1]), "{counts:?}");
}
#[test]
fn auto_smooth_uses_the_middle_level() {
let mut s = session();
let id = jagged_wave(&mut s);
let out = s.execute("object.path.smooth", &json!({"auto": true, "level": 0})).unwrap();
assert_eq!(out["level"], json!(50.0));
let auto = path_of(&s, id);
s.execute("edit.undo", &json!({})).unwrap();
s.execute("object.path.smooth", &json!({"level": 50})).unwrap();
assert_eq!(path_of(&s, id), auto);
}
#[test]
fn closed_paths_and_compound_path_members_smooth_in_one_undo_step() {
let mut s = session();
let outer = jagged_ring(&mut s, 300.0, 100.0, 2.0);
let inner = jagged_ring(&mut s, 300.0, 40.0, 1.0);
s.execute("select.set", &json!({"ids": [outer.0, inner.0]})).unwrap();
s.execute("object.compoundPath.make", &json!({})).unwrap();
let (o, i) = (path_of(&s, outer), path_of(&s, inner));
let steps = undo_len(&s);
let out = s.execute("object.path.smooth", &json!({"level": 100})).unwrap();
assert_eq!(out["ids"].as_array().unwrap().len(), 2);
assert_eq!(undo_len(&s), steps + 1);
for id in [outer, inner] {
let now = path_of(&s, id);
assert!(now.subpaths[0].closed);
assert!(now.anchor_count() < 120 / 4, "{}", now.anchor_count());
}
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!((path_of(&s, outer), path_of(&s, inner)), (o, i));
}
#[test]
fn smooths_the_objects_given() {
let mut s = session();
let a = jagged_wave(&mut s);
let b = jagged_ring(&mut s, 300.0, 50.0, 1.0);
let ring = path_of(&s, b);
// `b` is selected; `ids` names `a` instead.
let out = s.execute("object.path.smooth", &json!({"ids": [a.0], "level": 100})).unwrap();
assert_eq!(out["ids"], json!([a.0]));
assert_eq!(path_of(&s, b), ring);
}
#[test]
fn bad_params_are_refused_and_change_nothing() {
let mut s = session();
let id = jagged_wave(&mut s);
let before = path_of(&s, id);
let steps = undo_len(&s);
for p in [json!({"level": "50"}), json!({"auto": true, "level": "max"}), json!({"auto": "yes"}), json!({"ids": "all"}), json!({"ids": [999_999]})]
{
assert!(s.execute("object.path.smooth", &p).is_err(), "{p}");
}
assert_eq!((path_of(&s, id), undo_len(&s)), (before, steps));
// Levels past the ends count as the ends.
assert_eq!(s.execute("object.path.smooth", &json!({"level": 250})).unwrap()["level"], json!(100.0));
}
+49
View File
@@ -314,6 +314,55 @@ pub fn smooth(path: &PathData, amount: f64) -> PathData {
out
}
/// The Smooth slider's level Auto-Smooth picks, halfway between minimum and maximum smoothing.
/// VectorCraft's own rule: the help page says only that Auto-Smooth smooths the path automatically
/// (https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/refine-path-segments-with-the-smooth-slider.html).
/// [`smooth_level`] already scales with each path's size, so one level suits every path.
pub const SMOOTH_AUTO_LEVEL: f64 = 50.0;
/// Object › Path › Smooth at slider level `level`, from 0 (minimum smoothing: the path stays within
/// 0.1 % of its longer side of where it was) to 100 (maximum). The help page names only the two
/// ends; the rest is VectorCraft's own rule. Each subpath is refitted with curves as Simplify does,
/// but keeping a corner only where the path turns back on itself (so jags and kinks become curves),
/// within a tolerance from 0.1 % of the path's longer side at level 0 to 2.5 % at level 100. A refit
/// never has more anchor points than the subpath had: a shape whose corners the tolerance can't
/// round, such as a rectangle, stays as it is. Closed subpaths stay closed, with no kink left where
/// they start, and open ones keep their end points. A level outside 0..=100 counts as the nearer end, and one that is not a number as
/// [`SMOOTH_AUTO_LEVEL`].
pub fn smooth_level(path: &PathData, level: f64) -> PathData {
let level = if level.is_finite() { level.clamp(0.0, 100.0) } else { SMOOTH_AUTO_LEVEL };
let size = path.bounds().map_or(0.0, |b| b.width().max(b.height()));
if !size.is_finite() {
return path.clone();
}
let opts = SimplifyOptions { tolerance: size * (0.001 + 0.024 * level / 100.0), corner_angle_deg: SMOOTH_CORNER_DEG, straight_lines: false };
let smoothed = |sp: &SubPath| {
let Some(mut fit) = simplify_subpath(sp, &opts).filter(|f| f != sp) else { return sp.clone() };
smooth_seam(&mut fit);
fit
};
PathData::new(path.subpaths.iter().map(smoothed).collect())
}
/// [`smooth_level`] keeps a corner only where the path turns by more than this (turns back).
const SMOOTH_CORNER_DEG: f64 = 179.0;
/// A closed subpath's refit leaves its start anchor with the tangents of the original segments on
/// either side of it, a kink when the path had one there: turn it into a smooth anchor with the same
/// handle lengths, unless the path turns back there.
fn smooth_seam(sp: &mut SubPath) {
if !sp.closed {
return;
}
let Some(a) = sp.anchors.first_mut() else { return };
let (Some(i), Some(o)) = (crate::fit::unit(a.p - a.h_in), crate::fit::unit(a.h_out - a.p)) else { return };
if i.dot(o) < SMOOTH_CORNER_DEG.to_radians().cos() {
return;
}
let Some(dir) = crate::fit::unit(i + o) else { return };
*a = Anchor::with_handles(a.p, a.p - dir * a.h_in.distance(a.p), a.p + dir * a.h_out.distance(a.p));
}
/// Remove anchors that can be deleted without changing the shape by more than `tolerance`
/// (collinear corner points, duplicate points, and curve joints that one cubic can replace).
pub fn remove_redundant_points(path: &PathData, tolerance: f64) -> PathData {
+2 -2
View File
@@ -20,8 +20,8 @@ pub use boolean::{
try_unite_all_with, unite_all, unite_all_with,
};
pub use edit::{
AverageAxis, SIMPLIFY_LEVEL, SimplifiedPart, SimplifyOptions, add_anchor_points, auto_simplify_level, average, join, remove_anchor,
remove_redundant_points, simplify, simplify_level_tolerance, simplify_part, simplify_with, smooth, split_into_grid,
AverageAxis, SIMPLIFY_LEVEL, SMOOTH_AUTO_LEVEL, SimplifiedPart, SimplifyOptions, add_anchor_points, auto_simplify_level, average, join,
remove_anchor, remove_redundant_points, simplify, simplify_level_tolerance, simplify_part, simplify_with, smooth, smooth_level, split_into_grid,
};
pub use offset::{Cap, Join, offset_path, outline_stroke, stroke_region};
pub use pathfinder::{FaceMerger, PathfinderOp, Region, Shape, merge_regions, pathfinder, pathfinder_with, region_at, regions};
+16 -1
View File
@@ -1,7 +1,7 @@
use std::f64::consts::PI;
use kurbo::{ParamCurve, Point, Rect, Shape as _};
use vectorcraft_geom::{FillRule, PathData, SubPath};
use vectorcraft_geom::{AnchorKind, FillRule, PathData, SubPath};
use vectorcraft_pathops::*;
const NZ: FillRule = FillRule::NonZero;
@@ -430,6 +430,21 @@ fn smooth_rounds_corners() {
assert_eq!(same, rect(0.0, 0.0, 10.0, 10.0));
}
/// Object › Path › Smooth: a rectangle, whose corners no curve rounds within the tolerance, stays as
/// it is even at maximum smoothing; a polygon of 16 sides becomes a closed curve of fewer anchors once the tolerance
/// passes its 0.96 pt sagitta, smooth all round.
#[test]
fn smooth_level_keeps_corners_it_cannot_round_and_curves_polygons() {
let r = rect(0.0, 0.0, 100.0, 50.0);
assert_eq!(smooth_level(&r, 100.0), r);
let pts: Vec<Point> = (0..16).map(|i| Point::new(50.0 * (f64::from(i) * PI / 8.0).cos(), 50.0 * (f64::from(i) * PI / 8.0).sin())).collect();
let polygon = PathData::single(SubPath::polyline(&pts, true));
let s = smooth_level(&polygon, SMOOTH_AUTO_LEVEL);
assert!(s.subpaths.len() == 1 && s.subpaths[0].closed && s.anchor_count() < 8, "{}", s.anchor_count());
assert!(s.subpaths[0].anchors.iter().all(|a| a.kind == AnchorKind::Smooth), "no kink, its start included: {s:?}");
assert_eq!(smooth_level(&polygon, 0.0), polygon, "0.1 pt is less than the sagitta");
}
#[test]
fn remove_redundant_collinear_and_curve() {
let p = PathData::single(SubPath::polyline(
+3 -2
View File
@@ -327,8 +327,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
if app.ui.view.rulers && app.ui.screen_mode < 3 {
ruler_bands(app, ui, &xf, (full, rect), origin_preview, &t);
}
// The task bar is not drawn while the Simplify slider shows.
if app.ui.task_bar && !revolve_edit && !app.session.tool_busy() && app.ui.screen_mode < 3 && !crate::dialogs::simplify_slider::showing(app) {
// The task bar is not drawn while the Simplify or Smooth slider shows.
let strip = crate::dialogs::simplify_slider::showing(app) || crate::dialogs::smooth_slider::showing(app);
if app.ui.task_bar && !revolve_edit && !app.session.tool_busy() && app.ui.screen_mode < 3 && !strip {
task_bar(app, ui, &xf);
}
if app.ui.screen_mode < 3 {
+2
View File
@@ -99,6 +99,7 @@ pub(crate) mod shapes;
pub mod shortcut_import;
pub(crate) mod simplify_slider;
pub mod slices;
pub(crate) mod smooth_slider;
pub mod spot_colors;
pub(crate) mod svg_options;
pub mod swatch_conflict;
@@ -352,6 +353,7 @@ registry! {
ScriptCheck: [script_check::KIND] => script_check::SPEC,
VectorHalftone: [halftone::KIND] => halftone::SPEC,
SimplifySlider: [simplify_slider::KIND] => simplify_slider::SPEC,
SmoothSlider: [smooth_slider::KIND] => smooth_slider::SPEC,
PerspectiveGrid: [perspective_grid::KIND] => perspective_grid::SPEC,
Envelope: [envelope::WARP, envelope::MESH, envelope::OPTIONS] => envelope::SPEC,
LiquifyOptions: [liquify::KIND] => liquify::SPEC,
+126 -80
View File
@@ -8,6 +8,8 @@
//! (https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/manually-simplify-paths.html).
//! `vectorcraft_pathops::simplify_level_tolerance` maps levels to tolerances.
//!
//! The strip itself ([`Strip`]) is shared with Object › Path › Smooth's slider (`smooth_slider`).
//!
//! Fields: `level` (0..=100, the command's parameter).
use egui::{Color32, CornerRadius, Order, Pos2, Sense, Stroke, vec2};
@@ -25,9 +27,122 @@ pub(crate) const KIND: &str = "simplifySlider";
pub(super) const SPEC: DialogSpec = DialogSpec { window: Some(show), confirm, preview: true, ..DialogSpec::FORM };
/// The command the slider previews, and its undo label.
const CMD: &str = "object.path.simplify";
const LABEL: &str = "Simplify";
/// What the Simplify slider previews, and its buttons.
const STRIP: Strip = Strip {
kind: KIND,
cmd: "object.path.simplify",
label: "Simplify",
default_level: SIMPLIFY_LEVEL,
auto: ("Auto-Simplify", auto),
more_options: Some(more_options),
};
/// A UI command a button of a strip runs.
type Button = fn(&mut VectorcraftApp) -> Result<Value, String>;
/// A slider strip under the selection that previews `cmd {level}` while it is open: its first
/// button picks the level `cmd {auto: true}` does, and an optional last one, More Options, opens a
/// dialog in its place. Enter or a click outside the strip keeps the result as one undo step, and
/// Escape puts the paths back.
pub(super) struct Strip {
/// The dialog kind.
pub kind: &'static str,
/// The command previewed.
pub cmd: &'static str,
/// The undo step's name, and the slider's.
pub label: &'static str,
/// The level when the field holds no number.
pub default_level: f64,
/// The auto button's tip, and what it runs.
pub auto: (&'static str, Button),
/// What More Options runs, when the strip has it.
pub more_options: Option<Button>,
}
impl Strip {
/// Whether this strip is open.
pub fn showing(&self, app: &VectorcraftApp) -> bool {
app.ui.dialog.as_ref().is_some_and(|d| d.kind == self.kind)
}
/// The open strip, if it is this one.
pub fn open(&self, app: &VectorcraftApp) -> Option<Dialog> {
app.ui.dialog.clone().filter(|d| d.kind == self.kind)
}
/// The slider's level, a whole number from 0 to 100.
fn level(&self, d: &Dialog) -> f64 {
let l = d.f64("level", self.default_level);
if l.is_finite() { l.round().clamp(0.0, 100.0) } else { self.default_level }
}
fn params(&self, d: &Dialog) -> Value {
json!({ "level": self.level(d) })
}
pub fn confirm(&self, app: &mut VectorcraftApp, d: &Dialog) -> DialogResult {
form::commit_preview(app, self.cmd, self.params(d))
}
/// The open strip `d` moves to the level `cmd {auto: true}` picks for the selection, and the
/// canvas previews it → {level}.
pub fn auto(&self, app: &mut VectorcraftApp, mut d: Dialog) -> Result<Value, String> {
let _ = app.session.begin_interaction(self.label);
let r = app.session.preview(self.cmd, &json!({ "auto": true })).map_err(|e| e.to_string())?;
let level = r["level"].as_f64().unwrap_or(self.default_level);
d.fields.insert("level".into(), json!(level));
d.fields.insert(form::PREVIEWED.into(), self.params(&d));
app.ui.dialog = Some(d);
Ok(json!({ "level": level }))
}
pub fn show(&self, app: &mut VectorcraftApp, ctx: &egui::Context) {
let Some(mut d) = app.ui.dialog.clone() else { return };
let t = Tokens::get(ctx);
let id = egui::Id::unique(("dialog", self.kind));
let b = bounds(app, &mut d);
let pos = place(app, ctx, id, b);
// A click anywhere else keeps the result, and the rest of the app doesn't take it.
let dim = modal::backdrop();
let outside =
egui::Area::new(dim.id).order(dim.order).fixed_pos(Pos2::ZERO).show(ctx, |ui| ui.allocate_rect(ctx.content_rect(), Sense::click())).inner;
let mut action: Option<Button> = None;
egui::Area::new(id).order(Order::Foreground).fixed_pos(pos).show(ctx, |ui| {
egui::Frame::NONE
.fill(t.panel)
.stroke(Stroke::new(1.0, t.tool_active))
.corner_radius(CornerRadius::same(5))
.inner_margin(egui::Margin::symmetric(8, 6))
.shadow(egui::epaint::Shadow { offset: [0, 3], blur: 10, spread: 0, color: Color32::from_black_alpha(70) })
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 6.0;
if widgets::icon_button(ui, "wand-sparkles", tl!(self.auto.0), false, 28.0).clicked() {
action = Some(self.auto.1);
}
let rail = t.input_border;
form::slider_rail_named(ui, &mut d, "level", tl!(self.label), 0.0..=100.0, &|_| rail);
if let Some(more) = self.more_options
&& widgets::icon_button(ui, "ellipsis", tl!("More Options"), false, 28.0).clicked()
{
action = Some(more);
}
});
});
});
let p = self.params(&d);
let _ = form::preview_now(app, &mut d, self.label, self.cmd, p);
app.ui.dialog = Some(d);
let r = match action {
Some(run) => run(app),
None if outside.clicked() || ctx.input(|i| i.key_pressed(egui::Key::Enter)) => super::confirm(app),
None => Ok(Value::Null),
};
if let Err(e) = r {
app.status(e);
}
}
}
/// The fields the slider opens with: Auto-Simplify's level for the selection, or
/// [`SIMPLIFY_LEVEL`] without a document.
@@ -37,46 +152,29 @@ pub(crate) fn fields(app: &VectorcraftApp) -> Value {
/// Whether the Simplify slider is open.
pub(crate) fn showing(app: &VectorcraftApp) -> bool {
app.ui.dialog.as_ref().is_some_and(|d| d.kind == KIND)
}
/// The slider's level, a whole number from 0 to 100.
fn level(d: &Dialog) -> f64 {
let l = d.f64("level", SIMPLIFY_LEVEL);
if l.is_finite() { l.round().clamp(0.0, 100.0) } else { SIMPLIFY_LEVEL }
}
fn params(d: &Dialog) -> Value {
json!({ "level": level(d) })
STRIP.showing(app)
}
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> DialogResult {
form::commit_preview(app, CMD, params(d))
STRIP.confirm(app, d)
}
/// The open slider, or an error when it isn't open.
fn open_slider(app: &VectorcraftApp) -> Result<Dialog, String> {
app.ui.dialog.clone().filter(|d| d.kind == KIND).ok_or_else(|| "the Simplify slider is not open".to_string())
fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
STRIP.show(app, ctx);
}
/// Auto-Simplify (`ui.simplifyAuto`): the open slider moves to the level `object.path.simplify
/// {auto: true}` picks for the selection, and the canvas previews it → {level}.
pub(crate) fn auto(app: &mut VectorcraftApp) -> Result<Value, String> {
let mut d = open_slider(app)?;
let _ = app.session.begin_interaction(LABEL);
let r = app.session.preview(CMD, &json!({ "auto": true })).map_err(|e| e.to_string())?;
let level = r["level"].as_f64().unwrap_or(SIMPLIFY_LEVEL);
d.fields.insert("level".into(), json!(level));
d.fields.insert(form::PREVIEWED.into(), params(&d));
app.ui.dialog = Some(d);
Ok(json!({ "level": level }))
let d = STRIP.open(app).ok_or_else(|| "the Simplify slider is not open".to_string())?;
STRIP.auto(app, d)
}
/// More Options (`ui.simplifyMoreOptions`): the Simplify dialog takes the slider's place, at the
/// slider's tolerance → {tolerance}.
pub(crate) fn more_options(app: &mut VectorcraftApp) -> Result<Value, String> {
let d = open_slider(app)?;
let tolerance = simplify_level_tolerance(level(&d));
let d = STRIP.open(app).ok_or_else(|| "the Simplify slider is not open".to_string())?;
let tolerance = simplify_level_tolerance(STRIP.level(&d));
app.ui.dialog = Some(Dialog::new("simplify", super::path_ops::simplify_dialog(tolerance)));
Ok(json!({ "tolerance": tolerance }))
}
@@ -115,55 +213,3 @@ fn place(app: &mut VectorcraftApp, ctx: &egui::Context, id: egui::Id, bounds: Op
let bounds = rect.shrink(8.0);
under.clamp(bounds.min, (bounds.max - size).max(bounds.min))
}
/// What a button of the strip asked for.
enum Action {
Auto,
MoreOptions,
}
fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
let Some(mut d) = app.ui.dialog.clone() else { return };
let t = Tokens::get(ctx);
let id = egui::Id::unique(("dialog", KIND));
let b = bounds(app, &mut d);
let pos = place(app, ctx, id, b);
// A click anywhere else keeps the result, and the rest of the app doesn't take it.
let dim = modal::backdrop();
let outside =
egui::Area::new(dim.id).order(dim.order).fixed_pos(Pos2::ZERO).show(ctx, |ui| ui.allocate_rect(ctx.content_rect(), Sense::click())).inner;
let mut action = None;
egui::Area::new(id).order(Order::Foreground).fixed_pos(pos).show(ctx, |ui| {
egui::Frame::NONE
.fill(t.panel)
.stroke(Stroke::new(1.0, t.tool_active))
.corner_radius(CornerRadius::same(5))
.inner_margin(egui::Margin::symmetric(8, 6))
.shadow(egui::epaint::Shadow { offset: [0, 3], blur: 10, spread: 0, color: Color32::from_black_alpha(70) })
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 6.0;
if widgets::icon_button(ui, "wand-sparkles", tl!("Auto-Simplify"), false, 28.0).clicked() {
action = Some(Action::Auto);
}
let rail = t.input_border;
form::slider_rail_named(ui, &mut d, "level", tl!("Simplify"), 0.0..=100.0, &|_| rail);
if widgets::icon_button(ui, "ellipsis", tl!("More Options"), false, 28.0).clicked() {
action = Some(Action::MoreOptions);
}
});
});
});
let p = params(&d);
let _ = form::preview_now(app, &mut d, LABEL, CMD, p);
app.ui.dialog = Some(d);
let r = match action {
Some(Action::Auto) => auto(app),
Some(Action::MoreOptions) => more_options(app),
None if outside.clicked() || ctx.input(|i| i.key_pressed(egui::Key::Enter)) => super::confirm(app),
None => Ok(Value::Null),
};
if let Err(e) = r {
app.status(e);
}
}
@@ -0,0 +1,57 @@
//! Object › Path › Smooth's slider, as the Smooth slider help page describes
//! (https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/refine-path-segments-with-the-smooth-slider.html):
//! the strip of Simplify's slider (`simplify_slider::Strip`) with Auto-Smooth and a slider from
//! minimum smoothing (left) to maximum smoothing (right). While it is open the canvas previews
//! `object.path.smooth {level}`; Enter or a click outside the strip keeps the result as one undo
//! step, and Escape puts the paths back. It opens at Auto-Smooth's level, as Simplify's slider
//! opens at Auto-Simplify's (VectorCraft's choice: the page doesn't say where it starts).
//!
//! Fields: `level` (0..=100, the command's parameter).
use serde_json::{Value, json};
use vectorcraft_engine::SMOOTH_AUTO_LEVEL;
use super::simplify_slider::Strip;
use super::{DialogResult, DialogSpec};
use crate::VectorcraftApp;
use crate::state::Dialog;
/// The dialog kind of the Smooth slider.
pub(crate) const KIND: &str = "smoothSlider";
pub(super) const SPEC: DialogSpec = DialogSpec { window: Some(show), confirm, preview: true, ..DialogSpec::FORM };
/// What the Smooth slider previews, and its button.
const STRIP: Strip = Strip {
kind: KIND,
cmd: "object.path.smooth",
label: "Smooth",
default_level: SMOOTH_AUTO_LEVEL,
auto: ("Auto-Smooth", auto),
more_options: None,
};
/// The fields the slider opens with: Auto-Smooth's level.
pub(crate) fn fields() -> Value {
json!({ "level": SMOOTH_AUTO_LEVEL })
}
/// Whether the Smooth slider is open.
pub(crate) fn showing(app: &VectorcraftApp) -> bool {
STRIP.showing(app)
}
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> DialogResult {
STRIP.confirm(app, d)
}
fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
STRIP.show(app, ctx);
}
/// Auto-Smooth (`ui.smoothAuto`): the open slider moves to the level `object.path.smooth {auto:
/// true}` picks, and the canvas previews it → {level}.
pub(crate) fn auto(app: &mut VectorcraftApp) -> Result<Value, String> {
let d = STRIP.open(app).ok_or_else(|| "the Smooth slider is not open".to_string())?;
STRIP.auto(app, d)
}
+32
View File
@@ -513,6 +513,38 @@ fn simplify_slider_and_dialog_simplify_only_the_direct_selected_part_of_a_path()
kept(&app, "the dialog");
}
/// Object › Path › Smooth opens its slider (#4115) at the Auto-Smooth level, previewing that
/// result; moving it previews the level, Auto-Smooth (the strip's only button) moves it back, and a
/// click outside keeps the result as one undo step.
#[test]
fn smooth_slider_previews_auto_smooth_and_keeps_the_result() {
let ctx = ctx();
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
let anchors: Vec<_> =
(0..=60).map(|i| json!({"x": 20 + i * 5, "y": 150.0 + 40.0 * (f64::from(i) / 10.0).sin() + f64::from(i % 2) * 3.0})).collect();
app.run("path.create", json!({"anchors": anchors})).unwrap();
let before = app.session.doc().unwrap().doc.clone();
let undo_len = app.session.doc().unwrap().history.undo.len();
assert!(app.run("ui.smoothAuto", json!({})).is_err(), "the slider is not open");
crate::menus::invoke(&mut app, "object.path.smooth", json!({}));
assert_eq!(app.ui.dialog.as_ref().map(|d| (d.kind.as_str(), d.f64("level", -1.0))), Some((smooth_slider::KIND, 50.0)));
settle(&ctx, &mut app);
assert_eq!(simplify_preview(&app), json!({"level": 50.0}));
assert!(anchor_count(&app) < 61 / 4, "the preview smooths the jags: {}", anchor_count(&app));
app.ui.dialog.as_mut().unwrap().fields.insert("level".into(), json!(0));
settle(&ctx, &mut app);
assert_eq!(simplify_preview(&app), json!({"level": 0.0}));
let strip = rect_of(&ctx, egui::Id::unique(("dialog", smooth_slider::KIND)));
click(&ctx, &mut app, Pos2::new(strip.left() + 8.0 + 14.0, strip.center().y));
assert_eq!(app.ui.dialog.as_ref().unwrap().f64("level", 0.0), 50.0);
assert_eq!(simplify_preview(&app), json!({"auto": true}));
click(&ctx, &mut app, Pos2::new(20.0, 20.0));
assert!(app.ui.dialog.is_none() && !app.session.in_interaction());
assert_eq!(app.session.doc().unwrap().history.undo.len(), undo_len + 1);
assert_ne!(app.session.doc().unwrap().doc, before);
}
/// More Options opens the Simplify dialog at the slider's tolerance. With "Open this dialog with
/// these settings next time" ticked, OK makes the menu item open the dialog on its settings; with
/// it unticked, the slider again.
+2
View File
@@ -2950,6 +2950,7 @@ font-technology Variable Променливи
Show Original Path Показване на оригиналния път
Open this dialog with these settings next time Отваряне на този прозорец с тези настройки следващия път
Auto-Simplify Автоматично опростяване
Auto-Smooth Автоматично изглаждане
Show Package Показване на пакет
Show Panel Показване на панел
Show Pattern Swatches Показване на образци за мостра
@@ -2984,6 +2985,7 @@ font-technology Variable Променливи
Simplify Опростяване
Simplify: Опростяване:
Simplify… Опростяване…
Smooth… Изглаждане…
Simulate Colored Paper Симулиране на цветна хартия
Simulate Paper Симулиране на хартия
Double Column Две колони
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -224,6 +224,7 @@
Offset Path… Odsadit cestu…
Reverse Path Direction Obrátit směr cesty
Simplify… Zjednodušit…
Smooth… Vyhladit…
Add Anchor Points Přidat kotevní body
Remove Anchor Points Odstranit kotevní body
Divide Objects Below Rozdělit objekty pod
@@ -1755,6 +1756,7 @@ lighting Light Světlo
Show Original Path Zobrazit původní cestu
Open this dialog with these settings next time Příště otevřít toto dialogové okno s tímto nastavením
Auto-Simplify Automaticky zjednodušit
Auto-Smooth Automaticky vyhladit
Transparency Options Možnosti průhlednosti
Color Mixer (Shift-click the chip) Míchání barev (Shift+kliknutí na políčko)
Fill: click for swatches, Shift-click for the color mixer Výplň: kliknutím otevřete vzorník, Shift+kliknutím míchání barev
@@ -4307,6 +4309,7 @@ imageTrace Outline Obrys
@msg videoRulerPixelAspectRatio must be a number from 0.1 to 10 videoRulerPixelAspectRatio musí být číslo od 0,1 do 10
@msg the Color Picker is not open Výběr barvy není otevřený
@msg the Simplify slider is not open Posuvník Zjednodušit není otevřený
@msg the Smooth slider is not open Posuvník Vyhladit není otevřený
@msg the screen color sampler is already open nástroj pro nabírání barvy z obrazovky už je otevřený
@msg this system has no screen color sampler tento systém nemá nástroj pro nabírání barvy z obrazovky
@msg bad colour `{hex}` (#rrggbb) chybná barva `{hex}` (#rrggbb)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -208,6 +208,7 @@
Offset Path… Forskyd kurve…
Reverse Path Direction Omvend kurveretning
Simplify… Forenkl…
Smooth… Udglat…
Add Anchor Points Tilføj ankerpunkter
Remove Anchor Points Fjern ankerpunkter
Divide Objects Below Opdel objekter nedenunder
@@ -5196,6 +5197,7 @@ swatch Bright Violet Kraftig violet
Show Original Path Vis oprindelig kurve
Open this dialog with these settings next time Åbn denne dialogboks med disse indstillinger næste gang
Auto-Simplify Forenkl automatisk
Auto-Smooth Udglat automatisk
@msg check a layer to export markér et lag, der skal eksporteres
@msg select the art to export first markér først grafikken, der skal eksporteres
@msg no saved selection recalled ingen gemt markering hentet
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -2981,6 +2981,7 @@ mark-style Japanese Japanisch
Show Original Path Originalpfad einblenden
Open this dialog with these settings next time Dieses Dialogfeld nächstes Mal mit diesen Einstellungen öffnen
Auto-Simplify Automatisch vereinfachen
Auto-Smooth Automatisch glätten
Color Panel Options Optionen für das Farbbedienfeld
Show Package Paket anzeigen
Show Panel Bedienfeld einblenden
@@ -3017,6 +3018,7 @@ mark-style Japanese Japanisch
Simplify Vereinfachen
Simplify: Vereinfachen:
Simplify… Vereinfachen…
Smooth… Glätten…
Simulate Colored Paper Farbiges Papier simulieren
Simulate Paper Papier simulieren
Double Column Zweispaltig
@@ -3924,6 +3926,7 @@ theme Appearance Erscheinungsbild
@msg videoRulerPixelAspectRatio must be a number from 0.1 to 10 videoRulerPixelAspectRatio muss eine Zahl von 0,1 bis 10 sein
@msg the Color Picker is not open der Farbwähler ist nicht geöffnet
@msg the Simplify slider is not open der Regler „Vereinfachen“ ist nicht geöffnet
@msg the Smooth slider is not open der Regler „Glätten“ ist nicht geöffnet
@msg the screen color sampler is already open die Farbaufnahme vom Bildschirm ist bereits geöffnet
@msg this system has no screen color sampler dieses System hat keine Farbaufnahme vom Bildschirm
@msg bad colour `{hex}` (#rrggbb) ungültige Farbe `{hex}` (#rrggbb)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -1866,6 +1866,7 @@ font-technology Variable Μεταβλητές
Simplify Απλοποίηση
Simplify: Απλοποίηση:
Simplify… Απλοποίηση…
Smooth… Εξομάλυνση…
Size Μέγεθος
Size: Μέγεθος:
Skew Λοξότητα
@@ -3424,6 +3425,7 @@ theme Appearance Εμφάνιση
Show Original Path Εμφάνιση αρχικής διαδρομής
Open this dialog with these settings next time Άνοιγμα αυτού του παραθύρου με αυτές τις ρυθμίσεις την επόμενη φορά
Auto-Simplify Αυτόματη απλοποίηση
Auto-Smooth Αυτόματη εξομάλυνση
Show Package Εμφάνιση συσκευασίας
Show Panel Εμφάνιση πίνακα
Show Pattern Swatches Εμφάνιση δειγμάτων μοτίβου
@@ -5300,6 +5302,7 @@ effect Points Σημεία
@msg span: "selection" places the gradient itself: leave out `start` and `end` (they already place it in document coordinates) span: το "selection" τοποθετεί το ίδιο το ντεγκραντέ: παραλείψτε τα `start` και `end` (το τοποθετούν ήδη σε συντεταγμένες εγγράφου)
@msg the Color Picker is not open ο επιλογέας χρώματος δεν είναι ανοιχτός
@msg the Simplify slider is not open ο ρυθμιστής Απλοποίησης δεν είναι ανοιχτός
@msg the Smooth slider is not open ο ρυθμιστής Εξομάλυνσης δεν είναι ανοιχτός
@msg the action has no steps η ενέργεια δεν έχει βήματα
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer τα επίπεδα του αρχείου δεν διαβάστηκαν ({_1}): εισάγεται ως η σελίδα του, σε ένα επίπεδο
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part τα επίπεδα του αρχείου δεν διαβάστηκαν από τα δεδομένα επεξεργασίας του ({_1}): προέρχονται από το τμήμα PDF του
Can't render this file because it contains an unexpected character in line 2071 and column 19.
+3
View File
@@ -3016,6 +3016,7 @@ font-technology Variable Variables
Show Original Path Mostrar trazado original
Open this dialog with these settings next time Abrir este cuadro de diálogo con estos ajustes la próxima vez
Auto-Simplify Simplificar automáticamente
Auto-Smooth Suavizar automáticamente
Color Panel Options Opciones del panel Color
Show Package Mostrar paquete
Show Panel Mostrar panel
@@ -3052,6 +3053,7 @@ font-technology Variable Variables
Simplify Simplificar
Simplify: Simplificar:
Simplify… Simplificar…
Smooth… Suavizar…
Simulate Colored Paper Simular papel de color
Simulate Paper Simular papel
Double Column Columna doble
@@ -4004,6 +4006,7 @@ axis Both Ambos
@msg videoRulerPixelAspectRatio must be a number from 0.1 to 10 videoRulerPixelAspectRatio debe ser un número de 0,1 a 10
@msg the Color Picker is not open el Selector de color no está abierto
@msg the Simplify slider is not open el regulador de Simplificar no está abierto
@msg the Smooth slider is not open el regulador de Suavizar no está abierto
@msg the screen color sampler is already open el muestreador de color de la pantalla ya está abierto
@msg this system has no screen color sampler este sistema no tiene muestreador de color de la pantalla
@msg bad colour `{hex}` (#rrggbb) color no válido `{hex}` (#rrggbb)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -231,6 +231,7 @@
Offset Path… Siirrä polkua…
Reverse Path Direction Käännä polun suunta
Simplify… Yksinkertaista…
Smooth… Tasoita…
Add Anchor Points Lisää ankkuripisteitä
Remove Anchor Points Poista ankkuripisteitä
Divide Objects Below Jaa alla olevat objektit
@@ -1718,6 +1719,7 @@ lighting Light Valo
Show Original Path Näytä alkuperäinen polku
Open this dialog with these settings next time Avaa tämä valintaikkuna näillä asetuksilla ensi kerralla
Auto-Simplify Yksinkertaista automaattisesti
Auto-Smooth Tasoita automaattisesti
Transparency Options Läpinäkyvyysasetukset
Color Mixer (Shift-click the chip) Värisekoitin (Vaihto-napsauta värilaattaa)
Fill: click for swatches, Shift-click for the color mixer Täyttö: napsauta avataksesi värimallit, Vaihto-napsauta avataksesi värisekoittimen
@@ -5312,6 +5314,7 @@ mark-style Japanese Japanilainen
@msg span: "selection" places the gradient itself: leave out `start` and `end` (they already place it in document coordinates) span: "selection" sijoittaa itse liukuvärin: jätä pois `start` ja `end` (ne sijoittavat sen jo asiakirjan koordinaatteihin)
@msg the Color Picker is not open värinvalitsin ei ole auki
@msg the Simplify slider is not open Yksinkertaista-liukusäädin ei ole auki
@msg the Smooth slider is not open Tasoita-liukusäädin ei ole auki
@msg the action has no steps toiminnossa ei ole vaiheita
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer tiedoston tasoja ei luettu ({_1}): se tuodaan sivunaan yhdelle tasolle
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part tiedoston tasoja ei luettu sen muokkaustiedoista ({_1}): ne tulevat sen PDF-osasta
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -3013,6 +3013,7 @@ font-technology Variable Variables
Show Original Path Afficher le tracé d’origine
Open this dialog with these settings next time Ouvrir cette boîte de dialogue avec ces réglages la prochaine fois
Auto-Simplify Simplifier automatiquement
Auto-Smooth Lisser automatiquement
Color Panel Options Options du panneau Couleur
Show Package Afficher le dossier d’assemblage
Show Panel Afficher le panneau
@@ -3049,6 +3050,7 @@ font-technology Variable Variables
Simplify Simplifier
Simplify: Simplifier :
Simplify… Simplifier…
Smooth… Lisser…
Simulate Colored Paper Simuler un papier de couleur
Simulate Paper Simuler le papier
Double Column Deux colonnes
@@ -4001,6 +4003,7 @@ axis Both Les deux
@msg videoRulerPixelAspectRatio must be a number from 0.1 to 10 videoRulerPixelAspectRatio doit être un nombre de 0,1 à 10
@msg the Color Picker is not open le Sélecteur de couleurs n’est pas ouvert
@msg the Simplify slider is not open le curseur Simplifier n’est pas ouvert
@msg the Smooth slider is not open le curseur Lisser n’est pas ouvert
@msg the screen color sampler is already open l’outil de prélèvement de couleur à l’écran est déjà ouvert
@msg this system has no screen color sampler ce système n’a pas d’outil de prélèvement de couleur à l’écran
@msg bad colour `{hex}` (#rrggbb) couleur non valide `{hex}` (#rrggbb)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -3640,6 +3640,7 @@ mark-style Japanese Japán
Show Original Path Eredeti útvonal megjelenítése
Open this dialog with these settings next time Legközelebb ez a párbeszédablak nyíljon meg ezekkel a beállításokkal
Auto-Simplify Automatikus egyszerűsítés
Auto-Smooth Automatikus simítás
Color Panel Options Szín panel beállításai
Show Package Csomag megjelenítése
Show Panel Panel megjelenítése
@@ -3683,6 +3684,7 @@ mark-style Japanese Japán
Simplify Egyszerűsítés
Simplify: Egyszerűsítés:
Simplify… Egyszerűsítés…
Smooth… Simítás…
Simulate Szimulálás
Simulate Colored Paper Színes papír szimulálása
Simulate Paper Papír szimulálása
@@ -4560,6 +4562,7 @@ imageTrace Outline Körvonal
@msg videoRulerPixelAspectRatio must be a number from 0.1 to 10 videoRulerPixelAspectRatio: 0,1 és 10 közötti szám kell
@msg the Color Picker is not open a Színválasztó nincs megnyitva
@msg the Simplify slider is not open az Egyszerűsítés csúszka nincs megnyitva
@msg the Smooth slider is not open a Simítás csúszka nincs megnyitva
@msg the screen color sampler is already open a képernyős színmintavevő már nyitva van
@msg this system has no screen color sampler ezen a rendszeren nincs képernyős színmintavevő
@msg bad colour `{hex}` (#rrggbb) hibás szín: `{hex}` (#rrggbb)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -208,6 +208,7 @@
Offset Path… Jalur ofset…
Reverse Path Direction Balikkan arah jalur
Simplify… Sederhanakan…
Smooth… Haluskan…
Add Anchor Points Tambahkan titik jangkar
Remove Anchor Points Hapus titik jangkar
Divide Objects Below Bagi objek di bawah
@@ -5196,6 +5197,7 @@ swatch Bright Violet Violet cerah
Show Original Path Tampilkan jalur asli
Open this dialog with these settings next time Buka dialog ini dengan pengaturan ini lain kali
Auto-Simplify Sederhanakan otomatis
Auto-Smooth Haluskan otomatis
@msg check a layer to export centang lapisan yang akan diekspor
@msg select the art to export first pilih dulu karya yang akan diekspor
@msg no saved selection recalled tidak ada seleksi tersimpan yang dipanggil
Can't render this file because it contains an unexpected character in line 3862 and column 19.
+3
View File
@@ -3050,6 +3050,7 @@ mark-style Japanese Giapponese
Show Original Path Mostra tracciato originale
Open this dialog with these settings next time Apri questa finestra di dialogo con queste impostazioni la prossima volta
Auto-Simplify Semplifica automaticamente
Auto-Smooth Smussa automaticamente
Color Panel Options Opzioni pannello Colore
Show Package Mostra pacchetto
Show Panel Mostra pannello
@@ -3086,6 +3087,7 @@ mark-style Japanese Giapponese
Simplify Semplifica
Simplify: Semplifica:
Simplify… Semplifica…
Smooth… Smussa…
Simulate Colored Paper Simula carta colorata
Simulate Paper Simula carta
Double Column Colonna doppia
@@ -4044,6 +4046,7 @@ axis Both Entrambi
@msg videoRulerPixelAspectRatio must be a number from 0.1 to 10 videoRulerPixelAspectRatio deve essere un numero da 0,1 a 10
@msg the Color Picker is not open il Selettore colore non è aperto
@msg the Simplify slider is not open il cursore Semplifica non è aperto
@msg the Smooth slider is not open il cursore Smussa non è aperto
@msg the screen color sampler is already open il campionatore di colore dello schermo è già aperto
@msg this system has no screen color sampler questo sistema non ha un campionatore di colore dello schermo
@msg bad colour `{hex}` (#rrggbb) colore non valido `{hex}` (#rrggbb)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -228,6 +228,7 @@ font-technology Variable バリアブル
Offset Path… パスのオフセット…
Reverse Path Direction パスの方向反転
Simplify… 単純化…
Smooth… スムーズ…
Add Anchor Points アンカーポイントの追加
Remove Anchor Points アンカーポイントの削除
Divide Objects Below 背面のオブジェクトで型抜き
@@ -2850,6 +2851,7 @@ mark-style Japanese 日本式
Show Original Path 元のパスを表示
Open this dialog with these settings next time 次回はこの設定でこのダイアログを開く
Auto-Simplify 自動単純化
Auto-Smooth 自動スムーズ
Color Panel Options カラーパネルオプション
Show Package パッケージを表示
Show Panel パネルを表示
@@ -4367,6 +4369,7 @@ swatch Bright Violet ビビッドバイオレット
@msg videoRulerPixelAspectRatio must be a number from 0.1 to 10 videoRulerPixelAspectRatio には 0.1 〜 10 の数値を指定してください
@msg the Color Picker is not open カラーピッカーが開いていません
@msg the Simplify slider is not open 単純化スライダーが開いていません
@msg the Smooth slider is not open スムーズスライダーが開いていません
@msg the screen color sampler is already open 画面のカラーサンプラーはすでに開いています
@msg this system has no screen color sampler このシステムには画面のカラーサンプラーがありません
@msg Corners edits the corners of a path 「コーナー」で編集できるのはパスの角だけです
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -173,6 +173,7 @@
Offset Path… ტრაექტორიის წანაცვლება…
Reverse Path Direction ტრაექტორიის მიმართულების შებრუნება
Simplify… გამარტივება…
Smooth… გაგლუვება…
Add Anchor Points საყრდენი წერტილების დამატება
Remove Anchor Points საყრდენი წერტილების მოცილება
Divide Objects Below ქვედა ობიექტების დაყოფა
@@ -4910,6 +4911,7 @@ imageTrace Outline კონტური
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part ფაილის შრეები რედაქტირების მონაცემებიდან ვერ წაიკითხა ({_1}): ისინი მისი PDF ნაწილიდან აიღება
@msg the Color Picker is not open ფერის ამრჩევი გახსნილი არ არის
@msg the Simplify slider is not open გამარტივების სლაიდერი გახსნილი არ არის
@msg the Smooth slider is not open გაგლუვების სლაიდერი გახსნილი არ არის
@msg the screen color sampler is already open ეკრანიდან ფერის აღება უკვე გახსნილია
@msg this system has no screen color sampler ამ სისტემას ეკრანიდან ფერის აღების საშუალება არ აქვს
# Dimension tool and Object › Dimensions. A drafting dimension is ზომა (ზომის დასმა), like a dimension on a technical drawing.
@@ -5070,6 +5072,7 @@ imageTrace Outline კონტური
Show Original Path საწყისი ტრაექტორიის ჩვენება
Open this dialog with these settings next time შემდეგ ჯერზე ამ დიალოგის ამ პარამეტრებით გახსნა
Auto-Simplify ავტომატური გამარტივება
Auto-Smooth ავტომატური გაგლუვება
Texture Image ტექსტურის გამოსახულება
@msg check a layer to export მონიშნეთ საექსპორტო შრე
@msg select the art to export first ჯერ მონიშნეთ საექსპორტო ნახატი
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -224,6 +224,7 @@ font-technology Variable 가변
Offset Path… 패스 오프셋…
Reverse Path Direction 패스 방향 반전
Simplify… 단순화…
Smooth… 매끄럽게…
Add Anchor Points 앵커 포인트 추가
Remove Anchor Points 앵커 포인트 제거
Divide Objects Below 아래 개체 나누기
@@ -2844,6 +2845,7 @@ mark-style Japanese 일본식
Show Original Path 원래 패스 표시
Open this dialog with these settings next time 다음에 이 설정으로 이 대화상자 열기
Auto-Simplify 자동 단순화
Auto-Smooth 자동 매끄럽게
Color Panel Options 색상 패널 옵션
Show Package 패키지 표시
Show Panel 패널 표시
@@ -4299,6 +4301,7 @@ theme Appearance 외관
@msg videoRulerPixelAspectRatio must be a number from 0.1 to 10 videoRulerPixelAspectRatio은(는) 0.1에서 10 사이의 숫자여야 합니다
@msg the Color Picker is not open 색상 피커가 열려 있지 않습니다
@msg the Simplify slider is not open 단순화 슬라이더가 열려 있지 않습니다
@msg the Smooth slider is not open 매끄럽게 슬라이더가 열려 있지 않습니다
@msg the screen color sampler is already open 화면 색상 추출기가 이미 열려 있습니다
@msg this system has no screen color sampler 이 시스템에는 화면 색상 추출기가 없습니다
@msg Corners edits the corners of a path 모퉁이는 패스의 모퉁이를 편집합니다
Can't render this file because it contains an unexpected character in line 2 and column 73.
+2
View File
@@ -222,6 +222,7 @@
Offset Path… Pad verschuiven…
Reverse Path Direction Padrichting omkeren
Simplify… Vereenvoudigen…
Smooth… Gladmaken…
Add Anchor Points Ankerpunten toevoegen
Remove Anchor Points Ankerpunten verwijderen
Divide Objects Below Objecten eronder verdelen
@@ -5207,6 +5208,7 @@ swatch Bright Violet Felviolet
Show Original Path Oorspronkelijk pad tonen
Open this dialog with these settings next time Dit dialoogvenster de volgende keer met deze instellingen openen
Auto-Simplify Automatisch vereenvoudigen
Auto-Smooth Automatisch gladmaken
@msg check a layer to export vink een laag aan om te exporteren
@msg select the art to export first selecteer eerst de illustratie die je wilt exporteren
@msg no saved selection recalled geen opgeslagen selectie opgeroepen
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -189,6 +189,7 @@
Offset Path… Odsuń ścieżkę…
Reverse Path Direction Odwróć kierunek ścieżki
Simplify… Uprość…
Smooth… Wygładź…
Add Anchor Points Dodaj punkty kontrolne
Remove Anchor Points Usuń punkty kontrolne
Divide Objects Below Podziel obiekty poniżej
@@ -1675,6 +1676,7 @@ lighting Light Światło
Show Original Path Pokaż oryginalną ścieżkę
Open this dialog with these settings next time Następnym razem otwórz to okno dialogowe z tymi ustawieniami
Auto-Simplify Uprość automatycznie
Auto-Smooth Wygładź automatycznie
Transparency Options Opcje przezroczystości
Color Mixer (Shift-click the chip) Mikser kolorów (kliknij próbkę z Shift)
Fill: click for swatches, Shift-click for the color mixer Wypełnienie: kliknij, aby wybrać próbkę, z Shift — mikser kolorów
@@ -4131,6 +4133,7 @@ imageTrace Outline Kontur
@msg videoRulerPixelAspectRatio must be a number from 0.1 to 10 videoRulerPixelAspectRatio musi być liczbą od 0,1 do 10
@msg the Color Picker is not open Próbnik kolorów nie jest otwarty
@msg the Simplify slider is not open Suwak Uprość nie jest otwarty
@msg the Smooth slider is not open Suwak Wygładź nie jest otwarty
@msg the screen color sampler is already open narzędzie do pobierania koloru z ekranu jest już otwarte
@msg this system has no screen color sampler ten system nie ma narzędzia do pobierania koloru z ekranu
@msg bad colour `{hex}` (#rrggbb) nieprawidłowy kolor `{hex}` (#rrggbb)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -2498,6 +2498,7 @@
Show Original Path Mostrar Caminho Original
Open this dialog with these settings next time Abrir este diálogo com estas configurações na próxima vez
Auto-Simplify Simplificar Automaticamente
Auto-Smooth Suavizar Automaticamente
Show Package Mostrar Pacote
Show Panel Mostrar Painel
Show Perspective Grid Mostrar Grade de Perspectiva
@@ -2535,6 +2536,7 @@
Simplify Simplificar
Simplify: Simplificar:
Simplify… Simplificar…
Smooth… Suavizar…
Simulate Colored Paper Simular Papel Colorido
Simulate Paper Simular Papel
Double Column Coluna Dupla
@@ -5255,6 +5257,7 @@ mark-style Japanese Japonesas
@msg Unknown dimension option {k} Opção de cota desconhecida {k}
@msg the Color Picker is not open o Seletor de Cores não está aberto
@msg the Simplify slider is not open o controle deslizante Simplificar não está aberto
@msg the Smooth slider is not open o controle deslizante Suavizar não está aberto
@msg the screen color sampler is already open o coletor de cores da tela já está aberto
@msg this system has no screen color sampler este sistema não tem coletor de cores da tela
Darkness Escuridão
Can't render this file because it contains an unexpected character in line 6 and column 47.
+3
View File
@@ -3046,6 +3046,7 @@ mark-style Japanese Японский
Show Original Path Показать исходный контур
Open this dialog with these settings next time Открывать это диалоговое окно с этими параметрами в следующий раз
Auto-Simplify Автоматическое упрощение
Auto-Smooth Автоматическое сглаживание
Color Panel Options Параметры панели «Цвет»
Show Package Показать пакет
Show Panel Показать панель
@@ -3082,6 +3083,7 @@ mark-style Japanese Японский
Simplify Упростить
Simplify: Упростить:
Simplify… Упростить…
Smooth… Сгладить…
Simulate Colored Paper Имитировать цветную бумагу
Simulate Paper Имитировать бумагу
Double Column Двойной столбец
@@ -4040,6 +4042,7 @@ axis Both Оба
@msg videoRulerPixelAspectRatio must be a number from 0.1 to 10 videoRulerPixelAspectRatio должно быть числом от 0,1 до 10
@msg the Color Picker is not open палитра цветов не открыта
@msg the Simplify slider is not open ползунок «Упростить» не открыт
@msg the Smooth slider is not open ползунок «Сгладить» не открыт
@msg the screen color sampler is already open средство выбора цвета с экрана уже открыто
@msg this system has no screen color sampler в этой системе нет средства выбора цвета с экрана
@msg bad colour `{hex}` (#rrggbb) недопустимый цвет `{hex}` (#rrggbb)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -2950,6 +2950,7 @@ font-technology Variable Variabla
Show Original Path Visa ursprunglig bana
Open this dialog with these settings next time Öppna den här dialogrutan med de här inställningarna nästa gång
Auto-Simplify Förenkla automatiskt
Auto-Smooth Jämna ut automatiskt
Show Package Visa paket
Show Panel Visa panel
Show Pattern Swatches Visa mönsterprover
@@ -2984,6 +2985,7 @@ font-technology Variable Variabla
Simplify Förenkla
Simplify: Förenkla:
Simplify… Förenkla…
Smooth… Jämna ut…
Simulate Colored Paper Simulera färgat papper
Simulate Paper Simulera papper
Double Column Dubbel kolumn
@@ -5304,6 +5306,7 @@ theme Appearance Utseende
@msg span: "selection" places the gradient itself: leave out `start` and `end` (they already place it in document coordinates) span: "selection" placerar själva gradienten: utelämna `start` och `end` (de placerar den redan i dokumentkoordinater)
@msg the Color Picker is not open färgväljaren är inte öppen
@msg the Simplify slider is not open Förenkla-reglaget är inte öppet
@msg the Smooth slider is not open Jämna ut-reglaget är inte öppet
@msg the action has no steps åtgärden har inga steg
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer filens lager lästes inte ({_1}): den hämtas in som en sida, i ett enda lager
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part filens lager lästes inte från dess redigeringsdata ({_1}): de kommer från dess PDF-del
Can't render this file because it contains an unexpected character in line 5 and column 25.
+3
View File
@@ -2891,6 +2891,7 @@ font-technology Variable Değişken
Simplify Basitleştir
Simplify: Basitleştirin:
Simplify… Basitleştir…
Smooth… Düzgünleştir…
Simulate Colored Paper Renkli Kağıdı Simüle Et
Simulate Paper Kağıdı Simüle Et
Double Column Çift Sütun
@@ -5249,11 +5250,13 @@ theme Appearance Görünüm
@msg nothing in data set {name} was applied: no object is bound to a variable it has a value for {name} veri kümesinden hiçbir şey uygulanmadı: değer içerdiği bir değişkene bağlı hiçbir nesne yok
@msg select an Objects on Path group bir Yol Üzerindeki Nesneler grubu seçin
@msg the Simplify slider is not open Basitleştirme kaydırıcısı açık değil
@msg the Smooth slider is not open Düzgünleştirme kaydırıcısı açık değil
@msg {_1} is an exporter (File › Export › Export As…) {_1} bir dışa aktarma eklentisidir (Dosya › Dışa Aktar › Farklı Dışa Aktar…)
@msg `documentData` is {size} bytes (limit {MAX_DOCUMENT_DATA}) `documentData` {size} bayt (sınır {MAX_DOCUMENT_DATA})
Alt key toggles to Smooth Tool Alt tuşu Düzleştirme Aracına geçiş yapar
Live curve fitting Çizerken eğri uydurma
Auto-Simplify Otomatik Basitleştir
Auto-Smooth Otomatik Düzgünleştir
Drag an item here, or right-click one, to put it in this group. Bu gruba eklemek için bir öğeyi buraya sürükleyin veya sağ tıklayın.
Drop here to take it out of its group Grup dışına çıkarmak için buraya bırakın
Locate Selected Objects Seçili Nesneleri Bul
Can't render this file because it contains an unexpected character in line 5 and column 25.
+3
View File
@@ -3042,6 +3042,7 @@ mark-style Japanese Японський
Show Original Path Показати початковий контур
Open this dialog with these settings next time Наступного разу відкривати це діалогове вікно з цими параметрами
Auto-Simplify Автоматичне спрощення
Auto-Smooth Автоматичне згладжування
Color Panel Options Параметри панелі «Колір»
Show Package Показати пакет
Show Panel Показати панель
@@ -3078,6 +3079,7 @@ mark-style Japanese Японський
Simplify Спростити
Simplify: Спростити:
Simplify… Спростити…
Smooth… Згладити…
Simulate Colored Paper Імітувати кольоровий папір
Simulate Paper Імітувати папір
Double Column Подвійний стовпець
@@ -4028,6 +4030,7 @@ axis Both Обидві
@msg videoRulerPixelAspectRatio must be a number from 0.1 to 10 videoRulerPixelAspectRatio має бути числом від 0,1 до 10
@msg the Color Picker is not open палітра кольорів не відкрита
@msg the Simplify slider is not open повзунок «Спростити» не відкрито
@msg the Smooth slider is not open повзунок «Згладити» не відкрито
@msg the screen color sampler is already open засіб вибору кольору з екрана вже відкритий
@msg this system has no screen color sampler у цій системі немає засобу вибору кольору з екрана
@msg bad colour `{hex}` (#rrggbb) неприпустимий колір `{hex}` (#rrggbb)
Can't render this file because it contains an unexpected character in line 749 and column 40.
+3
View File
@@ -3380,6 +3380,7 @@ mark-style Japanese Kiểu Nhật
Show Original Path Hiện đường dẫn gốc
Open this dialog with these settings next time Lần sau mở hộp thoại này với các cài đặt này
Auto-Simplify Tự động đơn giản hóa
Auto-Smooth Tự động làm mượt
Color Panel Options Tùy chọn bảng màu
Show Package Hiện gói
Show Panel Hiện bảng
@@ -3420,6 +3421,7 @@ mark-style Japanese Kiểu Nhật
Simplify Đơn giản hóa
Simplify: Đơn giản hóa:
Simplify… Đơn giản hóa…
Smooth… Làm mượt…
Simulate Colored Paper Giả lập giấy có màu
Simulate Paper Giả lập giấy
Single Left Quotation Mark Dấu nháy đơn mở
@@ -4210,6 +4212,7 @@ theme Appearance Giao diện
@msg videoRulerPixelAspectRatio must be a number from 0.1 to 10 videoRulerPixelAspectRatio phải là một số từ 0,1 đến 10
@msg the Color Picker is not open Bộ chọn màu chưa được mở
@msg the Simplify slider is not open Thanh trượt Đơn giản hóa chưa được mở
@msg the Smooth slider is not open Thanh trượt Làm mượt chưa được mở
@msg the screen color sampler is already open công cụ lấy mẫu màu trên màn hình đã được mở
@msg this system has no screen color sampler hệ thống này không có công cụ lấy mẫu màu trên màn hình
@msg bad colour `{hex}` (#rrggbb) màu không hợp lệ `{hex}` (#rrggbb)
Can't render this file because it contains an unexpected character in line 4079 and column 10.
+3
View File
@@ -2882,6 +2882,7 @@ font-script Japanese 汉字
Show Original Path 显示原始路径
Open this dialog with these settings next time 下次以这些设置打开此对话框
Auto-Simplify 自动简化
Auto-Smooth 自动平滑
Color Panel Options 颜色面板选项
Show Package 显示打包
Show Panel 显示面板
@@ -2918,6 +2919,7 @@ font-script Japanese 汉字
Simplify 简化
Simplify: 简化:
Simplify… 简化…
Smooth… 平滑…
Simulate Colored Paper 模拟有色纸张
Simulate Paper 模拟纸张
Double Column 双列
@@ -4098,6 +4100,7 @@ imageTrace Outline 轮廓
@msg videoRulerPixelAspectRatio must be a number from 0.1 to 10 videoRulerPixelAspectRatio 必须是 0.1 到 10 之间的数字
@msg the Color Picker is not open 拾色器未打开
@msg the Simplify slider is not open 简化滑块未打开
@msg the Smooth slider is not open 平滑滑块未打开
@msg the screen color sampler is already open 屏幕取色器已打开
@msg this system has no screen color sampler 此系统没有屏幕取色器
@msg Corners edits the corners of a path 转角用于编辑路径的转角
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -2055,6 +2055,7 @@ file-filter Templates 範本
@msg videoRulerPixelAspectRatio must be a number from 0.1 to 10 videoRulerPixelAspectRatio 必須是 0.1 到 10 之間的數字
@msg the Color Picker is not open 「顏色選擇器」未開啟
@msg the Simplify slider is not open 「簡化」滑桿未開啟
@msg the Smooth slider is not open 「平滑」滑桿未開啟
@msg the screen color sampler is already open 螢幕取色器已開啟
@msg this system has no screen color sampler 此系統沒有螢幕取色器
@msg Corners edits the corners of a path 「轉角」用於編輯路徑的轉角
@@ -3831,6 +3832,7 @@ font-script Japanese 漢字
Show Original Path 顯示原始路徑
Open this dialog with these settings next time 下次以這些設定開啟此對話方塊
Auto-Simplify 自動簡化
Auto-Smooth 自動平滑
Color Panel Options 顏色面板選項
Show Package 顯示封裝
Show Panel 顯示面板
@@ -3867,6 +3869,7 @@ font-script Japanese 漢字
Simplify 簡化
Simplify: 簡化:
Simplify… 簡化…
Smooth… 平滑…
Simulate Colored Paper 模擬有色紙張
Simulate Paper 模擬紙張
Double Column 雙欄
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -54,6 +54,7 @@ pub(super) fn menu_dialog(app: &VectorcraftApp, id: &str) -> Option<(&'static st
// "Open this dialog with these settings next time" was ticked.
"object.path.simplify" if app.ui.simplify.is_object() => ("simplify", app.ui.simplify.clone()),
"object.path.simplify" => (crate::dialogs::simplify_slider::KIND, crate::dialogs::simplify_slider::fields(app)),
"object.path.smooth" => (crate::dialogs::smooth_slider::KIND, crate::dialogs::smooth_slider::fields()),
"object.path.splitIntoGrid" => {
// The grid starts at the size of the selection.
let size =
+1
View File
@@ -695,6 +695,7 @@ fn run_ui_part10(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result
"ui.expandDialog" => crate::dialogs::expand::open(app),
"ui.simplifyAuto" => crate::dialogs::simplify_slider::auto(app),
"ui.simplifyMoreOptions" => crate::dialogs::simplify_slider::more_options(app),
"ui.smoothAuto" => crate::dialogs::smooth_slider::auto(app),
"attributes.openUrl" => crate::panels::attributes::open_url(app, p),
"ui.javascriptFilesDialog" => crate::panels::svg_interactivity::files_dialog(app, p),
"ui.spotColors" => crate::dialogs::spot_colors::open(app),
+1
View File
@@ -237,6 +237,7 @@ pub fn enabled(app: &VectorcraftApp, id: &str) -> bool {
"ui.spotColors" => app.session.active().is_some(),
"ui.menuDialog" => app.session.active().is_some_and(|d| !d.selection.is_empty()),
"ui.simplifyAuto" | "ui.simplifyMoreOptions" => crate::dialogs::simplify_slider::showing(app),
"ui.smoothAuto" => crate::dialogs::smooth_slider::showing(app),
"ui.dxfOptionsDialog" => app.session.active().is_some(),
"links.editOriginal" | "links.reveal" => selected_image(app, |im| im.link.is_some()) || selected_placed(app),
"ui.placementOptionsDialog" => selected_image(app, |_| true) || selected_placed(app),
+1
View File
@@ -219,6 +219,7 @@ pub fn menu_tree_named(english_names: bool) -> Vec<(&'static str, Vec<Item>)> {
c("Reverse Path Direction", "path.reverse"),
Sep,
c("Simplify…", "object.path.simplify"),
c("Smooth…", "object.path.smooth"),
c("Add Anchor Points", "object.path.addAnchorPoints"),
c("Remove Anchor Points", "path.removeAnchors"),
c("Divide Objects Below", "object.path.divideObjectsBelow"),
+7 -1
View File
@@ -838,7 +838,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"ui.menuDialog",
"Menu Dialog",
"",
"{command: object.move|object.rotate|object.scale|object.reflect|object.shear|object.transformEach|path.average|object.path.offsetPath|object.path.simplify|object.path.splitIntoGrid|object.vectorHalftone|artboard.rearrange|object.envelope.makeWithWarp|object.envelope.resetWithWarp|object.envelope.makeWithMesh|object.envelope.resetWithMesh|object.envelope.options} open the dialog that command's menu item opens (dialog kind: move, rotate, scale, reflect, shear, transformEach, …, vectorHalftone, rearrangeArtboards, envelopeWarp, envelopeMesh, envelopeOptions; Scale and Transform Each have `corners` and `strokes`, from the preferences, which OK updates; Transform Each opens on the values it was last confirmed with (not `copy`), and its Copy button confirms with `copy: true`; the envelope dialogs start from the selected envelope, Make opens as Reset (`reset: true`) while one is selected)",
"{command: object.move|object.rotate|object.scale|object.reflect|object.shear|object.transformEach|path.average|object.path.offsetPath|object.path.simplify|object.path.smooth|object.path.splitIntoGrid|object.vectorHalftone|artboard.rearrange|object.envelope.makeWithWarp|object.envelope.resetWithWarp|object.envelope.makeWithMesh|object.envelope.resetWithMesh|object.envelope.options} open the dialog that command's menu item opens (dialog kind: move, rotate, scale, reflect, shear, transformEach, …, vectorHalftone, rearrangeArtboards, envelopeWarp, envelopeMesh, envelopeOptions; Scale and Transform Each have `corners` and `strokes`, from the preferences, which OK updates; Transform Each opens on the values it was last confirmed with (not `copy`), and its Copy button confirms with `copy: true`; the envelope dialogs start from the selected envelope, Make opens as Reset (`reset: true`) while one is selected)",
),
(
"ui.widthPointEdit",
@@ -858,6 +858,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
"",
"{} the open Simplify slider (Object › Path › Simplify opens it: dialog `simplifySlider` with field `level` 0..100, object.path.simplify's level, opening at the level object.path.simplify {auto: true} picks for the selection, previewed on the canvas until ui.dialog.confirm keeps it as one undo step; after OK in the Simplify dialog with `retain` ticked, the menu item opens the dialog `simplify` with the fields of that OK instead) moves to the level object.path.simplify {auto: true} picks for the selection and previews it → {level}; an error when the slider isn't open",
),
(
"ui.smoothAuto",
"Auto-Smooth",
"",
"{} the open Smooth slider (Object › Path › Smooth opens it: dialog `smoothSlider` with field `level` 0..100, object.path.smooth's level, opening at the level object.path.smooth {auto: true} picks, previewed on the canvas until ui.dialog.confirm keeps it as one undo step) moves to the level object.path.smooth {auto: true} picks and previews it → {level}; an error when the slider isn't open",
),
(
"ui.simplifyMoreOptions",
"More Options",
+17
View File
@@ -2569,6 +2569,23 @@ dialog `simplifySlider` with field `level`) at the Auto-Simplify level for the s
opens the Simplify dialog at the slider's tolerance. When OK confirms the dialog with `retain` ticked, the menu item opens
the dialog on those settings next time.
## Smooth
`object.path.smooth` smooths the selected paths (or the objects in `ids`), compound path members included, as one undo
step. `level` is the Smooth slider's, a whole number from 0 (minimum smoothing: the path stays within 0.1% of its longer
side) to 100 (maximum smoothing); `auto: true` (Auto-Smooth) uses level 50, the default. Each path is refitted with
curves within 0.1% to 2.5% of its longer side, keeping a corner only where it turns back on itself, so jags and kinks
become curves; a refit never has more anchor points than the path had, so a shape whose corners the tolerance can't
round, such as a rectangle, stays as it is. Closed paths stay closed and open paths keep their end points. The reply has the anchor counts `before` and `after` and the `level` used:
```json
{"name":"run_command","arguments":{"command":"object.path.smooth","params":{"level":80}}}
```
In the desktop app, Object › Path › Smooth opens the slider (`ui.menuDialog {command: "object.path.smooth"}`, dialog
`smoothSlider` with field `level`) at the Auto-Smooth level and previews the result until `ui.dialog.confirm`;
`ui.smoothAuto` moves the open slider back to the Auto-Smooth level.
## Editing a path with the Curvature tool
The Curvature tool edits any selected path, whatever drew it, keeping its shape except where edited (#798). Each edit
+2
View File
@@ -786,6 +786,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
The Simplify dialog also has Corner Point Angle Threshold, Convert to Straight Lines and Show Original Path. The threshold is a slider from 0° to 180° and opens at 150°: an anchor whose corner is sharper than the threshold stays a corner, so moving the slider right keeps more corners, and the dialog sends `cornerAngle` as 180° less the threshold. While Preview is on, Show Original Path draws the paths as they were before the preview in a wide, faint band of their layer's color under the selection's outlines.
Object › Path › Simplify opens a slider under the selection ([Manually simplify paths](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/manually-simplify-paths.html), [Auto simplify paths](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/auto-simplify-paths.html)) with Auto-Simplify, a rail from the fewest anchor points to the closest fit, and More Options, which opens the dialog at the slider's tolerance. The rail's levels are VectorCraft's own scale (`object.path.simplify {level}`): level 50 is 1 pt, and every 10 levels halve or double the tolerance, from 32 pt at 0 to 1/32 pt at 100. Auto-Simplify picks the lowest level whose tolerance is at most 1/400 of the selection's longer side, or level 100 when that side is under 12.5 pt; the slider opens at that level, with its result previewed, as the manual-simplify page describes. The dialog's "Open this dialog with these settings next time" makes the menu item open the dialog on the settings of its last OK.
With anchors or segments of a path selected with Direct Selection, Simplify acts on just that part of the path, as the [Manually simplify paths](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/manually-simplify-paths.html) page puts it: the slider, Auto-Simplify and the dialog refit only each run of selected segments, between its end anchors, which keep their place and their handles on the side away from the run. The anchors and segments outside the runs stay exactly as they were, a closed path's run may cross its start point, separate runs on one path are refitted independently in one undo step, and the runs stay selected. A path with only lone anchors selected is still simplified whole, and `object.path.simplify {wholePaths: true}` simplifies partly selected paths whole. Tests: `crates/engine/src/tests/simplify_part.rs` and `simplify_slider_and_dialog_simplify_only_the_direct_selected_part_of_a_path`.
Object › Path › Smooth… (#4115) opens a slider under the selection from Minimum to Maximum smoothing with an Auto-Smooth button, as the [Smooth slider](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/refine-path-segments-with-the-smooth-slider.html) page describes; the canvas previews it and Enter or a click outside keeps it as one undo step (`object.path.smooth {level, auto, ids}`). The page names only the ends, so the rest is VectorCraft's own rule: each selected path, compound path members included, is refitted with curves within 0.1% (level 0) to 2.5% (level 100) of its longer side, corners kept only where it turns back and never with more anchors (a rectangle stays as it is); Auto-Smooth is level 50. Closed paths stay closed and open ones keep their end points. Tests: `crates/engine/src/tests/smooth_path.rs` and `smooth_slider_previews_auto_smooth_and_keeps_the_result`.
- **Devanagari shaping:** with an OpenType Devanagari font, Ka, virama and Ssa (क्ष) form one conjunct glyph, Ra and virama before a consonant (र्क) become a reph drawn after it, the i matra (कि) is drawn before its consonant, and Ka with a nukta is drawn like the precomposed क़ (U+0958), as [Microsoft's Devanagari shaping specification](https://learn.microsoft.com/en-us/typography/script-development/devanagari) describes. Each syllable is one cluster of its source bytes, and the arrow keys stop at its ends. A click inside a syllable drawn with several glyphs puts the caret at the syllable's nearer end, in point, area and path type. Tests: `crates/text/src/tests/devanagari.rs`; they print SKIPPED when no such font is installed.
- **Vertical punctuation:** fullwidth `!` (U+FF01) and `?` (U+FF1F) use their nominal glyph upright when no vertical alternate is present, following the Tu fallback in [Unicode UAX #50 §3.1](https://www.unicode.org/reports/tr50/#vo). Code-built test fonts cover point and area type with and without `vert` alternates, retain the rotated fallback for a bracket and long vowel mark, and leave horizontal type unchanged. Tests: `crates/text/src/tests/vertical.rs`.
- Free Distort's dialog (Effect › Distort & Transform › Free Distort…) shows a box in the proportions of the first selected object's bounds with a handle on each corner. Dragging a handle moves that corner. A handle focused by a click or Tab moves 1% of the box with each arrow key, and 10% with Shift. Reset puts the four corners back on the box. The preview and OK run `effect.apply`, or `effect.setParams` when the dialog edits an applied effect, and the Appearance panel's inline editor opens the dialog with Edit…. Behavior reference: Illustrator CC Help (2014, [PDF](https://help.adobe.com/archive/en/illustrator/cc/2014/illustrator_reference.pdf)), page 285 (PDF page 291).
@@ -1168,6 +1169,7 @@ applies to fills). Tests: `color::gradient::tests` (perceptual midpoint, expansi
| Date | Change | Summary |
|---|---|---|
| 2026-10-29 | minor | Object › Path › Smooth… with its slider and Auto-Smooth (#4115) added to the evidence; score and hours unchanged. |
| 2026-10-26 | minor | File formats: EPS Save and Save As: an opened EPS saves back to its own path as EPS, keeping the chosen artboard's page frame and transparent margins and the complete editable VectorCraft document; Save's TIFF preview is capped at 1024 px (Export keeps 2048); partly read, preview-only or damaged-editing-data sources still need confirmation before they are overwritten; evidence added ([file-format-parity.md](file-format-parity.md#eps-save-and-source-protection)); writing Illustrator's editing data (#868) and the `.ai` structure issues stay in the missing items; score and hours unchanged (estimated) |
| 2026-10-26 | minor | View & navigation: New View… / Edit Views… taken off the missing items (all 25 saved views in the View menu, an Edit Views… list dialog, views checked on open); evidence added; score and hours unchanged. |
| 2026-10-25 | minor | Type core: a double-click on a threaded port breaks the thread there (`text.thread.break`); evidence added; scores unchanged. |
+2
View File
@@ -99,6 +99,7 @@ tools, never checked side by side) isn't captured row by row.
| Smooth tool smooths only the stretch of path under the drag | done | #1512 |
| Drawing modes apply to pastes: Draw Inside takes Paste, Paste in Place and Paste on All Artboards inside the object; Draw Behind puts Paste in Place and Paste on All Artboards behind the selection; Shift+D cycles past Draw Behind with any selection ([drawing modes](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/learn-drawing-basics/drawing-modes-overview.html)) | done | #3705; `pasting_in_draw_inside_puts_the_copies_inside_the_object`, `paste_in_place_in_draw_behind_goes_behind_the_selection`, `shift_d_goes_past_draw_behind_whatever_is_selected` |
| Simplify (slider, auto, corner angle threshold), of a whole path or of the part of it selected with Direct Selection ([manually simplify paths](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/manually-simplify-paths.html)) | done | Tolerance, default-on Preview and original/new point counts (#1272); Corner Point Angle Threshold, Convert to Straight Lines and Show Original Path; the slider under the selection with Auto-Simplify and More Options, opening at the Auto-Simplify level for the selection with that result previewed, and the dialog's option to open it directly with its last settings. With anchors or segments direct-selected, the slider, Auto-Simplify and the dialog refit only each run of selected segments between its end anchors, a closed path's run across its start point included; the rest of the path stays exactly as it was (`tests/simplify_part.rs`, `simplify_slider_and_dialog_simplify_only_the_direct_selected_part_of_a_path`) |
| Smooth (Object › Path › Smooth…: slider from minimum to maximum smoothing, Auto-Smooth) ([smooth slider](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/refine-path-segments-with-the-smooth-slider.html)) | done | #4115: the slider under the selection previews `object.path.smooth {level}`, Auto-Smooth picks level 50, Enter or a click outside keeps one undo step and Escape restores; the smoothing rule beyond the two ends is VectorCraft's own (`tests/smooth_path.rs`, `smooth_slider_previews_auto_smooth_and_keeps_the_result`) |
| Draw Inside in isolation mode: art drawn or pasted inside the isolated object takes isolation mode into its clip group, clickable and undimmed ([isolation mode](https://helpx.adobe.com/illustrator/desktop/manage-objects/select-objects/isolate-objects.html), [drawing modes](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/learn-drawing-basics/drawing-modes-overview.html)) | done | #3705; `drawing_inside_an_isolated_object_keeps_the_new_art_in_isolation_mode` |
| Simplify (slider, auto, corner angle threshold) | partial | Tolerance, default-on Preview and original/new point counts (#1272); Corner Point Angle Threshold, Convert to Straight Lines and Show Original Path; the slider under the selection with Auto-Simplify and More Options, and the dialog's option to open it directly with its last settings; the slider opens at 1 pt rather than at the Auto-Simplify level, and simplifying only a Direct Selection part of a path remains missing |
| Simplify (slider, auto, corner angle threshold) | partial | Tolerance, default-on Preview and original/new point counts (#1272); Corner Point Angle Threshold, Convert to Straight Lines and Show Original Path; the slider under the selection with Auto-Simplify and More Options, opening at the Auto-Simplify level for the selection with that result previewed, and the dialog's option to open it directly with its last settings; simplifying only a Direct Selection part of a path remains missing |
@@ -357,6 +358,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
| Date | Change | Summary |
|---|---|---|
| 2026-10-29 | minor | Object › Path › Smooth… with its slider and Auto-Smooth (#4115); scores and estimates unchanged |
| 2026-10-25 | minor | Selection tool: a double-click on a threaded port breaks the thread there (`text.thread.break`); scores and estimates unchanged |
| 2026-10-25 | minor | A selected Grid Repeat shows handles: the one below the grid adds or removes rows, the one right of it columns, the one past its bottom-right corner both, and the sliders between the first two columns and rows set the horizontal and vertical spacing; each drag is one undo step and Escape cancels it (#2691); headless canvas and tool regressions; scores and estimates unchanged. |
| 2026-10-25 | minor | Floating panels open in OS windows of their own on the desktop (inside the window on the web and on Wayland); saved layouts from earlier versions convert to the shared docking layout (#4135); scores and estimates unchanged (estimated) |