A path with an Art brush comes in as the path, with the brush on its
stroke at its weight, instead of as its drawn look, when the brush
stretches art of solid-filled paths along the path, unflipped, at 100% in
Tints. The art runs on the side of the path the file draws it.
The drawn look stays for other colorizations and widths; Proportional,
between-guides and flipped brushes; type; a path with settings of its own,
a variable width, effects, a swatch stroke or an entry that isn't read;
and art with strokes, gradients, patterns, masks or effects, or no length.
A brushed stroke, Calligraphic ones too, takes the brush the Brushes panel
keeps under its name, and an image with a brush keeps its drawn look.
In every document, Tints and Tints and Shades take art in black ink alone
by its ink amount, and a process or gray stroke color's tints and shades
stay in that color's model.
Place and Expand bring the brushes the art uses, renaming on conflict.
Part of #637.
The width between two width points was a straight line, so a variable-width
stroke turned a corner at every width point.
- Each side of a width profile now follows a monotone cubic (Fritsch-Carlson)
through its points: no kink at a point, flat at a point wider or narrower
than both neighbours, never past either point of a span. Points on one line
stay on it and two-point tapers are unchanged; stacked points still step.
- widest() counts each span's wider left and wider right, since the two sides
needn't peak together.
- The outliner samples straight runs between width points closely enough to
follow the curve to the drawing tolerance (at most 64 samples per span and
4,096 per piece); straight spans get none.
- Tests that expected straight-line widths now expect the curve. The stroke
gradient coverage test uses Teardrop instead of Wave until #5964 is fixed.
When Distort Appearance is on and the size a `.ai` saves for an
envelope's contents (`AIDeformArtWidthKey`, `AIDeformArtHeightKey`)
matches the contents' bounds with strokes, the reader now maps the
contents from that stroked box. It used to map them from the geometric
bounds, half a stroke inside that box, so an envelope of stroked art
could differ from its drawn art and keep the drawn art with a note.
Envelope Options hold the box's margins as `boundsMargin`, shares of the
geometric bounds at the top, bottom, left and right. Native files write
them only when they are not zero, and `object.envelope.info` reports
them then. Leaving Edit Contents measures them again when the strokes
reach differently than when Edit Contents began, and the node holds
what they measured then until it leaves. New envelopes have no
margins, so their results do not change. Contents with no width or no
height, such as one straight line, get no margins.
Docs: docs/mcp.md, docs/target-app-parity.md.
The walk that writes link annotations measured every object at every
level, on every export that can have links, and read the box only for
an object with a URL that opens and for a clipping group. It now
measures only those, so a document with no URLs or clipping groups is
not measured a second time. The link annotations are unchanged.
The output is byte for byte the same on a document of linked objects in
plain and clipping groups on and off the page, with default settings,
with uncompressed text and with PDF layers.
The walk measured every object's visual box to leave out what is off
the page. A plain group or layer is measured as the union of its
members, and each member then measured itself again when it was
written, so an object in plain groups was measured once for each level
it sits at.
Measuring a plain group or layer now keeps its members' boxes in a
tree, and each member uses its box when it is written. A container
written whatever its box is (a PDF layer's group, placed symbol art) is
not measured. A knockout group's elements are measured for the group's
box and again for the list of elements on the page, and that second box
is used for the element and for its mask. Objects in a live container
(Blend, Envelope, Repeat, Intertwine) are still measured with the
container and again after it is expanded. Node::visual_of_members holds
the formula that Node::visual also uses, so the boxes and every decision
are unchanged.
The output is byte for byte the same on one open path of 200,000
anchors, 20,000 rectangles in 200 groups of 100, and 5,000 point type
objects, and on a document of plain, see-through, clipping and knockout
groups on and off the page, each exported with default settings, with
uncompressed text and with PDF layers.
When the scan for swatch names searched data outside a section and found no `~>`, it stopped searching for the end of all later data. Data in a later section then was not skipped, so a `(` on a `%_` line of it opened a string that ran past the section's end marker, and a color named after the section did not reserve its name. The scan now records the search that found nothing per section's end marker, so a search in another section still stops at that section's markers.
Data with no `~>` before the section's end marker was lexed as tokens, so a `(` on a `%_` line of it opened a string that ran past the marker and the section did not end. The scan now skips that data up to the section's end marker, or up to the start of a section of the same kind nested after it. A `%` on the key's own line starts no marker, and a marker line with bytes that are not UTF-8 counts, as the scan reads comments.
When versioned content does not end, the scan for swatch names goes back to where its dictionaries stood at the start of that content, so data in the range it reads again is skipped.
A section scan lexed a /ForeignObject's /Data, a /Binary dictionary's data and a text document's data as tokens. A `%_` line of that data holding a `(` opened a string, so the section ran past its end marker: the editing data failed to read, or the art after it was misread. Reader::section now skips the data up to its `~>`, as the reader does for art outside a section. Data without its `~>` before the section's end marker is not skipped. The scan for swatch names does the same.
The `Tool` implementation's `busy`, `pointer`, `key`, `claims_key`, `overlays`
and `cursor` in shape.rs, and `pointer` and `cursor` in select.rs, are the ones
this pull request edits; each now has a doc comment, so the docstring coverage
of the functions the diff touches is complete.
- The 45° move assertion compares dx and dy with a small tolerance instead of
exact equality (the matrix's floats are rounded), keeping the non-zero check
and the message.
- The engine test helpers say what they do, so the docstring coverage of the
functions this pull request touches is complete.
The roadmap's status line and revision history now record the shape tools'
centre-point move and corner rotate (#5477), and the parity status line no
longer names the Transform tab-order fix, which is its own pull request.
The Selection tool's cursor now checks the ruler guides before a selected
object's centre point, as a press does. Tests: a ruler guide under the centre
point keeps the pointer and drags; on a box too thin for its centre to clear the
handles the handles take both; a shape-tool move from the centre snaps and shows
the hollow arrow; and Alt pressed mid-drag copies the shape through the engine.
The Selection tool's cursor now checks the bounding-box handles before a
selected object's centre point, as a press does (on a thin box the centre lies
within a handle's reach). The shape tools' tests cover a Live Corners widget
that lands on another selected shape's centre (the widget rounds), Alt pressed
mid-drag (the preview copies and the copy cursor shows), and the engine tests
check the copy's place and both gestures' Undo and Redo. The cursor catalog says
which arrow a drag of the selection shows.
- The Selection tool takes a Shift-press on a selected object's centre point
too: a Shift-drag moves the selection held to 45° steps, and a Shift-click
there takes the object out of the selection, as a Shift-press on the object
does.
- One rotate zone for the Selection tool and the shape tools
(`select::rotate_zone`): a press on a bounding-box handle is not a turn, so
the shape tools' zone matches the Selection tool's classification exactly.
- The shape tools' hover cursor follows the press order (corner and pie widgets,
then the centre point, then the rotate zone), and a move of the selection
shows the Selection tool's feedback: SelectionCopy with Alt, ArrowHollow while
it snaps.
- Engine-level tests for the whole gesture (`crates/engine/src/tests/shapetools.rs`):
repeated previews and one undo step, Alt copy, Escape then release, a turn
about the centre, the corner widget still rounding, and an unfilled shape
moved by the Selection tool.
- docs: evidence in target-app-parity.md's Shapes bullet and the M2 milestone,
and the Move cursor's entry names the copy and snap cursors.
A press on the centre point of the selected shape now moves the selection, as
asked (#5477), so the Constrain Angle test's repeated draws from the same point
moved the polygon drawn before them. It deselects first: nothing selected, the
press draws.
The Artboard tool's own Control bar drew the W/H Constrain Proportions link
between W and H, so with that tool Tab stopped on it before H, as the object
Control bar did before the last commit. It reserves the link's place and makes
the button after the fields too, and the link now toggles with Space or Enter
when it has the keyboard, as a dialog's button does.
Tests: `one_tab_moves_from_a_field_to_the_next` covers the Artboard Control bar
(W to H in one press), and `the_control_bar_link_toggles_by_click` Tabs from H
to the link and toggles it with Enter.
A shape just drawn keeps its bounding box with the Rectangle, Rounded Rectangle,
Ellipse, Polygon and Star tools, but its centre point (Attributes › Show Center)
did nothing and the box's handles only drew: a press anywhere started a new
shape, and with the Selection tool a press on the centre of an unfilled shape
started a marquee.
- The Selection tool now takes a press on a selected object's centre point as
the start of a move (held by that point, so it snaps and Alt copies), as
Direct Selection has since #4891. A click there still makes the object the
key object of a multi-selection.
- The shape tools take it too, and take a press in the rotate zone just outside
a bounding-box corner (the Selection tool's own zone and cursor) as the start
of a rotation about the box's centre. Live Corners widgets keep the corners
themselves, so a press on a corner still rounds it; every other press still
draws a new shape. Escape cancels, as for the Selection tool's drags.
`hit_center` moves from direct.rs to select.rs, shared by the three tools.