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.
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.
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 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.
The SVG writer always wrote the document's title in <title>
(crates/svg/src/export.rs), so every file of Export As with Use
Artboards, Export for Screens or an `artboard`/`range` export was titled
after the document ("Icons"), whatever its artboard was called. The
writer's `artboard` option can't tell those apart from Save: Save writes
the first artboard too, standing for the whole document.
ExportOptions has a new `artboard_title` (set by the caller, as
`artboard` is, not deserialized): the file is that one artboard, exported
on its own, and its <title> is the artboard's name (XML-escaped as
before), or the document's title when the artboard has no name. The
engine's SVG encoder (fileio/svg.rs) sets it exactly when the export
names its artboards, which is also when a file holds only that
artboard's art (#550): Export As per artboard, document.export /
serialize with `artboard` or `range`, MCP export, and Export for Screens'
artboards, assets, layers and selections (each written as an artboard
named after it). Save and Save As keep the document's title. Export for
Screens' Full Document draws the art bounds as one artboard, which is now
named after the document, so its SVG keeps the document's title too.
docs/file-format-parity.md's SVG row says so.
Tests: title_control_chars::an_artboard_exported_alone_is_titled_after_it
(crates/svg: two named artboards, the second named with markup;
exporting either alone gives its escaped name, the default and an
artboard without artboard_title give the document's title, an unnamed
artboard falls back to it) and
tests_svgenc::an_svg_of_one_artboard_is_titled_after_it (the engine:
`artboard` 0 and 1, useArtboards range all, Save's SVG, Export for
Screens per artboard, an asset and Full Document). Both fail without the
change (the title was "Icons") and pass with it.
A line style's `Patn` was copied twice (filtered, then scaled) whatever its
length, before any budget saw it. Patterns longer than 64 lengths now import
as solid lines, with a warning.
Addresses CodeRabbit's third review on #3548.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Overlays bake child-first: a group's overlay now lies over its children's
(they used to be applied after it and win). Each one pays out of the
import's overlay work budget.
- Master page instances inside embedded documents are left out with a warning:
their indices count the embedded document's masters, which aren't kept, and
would show the outer document's.
- A text outline's gradient stops and dashes are charged to the text attribute
budget, like the fill's.
Addresses CodeRabbit's second review on #3548.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- One budget for the whole read (layers, pixels, characters, table
cells), shared with embedded documents and charged also when their
reading fails; the first reading is dropped before the second.
- Tables: at most 65 536 cells; lines and cell formats that don't
match the grid are left out instead of shifted.
- Paragraph formats matched to runs by binary search; font names
longer than 256 bytes are dropped.
- Overlays: one work budget per import; encoded originals decoded
within their layer's size; table cell outlines recoloured too.
- Engine: master page copies capped, each page's master found in
linear time, line gaps looked up once per font, the working CMYK
table built once, objects past MAX_COORD left out.
- Embedded content stays with its node; pixel masks under artistic
text are read whole and include stroke reach; gradient overlays on
hidden layers are kept.
- Render: contexts and raster effect images stay within
MAX_RASTER_SIDE per side (vello overflowed near u16::MAX; found by
the new fuzz target).
- Tests: affinity tests/import_limits.rs, feature samples in
import_fuzz.rs, shared builders in engine/tests/support/.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Strokes set to None no longer import as black 1px strokes.
- Underline and strikethrough are kept.
- Frame text starts at cap height; lines hanging out of a frame make it taller;
mixed-alignment artistic text lines up within its longest line; line breaks
inside a paragraph get no paragraph spacing.
- Table cell text taller than its cell is centred and stays visible.
- Outline effects on stroked shapes are drawn around the stroke.
- CMYK pixel layers go through the document's embedded CMYK profile; without
one, opaque layers stay CMYK.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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 the arrow keys did nothing to the artboards: the
tool claimed only Escape, Delete and Backspace (tools/src/xform/artboard.rs
claims_key), so the arrows fell through to object.nudge in
ui-egui/src/shortcuts.rs, which moved the selected art on its own (or
nothing, with no art selected) and left the artboard where it was.
- artboard.rs: the tool claims the arrow keys and turns each press into
one artboard.move of the selected artboards that aren't locked, by the
keyboard increment (Preferences > General > Keyboard Increment, read
from the tool context as Touch Type does), Shift ten times as far as
object.nudge does, with moveArt from the tool's Move/Copy Artwork with
Artboard option, as a drag sends it. One undo step per press; nothing
during a drag or with only locked artboards selected.
- tests: arrow_keys_nudge_the_selected_artboards (tools: claims, step,
Shift, moveArt, several boards, locked, mid-drag) and
artboard_tool_arrow_keys_move_the_selected_artboards_with_their_art
(engine: the artboards and the art on them move, one step per press).
Verified live over --control: a 400x300 artboard with a selected 40 pt
square, Artboard tool, ArrowRight: before, the artboard stayed at x 0 and
the square moved to x 51 ("Move"); after, the artboard goes to x 1 and the
square with it ("Move Artboard"); Shift+Down moves both 10 pt; with
Move Artwork off only the artboard moves; increment 5 moves 5 pt.
The drag case in the report: a drag already moves the art, but only the
art that lies wholly inside the artboard and is unlocked and visible
(doc/src/lib.rs art_on_artboard; locked and hidden art only with
Move Locked and Hidden Artwork with Artboard). Art overlapping the
artboard's edge stays behind by design, which may be what the reporter
saw when dragging.
Replace length/angle with Length X/Y, reference point, Type, Base Along, Slope and Fill Arc; extend geom and shape.arc params.
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
Exporting a CMYK document to PDF/X-1a or PDF/X-3 flattens transparency
first. Where the flattener has no single colour (an image with see-through
pixels over a flat fill, gradients, masks, raster effects) it renders the
area into an image, and that image was always an RGB PNG. The PDF writer
then separated it back to CMYK, so CMYK art behind the image came back with
other inks: a C100 M6 Y41 K3 panel printed C75 M14 Y35 K5 under a
transparent logo, with a hard edge against the rest of the panel, and K
moved into C, M and Y.
For files without transparency the flattener's images are opaque and
clipped to their regions anyway, so in a CMYK document it now draws them
as ink amounts over the paper (Renderer::render_region_inks) and stores a
CMYK TIFF, which the PDF writer keeps as DeviceCMYK samples. An area too
large for a CMYK image falls back to the PNG it made before. In-app
Flatten Transparency and other flattening, whose images keep alpha, are
unchanged.
Test: tests_pdfx::pdfx_1a_flattening_keeps_the_inks_behind_an_image_in_a_cmyk_document
exports a CMYK panel under a half see-through PNG to PDF/X-1a, opens the
file again and checks the inks behind the see-through half.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
## Summary
Swatch library panels title imported and User Defined .acb color books from the file name (without the extension), matching Illustrator. Host temporary import paths still fall back to the book title when copying into the user library folder (#2996).
## Tests
- `color_book_tab_titles_use_the_file_name` covers user-folder scan, load by path, and copyToUser keeping the source file name.
- Existing `copying_a_color_book_from_a_temporary_path_uses_its_title` unchanged.
Closesstorytold/vectorcraft#3395
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
path.create read closed only when it drew from anchors. Given path
data, {"d": "M0 0 L10 0 L10 10", "closed": "true"} drew the path and
recorded an undo step, while the same closed with anchors fails with
"`closed` must be true or false". shape.rectangularGrid and
shape.polarGrid read rows, columns, concentric and radial given as text
as the count left out, so {"rows": "3"} drew the default 5 horizontal
dividers instead of 3.
path.create now checks closed before it reads d or anchors, and the
grid commands refuse such a count with "`rows` must be a number, not
"3"". Neither command changes the document or its undo history then.
With d, a closed of true, false or null still leaves the path as its
path data draws it.
Docs: docs/mcp.md, docs/target-app-parity.md.
Add the three Turkish catalog entries requested by the maintainer on #3479. Preserve upstream catalog additions and place existing tab-drag entries beside stable menu labels to avoid append conflicts. Update feature progress notes for all 24 catalogs.
Simplify an equivalent Effect Gallery selection predicate required by the Rust 1.95 clippy gate. Full cargo xtask ci passed all 10 checks, including tab-drag regressions and wasm.
## Summary
Linux file portals grey out files that do not match the open dialog's default
filter. Other Library… and Locate Library… used File › Open filters, whose
first entry is All readable files and omits swatch-library extensions such as
.ase. Add library_open_filters with the library formats first (matching
Illustrator's Swatch Libraries › Other Library…) and use it from swatch and
graphic style library pickers.
## Tests
- engine: library_open_filters_list_library_formats_first
- ui-egui: other_swatch_library_asks_off_the_ui_thread asserts Swatch libraries
is the default filter and includes ase
Closesstorytold/vectorcraft#3465
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
the_reply_names_each_expanded_copy_by_its_own_id checked that no id in
the effect.expandAppearance reply repeats and that each one names a
node in the document. Only for group copies did it check which ids the
reply holds. In the other four cases a reply that left out the
expanded object, or held no ids at all, passed.
The test now checks in every case that the reply starts with the id of
the selected object, and keeps the exact check of the group and the
two copies of the square for group copies.
object.transformEach refused reflectX, reflectY, random and copy given
as text, but read strokes, corners, objects and patterns given as text
as the key left out. With Scale Strokes & Effects on,
{"scaleH": 200, "scaleV": 200, "strokes": "false"} scaled the stroke,
recorded an undo step and kept the params for Transform Again, which
repeated the call. artboard.rearrange, artboard.move and
artboard.duplicate read their patterns option the same way, and
artboard.setProps reads its patterns, strokes and corners options the
same way.
These commands now refuse such a value with "`<key>` must be true or
false" before they change anything. So do the other options and
numbers of artboard.setProps and artboard.move, the art of Duplicate,
Copy and Cut Artboard, the shape commands and path.create, path.close,
path.setHandle, path.moveAnchors, the Pencil, Curvature, Reshape
Segment and Blob Brush options, text.create, text.createInPath,
text.setRangeStyle, text.editRange, text.getRange, text.touchType,
text.areaOptions, type.step, the font size steps,
type.opticalMarginAlignment, type.systemLayout, type.fractionalWidths,
type.ruby, the symbol and swatch commands, the Appearance panel's
commands, the gradient and freeform gradient commands, Shape Builder,
Live Paint, Image Trace, the opacity of object.setProps, the radius,
sides and relativeRadii of object.setLiveShape, the reference point of
object.setBounds and the artboard of Align, Distribute and Distribute
Spacing. They refuse a number given as text with "`<key>` must be a
number, not <value>", and a gradient stop's tint with "stop <n>:
`tint` must be a number". A null still reads as the key left out.
Docs: docs/mcp.md, docs/target-app-parity.md.
EPS, EMF, WMF and DXF exports gave each image they left out because it
couldn't be decoded its own warning, "image '<key>' could not be
decoded and was left out", which names the internal key the document
stores the image under. A version 7 .ai said "an image that can't be
decoded or is too large was left out".
The four writers now give the note raster exports, PDF and the legacy
.ai flattening give, "an image couldn't be decoded and is missing from
the picture", once per export. In a version 7 .ai it is one line with
the note for flattened art. The other image notes in the same
functions no longer name the key: EPS's note for an image too large to
write, and DXF's notes for an image with no pixels (its rows in 23
catalogs are reworded) and one that could not be written. Legacy .ai
gives a separate note for an image that is empty or too large.
Docs: docs/mcp.md (EPS, DXF export, EMF and WMF, aiVersion "7").
When the Compression settings keep a PNG as it is, the PNG goes to
the PDF writer unchanged, and the writer refuses images of 16 bits a
sample below PDF 1.5. Saving a document with a placed 16-bit PNG as
PDF 1.4, or as a .ai file at 1.4, failed with "PDF writer error:
SixteenBitImage(...)" and wrote nothing. PDF 1.3 and PDF/X-3 failed
the same way for an opaque 16-bit PNG, and PDF/X-1a failed for a
16-bit gray PNG.
In PDF 1.3 and 1.4 files, such a PNG is now decoded and encoded again
at 8 bits a sample, with the same color type and ICC profile, before
the writer reads it. From PDF 1.5 on, it keeps its 16 bits. A 16-bit
PNG whose pixel data can't be decoded is left out with the note "an
image couldn't be decoded and is missing from the picture".
Docs: docs/mcp.md (PDF options).
## Summary
object.align now moves selected ruler guides when aligning to the artboard,
matching Illustrator and the existing Distribute behaviour. Guide-only
selection can run align; the Align panel enables its buttons when only
guides are selected. Guides still stay put when objects are in the selection
and Align To is Selection; key-guide behaviour is unchanged.
## Tests
- engine tests_keyguide: guides_selected_alone_align_to_artboard; updated
selected_guides_align_to_a_clicked_key_object for artboard align
- ui-egui align panel: the_align_buttons_align_guides_selected_alone_to_artboard
Closesstorytold/vectorcraft#3463
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
Activate visible tabs after a delayed Selection drag and copy artwork through a document-scoped command without changing the clipboard or source. Cancel stale transfers, preserve pattern alignment and transparency, and insert into the editable destination context. Cover pointer gestures, resource rendering, input bounds and locked layers; translate the new labels and messages.
A face hidden on its own (for example with Object > Hide after Live
Paint Selection picks it) while its Live Paint group stays visible was
still highlighted, selected by Live Paint Selection and filled by the
Live Paint Bucket, and it kept the face of a group behind it from being
picked.
live_paint_at and lp_part_at now take the front-most face that is not
hidden (visible_face_at). A point on a hidden face falls through to the
visible face of a group behind it; where there is none, an edge pick
takes the nearest visible edge as it does outside the faces.
livePaint.fill also skips hidden faces. With group, a point on a hidden
face is an error. With ids, it fills the face there of another Live
Paint group among the objects, or else fails and does not merge the
group.
Docs: docs/mcp.md.
The docstring check of the review counts the functions a pull request
touches: effect.apply, effect.setParams, plugin.run, the line cap
name, the reader of an output object and seven of the object tests had
no doc comment. Each now says what it does or checks; the join and cap
names get one each.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
A filter could reshape and repaint objects, but not set the stroke's
join, cap or miter limit, nor the effects an object's appearance holds.
A tool that prepares objects for a live effect (a sheet of samples,
each with the effect at its own settings) had to leave the user to
apply the effect to each object by hand.
- Each input stroke has its join, cap and miterLimit beside its paint
and width (live effects get the same, through the shared encoder),
and each object its own effect records, {id, params, visible}.
- An output stroke may set them (miter limit clamped to 1-500), and an
output object's "effects" replaces its own effects.
- The host admits each record as effect.apply does: an effect that
effect.list lists or an installed live-effect plug-in, parameters it
takes (filled in to their defaults; a plug-in's checked against its
manifest), one 3D effect that fits the object (its fills' and
strokes' own effects counted), and the texture of an effect with
Load Texture (Glass, Texturizer) as the effect stores it, never a
file to read. A record the object already had stays as
it is, so a filter that hands its input back changes nothing. One
refused record refuses the whole output and the document stays as
it was.
The plug-ins crate checks the records' form; which effects exist is the
engine's to say (objects::AdmitEffects, engine plugin::admit_effects).
effects::validate_three_d is the one check of whether a 3D effect fits
an object, for effect.apply, effect.setParams and the filters.
A path handed back as it was now stays the live shape it was: write()
dropped a live rectangle's or ellipse's parameters whenever the output
held a path, changed or not, so a filter that returned its input
turned a live rectangle into a plain path.
objects/tests.rs checks the round trip, stroke styles on existing and
new strokes, the host's refusal, the bad forms refused, and a live
rectangle handed back unchanged (a plain path before the fix);
tests_plugins.rs runs filters that set a built-in and a plug-in effect,
an echo that changes nothing, and six refused outputs.
CRAFT_PLUGIN_FRAME_WASM=<folder> saves a test filter to try in the app.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
## Summary
Opening or placing a .pmd file now fails with a dedicated unsupported-format
hint (like DWG and PICT), telling users to export PDF or EPS from PageMaker
instead of a generic unknown-format error. document.formats lists PageMaker
in its unsupported table.
## Tests
- formats_list_dxf_and_what_can_not_be_written
- dwg_and_pict_say_what_to_use_instead (includes brochure.PMD open)
Part of storytold/vectorcraft#3343
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>