Merge pull request #4182 from dotCooCoo/feat/pencil-live-curve-fitting

Pencil: Live curve fitting in Pencil Tool Options shows the stroke's points until the release fits them
This commit is contained in:
BFlat
2026-10-26 09:19:54 +07:00
committed by GitHub
30 changed files with 199 additions and 16 deletions
+20 -8
View File
@@ -26,7 +26,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Pencil",
[],
None,
"{points: [[x,y]…] or [[x,y,pressure]…] (pen pressure 0..1: the stroke keeps it, for Calligraphic brushes with Pressure variation; a point without one presses fully), fidelity?: pt (1.5), closed?, style?: \"pencil\"|\"brush\", fill?: bool, keepSelected?: bool (true: the drawn or continued path is selected; false: it is not, and the rest of the selection stays), extend?: {id, end: \"start\"|\"end\"}, redraw?: {id, within?: pt (12)}} fit a freehand stroke → {id}. extend continues the path from that end; redraw (Edit Selected Paths) replaces the section of the path between where the stroke starts and ends, each within `within` of one subpath (the shorter way round a closed one), and draws a new path when the stroke doesn't end on it",
"{points: [[x,y]…] or [[x,y,pressure]…] (pen pressure 0..1: the stroke keeps it, for Calligraphic brushes with Pressure variation; a point without one presses fully), fidelity?: pt (1.5), fit?: bool (true; false: the points are joined by straight segments, as the Pencil previews a stroke with Live curve fitting off), closed?, style?: \"pencil\"|\"brush\", fill?: bool, keepSelected?: bool (true: the drawn or continued path is selected; false: it is not, and the rest of the selection stays), extend?: {id, end: \"start\"|\"end\"}, redraw?: {id, within?: pt (12)}} fit a freehand stroke → {id}. extend continues the path from that end; redraw (Edit Selected Paths) replaces the section of the path between where the stroke starts and ends, each within `within` of one subpath (the shorter way round a closed one), and draws a new path when the stroke doesn't end on it",
has_doc,
freehand
),
@@ -575,11 +575,21 @@ fn fit_freehand(pts: &[Point], tol: f64, closed: bool) -> SubPath {
fitted.subpaths.into_iter().next().filter(|s| s.anchors.len() >= 2).unwrap_or(sp)
}
/// The path of a freehand stroke: fitted at `tol` ([`fit_freehand`]), or with no `tol` the points
/// joined by straight segments (`path.freehand` with `fit: false`).
fn stroke_subpath(pts: &[Point], tol: Option<f64>, closed: bool) -> SubPath {
match tol {
Some(tol) => fit_freehand(pts, tol, closed),
None => SubPath::new(pts.iter().map(|&p| Anchor::corner(p)).collect(), closed),
}
}
/// Pencil's Edit Selected Paths in the middle of a path: a stroke from one point of a subpath of
/// `pd` to another, each within `within` of it, replaces the section between them (the shorter
/// way round a closed subpath) with the stroke fitted at `tol`. The subpath's index and its new
/// shape; None when the stroke doesn't start and end on the same subpath, or meets it once.
fn redraw_section(pd: &PathData, pts: &[Point], tol: f64, within: f64) -> Option<(usize, SubPath)> {
/// way round a closed subpath) with the stroke fitted at `tol` (with no `tol`, unfitted). The
/// subpath's index and its new shape; None when the stroke doesn't start and end on the same
/// subpath, or meets it once.
fn redraw_section(pd: &PathData, pts: &[Point], tol: Option<f64>, within: f64) -> Option<(usize, SubPath)> {
use kurbo::ParamCurveArclen;
let (first, last) = (*pts.first()?, *pts.last()?);
let (si, _, _, _, d_a) = pd.nearest(first)?;
@@ -633,7 +643,7 @@ fn redraw_section(pd: &PathData, pts: &[Point], tol: f64, within: f64) -> Option
*s0 = s1.p;
let (Some(e0), Some(e1)) = (stroke.last_mut(), sp.anchors.get(end)) else { return None };
*e0 = e1.p;
let fit = fit_freehand(&stroke, tol, false).anchors;
let fit = stroke_subpath(&stroke, tol, false).anchors;
let (Some(f0), Some(f1)) = (fit.first().copied(), fit.last().copied()) else { return None };
let inner = fit.get(1..fit.len().saturating_sub(1)).unwrap_or_default();
let (before, after): (Vec<Anchor>, Vec<Anchor>) = if sp.closed {
@@ -665,6 +675,8 @@ fn freehand(s: &mut Session, p: &Value) -> Result<Value> {
let pressure = stroke_pressure(&samples);
let mut pts: Vec<Point> = samples.into_iter().map(|(pt, _)| pt).collect();
let tol = number_or(p, "fidelity", 1.5, C)?.clamp(0.05, 100.0);
// The tolerance the stroke is fitted at; with `fit: false` it is not fitted.
let fit_tol = switch_or(p, "fit", true, C)?.then_some(tol);
let closed = switch_or(p, "closed", false, C)?;
let brush = str_param(p, "style") == Some("brush");
let keep_selected = switch_or(p, "keepSelected", true, C)?;
@@ -692,7 +704,7 @@ fn freehand(s: &mut Session, p: &Value) -> Result<Value> {
if closing && let Some(l) = pts.last_mut() {
*l = first;
}
let fit = fit_freehand(&pts, tol, false);
let fit = stroke_subpath(&pts, fit_tol, false);
let mut add = fit.anchors;
if let (Some(last), Some(a0)) = (sp.anchors.last_mut(), add.first()) {
last.h_out = a0.h_out;
@@ -748,7 +760,7 @@ fn freehand(s: &mut Session, p: &Value) -> Result<Value> {
let (pd, _) = path_of(&s.doc()?.doc, id)?;
// shortcut: a stroke pressure profile is left as it was, measured along the old path; fit
// it to the new length if Calligraphic pressure strokes get redrawn.
if let Some((si, sp)) = redraw_section(&pd, &pts, tol, within) {
if let Some((si, sp)) = redraw_section(&pd, &pts, fit_tol, within) {
s.edit(if brush { "Paintbrush" } else { "Pencil" }, |d, sel| {
*path_mut(d, id)?.subpaths.get_mut(si).ok_or_else(|| bad(C, "no such subpath"))? = sp;
if keep_selected {
@@ -764,7 +776,7 @@ fn freehand(s: &mut Session, p: &Value) -> Result<Value> {
if pts.len() < 2 {
return Err(bad(C, "need at least two points"));
}
let sp = fit_freehand(&pts, tol, closed);
let sp = stroke_subpath(&pts, fit_tol, closed);
let fill = if filled { s.paint.fill.clone() } else { Paint::None };
// A pencil path with a fill keeps a stroke of None (#1679); only one that would show nothing
// gets black. A brush is its stroke.
+36
View File
@@ -267,6 +267,42 @@ fn pencil_with_keep_selected_off_leaves_the_new_path_unselected() {
assert!(selected(&s).is_empty());
}
/// `fit: false` (the Pencil's preview with Live curve fitting off) joins the stroke's points with
/// straight segments: a new path, a continued one and a redrawn section; by default the stroke is
/// fitted.
#[test]
fn freehand_with_fit_off_joins_the_points_with_straight_segments() {
let corners = |sp: &vectorcraft_geom::SubPath| sp.anchors.iter().all(|a| a.h_in == a.p && a.h_out == a.p);
let at = |sp: &vectorcraft_geom::SubPath| sp.anchors.iter().map(|a| (a.p.x, a.p.y)).collect::<Vec<_>>();
let pts = wave(100.0, 400.0, 100.0);
let mut s = session();
let fitted = s.execute("path.freehand", &json!({"points": pts.iter().map(|p| [p.0, p.1]).collect::<Vec<_>>()})).unwrap();
let fitted = path(&s, NodeId(fitted["id"].as_u64().unwrap()));
assert!(!corners(&fitted.subpaths[0]) && fitted.subpaths[0].anchors.len() < pts.len(), "fitted by default");
let unfitted = s.execute("path.freehand", &json!({"points": pts.iter().map(|p| [p.0, p.1]).collect::<Vec<_>>(), "fit": false})).unwrap();
let sp = path(&s, NodeId(unfitted["id"].as_u64().unwrap())).subpaths[0].clone();
assert!(corners(&sp) && at(&sp) == pts, "{:?}", at(&sp));
// Continued from the end of a selected open path: the line's anchors, then the stroke's points.
let l = line(&mut s, 0.0, 300.0, 100.0, 300.0);
s.execute("select.set", &json!({"ids": [l.0]})).unwrap();
s.execute("path.freehand", &json!({"points": [[100, 300], [150, 330], [200, 290], [250, 320]], "extend": {"id": l.0}, "fit": false})).unwrap();
let sp = path(&s, l).subpaths[0].clone();
assert!(corners(&sp) && at(&sp) == [(0.0, 300.0), (100.0, 300.0), (150.0, 330.0), (200.0, 290.0), (250.0, 320.0)], "{:?}", at(&sp));
// A redrawn section: the stroke's inner points between where it meets the line.
let r = line(&mut s, 0.0, 500.0, 300.0, 500.0);
s.execute("select.set", &json!({"ids": [r.0]})).unwrap();
s.execute(
"path.freehand",
&json!({"points": [[100, 501], [125, 470], [150, 460], [175, 470], [200, 501]], "redraw": {"id": r.0, "within": 4}, "fit": false}),
)
.unwrap();
let sp = path(&s, r).subpaths[0].clone();
assert!(corners(&sp), "{:?}", sp.anchors);
let inner: Vec<_> = at(&sp).into_iter().filter(|p| p.1 < 499.0).collect();
assert_eq!(inner, [(125.0, 470.0), (150.0, 460.0), (175.0, 470.0)]);
assert!(s.execute("path.freehand", &json!({"points": [[0, 0], [1, 1]], "fit": "no"})).is_err());
}
#[test]
fn paintbrush_uses_stroke_blob_brush_fills() {
let mut s = session();
+59 -4
View File
@@ -2,7 +2,8 @@
//! Knife, Smooth, Path Eraser and Join.
//!
//! Pencil and Paintbrush preview the fitted path live (`path.freehand` re-applied on the snapshot)
//! and commit on release; Alt on release (or ending near the start) closes the path, and starting
//! and commit on release; with Live curve fitting off, the Pencil's preview joins the stroke's
//! points with straight segments and the release fits them; Alt on release (or ending near the start) closes the path, and starting
//! near an end of a selected open path continues it. Shift held at the press draws a straight
//! Pencil line at 45° steps from the Constrain Angle instead, and Alt held at the press (with Alt
//! key toggles to Smooth Tool off) draws a straight line at any angle. The Pencil's new path starts and ends on
@@ -50,6 +51,10 @@ pub struct GestureTool {
/// "Alt key toggles to Smooth Tool": holding Alt with the Pencil tool smooths selected paths
/// instead of closing the path (Pencil only, off by default).
pub alt_smooth: bool,
/// "Live curve fitting": the Pencil's preview fits the stroke while it is drawn; off, the
/// preview shows the points joined by straight segments and the release fits them (Pencil
/// only, on by default).
pub live_fit: bool,
/// Active drag is smoothing with the Pencil tool.
smoothing: bool,
/// Active Pencil drag draws a straight line (Shift or Alt held at the press): the stroke is
@@ -105,6 +110,7 @@ impl GestureTool {
keep_selected: id != "blobBrush",
// Off, as Pencil Tool Options ships it: Alt keeps drawing a closed path until it is on.
alt_smooth: false,
live_fit: true,
smoothing: false,
straight: false,
unconstrained: false,
@@ -417,7 +423,12 @@ impl Tool for GestureTool {
}
if crate::previews_at(self.points.len()) {
self.previewed = self.points.len();
out.push(Action::Preview("path.freehand".into(), self.freehand_params(false)));
let mut v = self.freehand_params(false);
if !self.live_fit {
// Live curve fitting off: the release fits the stroke (`finish`).
v["fit"] = json!(false);
}
out.push(Action::Preview("path.freehand".into(), v));
}
out
}
@@ -492,8 +503,9 @@ impl Tool for GestureTool {
}
fn deactivate(&mut self, _cx: &ToolContext) -> Vec<Action> {
let began = self.began;
// Drags past the last preview are in the stroke kept, too.
let stale = (began && self.previewed != self.points.len() && self.draws_path() && !self.smoothing)
// Drags past the last preview are in the stroke kept, too, and with Live curve fitting
// off the stroke kept is fitted.
let stale = (began && (self.previewed != self.points.len() || !self.live_fit) && self.draws_path() && !self.smoothing)
.then(|| Action::Preview("path.freehand".into(), self.freehand_params(false)));
self.snap.clear();
self.active = false;
@@ -557,6 +569,7 @@ impl Tool for GestureTool {
"fidelity": self.fidelity,
"fill": self.fill,
"altSmooth": self.alt_smooth,
"liveFit": self.live_fit,
"keepSelected": self.keep_selected,
"editWithin": self.edit_within,
"closeWithin": self.close_within
@@ -583,6 +596,7 @@ impl Tool for GestureTool {
"size" => self.size = v.as_f64().unwrap_or(self.size).clamp(0.1, 1000.0),
"fill" => self.fill = v.as_bool().unwrap_or(self.fill),
"altSmooth" => self.alt_smooth = v.as_bool().unwrap_or(self.alt_smooth),
"liveFit" if self.id == "pencil" => self.live_fit = v.as_bool().unwrap_or(self.live_fit),
"keepSelected" => self.keep_selected = v.as_bool().unwrap_or(self.keep_selected),
"mergeOnlySelection" => self.merge_only_selection = v.as_bool().unwrap_or(self.merge_only_selection),
"editWithin" => self.edit_within = v.as_f64().unwrap_or(self.edit_within).clamp(0.0, 100.0),
@@ -677,6 +691,47 @@ mod tests {
assert!(!t.busy());
}
/// Live curve fitting is the Pencil's, on by default. Off, the previews while a stroke is drawn
/// ask for the points unfitted (`fit: false`), and the release's preview is fitted as before.
#[test]
fn pencil_with_live_curve_fitting_off_previews_the_stroke_unfitted() {
let (d, _) = doc_with_rect();
let s = Selection::default();
let p = paint();
let cx = cx(&d, &s, &p);
let stroke = [(0.0, 0.0), (10.0, 5.0), (20.0, 0.0), (30.0, 6.0), (40.0, 0.0)];
// The `fit` each preview sends: those while drawn, then the release's.
let fits = |t: &mut GestureTool| {
let acts = drag(t, &cx, &stroke, Mods::default());
let fit = |a: &Action| if let Action::Preview(_, v) = a { Some(v.get("fit").cloned()) } else { None };
let (drawn, release) = acts.split_at(acts.len() - 1);
(drawn.iter().flatten().filter_map(fit).collect::<Vec<_>>(), release[0].iter().filter_map(fit).collect::<Vec<_>>())
};
let mut t = GestureTool::new("pencil");
assert_eq!(t.options()["liveFit"], json!(true));
let (drawn, release) = fits(&mut t);
assert!(!drawn.is_empty() && drawn.iter().all(Option::is_none), "{drawn:?}");
assert_eq!(release, [None]);
t.set_option("liveFit", &json!(false));
assert_eq!(t.options()["liveFit"], json!(false));
let (drawn, release) = fits(&mut t);
assert!(!drawn.is_empty() && drawn.iter().all(|f| *f == Some(json!(false))), "{drawn:?}");
assert_eq!(release, [None], "the release fits the stroke");
// Choosing another tool while the stroke is drawn keeps it fitted, too.
t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 0.0, 0.0));
for &(x, y) in &stroke[1..] {
t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, x, y));
}
let acts = t.deactivate(&cx);
assert!(matches!(acts.as_slice(), [Action::Preview(_, v), Action::Commit] if v.get("fit").is_none()), "{acts:?}");
// The Paintbrush has no such option, and its previews are fitted.
let mut t = GestureTool::new("paintbrush");
t.set_option("liveFit", &json!(false));
assert!(t.options().get("liveFit").is_none());
let (drawn, _) = fits(&mut t);
assert!(!drawn.is_empty() && drawn.iter().all(Option::is_none), "{drawn:?}");
}
/// Keep selected is on by default, and every Pencil and Paintbrush preview sends it.
#[test]
fn pencil_and_paintbrush_send_keep_selected() {
+3 -2
View File
@@ -35,8 +35,9 @@ const ROWS: &[Row] = &[
// Line Segment Tool Options: the Length and Angle a click's dialog opens with, Fill Line.
(&["lineSegment"], None, &["length", "angle", "fillLine"]),
(&["pencil", "paintbrush"], None, &["fidelity", "fill", "keepSelected", "editWithin", "closeWithin"]),
// Pencil Tool Options' Alt key toggles to Smooth Tool (the Paintbrush has no such option).
(&["pencil"], None, &["altSmooth"]),
// Pencil Tool Options' Alt key toggles to Smooth Tool and Live curve fitting (the Paintbrush
// has neither option).
(&["pencil"], None, &["altSmooth", "liveFit"]),
(&["smooth"], None, &["fidelity"]),
(&["blobBrush", "eraser"], None, &["size"]),
(&["blobBrush"], None, &["keepSelected", "mergeOnlySelection"]),
+56 -2
View File
@@ -1,6 +1,7 @@
//! Tool Options of the freehand tools (double-click Pencil, Paintbrush, Smooth, Blob Brush or
//! Eraser): the options each tool keeps, which last across tool switches and are saved with the
//! preferences. OK sets them with `tool.setOption {tool, values}`.
//! preferences. OK sets them with `tool.setOption {tool, values}`. The Pencil also has `liveFit`
//! (Live curve fitting, on: its preview fits the stroke while it is drawn).
//!
//! Fields: `tool`; `fidelity` (points; Pencil, Paintbrush and Smooth), `fill` (Pencil and
//! Paintbrush), `keepSelected` (Pencil, Paintbrush and Blob Brush), `mergeOnlySelection` (Blob
@@ -24,7 +25,7 @@ pub(super) const SPEC: DialogSpec = DialogSpec { heading, body, confirm, min_wid
pub const TOOLS: [&str; 5] = ["pencil", "paintbrush", "smooth", "blobBrush", "eraser"];
/// The options the dialog edits, in the order it shows them (a tool has those it reports).
const KEYS: [&str; 8] = ["fidelity", "size", "fill", "altSmooth", "keepSelected", "mergeOnlySelection", "closeWithin", "editWithin"];
const KEYS: [&str; 9] = ["fidelity", "size", "fill", "altSmooth", "keepSelected", "mergeOnlySelection", "closeWithin", "editWithin", "liveFit"];
/// Width of the label column: wide enough for the tolerances' labels when there are any.
fn label_w(d: &Dialog) -> f32 {
@@ -117,6 +118,10 @@ fn body(_app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
if d.fields.contains_key("editWithin") {
number(ui, d, ("editWithin", tl!("Edit selected paths within:")), "px", 0, (0.0, 100.0));
}
if d.fields.contains_key("liveFit") {
ui.add_space(6.0);
check(ui, d, "liveFit", tl!("Live curve fitting"));
}
false
}
@@ -209,6 +214,55 @@ mod tests {
}
}
/// Live curve fitting (the Pencil's, on by default): on, the path shown while a stroke is
/// drawn is fitted; off, it joins the stroke's points with straight segments, and the release
/// fits it as with the option on.
#[test]
fn live_curve_fitting_off_shows_the_points_until_the_release_fits_them() {
let mut app = app();
// The anchors of the newest path while a wave around height `y0` is drawn and after the
// release, with nothing selected (a stroke starting at the end of a selected path would
// continue it).
let draw = |app: &mut VectorcraftApp, y0: f64| {
app.run("select.set", json!({"ids": []})).unwrap();
app.select_tool("pencil");
let view = app.view_info();
let pts: Vec<_> = (0..=40).map(|i| (20.0 + i as f64 * 8.0, y0 + 30.0 * (i as f64 * 0.25).sin())).collect();
let pointer = |kind, (x, y)| vectorcraft_tools::PointerEvent::new(kind, x, y);
let newest = |app: &VectorcraftApp| {
let doc = &app.session.doc().unwrap().doc;
doc.layers[0].children().unwrap().last().unwrap().path_data().unwrap().subpaths[0].anchors.clone()
};
crate::canvas::dispatch(app, &pointer(vectorcraft_tools::PointerKind::Down, pts[0]), view);
for p in &pts[1..] {
crate::canvas::dispatch(app, &pointer(vectorcraft_tools::PointerKind::Drag, *p), view);
}
let drawing = newest(app);
crate::canvas::dispatch(app, &pointer(vectorcraft_tools::PointerKind::Up, pts[40]), view);
(drawing, newest(app))
};
let corners = |anchors: &[vectorcraft_geom::Anchor]| anchors.iter().all(|a| a.h_in == a.p && a.h_out == a.p);
// On by default: the Pencil's dialog shows it and the Paintbrush's does not.
app.run("tool.options", json!({"tool": "pencil"})).unwrap();
assert!(painted(&mut app).contains("Live curve fitting"));
assert!(app.ui.dialog.as_ref().unwrap().bool("liveFit"));
super::super::confirm(&mut app).unwrap();
let (drawing, on) = draw(&mut app, 100.0);
assert!(!corners(&drawing) && drawing.len() < 20, "fitted while drawn: {} anchors", drawing.len());
// Off: the points while the stroke is drawn, fitted at the release.
app.run("tool.options", json!({"tool": "pencil"})).unwrap();
app.ui.dialog.as_mut().unwrap().fields.insert("liveFit".into(), json!(false));
super::super::confirm(&mut app).unwrap();
app.select_tool("selection");
assert_eq!(app.session.tool_options_of("pencil")["liveFit"].as_bool(), Some(false), "it lasts while another tool is chosen");
let (drawing, off) = draw(&mut app, 200.0);
assert!(corners(&drawing) && drawing.len() > 30, "the points while drawn: {} anchors", drawing.len());
assert_eq!(off.len(), on.len(), "the release fits the stroke as with the option on");
assert!(off.iter().zip(&on).all(|(a, b)| (a.p.x - b.p.x).abs() < 1e-9 && (a.p.y - b.p.y - 100.0).abs() < 1e-9), "{off:?}");
app.run("tool.options", json!({"tool": "paintbrush"})).unwrap();
assert!(!painted(&mut app).contains("Live curve fitting"));
}
#[test]
fn a_new_fidelity_changes_what_the_pencil_draws() {
let mut app = app();
+1
View File
@@ -1089,6 +1089,7 @@ font-technology Variable Променливи
Fill and Stroke Запълване и щрих
Fill new strokes Запълване на нови щрихове
Alt key toggles to Smooth Tool Клавишът Alt превключва към инструмент „Гладкост“
Live curve fitting Напасване на кривата в реално време
Fill Color Цвят на запълване
Fill New Type Objects With Placeholder Text Запълване на нови текстови обекти с примесен текст
Fill Rule Правило за запълване
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2296,6 +2296,7 @@ lighting Light Světlo
Fidelity: Věrnost:
Fill new strokes Vyplňovat nové tahy
Alt key toggles to Smooth Tool Klávesa Alt přepíná na nástroj Vyhlazení
Live curve fitting Průběžné prokládání křivky
Keep selected Ponechat vybrané
Merge only with selection Sloučit jen s výběrem
Consider Open Filled Paths as Closed Považovat otevřené vyplněné cesty za uzavřené
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2106,6 +2106,7 @@ lighting Light Lys
Fidelity: Præcision:
Fill new strokes Udfyld nye konturer
Alt key toggles to Smooth Tool Alt-tasten skifter til Udglatningsværktøj
Live curve fitting Kurvetilpasning undervejs
The document has no colour swatches. Dokumentet har ingen farveprøver.
Style Name: Typografinavn:
Crop To Beskær til
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1086,6 +1086,7 @@ font-technology Variable Variabel
Fill and Stroke Fläche und Kontur
Fill new strokes Neue Striche füllen
Alt key toggles to Smooth Tool Alt-Taste schaltet auf Glätten-Werkzeug um
Live curve fitting Kurvenanpassung beim Zeichnen
Keep selected Ausgewählt lassen
Merge only with selection Nur mit Auswahl zusammenfügen
Consider Open Filled Paths as Closed Offene gefüllte Pfade als geschlossen behandeln
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1055,6 +1055,7 @@ font-technology Variable Μεταβλητές
Fill and Stroke Γέμισμα και περίγραμμα
Fill new strokes Γέμισμα νέων περιγραμμάτων
Alt key toggles to Smooth Tool Το πλήκτρο Alt αλλάζει στο εργαλείο Εξομάλυνση (Smooth Tool)
Live curve fitting Προσαρμογή καμπύλης σε πραγματικό χρόνο
Fill Color Χρώμα γεμίσματος
Fill New Type Objects With Placeholder Text Συμπλήρωση νέων αντικειμένων κειμένου με κείμενο υπόδειξης
Fill Rule Κανόνας γεμίσματος
1 # Ελληνικά (el) — VectorCraft
1055
1056
1057
1058
1059
1060
1061
+1
View File
@@ -1109,6 +1109,7 @@ font-technology Variable Variables
Fill and Stroke Relleno y trazo
Fill new strokes Rellenar trazos nuevos
Alt key toggles to Smooth Tool La tecla Alt cambia a la herramienta Suavizar
Live curve fitting Ajuste de curva en tiempo real
Keep selected Mantener seleccionado
Merge only with selection Combinar solo con la selección
Consider Open Filled Paths as Closed Tratar los trazados abiertos con relleno como cerrados
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2253,6 +2253,7 @@ lighting Light Valo
Fidelity: Tarkkuus:
Fill new strokes Täytä uudet viivat
Alt key toggles to Smooth Tool Alt-näppäin vaihtaa Tasoitustyökaluun
Live curve fitting Käyrän sovitus piirrettäessä
The document has no colour swatches. Asiakirjassa ei ole värimalleja.
Style Name: Tyylin nimi:
Crop To Rajaa kohteeseen
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1109,6 +1109,7 @@ font-technology Variable Variables
Fill and Stroke Fond et contour
Fill new strokes Remplir les nouveaux contours
Alt key toggles to Smooth Tool La touche Alt bascule sur l’outil Arrondi
Live curve fitting Ajustement de la courbe en direct
Keep selected Garder sélectionné
Merge only with selection Fusionner uniquement avec la sélection
Consider Open Filled Paths as Closed Traiter les tracés ouverts avec fond comme fermés
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1395,6 +1395,7 @@ font-technology Variable Változó
Fill Color Kitöltőszín
Fill new strokes Új vonások kitöltése
Alt key toggles to Smooth Tool Az Alt billentyű a Simítás eszközre vált
Live curve fitting Görbeillesztés rajzolás közben
Keep selected Kijelölés megtartása
Merge only with selection Összeolvasztás csak a kijelöléssel
Consider Open Filled Paths as Closed A nyitott kitöltött útvonalak zártnak számítanak
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2106,6 +2106,7 @@ lighting Light Cahaya
Fidelity: Fidelitas:
Fill new strokes Isi stroke baru
Alt key toggles to Smooth Tool Tombol Alt beralih ke Alat penghalus
Live curve fitting Penyesuaian kurva langsung
The document has no colour swatches. Dokumen tidak memiliki swatch warna.
Style Name: Nama gaya:
Crop To Pangkas ke
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
2106
2107
2108
2109
2110
2111
2112
+1
View File
@@ -1109,6 +1109,7 @@ font-technology Variable Variabili
Fill and Stroke Riempimento e traccia
Fill new strokes Riempi nuove tracce
Alt key toggles to Smooth Tool Il tasto Alt attiva lo strumento Smussa
Live curve fitting Adattamento della curva in tempo reale
Keep selected Mantieni selezionato
Merge only with selection Unisci solo con la selezione
Consider Open Filled Paths as Closed Considera chiusi i tracciati aperti con riempimento
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1513,6 +1513,7 @@ font-technology Variable バリアブル
Fill and Stroke 塗りと線
Fill new strokes 新しいストロークに塗りを適用
Alt key toggles to Smooth Tool Alt キーでスムーズツールに切り替え
Live curve fitting 描画中にカーブを調整
Keep selected 選択を保持
Merge only with selection 選択範囲とのみ結合
Consider Open Filled Paths as Closed 塗りのある開いたパスを閉じたパスとみなす
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5329,6 +5329,7 @@ imageTrace Outline კონტური
Light/Dark Balance ნათელი/მუქი ბალანსი
Move Anchor Point საყრდენი წერტილის გადაადგილება
Alt key toggles to Smooth Tool Alt კლავიში ჩართავს გაგლუვების ხელსაწყოს
Live curve fitting მრუდის მორგება ხატვისას
@msg {label} keeps 1 of the document's {n} artboards: the file reopens with one. Turn on Preserve Editing Capabilities to keep them all. {label} ინახავს დოკუმენტის {n} სამუშაო დაფიდან 1-ს: ფაილი ხელახლა ერთით გაიხსნება. ყველას შესანარჩუნებლად ჩართეთ „რედაქტირების შესაძლებლობების შენარჩუნება“.
Chalk & Charcoal… ცარცი და ნახშირი…
Chalk & Charcoal ცარცი და ნახშირი
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1508,6 +1508,7 @@ font-technology Variable 가변
Fill and Stroke 칠과 선
Fill new strokes 새 선 칠하기
Alt key toggles to Smooth Tool Alt 키로 매끄럽게 도구로 전환
Live curve fitting 실시간 곡선 맞춤
Keep selected 선택 유지
Merge only with selection 선택 영역과만 병합
Consider Open Filled Paths as Closed 칠이 있는 열린 패스를 닫힌 패스로 처리
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2120,6 +2120,7 @@ lighting Light Licht
Fidelity: Nauwkeurigheid:
Fill new strokes Nieuwe lijnen vullen
Alt key toggles to Smooth Tool Alt-toets schakelt over naar Gladmaken-gereedschap
Live curve fitting Curve aanpassen tijdens het tekenen
The document has no colour swatches. Het document heeft geen kleurstalen.
Style Name: Stijlnaam:
Crop To Bijsnijden tot
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -2211,6 +2211,7 @@ lighting Light Światło
Fidelity: Wierność:
Fill new strokes Wypełniaj nowe pociągnięcia
Alt key toggles to Smooth Tool Klawisz Alt przełącza na narzędzie Wygładzanie
Live curve fitting Dopasowywanie krzywej podczas rysowania
Keep selected Zachowaj zaznaczenie
Merge only with selection Scalaj tylko z zaznaczeniem
Consider Open Filled Paths as Closed Traktuj otwarte wypełnione ścieżki jako zamknięte
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -986,6 +986,7 @@
Fill and Stroke Preenchimento e Traçado
Fill new strokes Preencher novos traçados
Alt key toggles to Smooth Tool A tecla Alt alterna para a ferramenta Suavizar
Live curve fitting Ajuste de curva em tempo real
Keep selected Manter selecionado
Merge only with selection Mesclar somente com a seleção
Consider Open Filled Paths as Closed Tratar caminhos abertos preenchidos como fechados
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -1107,6 +1107,7 @@ font-technology Variable Вариативные
Fill and Stroke Заливка и обводка
Fill new strokes Заполнять новые обводки
Alt key toggles to Smooth Tool Клавиша Alt переключает на инструмент «Сглаживание»
Live curve fitting Подгонка кривой во время рисования
Keep selected Оставлять выбранным
Merge only with selection Объединять только с выделенным
Consider Open Filled Paths as Closed Считать открытые контуры с заливкой замкнутыми
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1091,6 +1091,7 @@ font-technology Variable Variabla
Fill and Stroke Fyllning och kontur
Fill new strokes Fyll nya konturer
Alt key toggles to Smooth Tool Alt-tangenten växlar till Utjämningsverktyg
Live curve fitting Kurvanpassning medan du ritar
Fill Color Fyllnadsfärg
Fill New Type Objects With Placeholder Text Fyll nya textobjekt med platshållartext
Fill Rule Fyllningsregel
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -5220,6 +5220,7 @@ theme Appearance Görünüm
@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
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
Can't render this file because it contains an unexpected character in line 5 and column 25.
+1
View File
@@ -1103,6 +1103,7 @@ font-technology Variable Варіативні
Fill and Stroke Заливка та обведення
Fill new strokes Заповнювати нові обведення
Alt key toggles to Smooth Tool Клавіша Alt перемикає на інструмент «Згладжування»
Live curve fitting Підгонка кривої під час малювання
Keep selected Залишати вибраним
Merge only with selection Об’єднувати лише з виділеним
Consider Open Filled Paths as Closed Вважати відкриті контури із заливкою замкненими
Can't render this file because it contains an unexpected character in line 749 and column 40.
+1
View File
@@ -1272,6 +1272,7 @@ font-technology Variable Biến thiên
Fill Grid Tô lưới
Fill new strokes Tô nét mới
Alt key toggles to Smooth Tool Phím Alt chuyển sang công cụ Làm mượt
Live curve fitting Khớp đường cong khi vẽ
Keep selected Giữ trạng thái đã chọn
Merge only with selection Chỉ hợp nhất với vùng chọn
Consider Open Filled Paths as Closed Coi đường dẫn mở có tô là đường dẫn đóng
1 # Vietnamese (vi) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
1272
1273
1274
1275
1276
1277
1278
+1
View File
@@ -1048,6 +1048,7 @@
Fill and Stroke 填色和描边
Fill new strokes 为新描边应用填色
Alt key toggles to Smooth Tool Alt 键切换为平滑工具
Live curve fitting 实时曲线拟合
Keep selected 保持选择状态
Merge only with selection 仅与所选对象合并
Consider Open Filled Paths as Closed 将有填色的开放路径视为闭合路径
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -1074,6 +1074,7 @@ font-technology Variable 可變
Fill Color 填色顏色
Fill new strokes 為新筆畫套用填色
Alt key toggles to Smooth Tool Alt 鍵切換為平滑工具
Live curve fitting 即時曲線擬合
Keep selected 保持選取狀態
Merge only with selection 僅與選取物件合併
Consider Open Filled Paths as Closed 將有填色的開放路徑視為封閉路徑
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1
View File
@@ -674,6 +674,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
In the Swatches panel, Alt-dragging a swatch onto another swatch of its kind (not a tint swatch) replaces that swatch's color, gradient or pattern, and the swatch keeps its name and place.
- **Tool double-clicks:** double-clicking a tool's button opens its options: Hand fits the artboard in the window, Zoom shows 100 %, Rotate, Scale, Reflect and Shear open their Object › Transform dialogs, the Selection, Direct Selection and Group Selection tools the Move dialog (Enter opens these seven tools' dialogs too; Move opens on the offset of the last drag or Move and with the checkboxes it was last confirmed with, so Enter, Enter repeats a move or a copy at the same spacing, as [an Adobe Community thread](https://community.adobe.com/questions-652/move-tool-771221) describes), Gradient, Eyedropper, Blend and the Liquify tools their panels or options, the Pencil, Paintbrush, Smooth, Blob Brush and Eraser tools their Tool Options (Fidelity, fill, Keep selected, the tolerances, the brush size), and (#812) the Flare tool its Flare Tool Options, the Artboard tool the active artboard's Artboard Options, the graph tools Graph Type for the selected graph, the Symbolism tools the Symbolism Tools Options (Diameter, Intensity, Symbol Set Density) and the Magic Wand tool its panel.
Double-clicking the Line Segment, Arc, Rectangular Grid or Polar Grid tool opens its Tool Options, as step 3 of the [Rectangular Grid tool](https://helpx.adobe.com/illustrator/using/tool-techniques/rectangular-grid-tool.html) and [Arc tool](https://helpx.adobe.com/illustrator/using/tool-techniques/arc-tool.html) pages and the [Tools](https://helpx.adobe.com/illustrator/using/tools.html) page describe (#2691): the dialog a click with the tool opens, without a place to draw. Line Segment Tool Options has Length, Angle and Fill Line (a new line takes the current fill, `shape.line` `fill`); Arc Segment Tool Options its lengths, Reference Point, Type, Base Along, Slope and Fill Arc; the grids' Tool Options the Reference Point, Default Size, dividers, skews and switches. OK draws nothing: the next drag draws with the options and a click's dialog opens at them, the Length, Angle and Default Size included; Cancel changes nothing. Tests: `toolbar::tests::double_clicking_the_line_arc_and_grid_tools_opens_their_tool_options`, `line_segment_options_and_fill_line`, `grid_default_size_is_an_option_the_drag_leaves_out`, `fill_line_gives_a_new_line_the_current_fill`.
Live curve fitting in the Pencil Tool Options is on by default; while it is off, the path shown while a stroke is drawn joins the stroke's points with straight segments, and the release fits the curve, as [Pencil Tool Options](https://web.archive.org/web/20261002104400/https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/pencil-tool-options.html) describes (`path.freehand` `fit`).
In the Pencil and Paintbrush Tool Options, Edit selected paths within 0 px turns editing off, so a stroke that starts exactly on an end of a selected open path draws a new path; Close paths when ends are within 0 px turns closing off, so a stroke that ends exactly where it started stays open.
Keep selected in the Pencil and Paintbrush Tool Options is on by default; while it is off, the path a stroke draws or continues is not selected afterwards and the rest of the selection stays as it was (`path.freehand` `keepSelected`).
Double-clicking the Shape Builder tool opens Shape Builder Tool Options ([Shape Builder tool options](https://web.archive.org/web/20260105172244/https://helpx.adobe.com/illustrator/using/building-new-shapes-using-shape.html)): with Consider Open Filled Paths as Closed off, a filled open path cuts regions as an unfilled one does; Pick Color From Color Swatches fills merged shapes with the current fill, the Left and Right arrow keys step it through the document's color swatches, and Cursor Swatch Preview shows the current fill and the swatches before and after it above the pointer; Selection Straight Line merges what the line from a drag's first point to its last crosses ([Setting Illustrator's Shape Builder Options](https://web.archive.org/web/20260615204453/https://creativepro.com/setting-illustrators-shape-builder-options/)); with Highlight Fill off, merge mode shades no regions (`shapeBuilder.merge` `openFilledAsClosed` and `pickColorFrom`).