Commit Graph
4041 Commits
Author SHA1 Message Date
dotCooCoo 8aa8bb4d9d Render: a mask art root, symbol art root or thumbnail keeps its own opacity mask
The renderer drew the root node of an opacity mask's art, of placed symbol
art, of an art brush piece, of an Image Trace source image and of a node
thumbnail without its own opacity mask. Make Opacity Mask uses the top
object as the mask art, so a mask made from an object that already had an
opacity mask ignored that inner mask. These roots now draw through the
same masked group as any other node, still drawn whatever their visibility
and bounds, and an incomplete inner mask still leaves out the outer object.
2026-10-31 19:43:15 +07:00
dotCooCoo b1ed0a512b Brushes: opacity masks on art brush pieces bend with the art
An art, scatter or pattern brush piece whose art carries a linked opacity
mask was bent along the path while the mask's art kept its place in the
brush's own coordinates, so the mask no longer covered the piece. The
mask's art now bends and has its strokes scaled with the object it
masks, on bent runs, on straight runs and where the brush narrows along
the path. A mask whose art cannot bend becomes an empty group. The
anchor budget for a brushed stroke counts the mask's art as well.
2026-10-31 19:43:15 +07:00
dotCooCoo 2a5e2e8588 Blob Brush: the tip preview draws 0% Roundness as thin as the brush
The Tool Options tip preview floored the short axis at 1% of the long one, while the brush draws 0% Roundness at 0.1%. The preview now uses the same 0.1% minimum, with a test that compares it with the tip the brush draws.
2026-10-31 19:43:15 +07:00
dotCooCoo baaffd63e7 Blob Brush: Tool Options set the brush's Angle and Roundness
Blob Brush Tool Options gain Angle (-180 to 180 degrees, 0 by default)
and Roundness (0 to 100 percent, 100 by default), with a preview of the
tip and three dabs; a drag in the preview square sets the Angle. The
elliptical tip keeps its angle on the page along a stroke, a 0 percent
tip is drawn 0.1 percent as wide as it is long, and Size set to
Pressure still varies its size. path.blob takes angle and roundness,
and both values are kept with the tool's options.

While a non-round tip drags, the Blob Brush shows the outline of the
area it covers with its length label, and an Alt drag that smooths
shows the Smooth tool's thin line.
2026-10-31 19:43:15 +07:00
dotCooCoo 958566468a DXF export: dashed polylines continue their linetype around vertices
A polyline with a linetype now sets group 70 bit 128, so the dash pattern runs on through its vertices instead of restarting at each corner. This applies to LWPOLYLINE and the R12 POLYLINE.
2026-10-31 19:43:15 +07:00
dotCooCoo d318a77560 DXF export: Alter Paths for Appearance outlines only strokes a CAD line can't draw
Alter Paths for Appearance wrote every stroke as its filled outline, so a
1 pt circle became two circles and a hatch, and an open arc became a
closed outline and a hatch: two cut lines where a CAM program expects
one. The option now outlines only the strokes a line can't draw: the ones
Preserve Appearance outlines, strokes wider than the widest lineweight
(2.11 mm, to the hundredth), and every stroke in R13 and R14, which have
no lineweights. Other strokes stay CIRCLE, ARC, SPLINE and LWPOLYLINE
entities with their lineweights; R12 keeps them as POLYLINEs with their
width.

A dashed stroke whose offset starts part way into its pattern is now
outlined with Preserve Appearance and with Alter Paths: a linetype always
starts at the start of its pattern, so the dashes were written in the
wrong place.
2026-10-31 19:43:15 +07:00
dotCooCoo d0a903d5d7 DXF export: ellipses and unevenly scaled circles are written as ELLIPSE
A closed path of four cubics that is the four-cubic circle under an
affine map (an ellipse, or a circle scaled out of proportion) is written
as a native ELLIPSE in R13 and later, with its center, major axis and
minor to major ratio. The check maps the path back onto the unit circle
and runs the existing circle recognition on it. R12 keeps the existing
fallback, and ellipses with a ratio under 1e-6 stay splines. The ratio
and end parameter are written in full so that a thin ellipse keeps its
size and a whole turn reads back closed.

Part of #1299.
2026-10-31 19:43:15 +07:00
dotCooCoo 06e8d711ae DXF export: an open path of one straight segment is written as a LINE
An open path with a single straight segment was written as a two-vertex
LWPOLYLINE (POLYLINE before R14). It is now a LINE entity from its start
to its end point. A stroke with a weight in R12, R13 and R14 keeps its
polyline, since a LINE has no width. Closed paths and paths with more
than one segment are unchanged.

Part of #1299.
2026-10-31 19:43:15 +07:00
dotCooCoo 9571ae0a46 Outline Stroke, Expand: hidden and locked objects stay hidden and locked
The art that replaces an outlined or expanded path did not take the
path's hidden, locked or Knockout Shape state. Outline Stroke on a
hidden path, or Expand on a group with a hidden member, made the new art
visible (a filled and stroked path became half visible: the outline
shown, the fill copy hidden), and a locked member's art was unlocked.

The replacement now takes the path's visibility, lock and Knockout Shape
state, and the fill copies and clipping paths inside it are shown and
unlocked, so showing or unlocking the new art shows or unlocks all of it.
This covers Outline Stroke, and Expand's strokes, gradient fills and
pattern fills.
2026-10-31 19:43:15 +07:00
dotCooCoo 9e8e091c86 Find Font: Find leaves out hidden and locked text
select.font (the Find Font dialog's Find button) selected every text
object using the font, including hidden or locked text and text in a
hidden or locked group or layer. It now selects only text that is
visible and unlocked through every group and layer, and not on a locked
artboard. Text on a visible, unlocked template layer is still found. A
font used only by hidden or locked text selects nothing.
2026-10-31 19:43:15 +07:00
dotCooCoo 9dcbe3688e Swatches: the French one-item delete prompt keeps the no-break space before ? 2026-10-31 19:43:15 +07:00
dotCooCoo 541a6c33f0 Swatches: the Ukrainian, Russian and Bosnian delete prompt for 21 or 31 items names swatches and groups
In these languages a count ending in 1 (other than 11) takes the singular form, which read as one swatch or one group. That form now names swatches and groups with the count in parentheses.
2026-10-31 19:43:15 +07:00
dotCooCoo ab2426ca15 Swatches: the delete prompt for several swatches uses each language's plural forms
The confirmation for deleting several swatches and groups now goes
through i18n::tn, as the Graphic Styles prompt does, and each catalog
gives the count-dependent forms in an @plural row.
2026-10-31 19:43:15 +07:00
dotCooCoo 37211078c2 Fix #4837: the saved color scheme, accent and canvas color show at launch
The first UI pass installed the default theme over the one the saved
preferences had applied earlier in the same frame, and later frames saw
the preferences as already applied. Classic, a non-default accent and a
Canvas Color showed as the defaults until a preference changed, and again
after the app took a new graphics context. The first pass now applies the
saved preferences.
2026-10-31 19:43:14 +07:00
dotCooCoo 7860931a3b Select Same: Fill Color from a Live Paint face stays in its Live Paint group
Select > Same > Fill Color and Fill & Stroke from a face of a Live Paint
group now select the matching faces of that group only, and from any
other object they select no Live Paint face. Before, they matched faces
of every Live Paint group and paths outside them alike.
2026-10-31 19:43:14 +07:00
dotCooCoo 9163c22396 Fix #4819: Convert to Smooth and Corner convert a selected blend's spine
With a blend selected, the Contextual Task Bar's Corner and Smooth
buttons failed with "select paths or anchor points", since
`path.convertAnchors` only read paths. They now convert every point of
the blend's spine, as they convert every anchor of a path selected
whole, in one undo step. The key objects stay where they are.

With Direct Selection, the Control bar and the Properties panel now
show their Convert buttons for a selected blend too.
2026-10-31 19:43:14 +07:00
dotCooCoo 4e785b40ce Part of #4516: Windows renders start workers only for the cores the process may use
The renderer sized its worker pool from available_parallelism, which on
Windows counts every processor in the machine, also when the process's
affinity mask allows fewer. A process allowed one core started 8 workers,
and the renderer's flush spun on that core while the workers got no time,
so renders crawled or stalled.

The desktop app and vectorcraft-cli now read the affinity mask at startup
(src/windows_cores.rs, shared) and pass the count to the renderer with
vectorcraft_render::set_usable_cores. With one usable core the renderer
renders on the calling thread.
2026-10-31 18:44:55 +07:00
dotCooCoo 663e691908 Fix #4864: a search pick of an Asset Export command opens the panel
The command palette runs a picked command with no params. Export Assets
then failed with "no assets yet" (or, with assets collected, exported
to no folder), and Remove, Rename and Asset Export Settings failed or
did nothing. Run with no params from the palette, a menu or a shortcut,
these four commands now show the Asset Export panel, and leave it open
when it already shows, docked or floating. Calls with params are
unchanged.
2026-10-31 18:44:53 +07:00
dotCooCoo 2d9ceb8137 Shortcuts: rewrap the typing_presses doc comment 2026-10-31 18:44:50 +07:00
dotCooCoo d3ea958f8f Shortcuts: a modifier key press is never the key that typed a text
In the web build, pressing Shift and then a letter in one frame sends the Shift press, the
letter's text and then the letter's key press. The text paired with the Shift press as the press
before it, so a key-position shortcut looked up Shift instead of the letter, and a held Shift's
repeated presses dropped the text as a repeat. Modifier key presses now never pair with a text.
2026-10-31 18:44:50 +07:00
dotCooCoo 0127739597 Shortcuts: a text with no key press of its own leaves the next keystroke's press to that keystroke's text
A text-only event right before a desktop keystroke in the same frame ([text, key, text]) took the key press as the press after it, which left the keystroke's own text with no key. A text now takes the press after it only when every later text in the same run of alternating texts and presses has a press after it too, as in the web build's [text, key] order; otherwise the press goes to the text after it.
2026-10-31 18:44:50 +07:00
dotCooCoo 8579aa07e3 Shortcuts: in the web build a key-position shortcut uses the key of the same press
eframe's web build sends a keystroke's text before its key press. On a
layout without Latin letters, the one-key shortcuts paired each text with
the last key press before it, so in the web build a lone keystroke had no
key and ran no shortcut, and a second keystroke in the same frame used
the first one's key.

Each text now takes the press right before it, unless an earlier text in
the frame took that press, and otherwise the press right after it. The
one-key shortcuts and the shortcut names a tool sees while it is busy use
the same pairing. The desktop order (key, then text) pairs as before.
2026-10-31 18:44:50 +07:00
Dylan M. Taylor cf0d4d4a3f Merge pull request #4807 from aiongg/docs-tidy
docs: tidy agent, contributor and asset docs
2026-10-31 01:06:55 -04:00
aiongg 91e97c92dd updates: show check results with craft-update's CheckResultDialog 2026-10-31 04:42:43 +00:00
aiongg 8ae1f7dae6 docs: tidy agent, contributor and asset docs 2026-10-31 04:17:23 +00:00
bflatastic 41508e7b24 Path Precision Tools: the Shared Edges fields read like the others in the panel 2026-10-31 07:47:55 +07:00
dotCooCoo 44bac0d7cf Render: leave out an object whose opacity mask lost an image
With undecoded-image marks off, an image in an opacity mask's art that
failed to draw left a hole in the mask. Without Clip, the area outside
the mask art counts as white, so the masked object showed in full.

The renderer now notes an image that fails to draw while mask art is
drawn and leaves the masked object out, as PDF export does since #4765.
This covers images in pattern tiles and in cached effect and shadow
rasters, which remember the failure and report it again when they are
reused. Nested masks pass the failure outward, and View Opacity Mask
shows no coverage for such a mask. An image still waiting to be decoded,
or drawn with the undecoded-image mark, keeps today's behavior.
2026-10-31 07:33:49 +07:00
dotCooCoo 462b7cca15 Image Trace: Document Library fills are not linked to swatches
With Document Swatches as the library, traced fills and strokes take the
library color without a swatch link. Global, spot and tint swatches no
longer link the trace, so editing a swatch later leaves the trace
unchanged, and swatch order no longer decides whether a fill is linked.
Each region still takes the nearest library color, and a tint traces at
the color the tint swatch shows.
2026-10-31 07:17:08 +07:00
dotCooCoo edbcc5a88c Tabs: Delete Tab and Repeat Tab are off while an indent marker is selected 2026-10-31 07:17:04 +07:00
dotCooCoo 830cbbcecb Tabs: the ruler's indent markers set the first-line and left indents
The Tabs panel's ruler shows an upper marker at the first line's position
(left indent plus first-line indent) and a lower marker at the left
indent. Dragging the upper marker sets the first-line indent. Dragging the
lower marker sets the left indent and keeps each paragraph's first line
where it was; Ctrl-dragging it (Command on macOS) moves both markers.
Marker drags land on whole points, or on ruler ticks with Snap to Unit or
Shift, and each release is one undo step through text.setFormat. With a
marker selected, the X (or Y) field shows and sets its position.

When the touched paragraphs have different left indents, only the upper
marker is shown, and its position applies to every touched paragraph.
2026-10-31 07:17:04 +07:00
bflatastic 79f7b393e9 Add-ons: Path Precision Tools — Shared Edge Merge
Path Precision Tools gains the Shared Edge Merge Tool, kept out of the
toolbars (All Tools drawer and Object › Plug-ins › Path Precision Tools,
no shortcut), and Keep Shared Points Together:

- Shared Edge Merge Tool (sharedEdge): over two paths touching at two
  coincident anchors (tolerance 0 to 3 pt) hovering shows the two
  stretches and the merged edge; a click replaces both with it, half way
  between them, or following the upper (Shift) or lower (Alt) path. Both
  paths stay closed or open, anchors either stretch has inside are
  matched on the other, one undo step.
- Making ends: near anchors meet on a click and an anchor one path lacks
  is added to match; Shift+Alt asks for this inside a stretch.
- Keep Shared Points Together: an option that makes Slide Anchor Along
  Path, Extend Path and Set Anchor take coincident anchors along. Nothing
  else in the app changes.
- Shared Edges section in the Anchor Inspector: brush diameter,
  coincidence and the option.
- Commands addon.pathtools.mergeEdge and movePointsTogether, params
  validated and covered by the command sweep.
- Cursor stand-in, catalog rows in every language, docs and tests.
2026-10-31 07:16:22 +07:00
dotCooCoo 82025e57b5 Live Paint: Alt-click and Alt+Shift-click with the Bucket sample with the Eyedropper
With the Live Paint Bucket, Alt-click on vector art samples its fill,
stroke and stroke weight into the paint defaults, and on a placed image
it samples the pixel as a solid fill with no stroke. Alt+Shift-click
samples only the color under the pointer, including an intermediate
gradient color or image pixels, into the active fill or stroke. Neither
click paints or changes the selection or selected art, and the next
Bucket click paints with the sampled paint. While Alt is held, the
Bucket shows the Eyedropper cursor and no face or edge highlight.

appearance.copyFrom takes `defaultsOnly: true` to update only the paint
defaults, and paint.sampleColor takes `clearStroke: true` to clear the
stroke default after an image sample into the fill.
2026-10-31 06:28:46 +07:00
Don Omondi de703fe722 M5: opaque knockout fills draw once in hard-edge and supersampled raster export
A knockout element with one opaque Normal solid fill (a path or
compound, no mask, effects or isolation) is drawn once under
AntiAlias::None, including the internal hard-edge frame of Art
Optimized supersampled exports, instead of being erased and added back
through per-element backdrop layers. Analytical antialiasing keeps the
existing compositor and rounding.

Exact-pixel tests compare against the general knockout path for paths
and compound holes across precision, supersampling, translucent parents
and copied backdrops, and check that the backdrop rasters are skipped;
translucent fills keep their knockout compositing.

# Conflicts:
#	docs/roadmap.md
#	docs/target-app-parity.md
2026-10-31 06:28:42 +07:00
dotCooCoo 51b6bbdd17 Paintbrush: Alt held at the press smooths a selected path
With a path selected that the Smooth tool can change, Alt held at a
Paintbrush press now switches the drag to the Smooth tool: it smooths the
selected paths under the drag with the Smooth tool's radius and Smooth Tool
Options' Fidelity, and the cursor shows the Smooth tool before the press.
Alt pressed after a stroke begins still closes the path on release. With no
such path selected (nothing, only text or an image, or only a two-point
line), Alt at the press draws the closed stroke as before.

The Paintbrush no longer reads the Pencil's "Alt key toggles to Smooth Tool"
setting: its Alt close and Alt smoothing do not depend on it.
2026-10-31 06:11:00 +07:00
dotCooCoo a072cf16be View: Escape that cancels a ruler or Tools move does not reach the tool
An Escape that cancels a ruler-origin drag or a floating Tools panel move
now stops there: the active tool does not receive it, so a tool's own
Escape action (for example Intertwine returning to the Selection tool)
waits for the next press. The ruler-origin test also covers the drawer
and Global Edit.
2026-10-31 06:10:57 +07:00
dotCooCoo 501ae57fef Canvas: pressing or releasing ` mid-drag updates the live preview
A Selection or transform drag held still now gets another drag event when
the backquote key goes down or up, as it already did for Shift, Alt,
Cmd/Ctrl and Space, so the preview switches between a pattern-only and an
ordinary transform without waiting for the next pointer move.
2026-10-31 06:10:53 +07:00
dotCooCoo 7584b6335e Legacy .ai: colorized image pattern tiles keep their color
Saving a legacy .ai with transparent images flattened expands pattern
fills whose tile image is see-through or has opacity or a blending mode,
so the tile images flatten with the art under them. A pattern whose tile
is an opaque grayscale image with a colorize paint was not expanded, so
the writer drew the source gray pixels over white. A colorize paint now
counts too.
2026-10-31 06:10:51 +07:00
bflatastic e13c4bc9d0 Add-ons: Path Precision Tools — live path effects 2026-10-31 06:10:47 +07:00
dotCooCoo fa89546397 PDF export: leave out an object whose opacity mask lost an image
An unclipped opacity mask is drawn on a white backdrop. When an image in
the mask art could not be written (a colorized image whose color channel
is over the size limit, an image that can't be decoded, or one placed
beyond the coordinates a file holds), the backdrop stayed white and the
masked object showed in full where the image should hide it.

The exporter now records an image left out while it writes mask art,
and leaves the masked object out instead of applying the incomplete
mask, with a warning. A failure in a nested mask makes the outer mask
incomplete too.
2026-10-31 05:42:51 +07:00
dotCooCoo 7d706ffa26 PDF: colorized images that can't be exported are cached as missing
colorized_image returned before its cache insertion when the image was
missing, left out or undecodable, or when its color channel was larger
than MAX_RASTER_ALLOC. Every placement of such an image then decoded it
and computed its coverage again. Both cases now cache None for the key,
as load_image does.
2026-10-31 05:42:41 +07:00
Don Omondi 20f7f8be1e Testkit: the temp_dir test pre-makes a private directory, as temp_dir does
a_temp_dir_starts_empty_in_a_process_and_stays_for_later_calls
pre-created the "earlier process" directory with create_dir_all, which
under a usual umask gives others access, so temp_dir refused it as not
private and the test failed on every Unix run (main's CI included).
The test now makes it with mode 0o700; temp_dir and the assertions are
unchanged.
2026-10-31 05:42:38 +07:00
Don Omondi b0cc17343e fix(pdf-import): take a group's knockout and isolate flags from the interpreter
The import read each form's /Group /I and /K from the file in order and
matched them to the transparency groups the interpreter pushed. The
interpreter draws a group that adds nothing (full opacity, Normal, no
mask, neither flag) straight onto its parent without pushing it, so every
group after one took another group's flags. Blend steps written as a
knockout group of falling opacity then painted over each other and drew
as solid dark shapes instead of light rings.

The interpreter already reports the flags right after pushing a group
(set_knockout, set_isolated); the import now uses those and drops the
file-order list. That list was capped at 10,000 entries, past which a page
failed to import; that limit goes with it.
2026-10-31 05:27:13 +07:00
Don Omondi d4745fe046 fix(eps): a layer's state after its last blend stays the layer's
#4757 gives a blend the Xy written after its record. When the blend is a
layer's last object, the layer's own state block (Xy, Xd, 7 () XW, LB)
went to the blend instead, and the layer stayed opaque. The blend's state
is now tentative like a mesh's (#4748): its own 6 () XW confirms it, the
layer's LB gives it to the layer and the blend keeps what it had.

Also records the .ai/EPS audit fixes under G9, the File formats row and M4.
2026-10-31 05:11:04 +07:00
Don Omondi aa6a960062 fix(ai-import): compare a PDF of some artboards on the artboard its page shows
A PDF saved with Illustrator editing data for only some of its artboards
has pages for those artboards, while the editing data keeps them all. The
layers were compared with the first page on the first artboard, so a page
showing a later artboard never matched and the file came in as its PDF
part, without its layers.

With fewer pages than artboards, the first page is now taken to be the
artboard of its size whose art draws most like it (up to 16 weighed), and
the pages' type goes to that artboard and the ones after it. All the
artboards still come in, and the comparison still decides whether the
layers are used.
2026-10-31 04:55:14 +07:00
Don Omondi c4d2480132 .ai/EPS import: a blend takes the transparency written after its record
Squashed from #4757 onto main.
2026-10-31 04:39:05 +07:00
Don Omondi a1fc3cfdb1 fix(eps): a masked gradient mesh takes the transparency after its mask
A gradient mesh with an opacity mask has its own state (`Xy`) after the
mask's dictionary, not right after its `/End X!`. The operators of the
mask's art, on `%_` lines, cleared the flag that says a mesh just ended,
so the state was read as the next object's and each masked mesh in a
run drew with the blend mode of the one before it.

The flag now goes back to what it was when a dictionary opened once it
closes, as `after_object` does.
2026-10-31 04:21:56 +07:00
bflatastic c9dc4fa857 Add-ons: Path Precision Tools — Corner Tool and Slide Anchor
Path Precision Tools gains two tools, kept out of the toolbars (All Tools
drawer and Object › Plug-ins › Path Precision Tools, no shortcuts):

- Corner Tool (cornerTool): editable corners on any path through the
  existing Live Corners. Hover shows an eligible anchor, a click cuts a
  corner with the tool's radius and kind, a drag sizes it (Shift: whole
  values), a corner's marker selects it and loads its values, resizes it
  and removes it at 0 or on a double-click; a corner never grows past
  what fits; an Alt-marquee cuts every eligible anchor inside.
- Corners section in the Anchor Inspector: radius, kind, selected count,
  Apply to Selected Anchors.
- Slide Anchor Along Path (slideAnchor): a drag runs an anchor along its
  curve, the neighbouring segments refitted by the keep-shape kernel;
  Shift snaps where the tangent is a multiple of 45° from the constrain
  angle.
- Commands addon.pathtools.cornerSet, cornerRemove and slideAnchor, one
  undo step each, params validated and covered by the command sweep.
- Cursor stand-ins, catalog rows in every language, docs and tests.
2026-10-31 04:21:26 +07:00
Don Omondi ff1f721013 fix(eps-import): keep a layer's opacity when its state block has the stroke style
A layer's own state block (Xy, Xd, 7 () XW before LB) can have the
stroke style (J j w M d XR) between its Xy and Xd. Read as the art moving
on, it dropped the layer's opacity, so a layer at 17% drew opaque and the
file came in as its page. The stroke style now keeps the pending layer
state, and the state a mesh before it had; an object's own 6 () XW block
still isn't the layer's.
2026-10-31 04:02:35 +07:00
dotCooCoo 6a034c77c3 Transform tools: backquote drag transforms only the pattern
Holding backquote while dragging with the Selection tool (move, bounding-box
scale or rotate) or the Rotate, Reflect, Scale or Shear tool applies the drag
to the selected objects' pattern fills and strokes and leaves their geometry
in place. The key state at release decides the result: the key may be pressed
after the mouse button goes down, and releasing it before the button comes up
commits an ordinary transform. Objects without pattern paint do not move.

PointerEvent gains a `backquote` flag (false when absent). The desktop canvas
reads it from the held key on every pointer event, and `ui.pointer` and the
`pointer_gesture` tool accept it per event or as a default for the gesture.
2026-10-31 03:34:55 +07:00
dotCooCoo 5f9b524d1a Shortcuts: Shift+Comma and Shift+Period run their bindings where Shift types another character
On layouts such as German, Shift+Period types `:` and Shift+Comma types
`;`, so the key event carries the Colon or Semicolon key and the
`Shift+.` and `Shift+,` bindings did not run. A shifted press at the
Comma or Period key position now runs the chord bound to that position
when no binding exists for the typed character; a binding the keymap
has for the typed character, such as a user's `Shift+;`, still wins.
Each press runs one command.

The tests send the key events these layouts produce, and the text
editing test checks after each press that fill and stroke stay as they
were.
2026-10-31 03:34:52 +07:00