Commit Graph
1535 Commits
Author SHA1 Message Date
dotCooCoo 80bf11dd8b Blob Brush: Alt switches to the Smooth tool while it is held
The reference app switches the Blob Brush to the Smooth tool while Alt
(Option) is held. In VectorCraft an Alt drag with the Blob Brush painted
a blob like any other drag.

With Alt held at the press, or pressed before the drag has moved, the
Blob Brush shows the Smooth tool's cursor and a thin line without the
length label, and the release smooths the selected paths under the
drag with path.smoothRegion, as the Pencil does when Alt key toggles to
Smooth Tool is on. Shift does not constrain a smoothing drag, a stroke
that has moved keeps painting when Alt goes down, and with nothing
selected the release changes no path.

Choosing another tool during a Pencil smoothing drag no longer leaves
the Pencil showing the Smooth tool's cursor when it is chosen again.
deactivate now clears the smoothing state, for the Blob Brush too.

Docs: docs/target-app-parity.md.
2026-10-29 10:01:52 +07:00
dotCooCoo 78c51b86e6 Docs: Edit Colors and Color Guide read a null parameter as one left out 2026-10-29 09:57:33 +07:00
dotCooCoo 8c0750e848 Edit Colors: Saturate without an intensity is an error instead of a pass at 0
edit.colors.saturate's params text names intensity as required, but a
call without it, or with a string such as "40", read the intensity as
0 and still ran Saturate. It changed no visible color but recorded a
Saturate undo step.

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

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

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

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

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

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

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

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

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-29 09:57:33 +07:00
Don Omondi def9e0f08e Blend: nested blends keep to a step budget (#3579)
A blend whose keys are blends draws each of its steps as a copy of a key,
and that copy makes its own steps when it is drawn, so step counts capped
per pair multiplied with nesting: five levels of 15 steps were 1.3 million
steps from a 23 KB file, 3.4 GB to export.

A blend now makes at most 100,000 steps in all (MAX_BLEND_STEPS), counting
the steps of the blends inside its keys and steps. Past the budget each pair
of keys keeps the same share of its steps; a copy of a blend spaced by
distance counts the most steps its spacing can make, since scaling it up
adds steps. Blends within the budget draw every step as before.
2026-10-29 09:57:33 +07:00
Dylan M. Taylor 7a3b62b3b9 Docs: point the review disposition at the renumbered §8.10; drop Windows 7 from the change headers 2026-10-28 18:36:04 -04:00
Dylan M. Taylor cc3ca69d50 Remove the experimental Windows 7 build
Drop the unsupported Windows 7 x64 portable build and everything that existed only for it:
the windows7 workflow, packaging script and docs/windows7.md; the windows7 feature and the
win7-target cfgs (plain cfg(windows) now); the Windows 7 print transport
(printing/windows7.rs); the glow renderer path; and vendor/windows-link, the patched crate
only that build used. NOTICE, README and the living docs no longer describe it; dated
change-history rows are left as they were.
2026-10-28 17:57:17 -04:00
aiongg 54c4f82638 Merge pull request #4295 from Jeremy8776/feat/install-scope-policy
Windows installation policy: VectorCraft
2026-10-27 17:16:50 -04:00
aiongg 13d837363d Dropdowns: menus grow back after a search with no matches (#4337)
* chore(deps): pin craft-libs to 62bb0e1d (shared combo box)

* fix(ui-egui): dropdown menus grow back after their contents shrink

Every combo box is craft_ui::ComboBox, whose menu follows its contents'
height while it is open (egui#5225); clippy.toml bans egui::ComboBox.

The font family menu is a craft_ui::combo::Menu under its field, with the
search field and filters in its header and the family rows scrolling under
them, still laid out only in view. The menu keeps to the room on its side
of the field, so the list's minimum height and the popup's room are gone.

* fix(ui-egui): the shortcut import's target picker grows back after a search

Its search field, "No equivalent" and the row's choices are the header of
a craft_ui::combo::Menu, and Keyboard Shortcuts' entries scroll under
them. That was the last menu on the popup path that capped itself at the
room left, so that path and combo's `searchable` flag are gone.

* chore(deps): craft-libs 60d60d48 (dropdown fixes)

* chore(deps): craft-libs 4d7b82f7 (dropdown fixes)

* chore(deps): craft-libs 92d392cf (#387 merged)

* chore(xtask): pdf-deps expects craft-libs 92d392cf
2026-10-27 10:36:58 -04:00
Jeremy 2298383f55 docs(installer): say that silent installs are per-user and when the scope page appears 2026-10-27 12:26:56 +00:00
Jeremy 005f5b4d39 docs(installer): note that the 32-bit all-users folder is Program Files (x86); tidy wording 2026-10-27 12:25:21 +00:00
Jeremy 094b228aea docs(installer): describe the per-user default install location and scope 2026-10-27 12:09:04 +00:00
dotCooCooandHiggs32584 52c92cb419 Windows: a maximized window stops a pixel short of an auto-hidden taskbar
An auto-hidden taskbar reserves no work area, so the guard that keeps
the custom title bar's window borderless left a maximize covering the
whole monitor. The shell treats a caption-less window that covers its
monitor as a full-screen app, and in PhotoCraft, which has the same
guard, the hidden taskbar then did not show when the pointer reached
its edge (storytold/photocraft#4845).

While a maximized window covers its whole monitor, the guard now finds
the taskbars (the Shell_TrayWnd and Shell_SecondaryTrayWnd windows),
takes the monitor edge nearest each one's center, and stops the window
one pixel short of the monitor on each of those edges where the work
area reaches the monitor's edge. A taskbar that reserves a work area
is still cleared by the move into the work area. This ports
Higgs32584's fix in PhotoCraft (storytold/photocraft#6878).

Docs: docs/target-app-parity.md.

Co-authored-by: Higgs32584 <mhiggins217@gmail.com>
2026-10-27 14:36:12 +07:00
dotCooCooandFredApps a5f0e6ba36 Windows: a maximized window stays clear of a taskbar on the top or a side
With the taskbar on the top edge, a maximize, a restore from the
taskbar or a drag to the top of the screen showed a classic title bar
above the app bar (#3242, #3723, #2072). The guard that keeps the
custom title bar's window borderless trimmed only the bottom of a
parked maximize to the work area, so with the taskbar on the top or a
side the window still covered it, and the strip between the window's
edge and the client area, which winit keeps on the work area, was
drawn as an old-style caption.

The guard now moves a parked maximize inside the work area on every
edge, keeping the resize border it was parked with, which it reads
from an edge without a taskbar. A rect it already moved, a dragged or
snapped window and a monitor with no reserved taskbar are left alone.
The move uses SWP_FRAMECHANGED and SWP_NOCOPYBITS and invalidates the
window, so the whole window is drawn again, and the style strip uses
SWP_NOCOPYBITS too. This ports FredApps's fix in PhotoCraft
(storytold/photocraft#5237).

Docs: docs/target-app-parity.md.

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-10-27 14:36:12 +07:00
dotCooCoo a0d11d0da6 Docs: cite the public thread help for outlining a threaded frame
The parity doc's line on Create Outlines, Expand and Expand Appearance
on a frame of threaded text gave no source. It now links the public
help pages on creating and removing text threads, and says that they do
not describe outlining a threaded frame, so the handling is VectorCraft's
own behavior.
2026-10-27 14:36:12 +07:00
aiongg e724228e25 docs: update source paths for the moved files 2026-10-26 22:01:25 +00:00
dotCooCoo 1c4ec81ec2 Placed documents: a ready bitmap redraws only its objects, and bitmaps on screen are not made again
On screen, placed documents (PDF, .ai and .vectorcraft) draw from
bitmaps made in the background. Each bitmap that became ready changed
a generation in the canvas key, so the canvas rendered the whole frame
again. The bitmaps share a 512 MiB budget, and making one dropped the
least recently drawn others, even those on screen: with more than that
on screen, every frame dropped a bitmap it drew and asked for it again,
without end. A bitmap that could not be made (the file can't be read
and there is no preview) also changed the generation and was asked for
again in every frame.

When a bitmap is ready, placed_document::made_between names its file,
and the canvas renders again only where the objects showing it are
(placed_area), as it does after an edit, when the frame shown was
rendered on the UI thread and that part is at most half the frame.
Art of a symbol, a graph design, a pattern or a brush that shows the
file still renders the whole frame.

Bitmaps drawn or made since a frame began are not dropped to make room
for the ones it asks for. They stay past the budget up to 2 GiB. A
bitmap that would take them past 2 GiB is not kept, and its file draws
exactly at that zoom. A bitmap that can't be made shows the
placeholder, does not change the generation, and is not asked for
again.

Docs: docs/target-app-parity.md.
2026-10-26 21:41:44 +07:00
dotCooCoo 6ed4608d82 Images: an image past the 2 GiB decode allowance draws at reduced size instead of a crossed box
A PNG, JPEG, GIF or WebP image that takes more than the decode
allowance as RGBA (2 GiB, 512 MiB in the web app), such as a 24000 ×
24000 JPEG, was placed with the warning "too large to draw" and then
drew as a crossed gray box on the canvas and was left out of raster
exports. A 16-bit PNG whose decoded samples were past the allowance
was shown the same way, with the same warning, although its RGBA fits.

The renderer now decodes such an image at the largest size, keeping its
proportions, that fits the allowance as RGBA. Each pixel of the result
is the mean of the image's pixels it covers. A PNG that isn't
interlaced is read a row at a time; other images are decoded whole when
their decoded samples fit the allowance. A 16-bit PNG that isn't
interlaced and whose RGBA fits is read a row at a time at its own size.
An image of more pixels than the allowance has bytes still draws as the
crossed box, and so does one whose decoded samples are past the
allowance, other than a PNG that isn't interlaced. The renderer
decodes an image at reduced size only while the result and the memory
reading it takes (the image decoded whole, or the PNG reader's row
buffers) fit beside the images being painted; otherwise the image is
not decoded on that render (on the canvas it draws as the crossed box)
and is tried again on the next render. The document keeps the whole
file for saving and export. A placed document's saved preview past the
allowance is decoded the same way, at the size it is drawn at or at the
reduced size when that is smaller.

Open, Place and the Links panel say "an image of W × H pixels is shown
at reduced size" for an image drawn smaller. They say nothing for an
image drawn at its own size, and they say "too large to draw" for one
that is not drawn at reduced size.

Docs: docs/mcp.md, docs/target-app-parity.md (the crossed box's line
now names an image over the allowance that can't be drawn at reduced
size).
2026-10-26 21:41:42 +07:00
dotCooCoo 7705983a72 PDF: list markers export as selectable text
Squashed from #4098 onto main.
2026-10-26 21:00:30 +07:00
dotCooCoo 7470505113 Render: cropped shadow and blur rasters are kept across pans within a margin
A Drop Shadow, Gaussian Blur, Feather or Photoshop-style filter raster
too large to keep whole is cropped to the view. It was drawn again
only while a later frame's rect, panned back, lay inside the frame it
was made in, so every pan that showed new pixels made it again. For a
Drop Shadow on a 6000 x 4000 pt image in a 2560 x 1440 view at 100% to
400%, a pan took 60 to 84 ms; with Gaussian Blur or Feather, a pan took
158 to 217 ms.

When such a raster is made again at the zoom and rotation of the one
kept for the object, it now also covers a margin around the view: up
to half the view's longer side, while the raster stays within the
8.4 million pixel budget at full size (441 pixels on each side of a
2560 x 1440 view for a 20 pt blur at 100%). Later pans that keep the
view inside the margin draw that raster. The first frame at a zoom, a
zoom step and the frames of a drag make the raster without the margin,
as before. A shadow made over an earlier cropped shadow covers only
the rect the earlier one was made for.

A kept raster is drawn again only after a pan by whole device pixels,
but the wheel, a trackpad and the scroll bars scrolled the view by
amounts that are not whole pixels. The canvas now moves the view by
whole device pixels when it scrolls or is dragged. A scroll by the wheel, a trackpad, a touch screen or a scroll
bar is rounded, and the part of a pixel left over is added to the next
scroll of the same view; it is dropped after a zoom, a turn, Fit or a
document switch. A Hand tool, Space or middle-button drag and a scroll
bar thumb drag round the distance from where the drag began.

The silhouette of a shadow or glow raster is now drawn with art culled
against the raster's own area instead of the view, as filtered content
already is. Before, a group member outside the view cast no shadow
into a raster kept whole, so a pan that showed the member drew it
without its shadow. A cropped raster kept across the margin would have
done the same. The moved silhouette that a drag compares is drawn the
same way.

Docs: docs/target-app-parity.md.
2026-10-26 20:43:54 +07:00
dotCooCoo 7a815b0acc Type: text.convertListToText moves the Type tool's caret past the markers
The Type tool's anchor and caret were moved past the inserted markers
only by the menu item in the desktop app. When text.convertListToText
came over MCP or the control channel while the Type tool was editing,
they kept their old byte offsets, and the inserted characters (a bullet
is 3 bytes) could leave an offset inside a character.

The command now ends the Type tool's typing, then moves its anchor and
caret past the characters inserted at or before them. In threaded type,
when the caret's character moved to another frame, the tool edits that
frame, which becomes the selection when it was not selected. The menu
item runs the command without adjusting the selection itself.

Docs: docs/mcp.md.
2026-10-26 19:38:12 +07:00
dotCooCoo 2f445e117f Type: Convert to Text makes a list's bullets and numbers characters of its text
The reference app's Type › Bullets and Numbering › Convert to Text turns
the bullets and numbers of the selected list paragraphs into editable
text. VectorCraft drew list markers but could not convert them.

Type › Bullets and Numbering › Convert to Text (type.convertListToText,
text.convertListToText) puts each list paragraph's marker at the start
of the paragraph as characters: the bullet, or the number and its
period as the paragraph showed it, then a tab, in the marker's font,
size and color. The paragraph leaves its list. Where the marker's tab
went to the left indent, the paragraph gets a plain left tab stop
there, so the text after the tab starts where it did. Where the
marker's tab went to a stop with a leader, or to a right, center or
decimal stop, a plain stop where the tab ended keeps the converted tab
there without the leader; right, center and decimal stops between the
marker and that place are removed. Past the left indent this applies
only to a paragraph with no tab of its own. In threaded type the
numbers are those the story counted, and a paragraph that goes on into
the next frame is converted once, where it starts. A right-to-left
paragraph with no direction set gets the Right to Left direction when a
letter or roman numeral in front of it would make it run left to right.
The change is one undo step. The Type tool's caret stays next to the
character it was next to, and in threaded type it goes with that
character to the frame it moved to, which becomes the selection when it
was not selected.

With the Every-line Composer a paragraph that holds a tab is broken
line by line, so a converted paragraph can break its lines at other
places than the list paragraph did.

Docs: docs/mcp.md, docs/target-app-parity.md.
i18n: Convert to Text in the 24 catalogs.
2026-10-26 19:38:12 +07:00
dotCooCoo bdba8c1cea Render: an image that can't be decoded shows as a crossed box and isn't decoded again
An image whose pixels can't be decoded, such as a PNG cut short by an
interrupted download, drew nothing on the canvas and the status bar
said nothing. Every render decoded it again, so each frame that drew it
paid for a decode that fails.

The renderer now notes such an image by its content and does not decode
it again. Each render still counts it in decode_failures, so an export
reports it as before. An image refused only because the images being
drawn leave it no room within twice the decoding allowance is not
noted, and the next render tries it again.

On the canvas the image draws as a light gray box crossed from corner
to corner, and the status bar says once for each image that it couldn't
be decoded. Exports still draw nothing there.

Docs: docs/target-app-parity.md.
2026-10-26 19:00:28 +07:00
Andrés Ortega 88060b199f Windows fonts: list the fonts only GDI has loaded, such as Creative Cloud's Adobe Fonts (#1258)
Squashed from #4130 onto main.
2026-10-26 17:51:42 +07:00
ashokram-si 87470ef0b7 View: all 25 saved views in the View menu; Edit Views… lists them
A document keeps up to 25 saved views, but the View menu listed only the
first 10, so views 11 to 25 could be saved and never chosen. All 25 are
listed now, by name, and choosing one changes the current window to its
zoom, centre and rotation (within the canvas's zoom range).

Edit Views… used to open the generic parameter dialog on the first view,
where the name of the view to change had to be typed. It now opens a list
of the document's views, like Edit Selection…: pick one to rename it or
delete it; OK applies every change as one undo step and Cancel leaves the
views as they were. view.saved.edit takes {edits: [{name, newName?,
delete?}…]} for that, the one-view form still works, and the command is
off while the document has no saved view.

A file's views are untrusted: on open at most 25 are kept, names are
trimmed, capped at 255 characters and unique, and views without a finite
centre and a positive zoom are dropped. New View… starts from the first
free "View N" and caps names the same way.

Behaviour from the public help page "Use multiple windows and views":
https://helpx.adobe.com/illustrator/using/viewing-artwork.html

docs/gaps.md (G7) and docs/target-app-parity.md take New View/Edit Views
off the missing list; docs/mcp.md documents the view.saved commands; the
catalogs get the Saved View 11-25 rows in the pattern of 1-10.
2026-10-26 17:40:42 +07:00
Taha-Mahmoodi 54d9fbc162 Type: a double-click on a threaded port breaks the thread there 2026-10-26 16:00:39 +07:00
dotCooCoo 45440b5444 Toolbar: double-clicking the Type on a Path tool opens Type on a Path Options
A double-click on the Type on a Path or Vertical Type on a Path tool
reported that the tool has no options. It now opens Type on a Path
Options for the selected type on a path, the same dialog as Type ›
Type on a Path › Type on a Path Options…. With nothing selected, or
with no type on a path selected, nothing opens and `tool.options`
returns the menu command's error, as the Rotate tool's double-click
does without a selection.

Docs: docs/control-protocol.md, the `tool.options` params doc.
2026-10-26 15:44:50 +07:00
Taha-Mahmoodi cfd085f34a Fix #3426: the Pathfinder panel's Pathfinder Options… sets Precision, Remove Redundant Points and Divide and Outline Will Remove Unpainted Artwork
Squashed from #4141 onto main.
2026-10-26 15:28:51 +07:00
Taha-Mahmoodi 0a241a7fdd Brushes: Window › Brush Libraries opens brush libraries whose brushes click or drag into the document's Brushes
Squashed from #4020 onto main.
2026-10-26 15:17:30 +07:00
Taha-Mahmoodi a34055beec Artboards: the Artboards panel's Delete Empty Artboards deletes every artboard with no artwork in one undo step
Squashed from #4142 onto main.
2026-10-26 14:10:36 +07:00
Taha-Mahmoodi 5b91057dca Repeat: a selected Grid Repeat has on-canvas handles for its rows, columns and spacing
With the Selection tool, a single selected grid repeat shows a handle below the grid (rows), one right of it (columns), one past its bottom-right corner (both) and sliders between its first two columns and rows (horizontal and vertical spacing), as the public Set grid repeat options page describes. Each drag previews object.repeat.options and commits as one Repeat Options undo step; Escape cancels, and a drag that ends where it began leaves no step. Bounding-box handles keep priority where they overlap, and the controls hide with the edges or the bounding box. Before, a drag there scaled the source or started a marquee.

Part of #2691.
2026-10-26 13:44:06 +07:00
dotCooCoo 64f29d5848 i18n: the message scan skips comments and reads EngineError::Disabled's reason
The message scan read the first argument of every error variant, so
for EngineError::Disabled it read the command id and missed the reason
the status bar shows. It also read error calls inside comments, such as
a commented-out bad(cmd, "..."), as messages that need catalog rows.
The scan now reads Disabled's second argument and removes // and
/* ... */ comments (nested ones included) before it looks for calls. A
// or /* inside a string or character literal does not start a comment.
2026-10-26 12:20:24 +07:00
skylar taylor-barrick 581c79dcb1 M14: distinguish qualification from publication (#4173) 2026-10-25 23:47:58 -04:00
Taha-Mahmoodi 502b204d74 Transform panel: the menu's Transform Object Only, Pattern Only, Both and Use Registration Point for Symbol act
Squashed from #4146 onto main.
2026-10-26 10:04:25 +07:00
dotCooCoo df716c3118 Image Trace: Document Library traces with a color group from the Swatches panel
Squashed from #3975 onto main.
2026-10-26 09:25:42 +07:00
vangel76 b54a5e6765 Artboards: canvas menu to lock and unlock artboards (#2531)
Squashed from #2532 onto main.
2026-10-26 09:25:35 +07:00
Angelo Bernardini ea36291f30 G12: General › Corner Radius is the Rounded Rectangle tool's radius (#394)
Squashed from #4074 onto main.
2026-10-26 09:09:47 +07:00
Drew Dibble f6a20c9f56 Web: shortcuts the browser also has run only in the app
Squashed from #4106 onto main.
2026-10-26 09:09:47 +07:00
dotCooCoo ad75ea5d06 Type: Highlight Substituted Fonts moves to Preferences › Type and applies to every document
Highlight Substituted Fonts is now a preference in Preferences › Type,
next to Highlight Alternate Glyphs, on by default and saved with the
other preferences (`prefs.set highlightSubstitutedFonts`). The canvas
reads it through `RenderOptions::highlight_substituted_fonts` for every
open document; a document's own setup no longer decides it. File ›
Document Setup no longer shows the checkbox and keeps Highlight
Substituted Glyphs.

A file saved with Document Setup's Highlight Substituted Fonts off now
shows the pink highlight when it opens, while the preference is on.
The file still opens with the stored value, and saving it again keeps
that value.

`document.setup` no longer reports `highlightSubstitutedFonts`. It
ignores the key and adds a `warnings` entry that names `prefs.set` to
its reply, so an older reply passed back still applies its other keys.

The raster cache of type under a raster effect now also redraws when
Highlight Alternate Glyphs changes.
2026-10-26 09:09:47 +07:00
dotCooCoo be5bd7dbf0 Pen tablets: a pen's eraser end selects the Eraser in the web build
Pointer Events report a press of a pen's eraser end as the eraser button
(button 5). The browser build ignored that press. It now presses like the
tip, and the web runner writes the pen's samples into a stylus feed with
the end they came from, so the canvas selects the Eraser tool before the
press and the press erases. The next press of the tip selects the tool
used before, as on Linux and macOS. A hovering pen keeps the end it last
pressed with, since Pointer Events report no end while a pen hovers. A
mouse or finger takes the pen out of the feed; a palm rejected while the
pen presses does not.

Docs: docs/hardware-parity.md, docs/gaps.md (G14),
docs/target-app-parity.md.
2026-10-26 09:09:47 +07:00
dotCooCoo dbc446329f Pen tablets: a pen's eraser end selects the Eraser on Windows
With Windows Ink, winit reports a pen as a touch and drops its pen
flags, so on Windows the eraser end of a pen draws with the current
tool. The message hook that reads the barrel button now also reads
each pen pointer message's POINTER_PEN_INFO and writes the pen's
pressure and end into the canvas's pen feed: PEN_FLAG_INVERTED or
PEN_FLAG_ERASER is the eraser end. The canvas then selects the Eraser
tool and the tip brings back the tool used before, as on Linux and
macOS. A pen leaving detection range or the window, another pointer,
or a mouse message that Windows did not make from a pen or a touch
clears the feed, so a mouse presses fully.
2026-10-26 09:09:47 +07:00
ncic 09b3b25004 Fix #3328: Pencil Fidelity down to 0.01 pt, with a logarithmic slider and key steps that fit the range
Squashed from #4196 onto main.
2026-10-26 09:09:47 +07:00
dotCooCoo 05e09039f5 Pencil: a stroke from an end of one selected path to an end of another connects them
Squashed from #4095 onto main.
2026-10-26 09:09:47 +07:00
dotCooCoo c9fa005a54 Pencil: the cursor shows a star before a press that draws a new path
Draw freeform paths with the Pencil tool says a small star appears on
the Pencil when it is ready to draw a freeform path, and Reshape paths
with the Pencil tool says the star disappears over a selected path the
drag reshapes. The Pencil cursor had no star.

- New Cursor::PencilNew: the Pencil's crosshair with a small star where
  the closing cursor has its ring. It becomes the crosshair with Use
  Precise Cursors and as a system cursor.
- The Pencil shows it before a press when neither find_extend nor
  find_redraw (the lookups the press uses, within Edit Selected Paths)
  finds a selected path under the pointer. Over such a path and while a
  stroke is drawn, the cursor has no star.

Docs: docs/target-app-parity.md.
2026-10-26 09:09:46 +07:00
Taha-Mahmoodi 0dc9d9abb4 Fix #1732: Transform Each opens on the values it was last confirmed with and has a Copy button
Squashed from #4175 onto main.
2026-10-26 09:09:46 +07:00
dotCooCoo 3d8c40f500 Eraser: Angle and Roundness in Eraser Tool Options shape the tip
Squashed from #4180 onto main.
2026-10-26 09:09:46 +07:00
dotCooCoo 7117c82cab Brushes: Art Brush Options' Width can follow the pen pressure
Art Brush Options' Width had no pen tablet option, so an art brush
drew at one width whatever the pressure. The Width row now has a
Fixed or Pressure menu and a second width. With Pressure the art is
Width wide at the lightest pen pressure and the second width at the
heaviest, following the pressure the Paintbrush recorded along the
stroke, and half way between the two on a stroke drawn without
pressure. brush.options takes width_mode and width_max; a brush
without them is saved as before.
2026-10-26 09:09:46 +07:00
dotCooCoo 60cb4c45ab Pencil: Live curve fitting in Pencil Tool Options
Pencil Tool Options has a Live curve fitting checkbox, on by default.
With it off, the path shown while a Pencil stroke is drawn joins the
stroke's points with straight segments, and the release fits the curve
at the Fidelity, as it does with the option on. Choosing another tool
while a stroke is drawn also keeps the stroke fitted. The option is
saved with the tool's other options.

path.freehand takes fit (true by default): with fit false, a new path,
a continued path and a redrawn section join the points with straight
segments.
2026-10-26 09:09:46 +07:00