Commit Graph
1402 Commits
Author SHA1 Message Date
dotCooCoo dfbc1ee866 Render: brush art is made again when a symbol or pattern it can show changes
Brush art leaves out the symbol instances and pattern paints whose art
draws a brushed stroke. The brush cache now records whether a brush's art
or the stroke's paint can show a symbol or a pattern, and drops those
entries when the art of the document's symbols or patterns changes, so an
edit that makes a symbol or pattern start or stop drawing a brushed stroke
draws as a new renderer does.

The parity line gives the median and fastest-frame figures of the
benchmark for both documents it names.
2026-10-24 17:29:33 +07:00
dotCooCoo bba0618f55 Render: brush art of symbol instances, inline graphics and repeat copies is kept between frames
The renderer kept brush art per node id and stroke. The copies drawn
for symbol instances, inline graphics and repeat copies share their
art's ids, so they replaced each other's brush art and it was made
again in every frame. The pieces were drawn as plain nodes, so brush
art that is a live object, such as an envelope that a scatter brush
places, was evaluated again in every frame, for a path on the page too.

The brush cache now also keys brush art by the stroked path, so each
copy keeps its own. Kept pieces are drawn through the renderer's
caches: their stroke outlines, live-effect geometry and live object
evaluations are kept between frames. Each frame draws what a new
renderer draws. On one thread at 1000 x 1000 pixels, 2,000 paths on
the page whose scatter brush art is an envelope took 400 ms a frame on
main and take 94 ms, and the fastest frames of 2,000 instances of a
symbol whose art is a brushed path went from 16 ms to 12 ms, with the
same pixels.

Docs: docs/target-app-parity.md
2026-10-24 17:29:33 +07:00
dotCooCoo c5d010e7d7 MCP: document.inspect and document.open report the File Info title as fileInfoTitle
The title field of document.inspect and the document.open reply is the
document's tab name, which comes from the file name once the document
is saved to or opened from a file. A client had to call file.info or
document.info to read the File Info title, so reading a saved
document's title from these replies gave the file name instead.

Both replies now add fileInfoTitle, the title file.info reports, and
keep title as it was. docs/mcp.md's File Info section describes both.
2026-10-24 17:29:33 +07:00
dotCooCoo e71bf71376 MCP: the file tools send paths made absolute against the server's working directory
With --connect, open_file, save_file, export and screenshot {path} sent a
relative path to the app unchanged, and the app resolved it against its
own working directory (/ when started from the Finder). A relative path
then named a different file than it does headless, where the server
resolves it against its own working directory.

The server now makes these paths absolute against its working directory
before it sends them, including export's options.path and the path a
screenshot reply reports. Paths inside run_command and command_batch
params go to the app as given. docs/mcp.md describes the rule.
2026-10-24 17:29:33 +07:00
dotCooCoo 58827952b7 MCP: screenshot and render_preview draw edges as a PNG export does
ui.render, which render_preview and screenshot call in the headless
session and in the app, rendered the artboard with the canvas options:
8-bit compositing and edges smoothed shape by shape. A PNG export
composites in floating point and supersamples edges, so the preview
showed edge colors the exported file does not have, most visibly where
shapes meet edge to edge.

ui.render now renders with the options of a PNG export on white, so
its pixels equal the export's at the same scale. render_preview also
no longer enlarges an artboard smaller than max_side: with the default
max_side of 1024, a 612 x 792 page came back as an interpolated
791 x 1024 image, and it now comes back at 612 x 792. docs/mcp.md and
docs/control-protocol.md describe this.
2026-10-24 17:29:33 +07:00
dotCooCoo 5f16fc3639 Image Trace: imageTrace.make refuses unknown keys inside params
imageTrace.make and imageTrace.makeAndExpand checked their own keys, but
not the keys inside `params`. A misspelled setting such as `colours` was
dropped, and the trace ran with the preset's value and no error.

A key inside `params` that is not a trace setting is now an error that
names the nearest setting, as for a command's own keys. Errors from
imageTrace.makeAndExpand, including those for `enhanced` and a missing
swatch library, name that command instead of imageTrace.make.
2026-10-24 17:29:33 +07:00
dotCooCoo 389bb4d790 Image Trace: the semi-transparent edge of a cut-out image traces as the color beside it
A pixel on the edge of a cut-out image with an alpha of 128 or more was
traced with its own color. When that color was a darkened edge tone, it
took whichever palette color lay nearest, and the edge traced as 1 px
slivers of a color that appears nowhere near it.

In Color and Grayscale, a pixel with an alpha of 128 to 254 next to a
pixel under half opaque now takes a color of the fully opaque pixels
within two pixels of it when its own color is that color or a darker
shade of it, counting only pixels it reaches without crossing a
transparent gap; of several such colors, it takes the one the most of
those pixels have. It keeps its own color when one of those pixels has
it, or when its color is not a shade of any of theirs, so a thin
semi-transparent outline of another color stays as it is. Ties go to the color nearest its own, by its gray
in Grayscale. Opaque images trace as before.
2026-10-24 17:29:32 +07:00
dotCooCoo 7da8edab79 Recolor: recolor.colors and recolor.reduce read the art Select All takes when nothing is selected
recolor.colors and recolor.reduce were unavailable with nothing selected, so
listing a document's colors needed a select.all first, which also changed the
selection. Both now take ids and read the selection when ids is left out.
With nothing selected they read the objects Select All takes and the objects
inside them: hidden and locked objects are left out with what they hold, also
inside a group, as are the objects of hidden, locked and template layers, and
in isolation mode only the isolated art is read. The selection is left
unchanged. An object named in ids or selected is read with everything it
holds, and an object listed twice, or listed together with a group that holds
it, counts once. recolor.apply still recolors only the selection.
2026-10-24 17:29:32 +07:00
dotCooCoo 5e8d41fb8f Image Trace: a filled traced path that straightens to no area is left out
Fitting straightens the pieces of an outline that lie close to a line, and
Snap Curves To Lines straightens more of them. The outline of a single
pixel, or of a region one pixel thick along an edge such as an opaque row
of a mid tone under a light shape, can then have all its pieces on one
line. The trace held a selectable path with no area, for example a path
140 x 0 px with Snap Curves To Lines.

In a Color, Grayscale or Black and White trace with Fills, a fitted
outline or hole whose area is below 1e-6 px^2 is now left out. A path
with no area paints nothing, so the traced result paints the same pixels
as before. Without Fills, the 1 px stroke that outlines each area draws
such a loop, so it is kept. A single closed loop keeps its area as
before.
2026-10-24 17:29:32 +07:00
dotCooCoo fea8c084e8 Pencil: the closing cursor's texts say Alt is pressed after the drag starts
The Pencil and PencilClose entries in cursors.toml, the PencilClose doc
comment and the closing-cursor line in docs/target-app-parity.md said Alt
held while drawing closes the path. A drag started with Shift or Alt
held draws an open straight line and does not show the closing cursor,
so the texts now say Alt is pressed after the drag starts and name that
exception.
2026-10-24 17:29:32 +07:00
dotCooCoo 4c5c54ccbc Fix #3799: Alt held at the press draws a straight Pencil line at any angle
With Alt key toggles to Smooth Tool off, holding Alt when a Pencil drag
starts now draws one straight segment from the press point to the
pointer, at the angle the pointer takes, as Shift does at 45° steps.
Pressed on an end of a selected open path, the line continues the path
from that end. Both ends snap to Smart Guides as a stroke's ends do, and
the line stays open when Alt is still held at the release. Shift and Alt
together keep the 45° steps.

Before, a drag started and released with Alt held drew a closed freehand
path. Alt pressed after the drag has started still closes the path at
the release, and with Alt key toggles to Smooth Tool on, Alt still switches
to the Smooth tool. Before a Pencil press, Alt no longer shows the closing
cursor.

Docs: docs/target-app-parity.md, docs/ui-parity.md.
2026-10-24 17:29:32 +07:00
Taha-Mahmoodi 23c0e8c501 Fix #3964: Create Outlines stacks each run's ruby right after that run's glyphs, as the canvas paints it 2026-10-24 17:29:32 +07:00
Taha-Mahmoodi cab62cbb18 Fix #3964: Create Outlines, EPS, EMF/WMF and legacy .ai keep the ruby where the canvas draws it 2026-10-24 17:29:32 +07:00
dotCooCoo 3e0f9795c2 Threaded text: Create Outlines and Expand on a frame no longer duplicate its text
Create Outlines on a frame of threaded text removed the frame, and the
thread's re-flow then added the frame's text to the story, as it does
for a frame that was deleted. The other frames held that text a second
time while the outlines drew it. Object › Expand, and Expand Appearance
where it outlines type or groups it with a shadow, outer glow or Crop
Marks, did the same.

An outlined frame now leaves its thread with the text it shows, and the
other frames keep the text they hold. When the thread's last frame is
outlined, the text it has no room for goes after the text of the last
frame that stays. When Expand Appearance keeps a frame as type in a
group, the type takes the frame's place in the thread. 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 17:20:47 +07:00
Taha-Mahmoodi d3a01307ea PDF import: a string entered from many dictionaries in skipped text is read once
Review of #3924: in a flat area of ` <<(` (CMYK or RGBA 32, 60, 60, 40) in an uncompressed image, each `<<` after white space was read as a dictionary, and each read its string, which never ends, to 64 KiB past it: about 16 K bytes a pixel, so a few hundred bytes of it spent the budget and the file was refused.

Whether a byte in a literal string is escaped doesn't depend on where reading began (the run of `\` before it lies after any `(` before it), so the string from any `(` ends after the first `)` that takes the file-wide count of `(` less `)` below the count at that `(`. Those ends are found for the whole file in three linear passes the first time a string read in skipped text runs past 64 bytes: forward for the final count, back for the `(` whose string never ends, forward pairing the rest. Ends further than 64 bytes are kept (at most one for every 16 bytes of the file; a file built to nest past that is refused); nearer ones are read again. So entering a string costs at most 64 bytes, charged as one. A comment's end of line is kept for every comment read, not only the last.
2026-10-24 17:19:59 +07:00
Taha-Mahmoodi 2d5024bf82 PDF import: a run of "<" in skipped text no longer spends the pre-open scan's budget
Review of #3924: every `<<` in a string, comment or stream's data was read as a dictionary of its own, and in a run of `<` (an uncompressed image's flat area of grey 60) every position is one, each read to past the run, so about N²/2 bytes for N `<` and a 1 MiB run refused the file.

The reader starts a dictionary there only where its repair scan tries one (after white space or a dictionary's `>>`) or after an object's `N G obj`: so only those `<<` are read, in a run of `<` only the first. A reading goes on as far as 64 KiB past the last `<<` opened on the way, looks at each nested dictionary's `>>` for a stream and marks each `<<`, so it is not read again; a comment's end of line is kept, as many readings cross the same comment; the `<<<` second-`<` reading in the main pass goes (the reader never starts there). The dictionary text a stream check parses is charged to the budget.
2026-10-24 17:19:59 +07:00
Taha-Mahmoodi 1ec46732b8 PDF import: the pre-open stream scan doesn't let a "stream" inside a literal string hide the real stream after it
Review of #3919: the scan raised its walk-back floor at every `>>stream`, so one inside a literal string moved the floor past the `<<` of the cross-reference stream around it, and that stream was never looked at. The backward walk also counted a `>>` inside a string, and missed a comment between `>>` and `stream`.

The scan now reads the file forward once, as the reader does: literal strings (nested, escaped), hex strings and comments are skipped, `<<` and `>>` pair on a stack, and a dictionary followed by `stream` is a stream's, whose data is skipped. Because the reader can still start at a `<<` inside skipped text (a cross-reference offset or its repair scan), each such `<<` is read as a dictionary of its own, up to 64 KiB, within a budget of four times the file plus 1 MiB; a crafted file that spends it is refused. The scan stays linear.
2026-10-24 17:19:59 +07:00
aiongg 75dacb4fec Build: craft-pdf, hayro and krilla at craft-libs 2eca9b3a, with hayro-postscript from craft-libs (#3957)
hayro-postscript now comes from craft-libs' vendored copy, whose token scanner skips << and >>
runs without recursion, instead of crates.io 0.1.0. The rev also brings the reader input bounds
and crash fixes from craft-libs #257 and #261. The pdf-deps check requires hayro-postscript from
craft-libs; ASSETS.md, NOTICE and docs name the new rev. The asset hashes are unchanged.
2026-10-24 04:07:00 -04:00
aiongg 41c765e984 Build: craft-pdf, hayro and krilla at craft-libs main (#3931)
* Build: craft-pdf, hayro and krilla at craft-libs main

Picks up the input bounds that landed after ca50d63b: zero-width xref streams, the JBIG2
referred-to segment count, the graphics-state stack depth and decoded PNG sizes.

* Build: craft-libs 9f75d3dc, with hayro-cmap from craft-libs

Adds the /W2 CID-width bound (#254) and takes hayro-cmap from craft-libs' vendored copy,
which skips malformed CMap parts, instead of crates.io 0.1.0.

* Build: the pdf-deps check, ASSETS.md, NOTICE and docs name craft-libs 9f75d3dc

The check also requires hayro-cmap from craft-libs. The asset hashes are unchanged.
2026-10-24 03:05:17 -04:00
bflatastic b1e36d69f3 macOS: a tablet pen's pressure reaches the tools (#852)
An AppKit local event monitor (apps/vectorcraft/src/mac_tablet.rs) reads each mouse and tablet event's pressure and the pen's end before winit's view handles it and writes them into the canvas's StylusFeed. The feed keeps the pressure of each pointer move, and the canvas pairs them with the frame's moves, so a fast stroke keeps its curve and each point its own pressure; mouse event coalescing is off. The eraser end selects the Eraser tool, as on Linux. Ported from PhotoCraft's crates/tablet (pressure and eraser end only).
2026-10-24 09:26:27 +07:00
bflatastic 76c9aa579d .ai import: an image too large for the editing data's reader opens the file as its PDF part, not with a blank layer (#1479) 2026-10-24 08:39:00 +07:00
aiongg 8cc69ed45c CI: one test binary per crate, nextest, line tables only (#3855)
* CI: line tables only for workspace crates, no debug info for dependencies

* CI: one integration-test binary per crate

Each crate's tests/*.rs files become modules of tests/it/main.rs. Files that change
process-wide state (Color Settings, the font database, installed plug-ins, the working
directory), the fuzz sweeps and the test targets the Affinity corpus workflow names stay
their own binaries.

* Docs: name the integration tests' new paths

* CI: xtask test with nextest and doctests, one wasm check, clippy --keep-going

cargo xtask ci runs the tests with cargo nextest (cargo test when it is not installed),
builds them with --lib --bins --tests, runs the doctests as their own step, and checks
the wasm crates in one cargo check --keep-going call.
2026-10-23 21:36:10 -04:00
Angelo BernardiniandClaude Opus 5.5 e11ff0352b G11: Shape Builder pieces: contract the sweep's stubs between two cuts of one path
Where two passes of a path (an open line's way there and back) cross another
path, the sweep can cut them a rounding error apart and join the two cuts
with a stub a little over its tolerance. Upstream's twin-piece joining
already merges the ends of the two passes, but not such stubs, so dense
hatches at exact binary steps (lines every 0.5 pt across a 45° line) still
came back with a speck of a piece per crossing. The planar map now contracts
such stubs into one vertex before the edges are built (within 4x eps up and
down the sweep's frame and 1/16 of the 0.01 pt precision along it), bounded
by each cluster's extent so a run of tiny, nearly level segments (a finely
flattened or traced path) is not collapsed transitively.

Ported from the fork's d5985b14 and 616a5c4a with their tests:
a_dense_hatch_at_exact_binary_steps_cuts_each_line_once_per_crossing (fails
without the fix), a_run_of_tiny_level_segments_is_not_contracted_as_stubs
(fails without the extent bound) and
a_line_crossed_where_its_two_passes_part_is_cut_once (already passing on
upstream, kept as a regression test).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014gCpjL3yUhaH3jRp7ksufh
2026-10-24 08:05:47 +07:00
Taha-Mahmoodi ea55f89c24 Fix #3586: the PDF pre-open scan reads a file dense with stream keywords in linear time
Each `stream` keyword walked back up to 64 KiB through every dictionary before it, so 1 MiB of `<</A <</B 1>> >>stream` took minutes. A walk back now stops where the previous keyword's walk began, and a stream without a Length shares the search for the next `endstream`.
2026-10-24 07:35:07 +07:00
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