Commit Graph
1505 Commits
Author SHA1 Message Date
dotCooCoo ead3cd1ebd Variables: Import Data Sets reads UTF-16 CSV files without a byte-order mark
A CSV file that holds NUL bytes and has no byte-order mark was refused.
It is now read as UTF-16 when at least half of its two-byte units hold
a NUL byte, in the byte order that reads a line break. A file with fewer
NUL bytes (UTF-8 or Windows-1252 text with stray NUL bytes, or UTF-16
text made mostly of characters above U+00FF) is still refused, and so is
a file that reads a line break in both byte orders, has an odd length,
isn't valid UTF-16 in that order or holds a NUL character. Refused files
get the same message as before.
2026-11-13 16:34:43 -08: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 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 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 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 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
dotCooCoo 291afd8aa6 Free Distort: the bounding box turns with the warped art
object.distort warped the anchors of paths, groups, compound paths and
clip groups but left their bounding-box angle as it was, so a rotated
path kept its old box after a warp and an upright one stayed square to
the page however far the warp tilted it. Text, images and symbol
instances already took the angle of the warp's affine approximation.

- Each path, group, compound path and clip group now takes the angle
  the warp turns its x axis to at its own center before the warp, the
  members of a warped group and the art of a linked opacity mask each
  at their own. An unlinked mask's art keeps its angle. Mirroring keeps
  the nearer angle and a collapsing warp keeps the old one. Reset
  Bounding Box squares the boxes again.
- Each subtree's bounds are measured once while it is warped.
- Corners that equal the corners of the box being warped change
  nothing and add no undo step.

Docs: object.distort params text, docs/mcp.md, docs/target-app-parity.md.
2026-11-13 12:07:21 -08:00
BFlat a105e14aef Merge pull request #7220 from dotCooCoo/feat/image-indexed-color
Edit Colors: convert embedded images to indexed color
2026-11-14 02:40:36 +07:00
BFlat 90a9e00fdd Merge pull request #7205 from dotCooCoo/fix/svg-interactivity-hide-script-paths
SVG Interactivity: replies name JavaScript files outside the read roots by file name
2026-11-14 02:40:06 +07:00
BFlat 4dc9c5c5d3 Merge pull request #7218 from dotCooCoo/fix/batch-fit-journal-index
Artboards: a batch's Fit to Artwork Bounds step journals the artboard it fits
2026-11-14 02:39:07 +07:00
BFlat 1e385ba06e Merge pull request #7210 from dotCooCoo/fix/envelope-empty-type-box
Envelope: type that draws nothing adds no box next to other content
2026-11-14 02:38:27 +07:00
BFlat b004977dba Merge pull request #7202 from dotCooCoo/fix/raster-effects-skipped-transform-budget
Effects: raster effects before a Transform effect past the budget stay with the art
2026-11-14 02:38:10 +07:00
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 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 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 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 b54f357ced Merge main into fix/raster-effects-skipped-transform-budget 2026-11-13 10:06:29 -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 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
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
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 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 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 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 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
dotCooCoo b4ad756721 Image Trace: Overlapping fills each shape whole and stacks what it encloses above it
An Overlapping layer covers the colors above it within its area, so an
area of another color that a shape enclosed was a hole in that shape
and in the shape of every layer around it, and its outline was fitted
once for each of them. In a code-drawn test, a white disc in a black
ring that four grays touch had six copies of its outline.

With Fills and without Create Strokes, each shape is now filled whole,
by a fill rule of VectorCraft's own. The area inside a filled hole is
traced on its own and drawn after the shapes around it, so its outline
is fitted once. An area of one color is drawn from the hole's outline.
A shape keeps a hole where the area holds a transparent pixel or a
color that Ignore Color or Keep Colors leaves out, and a hole smaller
than Noise is filled with the shape's color as before. Nested areas are
traced within a work budget of VectorCraft's own: four times the
image's pixels (counted as at least 65,536) times its colors, each area
costing its box times its colors. An area that costs more than what is
left keeps its holes and is not charged. Create Strokes and outlines
without Fills are unchanged.

Shapes have holes only in those cases, there are more paths, and the
stacking order changes. Expand by Color starts another group of a color
where an area is drawn above the shape around it. The trace's progress
counts finer steps across the rounds of enclosed areas, not one step
per color layer.

A layer's components without its own color are now left out after its
mask is traced, instead of in a labeling pass before it. Every route
finds its components through the same function.
2026-11-13 09:07:19 -08:00
dotCooCoo cbde1630a1 Documents: closing a background tab keeps the document in front
file.close {index} on a tab other than the current one made the
document now at the closed index current and reset the tool, so the
document the user was working in went to the back. The current
document now stays current, found by its id as document.move does, and
its tool keeps its state. Closing the current tab still makes its
neighbor current.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-13 09:05:44 -08:00
BFlat 60fd4bd644 Merge pull request #7090 from dotCooCoo/feat/pixel-snapping-options
View: Pixel Snapping Options choose whether Snap to Pixel snaps drawing, moving and scaling
2026-11-13 23:58:35 +07:00