Commit Graph
1378 Commits
Author SHA1 Message Date
Jürg Lehni caaccaf5be Type: images in SVG glyph documents draw with the glyph
They were left out because glyph art had no document to keep them in; since #3879 they hold their own pixels until a document takes them in.
2026-10-24 06:00:22 +07:00
bflatastic a564fe5d8e PDF export: pattern fills are written as tiling patterns (#3849) 2026-10-24 05:59:04 +07:00
bflatastic c2ad9f8bbb Type: emoji go with type that effects reshape and Expand Appearance
Squashed from #3879 onto main.
2026-10-24 04:49:06 +07:00
Marco Arnulfo d0919ad0df Actions: Create New Set and Set Options… name action sets, and a recording goes into the current set (#3712) 2026-10-24 03:52:20 +07:00
bflatastic 255ce30103 Docs: one ranked-table row each for G2 and G13 in gaps.md 2026-10-24 03:31:54 +07:00
bflatastic 6e3ecc3adf Select: Start Global Edit edits the objects of the same shape together, each in proportion
Squashed from #3781 onto main.
2026-10-24 03:09:12 +07:00
dotCooCoo 342d6e055d Docs: the bulleted list parity line gives the first line's start and the wrapped lines'
The line said the text and its wrapped lines start at the left indent. The first line's text starts at the next tab stop after the bullet, with the left indent counted as a stop; only the wrapped lines start at the left indent.
2026-10-24 03:09:11 +07:00
dotCooCoo 2a2e63856c Type: the Paragraph panel's Bullets button turns paragraphs into a bulleted list
VectorCraft had no lists. A bullet could only be typed as a character,
and the tab after it did not stop at the left indent. The reference app
turns paragraphs into a bulleted list from the Paragraph panel, the
context menu and Type › Bullets and Numbering › Apply Bullets.

text.setFormat takes list ("none" or "bullet") and bullet ("none",
"dot", "dash", "circle" or "square"). The layout draws the bullet before
the first line of each list paragraph that has text, in the font, size
and color of its first character, and starts the text at the next tab
stop, with the left indent counted as a stop. Wrapped lines start at
the left indent, and type on a path draws the bullet too. In a
horizontal right-to-left list paragraph the indents are mirrored: the
bullet hangs at the right, and the wrapped lines start at the right
where the first line's text starts after the bullet. The bullet is not
a character of the text. A paragraph whose left and first-line indents
are both 0 gets 18 pt and -18 pt when it joins a list. Return continues
the list, paragraph styles carry it, Reset Panel turns it off, and the
native file saves it only when it is set.

The Paragraph panel has a Bullets button with a list of the five
presets, and Type › Bullets and Numbering › Apply Bullets and the
context menu run type.applyBullets. Live SVG text writes the bullet's
character, and SVG writes type on a path with bullets as outlines. DXF
writes the bullet as a TEXT entity of its own, and legacy .ai export
warns that bulleted lists are left out. The caret, selections, Touch
Type and Show Hidden Characters skip the bullet.

Live SVG text of horizontally scaled type now writes each tab in a
tspan of its own. Before, the characters before a tab shared its tspan,
and its textLength made a viewer stretch them across the tab's gap.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-24 03:09:11 +07:00
Taha-Mahmoodi 89abc2c9d2 Symbols: a symbol's thumbnail draws the symbols it nests and the patterns and images it uses
Review follow-up (CodeRabbit): thumbnails rendered the symbol alone in an empty document, so a
symbol nesting another one, or painting with a pattern or showing an image, lost that art in the
Symbols panel and in symbol library panels. The preview now renders an instance alone on a copy of
the document (or library) the symbol comes from, which keeps its resources but none of its art,
and the thumbnail cache also follows the art of the document's symbols and patterns.
2026-10-24 03:09:10 +07:00
Taha-Mahmoodi 7da5e3062d Symbols: Window › Symbol Libraries opens symbol libraries whose symbols click into the document's Symbols
Window › Symbol Libraries and the Symbols panel's Symbol Libraries Menu open each symbol library
in a library panel of its own, on the shared library panel code the swatch and graphic style
libraries use (https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/create-or-import-symbol-libraries.html,
.../symbols-panel-options.html, .../create-and-place-symbols.html).

- Basic Shapes: one original library generated in code (circle, squares, triangle, diamond,
  hexagon, star, burst, arrow); no other app's libraries.
- A click adds a library symbol to the document's Symbols as one undo step, with its Symbol
  Options and the resources it uses (nested symbols, patterns, swatches, brushes, graphic styles,
  images), and makes it the current symbol; one already there isn't added again, a clash of
  names numbers the library's. Shift/Cmd-clicks select for Add to Symbols. Dragging a library
  symbol onto the canvas adds it and places an instance there, in the same step.
- Save Symbols… (library menu, Window menu) and Save Symbol Library… (Symbols panel menu) write
  the document's symbols as a .vectorcraft document holding only them, into the user library
  folder (User Defined, window.userSymbolLibrary1-10) or a file; Other Library… reads the symbols
  of any document VectorCraft opens, and a file it can't read is an error.

Commands: symbol.libraries, symbol.library, symbol.addFromLibrary (place, x, y),
symbol.saveLibrary, symbol.loadLibrary; UI: window.symbolLibrary (.other), ui.saveSymbolLibrary
(dialog saveSymbolLibrary). i18n rows in every complete catalog; docs updated.
2026-10-24 03:09:10 +07:00
bflatastic f247772042 Type: selected type shows its in and out ports, and the in port threads backwards
Squashed from #3817 onto main.
2026-10-24 03:09:10 +07:00
Taha-Mahmoodi b321a77d38 Smart Guides: a tool switched to under a still pointer is hovered at once
The hover's context now includes the active tool (PR review follow-up):
switching to the Selection tool with Alt held and the pointer still
showed no Distance Guides until the pointer moved, since the new tool
had never hovered. A tool switch re-hovers at the pointer whatever the
tool; the rest of the context (canvas, view, document and revision)
re-hovers tools that ask (Tool::wants_rehover).

canvas::tests::distance_guides_follow_what_lies_under_a_still_pointer
adds a tool switch (fails without the rule), a modal dialog with its
backdrop over the pointer, and another document opened under it.
2026-10-24 03:09:10 +07:00
Taha-Mahmoodi 7693aeb508 Smart Guides: Distance Guides follow what lies under a still pointer
One rule for the hover's context (PR review follow-ups): the target is
recomputed where the pointer is, or cleared, whenever what lies under it
can change while Alt is held.

- The tool hovers only where the pointer is on the canvas itself
  (`on_canvas`): under a floating panel, a menu, a popup or a dialog
  its hover ends (Tool::hover_ended), as when the pointer leaves the
  canvas or a pan, zoom or view-rotation press starts.
- The canvas keeps the hover's context: its rect, the view's centre,
  zoom and rotation, and the active document's id and revision (which
  moves with every edit, undo and selection change). When it changes
  under a still pointer, a tool that asks (Tool::wants_rehover; the
  Selection tool after an Alt hover) is hovered again at the pointer,
  so wheel, gesture, key or command scrolling and zooming, tab
  switches, edits and undos re-measure.
- Coming back onto the canvas re-hovers the tool.

canvas::tests::distance_guides_follow_what_lies_under_a_still_pointer:
a floating window over the pointer (fails without the on_canvas filter),
a 150 pt scroll that leaves empty artboard under it (fails without the
re-hover), a square made under it. select::tests::distance_guides_need_
alt_a_selection_and_smart_guides checks wants_rehover.
2026-10-24 03:09:09 +07:00
Taha-Mahmoodi 26bf324c81 Smart Guides: a pan, zoom or view-rotation press ends the Distance Guides hover
An Alt+Space pan press went to Drag::Pan and never reached the tool, so the Distance Guides stayed through the pan until the pointer left the canvas (PR review follow-up). One rule now covers all three cases: the tool's hover ends (Tool::hover_ended, renamed from pointer_left; Session::tool_hover_ended) when the pointer leaves the canvas with no press the tool holds, and when a press starts any drag that doesn't reach the tool (pan, middle-button pan, zoom box or scrub, view rotation, touch gesture). canvas::tests::distance_guides_go_when_the_hover_ends asserts they go at an Alt+Space press and stay gone panning out over a panel.
2026-10-24 03:09:09 +07:00
Taha-Mahmoodi 822effa384 Smart Guides: Distance Guides go when the pointer leaves the canvas with Alt held
Leaving the canvas (for a panel, or out of the window) sent the tool no
hover event, so the Selection tool kept its Alt-hover target and went
on drawing the measurements (PR review finding).

- Tool::pointer_left: the pointer left the canvas with no button held;
  the Selection tool drops its Distance Guides target and its Smart
  Guides hover label. Session::tool_pointer_left reaches it.
- The canvas remembers whether the pointer was over it last frame: on
  leaving with no drag it calls tool_pointer_left; on coming back it
  re-hovers the tool even after a single jump egui doesn't count as
  moving, so the guides show again.

Tests: canvas::tests::distance_guides_go_when_the_pointer_leaves_the_
canvas (fails before: the label stayed over a panel with Alt held),
select::tests::distance_guides_need_alt_a_selection_and_smart_guides
(pointer_left clears them).
2026-10-24 03:09:09 +07:00
Taha-Mahmoodi a301760aa6 Smart Guides: Alt over another object or the artboard shows the selection's distances to it
Distance Guides, from the public "Use Distance Guides for accurate
placement" and "Smart Guides options" (Invoke Distance Guides) pages:
with Smart Guides on, something selected and the Selection tool,
holding Alt/Option over another object shows the horizontal and
vertical distances between the selection and it, and over an empty
part of an artboard the selection's distances from the artboard's left,
right, top and bottom edges.

- guides::distance_guides measures the selection's bounds to a box: on
  an axis on which they lie apart, the gap between their facing edges;
  otherwise the offsets of both pairs of matching edges. Each line runs
  across the middle of the overlap on the other axis (else the
  selection's middle), with a dashed line out to the target's edge when
  it passes beside it. An artboard is always measured edge to edge.
  Labels are in the General unit, in the smart guide colour.
- The Selection tool keeps what its Alt hover points at and draws the
  guides while idle; letting Alt go (the canvas re-hovers on a modifier
  change), a press or moving off clears them.
- Overlay::Distance: the line with end ticks and its length in a pill;
  the canvas draws it.
- Preferences › Smart Guides › Invoke Distance Guides (`distanceGuides`,
  on by default), with i18n rows; View › Smart Guides gates it too.
- Hover only, as the pages describe: nothing while moving or nudging.

Tests: select::tests::alt_over_* (gap and edges beside and below,
artboard edges in pt and mm), distance_guides_need_alt_a_selection_and_
smart_guides, tests_prefs::invoke_distance_guides_measures_the_selection_
to_the_object_under_alt, canvas::tests::alt_over_another_object_draws_
the_distance_guide_label (frame test of the drawn label and line).
2026-10-24 03:09:09 +07:00
Taha-Mahmoodi e7f2ab9a8e Docs: Pathfinder › Trap ships; Effect › Pathfinder 13/13, Illustrator effects 49/50 2026-10-24 03:09:08 +07:00
bflatastic 422ecc0c77 Artboards: Artboard Options shows video safe areas and sets the pixel aspect ratio that View › Rulers › Show Video Rulers counts the artboard in
Squashed from #3831 onto main.
2026-10-24 03:09:07 +07:00
Taha-Mahmoodi 7c8840b93a Tools: double-clicking the Line Segment, Arc, Rectangular Grid and Polar Grid tools opens their Tool Options
Step 3 of the Rectangular Grid tool and Arc tool help pages
(https://helpx.adobe.com/illustrator/using/tool-techniques/rectangular-grid-tool.html,
https://helpx.adobe.com/illustrator/using/tool-techniques/arc-tool.html)
and the Tools page (https://helpx.adobe.com/illustrator/using/tools.html)
say a double-click on the tool opens its Tool Options. For these four tools
the double-click (tool.options, and Enter once #2891 lands) said the tool
had no options (#2691).

The Tool Options are the tool's click dialog in dialogs/shapes.rs, opened
without a place to draw (shapes::open_options; a `__toolOptions` field
marks it), as the review of #2979 asked: no copied form code. Its OK runs
tool.setOption and draws nothing; Cancel changes nothing. The grids' dialogs
are titled Rectangular Grid / Polar Grid Tool Options there. The options
are what the next drag draws with and what a click's dialog opens at:
- Line Segment: Length and Angle (now kept by the tool and used by the
  click dialog) and Fill Line (new: shape.line {fill: true} gives the line
  the current fill; the click dialog has it too);
- Arc: lengths, Reference Point, Type, Base Along, Slope, Fill Arc;
- the grids: dividers, skews and switches, plus the Default Size and
  Reference Point, now kept by the tool (width, height, reference) for the
  click dialog; a drag leaves them out of its command.
The grids' Reference Point is drawn only as the 9-point locator (it also
showed as a plain number field).

New strings (Rectangular Grid Tool Options, Polar Grid Tool Options, Fill
Line) have rows in all 24 catalogs.

Verification:
toolbar::tests::double_clicking_the_line_arc_and_grid_tools_opens_their_tool_options
double-clicks each tool's toolbar button: the dialog and its title, Cancel
leaves the next drag as it was, OK draws nothing, the next drag uses the
option and a click's dialog opens at it (a grid at its Default Size);
line_segment_options_and_fill_line, grid_default_size_is_an_option_the_drag_leaves_out,
fill_line_gives_a_new_line_the_current_fill. Grid dialog tests now expect
the stored Default Size. Live: double-clicking the Rectangular Grid tool in
the release build opens Rectangular Grid Tool Options; OK with 2 and 3
dividers, then a drag, draws a 3 × 4 grid.
2026-10-24 03:09:07 +07:00
dotCooCoo 01509e2316 Docs: the eraser end's switch waits for the pointer to come back over the window 2026-10-24 03:09:06 +07:00
dotCooCoo 8fa918b30b Pen tablets: a pen's eraser end selects the Eraser on Linux under X11 and XWayland
On Linux under X11, and under XWayland, the X server lists a pen's
eraser end as a pointer device of its own, and winit 0.30 hands it to
the window as a mouse. Turning the pen over and dragging the eraser
end drew with the active tool, for example a Paintbrush stroke.

The tablet reader now marks a pen device whose name or XInput type
contains "eraser", and Services::stylus carries the pen's end with
its pressure. Once a frame while no press is held and the pointer is
in the window, the canvas selects the Eraser when the eraser end
moves, and it selects the tool used before when the tip moves again.
The return goes through Session::switch_tool, so a perspective grid
hidden meanwhile stays hidden. A tool chosen while the eraser end
stands in stays when the tip comes back. The feed starts afresh when
the pen turns over, so a press of one end does not take the pressure
the other end last touched with. Native Wayland is unchanged.

The switch follows PhotoCraft's sync_eraser_tool
(storytold/photocraft#255, and storytold/photocraft#5043 for the
cleared tool).

Docs: docs/development.md, docs/gaps.md, docs/hardware-parity.md,
docs/target-app-parity.md.
2026-10-24 03:09:06 +07:00
Dan Green 632598ba17 Libraries panel: items show as icons or in a list, toggled beside the library picker
The list view gives colours and graphics a row each with the item's name (a colour's hex value too, where it fits); items drag and drop by row there. Graphic thumbnails are shrunk on the CPU to the size they are drawn at, as egui-wgpu doesn't mipmap textures and thin strokes broke up in small tiles.
2026-10-24 03:09:06 +07:00
Marco Arnulfo c39be04573 Pencil: Shift held at the press draws a straight line at 45° steps (part of #3799) 2026-10-24 03:09:06 +07:00
bflatastic b3673b7f32 Fix status-bar coordinates after changing the ruler origin
Squashed from #2847 onto main.
2026-10-24 03:09:06 +07:00
aiongg cb06c7b34e i18n: missing translations never fail; xtask ci prints one report (#3856)
* i18n tests: report missing translations instead of failing

The scans for untranslated menu strings, tl! literals, undo labels, menu labels and status
messages print what a complete language lacks. A missing translation shows the English
text until a translator adds the row, so it no longer fails the gate.

* i18n: one untranslated report in xtask ci; drop one-language wording tests

`i18n::tests::untranslated_report` lists, per language, the menu strings, tl! literals, undo
step names and templates, menu labels, render-site labels, status and error messages,
PostScript error meanings and default swatch names its catalog lacks; strings every language
lacks are listed once. It never fails on a gap, only on a scan that finds too few strings.
`cargo xtask ci` runs it in an `i18n-report` step, and `cargo xtask i18n-report` runs it alone.

The five scan tests, the render-site and swatch coverage tests and untranslated_messages.txt
fold into the report. Tests that pinned one language's wording are gone; where they also
checked code (lookups, plural rules, locale tags, menu and History step names, engine
parameter errors), that check stays and compares with the catalog lookup.
2026-10-23 16:01:11 -04:00
Taha-Mahmoodi 52d93cfce5 Font menu: Font Technology › Variable lists only variable font families
As Illustrator's help describes (Filter fonts by classification, Font Technology › Variable), the font menu gets a Font Technology row whose Variable choice lists only the families with variation axes, together with the script, kind, favourites and search filters, and stays set from one opening to the next. A family is variable when any of its faces, loaded or scanned, has an `fvar` table, so a family installed as static files beside a variable one counts.

Spec: https://helpx.adobe.com/illustrator/desktop/design-with-text/fonts-and-scripts/find-apply-and-adjust-variable-fonts.html
2026-10-24 02:06:16 +07:00
Jürg Lehni a26ac22794 SVG: type written as outlines stacks colour glyphs under their run's paint
As on the canvas: each run's bitmap, COLR and SVG glyphs come before the run's path, so a plain character set over a colour one stays on top.
2026-10-24 02:06:13 +07:00
Jürg Lehni c8fe9193d9 Type: colour glyphs go with type that effects reshape and Expand Appearance
COLR and SVG glyphs, which have no outlines, were left out wherever type was taken as its outlines: geometry effects, Expand Appearance, EMF/WMF export.
2026-10-24 02:06:11 +07:00
Jürg Lehni 863a954cd2 Type: SVG glyph documents follow the SVG table's coordinate rules
A root size without a viewBox sets up the em, a viewBox that isn't square is centred in it, and a glyph element nested in others is drawn on its own, as if used.
2026-10-24 02:06:09 +07:00
dotCooCoo e313c93e2c Render: live objects in symbol art and opacity masks keep their evaluations between frames
The renderer placed symbol art that holds a blend, an envelope or a
repeat again for every instance in every frame. The live object in the
placed copy was a new node each time, and the renderer evaluated it
again. Symbol art that is itself a live object, an opacity mask whose
art is one, and a live object in a clip group in Outline view were
drawn from an uncached copy in every frame. At 1000 x 1000 pixels,
2,000 instances of a symbol holding an envelope took 126 ms a frame on
main, and the same 2,000 envelopes on the page took 30 ms.

The renderer now keeps the placed art of instances and inline graphics
when it holds blends, envelopes or repeats, and when the art is itself
a live object. It draws kept live art, opacity-mask art and Outline
view clip members that are live objects from the live cache, which
evaluates them again when the symbols, patterns, fonts or color
settings they show change. The 2,000 instances take 28 ms a frame,
with the same pixels.

Docs: docs/target-app-parity.md
2026-10-24 02:06:08 +07:00
Taha-Mahmoodi 309119dc4c Docs: Simplify of part of a path is described in our own words, not the help page's
Review follow-up (CodeRabbit, clean-room): the test header, gaps.md and
target-app-parity.md quoted a phrase from the cited help page. Comments
and docs only.
2026-10-24 02:05:19 +07:00
Taha-Mahmoodi 606796b934 Simplify: with part of a path direct-selected, Object › Path › Simplify refits only the selected runs of segments
The Simplify help page says a path can be simplified whole or "a specific
path region with the Direct Selection tool"
(https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/manually-simplify-paths.html).
Simplify refitted every selected path whole, whatever part of it was
direct-selected.

object.path.simplify now looks at each path's direct selection: the
segments picked as segments, or else every segment with both anchors
selected. Each run of consecutive selected segments is refitted on its own
(vectorcraft_pathops::simplify_part) between its two end anchors, which stay
where they are with their handles on the side away from the run; every
other anchor and segment stays exactly as it was. A closed path's run may
cross its start point (the path then starts at the run's end anchor),
separate runs on one path simplify independently in one undo step, and the
runs stay direct-selected (as segments when segments were picked). A path
with only lone anchors selected still simplifies whole, as before, and
wholePaths: true simplifies partly selected paths whole. The slider, its
preview, Auto-Simplify and the Simplify dialog all run the command, so
they act on the part too. simplify_with now shares its run fitting with
the part refit (same results).

Verification: crates/engine/src/tests_simplify_part.rs (unselected anchors
and segments equal bit for bit, run end anchors fixed, the run within the
tolerance, two runs equal to each simplified alone in one undo step, a
closed run across the start point, picked segments, wholePaths, slider
preview and Auto-Simplify, a run that can't be simpler records no step)
and simplify_slider_and_dialog_simplify_only_the_direct_selected_part_of_a_path
(the slider's preview, Enter and the dialog's OK). Live: the release build's
slider at level 20 on a 91-anchor wave with anchors 30–60 selected refits
only that stretch.
2026-10-24 02:05:19 +07:00
Taha-Mahmoodi 0a83c19911 Fix #3705: art drawn or pasted inside an isolated object in Draw Inside stays in isolation mode
The drawing modes help page
(https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/learn-drawing-basics/drawing-modes-overview.html)
has you draw or paste into the selected object in Draw Inside. The isolation
help page
(https://helpx.adobe.com/illustrator/desktop/manage-objects/select-objects/isolate-objects.html)
isolates a path, group or object on a double-click with the Selection tool,
and puts art added in isolation mode where the isolated art was once the mode
ends, so that art belongs with what is isolated.

Steps: select a path, choose Draw Inside, double-click the path with the
Selection tool (it is isolated), then draw or paste. The art went into the
clip group Draw Inside makes around the path, but isolation mode stayed on the
bare path, so the new art was outside it: drawn dimmed, passed through by
clicks and marquees, and a double-click on it selected the path and never
entered the clip group. Double-clicking an existing Draw Inside clip group
from outside isolation mode already isolated it, and pastes then went inside
it; that is unchanged.

Cause: create::insert_look_turned and edit::paste_clip put the art into
create::draw_inside_group's clip group but left DocState::isolation on the
object drawn into, and with a single object isolated, hit testing reaches
only that object.

Change: when the object drawn into is the isolated one, isolation moves to its
clip group (create::isolate_draw_inside_group), after new art and after Draw
Inside pastes. A drag's previews replay from its snapshot and make the group
under the same id, and cancelling the drag restores the isolation it began
with. Undo and redo end isolation mode when the isolated object is no longer
in the document (the undone clip group), as mask editing already does,
instead of keeping an id a later object may take.

Verification: drawing_inside_an_isolated_object_keeps_the_new_art_in_isolation_mode
fails without the change (isolation Some(NodeId(2)) instead of the clip group
Some(NodeId(6))). A whole-window egui run through the control channel's
ui.click/ui.drag (double-click the rectangle, drag an ellipse inside, paste,
click the copy) left isolation on the rectangle and the click selected the
rectangle before; after, isolation is on the clip group and the click selects
the copy.
2026-10-24 02:05:19 +07:00
dotCooCoo 0083587131 Threaded text: Convert To Point Type on a frame no longer duplicates its text
Converting a frame of threaded text to point type left the frame's
text in the point type, and the re-flow also added that text to the
story, as it does for a frame that was deleted. The other frames then
held it a second time.

A frame that is still in the document as point type now leaves its
thread with the text it holds. The story flows through the other
frames without that text, so each character is held once. A thread
left with one frame is no longer a thread, and undo restores the
thread.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-24 02:05:18 +07:00
dotCooCoo 44b1310f49 Linux: a tablet pen's pressure reaches the tools under X11 and XWayland
On Linux under X11, and under XWayland, winit 0.30 hands a drawing
tablet's pen to the window as a mouse without its pressure. Every pen
stroke reached the tools at full pressure: a Calligraphic brush with
Size on Pressure drew one width, and the Liquify tools' Use Pressure
Pen applied full intensity.

When the window is on X11, the desktop app now reads XInput2 raw
events on a second X connection (x11rb's RustConnection, without
unsafe). A device with an "Abs Pressure" valuator is a pen, unless
its XInput type is TOUCHPAD or TOUCHSCREEN. Its pressure, scaled from
the valuator's range to 0..1, goes into Services::stylus, and another
pointer moving clears it. The canvas uses that pressure when a frame
has no touch force. A press takes the highest pressure of the pen's
latest touch since the canvas last read the feed, and waits up to
5 ms for the touch of a hovering pen. The moves and the release of a
held press keep the pressure the pen last touched with when its lift
has already arrived. Native Wayland is unchanged.

The valuator mapping and the reader come from PhotoCraft's tablet
crate (storytold/photocraft#255), with the pressure axis only.

Docs: README.md, docs/development.md, docs/hardware-parity.md,
docs/target-app-parity.md.
2026-10-23 11:31:17 +07:00
Louise FoxandClaude Opus 5.5 8906abf30d SVG: keep other applications' attributes from import to export
Many tools note things on SVG objects in attributes of their own XML
namespace: a plotter's pen, a cutter's passes, a game engine's
collision flag. Import dropped them, so a file that went through
VectorCraft lost what other software had set on its objects. #522
asked for unknown and data-* attributes to survive a round trip; #580
kept the data-* ones, and this keeps the ones in other applications'
namespaces.

- Import keeps every attribute in a namespace that isn't SVG's,
  XLink's, XML's, Inkscape's, Sodipodi's or VectorCraft's own, on the
  object or layer it belongs to, in order, with its namespace URI and
  the prefix the file gave it (ObjectAttributes::foreign, beside the
  data-* attributes and the event handlers). The file is untrusted: at
  most 64 per object and 64 KiB per value, and what is left out is
  warned about. Files that declare no other namespace skip the scan.
- They are the object's own, as its data-* attributes are: a pasted
  copy and a duplicate keep them, and deleting the object takes them
  with it. The native file keeps them in the object's attributes, a
  key written only when an object has some.
- Export writes them back on the same elements and declares each
  namespace once, on the root, under the file's prefix. A prefix the
  export uses itself, or that another namespace already has, becomes
  prefix2, prefix3...; one XML keeps for itself (xml...) becomes ns. A
  name or value XML can't hold is left out with a warning.

Nothing in VectorCraft reads them, and nothing in the UI lists them.

svg/tests/foreign_attributes.rs: a layer's and a shape's attributes
survive import, export and import again, Inkscape's and Sodipodi's
own aside and with no warnings, and the native form round-trips;
prefixes are made unique, and bad names and values are left out with
a warning; the caps hold and are warned about once.
engine tests_attributes.rs: a pasted copy and a duplicate keep them, a
deleted object leaves none behind, and the native file and SVG export
keep them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
2026-10-23 10:56:00 +07:00
Taha-Mahmoodi fb74be021f Docs: file-format-parity's status line carries the PDF import spacing change
Review follow-up (CodeRabbit): the revision rows were dated 2026-10-23 but
the status line still read 2026-10-21 and didn't mention the change.
2026-10-23 10:42:45 +07:00
Taha-Mahmoodi b6b1ec7300 PDF import: keep a line's glyphs on course when the file's widths are a little off the font's advances
Cause: since #3742 place_each measures a line's spacing from the file's
own widths, so tracking is only the file's Tc/Tw/TJ. Where the widths
are systematically off the installed font's advances (up to the 1% that
still counts as the same font), those differences add up along a line
and nothing made up for them. With no kerning and every tracking value
0, place_each returned the untracked layout early, without the 0.02 em
placement check (CodeRabbit's review of #3742): a line of embedded
Source Sans 3 with /Widths 0.8% wide opened 3.7 pt off. With the early
return removed the check rejects the line and the line-wide fit takes
over: one whole-thousandth tracking value with the font's kerning,
0.30 pt off (as before #3715). Before #3742 main placed it exactly but
with a run per character (49 and 62 runs).

Change: the early return is gone; every line goes through the check.
Before it, on_course() keeps the file's spacing where the glyphs stay
within 0.01 em of the file's; otherwise each stretch of the line takes
one share after every character (the longest stretch one share keeps
within 0.01 em), so ordinary text stays one run, and a line whose
differences change course (30 m then 30 i) takes a run per stretch (2).

Verification: new test
a_long_line_of_widths_a_little_off_the_fonts_advances_lands_in_one_run
(embedded Source Sans 3, widths x1.008 and x0.992, a kerned and an
unkerned line): one run each, glyphs under 0.24 pt (0.02 em) from where
the file draws them. Without on_course it fails after #3742, with and
without the early return ("glyphs 0.300 pt"); before #3742 it fails on
the run count. It replaces an Inter test drafted for the review: Inter
isn't embedded there, so the file's outlines are the interpreter's
stand-in stretched to the widths, not Inter, and the contours differ by
0.6 pt even with exact widths. tests_text_placement and
text_becomes_point_type pass; cargo xtask ci: all 10 steps passed.

Thanks to the CodeRabbit review on #3742 for the early-return finding.
2026-10-23 10:42:45 +07:00
Taha-Mahmoodi eb4635fd4b Fix #3705: pastes go inside the object in Draw Inside, and Shift+D no longer stops at Draw Behind
The drawing-modes help page
(https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/learn-drawing-basics/drawing-modes-overview.html)
has you draw or paste into the selected object in Draw Inside. It also says
the modes apply to Paste in Place and Paste on All Artboards but not to Paste
in Front or Paste in Back.

Cause: only new art (create::insert_look) read the drawing mode. The paste
commands (edit::paste_clip) always used the insertion parent, so a paste in
Draw Inside landed on the layer above the object, outside the clip.
Double-clicking the object first only isolated the bare path, which didn't
change where the paste went. Separately, view.drawMode {} went from Draw
Behind to Draw Inside whenever one object was selected, then refused it unless
the object was a path, compound path or type. So with one group selected
(such as the clip group Draw Inside makes), Shift+D and the toolbar button
failed every time and stayed in Draw Behind.

Change: the clip-group wrapping moves into create::draw_inside_group, which
new art and pastes now share. In Draw Inside, Paste, Paste in Place and Paste
on All Artboards insert the copies at the top of that group, in the paste's
one undo step, ignoring Paste Remembers Layers. In Draw Behind, Paste in Place
and Paste on All Artboards go just below the selection, as Paste in Back does.
Paste in Front, Paste in Back and pasted artboards keep their placement.
Shift+D goes on to Draw Inside only when it can draw into the selection, and
otherwise back to Draw Normal.

Verification: pasting_in_draw_inside_puts_the_copies_inside_the_object,
paste_in_place_in_draw_behind_goes_behind_the_selection and
shift_d_goes_past_draw_behind_whatever_is_selected all fail without the
change ("Layer" != "Clip Group"; index 3 != 0; the Draw Inside error on a
selected group). cargo xtask ci: all 10 steps passed.
2026-10-23 09:56:07 +07:00
Taha-Mahmoodi 64f6d2db50 Artboards: Artboard Options' Show Center Mark and Show Cross Hairs draw on the canvas
The Artboard Options help pages describe two display options
(https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/organize-manage-artboards/modify-display-settings-of-artboards.html,
.../set-video-display-options-for-artboards.html): a mark at the artboard's
center, and lines from the middle of each side through the center. The
document already had show_center_mark and show_cross_hairs on Artboard, but
nothing set or drew them.

artboard.setProps (each `artboards` entry too) and artboard.new take
showCenterMark and showCrossHairs as true/false, in the same single undo
step as the other fields; a locked artboard can still change them.
Artboard Options has both checkboxes, filled in from the artboard whether
it opens from the panel or a double-click with the Artboard tool, and its
New Artboard form passes them to artboard.new. The canvas draws them in the
new theme token artboard_mark, 1 point wide on screen at any zoom: a cross
12 points across at the center whose arms follow the artboard's sides, and
the two cross hairs. They are screen-only like the edges; exports and print
never read the fields. inspect_document reports them when on. Saved files
are unchanged: the two keys were already always written, so a document that
never shows them saves byte for byte as before, and a file without the keys
opens with both off. Video safe areas, video rulers and the pixel aspect
ratio are not part of this change.

Verification: artboard_display_marks_are_set_and_cleared_in_one_undo_step,
artboard_display_marks_round_trip_through_the_native_format,
artboard_display_marks_stay_out_of_exports (SVG and PDF identical with the
marks on and off), an_artboard_shows_its_center_mark_and_cross_hairs
(positions, screen-constant size under zoom, rotated view) and
artboard_options_show_center_mark_and_cross_hairs. cargo xtask ci: all 10
steps passed.
2026-10-23 09:42:09 +07:00
Taha-Mahmoodi 08cfb45561 Simplify: Object › Path › Simplify opens its slider at the Auto-Simplify level for the selection
The Simplify help page says the slider starts at an auto-simplified value
(https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-paths/manually-simplify-paths.html).
The slider opened at a fixed level 50 (1 pt), whatever the size of the
selection, so the first preview was the same tolerance for a 30 pt icon as
for a 1000 pt drawing.

The level Auto-Simplify picks for the selection is now one engine function,
vectorcraft_engine::auto_simplify_level (the existing 1/400-of-the-longer-
side rule in vectorcraft_pathops), which object.path.simplify {auto: true}
and the slider's opening fields both use. Object › Path › Simplify (and
ui.menuDialog, the Properties and contextual task bar buttons, which go
through the same menu dialog) opens the slider there, and the canvas
previews that result; Auto-Simplify still moves the slider back to it.
The retained Simplify dialog path is unchanged.

Verification: simplify_slider_opens_at_the_auto_simplify_level_of_the_selection
opens the slider on 1140, 285 and 28.5 pt waves (levels 35, 55, 89) and
checks the preview is exactly object.path.simplify {auto: true}'s result;
without the change it fails with level 50 != 35, and
simplify_opens_a_slider_whose_drag_previews_fewer_or_more_points fails with
{"level": 50} != {"level": 55}. cargo xtask ci: all 10 steps passed.
2026-10-23 09:42:07 +07:00
dotCooCoo ddaca94e5c Export for Screens: files are encoded and written on the background thread
Export for Screens and the Asset Export panel's Export encoded and wrote
every file on the UI thread, so the window stopped responding until the
last file was written. For 50 artboards in PNG at 1x, 2x and 3x, PDF and
SVG (250 files, 47 MiB), the dialog's OK took a median of 43.8 s in a
release build on Windows.

With Enable Background Export on (Preferences › File Handling), OK now
checks the settings, remembers them and takes a snapshot of the
document, and the dialog closes. The thread that writes background
saves and exports then encodes the files and writes them into the
folder, or one ZIP in it, and the export progress dialog's Cancel stops
it before the next file. The same OK takes under 0.001 s, and the files
have the same names and bytes. With the preference off, without a
folder, on the web and over MCP and scripts (document.exportForScreens
and assets.export), the files are made at once with the same reply as
before.

Both commands report one progress step per file, so the progress dialog
shows "File 3 of 250" and MCP progress notifications count the files in
place of the encoders' artboards and pages.

Docs: docs/target-app-parity.md, docs/control-protocol.md, docs/mcp.md.
2026-10-23 09:22:20 +07:00
Taha-Mahmoodi b87cda7d4b PDF import: keep one tracking value on letters whose file widths round off the installed font's advances
Cause: place_each (crates/pdf/src/import_text.rs, #3715) works out each
character's tracking as the file's distance between glyph origins minus
the installed font's advance. A PDF gives widths to the thousandth of an
em (the standard-14 AFM metrics or /Widths), so with a font of 2048 units
an em (Helvetica on macOS) every character is a hair (~0.0002 em) off.
When the line isn't evenly spaced as a whole (a TJ word gap, a space read
from a gap), `even` keeps every character's own value, rounding noise
included, and restyled() splits the line into a run per character.
text_becomes_point_type failed on macOS; Linux CI passed because Helvetica
isn't installed there, its stand-in's widths differ by more than 1% and
place_each falls back to the line-wide fit.

Change: measure the spacing from the file's own widths: each character's
pen is moved by how much wider the file says the characters before it are
than the font's advances, where the file gives the character's width and
the layout sets it as a glyph of its own. The tracking is then the file's
(Tc, Tw, TJ), whatever font is installed; the layout check that the glyphs
land within 0.02 em of the file's places is unchanged.

Verification: text_becomes_point_type and all of tests_text_placement pass
on macOS. New test widths_rounded_off_the_installed_fonts_advances_keep_one_tracking
uses built-in Inter (non-embedded, widths rounded to thousandths), so it
doesn't depend on system fonts; without the fix it fails on any platform
with runs N/e/x/t/ /li/n/e/ /h/e/re. cargo xtask ci: all 10 steps passed.
2026-10-23 09:22:20 +07:00
dotCooCoo 5fe4f3ead1 Place: files are read and decoded on a background thread
Squashed from #3740 onto main.
2026-10-23 09:07:55 +07:00
Jürg Lehni c28ad99678 Type: OpenType SVG colour fonts draw and export as vector art
Each glyph's SVG document is read by the SVG importer, which the engine registers with the text crate.
2026-10-23 09:07:54 +07:00
Angelo Bernardini ef1fa1d228 G12: Constrain Angle for the arrow keys, Shift drags and new objects (#394)
Squashed from #3363 onto main.
2026-10-23 08:54:57 +07:00
BFlat 960fee6bb3 Merge pull request #3738 from dotCooCoo/feat/windows-pen-barrel-button
Windows: a pen's barrel button is the right mouse button with Windows Ink
2026-10-23 08:29:33 +07:00
dotCooCoo 2416677edd Docs: twelve parity doc lines describe the reference pages without quoting them
Twelve lines in docs/target-app-parity.md quoted text from the
reference app's public pages: a masks tutorial, a blog post on
artboards, the default keyboard shortcuts, the Contextual Task Bar
announcement and the Brushes page. Each line now links the same page
and describes what it says in the doc's own words. Option, button and
tool names stay as they are, and so do VectorCraft's own labels and
status messages.
2026-10-23 08:19:55 +07:00
Louise FoxandClaude Opus 5.5 79cb38f678 Plug-ins: heavy filters, allowed at install and run in the background (G17)
A filter plug-in gets 50 M instructions and a minute for each run.
Filters that order a plotter's paths, pack shapes or run a simulation
need minutes, and the app shouldn't freeze while they compute.

A filter's manifest may now ask for "budget": "heavy" (Budget): each run
gets HEAVY_FUEL (1 G instructions) and HEAVY_WALL_TIME_MS (300 s). Only a
filter may ask. plugin.install refuses such a filter without allowHeavy:
true, and plugin.inspect returns the entry a module would have without
installing it. Install Plug-in… and File › Open ask the user and install
on OK; the plug-in store keeps the answer, and folder loads skip heavy
filters with a note, since they ask nobody.

The app runs a heavy filter on a thread of its own (plugin_runs), not in
Background Save's queue, with the status bar and a progress window
(Cancel, Continue in Background) that it shares with Background Export
(export_progress::window). FilterRun splits plugin.run: prepared on the
session, run on any thread, its output handed in, and the next
plugin.run with the same params applies that output instead of running
the plug-in again, so the edit stays one guarded, journaled command. A
result whose document or plug-in changed meanwhile is dropped, and the
status bar says to run it again. A cancelled plug-in run is now
EngineError::Cancelled.

What a filter keeps with the document (#3720) goes with the run: it is
given _context.documentData when the run is prepared, and the
documentData of its output is applied with its objects, in the same undo
step, when the run is handed in (FilterRun::finish).

Tests: plugins/tests/abi.rs (100 M loop steps out of a light budget,
within a heavy one; folders skip heavy filters), manifest.rs, engine
tests_plugins.rs (consent, inspect, the store, hand-in, a handed-in
run's document data, cancel) and
ui-egui tests_plugin_runs.rs (installing asks, no Preview, a background
run as one undo step, a stale result kept out, the progress window).
CRAFT_UI_HEAVY_FILTER_WASM=<folder> writes a test filter to try in the
app. Its strings and messages have rows in each complete catalog
(COMPLETE_MESSAGES).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
2026-10-23 08:18:47 +07:00
dotCooCoo 8064c77b1e Windows: a pen's barrel button is the right mouse button with Windows Ink
With Windows Ink on, winit reports a pen in contact as a touch and
drops its barrel button, and egui-winit presses the primary button for
that touch. A tap with the barrel button held selected art instead of
opening the canvas context menu, and a drag with it held moved art or
drew with the active tool.

A message hook (winit's with_msg_hook) now notes each WM_POINTERDOWN
and whether its wParam has POINTER_MESSAGE_FLAG_SECONDBUTTON, which
Windows sets for a pen in contact with its barrel button pressed.
Before egui reads a frame's input, the primary press and release that
egui-winit made for such a contact become the secondary button, as
#3659 maps the barrel button on Linux. A canceled contact, or one
whose end never arrives, gets a secondary release. A touch with
the tip alone and finger touches are unchanged. photocraft's 8fc62f55
by animo fixed the same fault there.

Docs: docs/target-app-parity.md.
2026-10-23 08:01:43 +07:00