Review follow-up (CodeRabbit): a gradient drawn as pixels was sized from its
area in the space it is drawn in, so type scaled 4 times got a quarter of the
raster effects resolution; the image is now sized from the area on the page,
within the same pixel caps. And type whose fill is drawn as art (a dithered
or freeform gradient's image, a pattern) went out as outlines only; its
characters are now also written as invisible text (rendering mode 3), as
unpainted type already is, so they can be selected and searched.
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.
Review follow-up (CodeRabbit): a dithered fill skipped set_paint's guard for
a gradient without stops and sampled a transparent grid to draw nothing. It
now takes the same no-fill path.
Review follow-up (CodeRabbit): a large dithered gradient mostly off-screen
sampled its whole grid (up to 4096 x 4096 cells, 64 MiB) to draw a small
visible part, and did it again on each zoom step. The renderer now samples
only the cells the render target can show, a cell more each way for the
bilinear mix, snapped out to 256-cell tiles so a pan within them reuses the
grid. Cells keep their place in the whole grid, so the gradient and its
noise are the same pixels as before (GradientPaint::pixels_within).
With Pick Color From Artwork, a Shape Builder merge took the look of
the front-most object over the first covered region the drag touched,
and the current fill when the drag touched only areas no object
covers. The reference app's combine page gives the look of the object
where the drag starts, else of the object where it ends, else of the
topmost selected object.
shapeBuilder.merge now follows that rule: the merged shape takes the
appearance, opacity and blending mode of the front-most of the objects
it is given (the selection by default) under the first point, else of
the one under the last point, else of the front-most of those objects.
A drag from a hole across a gray bar into a red square now gives a red
shape, where it gave the bar's look. A click in an area no selected
object covers, such as a hole that gray bars enclose or a triangle
that lines enclose, takes the front-most selected object's look
instead of the current fill. Pick Color From Color Swatches works as
before. The tool already sends the press point first and the release
point last.
Docs: docs/mcp.md, docs/target-app-parity.md.
## Summary
Illustrator `%AI9` art styles already stop reading filters after 4096 entries so
hostile styles cannot grow without bound. This adds a regression test that
importing a style with more than `MAX_FILTERS` basic filters finishes with a
bounded effect list, matching the existing hardening behaviour.
## Tests
- `art_styles_ignore_filters_beyond_the_cap` in `crates/eps/src/import/ai/style.rs`
Part of gap G8
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
## Summary
On Linux, asking winit to maximize before the window is mapped often does
nothing, and egui sometimes omits the maximized viewport flag while the
window manager still has the window maximized. Defer maximize until the
first frames after startup, and read maximized state from winit when
saving geometry for the next launch.
## Tests
cargo test -p vectorcraft window::
cargo clippy -p vectorcraft --all-targets -- -D warnings
Closesstorytold/vectorcraft#3635
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
## 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)".
PDF import measured each gap between two glyphs against the larger of
the line's first glyph size and the next glyph's. A word set beside a
much larger one, apart by positioning and no space glyph (a browser's
margin between two sizes), stayed under a fifth of that em: "≤ 4" at
20 pt and "hours" at 8 pt, 3.4 pt apart, came in as "≤ 4hours".
A gap is now measured against the smaller of the two glyphs beside it.
Lines in one size read their gaps as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Shared groundwork for gradient options SVG and PDF can't express (Illustrator
30's dither and perceptual gradients): GradientPaint::pixels samples a placed
linear or radial gradient on a grid (Ramp: its stops, midpoints as stops, mixed
as the renderer mixes them), and draws_as_pixels says which gradients are drawn
that way (freeform ones, so far). The SVG and PDF writers' freeform-gradient
image path takes any such gradient, a linear or radial one at the document's
raster effects resolution. No change in output yet.
A PDF or .ai page placed linked is a placed document, and File ›
Package listed only the fonts of the document's own type, so the fonts
of the page's type were left out of Fonts/ where an embedded page's
were copied. Package now reads each linked page a document places and
adds the fonts of its type, also when another document placing the same
file copied it first, and also with copyLinks off.
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): a line that wasn't valid JSON was reported
by its line only, while every other --commands error names the run's step.
It now says `step N (line L)`, N counted with the --cmd steps before it.
run_refuses_malformed_commands_naming_the_step expects `step 2 (line 3)`;
it fails without the change.
Review follow-up (CodeRabbit): the two older G2 bullets counting 46 and 49
filters were superseded by Conté Crayon (57/57) and are folded into one
current bullet; the G2 row in the ranked table and the entry's estimate now
both say 4–6 h, as ROADMAP.md and roadmap.md do. ROADMAP.md's revision
history gains its 2026-10-22 row.