Commit Graph
8774 Commits
Author SHA1 Message Date
Dylan M. Taylor f204445119 Repeat: the instance budget counts opacity-mask art and the nodes each copy holds
A Repeat kept up to 250 000 instances whatever its source held, and the
nested-instance count skipped the art of opacity masks. Expanding copied
the whole source for every kept instance, and a Repeat inside an enabled
mask was evaluated again for each outer copy.

The budget now counts the Repeats inside enabled opacity masks, and keeps
a second bound, MAX_REPEAT_COPIES, on the nodes and path anchors copied
in all (nested Repeats included).
2026-11-02 20:33:27 -05:00
Dylan M. Taylor 653f178b28 Affinity import: count path segments and masks in master page copies
Master page copies were charged one unit per shape whatever its number
of segments, and nothing for attached vector or pixel masks, so a master
holding one huge curve or mask could be rebuilt on thousands of pages
within the copy allowance. Copy costs now count every subpath and
segment of a shape's path and its vector mask, and a pixel mask's
pixels.

A node's vector mask also skipped the document range check the node's
own art passes; its control points are now checked as mapped into the
document (non-finite ones included) and an object whose mask reaches
beyond the range is left out, as one whose art does.
2026-11-02 20:33:10 -05:00
dotCooCoo eaab849f42 Document Info: count objects that share an id as separate objects
The walk that skips objects it has already visited keys them by address
instead of id, so two objects that share an id in a damaged file are
both counted. An object reached through a selected group and again on
its own still counts once.
2026-11-02 17:01:29 -08:00
dotCooCoo 33f429972b Merge main (11862f61) into feat/native-save-precision 2026-11-02 16:29:45 -08:00
Brandon ThomasandClaude Opus 5.5 11862f6119 Release 0.21.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.21.0
2026-11-02 19:27:35 -05:00
NetterSilverandClaude Haiku 5.5 6ecf286f6b Drop invalid XML characters from SVG link URLs and aria-labels
Found while testing crates/svg/src/export/nodes.rs:492 (Writer::node, `xlink:href` built with xml_escape)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 21:42:27 +00:00
dotCooCoo e2b6903a3a Symbols: test that a guide dragged on the canvas changes how a scaled instance draws
A 200 x 150 instance of a 9-slice symbol draws its 10 pt corner square about 17.14 pt wide once the left guide is dragged from 25% to 5% in symbol editing mode, as the square then reaches into the stretched center band.
2026-11-02 13:41:39 -08:00
dotCooCoo b63517df7f Symbols: a dragged 9-slice guide keeps its place when saved guides are closer than the minimum band
A guide whose saved position leaves a center band narrower than the minimum no
longer jumps to the art's edge when a drag does not move it closer to the
opposite guide. The 9-slice scaling line in docs/target-app-parity.md no longer
says guides are not drawn in symbol editing mode.
2026-11-02 13:16:49 -08:00
dotCooCoo 537511ae65 Symbols: a dragged 9-slice guide moves with Real-time Drawing off
With Performance > Real-time Drawing and Editing off, tool previews are
applied only on release. A ruler guide being dragged was already exempt;
a symbol's 9-slice guide, a symbol.options preview that sets only
nineSliceGuides, now is too, so the dotted guide follows the pointer
while it is dragged. The release still leaves one Symbol Options step.
2026-11-02 13:03:45 -08:00
dotCooCoo 931e94cdea Symbols: 9-slice guides are shown and dragged on the canvas while editing a symbol
While a symbol with Enable Guides for 9-Slice Scaling on is edited in
symbol editing mode, the canvas draws its two horizontal and two vertical
guides as dotted lines across its art, with any tool and with ruler guides
hidden. With the Selection tool the pointer near a guide shows the Move
cursor, and dragging the guide sets it through `symbol.options`'s
`nineSliceGuides` as one undo step; Escape during the drag puts it back.

A guide within 4 screen pixels takes the press before the art and the
bounding box. A dragged guide stays within the art's box and stops short
of the opposite guide; positions outside the art are not supported yet.
2026-11-02 12:38:33 -08:00
dotCooCoo 2d0fa32d30 Docs: the projection checks compare absolute values
The horizon test compares a point's signed w with the largest absolute
value in the bottom row, and the invertibility test compares the
absolute value of the determinant with the largest absolute value among
its six products, so a negative determinant counts as invertible. A
stored projection that overflows is divided by the largest absolute
value in its bottom row.
2026-11-02 08:53:47 -08:00
dotCooCoo 5f20bbc3e8 Document Info: test that a Repeat object in a selected group is listed once
With Selection Only, a selection that holds a group and the Repeat object
inside it, in either order, lists that Repeat object once.
2026-11-02 08:25:02 -08:00
dotCooCoo b5885e35fa Geometry: a projective map at a small scale maps a far point without overflowing
The power of two that brings a small map's entries up before a point is mapped is now reduced by the power of two of the point's larger coordinate, so no product of an entry and a coordinate reaches 1. A map that mapped a point before the scaling step does so again, and affine_at uses the same entries.
2026-11-02 08:24:29 -08:00
dotCooCoo 0f48ba21e9 Geometry: a projective map at a tiny scale maps points and derivatives as at unit scale
apply and affine_at multiply every entry by the power of two that brings the
largest to at least 1/2 when all entries are below 1/2, so products that would
be subnormal keep their precision. The identity times 5e-324 mapped (0.5, 0.5)
to (0, 0) before. Maps with an entry of 1/2 or more are unchanged.
2026-11-02 08:15:21 -08:00
dotCooCoo ce9002008b Docs: file-format-parity keeps one .ai editing-data paragraph
The editing-data paragraph appeared twelve times, each copy holding one
feature's change. The copies are merged into one paragraph.
2026-11-02 08:10:43 -08:00
dotCooCoo 4494d40c33 Geometry: the inverse of an invertible projective map is kept however small its relative determinant
adjugate_inverse no longer applies the relative determinant test to its result. That test is not symmetric between a map and its inverse: a map whose determinant is 1e-8 of its largest product can have a finite, accurate inverse whose own ratio is near 1e-16, so inverse() was None while is_invertible() was true. The result is now kept when every entry is finite and its determinant, summed from the six products in split form, is not 0.
2026-11-02 07:55:19 -08:00
dotCooCoo d1fb8e540c Geometry: a projective map with entries far apart in size keeps its inverse
The inverse holds every cofactor and the determinant as a mantissa and a
power of two, and forms each entry from their quotient. A map such as
diag(1e-300, 1e300, 1e-300), which counts as invertible, now gets its
inverse diag(1e300, 1e-300, 1e300) instead of none.
2026-11-02 07:45:10 -08:00
dotCooCoo b22912b25a Geometry: the inverse of a projective map with a tiny determinant keeps its w row
When a cofactor of a map with a normal but very small determinant underflows to 0, the
inverse formed from the map itself can have a w row of 0, so every point mapped through it
is refused. The inverse is now kept only when it is itself invertible, and is otherwise
formed from the map divided by a power of two that brings its determinant's products near 1.
2026-11-02 07:37:37 -08:00
dotCooCoo bffb4e6e87 Geometry: a projective map with entries of very different sizes stays invertible
The scale-free determinant test no longer builds a rescaled copy of the map. It
forms each of the six determinant products as a mantissa product and an
exponent sum and compares their sum with the largest of them, so a map such as
diag(1e307, 1e-157, 1e-157), whose rescaled copy overflows, is still invertible
and loads from a document. The rescaled copy is built only to invert a map whose
determinant is not finite or is below the normal range.
2026-11-02 07:21:32 -08:00
dotCooCoo e365c09141 Geometry: a projective map's horizon and invertibility checks do not depend on its scale
A homography means the same map at any nonzero uniform scale of its
entries, but `apply`, `is_invertible` and `inverse` compared w and the
determinant with absolute thresholds. A map stored with small entries
refused every point and could not be inverted, and a map with large
entries drew points past its horizon.

- `apply` refuses a point when w is at most 1e-9 times the largest
  magnitude in the w row.
- A map counts as invertible when its determinant is more than 1e-15
  times the largest of the six products the determinant sums, taken on
  a copy rescaled by a power of two. An inverse whose determinant
  overflows or underflows is taken on that copy.
2026-11-02 07:10:04 -08:00
dotCooCoo 588ff039a9 Type: Tate-chu-yoko offsets in character style definitions stay in range
A character style whose Up/Down or Left/Right offset lies past MAX_COORD
either way now opens with the nearer end, as text runs already do, so
text that takes the style no longer gets the out-of-range offset. An
offset within range is not rewritten.
2026-11-02 06:32:52 -08:00
dotCooCoo 8b249f8275 Document Info: Objects counts each object once
An object with a gradient or a pattern on both its fill and its stroke
counted twice in Objects. It now counts once for each paint kind it uses.

With Selection Only, a selection that holds a group and an object inside
it walked that object twice, so the object counts, the paths' total
length and Repeat Objects counted it twice. The walk now visits each
object once.
2026-11-02 05:06:38 -08:00
dotCooCoo 52d180910d PDF export: spot inks named All or None print as spot inks
In a Separation color space, the colorant name All marks every plate and
None marks no plate (ISO 32000-1:2008, 8.6.6.4). A spot swatch with one of
those names was written under its own name, so its paint printed on every
plate or on none.

PDF export now writes such an ink as All Spot or None Spot, numbered
All Spot 2, All Spot 3 and on when a spot swatch already has that name,
the same rule EPS export uses. The new name is used for fills and strokes,
tint shadings, DeviceN gradients, colorized images and the Lab alternates.
Registration still writes All.
2026-11-02 05:04:46 -08:00
dotCooCoo 750c34c247 Recolor Artwork: the Edit tab shows the colors on a smooth wheel, a segmented wheel or as color bars
Buttons beside the Edit tab's wheel switch between the smooth color wheel,
a segmented wheel of solid patches (24 hues by 5 saturations, its markers
moving as freely as on the smooth wheel) and color bars: one solid bar per
recolored color in row order, where a click selects a bar's row for the
color sliders and a drag moves the row without changing a color. The
dialog opens on the smooth wheel and keeps the choice while it is open;
the dialog field is `display` (`smooth`, `segmented`, `bars`).
2026-11-02 04:35:56 -08:00
dotCooCoo 5ba035aba4 Type: an inline graphic takes its character's manual kerning
A manual kerning value on an inline graphic's character was stored and
shown but did not move the text after it. The graphic's advance now
includes the value, as a letter's does, and in right-to-left horizontal
type its art moves right by the value, so the space goes between it and
the next character in reading order. Auto and Optical kerning still apply
no font pairs to an inline graphic.
2026-11-02 03:45:45 -08:00
dotCooCoo fa8addaa12 Select: Same › Text Fill & Stroke Color selects type whose fill and stroke both match
Select › Same › Text Fill & Stroke Color (select.same.textFillAndStrokeColor) selects the type
objects with a run of characters whose fill and stroke both match the first character of the
first selected type. Type that matches only the fill or only the stroke, or has the fill in one
run and the stroke in another, is left out. The entry follows Text Stroke Color in the menu and
has a row in every catalog.
2026-11-02 03:37:17 -08:00
Rahul Ranjanandrahul05ranjan 3976989cb7 i18n(sv): translate the 22 strings the report listed as missing
The Swedish catalog lacked 22 interface strings: the workspace names
(Essentials, Painting, Typography, Web, Touch), the 1P/2P/3P view
preset names and the Hindi digit-script name.

Adds them with the vocabulary the catalog already uses (Utskrift,
Teckenstorlek). After this the report lists nothing missing for sv.

Co-authored-by: rahul05ranjan <11793455+rahul05ranjan@users.noreply.github.com>
2026-11-02 11:27:09 +00:00
dotCooCoo 442ae20e14 SVG: made-up ids skip every id the file names
An id the importer writes into an element no longer matches a style sheet's
#id selector or a <use> link that no element matched before. The ids are
read from the decoded attribute values and text of the file, once.
2026-11-02 03:22:10 -08:00
Rahul Ranjanandrahul05ranjan 2a51404e74 i18n(ru): translate the 130 strings the report listed as missing
The Russian catalog covered 6,126 of the 6,256 interface strings: the
workspace names (Essentials, Painting, Typography…), their Reset
entries, the artboard and dimension annotations, the PDF and export
preset names, the library panel sentences and several control-channel
messages still fell back to English.

Adds the 130 missing entries in the catalog's TSV format with the
Illustrator-style vocabulary the catalog already uses (монтажная
область, выносные линии, плашечные цвета, трассировка). After this the
report lists nothing missing for ru.

Co-authored-by: rahul05ranjan <11793455+rahul05ranjan@users.noreply.github.com>
2026-11-02 11:17:49 +00:00
dotCooCoo 7c7a127ec8 SVG: a thin ellipse far from the origin imports with standard handles
An ellipse whose radius is smaller than the point tolerance at its coordinates, but at least two
f32 steps, now imports as the four-anchor ellipse instead of usvg's curves with rounded handles.
2026-11-02 03:09:08 -08:00
dotCooCoo b4d3ae41bf SVG: made-up ids skip the file's taken ids once
Each id made up on import starts where the last one stopped instead of at
the number of ids made so far, so a file that already holds many of the
made-up ids does not make every new id try them all again. The ids made
up are the same.
2026-11-02 03:04:04 -08:00
dotCooCoo 85b6c2a5eb SVG: ids made up on import never repeat an id in the file
The import gives an element an id when it needs to find it again in
usvg's tree (links, symbol uses, events, data attributes, and now
circles and ellipses). A made-up id could repeat one the file already
had, such as vectorcraft-id-0, so a <use> or paint reference to that
element showed the other one. Made-up ids now skip every id in the file.

Circles and ellipses in a file whose style sheet has an [id selector get
no made-up id and keep usvg's curves, so the selector matches the same
elements as before.
2026-11-02 02:59:51 -08:00
dotCooCoo 820a750310 SVG: a small circle far from the origin imports as a four-anchor ellipse
The tolerance that compares the points usvg makes of a <circle> or
<ellipse> grew with the center's distance from the origin, so a circle
of radius 400 at x 40,000,000 fell back to eight anchors. The tolerance
is now a few f32 steps at the size of the coordinates, and the on-ellipse
check allows the same steps relative to the smaller radius.
2026-11-02 02:53:34 -08:00
dotCooCoo 60e1600caa SVG: circles and ellipses import as four-anchor ellipses
usvg turns each <circle> and <ellipse> into arcs and then into a number
of cubics that depends on f32 rounding: an ellipse that export wrote as
<ellipse> could come back with eight anchors instead of the four it was
written from. The renderer flattens the two outlines differently, so a
round trip of a flat filled ellipse redrew 158 edge pixels by up to 43
levels, and prop_svg::plain_art_survives_visually failed on main.

Import now records the ids of <circle> and <ellipse> elements in the
prepass and rebuilds each as the ellipse VectorCraft draws: four smooth
anchors on the axes, starting at the right as SVG defines, with handles
KAPPA of the radii long, then transformed as before.

Tests: prop_svg::filled_ellipse_on_the_artboard_edge_survives_visually
(the failing case) and basic_shapes::circles_and_ellipses_import_as_the_ellipse_drawn
fail without the change.
2026-11-02 02:49:36 -08:00
dotCooCoo c5bfa08663 Merge branch 'main' into feat/global-edit-shift-exclude 2026-11-02 02:17:18 -08:00
dotCooCoo a5c0153596 Global Edit: a Shift-drag from a highlighted match snaps from its first move
The snap targets of a Shift-drag that starts on a highlighted Global Edit
match are gathered when the drag begins, for the selection with the match
added, before the first preview moves it. The first preview is now snapped
to the grid or to points, and later previews snap from the original
position.
2026-11-02 02:15:14 -08:00
BFlat 4ee0ef231a Merge pull request #5139 from dotCooCoo/fix/ai-os10-distort
.ai import: type keeps its Distort & Transform effects live
2026-11-02 16:54:41 +07:00
BFlat 04f5e6455d Merge pull request #5156 from dotCooCoo/fix/ai-os18-d
.ai import: open the variable library and data sets from the editing data
2026-11-02 16:54:26 +07:00
BFlat 942a29d9cb Merge pull request #5231 from dotCooCoo/feat/pixar-pxr-open
File formats: Pixar (.pxr) images open and place
2026-11-02 16:54:08 +07:00
BFlat d439a9f6e2 Merge pull request #5232 from dotCooCoo/fix/envelope-appearance-transform-options
Envelope: Distort Appearance keeps a Transform effect's Scale Strokes & Effects and Transform Patterns
2026-11-02 16:53:52 +07:00
BFlat f56b8ae578 Merge pull request #5230 from Hassan1305/fix-5159-cmdv-image
Fix #5159: ⌘V from the macOS menu bar pastes a picture another app copied
2026-11-02 16:53:34 +07:00
dotCooCoo 023b5eedec Docs: a Shift-drag from a Global Edit match selects and moves it 2026-11-02 01:51:38 -08:00
dotCooCoo c88d88548c Global Edit: a Shift-drag from a highlighted match selects it and moves it
A Shift-press on a highlighted object of the Global Edit waits for the release
to leave it out of the edit. A drag from it now selects it when the drag starts
and moves it with the selection, as a Shift-drag from an unselected object did.
The snap targets are gathered after the object joins the selection.
2026-11-02 01:41:48 -08:00
dotCooCoo 1cd5bc4016 Global Edit: a Shift-click on a match inside a selected group deselects the group
A match that sits inside a selected object is no longer highlighted, and a
Shift-click on it with the Selection tool deselects that object as before
instead of leaving the match out of the Global Edit.
2026-11-02 01:28:57 -08:00
dotCooCoo b664216a2a .ai import: type keeps its Distort & Transform effects live
Squashed from #5139 onto main.
2026-11-02 16:19:59 +07:00
dotCooCoo 8ab68e4223 .ai import: open the variable library and data sets from the editing data
Squashed from #5156 onto main.
2026-11-02 16:19:19 +07:00
dotCooCoo 952446253f File formats: Pixar (.pxr) images open and place
Squashed from #5231 onto main.
2026-11-02 16:18:48 +07:00
dotCooCoo 9aac3ca888 Envelope: Distort Appearance keeps a Transform effect's Scale Strokes & Effects and Transform Patterns
With Envelope Options > Distort Appearance on, a path whose Transform
effect has Scale Strokes & Effects or Transform Patterns on, or copies it
with a gradient, was baked with the effect as geometry only: every copy
kept the original's stroke weight, dashes and pattern placement before
the envelope bent it. bake_appearance now makes such a path into the
per-copy art that drawing, export and Expand Appearance already use, and
bakes each copy's strokes into filled outlines from there.
2026-11-02 16:18:15 +07:00
HassanKhanandClaude Opus 5.5 b6f90d42fb Fix #5159: ⌘V from the macOS menu bar pastes a picture another app copied
The menu bar takes ⌘V's key-down as Edit › Paste's key equivalent, and
`native_menu::key_events` turns it back into egui input. With text on the
clipboard that was a Paste event; with only a picture (a screenshot) it
was nothing, leaving the paste to the window's own key-up. egui's window
integration only releases a key whose press it saw, so that release never
came and ⌘V pasted nothing, while Edit › Paste and ⌘V with the menu open
worked.

`key_events` now ends ⌘V with the key's release, as it already did for
⌘X and ⌘C: without a Paste event that release is the picture paste
`PasteChord` looks for, and after one it ends that paste so the next
picture paste still fires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 16:18:13 +07:00
dotCooCoo 205e5b3e8b Global Edit: a Shift-click on a highlighted match leaves it out of the edit
While Global Edit is on, a Shift-click (not a Shift-drag) with the
Selection tool on a highlighted match, or on an object inside a matched
group, leaves that match out: it is no longer highlighted and later
edits leave it as it is. The object the edit started from stays in, and
Global Edit ends when fewer than two matches are left. Leaving a match
out is not an undo step.

- New command `select.globalEditExclude {id}` returns `{active, count,
  ids}`; the Selection tool calls it on the release of a Shift-click.
- `ToolContext::global_edit` gives the tools the Global Edit's objects.
- Label and messages in the i18n catalogs; docs/mcp.md,
  docs/ui-parity.md and docs/target-app-parity.md updated.

Source: https://helpx.adobe.com/ph_fil/illustrator/how-to/global-editing-sensei.html
2026-11-02 01:06:40 -08:00