Commit Graph
10785 Commits
Author SHA1 Message Date
BFlat 3bceade119 Merge pull request #5951 from dotCooCoo/fix/ai-active-artboard
.ai save: the editing copy names the active artboard
2026-11-07 09:05:42 +08:00
BFlat 76eca8e98e Merge pull request #5950 from mockydevs/feat/progress-indicator-5930
Opening a slow file shows a progress window with Cancel and Continue in Background
2026-11-07 09:05:27 +08:00
BFlat b497793902 Merge pull request #5948 from Hamza0101/feat/bulgarian-text-language
Languages: Bulgarian, with its quote marks and the fonts' Bulgarian forms (#5945)
2026-11-07 09:05:15 +08:00
BFlat 98c6e8ab5d Merge pull request #5947 from mockydevs/fix/grid-snap-anchors
Direct Selection: with Snap to Grid, a dragged straight segment lands its anchors on the grid
2026-11-07 09:04:57 +08:00
BFlat 0ebce55584 Merge pull request #5942 from mockydevs/feat/paste-spreadsheet-table
Paste: cells copied from a spreadsheet paste as a table of type over gridlines
2026-11-07 09:04:38 +08:00
BFlat d79c3574af Merge pull request #5940 from dotCooCoo/fix/paint-without-bounds
Appearance and Eyedropper: reading an object's paint does not measure its bounds
2026-11-07 09:04:16 +08:00
BFlat af4f590b6b Merge pull request #5939 from mockydevs/feat/alt-numpad-codes-5108
Type: Alt + numeric-keypad codes (Alt+0176 → °) type their character on Windows; type.insert takes a code
2026-11-07 09:03:59 +08:00
BFlat 84fc4b74ac Merge pull request #5938 from mockydevs/feat/3d-effects-on-groups
3D effects: Extrude & Bevel, Inflate and Rotate apply to groups, turned about one shared axis
2026-11-07 09:03:47 +08:00
BFlat e688fbea0e Merge pull request #5937 from mockydevs/feat/glyph-bounds-corners-5112
Snap to Glyph: each glyph's bounding-box corners are snap points, and the box turns with rotated type
2026-11-07 09:03:27 +08:00
BFlat bc57bdaee3 Merge pull request #5932 from Hamza0101/fix/paste-into-new-type-keeps-formatting
Type: pin that a Type-range paste into new point type keeps its formatting (#5929)
2026-11-07 09:03:09 +08:00
BFlat 8029748283 Merge pull request #5931 from mockydevs/fix/perspective-plane-offsets
Perspective grid: a grid dragged off its preset reports no preset name
2026-11-07 09:02:47 +08:00
BFlat c14ec164a2 Merge pull request #5928 from adikshan11/fix/library-untitled-format-copies
Don't copy a library on every save when its format keeps no title
2026-11-07 09:02:27 +08:00
BFlat d074756192 Merge pull request #5927 from mockydevs/fix/transform-effect-copies-5920
Effects: a Transform effect's copies of a filled and stroked path stack one above another
2026-11-07 09:02:10 +08:00
BFlat 0f0ded95b4 Merge pull request #5924 from jayzed80/fix/text-huge-gutter-stall
fix(text): a gutter too huge to step past no longer stalls area type layout
2026-11-07 09:01:34 +08:00
BFlat 145a10c2db Merge pull request #5922 from Hamza0101/fix/find-whole-word-across-runs
Find Whole Word: a change of style inside a word is not a word boundary (#5901)
2026-11-07 09:01:14 +08:00
BFlat 9ef87c19e5 Merge pull request #5921 from Hamza0101/fix/find-replace-respects-group-lock
Find and Replace: type inside a locked or hidden group is left alone (#5900)
2026-11-07 09:00:51 +08:00
BFlat 4166d31a54 Merge pull request #5918 from mockydevs/feat/drop-shadow-colour-picker
Drop Shadow: a swatch beside Color opens the Color Picker (#5364)
2026-11-07 09:00:28 +08:00
BFlat c0a40216ce Merge pull request #5917 from mockydevs/fix/boolean-area-check
Path booleans: check the windings against the operands' areas and resolve the operands when they don't add up (#5478)
2026-11-07 08:59:49 +08:00
bflatastic 53b40161f3 Docs: record kept selection marks in the parity status 2026-11-07 07:36:26 +07:00
bflatastic ccdccb1299 Tests: an image next to type keeps its knockout shape flag 2026-11-07 07:36:26 +07:00
dotCooCoo d323716068 Links: a link check whose worker ends without a result no longer stops later checks
When the link-watch worker unwound before storing its stamps,
poll_link_scan returned None on every frame and left link_scan set. The
app then asked for a repaint every 100 ms and start_link_scan returned
early, so the app did not look for changed linked files in the
background again for the rest of the session.

poll_link_scan now clears a look that has ended without stamps. The new
ended_without_stamps helper holds the slot's lock across its read of the
stamps and its read of the ended flag, so a look whose worker stores its
stamps during the check is applied. The app goes back to its two-second
wake, and the next look starts on schedule. A look still running and a
look that stored its stamps behave as before.
2026-11-07 07:36:26 +07:00
Don Omondi 36646863fc .ai import: Art brushes in the older form of their objects come into the Brushes panel
Art brush objects whose first number is 1, as older files write them, have one
field fewer before their numbers and 8 numbers at the end. The reader wanted
exactly 15 and left every such brush out with a note, though an Art brush is
read from its art's name alone. Scatter brushes still need all 15 numbers.
2026-11-06 19:12:35 -05:00
Don Omondi 4c791f0f81 EPS import: gradients drawn as abutting strips come in as one gradient
A page that paints a gradient as a row of abutting one-colour strips (as
Level 2 writers draw smooth shades) imported as hundreds of separate
fills. Each anti-aliased on its own, they left seams where the art below
showed through every shared edge (PDF 32000-1 §10.6.4, conflation): thin
stripes, and a pale tint over strips a pixel or two wide. An Illustrator
EPS whose page drew its gradients that way then differed from its layers
and opened as its page with empty layers.

A run of at least six such strips under the same clips, side by side in
one direction with no gap, each a small colour step from the last, now
becomes one object filled with a linear gradient through the strips'
colours (stops on a straight colour ramp dropped), and the clip path
itself when it was the clip's only art. Abutting blocks of distinct
colours stay apart.
2026-11-06 17:58:45 -05:00
Jared SmithandCursor 3617987e35 #5980: macOS save sheet uses alloc/init instead of +savePanel
+[NSSavePanel savePanel] returned nil inside VectorCraft on macOS 15.7 and
objc2 panicked; build the panel with init (and Option fallbacks) so Save As
shows again, and do not hand a refused panel to rfd.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-06 15:18:21 -07:00
dotCooCoo 0be78766f4 .ai import: a compound of clipping paths keeps their knockout shape flag
Clipping paths in a compound path that holds a drawn look become one
compound path. It took the paths' shared stroke, opacity and blend mode
but dropped the knockout shape flag. It now has the flag when every
clipping path has it.
2026-11-07 05:15:19 +07:00
dotCooCoo a9b49f716b .ai import: images, meshes and blends keep the knockout shape flag
An `Xy` whose shape operand is set now reaches an image (embedded or
linked) and a gradient mesh when it is written before the object, and a
mesh or blend when it is written after the object's end. A live blend
takes the flag from its drawn steps. Before, the reader dropped the flag
on these objects, so an image saved with Opacity & Mask Define Knockout
Shape lost it on reopening, with no warning.

A styled object takes the flag written on its drawn look. For a text
object, the flag goes to the object when the look draws only its type,
and otherwise to the group that holds the look and the type. An opaque
look that draws more than the type and sets the flag now goes into that
group as well.
2026-11-07 05:15:19 +07:00
Kyle Kanouse 86564528d3 SVG: centred and right-aligned type on a path keeps its place
Squashed from #5977 onto main.
2026-11-07 05:15:17 +07:00
ruchirguitar 5bb21cdddf Add hyphenation for eleven Indian languages
Squashed from #5946 onto main.
2026-11-07 05:15:17 +07:00
dotCooCoo 7be6f8731c .ai save: check a frame's segment count before building its path
A frame or path past the segments the reader reads is rejected from its count, before it is copied and converted.
2026-11-07 05:15:17 +07:00
dotCooCoo 9006177033 .ai save: test a thread with a frame in a clipping group
A thread with a frame in a clipping group whose clipping object is a path stays type and comes back as one thread.
2026-11-07 05:15:16 +07:00
dotCooCoo 63dbb0f0dd .ai save: an open frame whose ends meet is written as outlines
Area type whose open frame ends where it starts was written as text and
read back in a closed frame. It is now written as its outlines, alone or
as a frame of a thread.
2026-11-07 05:15:16 +07:00
dotCooCoo 0c5ad8e242 .ai save: objects next to type keep their own transparency
The reader gives an Xy right after a compound path or a text object to
that object. The writer now writes XR before the Xy of type, images and
guides, so type next to type, a compound path before type, image or
guide, and type before an image or guide keep their own opacity, blend
mode and knockout shape flag.

- Type keeps its Isolate Blending and Knockout Group state in a state
  block after its text object, as its outlines did on their group.
- The reader no longer gives a layer the Xy before its last text object.
- The ASCII85 end marker of the text document stays on one line.
- The warning and docs/file-format-parity.md also list area type in an
  open frame, type on an open path whose ends meet, type of only white
  space and threads of type on a path as written as outlines.
- README.md, ROADMAP.md, docs/gaps.md, docs/roadmap.md and
  docs/target-app-parity.md describe the editing copy a saved .ai holds.
- clip_path's doc comment is back on clip_path.
- import_fuzz reaches threaded area type and vertical type.
2026-11-07 05:15:16 +07:00
dotCooCoo 7e22d3816a Tests: the pan test also pans with the Selection tool
a_pan_moves_the_marks_without_building_them covered Direct Selection
only. It now runs with both tools.

The module comment and MAX_PAN's comment now say what is kept as
triangles and as document points, and when a pan builds the marks again.
A test label now says points rather than px.

The timings in docs/target-app-parity.md are measured again with 10 fill
colors on the 2,000 circles. The pixel sentence there also names the
rounding errors of kept vertices.
2026-11-07 05:15:15 +07:00
dotCooCoo aed9898b8d .ai save: the editing copy keeps type as type
The editing copy of a PDF-compatible .ai wrote every text object as its
outlines. Point type, area type and type on a path are now written as
text: a text object in the layers that names its story, and the text
document after the document data (%AI11_BeginTextDocument) holding the
stories.

- Each story holds its characters (a paragraph ends in \r, a forced line
  break is \x03), its character runs (font, size, leading, tracking,
  baseline shift, horizontal and vertical scale, solid fill and stroke
  with its weight, capitals, Tate-chu-yoko) and its paragraph runs
  (alignment, indents, space before and after, hyphenation, right-to-left
  direction).
- Point type keeps its point and, when it is turned, its matrix. Type
  scaled by one factor, such as all type on a large canvas, has its scale
  in its sizes, frame and path. Area type keeps its frame, rows,
  columns, gutters and first baseline, type on a path its path and where
  it starts and ends, and vertical type its orientation. A threaded story
  is one story through its frames in order. Area type and type on a path
  carry their frame's path in the layers as well.
- Type the text document can't hold is written as its outlines, with a
  warning: type in opacity masks, pattern tiles and art cut to a clipping
  object, mirrored type, turned or unevenly scaled area type and type on
  a path, frames of several parts, area type that flows around a text wrap,
  threads whose frames differ in orientation or include a frame written
  as outlines, type and stories past the sizes the reader reads, and type
  with inline graphics, ruby, gradient or pattern paint or an appearance
  or opacity mask of its own. Other character and paragraph attributes are left out, with
  a warning.

Tracking is written as a whole number of thousandths of an em, and
solid paints as simple paints; tracking that isn't a whole number is
rounded, with the warning.

Each construct is written in the form VectorCraft's .ai reader reads
(crates/eps/src/import/ate.rs at 36ef34dfdd: Texts::read :407, story
:501, frame :456, para :562, style :588, paint :622, AreaFrame::read
:284; the text object in import/ai/read/art.rs:15 and its path in
import/ai/frames.rs:26), and the tests read the written data back
through that reader. The scripting
reference documents the same attributes on the reading side (text frame
kinds, columnCount, columnGutter, rowCount, startTValue, endTValue,
orientation, and the character attributes):
https://web.archive.org/web/20160410101607/https://wwwimages.adobe.com/content/dam/Adobe/en/devnet/illustrator/sdk/CC2014/Illustrator%20Scripting%20Reference%20-%20JavaScript.pdf

The reader now closes a text object's frame path painted with a closing
operator (n, f, s, b, h), so area type's frame comes back closed; places
point type whose story keeps no composed lines at its frame's point;
keeps a story's last empty paragraph (only the \r that ends the story is
dropped); gives type made from the text document its text object's lock,
as it gives it its visibility; and gives type its text object's knockout
shape flag, as it gives it its opacity and blend mode.

Tests: the round trip of every kind of type and its state, a second
write of what is read back giving the same bytes, the text document and
stubs, scaled point type, a story split between threaded frames, the
fallbacks and the warnings, and the engine's editing copy with every save
option; a fuzz case for damaged text documents and stubs.

Docs: docs/file-format-parity.md.
2026-11-07 05:15:15 +07:00
dotCooCoo ae69e5d1e1 Docs: selection marks timings measured on the final build
The progress line gives the release-build timings from the head that places marks by document point.
2026-11-07 05:15:14 +07:00
dotCooCoo 6c1f426dd0 Canvas: selection marks are placed by the same sum as the rest of the canvas
Kept squares and handles hold document points, and each frame maps them from the view's center as Xf::to_screen does. A pan's distance comes from the difference of the two view centers.
2026-11-07 05:15:14 +07:00
dotCooCoo dcc1b2158c Tests: canvas frame helper reads the selection's marks as shapes
A frame that repeats a view now draws the marks as one mesh. frame_output drops the kept marks first, so the tests that read outlines and anchors from the frame's shapes keep working.
2026-11-07 05:15:14 +07:00
dotCooCoo 480bed78c2 Docs: selection marks timings measured on the final build
The progress line gives the release-build timings of the head that tests each subpath and handle against the view, and says which corner can differ after a pan.
2026-11-07 05:15:13 +07:00
dotCooCoo 94741cdae1 Canvas: selection subpaths and handles out of view add no triangles to a frame
Each subpath and each direction handle is now tested against the view on its own, and gets its triangles the first time it is in view. A group or compound path with one subpath in view, or a long path zoomed in with a few of its handles in view, draws only those.
2026-11-07 05:15:13 +07:00
Sven Lafebre a83a0054ea Part of #5964: stroke gradients cover an arrowhead to the end of its reach
With ribs only at the path's corners (an Along gradient), the ribs that reach
across an arrowhead stopped at the last corner inside its reach, so the slice
narrowed from the head's tip towards the next corner and left the corners of
a Triangle head's base outside every slice. Where each arrowhead's reach ends
now gets a rib of its own.
2026-11-07 05:15:12 +07:00
dotCooCoo 18015f3631 Type: a selected story stays selected when its text leaves the edited frame
When a frame threaded in before the edited frame takes all of the story's
text, the edited frame is left with no text while the story stays
selected. The other frames' text is now highlighted, Delete and Backspace
replace the story, an Alt+arrow step changes the tracking of every frame,
and Copy and Cut take the story. A story whose frames hold no text has
nothing to copy, and Delete in it records no undo step.
2026-11-07 05:15:12 +07:00
dotCooCoo 4a5ff880ff Canvas: selection outlines and anchors are reused between frames
The canvas flattened every selected object's outline and tessellated its
anchors and handles again in each frame. It now keeps them per object as
one mesh, valid for the object's node, layer color and selected anchors.
An idle frame draws the mesh again. A pan moves kept outlines and handles
and places the anchor squares again.

A frame with a new zoom, rotation or display scale draws plain shapes and
keeps nothing, and the next frame at that view builds the marks. Entries
of objects that leave the selection or the document are dropped.
2026-11-07 05:15:12 +07:00
dotCooCoo 22bb2ac8a6 Type: a selected story's highlight and typing follow its current thread
With a whole story selected, the highlight and typing read the frames saved
at Select All, while a pick, a fill and the other story-wide edits read the
document's thread. A frame added by text.thread.add took a pick without being
highlighted, and typing left its text in place.

Both now read the thread through Document::story_of. A frame threaded in
during the selection is highlighted and takes the edit, and a frame that
leaves the thread is not highlighted and keeps its text.
2026-11-07 05:15:11 +07:00
dotCooCoo c969b7857d Type: an Eyedropper pick on a selected story styles every frame
appearance.copyFrom and a fill or stroke change on a story selected by
Select All no longer skip locked frames, hidden frames and frames on a
locked artboard. They apply to every frame of the story, as the Character
panel's story edits do. appearance.copyFrom still leaves out a frame that
is its source.
2026-11-07 05:15:11 +07:00
Sven Lafebre 1b62f86b8c Fix #5714: doc comments on the width profile tests 2026-11-07 05:15:10 +07:00
dotCooCoo f534b0061a Type: switching to the Eyedropper keeps a whole-story selection
With a threaded story selected by Select All, switching to the Eyedropper kept
only the edited frame's selection, so a pick styled one frame. The selection now
carries the story's frames, a pick or Shift-click applies to the text of every
frame as one undo step, and switching back to a Type tool restores the story
selection. A selection inside one frame and unthreaded type behave as before.

A pick and a fill or stroke change on a selected story leave locked and hidden
frames and frames on a locked artboard as they are. appearance.copyFrom leaves
out a frame that is its source.

Typing over a selected story no longer removes the text of a frame whose thread
was removed after Select All.
2026-11-07 05:15:10 +07:00
Sven Lafebre d208f5da64 Contributors: Sven Lafebre's name 2026-11-07 05:15:10 +07:00
dotCooCoo 0c570dee87 .ai import: paths with an Art brush open with the brush on their stroke
A path with an Art brush comes in as the path, with the brush on its
stroke at its weight, instead of as its drawn look, when the brush
stretches art of solid-filled paths along the path, unflipped, at 100% in
Tints. The art runs on the side of the path the file draws it.

The drawn look stays for other colorizations and widths; Proportional,
between-guides and flipped brushes; type; a path with settings of its own,
a variable width, effects, a swatch stroke or an entry that isn't read;
and art with strokes, gradients, patterns, masks or effects, or no length.

A brushed stroke, Calligraphic ones too, takes the brush the Brushes panel
keeps under its name, and an image with a brush keeps its drawn look.

In every document, Tints and Tints and Shades take art in black ink alone
by its ink amount, and a process or gray stroke color's tints and shades
stay in that color's model.

Place and Expand bring the brushes the art uses, renaming on conflict.

Part of #637.
2026-11-07 05:15:09 +07:00
Sven Lafebre 9da56717a0 Fix #5714: widths run smoothly between width points
The width between two width points was a straight line, so a variable-width
stroke turned a corner at every width point.

- Each side of a width profile now follows a monotone cubic (Fritsch-Carlson)
  through its points: no kink at a point, flat at a point wider or narrower
  than both neighbours, never past either point of a span. Points on one line
  stay on it and two-point tapers are unchanged; stacked points still step.
- widest() counts each span's wider left and wider right, since the two sides
  needn't peak together.
- The outliner samples straight runs between width points closely enough to
  follow the curve to the drawing tolerance (at most 64 samples per span and
  4,096 per piece); straight spans get none.
- Tests that expected straight-line widths now expect the curve. The stroke
  gradient coverage test uses Teardrop instead of Wave until #5964 is fixed.
2026-11-07 05:15:09 +07:00
dotCooCoo ab3ac3d429 .ai import: envelopes map from their stroked box when the file saves its size
When Distort Appearance is on and the size a `.ai` saves for an
envelope's contents (`AIDeformArtWidthKey`, `AIDeformArtHeightKey`)
matches the contents' bounds with strokes, the reader now maps the
contents from that stroked box. It used to map them from the geometric
bounds, half a stroke inside that box, so an envelope of stroked art
could differ from its drawn art and keep the drawn art with a note.

Envelope Options hold the box's margins as `boundsMargin`, shares of the
geometric bounds at the top, bottom, left and right. Native files write
them only when they are not zero, and `object.envelope.info` reports
them then. Leaving Edit Contents measures them again when the strokes
reach differently than when Edit Contents began, and the node holds
what they measured then until it leaves. New envelopes have no
margins, so their results do not change. Contents with no width or no
height, such as one straight line, get no margins.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-07 05:15:08 +07:00