Commit Graph
1655 Commits
Author SHA1 Message Date
dotCooCoo c3cd065ef3 Character and Paragraph Styles: Alt-click on New asks for the new style's name
Squashed from #4402 onto main.
2026-10-30 00:15:55 +07:00
VoiceLessQ 401706ff8b PDF import: each Contents object is read once when naming a refused stream
Pages that share one Contents array made the name of a refused stream read that array once per page, quadratic in a crafted file. Each object is now read once, and a page's own array as before. From the review of #4612.
2026-10-30 00:15:54 +07:00
VoiceLessQ f0865193a8 PDF import: a page's content listed in an array object is named as content
A page's Contents can be an array written as an object of its own (/Contents 6 0 R). Its streams were named "a stream in it": the reference was compared with the stream, not the array's items. From the review of #4603.
2026-10-29 23:39:24 +07:00
VoiceLessQ 1411a821c1 Fix #3098: Image Trace traces off the UI thread, with progress and Cancel
Tracing a photo or a large image ran inside imageTrace.make on the UI
thread, so the window froze for as long as the trace took, with nothing
to say it was working.

A trace the user asks for (Object > Image Trace > Make and Make and
Expand, the task bar's Image Trace, the Image Trace panel's Trace and
presets) now runs on a thread of its own, as heavy plug-in filters do.
The status bar says so, and after 0.4 s a progress window shows how
many of the image's colors are traced, with Cancel and Continue in
Background (imageTrace.cancelRuns, imageTrace.hideRuns).

The result goes back to the command it was prepared for
(cmd::TraceRun, Traced::hand_in), which takes it only for the same
object, image and settings, so the edit is one undo step as before. A
newer trace of the same object replaces one still running, and a trace
whose image or settings changed meanwhile changes nothing. The control
channel, MCP, scripts and the web trace at once, as before.

The tracer reports its stages and color layers to a callback and stops
when it says so (trace_watched).
2026-10-29 22:39:34 +07:00
VoiceLessQ b96d35139c PDF import: a refused file says which stream it trips on (refs #4366)
Squashed from #4603 onto main.
2026-10-29 22:12:16 +07:00
bflatastic a7125f9e9f PDF import: bound the objects an import makes and the form content a page walks (#3569)
A page drawing one form over and over made its art again for every drawing
with no limit (a 1 KB file took 877 MB), and a form decoding to 64 MiB drawn
a thousand times hung. The import now makes at most half the objects a saved
document may hold (2M, 256K on the web), leaving the rest out with a warning,
and the scan reads at most 2 GiB of form content per page before refusing it
as past the reader's limits.
2026-10-29 21:48:36 +07:00
dotCooCoo 11d65eb8a8 Flare: the drag turns the rays, Shift keeps their orientation and Space moves the flare
The reference app's Flare tool rotates the rays as the center is dragged
out, Shift constrains them to their original orientation, and Space
held moves the shape being drawn. VectorCraft's Flare tool drew the
rays in one fixed orientation and ignored Shift and Space.

shape.flare takes rotation (degrees, clockwise), which turns the rays
about the center and leaves the halo, the center and the rings as they
are. While the center is dragged out, the rays turn with the pointer,
as a polygon's first corner does, and a drag straight up leaves them
unturned. Shift keeps them unturned, or turned by General › Constrain
Angle as a polygon drawn with Shift is. Space held during the center
drag or the drag that places the rings' end point moves the flare at
its current size, and the drag goes on from there when Space is let
go, as with the shape tools.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 20:47:47 +07:00
Taha-Mahmoodi d89031848c Fix #2754: Shift+Enter inserts a soft return (forced line break)
In typography and Illustrator, Shift+Enter inserts a soft return (forced
line break) rather than ending the paragraph. Text wraps to the next line
without applying paragraph-level spacing (space before/after, first-line
indents), list continuation lines keep their parent item without repeating
markers, and justified text does not stretch lines ending with a soft return.

- Forward active modifier keys for Enter in ui-egui shortcuts so the tool
  detects Shift+Enter.
- Type tool inserts U+2028 (LINE SEPARATOR) on Shift+Enter.
- Text shaping and layout treat U+2028 as an invisible zero-advance control
  character, breaking the line in point and area type.
- Paragraph spacing and list markers apply once per paragraph as intended.
2026-10-29 20:36:05 +07:00
bflatastic 906e496fbb Keep the existing message 2026-10-29 19:57:59 +07:00
bflatastic bbbc711b6a Fix #4382: the Eyedropper's Alt-click puts what it picked up on the object clicked when nothing is selected
With nothing selected, a click with the Eyedropper picks up an object's attributes and an Alt/Option-click on another object applies them, as the reference app's Eyedropper does. Alt-click only copied from a selected object, and with nothing selected it failed. The session now keeps a copy of what appearance.copyFrom picked up last, and a reversed copy with nothing selected applies it.
2026-10-29 19:57:59 +07:00
bflatastic d3d1e31219 Format 2026-10-29 19:57:58 +07:00
bflatastic a353ad3e7a Fix #4523: a new document's VectorCraft Options start as the last native save used them
Preview, Embed ICC Profiles, Create PDF-Compatible File and the other VectorCraft Options went back to their defaults for every document not saved yet. A native Save or Save As now records the options it used in the preferences (not the artboards it picked, nor Use Compression, which its preference decides), and file.formatOptions offers them for a document not yet saved as a native file. A document saved before keeps its own.
2026-10-29 19:57:57 +07:00
el-j 6e2e0740b0 Isolation mode: breadcrumbs are clickable to navigate hierarchy levels (#4468) 2026-10-29 19:54:35 +07:00
bflatastic 9534a36416 Fix #4538: New Layer and New Sublayer are refused while an opacity mask is edited
While an opacity mask is edited the Layers panel lists only the mask, so a new layer was added where no row showed it, and only the panel's layer count went up. Both commands are now disabled then, as isolation commands already are (outside_mask_editing).
2026-10-29 19:54:34 +07:00
dotCooCoo cdf4453051 Libraries: gradients and patterns can be added to a library and applied from it
Squashed from #4442 onto main.
2026-10-29 19:21:33 +07:00
dotCooCoo c9853094d4 Image Trace: Manage Presets saves, renames and deletes custom tracing presets
Squashed from #4071 onto main.
2026-10-29 19:21:32 +07:00
dotCooCoo e492c4298c Blend: a click picks again when the Blend tool's first pick is gone
Squashed from #4447 onto main.
2026-10-29 19:21:32 +07:00
Taha-Mahmoodi b41599f657 Fix #3089: a stroked line with an effect expands to a stroked line, then Expand outlines it
Squashed from #4169 onto main.
2026-10-29 18:54:59 +07:00
dotCooCoo 3cfdb58b90 Transparency: pattern editing mode and opacity mask editing can't be entered from each other
Squashed from #4458 onto main.
2026-10-29 18:54:45 +07:00
dotCooCoo c8a7420b93 Place: an image whose pixels can't be read is refused instead of placed blank
Squashed from #4257 onto main.
2026-10-29 18:54:38 +07:00
dotCooCoo e764d770db Image Trace: Document Library fills link to the document's global swatches
Squashed from #4396 onto main.
2026-10-29 18:50:56 +07:00
dotCooCoo 74b408e8da Blend: choosing the Blend tool again finishes the blend
Squashed from #4454 onto main.
2026-10-29 18:50:34 +07:00
dotCooCoo 6fd042e4be Tests: the MCP concurrency, folder search and artboard export tests pass on a loaded machine
Squashed from #4473 onto main.
2026-10-29 18:50:24 +07:00
el-j 31a482bade perf(engine): rerun objects_on_path when dimension refresh mutates artwork 2026-10-29 18:46:48 +07:00
el-j 3e70c38d7b perf(engine): gate post-edit Shaper and Text-Wrap maintenance via edited-root diffing (#4554)
- In `shaper::refresh`, replace full document walks with `changed_shaper_groups`,
  diffing `before` and `after` layers and short-circuiting on pointer-equal (`Arc::ptr_eq`)
  subtrees in O(1).
- Return whether Shaper groups rebuilt or modified artwork (`Result<bool>`),
  allowing `Session::edit` to skip the second `cmd::objects_on_path::refresh`
  call when no artwork was modified.
- In `textwrap::refresh`, check pointer equality on layers against `before.layers`,
  skipping untouched layers in O(1) and checking only modified layers.
2026-10-29 18:46:47 +07:00
dotCooCoo d93657c631 Object: Hide Unselected Artwork keeps a selected layer's art and groups that hold locked guides
With a layer selected, Object > Hide > Unselected Artwork hid every object
on that layer, since the selected layer was dropped from the objects to
keep. The objects of a selected layer now count as selected.

While Lock Guides is on, a group that held a guide path was hidden whole,
which hid the guide with it. Guide paths now count as selected, so the
command keeps such a group visible and hides its other members one by one.
2026-10-29 18:46:47 +07:00
dotCooCoo cfdeda35c4 Tests: the non-sRGB Drop Shadow test fails when its child test does not run
svg_darkness_uses_rendered_shadow_in_non_srgb_working_space runs itself
again in a child process with --exact and checked only the exit status.
libtest exits 0 when --exact matches no test, so a rename of the test
would have left the parent passing without running the non-sRGB check.
The parent now also requires "1 passed" in the child's output, as
layout_cache_follows_fonts_added_later in crates/tools does.
2026-10-29 18:45:54 +07:00
dotCooCoo a4ca347692 Shaper: dragging a shape out of a Shaper Group's bounds takes it out of the group
Squashed from #4415 onto main.
2026-10-29 15:16:17 +07:00
dotCooCoo b860a7b9af Direct Selection: a drag from a fill moves only the selected anchors of partly selected paths
Squashed from #4443 onto main.
2026-10-29 15:16:11 +07:00
dotCooCoo 6d66eaf3dc Object: Cmd/Ctrl+Alt+Shift+3 hides every unselected object
Squashed from #4418 onto main.
2026-10-29 15:16:05 +07:00
dotCooCoo dfa9f883c0 Fix Drop Shadow Darkness in source color space 2026-10-29 14:36:41 +07:00
dotCooCoo bb869dd9df Swatches: global swatch edits also update graph designs and the art of brushes
Squashed from #4489 onto main.
2026-10-29 14:36:04 +07:00
bflatastic efc1501ac9 Path: Object › Path › Smooth smooths the selected paths with a slider and Auto-Smooth (#4115) 2026-10-29 13:51:51 +07:00
Don Omondi 1196d9a335 fix(eps): import bool_or after main's explicit command imports
Main replaced the glob import of the command helpers in fileio/eps.rs
with an explicit list; the EPS Save path's selectedOnly check uses
bool_or, so it is added to that list.
2026-10-29 13:00:49 +07:00
Don Omondi c6070f633e Support original-format EPS Save and Save As 2026-10-29 13:00:47 +07:00
bflatastic 828d784c8c Shape Builder: import replace_leaves in buildcmds/shape_builder.rs (#4299 made the imports explicit) 2026-10-29 13:00:46 +07:00
dotCooCoo f30819b7eb Shape Builder: a merge replaces only the paths it uses, where they are
Squashed from #4094 onto main.
2026-10-29 13:00:46 +07:00
dotCooCoo 6923939460 Document Color Mode: also converts graph designs, graphic styles, opacity masks and brush art
File > Document Color Mode and document.open {colorMode} converted the
colors of the layers, symbols, pattern tiles and swatches only. Graph
designs, graphic styles, the art of opacity masks, the tile that
pattern editing's Cancel puts back and the art of brushes kept their
old color model (only a tint of a global swatch in a graphic style or
a mask followed the swatch), so a CMYK document could still apply,
place or print RGB colors from them.

These now convert with the rest, in the same undo step: their solid
colors, gradient stops, text runs and mesh points, nested masks
included. A Hue Shift brush's key color takes the converted color of
the art color it matches, a tint of a global swatch included. The
brush library is stored again only when one of its colors changed. A
tint of a global swatch in any of these places becomes the converted
swatch's color at its tint, as in the layers. The reply's changed
count covers the colors of the layers, symbols, graph designs, pattern
tiles and opacity masks; the colors of graphic styles and brushes
convert without being counted.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 13:00:40 +07:00
dotCooCoo edc2602ae4 Shaper: Shaper Group edits keep the opacity mask and pattern editing layers isolated
While an opacity mask or a pattern was edited, a Shaper scribble and
Object › Shaper › Release and Expand ended isolation, and a Shaper
double-click on a Shaper Group isolated the group's original shapes.
Each replaced the editing layer's isolation while the document stayed
in mask or pattern editing. A click on the Shaper Group's mode widget
did the same through object.isolate.

A scribble, Release and Expand now keep the editing layer isolated in
these modes, and end isolation as before outside them. A double-click
with Shaper selects the original shape under the pointer without
isolating the group's original shapes, and the mode widget does not
show while a mask or a pattern is edited.

Docs: docs/shaper.md, docs/target-app-parity.md.
2026-10-29 13:00:36 +07:00
dotCooCoo fa7df461bc Live Paint: a Live Paint Selection marquee selects the faces and edges it touches
The Live Paint Selection tool acted only on the press, so a drag
selected at most the one face or edge under the pointer. The reference
app selects every face and edge a marquee touches, partly enclosed ones
included, and a Shift-drag adds them to the selection or removes them.

A drag of 3 pixels or more now draws a marquee. On release, select.set
selects the faces and edges it touches, or select.none runs when it
touches none. With Shift held at the press, select.toggle runs with
what it touches, and nothing runs when it touches none. A face that holds the whole marquee counts. Select Fills off
leaves faces out, and Select Strokes off leaves edges out.

A click now changes the selection on release instead of on the press,
so Escape between the press and the release, which also removes the
marquee, leaves the selection as it was. The tool no longer highlights
or selects the faces and edges of a Live Paint group that is locked, on
a locked layer or on a locked artboard, or that is outside the isolated
art in isolation mode.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 13:00:33 +07:00
dotCooCoo d4a30cc084 Brushes: Pattern Brush Options' corner tiles can be None or made from the side tile
Pattern Brush Options has a menu below the Outer Corner Tile and the Inner
Corner Tile. Each can be the brush's own corner art (Original), None, or one
of four tiles made from the side tile at each corner of the path:

- Auto-Centered stretches one side tile around the corner, centered on it.
- Auto-Between bends a side tile on each side into the corner, the two
  meeting on the corner's diagonal.
- Auto-Sliced lays a side tile on each side past the corner and masks it
  at the diagonal, so the two meet like a mitered frame.
- Auto-Overlap lays a side tile on each side past the corner, overlapping.

The tiles are straight at the corner and follow the path towards the side
tiles, so they meet a curve that runs into the corner. A side tile taller
than the corner can reach is stretched along until the two sides meet. On a
short path, a corner's tile is squeezed along so that it covers at most the
path up to an open path's end and half way to the next corner, and its two sides still meet on the
corner's diagonal.

The definition stores the choice as `outer_source` and `inner_source`
(`none`, `autoCentered`, `autoBetween`, `autoSliced`, `autoOverlap`), which
`brush.options` sets. Corner art wins over a source. A brush saved without
a source draws as before: a kind of corner without art takes the other
kind's art. None leaves that kind of corner empty.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 11:22:56 +07:00
dotCooCoo 3ea2bba6e3 Blend: Escape drops the Blend tool's first pick
After a click picked the first object with the Blend tool, Escape did
nothing: the pick stayed, and the next click on another object blended
the two. After a blend was made, Escape did not stop the next click
adding to it either.

Escape now drops the object picked first and stops the next click
adding to the blend just made, as a click on empty canvas does, so the
next click picks a new first object. The document and the selection
stay as they are, and no undo step is added.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 11:22:56 +07:00
dotCooCoo 7d04096840 Transparency: Make Opacity Mask is off for a targeted layer in isolation mode 2026-10-29 11:20:08 +07:00
dotCooCoo dfe201dec3 Transparency: isolation mode and opacity mask editing can't be entered from each other
Enter Isolation Mode worked while an opacity mask was edited, and Edit
Opacity Mask and View Opacity Mask worked in isolation mode, so each
mode replaced the other's isolated art. In the reference app, isolation
mode can't be entered while a mask is edited, and mask editing can't be
entered in isolation mode.

object.isolate and perspective.editText, which shows type in isolation
mode, are now off while an opacity mask is edited, and a call returns
an error that ends "stop editing the opacity mask first". In isolation
mode transparency.editOpacityMask and transparency.viewOpacityMask are
off, and transparency.makeOpacityMask refuses one target, an object or
a layer, which it would take into mask editing; a call returns an error
that ends "exit isolation mode first". Make Opacity Mask with two
objects still masks one with the other. Pattern editing mode does not
count as isolation mode for these commands.

Object > Perspective > Edit Text, the canvas context menu and the
Transparency panel's menu and Make Mask button follow the commands'
enablement. A click or Alt-click on the mask thumbnail in isolation
mode shows the error and does not edit the mask. While a mask is
edited, the Contextual Task Bar leaves out Isolate, and the Properties
panel's Quick Actions leave out Isolate Group.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 11:20:08 +07:00
bflatastic 0362ae5453 Object: import choice in object/arrange.rs (#4299 made the imports explicit) 2026-10-29 11:20:07 +07:00
dotCooCoo 02f2dd8c5a Object: Unlock All and Show All with Shift+Alt act only within the selected object's group
Squashed from #4439 onto main.
2026-10-29 11:18:38 +07:00
dotCooCoo f87b8af62b Snap to Glyph: points land on glyph anchors and line up with x-height, character widths and angles
With View › Snap to Glyph and Smart Guides on, a point drawn near a single
line of live type now also lands on the anchor points of its glyphs and lines
up with its x-height (raised or lowered by Baseline Shift) and with the edges
of each character's advance width along the baseline (proximity guides). In
rotated type the baseline, x-height and proximity guides run at the type's
angle, and type of one glyph also has angular guides along the glyph's slanted
straight segments; a segment that Shift keeps to its angle slides onto them.
A paragraph shows glyph bounds only, so its baselines are no longer guides.

FontFace::x_height gives a face's x-height in font units.
2026-10-29 11:18:38 +07:00
dotCooCoo ab69d8f6d9 Envelopes: Anti-Alias and Preserve Shape Using change how distorted images draw
Envelope Options stored Anti-Alias and Preserve Shape Using, but nothing that
draws an image read them: a distorted image was always smoothed and always
cut into clip groups.

- With Anti-Alias off, each piece of a distorted image is marked pixelated
  (saved as `pixelated`). The renderer draws it with nearest-neighbor
  sampling and without mips, SVG export writes `image-rendering: pixelated`,
  and PDF export writes it without /Interpolate.
- With Preserve Shape Using Transparency, each piece is the image with a
  white opacity mask in the piece's shape instead of a clip group. The
  renderer paints an image whose mask is one plain white path straight into
  that path, as it does for an image in a clip group, when the path lies
  inside the image's frame.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 11:07:57 +07:00
Don Omondi 5b18da25a8 fix(format): remove what a native file has beyond the canvas (#3575)
Edits refuse to leave anything beyond MAX_COORD (doc_sane checks the
artboards, the guides, the ruler origin and the objects an edit touches),
but the native loader didn't check them. A file with one object, artboard,
guide or ruler origin out there opened without a warning, and then every
move failed with "result would exceed the canvas", in-range objects
included; saving kept it that way.

Document::drop_beyond_canvas removes objects whose geometric bounds aren't
finite or reach past MAX_COORD (a layer or non-clipping group keeps its
children within it), artboards with their artboard guides, and guides out
there, and puts the ruler origin back at the origin. load_file runs it
after the depth limit and reports what it did with the other load notes; a
file whose artboards all lie beyond the canvas is refused.
2026-10-29 11:07:40 +07:00
dotCooCoo 7b36442f41 Keyboard Shortcuts: Distribute Spacing stays listed, its axis is optional
object.distributeSpacing already spaces horizontally without an axis; its params doc now says so, so the editor keeps listing it.
2026-10-29 10:13:23 +07:00