Commit Graph
263 Commits
Author SHA1 Message Date
dotCooCoo 7e9d0be723 Fields: unit fields take % in arithmetic, as in 3cm*50% and 50pt+25%
Unit fields and length strings read a percentage operand as a share of
a value. After * or /, the percentage scales the term before it:
3cm*50% is 1.5 cm and 3cm/50% is 6 cm. After + or -, it adds or takes
away that share of everything before it: 50pt+25% is 62.5 pt and
100pt+20pt-50% is 60 pt. Before this change these expressions read as
nothing, and the field kept its old value.

The parser now splits a sum at its last + or -, so a percentage applies
to the whole sum before it. Text without % gives the same values as
before, apart from float rounding. A percentage on its own (50%) still
reads as nothing, and percent and degree fields still read % as their
suffix.

Command length strings use the same parser: file.new's width and
height, the distance params and the length preferences.

Docs: docs/mcp.md, docs/ui-parity.md, docs/target-app-parity.md.
2026-10-16 06:16:35 +07:00
Fabio William ConceiçãoandCursor dc390aba37 G6: graph variables (CSV data merge)
VariableKind::Graph binds a graph root; dataset rows supply CSV as graph.setData.
Make Graph Dynamic, Variables dialog, capture/apply, and pt-br for the new strings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 04:50:07 +07:00
Fabio William Conceição 41c8bd2a2d G6: image variables swap linked files and keep bounds
Add VariableKind::Image with path values on datasets, makeImageDynamic,
panel/dialog UI, and MCP docs so data merge can drive placed images.
2026-10-16 04:45:06 +07:00
Fabio William ConceiçãoandCursor 80090e7623 G5: Symbol Options Registration 9-point grid
Store the registration point with the symbol and place instances so that point lands on (x, y). Symbol Options exposes the same 9-point control as Transform.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 04:45:03 +07:00
Gert Pellin 3ae6cf28e4 G13: Intertwine tool, Object › Intertwine › Edit, menu and translations (#1763)
Make and Edit choose the Intertwine tool: hovering highlights the overlap
area under the pointer, a click swaps which object is on top there, and a
path drawn around overlaps (Shift: a rectangle) arranges every overlap
inside it; Esc or Enter goes back to the tool chosen before. The overlap
faces move to vectorcraft-tools, shared by the tool and the command.

Clicks inside a raised area pick the member drawn on top there, the
Object menu gets the Intertwine submenu, and the new labels and the
predicate message are translated in the complete catalogs. docs/gaps.md
records what landed under G13.
2026-10-16 04:44:53 +07:00
Gert Pellin 74c391c7ba G13: Object › Intertwine: Make, Release and arranging overlap areas (#1763)
An Intertwine group keeps its objects as they are, in their own stacking
order; each member can be drawn over the others in an area of its own.
The group draws, hits, saves and exports through its live expansion, so the
members stay editable and Release gives them back unchanged.

object.intertwine.arrange picks an overlap area by a click point or by a
closed path drawn around overlaps and toggles which member is on top
there, or brings a member to the front or sends it to the back where three
or more objects overlap.
2026-10-16 04:44:52 +07:00
dotCooCoo 3bef84aaa6 Type: type drawn as glyph outlines keeps its underline, stroke weight, gradients and patterns
Type is drawn as its glyph outlines under a geometry effect, in EMF and
WMF export, in DXF export when it is written as outlines, through
Flatten Transparency with Convert All Text to Outlines, and inside an
envelope. Those outlines differed from the type the canvas draws in
four ways. They had no underline or strikethrough bars. A character
stroke on scaled type kept its unscaled weight. An unplaced gradient was
fitted to the glyphs it painted instead of the layout box. A run's
pattern tiles stayed in document space instead of scaling and turning
with the type. A group's or layer's own fills and strokes also painted
the glyphs of the type in it without the bars.

The outlines now include each run's underline and strikethrough bars,
as the canvas has drawn them since #850. Each run's path is made in
text space with its unplaced gradients fixed to the layout box, then
placed by the type's transform, which also scales its stroke weight
and dashes and transforms its pattern tiles. The type's own fills and
strokes have their unplaced gradients fixed to the bounding box of the
layout box in the document, where the canvas fits them.

Geometry effects that move or scale the outlines move and scale the
type's gradients with them: the type's own effects, also inside an
envelope and after Flatten Transparency has converted the type to
outlines, and the effects of a group, symbol instance or envelope that
holds the type. outline_text marks its group with a new node field,
type_outlines, which is saved with the document. reshape_leaves maps
the placed gradients in each marked group through the map that takes
the group's bounds before the effects onto its bounds after them. A
scale out of proportion stretches a radial gradient into an ellipse
and turns the isolines of a diagonal linear gradient, as it does on
type scaled by its own transform. Placed gradients outside those
groups stay where they are.
2026-10-16 02:26:15 +07:00
dotCooCoo d069f0fe98 Variables: selecting a data set sets the objects inside a group that it shows
dataset.select applied a row's values in the order the variables were
defined, and it skips an object inside a hidden group. When a row
showed a group and set an object inside it, and the object's variable
was defined first, the object was skipped while the group was still
hidden: one selection applied the row in part, and selecting the row
again applied the rest. A text variable on type inside the group was
skipped the same way, and a row that hid a group skipped the objects
inside it whose variables were defined after the group's.

dataset.select, dataset.next and dataset.prev now apply the visibility
values that show objects first, outermost object first, then the other
values in definition order, then the visibility values that hide
objects, innermost object first. Of two variables of the same kind on
one object, the one defined last still wins.

Docs: docs/mcp.md.
2026-10-16 02:26:11 +07:00
dotCooCoo 00b02c1bdb Variables: Update Data Set writes only the variables the active data set holds
A data set can hold values for only some variables: `dataset.new` and
`dataset.set` store only the values they are given, and selecting the
data set leaves the other variables alone. Update Data Set replaced the
active data set's values with the values the art reports for every
variable. After one Update, a data set that set only a visibility
variable also held the current text of the bound type, and selecting it
again wrote that text back. Update also dropped a variable's value when
its objects were locked or inside a hidden or locked layer or group,
because such objects report no value.

`dataset.update` now writes the art's current value for each variable
the active data set holds, and keeps the data set's value when the art
reports none. A variable that the data set does not hold stays out of
it, including one defined after the data set was captured;
`dataset.update {all: true}` also adds the other variables that the art
reports a value for. The reply adds `left`, the variables it did not
write. The Variables panel's Update Data Set button and menu item run
it without `all`.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 02:26:08 +07:00
dotCooCoo 0c3a5929c7 Type: area type opens and lays out with at most 100 rows and 100 columns
In a dev build, opening a native file whose area type has 2^60 rows
or 2^60 columns failed with an internal error ("capacity overflow"),
and opening one with 2^32 rows and 2^32 columns failed with "attempt
to multiply with overflow". Text layout reserved room for rows times
columns frame cells. `text.areaOptions` keeps both counts within 1 to
100, but reading a document kept the counts the file gave.

Reading a document now clamps each count to 1 to 100
(`AreaOptions::MAX_ROWS_COLUMNS`): a larger count reads as 100, and 0
reads as 1, as layout already treated it. Text layout clamps both
counts the same way. Area type with 1 to 100 rows and columns lays out
as before.

The cells of a frame now share one copy of the frame's flattened
polygons and one copy of the flattened text wrap shapes. Each cell held
its own copy: in a dev build, a test process that laid out text in 100
rows and 100 columns of a circle frame of radius 1,000,000 pt peaked at
1.33 GB of memory, and now peaks at 100 MB.
2026-10-16 02:21:47 +07:00
NetterSilverandClaude Haiku 5.5 18c1bb0d55 Apply the sign of a picas+points value to the whole value
Found while testing crates/doc/src/lib.rs:~298 (parse_measure, picas+points branch)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-16 02:21:27 +07:00
NetterSilverandClaude Haiku 5.5 1524ceae09 Evaluate * and / left to right in field arithmetic
Found while testing crates/doc/src/lib.rs:269-297 (parse_measure, used by Unit::parse and parse_number)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 02:21:27 +07:00
NetterSilverandClaude Haiku 5.5 a59621fa78 Accept a negative operand after an operator in field arithmetic
Found while testing crates/doc/src/lib.rs:272

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 02:21:27 +07:00
bflatastic 7217dcd121 Dimensions: a file whose dimensions carry options this version doesn't know still opens; commands still refuse unknown options 2026-10-16 01:36:24 +07:00
Osama Amin 8aea665c4e Dimension tool: the documented gestures, arcs, picas and text positions; Properties section; review fixes
Drag out from a segment, corner, circle or arc to place a dimension; plain clicks pick two points or two lines. Radial dimensions on perfect arcs, picas, Away/Between and Left/Center/Right text placement. The Properties panel edits a selected dimension, with Apply to All (dimension.applyToAll) and Set as Default. Dimension Selected Paths, the Arc/R/Ø prefixes and the custom defaults are left out. The refresh returns at once in documents without dimensions (Document::dimensions); a linked dimension moved on its own keeps its place; modes, units, line types, arrows and text placement are enums; the tool has its own icon.
2026-10-16 01:36:24 +07:00
Osama Amin 9d813e906a Fix #1372: Dimension tool and Object > Dimensions: linked length, angle, radius and diameter annotations 2026-10-16 01:36:24 +07:00
bflatastic 6bc0222074 #1723 follow-up: pattern tiles are drawn again after a brush edit or new Color Settings
A tile was kept while the pattern's art and what it shows were unchanged,
but a brush the art is stroked with lives in the document's other data, and
every tile is drawn through the color settings. A tile whose art reads more
than itself now also remembers the document's other data, and every tile
remembers the color settings it was drawn through (both weakly).
2026-10-14 12:27:04 +07:00
bflatastic 0efcaeb558 Importers: a brush that draws itself by name no longer crashes
A scatter brush whose art is an instance of a symbol stroked with that brush, or any brush whose
art is painted with a pattern whose tile is stroked with it, drew itself without end: a stack
overflow in a hand-written native file. Brush pieces already strip the brushes of their own
strokes; now what they draw by name that would draw a brushed stroke (a symbol instance, or a
pattern paint, also through the symbols and patterns those use) is left out too, in one place
(`stroke_pieces`, which every renderer and exporter calls with the document).

Probed the rest of item 2 of the depth follow-up with hand-written hostile inputs, kept as tests:
graph designs of deep art and of a symbol holding itself, Affinity groups nested to and past
both of its reader's caps, DXF blocks inserting themselves, EMF/WMF saving states without end,
and plug-in manifests and output nested 30,000 to 100,000 deep. None of them overflowed.
2026-10-14 10:13:45 +07:00
bflatastic 0cb55bc0aa Fix #1717: sampling and recolouring check an image's RGBA size before decoding
The decoder's 512 MB limit is on the file's samples; a grey image within it
is four times that once converted to RGBA for the Eyedropper or a recolour,
which the web build can't allocate. The size is now read from the header and
held to the allowance the importers and the renderer use.
2026-10-14 08:47:10 +07:00
bflatastic 7aa00b41da Fix #1718: ids at their largest value in a file no longer exhaust the counters
A native file (or a pasted document) whose node, slice or asset ids or whose
next-id counter sit at u64::MAX made the next allocation overflow: a panic
with overflow checks, a wrapped counter handing out ids already in use
without. fix_next_id now leaves ids and counters from 2^62 up out of the
count, so new ids start under them; alloc_id saturates. Artboard and graphic
style ids take the smallest free id when the largest is u32::MAX instead of
wrapping to the reserved 0.
2026-10-14 08:47:10 +07:00
Gert Pellin 904414aa7d Global swatch edits reach opacity-mask art (#1735)
A mask's art lives outside the layer tree (in the masked object's extras),
and the walks over linked colours skipped it: editing a global swatch
recoloured everything but the mask, which kept showing (or hiding) as
before, and a swatch used only in a mask counted as unused. The colour
walks now go into opacity masks: map_solid_paints (swatch edits, renames,
deletes, Merge and the like) and visit_paints (unused swatches).
2026-10-14 08:01:50 +07:00
bflatastic c10da25ce4 Importers: nesting is capped, so a deeply nested file can't overflow the stack 2026-10-14 07:37:52 +07:00
bflatastic bc50c7fa56 Identify Objects By: rustfmt 2026-10-14 05:13:36 +07:00
Fabio William Conceição 958c1dffe8 Part of #394: Identify Objects By shows and edits Layers names as XML IDs 2026-10-14 05:13:15 +07:00
dotCooCoo d9bd9b134d Symbols: instances of imported symbols are measured by their art
Every symbol instance was measured as its transform applied to the
20 × 20 square around the origin, which symbols made in the app keep
their art in. The SVG and DXF importers keep symbol art as drawn, so
the box of an instance of an imported symbol did not match its art.
The SVG Star instance at x 20, y 30 draws from (20, 30) to (40, 50)
and was measured from (10, 20) to (30, 40). Align moved such an
instance by the wrong amount, clicks and marquees beside its art
selected it, and the canvas, exports and prints left it out when its
art reached the view or the page and the square did not.

An instance is now measured by its symbol's art, which
Document::symbol_boxes looks up in the document each time
(Node::geometric_bounds_with, visual_bounds_with and
reach_bounds_with). With Use Preview Bounds on, the strokes in the art
count at their own weight, as the canvas draws them, and
object.setBounds counts them at that weight when it sizes an instance
or an object that holds one. When strokes do not scale and a width or
height of 0 or more is smaller than what a path's stroke adds,
object.setBounds now gives the path no width or height, as it does
for an instance whose art has strokes; before, it mirrored the path.
With Use Preview Bounds on, object.setBounds now measures the margins
that strokes add object by object, the members of groups and the
objects in symbol instances' art included, and solves for the width,
height and reference point asked for when the width and height are 0
or more. Before, it measured the margins of the whole selection and
could miss the size or the reference point asked for when strokes did
not scale and the objects' strokes added different margins, and when
strokes scaled and the width and height were out of proportion, even
for one path. A clip group, a Shaper Group, a compound path, type, and
a group or instance with effects or an opacity mask of its own are each
measured as one object, and so is an instance in perspective or one
turned or slanted relative to the bounding box, other than by quarter
turns. When the selection holds more than 4,096 objects to measure this
way, each selected object is measured as one. Instances inside symbol
art are measured by their own symbol's art. An instance is measured as
the square when its symbol is missing, when its symbol or a symbol
nested in its art holds an instance of itself, and when symbols nest 32
deep in its art.

Align and Distribute, the Transform panel, the bounding box and the
selection outline, clicks, the marquee, document.inspect and
document.node, Fit to Artwork Bounds and Fit to Selected Art, canvas
culling, thumbnails, the SVG filter region of an instance with a
raster effect, raster effect images, Export Selection, the SVG of an
artboard, printing, and the PDF, EPS, EMF/WMF and DXF writers use the
new box. The renderer also culls an instance by the effects and
brushed strokes in its art, and it culls a blend, envelope or repeat
by the brush art of what it holds and the effects in the art of the
instances in it. It culls an instance with geometry effects of its own
by the effects in its art after those geometry effects reshape the art.
It measures the effects in a symbol's art once for each symbol and
linear part of the transform that places it. For symbols whose art
holds instances, it measures at most 4,096 placements a frame, and at
most 4,096 more for each pattern tile it rasterizes and each placed
document it draws. Past that, it estimates the reach from the symbol's
reach with no transform, which it measures once. When the drawn
document's symbols change, the renderer drops the cached bounds of the
instances and of the objects that hold them.

The PDF and EPS writers, the SVG of an artboard and Skip Blank
Artboards grow the box of an instance whose art holds brushed strokes
by 16 times the widest stroke (at least 1 pt) of the objects in that
art that have a brushed stroke, plus 8 pt, as the renderer grows the
cull bounds of a brushed path. They keep an instance whose brush art
reaches the page. The SVG of an artboard also keeps an object that
holds an instance when what the canvas paints for it, the effects in
the symbol's art and the instance's own included, reaches the artboard.

Not yet: other commands that measure objects, such as Select › All on
Active Artboard, Transform Each and Convert to Artboards, still measure
an instance by the square.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-14 04:56:30 +07:00
bflatastic bdb9963c73 Select Behind: one test of what may be hit for both walks, and random documents checked against the repeated hit test 2026-10-13 10:52:38 +07:00
Ayanprogrammer11 507c6e0e63 Select Behind: test each object once instead of re-hitting the whole document
objects_at() found Select Behind targets by running hit_test_skipping()
in a loop, skipping everything found so far. Each round is a full tree
traversal, so a stack of n overlapping paths costs O(n^2) hit tests
before the loop finds no further object.

The fast path walks each editable top-level layer once. Every selectable
object's subtree is visited only until its first hit, and a HashSet
records found ids so subtrees already covered by an earlier hit are not
searched again. Sublayers recurse the same way, so a stack of n paths
costs one traversal total.

The original traversal stays in charge where Select Behind semantics
differ from the plain front-to-back order: isolation (scope), and Edit
Contents, which exposes objects nested inside a top-level group. Non-
layer roots in malformed files also fall back. objects_at_repeated()
keeps the old loop for those cases.

Three tests: a 256-path stack asserts front-to-back order and
completeness; a mixed document (sublayer, groups, clipping group,
outline and path-only options) asserts the one-pass result equals the
repeated-traversal result; an envelope in Edit Contents asserts the
fallback preserves the original order.
2026-10-13 10:52:38 +07:00
Gert Pellin a169693817 Warp › Arc: bend in the object's real proportions
warp_point worked on box coordinates normalised to -1..1 on both axes:
Arc's radius was in x units but its edges were offset in y units, so a
box that isn't square bent into no circular arc (skewed sides from
Bend 10 %), and at 100 % the radius (2/π) was smaller than the
half-height, folding the inner edge over. warp_point now takes the
box's aspect along the warp; Arc keeps the centre line's length on a
circular arc in real proportions, with concentric edges, and an inner
edge that would pass the centre stops there. Other styles are as they
were; a square is bent as before.

Fixes #1578
2026-10-13 10:52:37 +07:00
Marco Arnulfo ee97225c19 Symbols panel: New Symbol asks for Symbol Options before making the symbol (fixes #1560) 2026-10-13 08:53:06 +07:00
bflatastic 897d99a70f Fix #1448, #1460: Transform Objects in the Transform dialogs and Transform Each, and Reflect across an angle 2026-10-13 02:54:08 +07:00
bflatastic 81a568f4c6 Part of #1466: grayscale TIFFs with an alpha channel or extra channels can be placed
File > Place, Relink and Open refused a grayscale TIFF whose pixels carry an alpha channel or
another extra channel: the image decoder reads neither. They are now read like CMYK TIFFs with
alpha: the first extra sample is the transparency when the file says it is alpha (associated alpha
divided back out), and other channels are left out.
2026-10-13 02:48:39 +07:00
dotCooCoo ffa948dcc3 Part of #1429: Edit and Object commands on many objects take time linear in the document
Delete, Cut, Group, Make Compound Path, Make Clipping Mask, Send to
Current Layer, Ungroup and Release Compound Path removed, inserted or
moved one object at a time. duplicate_in inserted one copy at a time;
Duplicate, Copy in the Move, Rotate, Scale, Reflect and Shear dialogs,
Transform Again after a copy, Transform Each with Copy, the perspective
copies and each move of an Alt-drag call it. Each insertion or removal
moved the later objects of the same layer or group to other indices.
The next lookup by id then walked the tree, and after every fourth such
walk Document::index_path rebuilt the whole lookup cache. Each command
did work that grew with the number of objects times the size of the
document: with 25,000 rectangles selected, a release build of
vectorcraft-cli took 7.2 s to group them and 11.5 s to copy them with
object.transform.

Document::remove_many, Document::wrap and Document::insert_beside
remove nodes, wrap them in a new node, or put nodes directly above or
in place of their anchors, in one pass over the children of each
parent. Each finds the nodes with one index_paths call, checks every id
before it changes anything, and clears the lookup cache once. The
commands above use them. The objects keep their order, the copies take
their ids in the same order, and the selection, the undo step and the
journal are the same. duplicate_in still copies one root at a time when
a root is listed twice or lies inside another root. A release build
with this change groups the 25,000 rectangles in 27 ms and copies them
in 121 ms.

Docs: docs/target-app-parity.md.
2026-10-12 23:42:43 +07:00
bflatastic a41d59604b Fix #1408: the Transform tools and commands act on the anchors selected with Direct Selection 2026-10-12 21:01:15 +07:00
Ayberk A. 3073f0ab03 Isolation mode: double-clicking a single object isolates it
With Double Click To Isolate on, the Selection tool isolated groups only. A
double-click on a single path, compound path, image, mesh or placed file now
isolates it too, as the public documentation describes. Type still switches to
the Type tool; blends, envelopes, repeats and symbol instances keep their own
editing modes.

While a single object is isolated only it is hit, marqueed and taken by Select
All, new art goes beside it in its group or layer, and a double-click on the
empty canvas leaves with it selected. object.isolate accepts any object except
type.
2026-10-12 18:56:41 +07:00
bflatasticandLuca Lorenz 8b96db8f0b Artboards: art flush with an artboard moves with it despite a rounding error (#1295)
A background drawn at the artboard's own position could sit 3e-14 pt outside it and
stay behind when the artboard moved, was copied or scaled its art. Art within a
millionth of a point of the artboard's edges now counts as inside it.

The fix and both tests are from #1295 by Luca Lorenz, without its change to art that
only partly overlaps the artboard.

Co-authored-by: Luca Lorenz <lucalorenzetti1993@gmail.com>
2026-10-12 16:43:41 +07:00
Ikki Nishihara 29ecab8c48 feat: change and reset the ruler origin (#1212) 2026-10-12 15:17:24 +07:00
Omar Saidahmed 497172bc97 deselect hidden and locked objects in linear time 2026-10-12 14:50:00 +07:00
Omar Saidahmed 88e9381de6 move selection tests into their own module 2026-10-12 14:50:00 +07:00
arhammusheerandClaude Opus 5.5 023c8e3edd #1244: streaming open and save, per-section limits, recovery that keeps images apart, background open
Save serializes JSON straight into the compressor and writes images from their Arc into the
file; open reads each image into its own buffer, and NodeKind reads its tag first instead of
buffering every node. Already-compressed images are stored rather than deflated again in the
same gzip stream. Limits apply per section, so any file VectorCraft writes opens again. Data
Recovery writes each image once, content-addressed, instead of every interval. Native files open
on a background thread. Files stay readable by main both ways; JSON-only saves are identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc
2026-10-12 11:02:33 +07:00
arhammusheer 5beac4f122 #1244: smaller objects: Node 352 -> 152 bytes, NodeKind 200 -> 80, and a cheaper undo estimate
Rarely set fields move into one optional boxed NodeExtras, and large NodeKind payloads (images,
live shapes, blends, envelopes) are boxed. Every object, and every undo step that copies it,
shrinks: a 50k-path move keeps ~24.5 MB per step instead of ~40 MB. Files stay byte-identical
(Node keeps its flat JSON through a borrowed Serialize and a from-wire Deserialize, no flatten),
and loading is slightly faster. Undo and redo reuse their step's size estimate instead of
walking the document again (8.3 -> 5.6 ms for a 50k-path move).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc

# Conflicts:
#	crates/engine/src/cmd/graph.rs
#	crates/svg/src/export.rs
2026-10-12 11:02:32 +07:00
arhammusheerandClaude Opus 5.5 bbbafdef6b #1244: undo history bounded by bytes, without lookup tables or repeated selections in its steps
History was limited by step count only; a whole-selection move of 50k paths keeps ~40 MB per
step. Steps now carry a size estimate (only unshared subtrees are visited) and the oldest are
dropped beyond a byte budget, keeping the last 8. Documents entering history drop their id
index, selections are shared between steps, the command journal is bounded (held while an
Action records), and document resources are copy-on-write. Saved files are byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc
2026-10-12 11:02:17 +07:00
dotCooCoo b3f832cbcd Links: Find in Folder… looks for missing linked files in a folder and its subfolders
Find in Folder… in Linked File Not Found and in the Links panel menu
looks in a folder the user picks, and in its subfolders, for the files
with the names of missing linked files, in any letter case. In the
Links panel menu it looks for the selected linked files when there are
any. It uses the folder search of the Missing Fonts dialog. Find
Linked Files lists each file it looks for with the files found for it.
A file with the size and contents saved with the link comes first. A
file of the saved size comes next when the link saved no hash or the
file is larger than 1 GB, and any other file of the name comes last.
Within each group, a file named in the same letter case comes first.
Then each file's folder names are compared with the saved path's, from
the innermost outward, up to the first one that differs, and a file
with more matching names comes before one with fewer. A file is chosen
when it is the only file found, or the only one with the saved
contents, and the user can choose another file or Don't Relink.

Relink relinks each file chosen, and each relink is one undo step.
With Copy into the document's folder, which shows for a saved
document, each image's file is copied next to the document first. The
file's name, then Name 2.png, Name 3.png and so on, are tried in turn
until one is free, which gets the copy, or is a file with the same
contents, which is used. A placed document is relinked where its file
is. The status line names the first file that fails to relink, and the
other files still relink. When Find Linked Files opens from Linked
File Not Found, the missing files left without a choice, and any that
fail to relink, go back to that dialog. On the web, Find in Folder… is
disabled in the Links panel menu, and links.findFiles can't start a
search.

- links.findFiles searches on separate threads and returns at once;
  links.findFiles {} reports the search, and {stop: true} stops it. It
  looks for at most 1,000 linked files at once: the active document's
  missing ones, or those of the objects it is given. It hashes a file
  only when a link of its name saved the file's size and a hash, and
  only when the file is 1 GB or smaller. It ends once every linked
  file has its same file, and it searches only the folders
  text.findFontFiles may search, with the same maxSeconds, depth and
  count limits.
- links.relink {copy} copies the file it is given into the saved
  document's folder and links images to the copy; a call that includes
  a placed document fails. Each name it writes is checked against the
  automation write root. text.addFontFiles and links.relink {copy}
  make their copies with fileio::copy_new, which never replaces a
  file. The text.addFontFiles params text and docs/mcp.md say which
  names are tried for a copy.
- vectorcraft-doc adds links::hash_reader, which hashes what a reader
  reads as hash_bytes hashes bytes.
- ui.findLinksInFolder is the Links panel's Find in Folder… for agents;
  docs/control-protocol.md lists the dialog's fields.

Docs: docs/mcp.md, docs/control-protocol.md, docs/target-app-parity.md.
2026-10-12 08:16:41 +07:00
Ikki Nishihara 2dc37d424d fix: ロック中と隔離の外側の図形から色を取得する #1251 2026-10-12 07:25:43 +07:00
Ikki Nishihara 1ac797685f fix(selection): 線分の選択と削除をアンカーから分ける (#1278) 2026-10-12 07:25:36 +07:00
IHAGI-c e4c03b5cfd Graphs keep their label formatting when drawn again
Text formatted in a graph, as
https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/visualize-data/format-the-text-in-graphs.html
describes (the whole graph with the Selection tool, or labels picked
with Group Selection, through the Character and Appearance controls),
now survives the graph being drawn again from its data.

- A graph keeps a character style per kind of label: value axis
  numbers, category labels (category axis, radar spokes, pie names)
  and legend labels (beside or across the top, or in wedges).
- text.setStyle, text.setFormat and fill/stroke changes on a graph's
  labels apply the same change to their kind's kept style, so only what
  the user changed is kept and every label of that kind follows. A
  change to part of a label's text isn't kept.
- Labels are told apart by where the graph draws them, not by their
  text, so a category named "2" and the value axis' 2 stay apart.
- Label spacing follows a kept size (legend rows, offsets from the axes
  and wedges), so bigger labels don't overlap; at 9 pt it's as before.
  Wedge labels stay white on dark wedges unless given a colour.
- A kept style from a file is held to the Character controls' limits.

Own choice, as the page doesn't say what happens on a data change:
formatting one label formats its whole kind, since the labels are drawn
again from the data. Other ways of restyling text (eyedropper,
character styles, Find Font) aren't kept yet. New fields are written
only when set; graphs without kept formatting draw the same art as
before.
2026-10-12 06:42:50 +07:00
IHAGI-c 4de410c7c8 Graph Type: widths past 100%, First Row and First Column in Front, Draw Filled Lines
Graph Type's column, bar and cluster widths from 1% to 1000%, overlapping
past 100%, the overlap order, and Draw Filled Lines, as
https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/visualize-data/format-columns-bars-and-lines.html
describes.

- Column and cluster widths take 1–1000% and are drawn as set: past
  100% columns (or clusters) overlap. The legend moves past columns
  that reach beyond the plot.
- firstColumnInFront draws the first series over the others (in each
  layer); firstRowInFront draws each series' first category over the
  next. Across series the series order decides, so each series stays
  one group for Group Selection.
- filledLines draws the connecting lines of line, scatter and radar
  graphs as 3 pt bands filled with the series colour (set as a fill or,
  on a thin line, a stroke) with a thin black outline. A run with no
  outline to make, or a graph past 20,000 data points, keeps thin lines.
- The Graph Type query lists the overlap options only for graphs with
  columns or bars, and filled lines only for graphs with lines.

Own choices, as the page doesn't say: both overlap options start off
(today's order), and the band is 3 pt wide. New fields are written only
when set. Graphs with default options draw the same art as before.
2026-10-12 06:42:50 +07:00
IHAGI-c fe33dec0bb Graph Type: Add Drop Shadow and Add Legend Across Top
Graph Type's Add Drop Shadow, as
https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/visualize-data/add-drop-shadows.html
describes, and Add Legend Across Top, as
https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/visualize-data/change-the-position-of-legends.html
describes.

- dropShadow: a shadow behind each column, bar, stacked segment, line
  (line, scatter and radar) and pie wedge, just behind the series of
  its layer, so areas and columns drawn in front don't hide the shadows
  of the layer behind them.
- legendAcrossTop: the legend's swatches and labels left to right from
  the plot's left edge, wrapping at the plot's width, the last row just
  above the plot and anything drawn over it (radar and stacked-pie
  labels).

Own choices, as the pages don't say: a shadow is a black copy 2 pt down
and right at 35% opacity; markers, areas and legend swatches cast none,
nor do stacked segments of 0; a graph with more than 200,000 shadowed
marks draws none.

New fields are written only when set. Graphs with default options draw
the same art as before.
2026-10-12 06:42:50 +07:00
IHAGI-c 5e4a282f90 Graph Data: Transpose and Switch x/y; | line breaks in labels; pie Legend, Position and Sort
Graph Data's Transpose row/column and Switch x/y, as
https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/visualize-data/add-graph-data.html
describes, a `|` in a label as a line break, as
https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/visualize-data/add-graph-labels-and-data-sets.html
describes, and the pie graph options of
https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/visualize-data/format-pie-graphs.html

- graph.setData {transpose: true} swaps rows and columns (after any new
  data in the same call); a graph with more categories than a graph can
  have series (256) is refused rather than cut. {switchXY: true} swaps
  each scatter series' y and x columns, labels included.
- A `|` in a category or series label starts a new line: legend rows
  grow with their lines, labels beside an axis or in a wedge centre
  theirs. A label of only `|` is blank.
- Pie graphs: pieLegend none|standard|wedges (Legends in Wedges puts the
  series labels inside their wedges, in white on dark wedges, drawn over
  the pies), piePosition ratio|even|stacked and pieSort all|first|none,
  as dropdowns in the Graph Type dialog, which lists them for pie graphs
  only.

Own choices, as the page doesn't say: Position and Sort keep today's
drawing by default (Even, None); Ratio sizes pies by area, the biggest
keeping the full size; Stacked pies share a centre, each drawn as the
ring outside the next smaller one so none covers another, with their
names over them.

New fields are written only when set. Graphs with default options draw
the same art as before.
2026-10-12 06:42:50 +07:00
IHAGI-c 3979872283 Graph designs and marker designs: Object › Graph › Design… and Marker…
Graph designs are named art a graph draws in place of its default
marks. Marker designs follow
https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/visualize-data/apply-marker-designs-to-graphs.html
and the Graph Design dialog
https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/visualize-data/reuse-graph-designs.html

- graph.design saves the selected art as a named design (Save Design),
  pastes a copy of a design into the document, selected, to edit and
  save again (Paste Design), and deletes one; graphs drawing a deleted
  design go back to their default marks.
- graph.marker draws the data points and legend swatches of the series
  picked with Group Selection (or listed, or every series) with a
  design: its backmost object is scaled to fill the default marker's
  square, strokes, effects and pattern tiles scaling with it. Line,
  scatter and radar series; null goes back to the default square.
- Object › Graph › Design… and Marker… open dialogs listing the
  document's designs.

Own choices, as the pages don't say: the backmost object's bounds, not
only a rectangle's, set the size (a single object uses its own bounds);
Delete Design manages the list; a design keeps its own paint, so
painting a series doesn't recolour it.

Designs are stored in a new `graphDesigns` list and series designs in
`seriesMarkers`, written only when present. A graph saved as a design is
kept as the plain art it draws. A design holds at most 2,000 objects, a
document 1,000 designs, and a graph whose designs would draw more than
200,000 objects draws default squares; a file's designs are tidied the
same way on open. Global swatch edits, image relinking and placed
documents see design art like symbol art. Graphs without marker designs
draw the same art as before.
2026-10-12 06:42:49 +07:00
arhammusheerandClaude Opus 5.5 0724aafbb6 #1172: share an object's appearance with its undo history until it changes
Every edit of an object copied its whole appearance (its fills, strokes and effects, about
540 bytes for a basic object) into the new node, though a move leaves it as it is. Over 45
whole-selection moves of 10,000 paths, undo history grew by 589 MB; now 349 MB (41 % less), and
a nudge or a drag of the selection takes 12 ms instead of 15.

- Node::appearance is a vectorcraft_doc::Shared<Appearance>: copy-on-write. A clone shares it,
  and the first change through DerefMut copies it (Arc::make_mut). Reads, writes, equality (also
  against a plain Appearance) and the saved form are those of the Appearance itself, so call
  sites read and change it as before. Sites that build or assign a whole appearance add .into(),
  and the few that need a plain copy take (*n.appearance).clone().
- Node::transform changes the appearance only when it has gradients to transform, and pattern
  paints are transformed only when there are some: a move no longer copies an appearance it
  doesn't change.

The workspace suite (5,301 tests) and clippy pass with the change; the doc and engine suites
pass again after the transform guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc
2026-10-11 23:27:21 +07:00