Commit Graph
4900 Commits
Author SHA1 Message Date
dotCooCoo bd12b48e02 Shortcuts: a command on Cmd+Enter or Cmd+Escape runs without the tool's Enter or Escape
Enter and Escape went to the active tool whatever modifiers were held,
and the command bound to the chord then ran as well. With a command
bound to Cmd+Enter, Cmd+Enter ended the Pen tool's path, opened the
Move dialog with the Selection tool, or typed a line break while the
Type tool edited text, and then ran the command. With a command bound
to Cmd+Escape, Cmd+Escape ended the Pen path, the Type tool's text
editing or isolation mode before the command ran.

A press of Enter or Escape whose chord a command is bound to, matched as
the shortcut dispatcher matches it, now runs only that command, also
when the tool claims the key. While the Type tool edits text, only Cmd
chords count. While a tool is in the middle of an interaction (a drag,
or between the clicks of a tool such as Blend or Dimension), and for
plain Enter and Escape and chords that no command is bound to, the key
goes to the tool as before.
2026-10-22 22:44:58 +07:00
BFlat 30d1948f51 Merge pull request #3496 from dotCooCoo/feat/place-links-pdf-and-ai
Place: Link keeps a placed PDF or .ai linked to its file
2026-10-22 22:09:29 +07:00
dotCooCoo c4ac0c5df5 Package: copies the fonts of the type on a linked PDF or .ai page
A PDF or .ai page placed linked is a placed document, and File ›
Package listed only the fonts of the document's own type, so the fonts
of the page's type were left out of Fonts/ where an embedded page's
were copied. Package now reads each linked page a document places and
adds the fonts of its type, also when another document placing the same
file copied it first, and also with copyLinks off.
2026-10-22 21:52:24 +07:00
dotCooCoo 22b6f2a89c Place: Link's tooltip treats a PostScript .ai as a file it embeds
The Place dialog classed every .pdf, .ai and .ait path as a document
that Link places as one locked object. A .ai saved without PDF
compatibility is PostScript, and file.place embeds it as editable art
with Link on or off, so the tooltip was wrong for it. The dialog now
reads the file's header with the same PostScript check file.place uses,
and describes such a file as it does other files Link does not keep
linked.
2026-10-22 21:52:23 +07:00
dotCooCoo 9a72eadc51 i18n: Turkish row for Place's not-a-document message
Turkish is now a complete-message language, so the message file.place
gives for a file that is not a VectorCraft document, a PDF or a
PDF-compatible .ai file needs a tr row.
2026-10-22 21:52:23 +07:00
dotCooCoo 781e62b9cc Place: Link keeps a placed PDF or .ai page linked, as it does images
The File Handling preference Link Placed PDF and .ai Files
(linkPlacedPdf) is removed. File › Place with Link now places a page of
a PDF, .ai or .ait file read as PDF pages as a linked placed document,
as it links a raster image, and link: false embeds the page. Link is on
by default in the Place dialog and in file.place, so a PDF or .ai page
placed from a file, which used to be embedded, is now linked unless
Link is off. A password or textAs: "outlines" still embeds, and so do
EPS and PostScript .ai files.

The Place dialog's Link tooltip describes a PDF or .ai file as a
document.

A .ai file read from its editing data (its PDF part only a placeholder)
finds the files its art places by name, beside it. Package now copies
those files into the same folder as the .ai file, and warns when one has
to be copied under another name.
2026-10-22 21:52:23 +07:00
dotCooCoo 3a1d407f1a Place: a placed PDF or .ai can stay linked to its file (preference, off by default)
File › Place with Link kept a link only to raster images and VectorCraft
documents. A PDF, .ai or .ait page was always embedded as a group, so
Update Links never saw a newer version of the file.

A new preference, File Handling › Link Placed PDF and .ai Files
(linkPlacedPdf), is off by default. With it on, Link places the page as
a placed document, as it places a VectorCraft document: the object
keeps the file's bytes, a preview, the page number and the page's box
(crop, art, trim, bleed or media, stored as page_box). Links check,
update and relink it, Break Link and Object › Expand give the group
placing it without Link gives, every output draws the page's vectors,
and saving and reopening keep the link. With the preference off, or
with a password or textAs: "outlines", the page is embedded as before.
EPS and PostScript .ai files are still embedded.

Package copies a placed PDF or .ai file as it is. The Place dialog's
Link tooltip describes a PDF or .ai file as a document while the
preference is on. Relinking a placed document to a file that is not a
VectorCraft document, a PDF or a PDF-compatible .ai file says so.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-22 21:52:23 +07:00
BFlat def14fc8b2 Merge pull request #3631 from Taha-Mahmoodi/feat/3546-multiple-automation-roots
Automation: --automation-read-root and --automation-write-root may each be given more than once (#3546)
2026-10-22 21:41:28 +07:00
BFlat 0919e54785 Merge pull request #3630 from Taha-Mahmoodi/feat/3147-cli-commands-file
CLI: run --commands reads the steps from a JSON file or standard input (#3147)
2026-10-22 21:41:12 +07:00
bflatastic 1b7cfc8bee Automation: --automation-read-root and --automation-write-root may each be given more than once (#3546)
Squashed from #3631 onto main.
2026-10-22 21:36:15 +07:00
Taha-Mahmoodi 49d69d48e5 CLI: a malformed JSON Lines entry names its step as well as its line (#3147)
Review follow-up (CodeRabbit): a line that wasn't valid JSON was reported
by its line only, while every other --commands error names the run's step.
It now says `step N (line L)`, N counted with the --cmd steps before it.
run_refuses_malformed_commands_naming_the_step expects `step 2 (line 3)`;
it fails without the change.
2026-10-22 21:36:05 +07:00
BFlat 0c41f0af55 Merge pull request #3629 from Taha-Mahmoodi/feat/conte-crayon
Effects: Sketch › Conté Crayon draws the dark areas in a black crayon and the light areas in a white one on textured paper
2026-10-22 21:34:59 +07:00
Taha-Mahmoodi 87fdd4a370 CLI: run --commands reads the steps from a JSON file or standard input (#3147) 2026-10-22 21:34:37 +07:00
Taha-Mahmoodi 9097c4a564 Docs: G2 lists the 57 filters once and its table estimate matches; ROADMAP records Conté Crayon in its revision history
Review follow-up (CodeRabbit): the two older G2 bullets counting 46 and 49
filters were superseded by Conté Crayon (57/57) and are folded into one
current bullet; the G2 row in the ranked table and the entry's estimate now
both say 4–6 h, as ROADMAP.md and roadmap.md do. ROADMAP.md's revision
history gains its 2026-10-22 row.
2026-10-22 21:34:37 +07:00
Taha-Mahmoodi a5778fcfca Effects: Conté Crayon's Foreground Level and Background Level are translated in every catalog 2026-10-22 21:18:35 +07:00
Taha-Mahmoodi bd6d076484 Effects: Sketch › Conté Crayon draws the dark areas in a black crayon and the light areas in a white one on textured paper 2026-10-22 21:18:34 +07:00
BFlat cee30f05b6 Merge pull request #3616 from devius/ka-update
i18n(ka): Georgian for 7 strings added since #2045
2026-10-22 19:30:14 +07:00
BFlat b0be9edabb Merge pull request #3613 from Taha-Mahmoodi/fix/3544-svg-title-artboard
Fix #3544: an SVG of one artboard on its own has the artboard's name as its title
2026-10-22 19:19:23 +07:00
BFlat 896f3aa4a1 Merge pull request #3612 from Taha-Mahmoodi/fix/3370-stroke-scrub-bounds
Fix #3370: stepping a number field stops at its bounds: a stroke weight at 0, a miter limit at 1
2026-10-22 19:19:02 +07:00
BFlat ab1f9f2b92 Merge pull request #3611 from Taha-Mahmoodi/fix/3545-draw-path-invalid-paint
Fix #3545: draw_path and draw_shape with a paint the engine refuses leave the document unchanged
2026-10-22 19:18:41 +07:00
Davit MatchakhelidzeandClaude Opus 5.5 2e2e6a2f8e i18n(ka): Georgian for 7 strings added since #2045
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-10-22 19:18:01 +07:00
BFlat a1ceed08fc Merge pull request #3548 from dwetscher/feat/affinity-designcraft-port
AF.5: Affinity import — strokes, text decoration, text placement, outline effects, CMYK photos
2026-10-22 19:06:31 +07:00
Taha-Mahmoodi 4f9b620dc6 Fix #3544: an artboard name XML can't hold falls back to the document title
Review follow-up (CodeRabbit): the blank-name check ran on the raw name, so
a name made only of characters XML can't hold (control characters) wrote an
empty <title>. The name is sanitised first, then the fallback applies. The
title test covers such a name; it fails without the change.
2026-10-22 18:56:34 +07:00
Taha-Mahmoodi 439d32fcf8 Fix #3544: an SVG of one artboard on its own has the artboard's name as its title
The SVG writer always wrote the document's title in <title>
(crates/svg/src/export.rs), so every file of Export As with Use
Artboards, Export for Screens or an `artboard`/`range` export was titled
after the document ("Icons"), whatever its artboard was called. The
writer's `artboard` option can't tell those apart from Save: Save writes
the first artboard too, standing for the whole document.

ExportOptions has a new `artboard_title` (set by the caller, as
`artboard` is, not deserialized): the file is that one artboard, exported
on its own, and its <title> is the artboard's name (XML-escaped as
before), or the document's title when the artboard has no name. The
engine's SVG encoder (fileio/svg.rs) sets it exactly when the export
names its artboards, which is also when a file holds only that
artboard's art (#550): Export As per artboard, document.export /
serialize with `artboard` or `range`, MCP export, and Export for Screens'
artboards, assets, layers and selections (each written as an artboard
named after it). Save and Save As keep the document's title. Export for
Screens' Full Document draws the art bounds as one artboard, which is now
named after the document, so its SVG keeps the document's title too.
docs/file-format-parity.md's SVG row says so.

Tests: title_control_chars::an_artboard_exported_alone_is_titled_after_it
(crates/svg: two named artboards, the second named with markup;
exporting either alone gives its escaped name, the default and an
artboard without artboard_title give the document's title, an unnamed
artboard falls back to it) and
tests_svgenc::an_svg_of_one_artboard_is_titled_after_it (the engine:
`artboard` 0 and 1, useArtboards range all, Save's SVG, Export for
Screens per artboard, an asset and Full Document). Both fail without the
change (the title was "Icons") and pass with it.
2026-10-22 18:56:34 +07:00
Taha-Mahmoodi cc499d7e8e Fix #3370: each step stops at the bounds, so a step back in the same frame starts from there
Review follow-up (CodeRabbit): step_value clamped once after all of a
frame's steps, so at the lower bound a step down then a step up in the
same frame left the field at the bound. Each step is now clamped as it is
applied. The Stroke panel test steps down and up in one frame at each
bound and expects one step above it; it fails without the change.
2026-10-22 18:56:33 +07:00
Taha-Mahmoodi 28ac044c74 Fix #3370: stepping a number field stops at its bounds: a stroke weight at 0, a miter limit at 1
Wheel notches and the arrow keys over a focused number field step the
value its text shows (widgets::step_value), and nothing there knew the
field's bounds: the Stroke panel's Weight went on to -3 pt and its Limit
to 0 x in the field, while the panel only clamped what it applied (the
spinner's min, Limit's clamp to 1..500), so the next step went on from
the out-of-range text. A label scrub (scrub::field) had the same gap.

step_value and scrub::field now take the field's bounds and stop at
them, so the field shows where they stopped and the value applied is
the same. The fields that have bounds pass them:
- spin_field / spin_field_auto / spin_plain: their `min` (the spinner
  and a typed value already stopped there);
- range_field: its range; it now also takes a value that may be blank
  (None), as mixed_field does;
- opt_field (dash and gap lengths): 0;
- the Revolve dialog's values: their range.
Fields that clamped only what they applied now say their range with
range_field instead: the Stroke panel's Limit (1..500), the Transparency
and Properties opacity (0..100), the Transform panel's polygon sides,
and the dialogs' bounded counts, percentages and lengths (Blend Options,
Envelope, Flattener, Liquify, Freehand, Live Paint, Path ops, Perspective
Grid, PNG ppi, Print, Save for Web, Shape Builder, Slices, Swatch
Options, Symbol options, Justification, Dimension, PDF page, the effect
dialogs' number rows). docs/ui-parity.md's number-field row says so.

Test: panels::stroke::tests::the_wheel_stops_weight_at_zero_and_limit_at_one
(the Stroke panel in a headless frame; focus Weight at 1 pt and turn the
wheel down four notches: the field shows 0 pt and the stroke is 0 pt;
the same with Limit at 2 x: 1 x and a limit of 1). It fails without the
change (the field showed -3 pt) and passes with it; the other numeric
field, scrub and dialog tests still pass.
2026-10-22 18:56:33 +07:00
Taha-Mahmoodi 648a8f5104 Fix #3545: draw_path and draw_shape with a paint the engine refuses leave the document unchanged
The MCP drawing tools ran the creating command (path.create, shape.*)
and only then paint.setFill, paint.setStroke, stroke.set,
object.setProps and effect.apply, each as a command of its own.
check_draw_options checked dash, cap, join, opacity, name and effects up
front, but not the paints: a fill or stroke the engine can't read
(stroke: "currentColor"), or a swatch the document lacks, failed after
the object was made, so the call returned an error and left the path,
its undo step and its SVG node behind. Each styling command was also an
undo step of its own.

The tools now send the creating command and the styling commands as one
command.batch (crates/mcp/src/tools.rs, draw): the engine rolls the
whole batch back when a step fails, so a refused paint leaves no object,
no undo step and nothing in the SVG, and a good call is one undo step
named as the creating command names its own (Pen, Rectangle, …). The
styling steps act on the new object, which the creating command selects,
with item: null (its whole appearance), as the ids did before. The
reply gives the failed step's own error, without the batch's prefix.
docs/mcp.md says so.

Test: tests_drawopts::a_bad_paint_draws_nothing (invalid fill, stroke,
a missing swatch and a non-numeric strokeWidth on draw_path and
draw_shape: error, node count, undo history and SVG export unchanged;
a good call adds one undo step named Pen / Rectangle). It fails without
the change (draw_path with stroke "currentColor" drew a path) and passes
with it; the other draw option and command_batch tests still pass.
2026-10-22 18:56:32 +07:00
dwetscherandClaude Opus 5.5 2d7004f972 AF.5: cap dash patterns at 64 lengths
A line style's `Patn` was copied twice (filtered, then scaled) whatever its
length, before any budget saw it. Patterns longer than 64 lengths now import
as solid lines, with a warning.

Addresses CodeRabbit's third review on #3548.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:44:39 +07:00
BFlat 2f27759bcc Merge pull request #3614 from storytold/land/3610
Fix EPS counted metadata reads after CRLF line endings
2026-10-22 18:34:10 +07:00
dwetscherandClaude Opus 5.5 b3ccdf01a7 AF.5: nested overlays innermost first; embedded master pages; text outline budget
- Overlays bake child-first: a group's overlay now lies over its children's
  (they used to be applied after it and win). Each one pays out of the
  import's overlay work budget.
- Master page instances inside embedded documents are left out with a warning:
  their indices count the embedded document's masters, which aren't kept, and
  would show the outer document's.
- A text outline's gradient stops and dashes are charged to the text attribute
  budget, like the fill's.

Addresses CodeRabbit's second review on #3548.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:24:57 +07:00
dwetscherandClaude Opus 5.5 f73a3df974 AF.5: bound hostile Affinity files; fuzz the new import paths
- One budget for the whole read (layers, pixels, characters, table
  cells), shared with embedded documents and charged also when their
  reading fails; the first reading is dropped before the second.
- Tables: at most 65 536 cells; lines and cell formats that don't
  match the grid are left out instead of shifted.
- Paragraph formats matched to runs by binary search; font names
  longer than 256 bytes are dropped.
- Overlays: one work budget per import; encoded originals decoded
  within their layer's size; table cell outlines recoloured too.
- Engine: master page copies capped, each page's master found in
  linear time, line gaps looked up once per font, the working CMYK
  table built once, objects past MAX_COORD left out.
- Embedded content stays with its node; pixel masks under artistic
  text are read whole and include stroke reach; gradient overlays on
  hidden layers are kept.
- Render: contexts and raster effect images stay within
  MAX_RASTER_SIDE per side (vello overflowed near u16::MAX; found by
  the new fuzz target).
- Tests: affinity tests/import_limits.rs, feature samples in
  import_fuzz.rs, shared builders in engine/tests/support/.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:24:57 +07:00
dwetscherandClaude Opus 5.5 fd50bd6f84 AF.5: Affinity import — strokes, text decoration, text placement, outline effects, CMYK photos
- Strokes set to None no longer import as black 1px strokes.
- Underline and strikethrough are kept.
- Frame text starts at cap height; lines hanging out of a frame make it taller;
  mixed-alignment artistic text lines up within its longest line; line breaks
  inside a paragraph get no paragraph spacing.
- Table cell text taller than its cell is centred and stays visible.
- Outline effects on stroked shapes are drawn around the stroke.
- CMYK pixel layers go through the document's embedded CMYK profile; without
  one, opaque layers stay CMYK.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:24:57 +07:00
BFlat a98ca04196 Merge pull request #3608 from dotCooCoo/fix/export-as-uses-the-picked-format-extension
Export: a typed extension of a format the export doesn't write is replaced
2026-10-22 18:22:47 +07:00
Don Omondi 76399175eb M4: preserve counted EPS metadata across CRLF line endings 2026-10-22 18:19:42 +07:00
BFlat dee53e3413 Merge pull request #3609 from storytold/fix/mesh-banding
Gradient mesh: each tessellated quad shades between its corner colours, no banding
2026-10-22 18:14:49 +07:00
dotCooCoo cbf2d9cc43 Export: a typed extension of a format the export doesn't write is replaced
An export through the desktop save panel kept any typed extension
that names a known format, whatever format it wrote. Export As with
PNG chosen and card.svg typed wrote PNG data to card.svg, and Save as
PDF, Save for Office Documents, Text Export Options, Export Library
and the other writes that go through pick_path did the same.
document.exportSelection with a format suggested and added .png
whatever the format.

Such an extension is now replaced with the written format's, as Save
As has done since #3372: card.svg is written as card.png. An extension
of the written format stays as typed in any letter case (photo.JPEG
for JPEG), Export As SVG (and file.exportAs, file.export.svg) keeps
art.svgz and writes it compressed, and document.exportSelection
without a format still writes the format the typed extension names.
With a format, it suggests and adds that format's extension.

Docs: docs/target-app-parity.md.
2026-10-22 18:13:11 +07:00
bflatastic 00fe2be469 Gradient mesh: each tessellated quad shades between its corner colours, no banding 2026-10-22 17:53:20 +07:00
BFlat 385f1588fc Merge pull request #3583 from storytold/readme/app-icon
README: the app icon above the title
2026-10-22 17:48:15 +07:00
bflatastic fe8b8c8106 README: the app icon above the title 2026-10-22 17:47:51 +07:00
BFlat fc996e0e03 Merge pull request #3560 from dotCooCoo/feat/package-off-ui-thread
Package: File › Package collects and writes the package on the background thread
2026-10-22 17:30:39 +07:00
dotCooCoo 052bbb14b4 Package: File › Package collects and writes the package on the background thread
File › Package… read every linked file and font and wrote the package
on the UI thread, so the window stopped responding until the package
was written. For a document that links 50 images of 21 MB each, OK
with Background Export off, which does the same work, takes a median
of 2.25 s in a release build on Windows.

With Enable Background Export on (Preferences › File Handling), OK now
takes the options, the report's Document Info text and a snapshot of
the document, and closes the dialog. The thread that writes background
saves and exports then collects the links and fonts and writes the
package. The same OK takes 0.001 s, and the package holds the same
bytes. A save of unsaved changes that Package starts is written first.
With the preference off, on the web and over MCP and scripts, the
package is made at once with the same reply as before.

Docs: docs/target-app-parity.md.
2026-10-22 17:20:48 +07:00
BFlat f553be25f7 Merge pull request #3476 from makesomethingshit/fix/3416-recolor-edit-scroll
Fix Recolor Artwork Edit overflow with large palettes
2026-10-22 17:17:38 +07:00
makesomethingshit e56e40dd85 M3.78: keep Recolor Edit chips within the screen (#3416) 2026-10-22 17:10:16 +07:00
BFlat bbaf7b02c3 Merge pull request #3557 from dotCooCoo/fix/artboard-paste-makes-the-new-artboard-active
Artboards: the active artboard follows the Artboard tool after Paste, Cut and tool.setOption
2026-10-22 17:07:02 +07:00
dotCooCoo 5d4cac31cf Artboards: the active artboard follows the Artboard tool after Paste, Cut and tool.setOption
With the Artboard tool, Paste and Paste in Place select the pasted
artboards, Cut gives the tool the artboard before the first cut one
(the new first artboard when artboard 1 is cut), and tool.setOption
sets its selected or active artboard. The active artboard, the view's,
stayed where it was, so the navigator showed another artboard than the
tool and the Artboards panel. document.activate and File › Revert
started the tool on artboard 1, while choosing a document's tab starts
it on that document's active artboard.

After a command that changes the artboards the Artboard tool has
selected, the app now makes the tool's active artboard the active one,
as a click with the tool does. After a command that chooses another
document, and after Revert, the tool starts on the document's active
artboard.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-22 16:59:38 +07:00
BFlat 22c83b5951 Merge pull request #3554 from Taha-Mahmoodi/fix/3443-layers-scrollbar
Fix #3443: the Layers list's scroll bar no longer covers the rows' selection marks
2026-10-22 16:50:18 +07:00
Taha-Mahmoodi 7141932dfb Fix #3443: the Layers list's scroll bar no longer covers the rows' selection marks
The Layers list scrolls in its own scroll area, which kept egui's floating
scroll bar when #3093 gave the panel bodies a bar in a column of their own.
Hovering the list showed the bar over the right edge of the rows, over the
target circles and the selection squares the user clicks to select a layer.

The list now uses the menus' slim solid bar (list_scroll_style) in its own
column, and the rows end beside it. The list knows its total height before
it lays the rows out, so the bar is shown exactly when the rows overflow
(decided up front): the rows never shift sideways a frame after the bar
appears.

Test: dock::tests::the_layers_scroll_bar_covers_no_row (40 layers in a
1440x600 window: no row's accessibility rect intersects the bar). It fails
without the change and passes with it. The Layers tests that pin row
positions across frames still pass. Checked live: the bar sits beside the
rows.
2026-10-22 16:43:39 +07:00
BFlat bbc9bd3de2 Merge pull request #3551 from Taha-Mahmoodi/fix/2452-hover-while-panning
Fix #2452: panning or zooming over detailed art no longer hit-tests it every frame
2026-10-22 16:17:18 +07:00
BFlat 423bc71705 Merge pull request #3550 from Taha-Mahmoodi/fix/3093-panel-scrollbars
Fix #3093: panel scroll bars take a column of their own instead of covering controls
2026-10-22 16:17:03 +07:00
BFlat c60ad8dcfa Merge pull request #3549 from Taha-Mahmoodi/fix/2103-artboard-nudge
Fix #2103: arrow keys with the Artboard tool move the selected artboards and their art
2026-10-22 16:16:48 +07:00