Commit Graph
8669 Commits
Author SHA1 Message Date
Don Omondi e1439bfe33 Scrubby labels: the Control bar's Stroke: and Opacity: links scrub their fields (#4088)
The Control bar's Stroke: and Opacity: and the Properties panel's Stroke
are links to their panels, so a drag on them did nothing while every
other field label scrubbed its field. A drag on such a link now scrubs
the field after it (same steps and modifiers, one undo step, Escape
cancels); a click still opens the panel, and the link's text is not
selected by the drag.
2026-11-06 08:11:10 +07:00
bflatastic cc0575f15d Tests: two canvas tests render on their own thread
A document's first frame goes to the render worker, and these read the
texture right after it, so under load it had not landed yet.
2026-11-06 08:10:45 +07:00
BFlat f3a8815e9a Merge pull request #5860 from mockydevs/fix/variable-library-keeps-bindings
Variables: Load Variable Library keeps a bound text variable the library declares (#4136)
2026-11-06 08:58:05 +08:00
Don Omondi 97697420f9 Variables: Load Variable Library keeps a bound text variable the library declares (#4136)
Loading a variable library deleted every variable of the document with its
bindings, including a text variable of the same name that the library
declares. Text made dynamic and then given a library's variable name lost its
binding, so selecting the library's data sets changed nothing. Such a
variable now stays bound, and the library's data sets change its text; other
variables are deleted as before.
2026-11-06 07:45:21 +07:00
BFlat 67089b9034 Merge pull request #5859 from mockydevs/fix/inner-glow-image-pixels
Effects: an image's Inner Glow follows its visible pixels, not its frame (#5308)
2026-11-06 08:43:58 +08:00
BFlat 4b8f63f2f8 Merge pull request #5858 from mockydevs/feat/quick-actions-repeat-options
Properties: Quick Actions has Repeat Options for a selected repeat (#5181)
2026-11-06 08:43:47 +08:00
BFlat 36574b7bbb Merge pull request #5857 from mockydevs/fix/intertwine-seam
Intertwine: the object brought over leaves no hairline of the one below (#5337)
2026-11-06 08:43:33 +08:00
BFlat 6e11583fb7 Merge pull request #5856 from simonhull/perf/image-tiles
Images: an image zoomed in on has its colours made only for the tiles in view (#4155)
2026-11-06 08:43:22 +08:00
BFlat 4264504ada Merge pull request #5855 from kk376/fix/svg-clippath-child-clip
SVG import: clip path on clipPath children clips child silhouettes before union (#5841)
2026-11-06 08:43:05 +08:00
BFlat 467bc3209c Merge pull request #5854 from mockydevs/feat/blend-spine-multi-select
Blend spine: Shift-click picks several spine points, which drag together (#5285)
2026-11-06 08:42:48 +08:00
Don Omondi 283c0d12bb Effects: an image's Inner Glow follows its visible pixels, not its frame (#5308)
Inner Glow clipped to the art's outline and bled in from outside it; for a
placed image that outline is the frame, so a PNG with transparent areas
glowed along the frame's edge, over pixels that don't show. For an image the
glow is now cast by its pixels' alpha (everything around them, blurred, or
the pixels themselves with Center) and kept to where the image shows, as
much as it shows.
2026-11-06 07:25:10 +07:00
Don Omondi 1f7f341c21 Properties: Quick Actions has Repeat Options for a selected repeat (#5181)
One selected radial, grid or mirror repeat (or one object of its source
art in isolation mode) shows a Repeat Options button in the Properties
panel's Quick Actions. It runs Object › Repeat › Options, opening the
Repeat Options dialog with the repeat's current values.
2026-11-06 07:23:39 +07:00
Don Omondi f987c2bbb0 Intertwine: the object brought over leaves no hairline of the one below (#5337)
The area an Intertwine member is drawn over another in was exactly the
overlap face, so its edge lay on the edge of the member below. Both edges
were anti-aliased, and the member below showed through as a thin line along
them. The area now reaches 2 pt past the face into the parts where only the
raised member paints (where drawing it again changes nothing), so its edge
lies inside that member.
2026-11-06 07:23:35 +07:00
Simon Hull c15383c510 Images: an image zoomed in on has its colours made only for the tiles in view (#4155)
From about 50% zoom on the file of #4155, the pixels drawn are the full 141-megapixel photo,
and in a CMYK document they were separated into ink planes (or colorized, or proofed) whole
for every view: 8 to 9 s of white canvas a zoom step.

When the pixels drawn are larger than a 1,024-pixel tile, their colours are made again for the
frame and part of them is out of view, only the tiles in view are made, each kept under its own
key (`ImageKey::tile`), and drawn together as one pixmap so their edges don't show. The colours
are made pixel by pixel, so the result is the same as for the whole image. CMYK images painting
their own inks are still made whole.

Zooming in to 50%, 100% and 200% on that file went from 9.0, 8.6 and 8.7 s to 0.78, 0.05 and
0.05 s (M3 Pro, one run each), with the same pixels on screen.
2026-11-06 07:23:32 +07:00
Kushagra Kumar 316f0d6213 SVG import: clip path on clipPath children clips child silhouettes before union (#5841)
Squashed from #5855 onto main.
2026-11-06 07:23:31 +07:00
Don Omondi dfe628e906 Blend spine: Shift-click picks several spine points, which drag together (#5285)
Direct Selection kept only one picked point of a blend's spine. Shift-click
now adds a point of the same spine or takes it away, and dragging any picked
point moves them all. object.blend.spine.moveAnchor takes `with`, the other
points to move by as much, with the key objects on them, in one undo step.
2026-11-06 07:23:19 +07:00
BFlat a402a48a3a Merge pull request #5852 from mockydevs/fix/svg-text-length
SVG import: textLength and lengthAdjust fit text to its length (#5842)
2026-11-06 07:37:56 +08:00
BFlat 4ae875bb96 Merge pull request #5851 from mockydevs/feat/direct-selection-cmd-bounding-box
Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box (#3901)
2026-11-06 07:37:41 +08:00
BFlat 0c5cd535c2 Merge pull request #5850 from mockydevs/fix/join-tool-trim-crossing
Join tool: scrubbing the ends of two crossing paths trims them and joins the paths at the crossing (#5844)
2026-11-06 07:37:25 +08:00
BFlat e33d30bcee Merge pull request #5849 from mockydevs/fix/svg-clippath-union
SVG import: a clipPath's children clip to their union, each under its own clip-rule (#5840)
2026-11-06 07:37:10 +08:00
BFlat 4d5a05d58e Merge pull request #5853 from storytold/fix/first-frame-test-placed-generation
Tests: the first-frame worker test tolerates another test's placed document
2026-11-06 07:36:55 +08:00
Don Omondi 8236a8e006 SVG import: textLength and lengthAdjust fit text to its length (#5842)
SVG text with a textLength came in at its natural width. Following
SVG 1.1 §10.9.1, the characters of each text or tspan with a textLength
are now fitted to it: by spacing them out (lengthAdjust="spacing", the
default), which becomes kerning, or by stretching them
(spacingAndGlyphs), which becomes horizontal scale (kept within
1–10000%). Inner elements are fitted first; vertical text is spaced out
only, with a warning for spacingAndGlyphs. Negative lengths are ignored.
2026-11-06 05:38:51 +07:00
Don Omondi 483de72dbb Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box (#3901)
Squashed from #5851 onto main.
2026-11-06 05:33:00 +07:00
Don Omondi b976de560d Join tool: scrubbing the ends of two crossing paths trims them and joins the paths at the crossing (#5844)
The Join tool drew a segment between the open ends of two crossing paths
instead of trimming what overhangs the crossing. path.joinScrub now looks for
two crossing open subpaths whose overhanging ends the scrub runs over, cuts
both at the crossing and joins the kept parts there with a corner, in one undo
step. Without a crossing the endpoint join is unchanged.
2026-11-06 05:32:24 +07:00
Don Omondi 446c3f071f SVG import: a clipPath's children clip to their union, each under its own clip-rule (#5840)
A clipPath with several children was imported as one compound path under
the first child's fill rule, so children wound in opposite directions
cancelled where they overlapped, and an even-odd child after a non-zero
one lost its hole. SVG 1.1 §14.3.5 defines the clip as the union of the
children's silhouettes, each computed on its own.

A clipPath with several children now comes in as a group of them as the
clip group's clipping path, each child keeping its own rule; a group
clips to the union of its members. One child imports as before.
2026-11-06 05:32:19 +07:00
bflatastic 887f59c7a3 Tests: the first-frame worker test tolerates another test's placed document
The frame key holds the process-wide placed-document generation, so a test
running beside this one could make a new frame between the two it compares.
2026-11-06 05:32:18 +07:00
BFlat 4134158907 Merge pull request #5848 from simonhull/perf/first-frame-off-ui
Canvas: a document's first frame renders on the render worker (#4155)
2026-11-06 05:03:56 +07:00
BFlat f2cf4b1b3b Merge pull request #5847 from ZIDAN44/fix/5372-control-bar-paragraph
Control bar: Paragraph options while type is selected (#5372)
2026-11-06 05:03:39 +07:00
Simon Hull af31e1464c Canvas: a document's first frame renders on the render worker (#4155)
A document's first frame (opened, or its tab chosen) rendered on the UI thread: only a
document with a frame already shown sent its frames to the worker. A heavy file froze the
window until it was drawn, 8.7 s for the file of #4155 and 1.4 s for a 50,000-path SVG.

The first frame now goes to the worker too, and the window shows the document's paper and
artboards until it lands. The UI thread waits for it up to FIRST_FRAME_GRACE (16 ms) when it
is queued (`Worker::wait`; `submit` says whether it queued), so a light document's art still
shows in the same frame. The longest UI-thread stall opening those files is now 64 ms and
62 ms (M3 Pro, one run each). The web build, which has no worker, is unchanged.
2026-11-06 04:55:02 +07:00
Zinadin ZidanandClaude Opus 5.5 d04585f6d2 Control bar: Paragraph options while type is selected (#5372)
With type selected the Control bar now shows a "Paragraph:" link, which
opens the Paragraph panel in a popover as Character: does, and Align
left/center/right. The bar now trims strictly from its right end as it
narrows: X/Y/W/H, Distribute, object Align, area type's vertical alignment,
then the Paragraph buttons, then the link, each group measured. That also
stops Align and Distribute running past the bar's edge for other
selections. On a 1440 pt bar with one point type object the buttons show.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 04:54:35 +07:00
.CooCoo 36c73f7bb9 Type: Enter, arrows, Home, End, Tab and the clipboard apply in order with the frame's text (#2636)
* Type: a Backspace and the text typed after it in one frame apply in order

The Type tool received all of a frame's typed text and IME events
first, and the frame's Backspace, Delete, arrows, Home, End, Tab and
clipboard keys afterwards. Enter went to the tool before the text, and
several Enters in one frame added one line break.

A phonetic keyboard tool can replace a character it typed by sending a
Backspace and the new character: ন for n, then a Backspace and ং when g
follows. When the Backspace and ং arrived in one frame, ং was inserted
and then deleted, so ন was left where ং belonged (#2615).

route_type_input now takes the frame's text, IME events, editing keys,
Enter and clipboard events in one pass, in the order they came. The
Type tool still drops the keys of a frame that began with IME marked
text, and the keys that come while the IME composes. A key or clipboard
event that comes before a new composition in the same frame is now
applied instead of dropped, and an Enter in the same frame as an Escape
no longer types a line break.

* Type: a command bound to Cmd+Enter runs without typing a line break

While the Type tool edits text, Enter went to the tool as a line break
before the shortcuts ran, so a command bound to Cmd/Ctrl+Enter also
typed a line break. Enter now types a line break only when no command
is bound to its chord. Plain Enter, and Cmd+Enter with nothing bound
to it, type a line break as before.

* Type: Enter, arrows, Home, End, Tab and the clipboard go in type_text's ordered pass

Enter, the arrows, Home, End, Tab, Backspace and Delete with modifiers,
Type > Bold and Italic, and the clipboard events now join the ordered
pass in type_text that takes text, IME events and plain Backspace and
Delete, instead of a separate pass that replaced it. route_type_input is
split into type_input, which takes one event, and apply_type_input.
Every event the Type tool takes, text and IME events included, is
removed from the input.

An Enter press whose chord a command is bound to stays in the input for
the shortcuts. The check uses chord_runs_a_command, which runs_a_command
now calls for each press, so the separate Cmd+Enter check in
route_type_input is removed. A plain Enter and a bound Cmd+Enter in one
frame type one line break and run the command.

hook_ime_backspace_and_reinject_builds_vietnamese covers a Backspace and
the text after it in one frame, so the Bangla test is removed. The order
test covers Home, End, Tab, a paste, and a key that comes before a
composition starts in the same frame.

* Type: editing keys after an IME commit apply in the same frame

In a frame that began with a composition, the Type tool dropped every
editing key, also one that came after the IME committed its text, so a
frame of a commit, ← and a letter typed the letter after the committed
text without moving the caret. Editing keys after a commit in that
frame now apply in order. A key that comes while the composition is
still open, and an Enter in a frame that began composing, still belong
to the IME.

* Type: Shift+Enter types a soft return when keys apply in order with the text

The ordered Type tool input passed Enter to the tool without its modifiers, so Shift+Enter typed a line break instead of the soft return main added. Enter now carries the key's modifiers.

* Type: test Home, Tab and Backspace against the frame's earlier text

A new test types text, Home and text in one frame, Tab inside a line,
and a paste followed by Backspace, and checks that each acts on the
text the earlier events left. It fails on main, where those keys apply
after all of the frame's text.

The Enter and Escape loop in shortcuts::handle skips Enter with
`skip` instead of an `if`, which keeps the function under main's
cognitive-complexity limit.
2026-11-05 22:22:47 +01:00
BFlat 80819ed7df Merge pull request #5845 from simonhull/perf/ink-after-mip
Images: a reduced image is shrunk before it is separated into ink planes (#4155)
2026-11-06 04:03:18 +07:00
Simon Hull 8147d33f56 Images: a reduced image is shrunk before it is separated into ink planes (#4155)
With image mips on (the canvas, navigator and layer thumbnails), an image was colorized,
separated into the frame's ink plane and shown through Separations Preview's plates at its
full size, and only then shrunk to the mip drawn. The 141-megapixel image of #4155 made
1.6 GB of planes a frame, more than the image store holds, so every frame made them again.

The mip is now picked first and everything else is made from it, under the keys `kept_mip`
and `nearest_kept` already look up. A CMYK image's own inks are halved with its pixels
(`ink::key_inks`, `images::half_inks`), so it still paints them rather than inks separated
from its displayed colours. Colour bitmap glyphs follow the same order.

On that file the app's first frame went from 9.2 s to 1.6 s and the sharp frame after a pan
from 3.9 s to 8 ms (M3 Pro, one run each). Fine detail now shows its true average: 1-pixel
black and white lines show mid grey, where separating first showed them 44 levels darker.
2026-11-06 04:02:47 +07:00
BFlat 5f44923011 Merge pull request #5843 from sadkotheguest/feat/outer-glow-color-swatch
Effect dialogs: a colour swatch beside a hex colour opens the Color Picker (#5365)
2026-11-06 02:42:52 +07:00
Alex c2ef67b505 Effect dialogs: the colour swatch in the progress docs (#5365)
Roadmap, target-app-parity (revision row, status line, evidence bullet and the UI chrome
area row) and ui-parity (revision row and a checklist row) record the swatch beside a hex
colour field, its Color Picker and the test that covers both.
2026-11-06 02:34:17 +07:00
Alex 18c0ef06e2 Outer Glow: a colour swatch beside Color opens the Color Picker (#5365)
Effect dialogs that take a hex colour (Outer Glow, Inner Glow, Drop Shadow's fields) show a
swatch beside the colour field: it follows the field as it is typed, and a double-click opens
the Color Picker over the dialog, whose OK fills the field and whose Cancel comes back to the
dialog. While the picker is open it previews nothing itself: the effect dialog previews the
colour when it returns.

The swatch is part of the generic effect parameter grid, so every effect dialog with a colour
parameter gets it, and the Color Picker learns to fill one field of a dialog opened over
(`over_effect`, a parent like Recolor Artwork's).

Test: dialogs::effect::tests::outer_glow_colour_swatch_opens_the_color_picker (swatch drawn
on #ffff00, a typed hex recolours it, a double-click opens the picker on it, OK fills Color and
the effect applies #00ff00).
2026-11-06 02:34:17 +07:00
BFlat 9bd11a3202 Merge pull request #5839 from mockydevs/feat/handles-after-drawing-5477
Shape tools: a shape just drawn shows its bounding box and Live Shape widgets, and its corner and pie widgets drag with the shape tool (#5477)
2026-11-06 02:02:55 +07:00
Don Omondi 87a656cdef Shape tools: a shape just drawn shows its bounding box and Live Shape widgets, and its corner and pie widgets drag with the shape tool (#5477) 2026-11-06 01:50:55 +07:00
BFlat 587c3f995a Merge pull request #5838 from dotCooCoo/fix/ai-symbols-open-live
.ai import: symbols and their instances open live
2026-11-06 01:45:05 +07:00
BFlat efae291664 Merge pull request #5837 from mockydevs/fix/move-cursor-5246
Dialogs: the header that moves a dialog keeps the arrow cursor until dragged (#5246)
2026-11-06 01:44:53 +07:00
BFlat a58fdad3d5 Merge pull request #5836 from dotCooCoo/docs/links-hash-limit-gib
Links: the hashing limit reads 1 GiB in the docs
2026-11-06 01:44:38 +07:00
dotCooCoo 0c272519fb .ai import: symbols and their instances open live
The reader measured a symbol's definition only to find the page art an
instance covered, so every file with a placed symbol instance opened
with that instance as drawn art, or as its page.

- Each `%AI14_BeginSymbol` definition is read by a reader of its own into
  a symbol of the document, with its art (in the symbol's space, y down),
  the registration point its dictionary gives
  (`AI13PatternRegistrationType`, 1 to 9 from the top left) and the
  instances of other symbols in its art. A definition whose art isn't
  read whole isn't kept: type, placed files, non-native art, pattern
  fills, a gradient or an image the reader can't read or place, and
  anything else the reader doesn't read. Its instances come in as the
  page draws them, within the box of the art that is read. A definition
  that holds an instance of a symbol the file doesn't have isn't kept
  either, nor is a definition holding an instance of one of those, and
  their instances aren't read. A definition after the first layer isn't
  read.
- `%AI24_BeginSymbolList` orders the symbols as the Symbols panel lists
  them. A name read twice is numbered ("Dot 2"), and instances name the
  first symbol with the file's name.
- `/SymbolInstance` with `/SymbolRef` and `/RTransformMatrix` comes in as
  a live instance, with the hide and lock set before it and the opacity,
  blending mode, isolation and knockout of the `Xy` after it, as a
  group's. An instance with appearance changes of its own
  (`AI19SymbolAppearanceMap`), of a symbol that isn't read, or placed
  only by `/Matrix` or `/Transform`, comes in as the page draws it.
- Symbol art takes the gradients and art styles read before it, and an
  object in it linked to a listed graphic style keeps the link.
- In a symbol's art, the matrices of instances, gradients and images are
  read in the canvas's space, y up, about the canvas point of the ruler
  origin. Without `%AI3_TemplateBox` an image can't be placed, so the
  symbol isn't kept.
- At most 4,096 definitions are read, and a symbol list names at most
  16,384 symbols.

Tests: reader tests for each case above, and fuzz cases for damaged and
hostile symbol data.
2026-11-06 01:32:42 +07:00
Don Omondi a57328be2f Dialogs: the header that moves a dialog keeps the arrow cursor until dragged (#5246)
Hovering the band that moves a modal dialog (New Document's top edge and
tab row, other dialogs' headings) set the grab cursor, which Windows
shows as the four-way move arrows. It now keeps the arrow, as a title
bar does, and shows the grabbing cursor only while the dialog is being
dragged. Dragging still moves the dialog.
2026-11-06 01:32:42 +07:00
dotCooCoo c4d40de212 Links: the hashing limit is 1 GiB in params text and MCP docs
links.findFiles hashes files up to MAX_HASHED (1 << 30 bytes) and compares larger ones by size only. The params text, the module doc comment and docs/mcp.md said 1 GB; they now say 1 GiB.
2026-11-06 01:32:42 +07:00
BFlat 9a46fba8f6 Merge pull request #5834 from mockydevs/fix/snap-to-glyph-options-5273
Type: Show Snap to Glyph Options scrolls the Character panel to the options (#5273)
2026-11-06 01:32:16 +07:00
BFlat 440e15959c Merge pull request #5833 from dotCooCoo/fix/ai-distort-paths
.ai import: objects other than type keep some Distort & Transform effects live
2026-11-06 01:32:00 +07:00
Don Omondi 496237242d Type: Show Snap to Glyph Options scrolls the Character panel to the options (#5273)
The Snap to Glyph section is drawn after every character attribute, so
in a short panel it appeared out of sight and the menu item seemed to do
nothing; its checkboxes were also dimmed with no way to turn Snap to
Glyph on from there. Choosing Show Snap to Glyph Options now scrolls the
panel to the section, which starts with a Snap to Glyph checkbox
(View > Snap to Glyph) that undims the guide options.
2026-11-06 01:20:04 +07:00
dotCooCoo ddb64fceb6 .ai import: objects other than type keep some Distort & Transform effects live
An art style with a Distort & Transform effect opened every object but
type as its drawn look. Objects other than type now open as themselves
with these effects live:

- Any object takes a Tweak that moves nothing and Pucker & Bloat at 0,
  beside the effects it already took.
- A path, compound path or group takes Transform without copies, or a
  Free Distort whose corners make a parallelogram, when it is the only
  effect. Neither the style nor anything in the object may paint with a
  gradient or pattern. Everything in the object must be a path, compound
  path or group without a clip, an opacity mask or effects of its own.
  When Transform scales, rotates or reflects, nothing in the object may
  be stroked.
- An unstroked path or compound path whose segments are all straight
  takes Twist when it is the only effect.

Other objects keep the drawn look, as do Zig Zag, Pucker & Bloat of
another amount, a Free Distort with other corners and Transform copies.

Docs: docs/file-format-parity.md, docs/target-app-parity.md.
2026-11-06 01:20:04 +07:00
BFlat c07f0087ce Merge pull request #5832 from storytold/perf/1280-hover-memo-check
Selection: a hover checks the bounds memo once, not once per object
2026-11-06 01:19:39 +07:00
FredAppsandClaude Opus 5.5 64f7b49dea Selection: a hover checks the bounds memo once, not once per object
Hovering looks up the bounds of every selected object, and each lookup
went through the memo key check, which compares the whole selection. That
made a hover quadratic in the selection: 1.5 s at 50,000 selected paths.

The walk now checks the key once and shares it across its lookups.

Part of #1280.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 01:07:34 +07:00