Commit Graph
3013 Commits
Author SHA1 Message Date
dotCooCoo 6026772765 Fix #3773: Offset Path offsets a path whose ends meet as a closed shape
Offset Path outlined an open path on both sides, so a shape whose last
anchor sits on its first came out as a ring with a hole. Such a path is
now offset as a closed shape: its last anchor merges into its first,
which takes the last anchor's in-handle, with the same 1e-6 test that
Object › Path › Join uses for one path (crates/engine/src/cmd/path.rs).
The Offset Path effect calls the same function.
2026-10-29 09:57:34 +07:00
NetterSilverandClaude Opus 5.5 b6422e5ba1 Keep compound path holes when using the Eraser and Knife
Fixes #4495

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 09:57:34 +07:00
bflatastic ace60bd542 Edit Colors: import f64_req and check_numbers in colorcmds.rs (#4299 made the imports explicit) 2026-10-29 09:57:34 +07:00
dotCooCoo 8c0750e848 Edit Colors: Saturate without an intensity is an error instead of a pass at 0
edit.colors.saturate's params text names intensity as required, but a
call without it, or with a string such as "40", read the intensity as
0 and still ran Saturate. It changed no visible color but recorded a
Saturate undo step.

edit.colors.saturate now reads intensity with f64_req, as shape.ellipse
reads its box. A missing, null or non-number intensity is the error
"missing number `intensity`", which changes nothing and records no undo
step. A number outside -100..100 still counts as its nearest end, and
the params text says so.

Saturate returns a color whose saturation stays the same, as at an
intensity of 0, as it was. The HSB round trip rewrote most such colors
in their last bit, so they counted as changed and lost their swatch
link.

In the same file, edit.colors.adjustBalance read a channel that is not
a number as 0, and color.harmony read steps or amount that is not a
number as its default. Both now refuse it with check_numbers, as the
commands docs/mcp.md lists for #1952 do. A null is still read as a key
left out.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 09:57:33 +07:00
bflatastic 49509be9a8 Layers: import switch_or in layer.rs and layerpanel.rs (#4299 made the imports explicit) 2026-10-29 09:57:33 +07:00
dotCooCoo 4519346cfb Layers: the Layers panel's commands refuse a boolean given as text
layer.new, layer.selectAll, layer.target, layer.move, layer.template,
layer.pasteRemembersLayers and layer.releaseToLayers read their on/off
params with bool_or or as_bool, which falls back to a default for a
value that is not true or false. layer.new {"top": "true"} added the
layer above the current one instead of on top, layer.template
{"on": "false"} and layer.pasteRemembersLayers {"on": "false"} turned
the option on when it was off, layer.move {"copy": "true"} moved the
rows instead of copying them, layer.selectAll and layer.target with
"add": "true" replaced the selection, and layer.releaseToLayers
{"build": "true"} released the objects without building up copies.

These commands now fail with "`<key>` must be true or false" for a
value that is not true, false or null, as the artboard and Object menu
commands do, and leave the document, the selection, the highlighted
rows and the undo history as they were. layer.newSublayer, which takes
layer.new's options, refuses such a top too. A null is read as a key
left out.

docs/mcp.md also lists showCenterMark, showCrossHairs and
showVideoSafeAreas among the keys artboard.new and artboard.setProps
refuse in this way, as they already did.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 09:57:33 +07:00
dotCooCoo 1deb1ec152 Artboards: artboard.new and artboard.setProps refuse a number or name of the wrong type
artboard.setProps refused an x, y, width or height given as text, but
artboard.new read such a value as the key left out: {"x": "10"} added
an artboard at the default position and recorded an undo step. Both
commands read a name that is not a string, such as 5, as no name, so
artboard.new gave the default name and artboard.setProps kept the old
one.

artboard.new now refuses an x, y, width or height that is not a number
with "`<key>` must be a number, not <value>", the message
artboard.setProps gives, and both commands refuse such a name with
"`name` must be a string", for each entry of artboard.setProps'
artboards too. They refuse it before they change anything. A null is
still read as the key left out.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 09:57:33 +07:00
NetterSilverandClaude Haiku 5.5 ad43722a56 Accept whole-number floats as grid divider counts
Found while testing crates/engine/src/cmd/create.rs:584 (dividers)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 09:57:33 +07:00
NetterSilverandClaude Haiku 5.5 66498b9740 Reject non-hex digits in RTF \'XX escapes
Found while testing crates/engine/src/cmd/place/rtf.rs:111

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-29 08:44:57 +07:00
dotCooCoo a368683eb4 Shape Builder: Find Objects with select waits for a pending merge
document.find is a query, so it ran at once while a Shape Builder merge
was still being computed. With select it changes the selection, and the
pending merge was then discarded. It now applies the merge first, as the
other queries that select do. The command sweep also runs document.find
with a filter and select, which one key at a time does not reach.
2026-10-28 17:51:28 -07:00
dotCooCoo 963346984e Shape Builder: import find_command and drop the unused SubPath import after merging main 2026-10-28 17:42:35 -07:00
dotCooCoo 83748190f6 Shape Builder: import waits_for_tool_edit and Arc after merging main
main moved session/execute.rs and cmd/buildcmds/shape_builder.rs off glob
imports, so the branch's new waits_for_tool_edit guard and Arc::ptr_eq call
needed those names added to the explicit import lists.
2026-10-28 17:15:07 -07:00
dotCooCoo a73637f4e4 Merge main into fix/outline-stroke-keeps-effects-on-outline 2026-10-28 16:56:27 -07:00
dotCooCoo f33617142c Save As .ai: carry editing data that other apps open as layers and paths
A PDF-compatible .ai now also carries, on its first page's PieceInfo, an
editing copy of the art in the form VectorCraft's own .ai reader reads:
AIPrivateData streams of 64 KiB holding the program compressed with
Zstandard after %AI24_ZStandard_Data, an AIMetaData stream of its header
comments, and the ContainerVersion, CreatorVersion, NumBlock,
RoundtripStreamType and RoundtripVersion entries.

The program holds the header comments, the color swatches and color
groups, every artboard, layers and sublayers (name, order, visibility,
lock, color, printing, dimming), groups, paths, fills and strokes (width,
caps, joins, miter limit, dashes, overprint, fill rule), process, spot and
global colors, guides, hidden and locked objects and object names. Type,
gradients, patterns, images, symbols, live objects, brushes, clipping
groups and compound paths are written as plain filled and stroked paths,
each with a warning; transparency and effects other than geometry effects
are left out.

The .ai option editingData (on by default, Include Editing Data for Other
Apps in Save As) turns it off. The legacy version 7 writer is unchanged;
the path, guide and line style operators both writers write move to
eps/src/ai_ops.rs. LoadOptions::native off opens a file from its editing
copy or pages instead of the VectorCraft document it carries.
2026-10-28 16:55:41 -07:00
dotCooCoo 72569ee6a4 Merge main into fix/shape-builder-merge-off-ui-thread 2026-10-28 16:48:55 -07:00
Dylan M. Taylor 484d25582c Merge remote-tracking branch 'origin/main' into pr-4299-ff 2026-10-28 17:20:12 -04:00
Dylan M. Taylor 12414aa9f6 Make PDF export comparison deterministic 2026-10-28 19:53:43 +00:00
Dylan M. Taylor 0b821b3c54 Extract engine command and state helpers 2026-10-28 16:44:14 +00:00
Dylan M. Taylor 7043ef6f3d Extract geometry command helpers 2026-10-28 16:44:11 +00:00
Dylan M. Taylor 90ef99cb2c Simplify file import and export checks 2026-10-28 16:44:05 +00:00
dotCooCoo 3c0c010522 Merge main into feat/trace-keep-colors 2026-10-28 02:58:05 -07:00
dotCooCoo 33ea3f5300 Merge main into fix/batch-override-export-for-screens-folder 2026-10-27 23:06:19 -07:00
dotCooCoo 8b1ff5f32b Merge main into feat/character-anti-aliasing-method 2026-10-27 22:12:22 -07:00
dotCooCoo f3104b154c Undo: the budget test allows unreadable memory; docs name the 32-bit cap
the_default_budget_follows_the_installed_memory_read compares the default
budget with the 1 GiB fallback when the installed memory can't be read,
as default_history_budget does, instead of failing. The reader itself is
still checked by vectorcraft-render's installed_memory_is_read.

The undo history line in docs/target-app-parity.md says the budget is at
most a quarter of the address space in a 32-bit build.
2026-10-27 20:47:51 -07:00
dotCooCoo 5d5b775e27 Merge main into feat/native-save-precision 2026-10-27 20:34:51 -07:00
dotCooCoo 8dcffb3bd3 Merge main into feat/visibility-variables-drive-locked-art 2026-10-27 20:19:07 -07:00
dotCooCoo 86e1d1c2d1 Merge main into fix/undo-history-follows-memory 2026-10-27 18:56:49 -07:00
dotCooCoo 93677c26ec Shape Builder: a large selection merges on another thread
A Shape Builder click or drag over a selection of more than 128
segments merged on the UI thread, so the app stopped drawing and
answering until the merge was done. The tool now computes such a
merge on another thread from the map it built for the hover.
Meanwhile the canvas keeps drawing, the touched regions stay
highlighted and the pointer is the busy pointer. When the merge is
done, the tool runs shapeBuilder.merge with the gesture's params as
one undo step, and the command takes the computed shapes when it
would compute the same ones. The web build merges in the command, as
before.

A press while the map is still being built no longer waits for it on
the UI thread: the tool records the gesture and makes it once the map
is there.

Clicks, drags and the Shape Builder's arrow keys made during the
merge, and commands, menu items, the app's own dialogs, saves and tool
or document switches, run after it on the merged art; engine queries
that only read run at once. A change to the selected paths made
outside a command drops the merge. The headless host and ui.pointer
reply once the merge is applied.

The Shape Builder and the Live Paint Bucket keep their map of the
selection over a new revision of the document when the selected paths
and their outlines are unchanged, instead of building it again.
2026-10-27 17:26:38 -07:00
dotCooCoo d50ac14da2 Outline Stroke: a stroke whose geometry effects leave no path is outlined to nothing
A stroke-only path whose Offset Path or other geometry effect leaves no path
paints nothing on the canvas. Outline Stroke reported "no stroked paths
selected" for it, and Expand and Flatten Transparency kept the live stroke.
An empty group now takes the path's place.
2026-10-27 14:31:17 -07:00
dotCooCoo c273227cf8 Merge main into fix/outline-stroke-keeps-effects-on-outline 2026-10-27 13:51:06 -07:00
skylar 1ffc04506c fix(engine): honor explicit ends when joining direct selections 2026-10-27 11:11:36 -06:00
el-j 464a0710b7 Merge remote-tracking branch 'upstream/main' into fix/compound-path-pen-join 2026-10-27 14:33:06 +01:00
ham9zah 0365cf3ae3 Appearance: shared rows map one by one, and effect dialogs edit each copy (#4174)
Review follow-ups:

- items_per_target maps each requested row on its own. Rows deleted or
  duplicated together, one shared and one not, fell back to the literal
  indices in every object, so the other objects could lose the wrong
  items. The first object takes its own rows; each other object takes
  its copies of the shared ones only.
- effect.dialog takes `aligned` (the panel passes it on a mixed
  selection's shared effect row), and its OK and preview run
  effect.setParams with it, so the dialog edits each object's own copy
  of the effect instead of the first object's index in every object.

Tests: removing a shared stroke with an unshared fill leaves the other
object's fills; an aligned dialog's command carries `aligned`.
2026-10-27 14:36:13 +07:00
ham9zah 8305ccf998 Appearance: a mixed selection lists the attributes its objects share (#4174)
With several objects selected whose appearances differ in anything (two
circles whose fills differ but whose stroke and effect are the same),
the Appearance panel showed only "Mixed Appearances": no stroke, effect
or opacity row, so attributes the objects share couldn't be edited
together. It now lists what they share, as the issue describes:

- `vectorcraft_doc::appearance::SharedStack` (with `align`, a stacking-
  order longest-common-subsequence match) gives the fills, strokes and
  object-level effects every object has an equal copy of, and where each
  is in every object's own stack.
- The panel's object row names the objects' common kind ("Path",
  "Ellipse"; "Mixed Objects" only when kinds differ, as the Control bar
  does), with an italic "Mixed Appearances" row under it when they
  differ, then only the shared rows with their usual controls. The
  Opacity row reads "Mixed" when opacity or blend mode differ (an edit
  sets them all). The object row shows no fill chip when the fills
  differ. Rows aren't dragged in a mixed selection: where a row would go
  in each of several different stacks isn't one place.
- The engine maps a shared row to each object's own item: the active
  item (a row of the first selected object's stack) always does, so the
  Color, Stroke and Transparency panels edit every object's matching
  item, and the item and effect commands do with `aligned: true`
  (appearance.setItem / removeItem / duplicateItem, effect.remove /
  duplicate / setParams / lightingPreset, transparency.set), each as
  one undo step. moveItem and effect.move refuse aligned rows of
  differing stacks. Without `aligned`, explicit indices keep their
  meaning for single objects and scripts.
- "Mixed" is added to every catalog.

Tests: the shared stack of the issue's case, of a stroke at another
index, of nothing alike and of equal stacks, and that matches never
cross; editing the shared stroke edits both strokes (at index 1 and 2)
in one undo step that restores both, also through stroke.set on the
active row; removing a shared effect and adding a fill reach both, and
reordering differing stacks is refused; the panel lists kind, note,
Stroke, Feather and Opacity, not Fill, the full stack for alike objects
and "Mixed Objects" for a path and type. The existing object-row test
now expects the kind for two rectangles and the shared rows when their
opacity differs.
2026-10-27 14:36:13 +07:00
NetterSilverandClaude Haiku 5.5 23f0d323b0 Accept whole-number floats and reject invalid counts for polygon, star and spiral
Found while testing crates/engine/src/cmd/create.rs:344 (polygon) and :358 (star; shape.spiral `segments` at :507 reads the same way)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 14:36:12 +07:00
dotCooCoo 8a3d393596 Merge main into feat/jxl-import
Keep JPEG XL format registration and tests in the reorganized file I/O modules.
2026-10-26 19:23:03 -07:00
aiongg ef991b9c8a refactor(engine): move lib.rs items to {command_sets,doc_state,error,history,interaction,prefs,sanity,session,session/documents,session/edit,session/execute,session/undo}.rs 2026-10-26 22:01:25 +00:00
aiongg 4f7dd93268 refactor(engine): move cmd/variablecmds.rs items to cmd/variablecmds/{csv,library}.rs 2026-10-26 22:01:25 +00:00
aiongg 43ae75d4dc refactor(engine): move cmd/object.rs items to cmd/object/{align,arrange,bounds,commands,compound,props,shape,spans,transform}.rs 2026-10-26 22:01:25 +00:00
aiongg c662e76b84 refactor(engine): move cmd/live.rs items to cmd/live/{blend,commands,common,envelope,mesh}.rs 2026-10-26 22:01:25 +00:00
aiongg f209d2aba2 refactor(engine): move cmd/graph.rs items to cmd/graph/{axis,columns,commands,data,designs,edit,generate,labels,markers,series}.rs 2026-10-26 22:01:25 +00:00
aiongg f02cf1cb7d refactor(engine): move cmd/flatten.rs items to cmd/flatten/{analysis,apply,commands,compose,options,presets,report}.rs 2026-10-26 22:01:25 +00:00
aiongg fa1848be4c refactor(engine): move cmd/fileio.rs items to cmd/fileio/{access,commands,export_names,formats}.rs 2026-10-26 22:01:25 +00:00
aiongg 2a90e82736 refactor(engine): move cmd/fileio/affinity.rs items to cmd/fileio/affinity/{document,import,layout,master_copies,nodes,paint,text}.rs 2026-10-26 22:01:25 +00:00
aiongg 71aef5cc85 refactor(engine): move cmd/draw2.rs items to cmd/draw2/{anchors,commands,common,cut,freehand,smooth}.rs 2026-10-26 22:01:25 +00:00
aiongg c75c6a9586 refactor(engine): move cmd/distortcmds.rs items to cmd/distortcmds/{perspective}.rs 2026-10-26 22:01:25 +00:00
aiongg c2ead918b1 refactor(engine): move cmd/buildcmds.rs items to cmd/buildcmds/{live_paint,shape_builder,trace}.rs 2026-10-26 22:01:25 +00:00
aiongg 48b3ea91f0 refactor(engine): move cmd/brushsym.rs items to cmd/brushsym/{brushes,symbols}.rs 2026-10-26 22:01:24 +00:00
dotCooCoo 70e70924c8 Merge branch 'fix/image-cache-follows-memory' into fix/undo-history-follows-memory
Brings in 8404fa42, which merges main e2a8280. The history budget keeps reading vectorcraft_render::physical_memory, now backed by main's Windows, macOS and FreeBSD readers.
2026-10-26 12:54:45 -07:00
aiongg f1d427762b style: cargo fmt on files moved without formatting 2026-10-26 19:32:10 +00:00