A closed freehand stroke is fitted as the open stroke would be and closed by a
straight segment from its end back to its start. Fitted as one loop, the
closing segment curved on past both ends and back, like a hat's brim.
The continuation cursor (a short rising line) shows over an open end of a
selected path before any press, freehand too, and while a continuing stroke
nears an open end of another selected path it connects to. The straight-line
cursor becomes a short level line. A stroke continuing a path, a straight line
too, shows the closing ring within Close Paths When Ends Are Within of the
path's other end, and the release closes it; path.freehand takes the tool's
closed for extend instead of its own four-Fidelity distance.
When a Smooth tool drag refits the stretch at the first or last anchor
of an open path, the curve there now leaves or arrives along the
smoothed path within the tool's reach instead of along the first or
last segment, so a jagged end no longer keeps its jag. The end point
stays where it is. An end where the smoothed path turns back by 90
degrees or more keeps its segment's direction, and a stretch that ends
inside the path keeps the direction of the part outside the drag.
- pathops: SimplifyOptions gains free_start and free_end, off by
default; closed subpaths and simplify_part ignore them, so Simplify
and Object > Path > Smooth give the same output as before.
- engine: path.smoothRegion sets them for the ends of an open path
that a run's refit reaches.
Docs: docs/mcp.md, docs/target-app-parity.md.
When a drag reached the first anchor of a closed path, the smoothed path
started at the first anchor past the drag, and a drag over the whole
path could start it at a corner the refit kept. Dashes and type on the
path then started somewhere else.
The smoothed path now starts at its point nearest where the first anchor
was: at an anchor there, or at a new anchor split into the segment
there, which leaves the outline as it is. Of points equally near, the
one earliest along the path before smoothing, from its first anchor,
is used. A drag that does not reach the first anchor leaves it first,
as before.
Part of #2224.
A Smooth drag along the middle of a curved segment smoothed nothing when
neither of the segment's anchors was within the reach, and paths of two
anchors were never smoothed.
- path.smoothRegion searches each curved segment where it can come
within the reach, halving it down to pieces no longer than half the
reach, and adds an anchor in the middle of each stretch in reach; the
existing reach split then cuts the segment where the drag enters and
leaves the reach. A drag that only grazes a curve finds the place on
it nearest the drag. The search is bounded per drag and shared
equally by the segments in reach.
- Paths of two anchors are smoothed, open or closed.
- Alt with the Paintbrush smooths a selected two-anchor curve; a
two-anchor straight line still gets the closed stroke. Both sides use
one segment_is_straight test.
Part of #2224
Object > Path > Remove Anchor Points removed each selected anchor with
its own Vec::remove, shifting the rest of the subpath every time, so
removing k anchors from an n-anchor subpath took O(k*n) moves.
- pathops: remove_anchors removes a subpath's anchors the last first,
reading each anchor's current neighbors, handles, length and closed
state as removing them one at a time does, and compacts the anchor
list once. remove_anchor and remove_anchors share one refit, so the
result is bit for bit the same.
- engine: the command groups the selected anchors by subpath, works out
the next selected anchor with each subpath reading only its own
selected anchors, and takes emptied paths out of the selection
together (Selection::remove_all).
- Tests compare remove_anchors bit for bit with remove_anchor over 600
generated subpaths and focused cases, count the anchors moved (2001
anchors, every other one removed: at most one move per anchor left,
500,500 one at a time), and compare the next-anchor selection and
the command on many paths and subpaths with removing one at a time.
Part of #2224.
A Smooth tool drag only refitted the anchors in its reach within
Fidelity, so a wobble wider than Fidelity stayed, and a second drag over
the same place changed nothing. Repeated refits also drifted away from
the path, and a drag over a whole closed path left a corner at its first
anchor.
- path.smoothRegion now smooths the stretch in reach along its length
with a Gaussian whose spread is a third of the reach, then refits it
within Fidelity. The run's outer end points and handles stay fixed.
- Each refit stays within twice the distance the smoothing moved the
path (at least a tenth of Fidelity), so repeated drags do not drift
(SimplifyOptions::smooth_moved_only).
- A whole closed path is smoothed across its start as a loop, so a
bump at the first anchor is smoothed like any other and no corner is
left. Object > Path > Smooth smooths a closed path across its start
the same way.
The Pencil and the Paintbrush send their Fidelity in points at the
current zoom, but path.freehand read at most 100 pt. At 3.13% zoom a
20 px Fidelity (639 pt) was fitted at 100 pt, the same as 3.13 px on
screen, so a stroke drawn zoomed out kept far more anchors than the
same stroke at 100%.
- path.freehand, and brush.freehand, which runs it, take a Fidelity
from MIN_FIT_TOLERANCE to MAX_FIT_TOLERANCE (100 / 0.0313 pt, the
100 px field maximum at the lowest zoom). A value outside that range
is now rejected with an error instead of being read as the nearer
end. The default stays 1.5 pt and path.blob keeps its 100 pt bound.
- The Pencil and the Paintbrush keep the value they send within that
range, so a stroke draws at any zoom.
- An extending stroke still closes the path within four Fidelities of
its other end, at most 400 pt, as before.
It conflicts with open #7089 in crates/tools/src/draw2.rs.
Docs: docs/mcp.md, docs/target-app-parity.md, path.freehand params.
The Smooth tool, and an Alt smoothing drag with the Pencil, Paintbrush or
Blob Brush, now send path.smoothRegion their Fidelity divided by the zoom,
as the Pencil and the Paintbrush do since #5748. Smooth Tool Options shows
Fidelity in px. A saved Fidelity keeps its number, so it smooths as before
at 100% zoom.
path.smoothRegion now refits at tolerances from 0.05 px at the highest zoom
to 100 px at the lowest (0.000078125 to about 3195 pt) instead of clamping
to 0.05 to 100 pt, so the setting keeps scaling across the whole zoom range.
path.smoothRegion replaced the anchors of each path it smoothed but left
the selection alone, so anchor and segment refs selected with Direct
Selection still named the old indices: after a smooth that removed
anchors, a ref could point past the end of the path or at another
anchor. The edit now drops those refs for each path it changes, so the
path stays selected whole. Selected paths the drag does not change keep
their anchors, and undo restores the earlier selection.
Simplify, Object > Path > Smooth, the Smooth tool and freehand strokes
turned every refitted curve whose handles lay near its chord into a
straight line. A short curve whose end directions were off the chord
passed that test, and the anchor between it and the next curve became a
corner the path did not have: a Smooth tool drag along a line jagged by
up to 6 pt left corners of 7.7 to 40.6 degrees.
A curve now becomes a line only when the line runs within 0.5 degrees
of the directions the path arrives and leaves along. A nearly straight
piece that would turn more stays a flat curve. Convert to Straight Lines
is unchanged.
Space held while dragging out a new anchor already moved it, handles and
all, but the closing drag skipped it, so the closing anchor could not be
repositioned. The closing drag now moves the first anchor by as far as the
pointer moves while Space is held, its handles with it, and the drag
shapes the handles round the new place once Space is released.
path.close takes `at: [x, y]`, which moves the first anchor, its handles
with it, before `in` and `out` are placed. docs/mcp.md and the Pen line
in docs/target-app-parity.md describe it.
Closing a path by dragging on its first anchor made that anchor smooth
with symmetric handles, so the first segment bent along with the closing
one. The drag now sets the first anchor's incoming handle to the mirror
of the pointer. Its outgoing handle stays where it is on a corner, so
the first segment keeps its shape, and on a smooth anchor it turns to
stay in line at its own length. Alt makes the anchor a corner and leaves
the outgoing handle where it was when Alt went down. A click on the
first anchor closes the path with the incoming handle retracted and the
anchor a corner, so the closing segment comes in straight.
The Smart Pen's closing drag follows the same rule.
path.close's `in` now places the handle as path.setHandle does, and a
new `out` param places the outgoing handle and makes the anchor a
corner.
path.insertAnchor (the Add Anchor Point tool, the Pen's Auto Add, the
Smart Pen's Alt-click on a segment and Alt-click with the Scissors)
added the anchor without changing the selection, so the direct-selected
anchors and selected segments after it on the subpath kept their old
numbers and the selection shifted onto the next points along. It now
renumbers them with renumber_after_insert, the helper path.reshape
uses. A selected segment that the new anchor splits stays selected as
its two halves, and the new anchor is direct-selected with them. Undo
restores the path and the selection in one step.
Docs: params text, docs/mcp.md, docs/target-app-parity.md.
After a Scissors cut (`path.split`) the whole path stayed selected: both
pieces of an open path, or the opened path of a closed one. A Direct
Selection drag from the cut then moved every anchor, so an ellipse cut
at an anchor could not be pulled open by dragging its end.
- `path.split` selects the first anchor of the piece after the cut and
nothing else, as `path.cutAtAnchors` does: anchor 0 of an opened
closed subpath, of the new path when an open path becomes two, or of
the subpath added to a path of several subpaths.
- A Direct Selection press on anchors of a selected path that lie on top
of each other takes the selected one. It took the first in path order,
which in a path of several subpaths was the unselected end.
Docs: docs/mcp.md, docs/target-app-parity.md, the `path.split` params
text.
With anchors picked with Direct Selection, Edit > Cut removed only the
segments between them, so the picked anchors stayed in the path. Cut now
removes picked anchors and the segments attached to them from a path or
a compound path's member, as Clear does. Picked segments still lose only
those segments. When a path has picked segments, Copy and Cut leave out
anchors picked on its other contours, as Clear does.
Docs: docs/target-app-parity.md.
With the Rectangle and Ellipse tools, Cmd lends the selection tool chosen
last, and Copy already took the anchors Direct Selection picked. The
Curvature tool, which Cmd lends Direct Selection, copied the whole path
before this PR, as the Pen did.
The remembered selection is now tied to the document it was made in, by its
uid, instead of being cleared on every tool reset: another view of the same
document, or closing an inactive tab, kept the anchors picked but made Copy
take the whole path.
A lent gesture that leaves the selection as it found it (a Cmd+Shift marquee
over nothing) is no longer recorded as a pick, so the Pen's last anchor stays
its own and Copy takes the whole path.
Also records #6123 in target-app-parity.md.
With the Pen or Curvature tool, Copy and Cut ignore the anchor selection,
so that the last anchor the Pen leaves selected doesn't make Copy take just
that anchor (#2122). Anchors picked with the Direct Selection that Cmd/Ctrl
lends those tools were ignored too, and the whole path was copied.
The session now remembers the selection the lent tool leaves when it gives
the tool back. While that is still the selection, Copy and Cut take the
picked anchors and segments, as with Direct Selection chosen.
The Join tool drew a segment between the open ends of two crossing paths
instead of trimming what overhangs the crossing. path.joinScrub now looks for
two crossing open subpaths whose overhanging ends the scrub runs over, cuts
both at the crossing and joins the kept parts there with a corner, in one undo
step. Without a crossing the endpoint join is unchanged.