Commit Graph
5391 Commits
Author SHA1 Message Date
BFlat fb34817531 Merge pull request #3982 from dotCooCoo/feat/image-trace-preview
Image Trace: a Preview checkbox chooses whether setting changes trace at once
2026-10-24 18:07:22 +07:00
BFlat f924c69f3e Merge pull request #3934 from dotCooCoo/feat/live-object-cache-checked-before-resource-walk
Render: an unchanged live object or effect art is not walked again for its resources
2026-10-24 17:55:17 +07:00
BFlat 7644d11c7b Merge pull request #3962 from dotCooCoo/feat/brush-pieces-kept-per-copy
Render: brush art of symbol instances, inline graphics and repeat copies is kept between frames
2026-10-24 17:55:01 +07:00
BFlat 391febaba2 Merge pull request #3963 from dotCooCoo/feat/inspect-file-info-title
MCP: document.inspect and document.open report the File Info title as fileInfoTitle
2026-10-24 17:54:46 +07:00
BFlat 6b29d3a551 Merge pull request #3971 from dotCooCoo/fix/mcp-file-tools-absolute-paths
MCP: the file tools send paths made absolute against the server's working directory
2026-10-24 17:54:31 +07:00
BFlat c3b7bfb0b9 Merge pull request #3981 from dotCooCoo/fix/mcp-render-matches-png-export
MCP: screenshot and render_preview draw edges as a PNG export does
2026-10-24 17:54:16 +07:00
BFlat 5b2d460e06 Merge pull request #3970 from dotCooCoo/fix/trace-grouping-grid
Image Trace: Auto Grouping looks up containing paths in a grid instead of testing every pair
2026-10-24 17:54:01 +07:00
BFlat d30caeca61 Merge pull request #3972 from dotCooCoo/fix/trace-params-unknown-keys
Image Trace: imageTrace.make refuses unknown keys inside params
2026-10-24 17:53:46 +07:00
BFlat 5fb1d863bb Merge pull request #3942 from dotCooCoo/fix/trace-cut-out-edge
Image Trace: the semi-transparent edge of a cut-out image traces as the color beside it
2026-10-24 17:53:31 +07:00
BFlat 2bc6244ad5 Merge pull request #3947 from dotCooCoo/fix/recolor-colors-without-selection
Recolor: recolor.colors and recolor.reduce read the art Select All takes when nothing is selected
2026-10-24 17:53:16 +07:00
BFlat da3a2f1839 Merge pull request #3943 from dotCooCoo/fix/select-same-matches-compound-paths
Select: Same commands for paint, appearance and shape select compound paths
2026-10-24 17:53:01 +07:00
BFlat d6b1312951 Merge pull request #3949 from dotCooCoo/fix/trace-flat-sliver
Image Trace: a filled traced path that straightens to no area is left out
2026-10-24 17:52:45 +07:00
BFlat 493d3ce2bf Merge pull request #3925 from cmain/fix/print-landscape-spool
Print: landscape pages print upright on macOS print queues
2026-10-24 17:52:30 +07:00
BFlat 149718393b Merge pull request #3959 from dotCooCoo/fix/issue-3799
Fix #3799: Alt held at the press draws a straight Pencil line at any angle
2026-10-24 17:52:14 +07:00
BFlat 1428c3f822 Merge pull request #3966 from Taha-Mahmoodi/fix/outlines-keep-ruby
Fix #3964: Create Outlines, EPS, EMF/WMF and legacy .ai keep the ruby where the canvas draws it
2026-10-24 17:51:59 +07:00
BFlat 0b31a3342b Merge pull request #3979 from dotCooCoo/fix/remembered-artboard-pick
Save: a remembered artboard pick follows the artboards it named
2026-10-24 17:51:44 +07:00
BFlat 7f8334e2fa Merge pull request #3977 from dotCooCoo/fix/outlines-of-threaded-frame-keep-text-once
Threaded text: Create Outlines and Expand on a frame no longer duplicate its text
2026-10-24 17:51:29 +07:00
BFlat cacca3e9aa Merge pull request #3938 from dylanmtaylor/security/review-findings-render
Security: render and PDF export bounds for symbol expansion, dotted guides, blending groups and pinned images
2026-10-24 17:51:13 +07:00
BFlat 04f5fb1df0 Merge pull request #3983 from Silvertree2010/fix/h1946-svg-import-css-parser-ignores-im
Match !important case-insensitively in SVG import CSS parser
2026-10-24 17:50:58 +07:00
BFlat 5adad7888a Merge pull request #3924 from Taha-Mahmoodi/fix/pdf-scan-literal-stream
PDF import: the pre-open stream scan doesn't let a "stream" inside a literal string hide the real stream after it
2026-10-24 17:50:42 +07:00
BFlat 0e9d65b169 Merge pull request #3930 from dylanmtaylor/security/review-findings-misc
Security: linear work for shortcut review, gradients, recovery names, include cycles, batch references, DOCX styles, DXF layouts and long strokes
2026-10-24 17:50:27 +07:00
BFlat abb5b9f773 Merge pull request #3933 from dylanmtaylor/security/review-findings-file-authority
Security: file authority for stored paths, linked files, placed documents, font folders, recovery areas and atomic saves
2026-10-24 17:50:11 +07:00
BFlat fb919479b1 Merge pull request #3936 from dylanmtaylor/security/review-findings-effects
Security: bounded flattening, plug-in budgets and caches, loaded textures, Revolve paint copies, color book and ICC names
2026-10-24 17:49:56 +07:00
BFlat c799240ef5 Merge pull request #3939 from dylanmtaylor/security/review-findings-docmodel
Security: document model budgets for live art, clipping runs, placed documents, graph masks and path reading
2026-10-24 17:49:41 +07:00
dotCooCoo d29790b34e Image Trace: a Preview checkbox chooses whether setting changes trace at once
Every change in the Image Trace panel traced the selected Image Trace
object again at once, and there was no way to adjust several settings
first and trace once.

The panel now has a Preview checkbox before Trace. It is on by default
and kept with the UI preferences; Reset to Default leaves it. With
Preview off, setting changes wait in the panel until Trace applies them
as one undo step, or until Preview is turned on again. When Preview is
turned on with no Image Trace object selected, the object is traced
with the waiting changes when it is selected again. A change of view
keeps the waiting settings, and selecting another Image Trace object
shows that object's own settings. The control channel's ui.set takes
imageTracePreview.
2026-10-24 17:46:12 +07:00
dotCooCoo 381cfea823 Render: an unchanged live object or effect art is not walked again for its resources
To check what it kept for a live object, or for effect art made from
live objects, type or symbols, the renderer walked the object in every
frame to hash the symbols, patterns and placed files it shows, and it
looked up each symbol instance it found by name in the list of every
symbol. At 1000 x 1000 pixels, the walk took most of the frame on a
page of 2,000 envelopes around instances of the last 2,000 of 10,000
symbols: 83 ms a frame when every frame walked them.

The renderer now keeps what the walk found with the node, which it
holds weakly, and with the document's symbols and patterns as the walk
read them. A later frame makes the key from what was kept while the
node and the document's symbols and patterns stay the same, and walks
the node again when any of them changes. A walk that finds a placed
file is not kept. An evaluation that the live cache does not keep, such
as a thumbnail's copy, is no longer walked. The same page now takes
30 ms a frame, with the same pixels.

Docs: docs/target-app-parity.md
2026-10-24 17:29:33 +07:00
dotCooCoo dfbc1ee866 Render: brush art is made again when a symbol or pattern it can show changes
Brush art leaves out the symbol instances and pattern paints whose art
draws a brushed stroke. The brush cache now records whether a brush's art
or the stroke's paint can show a symbol or a pattern, and drops those
entries when the art of the document's symbols or patterns changes, so an
edit that makes a symbol or pattern start or stop drawing a brushed stroke
draws as a new renderer does.

The parity line gives the median and fastest-frame figures of the
benchmark for both documents it names.
2026-10-24 17:29:33 +07:00
dotCooCoo c5d010e7d7 MCP: document.inspect and document.open report the File Info title as fileInfoTitle
The title field of document.inspect and the document.open reply is the
document's tab name, which comes from the file name once the document
is saved to or opened from a file. A client had to call file.info or
document.info to read the File Info title, so reading a saved
document's title from these replies gave the file name instead.

Both replies now add fileInfoTitle, the title file.info reports, and
keep title as it was. docs/mcp.md's File Info section describes both.
2026-10-24 17:29:33 +07:00
dotCooCoo e71bf71376 MCP: the file tools send paths made absolute against the server's working directory
With --connect, open_file, save_file, export and screenshot {path} sent a
relative path to the app unchanged, and the app resolved it against its
own working directory (/ when started from the Finder). A relative path
then named a different file than it does headless, where the server
resolves it against its own working directory.

The server now makes these paths absolute against its working directory
before it sends them, including export's options.path and the path a
screenshot reply reports. Paths inside run_command and command_batch
params go to the app as given. docs/mcp.md describes the rule.
2026-10-24 17:29:33 +07:00
dotCooCoo 58827952b7 MCP: screenshot and render_preview draw edges as a PNG export does
ui.render, which render_preview and screenshot call in the headless
session and in the app, rendered the artboard with the canvas options:
8-bit compositing and edges smoothed shape by shape. A PNG export
composites in floating point and supersamples edges, so the preview
showed edge colors the exported file does not have, most visibly where
shapes meet edge to edge.

ui.render now renders with the options of a PNG export on white, so
its pixels equal the export's at the same scale. render_preview also
no longer enlarges an artboard smaller than max_side: with the default
max_side of 1024, a 612 x 792 page came back as an interpolated
791 x 1024 image, and it now comes back at 612 x 792. docs/mcp.md and
docs/control-protocol.md describe this.
2026-10-24 17:29:33 +07:00
dotCooCoo b049e45b88 Image Trace: Auto Grouping looks up containing paths in a grid instead of testing every pair
Auto Grouping compared the box of every traced path with the box of every
path that can hold something, and with Overlapping it also compared each
grouped path with every path below it. On a 1400 x 1400 px noise image
traced with Limited 8, Overlapping (18,653 paths), parents() made
442,998,298 box tests and took 3.0 s in a release build.

parents() now keeps the holders and, for stacked traces, the paths below in
a grid of about one cell per path. A path's candidates are the holders in the
cell of its box center, smallest first, and the paths below it come from the
cells its box reaches. When a box reaches more cells or entries than there
are paths below it, the old scan is used. The cells grow larger when the
boxes would otherwise be listed in more than 32 cells each on average. The
same trace now takes 142,289 box tests and 0.08 s, and every parent is the
same as before. The 20,000-path limit is unchanged.

The tests compare parents() with the pairwise version on rings, faces and
noise traced Abutting and Overlapping, check that a dense trace takes at most
64 box tests per path while the pairwise version takes more, check that 300
nested discs take no more box tests than the pairwise version, and check
that boxes covering the whole image make the cells larger.
2026-10-24 17:29:33 +07:00
dotCooCoo 5f16fc3639 Image Trace: imageTrace.make refuses unknown keys inside params
imageTrace.make and imageTrace.makeAndExpand checked their own keys, but
not the keys inside `params`. A misspelled setting such as `colours` was
dropped, and the trace ran with the preset's value and no error.

A key inside `params` that is not a trace setting is now an error that
names the nearest setting, as for a command's own keys. Errors from
imageTrace.makeAndExpand, including those for `enhanced` and a missing
swatch library, name that command instead of imageTrace.make.
2026-10-24 17:29:33 +07:00
dotCooCoo bba0618f55 Render: brush art of symbol instances, inline graphics and repeat copies is kept between frames
The renderer kept brush art per node id and stroke. The copies drawn
for symbol instances, inline graphics and repeat copies share their
art's ids, so they replaced each other's brush art and it was made
again in every frame. The pieces were drawn as plain nodes, so brush
art that is a live object, such as an envelope that a scatter brush
places, was evaluated again in every frame, for a path on the page too.

The brush cache now also keys brush art by the stroked path, so each
copy keeps its own. Kept pieces are drawn through the renderer's
caches: their stroke outlines, live-effect geometry and live object
evaluations are kept between frames. Each frame draws what a new
renderer draws. On one thread at 1000 x 1000 pixels, 2,000 paths on
the page whose scatter brush art is an envelope took 400 ms a frame on
main and take 94 ms, and the fastest frames of 2,000 instances of a
symbol whose art is a brushed path went from 16 ms to 12 ms, with the
same pixels.

Docs: docs/target-app-parity.md
2026-10-24 17:29:33 +07:00
dotCooCoo 389bb4d790 Image Trace: the semi-transparent edge of a cut-out image traces as the color beside it
A pixel on the edge of a cut-out image with an alpha of 128 or more was
traced with its own color. When that color was a darkened edge tone, it
took whichever palette color lay nearest, and the edge traced as 1 px
slivers of a color that appears nowhere near it.

In Color and Grayscale, a pixel with an alpha of 128 to 254 next to a
pixel under half opaque now takes a color of the fully opaque pixels
within two pixels of it when its own color is that color or a darker
shade of it, counting only pixels it reaches without crossing a
transparent gap; of several such colors, it takes the one the most of
those pixels have. It keeps its own color when one of those pixels has
it, or when its color is not a shade of any of theirs, so a thin
semi-transparent outline of another color stays as it is. Ties go to the color nearest its own, by its gray
in Grayscale. Opaque images trace as before.
2026-10-24 17:29:32 +07:00
dotCooCoo 7da8edab79 Recolor: recolor.colors and recolor.reduce read the art Select All takes when nothing is selected
recolor.colors and recolor.reduce were unavailable with nothing selected, so
listing a document's colors needed a select.all first, which also changed the
selection. Both now take ids and read the selection when ids is left out.
With nothing selected they read the objects Select All takes and the objects
inside them: hidden and locked objects are left out with what they hold, also
inside a group, as are the objects of hidden, locked and template layers, and
in isolation mode only the isolated art is read. The selection is left
unchanged. An object named in ids or selected is read with everything it
holds, and an object listed twice, or listed together with a group that holds
it, counts once. recolor.apply still recolors only the selection.
2026-10-24 17:29:32 +07:00
dotCooCoo 500c84e2c7 Select: Same command descriptions state the compound-path rule
The params descriptions of Same Fill Color, Stroke Color, Fill & Stroke,
Graphic Style and Appearance Attribute say that a member path of a
compound path selected first compares as its compound path, except in
isolation mode inside that compound path.
2026-10-24 17:29:32 +07:00
dotCooCoo 5e8d41fb8f Image Trace: a filled traced path that straightens to no area is left out
Fitting straightens the pieces of an outline that lie close to a line, and
Snap Curves To Lines straightens more of them. The outline of a single
pixel, or of a region one pixel thick along an edge such as an opaque row
of a mid tone under a light shape, can then have all its pieces on one
line. The trace held a selectable path with no area, for example a path
140 x 0 px with Snap Curves To Lines.

In a Color, Grayscale or Black and White trace with Fills, a fitted
outline or hole whose area is below 1e-6 px^2 is now left out. A path
with no area paints nothing, so the traced result paints the same pixels
as before. Without Fills, the 1 px stroke that outlines each area draws
such a loop, so it is kept. A single closed loop keeps its area as
before.
2026-10-24 17:29:32 +07:00
Chris MainandClaude Opus 5.5 61504c84fb Print: turn only landscape paper, and only for macOS queues
Review of #3925: the spooled job turned every sheet wider than tall, so
custom paper that is wide in Portrait (a 4 × 2 in label) went to the queue
tall, and a Portrait + Transverse page was turned back. Only the sheets
whose paper the layout turned to landscape (not transverse) are written
upright now.

The desktop print service asked for upright pages on every platform, but
craft-print already turns each sheet to its own orientation on Windows:
only macOS asks for them now; Linux keeps main's behaviour.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 17:29:32 +07:00
dotCooCoo fea8c084e8 Pencil: the closing cursor's texts say Alt is pressed after the drag starts
The Pencil and PencilClose entries in cursors.toml, the PencilClose doc
comment and the closing-cursor line in docs/target-app-parity.md said Alt
held while drawing closes the path. A drag started with Shift or Alt
held draws an open straight line and does not show the closing cursor,
so the texts now say Alt is pressed after the drag starts and name that
exception.
2026-10-24 17:29:32 +07:00
Taha-Mahmoodi 23c0e8c501 Fix #3964: Create Outlines stacks each run's ruby right after that run's glyphs, as the canvas paints it 2026-10-24 17:29:32 +07:00
bflatastic 7f8bc837bd Save options test: take the file name after either path separator (Windows) 2026-10-24 17:29:32 +07:00
dotCooCoo a272d49ea7 Select: Same commands for paint, appearance and shape select compound paths
Select > Same > Fill Color, Stroke Color, Stroke Weight, Fill & Stroke,
Opacity, Blending Mode, Appearance and Shape skipped containers, so they
never selected a compound path, even when the compound path was the
object selected first. With a member path of a compound path selected
first, these commands and Same Graphic Style and Appearance Attribute
compared the other objects with the member, which has no paint or style
of its own. Graphic Style then reported that the selection had no
graphic style, and Appearance Attribute that the object had no fill,
stroke or effect.

The eight commands now match a compound path on its own paint and
attributes, as select.color does, and skip its member paths. When a
member path is selected first, all ten commands compare the other
objects with its compound path. In isolation mode inside a compound
path, the member paths are matched as before.
2026-10-24 17:29:32 +07:00
Chris MainandClaude Opus 5.5 41430cc171 Print: landscape pages print upright on macOS print queues
A landscape print job was a page wider than tall, and `lp` on macOS puts
each page on the paper as it is: on upright Letter it printed unturned,
cut off on the right (checked with cupsfilter through an AirPrint
driver). Saving the PDF and printing it from Preview worked because
Preview turns the page.

`file.print` takes `forPrinter`: a page wider than tall is written
upright, the art turned a quarter anticlockwise as a printer driver turns
a landscape job. Page by page, so auto-rotated jobs that mix wide and tall
artboards print right too. The desktop print service asks for it
(`PrintService::turns_landscape`); saved PDFs, PostScript, the preview and
the browser's print dialog keep landscape pages as they are.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 17:29:32 +07:00
dotCooCoo 4c5c54ccbc Fix #3799: Alt held at the press draws a straight Pencil line at any angle
With Alt key toggles to Smooth Tool off, holding Alt when a Pencil drag
starts now draws one straight segment from the press point to the
pointer, at the angle the pointer takes, as Shift does at 45° steps.
Pressed on an end of a selected open path, the line continues the path
from that end. Both ends snap to Smart Guides as a stroke's ends do, and
the line stays open when Alt is still held at the release. Shift and Alt
together keep the 45° steps.

Before, a drag started and released with Alt held drew a closed freehand
path. Alt pressed after the drag has started still closes the path at
the release, and with Alt key toggles to Smooth Tool on, Alt still switches
to the Smooth tool. Before a Pencil press, Alt no longer shows the closing
cursor.

Docs: docs/target-app-parity.md, docs/ui-parity.md.
2026-10-24 17:29:32 +07:00
Taha-Mahmoodi cab62cbb18 Fix #3964: Create Outlines, EPS, EMF/WMF and legacy .ai keep the ruby where the canvas draws it 2026-10-24 17:29:32 +07:00
dotCooCoo 11c61c97b1 Save: a remembered artboard pick follows the artboards it named
A save without options reuses the options the document was last saved
with in that format. Their artboard, artboards and range name artboards
by number, so once artboards were deleted or moved, the save wrote
other artboards or was refused: with three artboards, a PDF saved with
range "2" wrote the third artboard after the first was deleted, and one
saved with range "1, 3" was refused after the third was deleted. The
same held for SVG saves, a legacy .ai, and .ai and native files saved
with separateArtboards. file.formatOptions and SVG Options showed the
same numbers.

The document now keeps the ids of the artboards it had when it was
saved, and the remembered pick follows them: an artboard deleted since
is left out, one that moved is named by its new number, and the rest of
the pick is kept. A range open at its end ("2-") is written out as the
artboards it named once artboards are added. When none of the artboards
the save used is left, the pick is left out, and the save writes the
artboards it writes when none is named. A range of "all" stays as it
is. An artboard given to the save is still refused when the document
does not have it.

A new artboard no longer takes the id of an artboard deleted earlier in
the same document (Document::retire_artboard_id), or of one an undo took
away: the highest id given out is shared with the undo history, so a
new artboard is not taken for an earlier one.

file.revert keeps the pick only when the file read back has the
artboards the document had at the save, with the same ids in the same
order. A PDF saved with a range holds only those pages, and one saved
without preserveEditing reads back with new ids in page order, so their
picks are left out.
2026-10-24 17:20:48 +07:00
dotCooCoo 3e0f9795c2 Threaded text: Create Outlines and Expand on a frame no longer duplicate its text
Create Outlines on a frame of threaded text removed the frame, and the
thread's re-flow then added the frame's text to the story, as it does
for a frame that was deleted. The other frames held that text a second
time while the outlines drew it. Object › Expand, and Expand Appearance
where it outlines type or groups it with a shadow, outer glow or Crop
Marks, did the same.

An outlined frame now leaves its thread with the text it shows, and the
other frames keep the text they hold. When the thread's last frame is
outlined, the text it has no room for goes after the text of the last
frame that stays. When Expand Appearance keeps a frame as type in a
group, the type takes the frame's place in the thread. A thread left
with one frame is no longer a thread, and undo restores the thread.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-24 17:20:47 +07:00
bflatastic 285790d685 Security bounds: fix on landing #3938 (see the PR comment) 2026-10-24 17:20:03 +07:00
NetterSilverandClaude Haiku 5.5 f8ad2edad8 Match !important case-insensitively in SVG import CSS parser
Found while testing crates/svg/src/import/css.rs:25

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 17:19:59 +07:00
Taha-Mahmoodi d3a01307ea PDF import: a string entered from many dictionaries in skipped text is read once
Review of #3924: in a flat area of ` <<(` (CMYK or RGBA 32, 60, 60, 40) in an uncompressed image, each `<<` after white space was read as a dictionary, and each read its string, which never ends, to 64 KiB past it: about 16 K bytes a pixel, so a few hundred bytes of it spent the budget and the file was refused.

Whether a byte in a literal string is escaped doesn't depend on where reading began (the run of `\` before it lies after any `(` before it), so the string from any `(` ends after the first `)` that takes the file-wide count of `(` less `)` below the count at that `(`. Those ends are found for the whole file in three linear passes the first time a string read in skipped text runs past 64 bytes: forward for the final count, back for the `(` whose string never ends, forward pairing the rest. Ends further than 64 bytes are kept (at most one for every 16 bytes of the file; a file built to nest past that is refused); nearer ones are read again. So entering a string costs at most 64 bytes, charged as one. A comment's end of line is kept for every comment read, not only the last.
2026-10-24 17:19:59 +07:00