The web app's download callback returned nothing, so a save whose Blob, URL or
anchor couldn't be made was reported as written: the document counted as
saved, its recovery copy was dropped, and Save Changes closed it. The callback
now returns the error, which fails the save, export or file command like a
refused write on the desktop. A timer that can't be set after the download
started is not a failure.
The canvas faded a template layer as a whole by a fixed half and ignored its
Dim Images option, so unchecking it or setting another percentage changed
nothing, and the layer's paths were faded with its images. A template layer's
images are now dimmed to its Dim Images percentage (50 % when Layer Options
makes it a template), shown at full strength with the option off, and its
vector art is drawn as it is.
A tile was kept while the pattern's own art, tile and tiling were the ones it
was drawn from. Art that shows a symbol, is painted with another pattern or
holds an image or type doesn't change when those do, so the pattern kept
drawing the old symbol (in every renderer on the thread: the tiles are
shared). Such a tile now also remembers the document's pattern and symbol
art, image pixels and fonts, and is drawn again when they differ. A raster
effect on an object painted with a pattern reads all of that too, as one on
a symbol instance does.
Extrude & Bevel, Inflate and Rotate built their solid from the outline by
nesting alone: a contour inside an odd number of others was a hole. Under the
nonzero rule a contour inside another that runs the same way encloses filled
art, so the effect cut a hole the object doesn't have. The fill rule now
reaches the outline builder, which leaves out contours with filled art on
both sides before pairing outlines and holes.
Each Transform effect clamps its own copies to 1000, but each copies all of
what the effect before it made: three of them on one square ask for a
thousand million squares, from a small file. Copies now stop where the result
would pass two million anchors (1000 copies of a 2000-anchor path).
The fallback for a drop shadow, outer glow or inner glow whose raster the
shadow cache refuses rendered the view plus the blur's spread at full
resolution, capped only at 65535 pixels a side: a 1000 pt blur at 3200 %
spreads 48000 pixels, so the surface was up to 16 GB whatever the window's
size. The region is now planned by filter_region, which renders it at a lower
resolution (as filtered content already is) to stay within PIXEL_BUDGET.
Where a folder refuses the temporary file or the rename, the new content
(whole and flushed elsewhere) is copied into the file itself: the file was
truncated first, so a copy cut short by a lost connection or a crash left
neither version in place, and only the new one on disk under a hidden name.
The file as it was is now copied and flushed beside the temporary file before
it is truncated; if it can't be, the file is left alone and the save fails,
naming the temporary file. A failed copy names both.
The limits on a compressed file's entries were on their lengths. An entry of
one byte at a far offset unpacked everything before it, and each entry behind
the previous one started the stream over and unpacked the JSON and the gap
again: a small hand-made file could ask for hours of decompression. All of it
is now counted against one budget (the entries a writer can make, and the
JSON once more), past which the file is refused as too big.
Each pixel layer was held to 64 megapixels, and the archive's limits count
the bytes extracted from it; a layer whose tiles are flat colour needs none,
so a small file with many of them allocated 256 MB each without end. The
layers of one document now decode 512 megapixels (2 GB) at most together;
those past it are left out with a note, as an oversized one is.
Composite objects are reference-counted, so `/a 1 array def a 0 a put` (or two
dictionaries naming each other) outlived the import: emptying the standard
dictionaries and the stacks doesn't break a cycle, least of all one the
program no longer reaches. Up to the interpreter's whole memory budget could
stay allocated per imported file. The interpreter now lists the arrays and
dictionaries it makes (weakly, swept as they are dropped) and empties the
ones still alive when it is done.
A `z` is four bytes for one character, so the text's length didn't bound
the result, and the filter's and the interpreter's budgets only saw it once
it was whole. The decoder now stops at 256 MB, the most one filter or data
section may hold.
The decoder's 512 MB limit is on the file's samples; a grey image within it
is four times that once converted to RGBA for the Eyedropper or a recolour,
which the web build can't allocate. The size is now read from the header and
held to the allowance the importers and the renderer use.
The worker answered "timeout" after 60 s and left the request on the queue, so
the app ran it whenever it next drained the queue: an edit nobody was waiting
for, and a second one if the caller had retried. A request now leaves the
queue once: taken by the app, or given up by its caller, whichever comes
first. The timeout reply says which ("will not run" / "still running").
The rollback undid the batch's interaction, which holds the art, the selection
and panel state only: a Save As step's new file, format and saved state stayed,
and an Undo step (which ends the interaction) left the art and the history
undone. The batch now keeps the documents as they were and puts them back,
and settles a drag or typing in progress first, as Undo does, instead of
running inside it and keeping its baseline as the batch's step. Files the
steps wrote stay written.
A native file (or a pasted document) whose node, slice or asset ids or whose
next-id counter sit at u64::MAX made the next allocation overflow: a panic
with overflow checks, a wrapped counter handing out ids already in use
without. fix_next_id now leaves ids and counters from 2^62 up out of the
count, so new ids start under them; alloc_id saturates. Artboard and graphic
style ids take the smallest free id when the largest is u32::MAX instead of
wrapping to the reserved 0.
The preferences file was written only when the app quit normally, so settings
confirmed with OK were lost when the session ended any other way (the system
shutting down, the process being ended, a crash). OK now asks the host to
write the file at once.
Redo swapped the document and the history under a live interaction, whose
snapshot was still the document from before: the next preview or a cancel
brought that one back while the history recorded the redo, so the redone edit
was lost. Redo now settles the interaction first, the way Undo already did: a
drag that changed something is kept as the newest step (which ends the steps
to redo), one that changed nothing is dropped.
The Format menu added to the save sheet after rfd began it (#866) made recent
macOS versions (26.6.2 and 27.0.1 reported) dismiss the sheet at once: Save As,
Save a Copy and Save of an untitled document flashed and answered as cancelled,
while panels without it (Open, Export As) worked. The accessory is withdrawn
until it can be set before the sheet is presented; the format still follows
the extension typed in the name field, as before #866.
Gradients (off unless asked for) found a blurred or noisy linear gradient in
part or in pieces, with bands around it.
The areas that may be a gradient are tried largest first, and one that is a
gradient is grown over every pixel next to it whose colour is within 10 levels
of the gradient's there: the pixels noise put a little off, a soft rim, the
pieces noise cut off it.
Linear-gradient inputs with Gradients on, paths beside the gradient, before ->
after: blurred 3.0 -> 0.5 (6 Colors) and 55.5 -> 0.5 (High Fidelity Photo);
noisy 11.0 -> 0.5 and 23.5 -> 1.5; scanned 5.5 -> 3.0 and 46.5 -> 37.0; clean
and aliased as before; dE within 0.02. No gradient on any input that is flat
colour, with any preset (the harness fails if there is one).
Each documented preset has an enhanced version (`enhanced: true` in
`imageTrace.make`, an Enhanced checkbox under the panel's preset): the preset
with Auto Grouping, and with Shapes in all but High Fidelity Photo. Gradients
stay an option. Flat Logo, a preset of our own, has none. The legacy presets,
and documents traced with them, are as they were.
So that an enhanced preset is no worse than its preset on that preset's inputs:
- A hole that one colour fills is the same shape as the path that fills it, so
the two still share one outline (a shape beside a fitted hole left slivers).
- A rectangle is as large as where its sides lie on average, not their farthest
points.
- Auto Grouping keeps the paths of a group in the order they were traced in. It
put a path first and what is inside it on top: a white counter traced under
its black letter was then painted over the letter's edge, a light seam
(Default with Auto Grouping, edge dE 10.24 -> 9.88; legacy 10.05).
The harness measures every enhanced preset beside its preset.
Create: Strokes drew every line with round caps: a square-ended line 10 px wide
had its corners 2.94 px outside the stroke.
- A line's ends are square when its ink reaches as far a third of the width to
either side of its middle as along it (a round end stops short there by a
seventh of the width); told from lines 3 px wide and more, all the ends of a
line together. The stroke then has square caps (`TracedPath::square_caps`).
- The last points of a wide line's skeleton can turn towards a corner of its
end. An end that has left the line the skeleton followed a width back (by a
quarter of the width, 1 px at least) is put back on it; and an end may be
pulled back as well as pushed out to where the ink ends.
Outline on the thin-line inputs: largest distance from the art 2.94 -> 0.88 px
(clean 2.94 -> 0.41; aliased 0.88 -> 0.81), mean 0.055 -> 0.050 px, anchors
14 -> 10.
- A sharp corner may fall up to 0.75 px short within 3 px of where it should be:
anti-aliasing and blur take that much off a corner (not on a thin bar, which
is all corners).
- A rounded rectangle gets the radius that fits its outline best, searched
around the one its area says, instead of that one alone.
- A rectangle's direction is tried along its longest straight side, the longer
axis of its area and the pixel grid: a blurred one may have no straight side.
- A regular polygon's sides, corners and size are read from the outline's shape
(how its distance from the middle goes round, and its area), not from how many
anchors the fitter gave it.
- An outline without anti-aliasing is known to half a pixel: it gets half as
much room again.
Plain shapes found with Shapes on, all sizes and variants: Default 57% -> 98%,
6 Colors 62% -> 100%. The harness counts false shapes: none on any input but one
thin line (a rectangle 2 px wide) whose trace is 0.70 px off with or without
Shapes.
The VPORT view direction fix and its test stay. The Export Strokes as
Centerlines option and the preset button are not in the documented DXF/DWG
export options, so they wait for the owner's decision.