Commit Graph
423 Commits
Author SHA1 Message Date
dotCooCoo df155042d4 Merge main into feat/image-color-mask 2026-11-12 17:40:13 -08:00
BFlat 7430676ff0 Merge pull request #7000 from dotCooCoo/test/envelope-ink-type
Tests: envelope ink tests set type in a test face and check its outlines
2026-11-13 08:30:52 +07:00
BFlat ae014cc896 Merge pull request #7002 from dotCooCoo/fix/3d-plain-fill-keeps-compound
3D: a fill or stroke without the effect keeps an even-odd path's holes
2026-11-13 08:29:49 +07:00
BFlat 23825d2943 Merge pull request #6993 from dotCooCoo/fix/revolve-plugin-item-context
Effects: a fill's 3D Revolve tells the plug-in effect before it which fill it sits on
2026-11-13 07:48:14 +07:00
dotCooCoo 9b65bf2708 3D: a fill or stroke without the effect keeps an even-odd path's holes
With a 3D effect on one fill or stroke, the object's other fills and
strokes were drawn as nonzero paths, so an even-odd compound path or
path filled its holes in them. Extrude & Bevel, Inflate, Rotate,
Revolve and their Classic kinds now draw those items with the source's
fill rule, through the group code's shape_node helper.
2026-11-12 16:28:54 -08:00
dotCooCoo b1a35f8428 Tests: envelope ink tests set type in a test face and check its outlines
The three envelope ink tests set "Type" in the default family and
unwrapped ink_bounds(). Without bundled fonts, craft-fonts or system
fonts (vectorcraft-effects' tests on a bare Linux host), no face draws
and the effects test panicked at the unwrap.

Each test now adds the Heighttest Sans face built from the bundled
Source Sans 3 (test-fonts feature, now also on vectorcraft-effects'
dev-dependency), sets the text in it, and computes the expected box
from the layout's glyph outlines. It checks that envelope_ink gives
that box as well as the before and after registration boxes, so a
change to the registered measure fails the test.
2026-11-12 16:23:10 -08:00
dotCooCoo 3570efa0a9 Effects: a fill's 3D Revolve tells the plug-in effect before it which fill it sits on
The per-item Revolve loop passed a copy of each fill or stroke to the
geometry context, which finds the item by identity in the object's
appearance, so a plug-in effect before the Revolve was told it sat on
the object. Each copy now keeps its original item beside it.
2026-11-12 15:48:59 -08:00
dotCooCoo d5a02ee872 Merge main into feat/3d-graphics-planar 2026-11-12 15:19:09 -08:00
BFlat 1461bb2123 Merge pull request #6963 from dotCooCoo/fix/transform-effect-moves-member-raster-effects
Effects: a group's or object's Transform effect moves the raster effects inside it
2026-11-13 06:11:24 +07:00
dotCooCoo 505d11dae5 3D and Materials: straight graphic sides count once, and graphic handles stay with the drawn art
- Under a perspective, the shared 400,000-element limit of a 3D effect's graphics now counts
  what each path draws once mapped: a straight side stays one element, and only curves count
  as their 8 pieces. Two copies of a 30,000-sided polygon now both draw. The symbol check
  counts the same way, and without a perspective it counts each curve once.
- The Materials tab's handles on the artboard are hidden while a geometry effect (such as
  Transform) comes after the 3D effect of a selected object, since that effect draws the
  graphics away from the handles.
- A press on the canvas while its context menu is open only closes the menu, and no longer
  grabs a graphic's handle.
- A symbol instance inside a graphic's art shows its changes to a dynamic symbol, and a symbol
  with 9-slice guides is placed through them, as the canvas draws the instance. Paths that
  cross a guide are cut at it.
2026-11-12 15:02:34 -08:00
BFlat 822d4101b5 Merge pull request #6890 from dotCooCoo/feat/3d-expand-as-wireframes
3D and Materials: Expand as Wireframes turns a 3D object into its edges as paths
2026-11-13 05:23:34 +07:00
BFlat e0a8fdae5b Merge pull request #6958 from dotCooCoo/fix/bevel-join-visual-bounds
Transform: a bevel stops at the corners of the segments it joins with Use Preview Bounds
2026-11-13 05:23:09 +07:00
BFlat c2f97d2efa Merge pull request #6961 from dotCooCoo/fix/gradient-repeat-runs
Gradients: a repeated gradient with many stops keeps every run
2026-11-13 05:22:59 +07:00
dotCooCoo cafbaec053 Merge main into fix/transform-effect-moves-member-raster-effects 2026-11-12 14:02:27 -08:00
dotCooCoo 554d86765a Merge main into feat/3d-graphics-planar 2026-11-12 13:46:05 -08:00
dotCooCoo 367ec9998e 3D and Materials: graphic curves whose control points pass behind the eye still draw
Under a perspective, a piece of a graphic's curve that lies in front of the eye while one of its control points does not is halved until its control points map, instead of dropping the piece. A curve that dips behind the eye between the ends of its pieces is still clipped. The extra pieces count toward the effect's element limit, and a path that passes the limit is not sent to the clipping.
2026-11-12 13:32:37 -08:00
dotCooCoo 312d4a3914 Merge main into feat/image-color-mask 2026-11-12 13:30:24 -08:00
BFlat 3434e46240 Merge pull request #6825 from dotCooCoo/feat/custom-arrowheads
Stroke: custom arrowheads from an arrowheads file join the arrowhead menus
2026-11-13 04:25:55 +07:00
dotCooCoo c31af9898e 3D and Materials: one element limit per effect, pasted graphics keep their symbols, safer handle drags
- The graphics of one 3D effect share one limit on drawn path elements
  across every member and opacity group, so a group effect with many
  members no longer multiplies the limit.
- Copy and paste follow the symbols that mapped graphics name, in object
  and graphic style appearances: a document without the symbol gets it,
  and one whose symbol of the same name is other art gets the copied
  symbol under a free name, which the graphic then names.
- A face's inverse map refuses the points its page map refuses, so a
  handle drag cannot save an offset whose handle is not shown.
- A press on a graphic's handle with Space held, or with the Hand, Zoom
  or Rotate View tool, pans, zooms or turns the view.
- A handle drag ends, keeping its preview, when the selection or the
  document changes while the pointer is down.
2026-11-12 12:42:59 -08:00
BFlat 87485971b0 Merge pull request #6959 from dotCooCoo/fix/item-transform-later-raster-effects
Effects: a fill's or stroke's own Transform effect leaves the effects listed after it unscaled
2026-11-13 03:28:16 +07:00
dotCooCoo bd973548d7 Effects: an object's Transform effect moves its fills' raster effects beside a fill's 3D effect
When only a fill or stroke has an Extrude & Bevel, Inflate, Rotate or Revolve effect, the object's
Transform effects reshape the path, and now the raster effects of every fill and stroke follow them
too, as they do on an object without a 3D effect: a shadow after the fill's 3D effect and another
fill's shadow scale and turn with the art.
2026-11-12 12:21:38 -08:00
dotCooCoo 197d033b4f Merge main into feat/custom-arrowheads 2026-11-12 12:04:01 -08:00
BFlat 86f8a6dcbd Merge pull request #6927 from dotCooCoo/fix/roughen-size-zero
Effects: Roughen at size 0 keeps the outline
2026-11-13 02:46:39 +07:00
dotCooCoo 043e517ef6 Effects: a group's or object's Transform effect moves the raster effects inside it
A Transform effect on a group reshaped its members' paths but left the
raster effects inside it at their own values: a member's Drop Shadow
kept its 10 pt offset under a 200% Transform effect, and with Scale
Strokes & Effects on, it scaled by the mean scale without turning. The
same held for the raster effects of an object's own fills and strokes
under the object's Transform effect.

- A Transform effect that moves the art once now transforms the raster
  effects of a group's members (their own and their fills' and
  strokes'), of nested groups, of the art of the linked opacity masks
  in it and of an object's fills and strokes, after the members' own
  Transform effects: sizes scale by the mean scale and a Drop Shadow's
  offset turns, reflects and scales. A raster effect that a member's
  own Transform effect with copies or random holds back keeps its value
  through that effect and still transforms with the group's. Scale
  Strokes & Effects does not scale them a second time. A group's or
  object's Transform effect that makes copies or moves at random leaves
  them as before.
- The canvas, the redraw region, export, Expand Appearance, Object ›
  Expand and Outline Stroke read the same values.
- The Transform effect's params text and docs/target-app-parity.md
  describe the rule.
2026-11-12 11:35:03 -08:00
dotCooCoo 0691e1acbe Gradients: keep every run of a repeated gradient with many stops
When a repeated or reflected gradient needs more stops than a limit
allows, each run now leaves out the stops that change its colors
least, so every run is still written, instead of writing fewer runs
and padding the rest with the last color. Each run keeps its ends,
sharp steps, swatch-linked stops, tint and CMYK ink changes and spans
shaped by an off-center midpoint, and the colors are compared as the
gradient's method mixes them. This applies to gradients along or across
a stroke, CSS conic gradients, and PDF, EPS and .ai export.

- Outline Stroke's mesh fits the gradient to at most 1,024 stops, and
  splits a stroke into several meshes when one would hold more than
  2^20 points.
- Object > Expand leaves a gradient as it is when the box needs more
  than 256 runs, instead of expanding only the first runs.
- CSS writes the stop positions of repeating gradients with four
  decimals, so the period stays in place across the box.
- A swatch chip of a gradient that repeats gets at least four grid
  cells a run along each side (at most one a point, and 128), so it
  does not show one color when the count matches the chip's cells.
- docs/mcp.md states the stop limits and the PDF import sample budget.
2026-11-12 11:30:44 -08:00
dotCooCoo 5f7d4d3922 3D and Materials: graphics take the opacity mask of a cap in a transparent gradient
Under a colored light a surface in a gradient with transparent stops carries an
opacity mask. A graphic drawn on such a cap now takes that mask, and each such
solid gets a graphic of its own, so a graphic is hidden where its cap is.
2026-11-12 11:27:38 -08:00
dotCooCoo be50e5328b Transform: a bevel stops at the corners of the segments it joins with Use Preview Bounds
A bevel join, and a miter join whose miter limit bevels a corner, end the
stroke at the outer corners of the two segments they join. The visual box
measured such a corner as if the stroke reached half its weight round it:
a 40 x 100 pt triangle with a 20 pt stroke and a miter limit of 2 measured
10 pt above its tip, where the stroke paints about 1.96 pt above it. Use
Preview Bounds, and the alignment and sizing that use it, took that box.

- With bevel and miter joins, closed paths and open paths with round or
  projecting caps are now measured along their segments, as butt-capped
  open paths already were, with the miter tips the limit keeps and the
  caps added. Round joins still reach half the weight round each corner,
  the round caps of dashes, which can end at a corner, reach half the
  weight all round, and the projecting caps of dashes reach the diagonal
  of their square. The visual box of
  such a stroke can be smaller than before and is never larger. A path
  with no width, such as a rectangle sized to 0 pt wide, turns back at
  its ends, where the join bevels, so its box no longer reaches past them.
- object.setBounds with Use Preview Bounds measures an object as drawn
  when a beveled or mitered corner, or a butt cap, is on a segment at a
  slant. Before, a closed path with miter spikes the limit keeps missed
  the size typed when the width and height were out of proportion.
- A click past a bevel or a butt cap, by more than the click tolerance,
  no longer selects the stroke. Strokes with a width profile or with
  dashes that have round or projecting caps, and the ends of curves that
  bend more tightly than half the weight, keep the round reach.
- Paths measured along their segments skip the segments that cannot
  reach past the box measured so far, which starts with the path's own
  bounds.
2026-11-12 11:04:25 -08:00
dotCooCoo d46d8bb1fe Merge main into feat/3d-graphics-planar 2026-11-12 11:00:58 -08:00
dotCooCoo 85b3740cca Effects: a fill's or stroke's own Transform effect leaves the effects listed after it unscaled
Effects apply in the order they are listed, so an effect listed after a
Transform effect applies to the art the Transform effect made, at its
own values, as an object's later effects do after the object's
Transform effect. With Scale Strokes & Effects on, a fill's or stroke's
own Transform effect scaled the effects listed after it in that fill's
or stroke's list by the mean scale: a 300% Transform turned a 5 pt
Offset Path after it into a 15 pt one, and a Drop Shadow after it took
three times its offset. With Transform Patterns or Scale Strokes &
Effects on, an SVG filter after it also had the origin of its user space
moved.

- The effects after such a Transform effect now keep their values, on
  the transformed art and on each copy, on every fill and stroke a
  live-effect plug-in paints the piece with, and when the copies are
  counted against the effect budget.
- With Transform Objects off, the effects before it keep their values
  too.
- The Transform params text, docs/target-app-parity.md and docs/gaps.md
  describe the rule, and drop the note that listed an object's later
  effects keeping their distances as a shortfall.
2026-11-12 10:59:21 -08:00
dotCooCoo c3f7357ec2 3D and Materials: map graphics onto the planar faces of 3D objects
The Materials tab of the 3D and Materials panel lists the document's
graphics (symbols). Add as Single Graphic keeps the selected art as a
new symbol (threeD.addGraphic), and clicking a thumbnail maps it onto
the front of each selected object's 3D effect (threeD.mapGraphic) in
one undo step. The tab lists the mapped graphics with Face, X, Y, Scale
and Rotation and Remove Graphic, and a handle on the artboard drags a
graphic over its face.

The graphics are the new `graphics` parameter of threeD.extrude and
threeD.rotate: drawn on Extrude & Bevel's front and back caps and on
the Plane type, turned, foreshortened, twisted and tapered with the
face, shaded with its light and clipped to it. Symbol renames follow
into the graphics, and Select All Unused counts a symbol a graphic uses.

Docs: docs/mcp.md, docs/three-d.md, docs/effects-parity.md.
2026-11-12 10:31:37 -08:00
dotCooCoo 70a4c44033 Effects: every fill a painted effect returns follows the Transform effect
A painted live effect on a fill can return a piece with several fills,
each a copy of that fill with its effects. A Transform effect after it
moved the piece, but only the first returned fill's raster effects
followed it: with a Drop Shadow before the painted effect and a 200%
Transform effect after it, the first fill's shadow offset became 20 pt
and the second fill's stayed 10 pt. Each returned fill and stroke now
takes the followed values.
2026-11-12 10:08:53 -08:00
dotCooCoo 00e07698b1 Image Tools: a live color adjustment recolors an image whose masked pixels are missing
When the document does not hold a color mask's masked pixels, the image is
drawn from its own pixels. A live color adjustment now recolors those pixels
and drops the mask on the recolored copy, instead of pointing the image at
the missing masked pixels, which drew nothing and left Expand Appearance with
an image whose pixels were not in the document.
2026-11-12 09:59:11 -08:00
dotCooCoo 556123e691 3D and Materials: Expand as Wireframes keeps a layer's path guides and cuts Revolve's art for Revolve
A layer expanded to its edges keeps the paths made into guides inside it, as it keeps its ruler
guides. A clipping group with Revolve and Rotate is cut for Revolve, the effect the canvas draws,
so its fills go in one piece and art with more fills than a 3D effect takes still expands.
2026-11-12 09:47:53 -08:00
bflatastic cfce336215 Dashes fitted to corners take a subpath with no segments
An HP-GL wedge of almost no radius drawn with an adaptive line type
(LT-1;EW0.000001,0,0;) leaves a lone moveto. Fitting the dashes to its
corners indexed the empty segment list and panicked while rendering. Such
a subpath now uses the exact plan, which has no runs to fit.

Found by import_fuzz::random_hpgl_instructions_never_panic.
2026-11-13 00:47:30 +07:00
dotCooCoo fe34c2b818 Merge main into feat/custom-arrowheads 2026-11-12 09:23:20 -08:00
dotCooCoo 87d1f0b3d7 Merge main into fix/transform-effect-scales-raster-effects 2026-11-12 09:23:19 -08:00
dotCooCoo 8d27e53115 Merge main into feat/image-color-mask 2026-11-12 08:56:33 -08:00
dotCooCoo 91e1412d77 Effects: Roughen at size 0 keeps the outline
With Size 0, Roughen moved no point but still replaced each segment
with its Detail's points joined by straight lines (Corner) or by a
smoothed curve through them (Smooth), so a curved path lost its shape.

At Size 0 each segment is now split at the Detail spacing into pieces
of equal length on the segment itself, with `pathtools::insert_at`: a
straight segment gets corner points without handles, and a curve gets
smooth points whose handles follow it. The path keeps its outline with
Smooth or Corner points alike. The params text of `distort.roughen`
describes size 0.

`insert_at` now counts the anchors added before each returned mark in
one pass. It counted them again for every split segment, which took
quadratic time on a path with many split segments. Its results are
unchanged.

Existing documents with a Roughen of Size 0 on a curved path, or with
Smooth points, draw differently.

This is VectorCraft's own rule. What Roughen does is described in
https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/distort-and-transform-effects-in-illustrator.html
2026-11-12 08:55:07 -08:00
BFlat cc6509827d Merge pull request #6878 from dotCooCoo/fix/opacity-mask-follows-reshape
Opacity masks: linked masks follow group effects, the Free Transform distort and envelopes
2026-11-12 23:10:24 +07:00
BFlat 3df161a972 Merge pull request #6903 from dotCooCoo/feat/gradient-repeat
Gradients: repeat or reflect a gradient's stops a set number of times
2026-11-12 23:10:02 +07:00
dotCooCoo e54519d6bd Merge main into feat/image-color-mask 2026-11-12 07:51:12 -08:00
dotCooCoo 5a8cb30259 3D and Materials: Expand as Wireframes reshapes edges by the effects after the 3D effect drawn
With two 3D effects on an object, the geometry effects after the first one
reshaped the edges, even when the second one (Revolve, which is drawn first)
built the faces. A geometry effect between the two then moved the profile and
the edges both. The edges now take only the effects after the 3D effect that
built them.
2026-11-12 07:44:35 -08:00
dotCooCoo 55a80ca480 3D and Materials: Expand as Wireframes looks up swatches for plug-ins after a fill's 3D effect
A live-effect plug-in after a fill's or stroke's 3D effect (Extrude & Bevel,
Inflate, Rotate or Revolve) that links its pieces to a swatch or pattern was
run without the document, so its result was refused and the edges kept the
shape before it. The document now goes to those effects, as it does for the
effects after an object's 3D effect.
2026-11-12 07:31:39 -08:00
dotCooCoo 63a36fec51 Image Tools: geometry effects keep a color mask
An image with a geometry effect (Transform, for example) is drawn from a
copy of the image, and that copy now keeps the image's color mask. The
canvas and outputs show the masked pixels, and Expand Appearance keeps
the mask on the image it makes.

Test: a_transform_effect_keeps_the_mask.
2026-11-12 07:26:48 -08:00
dotCooCoo 2911958414 Image Tools: live color adjustments and envelope warps keep a color mask
- A live color adjustment recolors a masked image's masked pixels, on the
  canvas and in outputs that apply the adjustment, so the colors the mask
  hides stay hidden. Before, the adjustment recolored the image's own
  pixels and the hidden colors showed again.
- An envelope warp gives each piece of a masked image the image's color
  mask, so the warped image shows its masked pixels.

Tests: a_live_color_adjustment_keeps_the_hidden_colors_hidden and
an_envelope_warp_draws_the_masked_pixels.
2026-11-12 07:09:30 -08:00
BFlat 558fec9f96 Merge pull request #6838 from dotCooCoo/fix/3d-transparent-gradient-member-order
3D: a solid in a transparent gradient keeps the shared face order
2026-11-12 22:00:51 +07:00
dotCooCoo b4fbcc5672 Opacity masks: a strokes-only Transform effect scales a member's mask
With Transform Objects off, a group's Transform effect scales the
strokes and moves the patterns of a member's linked mask as it does
the member's, instead of leaving the mask as it was.
2026-11-12 06:58:57 -08:00
dotCooCoo 5036099b51 Effects: a copying Transform effect after 3D holds raster effects back; pixel filter sizes follow
- A Transform effect that makes copies or moves at random, listed after
  an Extrude or Revolve effect, now keeps the raster effects before the
  3D effect at their own values, as it does without the 3D effect.
  Before, the 3D art kept a Drop Shadow scaled by a Transform effect
  between them.
- In a fill's or stroke's own effects, such a Transform effect also
  keeps the raster effects before it from following an earlier
  Transform effect with Transform Patterns on, and the Transform
  effects between them.
- Colored Pencil's pencilWidth and Paint Daubs' brushSize are distances
  in document units, so a Transform effect after them and Scale Strokes
  & Effects scale them as they scale Palette Knife's strokeSize. The
  Paint Daubs params text names the unit.
2026-11-12 06:57:26 -08:00
dotCooCoo 891ca3859e Merge main into fix/opacity-mask-follows-reshape 2026-11-12 06:54:42 -08:00
dotCooCoo 91d705e155 Gradients: repeat or reflect a gradient's stops a set number of times
A linear, radial, conic or diamond gradient has a spread: Pad (the
default, as before), Repeat or Reflect, with a count of 1 to 100
(VectorCraft's own limit). Repeat and Reflect run the stops that many
times along the vector (around the turn for a conic) and keep going past
both ends, Reflect running every other run backwards, as SVG's
spreadMethod does.

The Gradient panel has a Spread menu and a Repeats field, and the
Gradient tool marks where the runs meet. paint.setFill and
paint.editGradient take `spread` and `repeats`; bad values are errors.
Presets, swatches, blends, strokes along or across a path, Outline
Stroke and Object › Expand follow the spread; Expand gives each run the
same whole number of objects, and Outline Stroke's mesh keeps the color
on each side of a sharp step between two stops at one offset.

SVG writes and reads spreadMethod: SVG import now reads a repeating or
reflecting gradient as one that repeats or reflects once, instead of
expanding its stops over the shape. CSS writes repeating gradients.
PDF writes a conic's sweep shading over one run. PDF, EPS, legacy .ai
and the .ai editing data write the other kinds with their stops
repeated across the shape: at most 256 runs and 16,384 stops (256 stops
in .ai files), and for a radial or diamond no farther than 65,536 runs
from its center, with a warning past those. PDF import now finds sharp
steps in any axial or radial shading instead of blurring them.

Docs: docs/mcp.md (Spread), docs/target-app-parity.md, docs/gaps.md.
2026-11-12 06:52:38 -08:00