Commit Graph
3936 Commits
Author SHA1 Message Date
dotCooCoo af91a37669 Merge main into feat/monitor-profile-windows-linux 2026-11-13 11:12:25 -08:00
BFlat 5c1f206213 Merge pull request #7207 from dotCooCoo/fix/engine-test-temp-folders
Tests: engine tests remove the temp folders they make
2026-11-14 01:54:55 +07:00
BFlat ca8e49e9e2 Merge pull request #7158 from dotCooCoo/fix/export-unused-option-warning
Export: warnings name an option the chosen format doesn't read
2026-11-14 01:54:29 +07:00
BFlat 820b82b8d3 Merge pull request #7188 from dotCooCoo/fix/color-gamut-cache-profile
Color: the out-of-gamut warning updates after a profile change
2026-11-14 01:54:13 +07:00
BFlat a8db64db47 Merge pull request #7208 from dotCooCoo/feat/live-paint-selection-cursors
Live Paint Selection: cursor shows a face, an edge or nothing to select
2026-11-14 01:54:04 +07:00
BFlat 5010861362 Merge pull request #7199 from dotCooCoo/fix/close-background-tab-keeps-front
Documents: closing a background tab keeps the document in front
2026-11-14 01:53:56 +07:00
BFlat 9bad58b73e Merge pull request #7215 from dotCooCoo/fix/new-profile-errors-hide-path
New Document profiles: errors name the profile file by its file name outside the read roots
2026-11-14 01:53:47 +07:00
BFlat 1c1bb2f96e Merge pull request #7191 from dotCooCoo/feat/gradient-hue-blend
Gradients: Hue Clockwise and Hue Counterclockwise mix stops around the hue wheel
2026-11-14 01:53:39 +07:00
BFlat 26869d1754 Merge pull request #7197 from dotCooCoo/fix/item-4498
Arrowheads: keep agents to the arrowheads file inside their read roots
2026-11-14 01:53:29 +07:00
BFlat 2370ad1b21 Merge pull request #7200 from dotCooCoo/fix/free-distort-constrain
Free Transform: Constrain keeps a Free Distort corner on its diagonal
2026-11-14 01:53:20 +07:00
dotCooCoo 2aa7bbd791 Plug-ins: file-key exemptions name only params a command takes
A command now counts as taking a file or folder when its params
description (with the commands its `…` entries name) or its schema takes
a file key as a param. A word in the description's prose or result no
longer counts; a description whose keys are not known keeps the text
scan. The code of the call-time scan of arguments is unchanged, but it
reads the changed exemption tables. The confined run is unchanged.

- NOT_FILES drops seven entries that named no param of their command
  (object.setLiveShape path, path.reverse paths, object.pathfinder.options
  paths, object.maskImage path, imageTrace.make paths,
  svgInteractivity.info files, addon.cleanup.rejoin paths). A top-level
  `path` to object.setLiveShape or object.maskImage, `paths` to
  path.reverse, imageTrace.make or addon.cleanup.rejoin, and `files` to
  svgInteractivity.info are now refused.
- Image Trace's path fidelity setting is exempt only inside `params` and
  `savedPreset` of imageTrace.make and imageTrace.makeAndExpand, so a
  call with it is no longer refused.
- every_command_is_sorted checks that each exemption names a param its
  command takes, and that each nested exemption starts at one.
- effect.expandAsWireframes is reviewed as allowed and
  stroke.arrowheads.list as refused.
2026-11-13 10:50:53 -08:00
dotCooCoo d1f76ede8a Smooth: a drag along a segment smooths it when its anchors are out of reach
A Smooth drag along the middle of a curved segment smoothed nothing when
neither of the segment's anchors was within the reach, and paths of two
anchors were never smoothed.

- path.smoothRegion searches each curved segment where it can come
  within the reach, halving it down to pieces no longer than half the
  reach, and adds an anchor in the middle of each stretch in reach; the
  existing reach split then cuts the segment where the drag enters and
  leaves the reach. A drag that only grazes a curve finds the place on
  it nearest the drag. The search is bounded per drag and shared
  equally by the segments in reach.
- Paths of two anchors are smoothed, open or closed.
- Alt with the Paintbrush smooths a selected two-anchor curve; a
  two-anchor straight line still gets the closed stroke. Both sides use
  one segment_is_straight test.

Part of #2224
2026-11-13 10:44:19 -08:00
dotCooCoo e25795f73e Path Precision Tools: Trim at Crossings leaves clipping paths and art outside isolation alone
- The clipping path of a clip group or a clipped layer takes no part,
  the members of a compound clipping path included. Before, only a
  plain clipping path was left out.
- In isolation mode only paths inside the isolated object are trimmed;
  paths outside it still count as crossing.
- A rectangle or line that reaches so many paths that comparing their
  boxes would take more than 50,000,000 tests is refused before any
  work starts.
- A tool drag that goes 3 screen pixels or more from the press stays a
  rectangle or line when it ends near the press. Before, it ran as a
  click and could remove a whole path the final rectangle did not
  touch.

Docs: docs/mcp.md.
2026-11-13 10:44:07 -08:00
dotCooCoo 9a4e3e6202 Smooth tool: the drag shows the band the tool smooths
While the Smooth tool drags with a path selected that it can smooth, a
dashed outline shows the band its release smooths: every point within
the tool's radius (12 screen pixels) of the drag, round at both ends.
Alt smoothing with the Pencil, Paintbrush and Blob Brush shows the same
band. A selection with no path the tool can change shows none.

- The band is kept in squares eight radii wide, and each step of the
  drag adds its area only to the squares it reaches. A piece of a
  square's outline along a side is left out only where the square
  across holds band area.
- A drag segment too long for the squares (a jump drawn zoomed out and
  viewed zoomed in) keeps its band near each end in the squares and
  shows the rest as the two sides of its band. A step that cannot be
  added whole is added one segment at a time.
- A coarser grid of cells lists the squares and long segments, so a new
  one is checked only against those near it, and an outline is made
  again only where the band near it changed.
- After a zoom change mid-drag the band is outlined again a few hundred
  points per frame, and the work on long segments stops for the frame
  after a fixed budget. Tools report unfinished overlays with
  Tool::overlays_pending, and the canvas then draws again on the next
  frame, so the band covers the whole drag even while the pointer holds
  still.
2026-11-13 10:41:00 -08:00
dotCooCoo 4d4d7ee1a7 Envelopes: opaque pixels of a warped image stay opaque where its pieces overlap
In SVG and PDF output, each piece of a warped image whose pieces can show
through was drawn through a mask of every later piece that reaches it. On a
later piece's antialiased edge the viewer multiplies the earlier piece's
coverage by the mask and draws the later piece over it, so an image with
both opaque and partly transparent pixels came out lighter at its opaque
pixels along the triangles' edges (alpha 184 of 255 in the new SVG test).

A later piece now goes into an earlier piece's mask only when a pixel of
either piece's image within 2 pixels of their overlap is less than opaque
(vectorcraft_effects::WarpSeams). Where two pieces overlap over opaque
pixels, the later one is drawn over the earlier one without a mask.
2026-11-13 10:36:16 -08:00
dotCooCoo 19be0bb880 Type: inline graphics in symbol, mask and pattern art follow their symbols
Type with an inline graphic was resolved only in the layers, so type in
the art of another symbol, a graph design, a pattern tile or an opacity
mask kept the size its graphic had when it was made. The symbol holding
that type, its instances and the inline graphics that show it were
measured from the stale layout.

- Inline graphics are resolved in every tree that holds type. Symbols
  are resolved in dependency order, so a symbol's type is laid out
  before the type that shows that symbol inline. Edits, opening,
  reverting and Refresh Font List use the same pass.
- The pass visits each object once: it resolves the children first and
  copies a node only when something below it changed. It measures each
  symbol's art once per pass.
- An inline graphic of a symbol whose art shows that symbol inline,
  directly or through other symbols, is an empty slot. The symbols on
  such a cycle are found once and cached until the symbols change.
- A changed inline graphic also clears the text's cached baselines.
2026-11-13 10:17:33 -08:00
dotCooCoo 1b935b6d0a Merge main into fix/smooth-fidelity-screen-pixels 2026-11-13 10:13:12 -08:00
dotCooCoo b54f357ced Merge main into fix/raster-effects-skipped-transform-budget 2026-11-13 10:06:29 -08:00
dotCooCoo 3c434a7e90 Merge main into fix/graph-series-paint-visible-rows 2026-11-13 10:02:14 -08:00
dotCooCoo 5d31439e46 Merge main into fix/graph-series-paint-other-commands 2026-11-13 09:57:15 -08:00
dotCooCoo 1da59bfb22 Document Info: hidden and clear objects list no brushes or paints
Brushes, Spot Color Objects, Pattern Objects and Gradient Objects left
out a fill or stroke that was hidden or at an opacity of 0, but still
listed an object that was itself hidden or at an opacity of 0, and the
objects inside such a group or layer. They now leave those out, their
characters' paints and colorized images included. With Selection Only,
an object selected inside a hidden group or layer, or one at an
opacity of 0, is left out too. Objects still counts every object.
2026-11-13 09:54:35 -08:00
BFlat 7553c1f679 Merge pull request #7182 from dotCooCoo/feat/objects-on-path-detach
Objects on Path: Detach takes the chosen object off the path
2026-11-14 00:51:59 +07:00
BFlat c7adea5518 Merge pull request #7144 from dotCooCoo/fix/script-line-numbers
Scripts: $.line and $.stack give the line of the file the code is in
2026-11-14 00:51:19 +07:00
BFlat e41ef1ea62 Merge pull request #7148 from dotCooCoo/fix/blend-without-spine-widget
Blend: a blend without a spine shows the Appearance widget
2026-11-14 00:51:09 +07:00
BFlat 3378e390ab Merge pull request #7175 from dotCooCoo/fix/graph-series-group-appearance
Graphs: Appearance commands and graphic styles given a series group edit its marks
2026-11-14 00:51:00 +07:00
BFlat 4f0d3f1eb3 Merge pull request #7111 from dotCooCoo/fix/freehand-tolerance-low-zoom
Pencil: Fidelity keeps its screen size at low zoom
2026-11-14 00:50:47 +07:00
BFlat 637d51df79 Merge pull request #7143 from dotCooCoo/feat/guides-layer-rows
Layers: ruler guide rows have their own eye and lock, and go to the trash
2026-11-14 00:50:35 +07:00
BFlat 8db4fe00dc Merge pull request #7179 from dotCooCoo/fix/place-read-own-document
Place: a file read apart from the session goes only into its document
2026-11-14 00:50:27 +07:00
BFlat 1f67fa13a5 Merge pull request #7190 from dotCooCoo/fix/item-4475
Expand: type reports a gradient only when Fill expands one of its outlines
2026-11-14 00:50:08 +07:00
BFlat 4f58b83c75 Merge pull request #7118 from dotCooCoo/fix/painted-effect-every-item
Effects: a later painted effect runs on every fill and stroke a piece returns
2026-11-14 00:49:38 +07:00
BFlat c1c06a839a Merge pull request #7183 from dotCooCoo/feat/pdf-long-pages-points-default
PDF: pages longer than 200 in are written at full size in points by default
2026-11-14 00:49:30 +07:00
BFlat 3904a7396c Merge pull request #7120 from dotCooCoo/feat/revolve-graphics
3D and Materials: wrap graphics around the side of Revolve objects
2026-11-14 00:49:21 +07:00
BFlat d58452f287 Merge pull request #7167 from dotCooCoo/fix/image-icc-16bit-kept
Images: an image stored again keeps its ICC profile and 16-bit samples
2026-11-14 00:49:03 +07:00
dotCooCoo 81d19f1718 Edit Colors: convert embedded images to indexed color
Edit > Edit Colors > Convert to Indexed Color... (edit.colors.toIndexed)
stores the embedded images in the selection as indexed-color PNGs: a
palette of at most 256 RGB colors, each pixel the index of one. The
dialog previews live and OK is one undo step.

- The palette is chosen as for a PNG-8 export (selective, perceptual,
  adaptive, web, gray, black and white), as evenly spaced levels of red,
  green and blue (uniform), or from the document's swatches. Dithering
  is diffusion, pattern, noise or none, with an amount.
- Pixels under half opacity become one transparent entry; the others
  become opaque in their own color. Linked images, colorized images and
  color glyph images keep their pixels.
- An image that is already an indexed PNG and would show the same pixels
  is left as it is, and no undo step is recorded when no image changes.
- An image of more than 134,217,728 pixels (33,554,432 in the web app)
  is refused, read from its header before decoding.
- With Select All, locked and hidden objects that hold an image that is
  not an indexed PNG yet are counted in notChanged, without converting
  them.
- The quantizer gains quantize_to, which maps pixels to a given palette.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-13 09:49:00 -08:00
BFlat f520e6f8c5 Merge pull request #7184 from dotCooCoo/fix/cleanup-one-pass
Artwork Cleanup: remove quadratic work from three path repairs
2026-11-14 00:48:45 +07:00
dotCooCoo d2be559b55 New Document profiles: errors name the profile file by its file name outside the read roots
file.newProfiles.edit and file.newProfiles.reset returned the automation
access check and file system errors with the profile file's whole path,
so a client confined to read roots that leave out the preferences folder
learned where the folder is. The errors now go through naming_in_error,
like other paths the client did not give: whole inside the read roots,
otherwise by file name. docs/mcp.md and the params text say so.
2026-11-13 09:40:38 -08:00
dotCooCoo c1ba246bb3 Graphs: a paint command that leaves a series' stored paint as it was records no undo step
The graph compares the paints a command leaves on it with the paints it
stored before the command, not with the value before each mark's
capture. Apply Fill to Stroke on two marks whose strokes already had
their fills stored the back mark's stroke and then the front mark's
again, and recorded an undo step with nothing changed. The same
comparison applies to the opacity and blend mode a series keeps.

docs/mcp.md states that only the topmost visible row's paint is stored,
and the Graphs line of docs/target-app-parity.md states that a series
that draws Filled Lines bands keeps no stroke paint or weight.
2026-11-13 09:37:35 -08:00
dotCooCoo 711d7163f8 Brushes: repeated brush names are numbered when a document loads
A document or brush library file could hold two brushes of one name.
Renaming the first in Brush Options moved every stroke of that name and
left the second brush unreachable, deleting by name removed both, and
the Brushes panel could not select the second row.

Reading a stored brush library now keeps the first brush of a name as
stored and numbers each later one with the first "Name 2", "Name 3" and
so on that the file does not already have. Strokes name their brush,
and a name always found the first brush, so every stroke keeps drawing
the brush it drew before. The rule applies to opened documents and to
brush library files; brushes added to a document that already has the
name keep today's numbering.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-13 09:37:14 -08:00
dotCooCoo b53cd7b52e Envelope: type that draws nothing adds no box next to other content
An envelope around type of only spaces and a rectangle mapped from the
union of the rectangle and the type's laid-out box, so the empty type
moved and scaled how the rectangle was distorted. Type that draws
nothing now adds no box while other content in the envelope has one,
in groups and layers and as a clipping path too. When nothing else in
the envelope has a box, the type keeps its laid-out box, so an envelope
of only empty type keeps its frame, Make with Mesh and Reset with Mesh
still work, and Release still gives back the warp shape.

This applies to Make with Warp, Make with Mesh, Make with Top Object,
Reset with Mesh, Edit Contents and the stroke margins. Type that has
not been measured yet keeps its laid-out box as before.

The PDF/.ai import's boxes for envelopes with Distort Appearance
measure each object as the whole contents measure it, so the margins
the import tries match the contents' geometric box. The three import
tests that measure type without registering the glyph measure now
register it first, so another test registering it while they run no
longer changes their result.
2026-11-13 09:37:03 -08:00
dotCooCoo ba2531bc3f Tests: engine tests remove the temp folders they make
vectorcraft_testkit::temp_dir returns a bare path, so every engine test
that wrote into one left the folder in the system temporary folder.

- testkit: TempDir, a test's own folder made through temp_dir (same
  private creation and ownership checks) under a tag no other TempDir in
  the process uses, removed with its contents when dropped, also when
  the test panics.
- Engine unit and integration tests hold a TempDir for the whole test
  instead of calling temp_dir: graph, fileio, compare, journal, library,
  shortcut set, spelling, store, web export, cancel, containers, build,
  color settings, command sweep, font folder, removed font, model-based
  and the font search fuzz.
- The DCS and swatch library tests' own helpers and the PDF editing
  tests' shared vc-pdfedit-<pid> folder use a TempDir per test too.
- The font and link file search, store, color settings and font folder
  helpers no longer delete the private folder temp_dir checked and make
  it again with default permissions.
2026-11-13 09:33:36 -08:00
dotCooCoo f5de590dee SVG Interactivity: replies name JavaScript files outside the read roots by file name
With automation read roots in force, svgInteractivity.info and the files
reply of addFile and removeFile returned every linked JavaScript file as
stored, including paths outside the roots. They now name such a file by
its file name alone, as document.json does; a file inside the roots and
a URL are named as they are, and the document keeps every path.

removeFile also unlinks a file outside the roots by that file name. When
the name fits more than one linked file, it returns an error that names
no stored path and unlinks nothing. addFile still treats only the same
text as already linked.

In a frame a request drives, the JavaScript Files dialog lists such a
file by its file name while its selection keeps the file as linked, and
ui.inspect gives the selected file the same way. The person's frames
show full paths.
2026-11-13 09:32:19 -08:00
dotCooCoo 40bd6a8811 Place: File › Revert drops reads started for the content it discards
A file read for Place, for the place cursor or dropped on the canvas,
a bitmap read for Paste, and a paste waiting at its Swatch Conflict
dialog are no longer placed or pasted in a document that File › Revert
replaced while they waited. Revert keeps the document's tab and uid, so
these were placed in the reverted content with the selection, clipboard
and insertion context of the content it discarded.

- DocState::reverts counts the Reverts of a document; replace_document
  increments it, and a batch that fails puts it back with the document.
- Reads, deferred swatch pastes and web canvas drops keep the count they
  started with and are dropped, with a status message, when it changed.
- A paste given after a Revert starts a chain of its own instead of
  following a paste given before it.
2026-11-13 09:29:54 -08:00
dotCooCoo 8fa0db4b51 Free Transform: Constrain keeps a Free Distort corner on its diagonal
The widget's Constrain button was grayed out in Free Distort, and
neither it nor Shift changed a Free Distort corner drag. Constrain is
now available in Free Transform and Free Distort, and stays unavailable
in Perspective Distort.

With Constrain on, or Shift held, a corner dragged in Free Distort
moves only along the line through the opposite corner, in the box's
own proportions, and the other three corners stay in place. On a
turned box the line runs through the box's own corners. A box with no
size leaves the corner where it is. Cmd/Ctrl+Alt+Shift on a corner
still drags in Perspective Distort. The measurement label shows how
far the corner moved.
2026-11-13 09:26:50 -08:00
dotCooCoo 3a336fd32f Path Precision Tools: Trim at Crossings removes the stretch of a path between crossings
The new Trim at Crossings Tool removes stretches of paths between the
places where they are crossed by another path or by themselves. A click
removes the stretch under the pointer, a drag removes every stretch its
rectangle touches, and an Alt-drag removes every stretch its freehand
line crosses. It works on every path that can be edited, selected or
not, and each gesture is one undo step.

- `addon.pathtools.trim {at, tolerance?}`, `{rect}` or `{points}` is the
  command behind the tool. A closed path that loses a stretch becomes
  open, an open path that loses a middle stretch becomes two paths with
  its appearance in its place in the stacking order, and a path that
  nothing crosses is removed whole. The stretches that stay get no new
  anchors.
- Hidden and locked paths, guides and clipping paths are not trimmed and
  do not count as crossing. Only the paths the gesture reaches are
  searched for crossings, and one work limit covers every path the
  command trims.
- `crossings_on` and `crossings_on_within` in `vectorcraft-pathops` find
  the crossings on one path and compare it only with the segments in
  its box.
- The new tool shares the crossing search with Add Points at Crossings
  and Split at Crossings, so those change too: two segments of a path
  that meet at an anchor, directly or across segments with no length,
  now count as crossing where they cross again away from that anchor,
  and do not count as crossing at the anchor itself. With Self
  Crossings, a curved segment that loops over itself counts as crossing
  itself. Before, both crossings were missed.
- The tool is an add-on tool: Object › Plug-ins › Path Precision Tools
  and the All Tools drawer list it, it has no shortcut, and neither
  shipped toolbar shows it.

Docs: docs/mcp.md, docs/ui-parity.md, docs/target-app-parity.md.
2026-11-13 09:24:53 -08:00
dotCooCoo 25a99d7c07 Effects: raster effects before a Transform effect past the budget stay with the art
A Transform effect whose result would pass the geometry budget (2 million
anchors or 8 million lines drawn) is left out and the art stays where it
was, but the raster effects listed before it still scaled and moved with
it: before a Transform effect that scales the art 1000 times, a Drop
Shadow's offset grew 1000 times and its blur went to the 1000 pt limit
around art that had not moved, and Expand kept those values.

The geometry pass now notes each Transform effect that moves the art once
and that it leaves out, in the Carry it already returns, and
Carry::hide_skipped hides those effects in the list the raster effects
are read from. The canvas (a path's own effects and its fills' and
strokes'), its bounds, bake (SVG and PDF export), shape art, Expand,
Expand Appearance and Outline Stroke use it, and a fill's Transform
effect inside a piece no longer scales the effects before it when the
budget leaves it out. The budget itself is unchanged.

A path drawn as its own art inside a group whose geometry effects run
after its members' own read its object effects whole. The reshaping pass
now notes a Transform effect that no path in the art took past the
budget, and the member's art hides it before its raster effects are read.

The bounds skip the extra geometry pass when no raster effect could
follow a Transform effect, and painted_reach runs it once for both
measurements.
2026-11-13 09:16:26 -08:00
dotCooCoo 6462060928 Merge main into fix/export-unused-option-warning 2026-11-13 09:15:25 -08:00
dotCooCoo 4b8cd30757 Artboards: a batch's Fit to Artwork Bounds step records the first artboard it defaults to
A Fit to Artwork Bounds step of `command.batch` that gets no artboards from
the Artboard tool or the batch fits the first artboard, as the command does
by default, and its journal entry now records that `index`. Before, the
entry kept the step without it, so a replay with the Artboard tool choosing
two or more artboards fitted those instead.
2026-11-13 09:12:29 -08:00
dotCooCoo 4acd6cf90d Batch: a step with null params gets the artboards a step without params gets
A `command.batch` step with `"params": null` now reads as a step without
params. Before, it got neither the Artboard tool's selected artboards nor
the batch's `artboard`, so a Fit to Artwork Bounds step fitted the first
artboard and its journal entry recorded `null`.
2026-11-13 09:12:29 -08:00
dotCooCoo 2e8a90d6e4 Artboards: a batch's Fit to Artwork Bounds step records the batch's artboard
When a Fit to Artwork Bounds step of `command.batch` takes the batch's
`artboard` as its `index`, the batch's journal entry now records that
`index`. Before, the entry kept the step without it, so a replay with the
Artboard tool choosing two or more artboards fitted those instead.

The params text, docs/mcp.md and docs/target-app-parity.md state that the
step uses the Artboard tool's selection only when two or more artboards
are selected, and the active artboard otherwise.
2026-11-13 09:12:29 -08:00
dotCooCoo 0a7bf3a7e2 Live Paint Selection: cursor shows a face, an edge or nothing to select
The Live Paint Selection tool always showed the plain arrow. Its cursor
now says what a click at the pointer selects, by the same rules as the
click (Alt, Select Fills, Select Strokes, locked, hidden and isolated
groups): the arrow with a filled square over a face, with a short line
between two anchor marks over an edge, and with an x where a click
selects no face or edge. From a press to the release the cursor keeps
the look it had at the press. Use Precise Cursors shows the crosshair.

- Cursor: LivePaintSelectFace, LivePaintSelectEdge and
  LivePaintSelectOutside, with cursors.toml entries and drawn glyphs.
- Tests: live_paint_selection_cursor_shows_what_a_click_selects,
  live_paint_selection_shows_the_arrow_with_a_face_edge_or_x_badge, and
  the precise-cursor list.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-13 09:10:18 -08:00