Commit Graph

856 Commits

Author SHA1 Message Date
Gert Pellin e54fe9c31e Native reopen: type in symbols gets its layout bounds back (#1738)
Type's layout bounds are a cache that isn't saved. On open they were
computed again only for type in the layers, so type in a symbol definition
(or a pattern tile, graph design or opacity mask) kept the rough estimate,
and an envelope warping it was fitted to a different box: warped type in a
symbol changed shape after a save and reopen. The refresh now goes through
every art tree, masks included.
2026-10-14 08:01:50 +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
Ayberk A. 8894da68d7 Part of #1736: one toolbar column runs on without group labels too
Without group labels, the single column no longer draws a dash between
the groups: its tools are evenly spaced from top to bottom, as two
columns already run on from group to group. With the labels, each group
still starts under its name.
2026-10-14 07:37:54 +07:00
Ayberk A. 03c0b8f355 Part of #1736: the toolbar without group labels, and the All Tools drawer's groups, flyout menu and Show section
- Preferences › User Interface › Show Tool Group Labels is off as it
  ships: the toolbar sets its groups apart with a faint dash, as the
  Basic and Advanced toolbars of the public toolbar reference do (the
  categories there are the Getting Started toolbar's). It no longer cuts
  "Navigate" to "Navi...". The labels are a preference away.
- The All Tools drawer lists the tools one per row (icon, full name,
  shortcut on the right), each flyout group framed together under the
  heading most of its tools have (the Pencil's group, led by the Shaper,
  under Draw). Tools already in the toolbar stay dimmed; dragging and
  picking are as before.
- Its ☰ menu has Reset and the Basic and Advanced toolbars, the drawer's
  flyout menu of the reference.
- Its Show section shows or hides the controls under the toolbar's tools
  (Fill and Stroke, Coloring, Draw Mode, Screen Mode), as the reference
  describes; `toolbar.show {control?, on?}`, saved with the preferences
  (`ui.toolbar_show`, all on for older ones). Edit Toolbar's ••• stays.
- New strings in every catalog.

Reference: https://helpx.adobe.com/illustrator/using/tools.html (the look
is VectorCraft's own, with its own icons).
2026-10-14 07:37:54 +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 1f4daf519d Image Trace: enhanced versions of the presets
Each documented preset has an enhanced version (`enhanced: true` in
`imageTrace.make`, an Enhanced checkbox under the panel's preset): the preset
with Auto Grouping, and with Shapes in all but High Fidelity Photo. Gradients
stay an option. Flat Logo, a preset of our own, has none. The legacy presets,
and documents traced with them, are as they were.

So that an enhanced preset is no worse than its preset on that preset's inputs:
- A hole that one colour fills is the same shape as the path that fills it, so
  the two still share one outline (a shape beside a fitted hole left slivers).
- A rectangle is as large as where its sides lie on average, not their farthest
  points.
- Auto Grouping keeps the paths of a group in the order they were traced in. It
  put a path first and what is inside it on top: a white counter traced under
  its black letter was then painted over the letter's edge, a light seam
  (Default with Auto Grouping, edge dE 10.24 -> 9.88; legacy 10.05).

The harness measures every enhanced preset beside its preset.
2026-10-14 06:43:57 +07:00
bflatastic 638d55983f Image Trace: stroked lines get square caps where their ink is cut square
Create: Strokes drew every line with round caps: a square-ended line 10 px wide
had its corners 2.94 px outside the stroke.

- A line's ends are square when its ink reaches as far a third of the width to
  either side of its middle as along it (a round end stops short there by a
  seventh of the width); told from lines 3 px wide and more, all the ends of a
  line together. The stroke then has square caps (`TracedPath::square_caps`).
- The last points of a wide line's skeleton can turn towards a corner of its
  end. An end that has left the line the skeleton followed a width back (by a
  quarter of the width, 1 px at least) is put back on it; and an end may be
  pulled back as well as pushed out to where the ink ends.

Outline on the thin-line inputs: largest distance from the art 2.94 -> 0.88 px
(clean 2.94 -> 0.41; aliased 0.88 -> 0.81), mean 0.055 -> 0.050 px, anchors
14 -> 10.
2026-10-14 06:43:57 +07:00
sanyamsingh001 bb07d267aa G1: keep Artboard deletion consistent across controls
Co-authored-by: Codex <noreply@openai.com>
2026-10-14 05:15:42 +07:00
sanyamsingh001 5f4da3beb2 G4: capture character styles from selected text
Co-authored-by: Codex <noreply@openai.com>
2026-10-14 05:15:34 +07:00
sanyamsingh001 123b8ad8ea G4: propagate character-style Auto-leading resets
Co-authored-by: Codex <noreply@openai.com>
2026-10-14 05:15:30 +07:00
sanyamsingh001 0e6b2e331d G1: create width points through Width Point Edit
Co-authored-by: Codex <noreply@openai.com>
2026-10-14 05:14:20 +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
bflatastic cfa65a2148 Appearance of Black › Printing / Exporting: cover ink-plane documents and CMYK exports
Drops the documented richBlack parameter: commands don't list it.
2026-10-14 05:12:59 +07:00
Fabio William Conceição b6d14b1fd0 Part of #394: Appearance of Black › Printing / Exporting draws pure CMYK black as RGB black
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-14 05:11:55 +07:00
GameDirection b5bc8f0d19 Corner Radius: a field per corner of a live rectangle, with a link toggle
The Corner Radius field in the Properties and Transform panels has a
link toggle behind it (the "singleCornerRadius" preference, on by
default). When the link is on, one field sets every corner, as before.
When it is off, the panels show one field for each corner of a live
rectangle: Top Left, Top Right, Bottom Right and Bottom Left. Each field
sets its own corner through object.setLiveShape {corners, radius}.

This is the part of #1594 that does not change the corner widget drag
or its modifier keys, split out for review.

Tests: a Properties panel test (the link on shows one field; off shows
the four labelled fields with each corner's radius, in line and in
order). The new labels are translated in all 15 catalogs.
2026-10-14 05:11:22 +07:00
Ayberk A. 043b446b94 3D and Materials: the light takes the documented settings
The public 3D and Materials panel options give the Light Settings as
Intensity (0–100 %), Rotation (-180° to 180°), Height (0° to 90°),
Softness (0–100 %) and Ambient Light (0–200 %), with the light moved to
the back or the front of the object. The 3D effects (Revolve included)
had Direction and Elevation (an azimuth and elevation), Intensity and
Ambient (0–100 %).

- Rotation turns the light round the object on the page (our reading:
  0° on its right, counterclockwise); Height raises it from beside the
  object (0°) to straight in front (90°); Light behind the object puts
  it at the back. The light gizmo's ring and line are Rotation and
  Height.
- Softness wraps the light round the object: at 0 % it falls off with
  the angle as before, at 100 % it reaches nothing only straight away.
- Ambient Light runs to 200 %; faces stay no brighter than the art.
- lightRotation, lightHeight, lightBehind and lightSoftness replace
  lightAzimuth and lightElevation. An earlier light is read as the same
  direction, so it shades exactly as before (old and new code give the
  same colours for the four effects and five lights); effect.setParams
  turns a stored one into the new form before applying new values, and
  the parameter description names lightAzimuth and lightElevation as the
  earlier form, so effect.apply and effect.setParams still take them. The
  default light is the earlier default's: Rotation 125.26°, Height 30°.
- The dialog's Lighting tab shows the settings in the documented order;
  Softness, Ambient Light and Light behind the object are in every
  catalog, and the unused Direction, Elevation and Ambient are gone.

Light colour, presets, several lights and shadows are not built.
2026-10-14 05:10:48 +07:00
Ayberk A. 39db6abfce 3D and Materials: Perspective runs 0° to 160°, as documented
The public 3D and Materials panel options give Perspective as 0° to 160°
("how close or far the object appears"); it was a 0–100 % strength, for
Revolve, Extrude & Bevel, Inflate and Rotate alike.

- The eye stands where a sphere about the object's centre, √3 times its
  half size (the box around it fits inside), fills that angle of the
  view: 0° is none, wider brings the eye nearer, and at 160° it is just
  outside the sphere, never inside the object. The documentation doesn't
  say how the angle places the eye; docs/three-d.md says this is our
  reading.
- `perspectiveAngle` (degrees) replaces `perspective` (%). A strength from
  an earlier file or script is read as the angle that shows exactly the
  same view: sin(angle / 2) = √3 × strength / 4, 100 % about 51.3°. An
  angle given too wins. effect.setParams turns a stored strength into the
  angle before applying new values. The parameter description names
  `perspective` as the earlier form, so effect.apply and effect.setParams
  still take it under the key check of #1592. Old and new code draw the four effects identically at 0, 25,
  60 and 100 % (bounds equal to 1e-9).
- The dialogs' Perspective fields run 0–160°.

# Conflicts:
#	docs/target-app-parity.md
2026-10-14 05:10:09 +07:00
dotCooCoo 4844d54fda Type: an edit copies a threaded story only when it changes one of its frames
After every edit of a document with threaded text, each frame of a
drag included, threads::reflow copied the runs and the text object of
every frame of every thread, and then dropped the copies for each
thread whose frames the edit had not changed. With a story of 50,000
characters threaded through 8 frames with a style change at every word
(9,618 runs), moving another object took 7.8 ms in a release build,
and a frame of a drag took 7.2 ms.

reflow now copies a thread's story only when one of its frames
changed. The same move takes 0.013 ms, and the drag frame takes
0.012 ms. Threads with a changed frame re-flow as before, and the
documents and undo steps after edits are the same as before.

Docs: docs/target-app-parity.md.
2026-10-14 04:56:30 +07:00
dotCooCoo 85cea7efea Open: the opening thread also reads linked files and lays out type
When the desktop app opened a document on a thread of its own, that
thread parsed it, but open_read, which the app ran on the UI thread,
read the document's linked files, decoding BMP, PSD and RGB TIFF files
and storing them again as PNG, and laid out the type. In release builds,
a document with 12 linked 3000 x 2000 TIFFs kept the UI thread busy for
1.04 s, and one with 300 Auto Size area type objects, every second one
in three columns, kept it busy for 0.85 s.

read_for_open now reads the linked files and lays out the type on the
opening thread, with the path and the Update Links preference that
opening::start passes it, and open_read only adds the finished document.
open_read now takes 0.02 ms and 0.1 ms on those documents. For the
measured documents, the opened documents, results and undo states are
the same as on main.

Docs: docs/target-app-parity.md.
2026-10-14 04:56:30 +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
dotCooCoo 6504dc7da2 Render: live objects are evaluated again when patterns, symbols, fonts or color settings change
The renderer kept the evaluation of each blend, envelope and repeat
while the object and a hash stayed the same: the addresses of the art of
the symbols the object showed and of the patterns it painted with, and
the patterns' tile bounds. When anything else the evaluation reads
changed, such as a pattern's tile type, symbols in symbol art, the
fonts or the color settings, a renderer that had drawn the object went
on drawing the old evaluation. The quads of a gradient mesh were kept
the same way after a change to the color settings, and the art drawn
for a live object with an effect of its own was kept by the object's
address alone.

resources_key now also walks the opacity-mask, symbol and pattern art
it finds. It hashes what it finds, the font generation when it finds
type or a placed document, and the color settings. Mesh quads are made
again when the color settings change. The art drawn for an object's
effects is made again when the hash changes, if it was made from
evaluated live objects.

With other builds running, a temporary release test timed the hash for
400 live objects at 17 to 506 microseconds a frame, against 10 to 16
before.

Docs: docs/target-app-parity.md, docs/gaps.md.
2026-10-14 04:56:29 +07:00
ham9zah 66a0f457e9 Add a Precise Cursors command and fix the misleading Caps Lock comment
General > Use Precise Cursors already worked and is saved with the other
preferences. This adds a `view.preciseCursors` command that toggles that same
preference, so it can be given a shortcut in Edit > Keyboard Shortcuts. It has
no default shortcut and no View menu item.

The comment in canvas.rs claimed Caps Lock gave precise cursors; nothing reads
Caps Lock (egui has no state for it), so it now describes what the code does.

Tests: for the tools whose own pointer the preference replaces (Pen, the anchor
tools, Curvature, Mesh, Eyedropper, Slice, Blend, Shape Builder) the pointer is
their own when off and a crosshair when on; every state of the Pen's pointer
turns into a crosshair; the preference round-trips through
to_json/apply_prefs; the command toggles it, updates the saved UI state, is
listed in the shortcut editor without a key and can be assigned one; switching
it creates no undo step and leaves the document saved; with system cursors the
glyph fallback is the system crosshair either way. The new label is translated
in the languages that claim complete menus.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 03:55:41 +07:00
ham9zah 3ea4802a53 Pen: test that a click after a smooth anchor curves from its side only
Covers the Illustrator behaviour that a corner placed after a smooth anchor
has no handles, so the segment is shaped by the previous outgoing handle alone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 03:55:41 +07:00
sanyamsingh001 4944facd0a CI: isolate text import fuzzing from installed fonts
Co-authored-by: Codex <noreply@openai.com>
2026-10-14 03:55:41 +07:00
Gert Pellin ed1e976264 Fix #1748: text.create takes justify, so point type can be centred on its point
text.create, text.createInPath and the MCP add_text tool now take a
`justify` (auto|left|center|right|justifyAll, as text.setStyle). Point
type already laid out centred or right-aligned text around its origin;
it just could not be made that way, so scripts measured the bounds and
moved the text. Changing it afterwards stays text.setStyle {justify}.
2026-10-14 03:55:41 +07:00
Marco Arnulfo d9328c7668 Select > Same: type matches on its characters' fill and stroke (fixes #1737) 2026-10-14 03:55:41 +07:00
Ayberk A. 2f4cc8a1cc Fix #1756: Object → Expand expands repeats
Expand had nothing for live repeats, so a radial, grid or mirror repeat stayed
live. Its Object option now turns every repeat in the selection (inside groups
too, inner ones first) into a group of its instances, keeping the repeat's id
and place, in the same undo step; the dialog enables Object for a repeat.
Expand Repeat shares the code.
2026-10-14 03:55:41 +07:00
bflatastic 3152436020 Brackets: keep to the tools whose size the keys are documented for
The typed [ and ] now reach the Blob Brush, the Eraser and the Symbolism
tools, whose bracket keys are in the public shortcut lists. A size of the
Paintbrush's own (and path.freehand's scale) is left out until its
behaviour is decided: #1661 stays open for that.
2026-10-14 03:55:41 +07:00
Ayberk A. 3f1e72d983 Fix #1661: [ and ] size the Paintbrush, and reach the tools that take them
The tools took `[` and `]` (the Blob Brush's and the Eraser's size, the
Symbolism tools' diameter and, with Shift, intensity), but the app never
passed the keys on: only `ui.key` over MCP reached them.

- The keyboard handler gives a typed `[`, `]`, `{` or `}` (Shift) to the
  active tool when it claims the key. It reads the typed character, so
  layouts where a bracket takes Option or AltGr (Option-8 on a Turkish
  Mac) work too. A field with the keyboard, the Type tool and Cmd/Ctrl
  shortcuts are left as they were.
- The Paintbrush has a size of its own, 10 to start as the Blob Brush's:
  `[` and `]` take a point off or add one, and the next stroke is drawn
  at size / 10 of the stroke weight (path.freehand `scale`). Public
  references describe the keys as changing the size the Paintbrush draws
  with, before drawing, the brush definition left as it is; so the
  stroke weight new art takes stays, and the selected path keeps its
  stroke. Every brush type, as the report and the Tuts+ guide describe;
  the step is our own.
2026-10-14 03:55:41 +07:00
Ayberk A. ef2d678cbf Fix #1679: a filled pencil path keeps a stroke of None
The Pencil gave every path a black stroke when the stroke was None. With Fill
new pencil strokes on and a fill set, the path now keeps no stroke; only a path
that would show nothing (no fill, no stroke) still gets black.
2026-10-14 03:55:40 +07:00
Ayberk A. 1e60a572b1 Fix #1686: Escape in type editing goes to the Selection tool
Escape still ends editing (an empty new point type is discarded) and the type
stays selected; the Type tool now claims Escape while editing and asks for the
Selection tool, so tool shortcuts work again without the mouse.
2026-10-14 03:55:40 +07:00
turtwig 89501d73c2 Delete removes every selected anchor and keeps the remainder selected
A fully selected object is deleted along with partial anchors on other
paths. Several anchors on one closed path are removed by their original
indexes. What remains of a path stays selected, so a second Delete
removes it.
2026-10-14 03:55:40 +07:00
BFlat 4d54fa27e3
Merge pull request #1773 from MarekSpodBiedry/issue-1722
Linked files: a change during a drag is applied after the drag, not lost (#1722)
2026-10-14 03:52:41 +07:00
BFlat 7f93a191c7
Merge pull request #1775 from MarekSpodBiedry/issue-1720
Do not journal a failed live preview at commit (#1720)
2026-10-14 03:52:17 +07:00
BFlat 0f0380b827
Merge pull request #1774 from MarekSpodBiedry/issue-1721
Stamp File Info dates only after a save succeeds (#1721)
2026-10-14 03:51:47 +07:00
BFlat 4892384168
Merge pull request #1780 from MarekSpodBiedry/issue-1707
Fix scatter X axis ticks looping forever on data near the f64 maximum (#1707)
2026-10-14 03:49:57 +07:00
BFlat 6f56ad2e7f
Merge pull request #1777 from MarekSpodBiedry/issue-1713
Keep Export for Screens suffix inside the export folder (#1713)
2026-10-14 03:48:36 +07:00
BFlat b374fee789
Merge pull request #1786 from MarekSpodBiedry/issue-1696
Refuse to package over the source document or its linked files (#1696)
2026-10-14 03:47:20 +07:00
bflatastic 8a7e36ede8 Background save edited meanwhile: undoing back to the saved state is unmodified (#1721) 2026-10-14 03:17:51 +07:00
bflatastic 7abc1cfa10 Scatter overflow test: data that far off the plot is refused, not drawn (#1707) 2026-10-14 03:17:49 +07:00
bflatastic d82d6e3620 Test that a hostile Export for Screens suffix or prefix stays in the export folder (#1713) 2026-10-14 03:17:47 +07:00
bflatastic f05fbfc292 Pathfinder panel: count a group's shapes without copying them 2026-10-14 02:09:08 +07:00
Marco Arnulfo 7ea75b01ba Pathfinder panel: a group selected alone counts its nested shapes, so an expanded stroke keeps the Shape Modes enabled (fixes #1635) 2026-10-14 02:09:07 +07:00
bflatastic 359d9bb833 Distribute spaces the selected anchor points too, and its buttons work with the points of one path (#1636) 2026-10-14 02:09:06 +07:00
MarekSpodBiedry b50519587d Refuse to package over the source document or its linked files (#1696) 2026-10-13 18:19:12 +02:00
MarekSpodBiedry b8744f6d33 Stamp File Info dates only after a save succeeds (#1721) 2026-10-13 18:18:47 +02:00
MarekSpodBiedry 1f44a18c7a Do not journal a failed live preview at commit (#1720) 2026-10-13 18:17:49 +02:00
MarekSpodBiedry ba94976bf2 Fix scatter X axis ticks looping forever on data near the f64 maximum (#1707) 2026-10-13 18:17:33 +02:00
MarekSpodBiedry 65cc818572 Linked files: a change during a drag is applied after the drag, not lost (#1722) 2026-10-13 18:17:02 +02:00