Commit Graph
4831 Commits
Author SHA1 Message Date
BFlat c36aaec289 Merge pull request #7296 from dotCooCoo/fix/delete-artboards-one-pass
Artboards: delete several artboards and their guides in one pass
2026-11-14 05:41:59 +07:00
BFlat 1b039350a3 Merge pull request #7297 from dotCooCoo/fix/global-edit-enabled-for-supported-groups
Global Edit: Start Global Edit is offered only for a group it can start on
2026-11-14 05:41:50 +07:00
BFlat 5fd9ef537a Merge pull request #7294 from dotCooCoo/fix/live-shape-unknown-keys
Live Shapes: object.setLiveShape refuses unknown keys and invalid sides or radius
2026-11-14 05:41:41 +07:00
BFlat 7b992a416e Merge pull request #7287 from dotCooCoo/fix/recovery-error-guidance
Data Recovery: a failed copy names the folder in use and how to change it
2026-11-14 05:41:33 +07:00
BFlat 795f01782f Merge pull request #7272 from dotCooCoo/fix/invert-wrap-one-pass
Text Wrap: Invert Wrap intersects a line's spans in one pass
2026-11-14 05:41:20 +07:00
BFlat df581b2521 Merge pull request #7282 from dotCooCoo/feat/banner-hems-grommets
Cutting Tools: Banner Hems and Grommets marks a banner's hems and grommet holes
2026-11-14 05:41:10 +07:00
BFlat 64b4cb1ad6 Merge pull request #7300 from dotCooCoo/fix/composite-fonts-per-document
Type: documents that define one composite font name differently each draw in their own
2026-11-14 05:40:49 +07:00
BFlat c135ba1f9b Merge pull request #7279 from dotCooCoo/fix/path-type-probe-wider
Type: threaded type on a path aligned Justify All is measured by paragraphs
2026-11-14 05:40:38 +07:00
BFlat 35d7c5d523 Merge pull request #7271 from dotCooCoo/fix/pdf-long-page-warning-names-option
PDF: the long-page warning names the in-points option
2026-11-14 05:40:30 +07:00
BFlat e244a65981 Merge pull request #7293 from dotCooCoo/fix/offset-path-parallel
Offset Path: offsets several selected paths on several threads
2026-11-14 05:40:00 +07:00
BFlat 55fe8b62d4 Merge pull request #7278 from dotCooCoo/fix/rtl-tab-line-hyphenation
Type: right-to-left lines with tabs are measured as they are drawn
2026-11-14 05:39:52 +07:00
BFlat 00d2f6d24b Merge pull request #7283 from dotCooCoo/fix/nested-svg-image-count
SVG import: broken nested SVG images do not use up the image allowance
2026-11-14 05:39:43 +07:00
BFlat 31eb3b0d1e Merge pull request #7285 from dotCooCoo/fix/pdf-warped-image-written-once
PDF export: a warped image writes its pixels once for all its pieces
2026-11-14 05:39:34 +07:00
BFlat 2f44c32060 Merge pull request #7032 from addibble/taper-one-face
3D: a tapered Extrude & Bevel wall is one face
2026-11-14 05:39:24 +07:00
BFlat 4558196e16 Merge pull request #7053 from dotCooCoo/feat/warichu
Type: Warichu stacks selected characters on lines inside their line
2026-11-14 04:32:40 +07:00
BFlat ce1e688c7c Merge pull request #7138 from dotCooCoo/fix/status-linked-files-keeps-notes
Status bar: a line about linked files keeps the open's notes listed
2026-11-14 04:32:29 +07:00
BFlat f8751a8c0c Merge pull request #7221 from dotCooCoo/fix/space-in-text-edit
Type: Space types a space while editing text instead of panning
2026-11-14 04:32:17 +07:00
BFlat a0e8701256 Merge pull request #7248 from dotCooCoo/fix/ai-twist-past-180-keeps-look
.ai import: straight paths twisted past 180° keep the drawn look
2026-11-14 04:32:07 +07:00
BFlat e5913e30e1 Merge pull request #7204 from dotCooCoo/fix/trace-overlapping-two-pass
Image Trace: Overlapping fills each shape whole and stacks what it encloses above it
2026-11-14 04:31:56 +07:00
BFlat bf0f78f9c4 Merge pull request #7256 from dotCooCoo/fix/blob-brush-preview-bounded
Blob Brush and Eraser: the drag outline does a bounded amount of work per frame
2026-11-14 04:31:48 +07:00
BFlat 200b65feea Merge pull request #7247 from dotCooCoo/fix/blend-spine-measured-once
Blend: spreading keys along a replaced spine measures it once
2026-11-14 04:31:35 +07:00
BFlat 4a039aa63c Merge pull request #7269 from dotCooCoo/fix/free-distort-box-turns
Free Distort: the bounding box turns with the warped art
2026-11-14 04:31:23 +07:00
BFlat 7a80e194a2 Merge pull request #7266 from dotCooCoo/fix/doc-info-clear-stop-spots
Document Info: spot colors from gradient stops that color nothing are not listed
2026-11-14 04:31:11 +07:00
BFlat 9aa330c475 Merge pull request #7219 from dotCooCoo/fix/docs-layers-row-menu
Docs: Layers row context menu offers Collect for Export
2026-11-14 04:30:49 +07:00
BFlat d15a725626 Merge pull request #7216 from dotCooCoo/feat/trim-at-crossings
Path Precision Tools: Trim at Crossings removes the stretch of a path between crossings
2026-11-14 04:30:27 +07:00
dotCooCoo 58cb261d7c Global Edit: Start Global Edit is offered only for a group it can start on
Select > Start Global Edit was enabled for any plain group, so a group
holding type, an image or nothing was offered in the Select menu and the
Properties panel's Quick Actions, the Control bar showed its Global Edit
Options, and starting then refused it. The enabled check now applies the
same support check as starting to every selected object, a group's
contents included.

The answer is kept per document revision and selected object, so the
Control bar, which asks every frame, walks a group once per change.

Docs: docs/mcp.md, docs/target-app-parity.md (a new line; the existing
Global Edit line no longer says the menu item checks only for a group).
2026-11-13 13:28:13 -08:00
dotCooCoo 4bdfc44b26 Type: documents that define one composite font name differently each draw in their own
A document's composite fonts were lent by writing them into the font
database's shared map, and a second lend of another definition of the same
name was skipped while the first was held. A document drawn in a tiled or
floating window while the front document drew, a document placed in one
that defines the name differently, and an export made while another
document is in front could draw their type in the other document's
definition. Registering the front document's fonts during a render could
also change the definition mid-render.

Lending now applies on the lending thread only: each guard pushes the
document's definitions onto a per-thread stack that lookups read before the
database's own fonts, latest loan first, and removes them when it drops,
also when the thread unwinds. The guard is neither Send nor Sync. Lending no
longer changes the database or its generation; while a thread lends
definitions other than the database's, FontDb::generation gives a number of
its own for them, so kept layouts and render caches made in one document's
definitions are not used for another's.
2026-11-13 13:24:38 -08:00
dotCooCoo a2efb2d88b Artboards: delete several artboards and their guides in one pass
Delete Artboards, Delete Empty Artboards and Cut Artboard removed the
chosen artboards one at a time, walking every ruler guide and the layer
tree once per artboard. They now share one helper that removes the
artboards in one walk and their guides in one more, leaving the same
artboards, guides and selected guides as before.

Empty Artboards in Artwork Cleanup had its own copy of the one-pass
removal. It now calls the same helper, and its test reads that helper's
walk counter.
2026-11-13 13:23:42 -08:00
dotCooCoo 55f43d2acb PDF export: a warped image writes its pixels once for all its pieces
With downsampling on, each piece of an image bent by a Warp effect was
resampled for its own placed size, so a warp wrote one image XObject for
each size a piece was placed at (four for a 4 x 4 warp that stretches
across, eight for a colorized one with its soft masks). The pieces are
now resampled for the widest and the highest size any piece places the
image at, and all paint that one image. Pieces off the page count too,
and another placement of the same image keeps its own size.

Without downsampling, a colorized image and a CMYK image with a grid
mask no longer key their cache by placed size, so each is decoded and
encoded once for all the sizes it is placed at, as plain images are.
2026-11-13 12:54:12 -08:00
dotCooCoo ac10c51381 Data Recovery: a failed copy names the folder in use and how to change it
When a recovery copy could not be written, the status bar said only
"Couldn't save recovery data: <error>", and a copy that failed on the
background worker showed as "Saving recovery data for <title> failed:
<error>". Neither said which folder was in use or how to pick another.

The message keeps the error text (and the document's title when one
copy fails), then names the folder copies go to, says whether it is
the default recovery folder or the folder set in Preferences, and
names the setting that picks another folder: Preferences → File
Handling → Data Recovery → Folder (recoveryFolder). The recovery code
writes the same line to the log. The folder is the one in use when the
round started, so a preference changed while a copy runs does not
change the folder the message names.

A copy that fails now reports its own message, so the job's result is
Ok rather than an error, and the job no longer adds "<label> failed:
<error>" over it. A store the host installed (browser storage) has no
folder to name: its failed copy shows "Saving recovery data for
<title> failed: <error>", as a copy on the worker did before, also
when the copy was written without the worker.

- engine: recovery::folder returns the folder in use and whether the
  recoveryFolder preference names it; recovery::store uses it.
- Four @msg rows in each complete-message catalog, and in the bg, bs,
  da and id catalogs, which translate the old message.
- Control replies to a client confined to read roots show a recovery
  folder outside them, and the paths under it, by the folder's name, as
  they already show other paths outside the roots. That covers the
  folder in use and the last eight folders a failure message named.
- docs/target-app-parity.md: a line next to the Data Recovery Folder
  preference.
2026-11-13 12:51:12 -08:00
dotCooCoo 1eaf421e69 Cutting Tools: Banner Hems and Grommets marks a banner's hems and grommet holes
Object › Plug-ins › Cutting Tools › Banner Hems and Grommets…
(`addon.cutting.banner`) marks where a printed banner is hemmed and where
its grommets go. The banner is the bounds of the selected art, or each
artboard when nothing is selected. On each chosen side the command adds a
hem line a set depth inside the edge (1 in by default) and grommet marks a
set inset inside it (0.5 in): one at each end of the side and the rest
spread evenly, as few as keep them at most a set spacing apart (24 in). A
corner that two chosen sides share gets one grommet. Each grommet mark is a
circle (27 pt by default) with a cross through its center. The marks are
stroked in a chosen swatch ([Registration] by default) with no fill, in one
group named Banner Marks per banner on top of the current layer, selected,
as one undo step. While a clip group is edited in isolation, the group goes
outside the outermost clip group, as Weed Box and Lines does.

The command refuses every side off, no hem and no grommets, and bad params
with a message. It also refuses a hem or inset that reaches the far edge of
the banner when one of two opposite sides is chosen, or that meets the marks
of the opposite side when both are chosen, an inset of more than half a
chosen side's length, and more than 100000 grommets. The dialog has Preview.

The grommets' distance from the edge is `grommetInset`, so the dialog shows
it as Grommet Inset with its own catalog rows. The new Diameter catalog
rows also translate the Diameter field of the `shape.flare` command dialog,
which had no rows.

Docs: docs/mcp.md (Cutting Tools add-on), docs/target-app-parity.md and
ROADMAP.
2026-11-13 12:49:43 -08:00
dotCooCoo 4dc5c4f719 Live Shapes: object.setLiveShape refuses unknown keys and invalid sides or radius
object.setLiveShape was in params::UNCHECKED, so a key it does not read,
such as `path`, ran the command with the key ignored. Its description
names every key it reads, so it now goes through check_keys like other
commands, for MCP, scripts and command.batch steps. Each of `items` takes
`id` and `corners` alone; another key is refused as
`items[0].corner`, with the nearest accepted key. Saved steps still
replay leniently.

A `sides` that is not a whole number from 3 to 1000 (2.5, -1, 2, 1001)
and a negative `radius` are now errors naming the key, and leave the
document and its undo history as they were; a radius of 0 still
removes rounding. The Transform panel's Sides field takes 3 to 1000,
the Corners dialog's radius field stops at 0, and the panels' corner
radius fields send 0 for a negative value typed in.
2026-11-13 12:46:15 -08:00
dotCooCoo 804177bddd Offset Path: work out the offsets of several selected paths on several threads
Offset Path took each selected path in turn, so offsetting many paths, or a
group of many paths, used one CPU. It now reads the selected paths first,
works out their offsets on as many threads as the computer has logical CPUs
(one thread on the web and for a single path), and then makes the new
objects one after another in the selection's order on the editing thread.

The new objects, their ids, their stacking, the selection, the messages and
the undo step are the same as before. When several paths fail, the error is
the one for the first of them in the selection's order, and the paths after
a failure that have not started yet are skipped.
2026-11-13 12:41:24 -08:00
dotCooCoo 25857b2bce Type: right-to-left lines with tabs are measured as they are drawn
A right-to-left line draws the text before a tab after it, but its width
was measured in the order of its text. A right-aligned line such as
"אב גד<tab>הו זח" with a stop at 150 pt in a 300 pt frame ended 5.59 pt
past the frame's right edge, and right, center and decimal stops aligned
the text before the tab instead of the text drawn after it.

- The line's width is measured in drawing order, each tab up to its stop
  from where the glyphs drawn before it end. Right, Auto and centered
  lines are aligned by that width. Stops are still measured from the
  left edge.
- Right, center and decimal stops align the text drawn after the tab.
- Area type breaks a line with a tab where it fits as drawn, and wraps a
  line that fits only in the order of its text. Once the line holds a
  tab, it reads on while the least it can be drawn at fits, then ends at
  the latest break that fits as drawn: up to 256 breaks are measured
  from the latest back, and earlier breaks without a hyphen are
  bisected. Hyphenation points are measured as drawn the same way, so
  the line hyphenates at a point that fits.
- Only lines of horizontal paragraphs with right-to-left text keep a
  list of their break candidates.
- A tab that ends a wrapped right-to-left line is drawn before the line
  at its own width instead of pushing the line to the right.

Docs: docs/target-app-parity.md.
2026-11-13 12:40:40 -08:00
dotCooCoo ab1b7a974a Type: threaded type on a path aligned Justify All is measured by paragraphs
Type on a path whose first paragraph is aligned Justify All places its line
from the start bracket, the same as Left, so a threaded story is now measured
with the paragraphs it needs rather than the whole rest. A 1,500-paragraph
story through 40 paths aligned Justify All was laid out over 2,135,202 bytes
while measuring and is now laid out over 20,583. The frames take the same
text as before.

Center, Right, Auto right to left, hanging punctuation, Dynamic Text and
horizontal right-to-left text are still measured with the whole rest: a
centered or right-aligned line starts where the width of all its glyphs puts
it, and negative tracking can make an advance negative.

Tests compare every frame's share with the whole-rest share for Justify All
with spacing, on closed paths, with negative tracking and with punctuation
ending paragraphs.
2026-11-13 12:40:31 -08:00
Drew Dibble abb1e0d820 Merge remote-tracking branch 'upstream/main' into taper-one-face
# Conflicts:
#	crates/three-d/src/solid/tests.rs
#	docs/target-app-parity.md
#	docs/three-d.md
2026-11-13 13:38:40 -07:00
dotCooCoo 9d9e0c2f8e SVG import: broken nested SVG images do not use up the image allowance
An SVG shown as an image that could not be read (text that isn't XML,
an SVG without a size) still took one of the 256 nested SVG images an
SVG may show, so an image after 256 broken ones was left out. Only
images that decode now count toward that limit; a failed image gives
its place back.

Every nested SVG image read, decoded or not, counts toward new limits
of 1,024 reads and 512 MB of text per import, so a broken image shown
many times through <use> is still read a limited number of times. Past
them, nested SVG images are left out with a warning.

A nested SVG image with text that usvg then refuses no longer warns
that its text isn't imported.
2026-11-13 12:38:00 -08:00
BFlat c9b2119552 Merge pull request #7176 from dotCooCoo/feat/monitor-profile-windows-linux
View: the canvas is drawn through the monitor profile on Windows and X11
2026-11-14 03:24:26 +07:00
BFlat 3ec0cdec71 Merge pull request #7245 from dotCooCoo/fix/not-files-real-params
Plug-ins: file-key exemptions name only params a command takes
2026-11-14 03:24:13 +07:00
BFlat f05cb5e7a4 Merge pull request #7223 from dotCooCoo/fix/inline-graphics-in-symbol-art
Type: inline graphics in symbol, mask and pattern art follow their symbols
2026-11-14 03:24:03 +07:00
BFlat cc0413ee34 Merge pull request #7217 from dotCooCoo/fix/brush-repeated-names-numbered
Brushes: repeated brush names are numbered when a document loads
2026-11-14 03:23:54 +07:00
BFlat 50084b789f Merge pull request #7243 from dotCooCoo/fix/smooth-segment-under-drag
Smooth: a drag along a segment smooths it when its anchors are out of reach
2026-11-14 03:23:45 +07:00
BFlat a3ab152a0a Merge pull request #7246 from dotCooCoo/fix/objects-on-path-flatten-once
Objects on Path: each path is flattened once per placement or transform
2026-11-14 03:23:37 +07:00
BFlat 9107dad274 Merge pull request #7194 from dotCooCoo/fix/item-3829
Envelopes: a partly transparent image keeps its alpha where its warp pieces overlap
2026-11-14 03:23:28 +07:00
BFlat 00fba7ae24 Merge pull request #7121 from dotCooCoo/fix/graph-series-paint-other-commands
Graphs: stroke, gradient and recolor commands update a series' stored paint
2026-11-14 03:21:58 +07:00
BFlat cf6c666d25 Merge pull request #6980 from addibble/seam-round-joins
3D effects: every facet's seam has round joins
2026-11-14 03:21:49 +07:00
dotCooCoo add71548db Merge main into fix/blob-brush-preview-bounded 2026-11-13 12:15:59 -08:00
dotCooCoo dd5a9e7cbc Text Wrap: Invert Wrap intersects a line's spans in one pass
With Invert Wrap, each piece of a line's band intersected the line's
spans so far with the spans the wrap shape holds over that piece by
comparing every pair of them. A wrap shape of many parts (a comb or a
zigzag) gives a line many spans, and with an offset the band is cut
into up to 128 pieces, so a dense shape made layout quadratic in the
number of parts for every line.

intersect_intervals now walks the two sorted lists once, stepping past
whichever interval ends first, and gives the same spans in the same
order. Tests compare it with the pairwise intersection on every list of
non-overlapping intervals with ends among seven points, check that two
interleaved lists of 3,000 and 2,000 intervals take at most 5,000
steps, and lay out a line inside a 500-tooth Invert Wrap comb with an
offset.
2026-11-13 12:12:22 -08:00
dotCooCoo 89ccf89461 Document Info: spot colors from gradient stops that color nothing are not listed
Spot Color Objects listed every spot color linked from a gradient's stops
once any stop was above opacity 0. A spot stop at opacity 0 between other
clear stops, at the end of the ramp after a clear stop, or meeting an
opaque stop only at one offset was listed though it prints no ink, and so
was an opaque stop out of order whose pairs have no width.

A stop now counts when it bounds a stretch of some width that the
gradient sampler mixes and either stop of that stretch is above opacity
0, or when it is the first or last stop and above opacity 0, since those
color the offsets before and after the others. Stops in any order are
judged by the stretches the sampler uses. A freeform gradient keeps the
old rule, since it draws from its points. Type runs follow the same rule.
The document.info params text, docs/mcp.md and the progress line
describe it.
2026-11-13 12:11:20 -08:00
dotCooCoo fd31331079 Merge main into fix/space-in-text-edit 2026-11-13 12:09:58 -08:00