Commit Graph
4947 Commits
Author SHA1 Message Date
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
jmverges deabf78747 Merge remote-tracking branch 'upstream/main' into fix/page-check-typed-envelopes
# Conflicts:
#	docs/target-app-parity.md
2026-11-13 17:22:20 +00:00
Drew Dibble 0654aef75b Merge remote-tracking branch 'upstream/main' into seam-round-joins
# Conflicts:
#	crates/effects/src/solids.rs
#	docs/target-app-parity.md
2026-11-13 10:18:23 -07: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 487da4b52a Type: area type lines stay inside a frame that pinches in within their band
A line in area type of a shape other than a rectangle was measured at five heights of its band, so an edge that pinched in between two of them (a notch tip at y = 47.5 in a 40..60 band) left the line running outside the frame. The frame's intervals over a band are now the intervals at its middle minus the x range of every edge within the band, which is exact for any number of vertices, holes and crossing contours.

Edges that contours meet along do not shorten a line: horizontal ones count by coverage, and collinear ones by overlap, and an edge that only touches the top or bottom of the band is left out. Rectangles keep their fast path.

Text wrap shapes were sampled at the same five heights, so a wrap object whose tip reached into a band between two of them let the line run across it. A line now keeps clear of every x at which a wrap shape, grown by its offset, lies anywhere in the band: the shape's intervals at the middle of the band plus every edge within it. With Invert Wrap, the band is cut into pieces a quarter of the offset tall (64 at most), and a line keeps to the x within the offset of the shape at the heights each piece's offset window holds, so it stays inside the grown shape over the whole band; an offset of 0 takes the shape's intervals over the whole band.

A frame less than 0.0002 pt tall no longer panics when a line is measured in it.
2026-11-13 09:15:01 -08:00
dotCooCoo c9734dc7b9 Merge main into fix/status-linked-files-keeps-notes 2026-11-13 09:14:37 -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 704b5793fc Merge main into fix/flatpak-clipboard-images 2026-11-13 09:09:59 -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
BFlat 6a1b4c42de Merge pull request #7040 from dotCooCoo/fix/nested-link-ids
Links: list and manage linked art in symbols, graph designs, patterns and masks by id
2026-11-13 23:58:20 +07:00
BFlat a9613094a9 Merge pull request #7171 from dotCooCoo/fix/ai-export-art-bounds
.ai export: a .ai of the art's bounds carries no editing data
2026-11-13 23:58:07 +07:00
BFlat b024fe75b6 Merge pull request #7102 from dotCooCoo/feat/layers-drag-autoscroll
Layers: layer and object rows held near the list's top or bottom edge scroll a long list
2026-11-13 23:57:46 +07:00
BFlat a8a4730eff Merge pull request #7142 from dotCooCoo/feat/ai-image-warp-live
.ai: an image's Warp effect is saved and opened live
2026-11-13 23:57:33 +07:00
dotCooCoo 67601f748f Merge main into fix/color-gamut-cache-profile 2026-11-13 08:57:25 -08:00
BFlat 910f3a7710 Merge pull request #7077 from Hassan1305/feat-6304-font-height-icons
Character panel: icons for the font height references (#6304)
2026-11-13 23:57:22 +07:00
dotCooCoo a8d6e06860 Merge main into feat/monitor-profile-windows-linux 2026-11-13 08:56:32 -08:00
dotCooCoo 9bf05dece4 Image Trace: blurred edges no longer trace as rings of in-between colors
A blend pixel took a side of its edge only when the picture's own colors
on both sides lay within 2 px of it. With a limited palette, an edge
blurred over more than about 4 px kept the middle of its ramp in the
palette's in-between tones, which traced as rings of thin paths around
the edge.

With a Limited palette of at most 16 colors (3 Colors, 6 Colors,
16 Colors) and in Grayscale with at most 16 grays (Shades of Gray),
settled_wide looks for the own colors within 8 px, pairs the 8 of them
nearest to the pixel's color, and moves the pixel to the nearer side of
the best pair when that pair explains it twice as well as its own color,
or when it lies between the two and off the line between them by less
than 0.15 of their distance apart. These values are VectorCraft's own.
Of two pairs that explain it equally well it takes the closer one. A
pixel keeps its color when an own pixel of that color lies within 4 px
or when its color is one of the pair. Grayscale compares lightness. The
colors within reach are kept as bitsets for a strip of 7 columns at a
time.

High Fidelity Photo, Low Fidelity Photo, Black and White and the other
palettes keep the 2 px rule unchanged.

On the quality harness, blurred and scanned flat art with 6 and
16 Colors goes from 6 to 9 paths to 3, and from 0.23 to 0.26 px off to
0.07 to 0.11. Askew shapes take up to 11 px more area in colors the art
does not have, thin lines with 16 Colors lose more (largest distance
23.5 to 45.3 px), and Shades of Gray traces thin lines in 7.3 paths
where it took 5.3.

Noisy images settle more of their pixels: the dense grouping test
traces 150 x 150 noise instead of 120 x 120 to keep more than its 500
paths.

Covers the limited-palette part of #1911 (leogrox).

Docs: docs/target-app-parity.md, docs/image-trace-quality.md.
2026-11-13 08:54:47 -08:00
dotCooCoo bed4e9aa4b Merge main into fix/graph-series-group-appearance 2026-11-13 08:50:06 -08:00
dotCooCoo ed046a7c4f Merge main into fix/graph-series-paint-other-commands 2026-11-13 08:49:45 -08:00
dotCooCoo c44392a4d0 Merge main into fix/graph-series-paint-visible-rows 2026-11-13 08:49:20 -08:00
BFlat 10c84a909e Merge pull request #7173 from dotCooCoo/fix/assigned-profile-follows-front-document
Color: assigned profiles follow the document in front
2026-11-13 23:45:03 +07:00
dotCooCoo d92e63257b Merge main into fix/smooth-fidelity-screen-pixels 2026-11-13 08:44:25 -08:00
dotCooCoo b018a849ab Arrowheads: keep agents to the arrowheads file inside their read roots
Under automation read roots, stroke.arrowheads.list gives `file` as
null for an arrowheads file outside them and names it by its file name
in `error`. stroke.arrowheads.edit and stroke.arrowheads.reload refuse
such a file with the root-check error, naming it by its file name:
edit makes no folder or file and neither opens nor activates the file,
and reload does not read it. Inside the roots, edit opens the file
under the client's roots. The Stroke panel menu is not confined.
2026-11-13 08:42:37 -08:00
dotCooCoo 0663c76be1 Envelopes: a partly transparent image keeps its alpha where its warp pieces overlap
An envelope or the Warp effect cuts an image into triangle pieces that
reach a little past their edges, so neighbors overlap and no seams show.
On an image with alpha, the overlap drew that alpha twice: a 50% image
came out about 75% opaque along every cell edge and diagonal.

- The pieces now form a knockout group, and each piece is marked as a
  warp piece (`warp_piece`) whose knockout shape is its whole triangle,
  so where pieces overlap only the top one draws.
- The canvas knocks each piece out by its triangle at full coverage.
- SVG and PDF draw the pieces of a partly transparent image (its pixels,
  opacity, color mask or colorize paint) through one mask each, of the
  later triangles that reach it; pixels on a triangle's antialiased edge
  come out slightly lighter there. Pieces of an opaque image are drawn
  one over another as before.
- SVG writes one copy of the image for pieces cut by opacity masks
  (Preserve Shape Using Transparency) too.
- A cell count past the generated-object budget makes an empty group
  instead of overflowing.

Docs: docs/target-app-parity.md.
2026-11-13 08:38:48 -08:00
dotCooCoo d1c8ff78de Context menu: Smooth… on a selected path opens the Smooth slider
A right-click on a selection that holds a path (a path, a group of
paths, a compound path, or anchors selected with Direct Selection) lists
Smooth…, after Join and Average… and the anchor entries when those are
listed. It opens the slider that Object › Path › Smooth opens, which
previews the result until OK keeps it as one undo step. With nothing,
only type or a group of type selected, Smooth… is not listed.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-13 08:35:40 -08:00
dotCooCoo 6b5e596de0 Color: the out-of-gamut warning updates after a profile change
The Color panel and Color Picker cached their out-of-gamut warning and
correction for the last color only. Assigning the document another CMYK
profile, removing it, or changing the working CMYK space in Color
Settings left the old warning showing until the color changed.

The cache entry now also holds the active color settings and the
registered profiles' generation, so the warning is checked again when
either changes. The comments and the color.gamutCheck params text name
the CMYK space actually used: the document's assigned CMYK profile, else
the working CMYK space.
2026-11-13 08:35:13 -08:00
dotCooCoo 3c463fa5fb Brushes: strokes draw with the first of two brushes with one name
When a document holds two brushes with the same name, the canvas drew
a stroke that names it with the last of them, while brush.get, Brush
Options, expanding, outlining and the SVG, PDF, EPS and .ai exports
took the first. Document Info gave the last one's type, and every row
of that name in the Brushes panel previewed and dragged the last one.

The renderer, Document Info and the Brushes panel now take the first
brush of a name, so the canvas, the panel and the exports agree.
2026-11-13 08:34:40 -08:00
BFlat a2adbf61e6 Merge pull request #7106 from dotCooCoo/feat/custom-toolbar-dock
Toolbars: toolbars made with New Toolbar dock along the window's edges
2026-11-13 23:32:06 +07:00
BFlat 7f92d8ffc2 Merge pull request #7149 from dotCooCoo/fix/document-info-labels-translated
i18n: Document Info shows every category title and count label in the interface language
2026-11-13 23:31:57 +07:00
BFlat e74c2d04df Merge pull request #7083 from dotCooCoo/fix/simplify-line-keeps-tangent
Simplify: a refitted curve becomes a line only when its joins stay smooth
2026-11-13 23:31:37 +07:00
BFlat 4c072b320c Merge pull request #7136 from dotCooCoo/fix/command-batch-artboards
Artboards: a batch's Fit to Artwork Bounds step fits every selected artboard
2026-11-13 23:31:28 +07:00
BFlat 538e471783 Merge pull request #7163 from dotCooCoo/feat/live-paint-bucket-cursor
Live Paint: the bucket shows a filled bucket over a face and a brush over an edge
2026-11-13 23:31:19 +07:00
BFlat d3bb35ff61 Merge pull request #7154 from dotCooCoo/fix/3d-walls-depth-sort
3D: fill walls and dashed or profiled strokes draw in depth order where they overlap
2026-11-13 23:31:09 +07:00
BFlat b7a9bd6e29 Merge pull request #7169 from dotCooCoo/fix/join-coincident-one-pass
Join: merge a run of coincident anchors in one pass over the path
2026-11-13 23:31:00 +07:00
dotCooCoo bc9280967a Image Trace: staircases of longer steps in smooth areas trace as slopes
Where a band border in a gradient or a shaded area runs at a slant, its
pixels form a staircase whose steps are two or more pixels long. When one
piece of outline also runs along a sharp edge in the image, that piece as
a whole is not the contour of a smooth area, and the outline kept a
corner at every step, so the border was traced as a series of right-angle
steps.

subpixel::outline now reads a run of three or more edges of 1 to 8 px,
each turning one way where it starts and the other where it ends, with no
junction on it, as a staircase when the run's own cracks pass
subpixel::low_contrast. A run is cut at any step whose own cracks show half
of the step between the two colors or more. Its edges are steps, as
one-pixel steps already were: no corner where two of them meet, and one
point at the middle of each, moved by the mean of its cracks' shifts. The
corner where a run meets an edge that is not a step stays, so a straight
edge longer than 8 px next to a staircase stays straight. Two one-pixel
steps inside a run are read as part of the slope and do not make a notch.
Longer steps, staircases along a sharp edge and Black and White traces
keep their corners. The runs are the same from either side of the border.

On gradient and photo inputs most traces take fewer anchors. Some rows get
worse: 6 Colors on gradient and photo leaves a gap of 0.9 px (was 1.2) and
an overlap of 1.0 px (was 0.8), High Fidelity Photo with Gradients on a
blurred input takes 50 anchors (was 47), and on a synthetic 800 px photo
the 95th percentile of High Fidelity Photo's pixel difference rises from
6.33 to 6.67 and 16 Colors leaves 2 staircases of 2 px steps or more where
it left none.

Docs: docs/target-app-parity.md, docs/image-trace-quality.md.
2026-11-13 08:29:28 -08:00
dotCooCoo 137bc11a2c Merge main into fix/smooth-fidelity-screen-pixels 2026-11-13 08:28:42 -08:00
dotCooCoo bc7eddeb5d Expand: type reports a gradient only when Fill expands one of its outlines
object.expand.info reported a gradient (and a fill) to expand for any
type whose characters have a gradient fill. Expand outlines type first,
and each outline then holds the run's gradient on its own box, so a
gradient that repeats or reflects in more than 256 runs across every
outline stays a gradient: the dialog enabled options that changed
nothing.

- The query now measures the paths type.createOutlines makes of the
  glyphs, ruby glyphs and underline and strikethrough bars, each with
  its run's paint, and reports a gradient when one of them expands. It
  lays the text out only when a run has a gradient fill, stops at the
  first outline that expands, and does not change the document. Type in
  symbol art, blends and repeats goes through the same check.
- docs/mcp.md says when gradient is false; docs/target-app-parity.md
  gets an evidence line.
2026-11-13 08:27:09 -08:00
dotCooCoo 5f77b65aa2 Gradients: Hue Clockwise and Hue Counterclockwise mix stops around the hue wheel
The Gradient panel's Method menu gets two methods of VectorCraft's own
beside Classic and Perceptual. They mix a linear, radial, conic or
diamond gradient's colors around the hue wheel: hue, saturation and
brightness each change evenly from one stop to the next, and the hue
angle keeps one direction, as the fixed-direction rules of
https://www.w3.org/TR/css-color-4/#hue-interpolation do. Hue Clockwise
lowers the angle (red, magenta, blue), which is clockwise on the color
wheels, and Hue Counterclockwise raises it (red, yellow, green).

Two stops whose hues are less than half a color level apart count as
one hue and keep it, so a color and a tint or shade of it do not turn
the whole wheel. A stop whose RGB numbers are less than half a color
level apart is gray and takes the hue of the stop it mixes with, and
black takes that stop's saturation too.

- `paint.editGradient` and `paint.setFill` take `method: hue-clockwise`
  or `hue-counterclockwise`, in any case, with a space or an underscore
  for the hyphen or without it; other spellings are an error. The native
  file writes `HueClockwise` and `HueCounterclockwise`.
- The canvas and raster exports draw the gradient through stops on its
  curve, within half a color level.
- Reverse turns the method the other way with the stops, and a Reflect
  spread is repeated from the curve's stops, so both keep the colors.
- SVG and PDF write the gradient as an RGB image, CMYK and spot color
  stops included, with the spot warning. EPS writes a shading in process
  colors with a warning for spot stops. A legacy .ai file and the .ai
  editing data get the colors between the stops as stops of their own,
  in process colors, at most 256, with a warning. CSS gets those stops
  too.
- The `method` error names the four values, in every catalog.
- The Method menu is as wide as its longest name in the interface
  language, within the panel.

Docs: docs/mcp.md, docs/plugins.md, docs/target-app-parity.md, ROADMAP.
2026-11-13 08:26:57 -08:00
dotCooCoo d41cf0d053 Merge main into feat/objects-on-path-detach 2026-11-13 08:26:49 -08:00
dotCooCoo 9b027bb43a Merge main into fix/image-icc-16bit-kept 2026-11-13 08:25:53 -08:00
dotCooCoo c9d85d61db Merge main into feat/actions-record-into-selected 2026-11-13 08:24:12 -08:00
jmverges 24bcdb5116 Merge remote-tracking branch 'upstream/main' into fix/page-check-typed-envelopes
# Conflicts:
#	crates/eps/src/import/ai.rs
#	docs/file-format-parity.md
#	docs/target-app-parity.md
2026-11-13 16:22:41 +00:00
dotCooCoo e11c21ce98 MCP: an old app's window capture goes through a private temporary folder
An app that does not send the image back wrote the window capture to
vectorcraft-window-<pid>.png directly in the system's temporary folder.
On a shared Unix temporary folder another user could put a link at
that name, and the app would then overwrite the file it points to. A
failed read also left the file behind.

The capture now goes to window.png in a new folder made for it inside
the temporary folder (owner-only on Unix; a name that is taken is
skipped). The folder is removed after the read, also when the app or
the read fails.
2026-11-13 08:19:07 -08:00
BFlat 9395fb9cec Merge pull request #7160 from dotCooCoo/fix/svg-image-orientation
SVG import: raster images follow their EXIF or TIFF orientation
2026-11-13 23:16:49 +07:00
dotCooCoo 87ad60ec42 Merge main into fix/graph-series-group-appearance 2026-11-13 08:15:48 -08:00
BFlat ebd7568b69 Merge pull request #7124 from dotCooCoo/fix/control-bar-trailing-separator
Control bar: no separator is left at the end of the bar when the controls after it are hidden
2026-11-13 23:15:28 +07:00