The Properties panel's flip buttons had been renamed Flip Horizontal and Flip Vertical, which are the Transform panel menu's names. The test lays out the Transform section for a selected rectangle and reads the screen-reader names of its buttons: Flip Along Horizontal Axis and Flip Along Vertical Axis must be there and the old names must not. With the old labels the first is missing.
The Fill/Stroke chip's popover closed on the click that finished a drag between its proxies. The test opens a popover and checks the three outcomes: a click elsewhere closes it, a click on its own anchor that does not toggle it keeps it open, and a click elsewhere during a panel drag keeps it open. Without the fix both of the last two close it.
The rows of the panels' menus did not light up under the pointer as the menu bar's items do, because they were drawn with no frame at all. The test hovers a menu_item_name row and checks that a filled rectangle appears behind it, and that none is there at rest. With frame(false) the hovered row stays unfilled.
The theme's widget fill matched the dialog background, so a slider in a dialog showed only its handle. The test draws widgets::slider in all four brightnesses and checks that a rail in the buttons' border colour and an accent fill from the rail's start up to the handle are painted. With a plain egui slider neither is drawn, only two rectangles in the panel's button fill.
Selected text in a value field was drawn in the accent on a selection tinted with the same accent, hard to read in every brightness. The tests focus a panel number field and a dialog text field, select their text and read the colours of the glyphs drawn, in all four brightnesses: all must be the strong text colour. Without strong_selected_text the glyphs come out in the accent.
A number field with a spinner and preset chevron drew as three separate boxes, each joint with two borders. The test lays out a spin field and checks that its three boxes are the row's height and that neighbours overlap by one point so they share a border. Without the overlap (item spacing 0) the boxes touch edge to edge instead.
The toolbar's and the dock icon column's scroll bar widened to 10 points while the pointer was on it, covering the group labels and the tool icons' right edge. The test lays out a scrolling strip, rests the pointer on its bar and measures the widest filled rectangle drawn: at most 4 points. Without the slim scroll style it measures 10 points.
With the Artboard tool the active artboard's name was drawn twice: by the canvas, as for every artboard, and by the tool, a little higher. The test draws a frame with two artboards and checks that each name is painted once and that the active artboard's sits where the other's does. It fails without the canvas skipping the tool-labelled artboard (two labels for the active one) and without the tool's label position (8 points to the right and 11 higher than the other name).
The Add and Delete Anchor Point tools' icons were drawn apart from the Pen tool's, so the three didn't read as one family. The test checks that both tools' icons contain every shape of the Pen tool's icon plus a badge of their own, and that plus and minus differ. With the tool mapping pointing back at the old dc-pen-add and dc-pen-delete icons it fails: the Add tool's icon lacks the Pen's outline.
Illustrator 30.0's Method choice in the Gradient panel
(https://helpx.adobe.com/illustrator/desktop/paint-and-fill/create-and-edit-gradients/control-perceptual-interpolation-in-gradients.html).
- paint.editGradient takes `method: classic|perceptual` (one undo step);
paint.setFill/setStroke take it in `gradient`, and gradient params (swatches,
copied paints) carry it when Perceptual. Anything else is refused without
editing. One edit can set `method` and `dither` together.
- The Gradient panel shows a Method menu (Classic, Perceptual) for linear and
radial gradients, dimmed without a gradient; the Dither box stays under the
stops.
- i18n: "Perceptual" where a complete catalog lacked it, and the `method`
error in all 24 catalogs.
- Docs: G13 (estimate 14–24 h → 13–23 h, after Dither's 15–25 h → 14–24 h),
target-app-parity (row and evidence), ui-parity, mcp.md, plugins.md.
With the Artboard tool, File › Revert kept the active artboard, but a
command.batch with a file.revert step made artboard 1 the active one.
A batch that switched to another document and back, and the Actions
panel menu's Batch… over a folder whose files it closes or over data
sets with a Revert step, did the same. Each of them starts the tool
afresh and ends in the document it started in, and the app moved the
view to the fresh tool's artboard 1. Only a plain file.revert was
handled, by its command id.
The engine now counts the times switching or closing documents, or
replacing the active one, starts the tool afresh (Session::tool_resets),
and records whether the tool's options were set after that
(Session::tool_set_since_reset). When the count changes during a
command and nothing set the tool's options after it, the app starts
the tool on the active artboard, as it does when a command chooses
another document. When a later step chose the tool's artboards (a
Paste or Cut after the revert), the artboard the tool then has active
becomes the active one.
Docs: docs/mcp.md, docs/target-app-parity.md.
Review follow-up (CodeRabbit): paint.setStroke kept `dither: true` from its
gradient, and a stroke can hold dither anyway (swapped from a fill). Outline
Stroke and appearance expansion copied the stroke's paint into the outline's
fill, which then drew dithered although the stroke never did. The outline's
fill now goes without dither (Paint::stroke_as_fill), and paint.setStroke
refuses a gradient with `dither: true`, as paint.editGradient does.
## Summary
When the Paragraph panel acts on more than one paragraph whose displayed
direction differs (explicit LTR/RTL or Auto resolved from content), neither
Left-to-Right nor Right-to-Left is highlighted, matching alignment and the
other mixed-value paragraph controls in Adobe Illustrator.
## Tests
- `paragraph_mixed_direction_buttons_show_neither` in `panels/paragraph.rs`
Part of gap G4
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
File › Package… put the file's path in front of every error its writer
returned. A refusal from the automation write root already names the
path, so the dialog's error and the status bar named the file twice,
for example on Windows for a document named aux:
`<path>: automation path rejected: reserved device names are not
allowed: <path>`.
The dialog now keeps a writer error that already contains the path as
it is, and puts the path in front of one that does not, such as the
desktop's "Access is denied. (os error 5)".
Enter and Escape went to the active tool whatever modifiers were held,
and the command bound to the chord then ran as well. With a command
bound to Cmd+Enter, Cmd+Enter ended the Pen tool's path, opened the
Move dialog with the Selection tool, or typed a line break while the
Type tool edited text, and then ran the command. With a command bound
to Cmd+Escape, Cmd+Escape ended the Pen path, the Type tool's text
editing or isolation mode before the command ran.
A press of Enter or Escape whose chord a command is bound to, matched as
the shortcut dispatcher matches it, now runs only that command, also
when the tool claims the key. While the Type tool edits text, only Cmd
chords count. While a tool is in the middle of an interaction (a drag,
or between the clicks of a tool such as Blend or Dimension), and for
plain Enter and Escape and chords that no command is bound to, the key
goes to the tool as before.
Illustrator 30.0's gradient Dither, from the public documentation
(https://helpx.adobe.com/illustrator/desktop/paint-and-fill/create-and-edit-gradients/control-dither-in-gradients.html):
a Dither checkbox in the Gradient panel; dither converts the gradient into
pixels with subtle noise, works with gradient fills only, and follows the
Document Raster Effects resolution.
- Gradient gains `dither` (serde default false, written only when on, so
older files open unchanged and new files keep it).
- The renderer draws a dithered linear or radial fill as an image paint: the
gradient sampled a cell per device pixel or finer (quarter-octave steps,
cached per thread), each channel given seeded triangular noise of up to a
level before rounding. Same pixels every run; strokes and undithered fills
draw exactly as before. PNG and other raster exports go through it.
- SVG and PDF write a dithered fill as an image at the document's raster
effects resolution, clipped to the shape (the freeform-gradient path);
characters' fills stay vector gradients in SVG, with a warning.
- paint.editGradient and paint.setFill take `dither`; strokes refuse it.
The Gradient panel's Dither box is dimmed behind the Stroke proxy.
- i18n rows in all 24 catalogs; docs: G13, target-app-parity, ui-parity,
mcp.md, plugins.md.
The Place dialog classed every .pdf, .ai and .ait path as a document
that Link places as one locked object. A .ai saved without PDF
compatibility is PostScript, and file.place embeds it as editable art
with Link on or off, so the tooltip was wrong for it. The dialog now
reads the file's header with the same PostScript check file.place uses,
and describes such a file as it does other files Link does not keep
linked.
Turkish is now a complete-message language, so the message file.place
gives for a file that is not a VectorCraft document, a PDF or a
PDF-compatible .ai file needs a tr row.
The File Handling preference Link Placed PDF and .ai Files
(linkPlacedPdf) is removed. File › Place with Link now places a page of
a PDF, .ai or .ait file read as PDF pages as a linked placed document,
as it links a raster image, and link: false embeds the page. Link is on
by default in the Place dialog and in file.place, so a PDF or .ai page
placed from a file, which used to be embedded, is now linked unless
Link is off. A password or textAs: "outlines" still embeds, and so do
EPS and PostScript .ai files.
The Place dialog's Link tooltip describes a PDF or .ai file as a
document.
A .ai file read from its editing data (its PDF part only a placeholder)
finds the files its art places by name, beside it. Package now copies
those files into the same folder as the .ai file, and warns when one has
to be copied under another name.
File › Place with Link kept a link only to raster images and VectorCraft
documents. A PDF, .ai or .ait page was always embedded as a group, so
Update Links never saw a newer version of the file.
A new preference, File Handling › Link Placed PDF and .ai Files
(linkPlacedPdf), is off by default. With it on, Link places the page as
a placed document, as it places a VectorCraft document: the object
keeps the file's bytes, a preview, the page number and the page's box
(crop, art, trim, bleed or media, stored as page_box). Links check,
update and relink it, Break Link and Object › Expand give the group
placing it without Link gives, every output draws the page's vectors,
and saving and reopening keep the link. With the preference off, or
with a password or textAs: "outlines", the page is embedded as before.
EPS and PostScript .ai files are still embedded.
Package copies a placed PDF or .ai file as it is. The Place dialog's
Link tooltip describes a PDF or .ai file as a document while the
preference is on. Relinking a placed document to a file that is not a
VectorCraft document, a PDF or a PDF-compatible .ai file says so.
Docs: docs/mcp.md, docs/target-app-parity.md.
Review follow-up (CodeRabbit): step_value clamped once after all of a
frame's steps, so at the lower bound a step down then a step up in the
same frame left the field at the bound. Each step is now clamped as it is
applied. The Stroke panel test steps down and up in one frame at each
bound and expects one step above it; it fails without the change.
Wheel notches and the arrow keys over a focused number field step the
value its text shows (widgets::step_value), and nothing there knew the
field's bounds: the Stroke panel's Weight went on to -3 pt and its Limit
to 0 x in the field, while the panel only clamped what it applied (the
spinner's min, Limit's clamp to 1..500), so the next step went on from
the out-of-range text. A label scrub (scrub::field) had the same gap.
step_value and scrub::field now take the field's bounds and stop at
them, so the field shows where they stopped and the value applied is
the same. The fields that have bounds pass them:
- spin_field / spin_field_auto / spin_plain: their `min` (the spinner
and a typed value already stopped there);
- range_field: its range; it now also takes a value that may be blank
(None), as mixed_field does;
- opt_field (dash and gap lengths): 0;
- the Revolve dialog's values: their range.
Fields that clamped only what they applied now say their range with
range_field instead: the Stroke panel's Limit (1..500), the Transparency
and Properties opacity (0..100), the Transform panel's polygon sides,
and the dialogs' bounded counts, percentages and lengths (Blend Options,
Envelope, Flattener, Liquify, Freehand, Live Paint, Path ops, Perspective
Grid, PNG ppi, Print, Save for Web, Shape Builder, Slices, Swatch
Options, Symbol options, Justification, Dimension, PDF page, the effect
dialogs' number rows). docs/ui-parity.md's number-field row says so.
Test: panels::stroke::tests::the_wheel_stops_weight_at_zero_and_limit_at_one
(the Stroke panel in a headless frame; focus Weight at 1 pt and turn the
wheel down four notches: the field shows 0 pt and the stroke is 0 pt;
the same with Limit at 2 x: 1 x and a limit of 1). It fails without the
change (the field showed -3 pt) and passes with it; the other numeric
field, scrub and dialog tests still pass.
An export through the desktop save panel kept any typed extension
that names a known format, whatever format it wrote. Export As with
PNG chosen and card.svg typed wrote PNG data to card.svg, and Save as
PDF, Save for Office Documents, Text Export Options, Export Library
and the other writes that go through pick_path did the same.
document.exportSelection with a format suggested and added .png
whatever the format.
Such an extension is now replaced with the written format's, as Save
As has done since #3372: card.svg is written as card.png. An extension
of the written format stays as typed in any letter case (photo.JPEG
for JPEG), Export As SVG (and file.exportAs, file.export.svg) keeps
art.svgz and writes it compressed, and document.exportSelection
without a format still writes the format the typed extension names.
With a format, it suggests and adds that format's extension.
Docs: docs/target-app-parity.md.
File › Package… read every linked file and font and wrote the package
on the UI thread, so the window stopped responding until the package
was written. For a document that links 50 images of 21 MB each, OK
with Background Export off, which does the same work, takes a median
of 2.25 s in a release build on Windows.
With Enable Background Export on (Preferences › File Handling), OK now
takes the options, the report's Document Info text and a snapshot of
the document, and closes the dialog. The thread that writes background
saves and exports then collects the links and fonts and writes the
package. The same OK takes 0.001 s, and the package holds the same
bytes. A save of unsaved changes that Package starts is written first.
With the preference off, on the web and over MCP and scripts, the
package is made at once with the same reply as before.
Docs: docs/target-app-parity.md.
With the Artboard tool, Paste and Paste in Place select the pasted
artboards, Cut gives the tool the artboard before the first cut one
(the new first artboard when artboard 1 is cut), and tool.setOption
sets its selected or active artboard. The active artboard, the view's,
stayed where it was, so the navigator showed another artboard than the
tool and the Artboards panel. document.activate and File › Revert
started the tool on artboard 1, while choosing a document's tab starts
it on that document's active artboard.
After a command that changes the artboards the Artboard tool has
selected, the app now makes the tool's active artboard the active one,
as a click with the tool does. After a command that chooses another
document, and after Revert, the tool starts on the document's active
artboard.
Docs: docs/mcp.md, docs/target-app-parity.md.
The Layers list scrolls in its own scroll area, which kept egui's floating
scroll bar when #3093 gave the panel bodies a bar in a column of their own.
Hovering the list showed the bar over the right edge of the rows, over the
target circles and the selection squares the user clicks to select a layer.
The list now uses the menus' slim solid bar (list_scroll_style) in its own
column, and the rows end beside it. The list knows its total height before
it lays the rows out, so the bar is shown exactly when the rows overflow
(decided up front): the rows never shift sideways a frame after the bar
appears.
Test: dock::tests::the_layers_scroll_bar_covers_no_row (40 layers in a
1440x600 window: no row's accessibility rect intersects the bar). It fails
without the change and passes with it. The Layers tests that pin row
positions across frames still pass. Checked live: the bar sits beside the
rows.
With the pointer resting over art while the view moves (trackpad or wheel
scrolling, pinch zoom, a Hand/space pan), the document point under the
pointer changes every frame, and the canvas hit-tested the art there on
each one, up to three times: Object Highlighting (canvas.rs
hover_highlight -> selection_hit_at) and the Selection tool's cursor
(select.rs cursor: hit_test, and with Snap to Point the nearest-anchor
search). Over a detailed engraving in a clip group (the reporter's
PhotoCraft icon: one path of tens of thousands of segments) each of those
costs roughly 3-6 ms, so every frame of the move took 12-15 ms of UI time;
off the art they return at once.
- canvas.rs: the canvas notes when its view (rect, zoom, centre,
rotation) last changed. Until it has been still for HOVER_STILL_SECS
(0.15 s) it draws no hover highlight and keeps the cursor the tool last
gave for the same tool and modifier keys instead of asking again; it
asks for a repaint then, so both come back without the pointer moving.
Pointer moves still reach the tool as before.
- test: a_moving_view_leaves_the_art_under_the_pointer_unhighlighted_until_it_stops
(wheel frames draw no hover outline and keep the cursor; once still,
the outline returns and the cursor follows the art). It fails on main
at the first wheel frame.
Measured on an Apple M4 (release build, 1440x900 window, the PhotoCraft
app icon SVG fitted at 138%, Selection tool, Smart Guides and Snap to
Point on), median UI frame time over 60 wheel-pan frames with the pointer
resting:
before: over the art 12.3-15.0 ms, off the art 1.0 ms
after: over the art 0.4-1.0 ms, off the art 0.6 ms
The render readout is the art's own raster time on the render worker,
which this change doesn't touch.