Commit Graph
5209 Commits
Author SHA1 Message Date
FractalEncrypt d572f3ce71 Shape tools: the tool methods this pull request changes say what they do
The `Tool` implementation's `busy`, `pointer`, `key`, `claims_key`, `overlays`
and `cursor` in shape.rs, and `pointer` and `cursor` in select.rs, are the ones
this pull request edits; each now has a doc comment, so the docstring coverage
of the functions the diff touches is complete.
2026-11-07 05:15:05 +07:00
FractalEncrypt 1c271c776c Shape tools: review follow-ups from CodeRabbit
- The 45° move assertion compares dx and dy with a small tolerance instead of
  exact equality (the matrix's floats are rounded), keeping the non-zero check
  and the message.
- The engine test helpers say what they do, so the docstring coverage of the
  functions this pull request touches is complete.
2026-11-07 05:15:05 +07:00
FractalEncrypt dd160dc768 Selection: Cmd/Ctrl over a centre point selects behind, so the arrow stays plain
The press with Cmd/Ctrl held selects the object behind rather than taking the
centre point; the pointer now agrees.
2026-11-07 05:15:04 +07:00
FractalEncrypt 6d3c0fef99 Shape tools: guides take the centre point's pointer too, and more regressions
The Selection tool's cursor now checks the ruler guides before a selected
object's centre point, as a press does. Tests: a ruler guide under the centre
point keeps the pointer and drags; on a box too thin for its centre to clear the
handles the handles take both; a shape-tool move from the centre snaps and shows
the hollow arrow; and Alt pressed mid-drag copies the shape through the engine.
2026-11-07 05:15:04 +07:00
FractalEncrypt c36971afe9 Shape tools: the centre point's cursor comes after the box's handles
The Selection tool's cursor now checks the bounding-box handles before a
selected object's centre point, as a press does (on a thin box the centre lies
within a handle's reach). The shape tools' tests cover a Live Corners widget
that lands on another selected shape's centre (the widget rounds), Alt pressed
mid-drag (the preview copies and the copy cursor shows), and the engine tests
check the copy's place and both gestures' Undo and Redo. The cursor catalog says
which arrow a drag of the selection shows.
2026-11-07 05:15:03 +07:00
FractalEncrypt cd88b15912 Shape tools: review follow-ups (Shift, handle boundary, cursor order, feedback)
- The Selection tool takes a Shift-press on a selected object's centre point
  too: a Shift-drag moves the selection held to 45° steps, and a Shift-click
  there takes the object out of the selection, as a Shift-press on the object
  does.
- One rotate zone for the Selection tool and the shape tools
  (`select::rotate_zone`): a press on a bounding-box handle is not a turn, so
  the shape tools' zone matches the Selection tool's classification exactly.
- The shape tools' hover cursor follows the press order (corner and pie widgets,
  then the centre point, then the rotate zone), and a move of the selection
  shows the Selection tool's feedback: SelectionCopy with Alt, ArrowHollow while
  it snaps.
- Engine-level tests for the whole gesture (`crates/engine/src/tests/shapetools.rs`):
  repeated previews and one undo step, Alt copy, Escape then release, a turn
  about the centre, the corner widget still rounding, and an unfilled shape
  moved by the Selection tool.
- docs: evidence in target-app-parity.md's Shapes bullet and the M2 milestone,
  and the Move cursor's entry names the copy and snap cursors.
2026-11-07 05:15:03 +07:00
FractalEncrypt 1c345d8e6a Shape tools: drawing another shape from the selected one's centre moves it
A press on the centre point of the selected shape now moves the selection, as
asked (#5477), so the Constrain Angle test's repeated draws from the same point
moved the polygon drawn before them. It deselects first: nothing selected, the
press draws.
2026-11-07 05:15:03 +07:00
FractalEncrypt adbcb3a224 Shape tools: the selection moves from its centre point and turns outside a corner
A shape just drawn keeps its bounding box with the Rectangle, Rounded Rectangle,
Ellipse, Polygon and Star tools, but its centre point (Attributes › Show Center)
did nothing and the box's handles only drew: a press anywhere started a new
shape, and with the Selection tool a press on the centre of an unfilled shape
started a marquee.

- The Selection tool now takes a press on a selected object's centre point as
  the start of a move (held by that point, so it snaps and Alt copies), as
  Direct Selection has since #4891. A click there still makes the object the
  key object of a multi-selection.
- The shape tools take it too, and take a press in the rotate zone just outside
  a bounding-box corner (the Selection tool's own zone and cursor) as the start
  of a rotation about the box's centre. Live Corners widgets keep the corners
  themselves, so a press on a corner still rounds it; every other press still
  draws a new shape. Escape cancels, as for the Selection tool's drags.

`hit_center` moves from direct.rs to select.rs, shared by the three tools.
2026-11-07 05:15:02 +07:00
FractalEncrypt 9c879301b4 Transform fields: the Artboard Control bar's link comes after its fields
The Artboard tool's own Control bar drew the W/H Constrain Proportions link
between W and H, so with that tool Tab stopped on it before H, as the object
Control bar did before the last commit. It reserves the link's place and makes
the button after the fields too, and the link now toggles with Space or Enter
when it has the keyboard, as a dialog's button does.

Tests: `one_tab_moves_from_a_field_to_the_next` covers the Artboard Control bar
(W to H in one press), and `the_control_bar_link_toggles_by_click` Tabs from H
to the link and toggles it with Enter.
2026-11-07 05:15:02 +07:00
FractalEncrypt 417039f456 Transform fields: the tab-order test says the link is focusable, after the fields 2026-11-07 05:15:01 +07:00
FractalEncrypt 5ecf7e7dc0 Transform fields: the W/H link stays a tab stop, after the fields
A review found the link lost its keyboard focus when it stopped being a tab
stop. It is focusable again, and the Control bar now keeps the link's place
between W and H but makes the button after the H field, so Tab still goes from
W straight to H and finds the link after it (as the Transform and Properties
panels already draw it). Tests: Shift+Tab goes back one field too, and a click
on the Control bar's link still toggles the preference.

The Blob Brush Tool Options test that expected two Tabs between the Roundness
field and the tip preview now expects one, as every field does. Docs: the Tab
entry in target-app-parity.md, both revision histories and the M1 milestone.
2026-11-07 05:15:01 +07:00
FractalEncrypt 892c0659e5 Transform fields: Tab moves from a field to the next field, one press each
A numeric field's label is a scrub zone laid over the label (a drag scrubs the
field, a click focuses it). It was registered with `Sense::click_and_drag()`,
which is focusable, so every field had a second, invisible tab stop: Tab went
from X to W's label zone and only then to W. The zone is now `CLICK | DRAG`,
not focusable, and the W/H Constrain Proportions link — a button between W and
H in the Control bar — is no longer a tab stop either, so Tab goes from W
straight to H. The preference stays reachable from Preferences › General.

Test: `one_tab_moves_from_a_field_to_the_next` (Transform panel, Properties
panel's Transform section and Control bar).
2026-11-07 05:15:01 +07:00
SofiaAFigueredoandClaude Sonnet 5.5 6173bd404f Tests: give the Home centring test a taller window (#4684)
The Community links can fall below the first scroll viewport at 1000x700
when the fonts differ, so the test measured nothing. 1000x900 keeps every
link visible.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-07 05:15:00 +07:00
SofiaAFigueredoandClaude Sonnet 5.5 9b8b1229b6 UI: centre the Home New / Open buttons and links too (#4684)
The first commit centred the headings but left the New / Open buttons
and the community links on the left, as `horizontal` fills the width.
The buttons' row is now padded by its measured width, and the Home
screen's links are centred (label only, no icon; About keeps its
left-aligned icon rows). The test now measures where the centred text
lies and checks the buttons, the Discord button and the links.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113uEh3VQ6et4zDMZ9cqzkf
2026-11-07 05:15:00 +07:00
SofiaAFigueredoandClaude Sonnet 5.5 3d31b623a4 UI: centre the Home screen and use the accent for the Discord button (#4684)
Part of #4684 (unify the UI with PhotoCraft). The Home screen's welcome,
New / Open buttons and Community block are centred, as PhotoCraft's
Home screen is, and the large Discord button takes the theme's accent
instead of Discord's own colour. Preset cards, Recent Files and the
wording are unchanged.

Still open from #4684: shortcut hints on the Home buttons, PhotoCraft's
PRO and STUDIO themes, the Preferences layout and the title bar.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113uEh3VQ6et4zDMZ9cqzkf
2026-11-07 05:15:00 +07:00
dotCooCoo 77d5f8bbea Type: tests compare opened documents with and without kept shaping plans
A new test binary opens a document of 13 type objects with the plans a
font face keeps and again while every shaped segment makes its own plan.
The document, its cached bounds and baselines included, and the first
frame's pixels must be the same. `test_fonts::keep_shape_plans` switches
the plans off; it exists only in tests and with the `test-fonts` feature.

The engine's open test now builds objects of one script and direction
that differ in kerning and language, and lays each one out in a font
database of its own, so a plan used for the wrong features fails its
comparison. It no longer sets Arabic, which could fall back to a system
font in one database and not the other.
2026-11-07 05:14:59 +07:00
dotCooCoo 25b42b2246 Type: layout keeps each font face's shaping plans
Every shaped segment built its own harfrust shaping plan, although a
plan depends only on the face, direction, script, language and features.
Opening a document lays out each type object once, so a document of many
short objects built thousands of identical plans.

A font face now keeps up to 32 plans, oldest dropped first, and shapes
with the one that matches. Feature ranges still come from the shape
call, so text with the same features over other ranges shares a plan.
The glyphs, bounds and baselines do not change.

Docs: docs/target-app-parity.md.
2026-11-07 05:14:59 +07:00
Don Omondi d16123efad Legacy .ai export: a flattened image's clip follows its edge smoothly (#5264)
The clipping path around a transparent image flattened for a version 7
.ai followed 2 pt cells, a visible staircase. It now follows the rendered
pixels (in blocks only past 4 million), grown by one pixel so the whole
anti-aliased edge is inside, through the middle of each pixel edge and
simplified with Douglas-Peucker, so slopes and curves are sloped lines.
2026-11-07 05:14:58 +07:00
dotCooCoo 6e39148623 .ai import: Soft Mix's Command and Mix rate were seen in saved files
Comments only. A saved Soft Mix holds Command 11 and a Mix of the Mixing
Rate divided by 100, and a saved Mix can fall outside the 0.01 to 1 the
reader takes, which keeps the drawn look.
2026-11-07 05:14:58 +07:00
dotCooCoo 92943095b8 .ai import: the other Effect › Pathfinder operations open as live effects on a group
A group whose style holds Effect › Pathfinder › Intersect, Exclude, Subtract,
Minus Back, Divide, Outline, Trim, Merge, Crop, Hard Mix or Soft Mix now opens
with its members editable and the effect live, as Add already did
(https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/combine-objects-using-pathfinder-effects.html).

The Command numbers 1 to 10 are VectorCraft's own reader rules, learned from
saved files. Soft Mix's number 11, and reading its Mix from 0.01 to 1 as a
Mixing Rate of Mix times 100, are also VectorCraft's own reader rules.

These still open as the drawn look:
- Trap, and the effect on a path, on type, on a layer or on a fill or stroke.
- Any operation whose saved Pathfinder Options (Precision, Remove Redundant
  Points, Divide and Outline Will Remove Unpainted Artwork), ConvertCustom,
  Mix or Trap settings differ from the values the reader accepts, although
  only Soft Mix uses Mix and none of these operations uses the Trap settings.

The import fuzz test now also gives the style every option with any Mix, and
checks that a live Soft Mix's rate is from 1 to 100.
2026-11-07 05:14:58 +07:00
dotCooCoo 5ce8ce486e .ai save: Export All names each document's own active artboard
Export All Open Documents read the active artboard recorded for each
document, which was kept current only for the active one. A document
whose artboard changed in its view while another tab was active wrote
that stale artboard as the file's active one. Before each command the
app now records every open document's active artboard from that
document's own view.
2026-11-07 05:14:57 +07:00
dotCooCoo 5216632e6b .ai save: exports to a path and timestamped copies name the active artboard
A .ai file that File > Export As, its dialog or a control request writes
to a path now names the view's active artboard, as document.export and
document.serialize do. The UI export records the view's artboard and
takes its document from fileio::export_doc, which applies
DocState::active_artboard to the export's copy; document.export and
document.serialize use the same function.

The app now records the view's active artboard before every command,
including the commands the UI handles itself, so a .ai copy from Save a
Timestamped Copy or Export All Open Documents names it too. Before,
these files named the artboard the document opened on, or the first.
The open document is unchanged.
2026-11-07 04:40:02 +07:00
dotCooCoo d45ebe465b .ai save: the editing copy names the active artboard
The editing copy of a .ai or .ait file that VectorCraft saves or exports
now has `%_<n> /Int (CropAreaActive) ,` after the ArtboardArray in its
recorded part, and marks that artboard as the selected one. The app
records its view's active artboard before each command
(DocState::active_artboard; an index past the last artboard is the last,
as in the Artboards panel); without a view, the artboard the document
opened on (Document::opened_artboard) is written, else the first. A .ai
file read from its editing copy opens on that artboard. Before, the copy
named no active artboard and always marked the first as selected.

Docs: docs/mcp.md, docs/file-format-parity.md, the editingData option text.
2026-11-07 00:17:17 +07:00
Don Omondi 4333c58033 Opening a slow file shows a progress window with Cancel and Continue in Background (#5930)
A file that takes longer than 0.4 s to open shows a modal progress window
with its name, a moving bar (reading reports no steps), how many more
files wait, Cancel (file.cancelOpen: the file doesn't open) and Continue
in Background (file.openInBackground). It never opens over another dialog
or progress window.
2026-11-07 00:17:17 +07:00
Hamza 090667fb26 Languages: Bulgarian, with its quote marks and the fonts' Bulgarian forms
Bulgarian was not among the text languages, so Bulgarian text could not
be tagged and fonts drew their Russian-style Cyrillic. Bulgarian (bg)
is now in the language list (Character panel, character styles,
text.setFormat by name or tag, the Hyphenation preference's default
language), with CLDR's quotes, „…“ at either level. Shaped as bg, the
fonts' locl forms for BGR apply: in the bundled Source Sans 3 Italic,
в, д, ж, з, к, л and ю take their Bulgarian forms.

Fixes #5945
2026-11-07 00:17:16 +07:00
Don Omondi fc099e849c Direct Selection: with Snap to Grid, a dragged straight segment lands its anchors on the grid (#3835)
A straight segment dragged with Direct Selection or Group Selection snapped the
point it was pressed at to a grid intersection, leaving both of its anchors off
the grid. With Snap to Grid or Snap to Pixel on, the box round the moved anchors
now lands an edge on a gridline on each axis, as a moved object's does; Shift
keeps the move on its line. A dragged anchor still lands on an intersection.
2026-11-07 00:17:16 +07:00
Don Omondi 591fc9b21e Paste: cells copied from a spreadsheet paste as a table of type over gridlines
Tab-separated rows with the same number of cells (quoted cells too)
become a group of point type, one per cell, in columns and rows over
0.5 pt gridlines; other text pastes as before. Fixes #5296.
2026-11-07 00:17:15 +07:00
dotCooCoo 42fe0bead3 Appearance and Eyedropper: reading an object's paint does not measure its bounds
The Eyedropper, the Live Paint check for paint to sample, the Appearance
panel's Characters chips and the colors the Libraries panel adds from
the selection found the paint behind the Fill or Stroke proxy through
Node::proxy_paint, which measures the object's geometric bounds. They
now read it with Node::proxy_paint_only.

- Eyedropper: the test for an empty fill reads the paint without
  measuring. A sample measures the object only for a gradient or a
  pattern, whose color depends on the point; a solid color gives its
  color at once.
- Live Paint: the check for paint to sample reads the paint without
  measuring.
- Appearance panel: the Characters row's two chips read the paint
  without measuring.
- Libraries: adding a color or a pattern from the selection reads the
  paint without measuring; a gradient is still measured for its
  placement.

The paints and colors are the same as before. On an open path of
200,000 anchors, the Live Paint check and the Eyedropper's sample for a
point inside it take 0.004 ms together instead of 26.0 ms in a release
build.

Docs: docs/target-app-parity.md.
2026-11-07 00:17:15 +07:00
Don Omondi b8ec5f4b23 Type: Alt + numeric-keypad codes type their character on Windows; type.insert takes a code (#5108) 2026-11-07 00:17:15 +07:00
Don Omondi 3f7f743ddb 3D effects: Extrude & Bevel, Inflate and Rotate apply to groups
A 3D effect on a group or layer builds a solid from every path,
compound path and type in it, all turned about one shared axis, as the
public docs describe for groups. Fixes #4506.
2026-11-07 00:17:14 +07:00
Don Omondi 6160d0bd45 Snap to Glyph: glyph bounds give corners and turn with rotated type (#5112) 2026-11-07 00:17:13 +07:00
Hamza 9916857dba Type: pin that a Type-range paste into new point type keeps its formatting
#5929 reports that a styled word copied with the Type tool loses its
formatting when pasted into a new point type object made by a Type
tool click (placeholder text selected): on 0.21.1 the Copy left plain
text on the system clipboard, so the paste could not match it with the
copied runs. #5636 (on main since 0.21.1) publishes the copy through
the clipboard publisher, and the paste keeps the runs. This test pins
that case end to end through the app: copy, two clicks (end editing,
place new type), paste, then font, size and colour of the new text.

Closes #5929
2026-11-07 00:17:12 +07:00
Don Omondi 5019846d08 Perspective grid: a grid dragged off its preset reports no preset name
perspective.grid.get reports define.name only while the grid still has
the preset's fields, as Define Grid's Preset menu shows [Custom]. Part of #5248.
2026-11-07 00:17:12 +07:00
Cursor AgentandAdithya Shankaran 4e11389bf0 Don't write over a library file that changed since it was saved
The name remembered for a user library file stays only while the file still
holds the bytes that were written. A file a device name could make, which can
no longer be read, is kept too, and the save goes to a new file.

Co-authored-by: Adithya Shankaran <adikshan11@users.noreply.github.com>
2026-11-07 00:17:12 +07:00
Adithya Shankaran 850fd620f9 Don't replace an .ase file whose owner isn't known
After a restart an .ase file called COM¹- could have been saved as COM¹ or as
COM¹-, and the same goes for its numbered files. Such a file is kept until it is
saved here, so a library named like that gets a new file, once per run. Other
names go on replacing their own file.
2026-11-06 23:05:29 +07:00
Adithya Shankaran a17efec894 Remember the name a library file was saved under
An .ase file can't say whose it is, so without this COM¹ saved after COM¹- wrote
over it. The app now remembers while it runs. After a restart COM¹ gets a new
file, which costs a copy and loses nothing.
2026-11-06 23:05:29 +07:00
Adithya Shankaran 857ba3972b Don't copy a library on every save when its format keeps no title
An .ase file reads its name from the file name, so COM¹ saved again looked like
another library and got a new numbered file each time.
2026-11-06 23:05:29 +07:00
Don Omondi 6537396ee0 Effects: a Transform effect's copies of a filled and stroked path stack one above another (#5920)
A path whose fills and strokes stack (a fill and a stroke, several of
either, an even-odd fill, or a partly transparent or blended one) drew
its Transform effect's copies as one path, so every copy's stroke lay on
top of every copy's fill. It now draws each copy as an object of its own
above the one before, as Expand Appearance makes them.

Fixes #5920
2026-11-06 23:04:56 +07:00
jayzed80andClaude Opus 5.5 7d465d4fa3 fix(text): a gutter too huge to step past no longer stalls area type layout
Past a gutter of 1e300 a step no longer moves the baseline, so every cell of
a 100 x 100 frame was searched for all MAX_STEPS steps: over 40 s per layout.
AreaOptions::gutter has no limit, so a saved file can do this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 23:04:37 +07:00
Hamza b474525e4b Find Whole Word: a change of style inside a word is not a word boundary
Find and Replace matched each style run on its own, and the whole-word
test looked only at the characters inside the run, so a run's end read
as a word boundary: with cat coloured red in catfish, a Whole Word
search for cat replaced it. The test now looks past the run's ends at
the characters next to it in the neighbouring runs. Matches still stay
inside one run, as the command documents.

Fixes #5901
2026-11-06 23:04:21 +07:00
Hamza d316edaad9 Find and Replace: type inside a locked or hidden group is left alone
all_texts walked every node of a visible, unlocked layer and checked
only each text object's own lock and visibility, so a text object that
was itself shown and unlocked inside a locked or hidden group was found
and replaced. It now stops at a hidden or locked group, as it stops at
a hidden or locked layer: Find and Replace and Find Next with no ids
leave that type alone, however deep the group is.

Fixes #5900
2026-11-06 23:04:06 +07:00
Don Omondi f482c9c243 Drop Shadow: a swatch beside Color opens the Color Picker (#5364)
Squashed from #5918 onto main.
2026-11-06 23:03:56 +07:00
Don Omondi 6725f24a80 Path booleans: check the windings against the operands' areas and resolve the operands when they don't add up (#5478) 2026-11-06 23:03:25 +07:00
Don Omondi f7135db6e9 Drop Shadow and Outer Glow: spinner arrows step Opacity, the offsets, Blur and Darkness (#5367, #5368) 2026-11-06 17:09:24 +07:00
NetterSilverandClaude Opus 5.5 a76e157de7 Keep a symbol's options when pasting it into another document
Fixes #5902

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 17:08:54 +07:00
Adithya Shankaran 0864db9494 Keep the other library when two names share a file name
A library called COM¹ is saved as COM¹-, which can be the file of a library
really called COM¹-, and the numbered file one of them then gets can be asked
for by a third name. Saving one replaced the other. Each now gets a free file
name, and saving it again goes back to that file.
2026-11-06 16:52:05 +07:00
Don Omondi f08381df8d SVG import: filter and mask regions clip what they declare (#5876, #5875)
Squashed from #5913 onto main.
2026-11-06 16:39:04 +07:00
Don Omondi 74b5966bbd Stroke Weight Up and Down: Cmd/Ctrl+Alt+. and , step the selected strokes' weight (#5797) 2026-11-06 16:19:37 +07:00
Don Omondi 41ceaadc7e SVG import: Gaussian blurs keep color-interpolation-filters; Gaussian Blur can blur in linear light (#5877) 2026-11-06 16:05:55 +07:00
Don Omondi dbbe8a9298 PDF and .ai export: objects' URLs become clickable links (#5785)
Each visible object with a URL (Attributes panel) is written as a Link
annotation with a URI action over its bounds on the page, trimmed to its
clipping groups. Hidden objects, non-printing layers and printed sheets get
none; PDF/X files leave them out with a warning. URIs are percent-encoded
to 7-bit ASCII.
2026-11-06 15:48:02 +07:00