Commit Graph
1367 Commits
Author SHA1 Message Date
Taha-Mahmoodi 64f6d2db50 Artboards: Artboard Options' Show Center Mark and Show Cross Hairs draw on the canvas
The Artboard Options help pages describe two display options
(https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/modify-display-settings-of-artboards.html,
.../set-video-display-options-for-artboards.html): a mark at the artboard's
center, and lines from the middle of each side through the center. The
document already had show_center_mark and show_cross_hairs on Artboard, but
nothing set or drew them.

artboard.setProps (each `artboards` entry too) and artboard.new take
showCenterMark and showCrossHairs as true/false, in the same single undo
step as the other fields; a locked artboard can still change them.
Artboard Options has both checkboxes, filled in from the artboard whether
it opens from the panel or a double-click with the Artboard tool, and its
New Artboard form passes them to artboard.new. The canvas draws them in the
new theme token artboard_mark, 1 point wide on screen at any zoom: a cross
12 points across at the center whose arms follow the artboard's sides, and
the two cross hairs. They are screen-only like the edges; exports and print
never read the fields. inspect_document reports them when on. Saved files
are unchanged: the two keys were already always written, so a document that
never shows them saves byte for byte as before, and a file without the keys
opens with both off. Video safe areas, video rulers and the pixel aspect
ratio are not part of this change.

Verification: artboard_display_marks_are_set_and_cleared_in_one_undo_step,
artboard_display_marks_round_trip_through_the_native_format,
artboard_display_marks_stay_out_of_exports (SVG and PDF identical with the
marks on and off), an_artboard_shows_its_center_mark_and_cross_hairs
(positions, screen-constant size under zoom, rotated view) and
artboard_options_show_center_mark_and_cross_hairs. cargo xtask ci: all 10
steps passed.
2026-10-23 09:42:09 +07:00
Taha-Mahmoodi 08cfb45561 Simplify: Object › Path › Simplify opens its slider at the Auto-Simplify level for the selection
The Simplify help page says the slider starts at an auto-simplified value
(https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/manually-simplify-paths.html).
The slider opened at a fixed level 50 (1 pt), whatever the size of the
selection, so the first preview was the same tolerance for a 30 pt icon as
for a 1000 pt drawing.

The level Auto-Simplify picks for the selection is now one engine function,
vectorcraft_engine::auto_simplify_level (the existing 1/400-of-the-longer-
side rule in vectorcraft_pathops), which object.path.simplify {auto: true}
and the slider's opening fields both use. Object › Path › Simplify (and
ui.menuDialog, the Properties and contextual task bar buttons, which go
through the same menu dialog) opens the slider there, and the canvas
previews that result; Auto-Simplify still moves the slider back to it.
The retained Simplify dialog path is unchanged.

Verification: simplify_slider_opens_at_the_auto_simplify_level_of_the_selection
opens the slider on 1140, 285 and 28.5 pt waves (levels 35, 55, 89) and
checks the preview is exactly object.path.simplify {auto: true}'s result;
without the change it fails with level 50 != 35, and
simplify_opens_a_slider_whose_drag_previews_fewer_or_more_points fails with
{"level": 50} != {"level": 55}. cargo xtask ci: all 10 steps passed.
2026-10-23 09:42:07 +07:00
dotCooCoo ddaca94e5c Export for Screens: files are encoded and written on the background thread
Export for Screens and the Asset Export panel's Export encoded and wrote
every file on the UI thread, so the window stopped responding until the
last file was written. For 50 artboards in PNG at 1x, 2x and 3x, PDF and
SVG (250 files, 47 MiB), the dialog's OK took a median of 43.8 s in a
release build on Windows.

With Enable Background Export on (Preferences › File Handling), OK now
checks the settings, remembers them and takes a snapshot of the
document, and the dialog closes. The thread that writes background
saves and exports then encodes the files and writes them into the
folder, or one ZIP in it, and the export progress dialog's Cancel stops
it before the next file. The same OK takes under 0.001 s, and the files
have the same names and bytes. With the preference off, without a
folder, on the web and over MCP and scripts (document.exportForScreens
and assets.export), the files are made at once with the same reply as
before.

Both commands report one progress step per file, so the progress dialog
shows "File 3 of 250" and MCP progress notifications count the files in
place of the encoders' artboards and pages.

Docs: docs/target-app-parity.md, docs/control-protocol.md, docs/mcp.md.
2026-10-23 09:22:20 +07:00
dotCooCoo 5fe4f3ead1 Place: files are read and decoded on a background thread
Squashed from #3740 onto main.
2026-10-23 09:07:55 +07:00
Jürg Lehni c28ad99678 Type: OpenType SVG colour fonts draw and export as vector art
Each glyph's SVG document is read by the SVG importer, which the engine registers with the text crate.
2026-10-23 09:07:54 +07:00
Angelo Bernardini ef1fa1d228 G12: Constrain Angle for the arrow keys, Shift drags and new objects (#394)
Squashed from #3363 onto main.
2026-10-23 08:54:57 +07:00
Louise FoxandClaude Opus 5.5 79cb38f678 Plug-ins: heavy filters, allowed at install and run in the background (G17)
A filter plug-in gets 50 M instructions and a minute for each run.
Filters that order a plotter's paths, pack shapes or run a simulation
need minutes, and the app shouldn't freeze while they compute.

A filter's manifest may now ask for "budget": "heavy" (Budget): each run
gets HEAVY_FUEL (1 G instructions) and HEAVY_WALL_TIME_MS (300 s). Only a
filter may ask. plugin.install refuses such a filter without allowHeavy:
true, and plugin.inspect returns the entry a module would have without
installing it. Install Plug-in… and File › Open ask the user and install
on OK; the plug-in store keeps the answer, and folder loads skip heavy
filters with a note, since they ask nobody.

The app runs a heavy filter on a thread of its own (plugin_runs), not in
Background Save's queue, with the status bar and a progress window
(Cancel, Continue in Background) that it shares with Background Export
(export_progress::window). FilterRun splits plugin.run: prepared on the
session, run on any thread, its output handed in, and the next
plugin.run with the same params applies that output instead of running
the plug-in again, so the edit stays one guarded, journaled command. A
result whose document or plug-in changed meanwhile is dropped, and the
status bar says to run it again. A cancelled plug-in run is now
EngineError::Cancelled.

What a filter keeps with the document (#3720) goes with the run: it is
given _context.documentData when the run is prepared, and the
documentData of its output is applied with its objects, in the same undo
step, when the run is handed in (FilterRun::finish).

Tests: plugins/tests/abi.rs (100 M loop steps out of a light budget,
within a heavy one; folders skip heavy filters), manifest.rs, engine
tests_plugins.rs (consent, inspect, the store, hand-in, a handed-in
run's document data, cancel) and
ui-egui tests_plugin_runs.rs (installing asks, no Preview, a background
run as one undo step, a stale result kept out, the progress window).
CRAFT_UI_HEAVY_FILTER_WASM=<folder> writes a test filter to try in the
app. Its strings and messages have rows in each complete catalog
(COMPLETE_MESSAGES).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
2026-10-23 08:18:47 +07:00
Angelo BernardiniandClaude Opus 5.5 2cf37aea2f G4: paragraph Auto Leading drives the leading panel, nudge and placeholder fill
Hyphenation... and Justification... already shipped upstream (#2310,
#2755). This ports the part of the archived fork PR #10 that upstream
lacked: a paragraph's Justification > Auto Leading now also sets

- the Leading the Character (and Properties) panel shows for Auto type,
  blank where the selected paragraphs differ;
- the leading Alt+Up/Down (type.step) steps from, per paragraph (a run
  spanning paragraphs is split so each takes its own);
- the lines Fill with Placeholder Text counts in area type.

ParaStyle::effective_leading mirrors the layout (exactly
CharStyle::effective_leading at the default 120%). Tests in doc, engine
and ui-egui; parity docs no longer list Hyphenation/Justification as
missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014gCpjL3yUhaH3jRp7ksufh
2026-10-23 07:02:49 +07:00
dotCooCoo e4a3adcea0 Shape Builder: a Shift-drag draws a marquee and merges every region it reaches
A Shape Builder drag merged only the regions the pointer passed over,
and the tool did not read Shift. The reference app's Shape Builder page
describes a Shift-drag that draws a rectangular marquee to merge many
paths at once.

With Shift held at the press, the drag now draws a rectangular marquee
from where it started to the pointer. The regions it reaches are shaded
while it is drawn and merge into one shape on release; with Alt also
held at the press, they are deleted. The marquee reaches each region
whose outline crosses it or has an anchor inside it, and the region that
holds the whole marquee. A Shift-click that does not move is a click,
and a drag without Shift works as before. With In Merge Mode, Clicking
Stroke Splits the Path on, a Shift-click on a stroke splits it as a
click does; a marquee splits no stroke, and while it is drawn the
pointer does not show scissors and no stroke is highlighted.

shapeBuilder.merge takes the marquee as rect: [x, y, width, height],
where x and y are where the drag started, and points may then be left
out. For Pick Color From Artwork, the corner where the drag started
counts as the drag's first point, and the opposite corner counts as its
last. With rect, splitStroke does not split a stroke.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-23 06:51:20 +07:00
Taha-Mahmoodi 970e62f27d Fix: a path moved by its stroke or edge lands exactly on a ruler guide
Cause: with Snap to Point, the point of a moved selection that snaps to a
ruler guide is the grabbed point (#1534). Away from an anchor or center
that was the raw pressed spot (select.rs: `grab` fell back to `p`;
direct.rs: a straight segment's `grab: p`, MoveObject's `start`). A press
on a stroke or within the pick tolerance of an edge lies a few pixels
beside the path, so the pressed spot landed on the guide, "align" showed,
and the path stopped that far beside it: 0.096 pt at 3,891% and 0.0099 pt
at 28,757% in the #3701 screenshots, visible only when zoomed in.

Change: `guides::hold_on_path` finds the nearest point of the path (on a
straight segment exactly on its line). `select::hold_point` uses it for a
press on the stroke or outline, or within 6 px of it. The Selection tool
(`State::Moving::hold`), Direct and Group Selection moves
(`State::MoveObject::hold`) and Direct Selection's straight-segment drag
(`grab`) snap that point. The move delta still runs from the press, so
nothing jumps. A press in the middle of a fill still snaps the pressed
spot, as Snap to Point is documented to.

Verification: tests_prefs::a_move_held_by_a_paths_stroke_lands_the_path_on_a_ruler_guide
and tests_prefs::a_straight_segment_dragged_by_direct_selection_lands_on_a_ruler_guide
fail without the change (edge 0.0257 pt and anchors -0.0052 pt off the
guide) and pass with it (|gap| < 1e-9 pt). A headless egui/wgpu render at
3,891% and 28,757% puts the guide, the selection outline and the centre of
the 1 pt stroke band in the same pixel column. cargo xtask ci: all 10
steps passed; 9429 tests passed, 0 failed.
2026-10-23 06:13:02 +07:00
Taha-Mahmoodi f365468684 Fix #3233: an opened dimension whose label characters are off the colour mode is redrawn too
Review follow-up (CodeRabbit): a text node's characters carry their own fill
(CharStyle), saved apart from the node's appearance, so conform also checks
the runs of the label. dimension_colours_follow_the_document_colour_model
opens a CMYK document whose label characters alone are RGB; without the
change they stay RGB.
2026-10-23 06:13:00 +07:00
Angelo Bernardini 3f16fabd89 G11 #885: Transform effect options (Transform Objects, Transform Patterns, Scale Strokes & Effects)
Squashed from #3358 onto main.
2026-10-23 06:07:37 +07:00
Louise FoxandClaude Opus 5.5 1505384501 Plug-ins: filters keep data with the document (G17)
A filter plug-in had nowhere to keep settings that belong to the whole
document rather than to one object, such as the pens a plotter holds or
a machine profile: it could only ask for them again on every run.

- A filter's output may hold documentData: any JSON of at most 1 MiB
  (objects::MAX_DOCUMENT_DATA) that replaces what the plug-in keeps with
  the document, null removing it, in the same undo step as its objects
  (objects::decode_output). Too much refuses the output and the
  document stays as it was.
- A filter gets it back in _context.documentData (null until it keeps
  something). A filter reads and writes only its own.
- It is the document's foreign data, Document::unknown["pluginData"] by
  plug-in id (vectorcraft_doc::plugin_data), where the perspective grid
  and the brushes keep theirs. So the native file needs no section of
  its own for it, and hosts from before keep it when they save. An
  exporter is given the whole document, so it can read every
  plug-in's. Nothing else lists it, as the reference app's Document
  Info lists no plug-in data.

A live effect isn't given it: its geometry is worked out from the
object alone wherever it is drawn.

Tests: plugin_data.rs (each plug-in its own entry, forgetting what isn't
kept copies nothing, a damaged value holds nothing), objects/tests.rs
(kept, forgotten, refused over the limit), tests_plugins.rs (one undo
step with the objects, read back, not given to another filter, kept by
the native file among its foreign data, refused over the limit with the
document unchanged, null removes it) and tests/plugin_export.rs (an
exporter finds another plug-in's). The size error has its row in each complete
catalog (COMPLETE_MESSAGES).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
2026-10-23 05:40:00 +07:00
Jürg Lehni 9de4b40241 Type: COLR colour fonts draw and export as vector art
Glyphs whose paints documents have no form for (sweep gradients, compositing modes) are drawn as images of the glyph.
2026-10-23 05:14:47 +07:00
Taha-Mahmoodi 3d24c663d2 Fix #3233: dimensions are redrawn in the colour mode after Document Color Mode and on open
Follow-up to #3702 (CodeRabbit): dimensions are only redrawn when their
recipe changes. Document Color Mode converted their art through the colour
profile, so black became a four-ink C67 M67 Y67 K100 again, and CMYK files
saved before #3702 kept RGB dimension art.

dimension::refresh now also redraws when the document's colour mode
changes. dimension::conform, run when a document is opened or reverted
(Session::add_document, replace_document), redraws a dimension whose art
has colours in the wrong model; one that can't be redrawn keeps its art.

dimension_colours_follow_the_document_colour_model covers RGB to CMYK and
back, and a CMYK document with RGB dimension art being opened. Without the
change it finds C0.67 M0.67 Y0.67 K1 after Document Color Mode.
2026-10-23 04:26:05 +07:00
dotCooCoo 6da6b4440f Shape Builder: a split stroke keeps its path's direction and gradients
With In Merge Mode, Clicking Stroke Splits the Path on, the run a click
split off could run against its path. A click on a rectangle's top side
gave a side from (100, 0) to (0, 0) on a rectangle that runs from
(0, 0) to (100, 0) there, so arrowheads and other settings that follow
a stroke's direction moved to the other end. The run now goes the way
the path goes.

The two paths of a split, and what an Alt-click or a merge leaves of a
path, had a gradient fitted to the path's bounds fitted again to their
own bounds. They now keep it where it was on the path.

A run that left 0.05 pt or less of a path still split it, into the
run and a speck. Such a path now does not split, and the tool shows
no scissors over it.

Docs: docs/mcp.md (Shape Builder Tool Options).
2026-10-23 04:14:38 +07:00
Taha-Mahmoodi 866bf61116 Fix #3233: in a CMYK document dimension lines and labels are CMYK, black on the K plate only 2026-10-23 03:35:07 +07:00
Gert Pellin 368fa3def2 Paint: a fill or stroke given to a compound path's member paints the compound path (#3692) 2026-10-23 03:22:54 +07:00
bflatastic 8bf7ffe39d Snapping: tangent and perpendicular snapping, View > Snapping (#2530)
Squashed from #2537 onto main.
2026-10-23 03:22:54 +07:00
bflatastic c1f96cc6f8 Honor Preferences › Performance › Real-time Drawing and Editing during tool drags
Squashed from #2942 onto main.
2026-10-23 02:22:09 +07:00
makesomethingshit 4d9749f4c7 #3607: open Save for Web on the active artboard 2026-10-23 01:53:35 +07:00
bflatastic efe2429618 Shape Builder: in merge mode, a click on a stroke splits the path there
Squashed from #3665 onto main.
2026-10-23 01:53:34 +07:00
Taha-Mahmoodi 8924b24e94 Gradients: Expand to a gradient mesh keeps a Perceptual gradient's colour curve
Review follow-up (CodeRabbit): a linear gradient expands to a mesh with a
column at each stop, and the mesh mixes linearly between columns, so a
Perceptual blue -> yellow came out as the grey RGB mix in the middle.
`breaks` now also takes a column at each stop the Perceptual expansion adds
(Gradient::expanded), so the mesh follows the OKLab curve; Classic is
unchanged. the_mesh_option_makes_a_mesh_that_paints_the_gradient gains a
Perceptual case; without the change 1812 pixels differ from the gradient.
2026-10-23 01:53:30 +07:00
Taha-Mahmoodi 6beb68f934 Gradients: the Gradient panel's Method menu and paint.editGradient's method choose Classic or Perceptual
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.
2026-10-23 01:53:30 +07:00
Taha-Mahmoodi 2bad646821 Gradients: a Perceptual gradient mixes its colours in OKLab, and SVG and PDF write it as an image
Illustrator 30.0's gradient Method, from the public documentation
(https://helpx.adobe.com/illustrator/desktop/paint-and-fill/create-and-edit-gradients/control-perceptual-interpolation-in-gradients.html):
Classic or Perceptual; perceptual interpolation matches colour transitions to
how the eye perceives differences between colours, and perceptual gradients
are rasterised when saved as PDF or SVG.

- Gradient gains `method` (Classic | Perceptual; serde default Classic,
  written only when Perceptual, so older files open as Classic and Classic
  files are written as before). The colour crate's OKLab helpers open up for it.
- Perceptual mixes neighbouring stop colours in OKLab (sample, and
  sample_keep_model for the stroke meshes, Expand and blends); opacity stays
  linear. Renderers that mix linearly get stops on the perceptual curve,
  placed by halving each half segment where a straight line strays more than
  0.4 of a colour level (a nearly straight segment needs none). Classic
  gradients expand exactly as before.
- draws_as_pixels includes Perceptual gradients, so SVG and PDF write a
  Perceptual fill or stroke as an image at the document's raster effects
  resolution, clipped to what it paints. A gradient along or across a stroke
  stays slices (PDF no longer draws it as a within-stroke image).
- With Dither: a dithered Perceptual fill is the perceptual mix with dither's
  noise (the canvas's dithered grids are keyed by method too); SVG and PDF
  write it as an image with the noise, and a dithered Perceptual stroke as an
  image without it, as the canvas draws it (GradientPaint::pixels, SVG's
  raster_paint and PDF's area are told whether they paint a fill).
2026-10-23 01:53:29 +07:00
bflatastic 997dd8d2bd SVG import: images in images share the depth cap, SVGZ images one unpacking cap (#3562, #3563)
Squashed from #3676 onto main.
2026-10-23 01:28:52 +07:00
Taha-Mahmoodi e20c330bd3 Gradients: paint.setStroke refuses a dithered gradient when "swatch" is null
Review follow-up (CodeRabbit): the refusal skipped any request carrying a
`swatch` key, so `"swatch": null` with a dithered gradient got through.
Only a named swatch (a gradient swatch keeps its own dither) is exempt now.
The dither test sends `"swatch": null`; it fails without the change.
2026-10-23 01:01:28 +07:00
dotCooCoo 48792842ec Package: reads placed pages and document previews only for the options that use them
With Copy Links on and Copy Fonts off, File › Package still read every
placed PDF or .ai page to find the fonts of its type, and then dropped
them. With Copy Links on and Relink off, it still read the preview and
the PDF of each placed .vectorcraft document, which only the rewritten
copy uses.

Package now reads the placed pages for their fonts only with Copy Fonts
on, and it reads the preview and the PDF only with Relink on. With Copy
Fonts off, packaging a document that places the 60 pages of a PDF took
about 390 ms in a release build and now takes about 20 ms. The package
it writes is the same.
2026-10-23 01:01:26 +07:00
Mirko Kerezovic 97c173beb9 Handle PDF title edge cases 2026-10-23 01:01:26 +07:00
Mirko Kerezovic d548b4ff74 Fix PDF title display provenance 2026-10-23 01:01:25 +07:00
BFlat 524ecee6ca Merge pull request #3561 from lehni/feature/emoji-bitmaps
Type: colour emoji fonts with bitmap glyphs draw and export
2026-10-23 00:59:15 +07:00
BFlat 7fd4b33dc5 Merge pull request #3670 from dotCooCoo/fix/batch-revert-keeps-the-view-artboard
Artboards: an action or Batch… that reverts or switches documents keeps the active artboard
2026-10-23 00:46:58 +07:00
bflatastic 26b9cf84ad Type: colour emoji fonts with bitmap glyphs draw and export
Squashed from #3561 onto main.
2026-10-23 00:44:31 +07:00
BFlat 9b2173611f Merge pull request #2975 from nrq6993-a11y/stream/vc-feat3-g12-low-res-proxy-eps
G12: honor Use Low Resolution Proxy for Linked EPS (#394)
2026-10-23 00:37:02 +07:00
BFlat b50fbc08cf Merge pull request #3668 from Taha-Mahmoodi/fix/dither-followups
Gradients: dithered fills sample only what shows; review follow-ups to #3637
2026-10-23 00:23:42 +07:00
dotCooCoo 44c306f142 Artboards: an action or Batch… that reverts or switches documents keeps the active artboard
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.
2026-10-23 00:20:54 +07:00
bflatastic 30360a67a0 G12: honor Use Low Resolution Proxy for Linked EPS (#394)
Squashed from #2975 onto main.
2026-10-23 00:14:52 +07:00
Taha-Mahmoodi 7c9edfaf7f Gradients: an outlined stroke leaves dither behind, and paint.setStroke refuses it
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.
2026-10-23 00:08:53 +07:00
dotCooCoo fffc8418d9 Shape Builder: Pick Color From Artwork takes the look where the drag starts, else where it ends
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.
2026-10-22 23:56:01 +07:00
Taha-Mahmoodi cc1a660a6d Gradients: Dither in the Gradient panel draws a gradient fill with fine noise that hides banding
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.
2026-10-22 22:38:42 +07:00
Taha-Mahmoodi 4bb6f32911 Gradients: built with Gradient::new, so a new option touches one place 2026-10-22 22:38:41 +07:00
dotCooCoo c4ac0c5df5 Package: copies the fonts of the type on a linked PDF or .ai page
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.
2026-10-22 21:52:24 +07:00
dotCooCoo 781e62b9cc Place: Link keeps a placed PDF or .ai page linked, as it does images
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.
2026-10-22 21:52:23 +07:00
dotCooCoo 3a1d407f1a Place: a placed PDF or .ai can stay linked to its file (preference, off by default)
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.
2026-10-22 21:52:23 +07:00
bflatastic 1b7cfc8bee Automation: --automation-read-root and --automation-write-root may each be given more than once (#3546)
Squashed from #3631 onto main.
2026-10-22 21:36:15 +07:00
Taha-Mahmoodi bd6d076484 Effects: Sketch › Conté Crayon draws the dark areas in a black crayon and the light areas in a white one on textured paper 2026-10-22 21:18:34 +07:00
BFlat b0be9edabb Merge pull request #3613 from Taha-Mahmoodi/fix/3544-svg-title-artboard
Fix #3544: an SVG of one artboard on its own has the artboard's name as its title
2026-10-22 19:19:23 +07:00
Taha-Mahmoodi 439d32fcf8 Fix #3544: an SVG of one artboard on its own has the artboard's name as its title
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.
2026-10-22 18:56:34 +07:00
dwetscherandClaude Opus 5.5 2d7004f972 AF.5: cap dash patterns at 64 lengths
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>
2026-10-22 18:44:39 +07:00
dwetscherandClaude Opus 5.5 b3ccdf01a7 AF.5: nested overlays innermost first; embedded master pages; text outline budget
- 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>
2026-10-22 18:24:57 +07:00