Commit Graph
4431 Commits
Author SHA1 Message Date
Drew DibbleandClaude Opus 5.5 061e905724 G1: selection tools take a clipping path on its outline
A clip group answered a press only strictly inside its clipping path,
and there the art above the path came first in the hit test, so where
the clipped art extends past the mask's edge the clipping path's
segments could not be reached on the canvas.

With the new HitOptions::clip_outlines, which the Selection, Direct
Selection and Group Selection tools set
(ToolContext::selection_hit_options), a press within the selection
tolerance of a clipping path's outline, inside or just outside it,
hits the clipping path (HitKind::Outline) before the art it clips:
Direct Selection takes its segments and the Selection tool the clip
group. It applies to a clip group's clipping path and to a layer's
clipping mask, and only to one a click may hit (may_hit: visible,
editable, within reach). Inside the mask the art is hit as before.

The other tools, which act on what is drawn (the eyedropper, type,
gradients...), leave the option off, and hit_test_visible never
applies it, so they still take the art at the mask's edge rather
than the mask's empty appearance. Select Behind's one-pass walk takes
a layer mask's outline first too, so it lists what the repeated hit
test finds. Object Highlighting and a right-click's selection in the
canvas (selection_hit_at) take what those tools take; dropping a
swatch on art still hits what is drawn.

The reference app reshapes a clipping path with Direct Selection:
https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-clipping-masks.html

Tests in doc: a_clipping_path_is_hit_on_its_outline_before_its_art,
only_the_selection_tools_hit_a_clipping_path_on_its_outline and
select_behind_takes_a_layer_masks_outline_first; the random Select
Behind test now clips layers and sublayers and sets clip_outlines,
and fails without the Select Behind change. In tools,
a_press_on_a_clipping_path_takes_its_segment_over_the_art fails
without the change; select::a_press_on_a_masks_edge_selects_its_clip_group
fails when the Selection tool uses the plain options; the eyedropper
copies the art on the edge. Recorded in docs/ui-parity.md,
docs/gaps.md and docs/target-app-parity.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 15:42:37 +07:00
Ashsajal 923c49eda0 G1: a touch takeover drops a move waiting over a panel (#2826)
A second finger, a long touch or a cancelled contact replaced Drag::Art
with Drag::Gesture without ending the live Move interaction, which then
outlived the pointer: later edits began on top of an open interaction.
drop_press now cancels the move for Drag::Art too and clears the panel
payload, as it already did for Drag::Tool.

Test: a_second_finger_drops_a_move_waiting_over_a_panel.
2026-10-21 15:42:37 +07:00
Ashsajal fbdfe5fc82 G1: a move over a panel waits and resumes on the canvas (#2826)
The Selection tool ended its Move interaction as soon as the pointer left
the canvas, so the art jumped back to where it started while the pointer
was over a toolbar or panel. As #2826 describes, the art should wait where
it last moved and the move should continue when the pointer returns.

Keep the interaction live while the pointer is off the canvas: Drag::Art
holds the art where it last moved, and coming back clears the panel-drop
payload and sends a Drag so the move goes on with the pointer. A release
over a panel ends the move before the panels draw (release_over_panel,
called from ui_frame), so a drop that takes art still gets it as it was
and no interaction is left open.

Tests: art_moved_off_the_canvas_waits_and_drops_on_the_panel,
art_moved_off_the_canvas_resumes_when_the_pointer_returns and
a_move_released_over_a_panel_ends_before_the_panels_draw cover the canvas
states; a_move_dragged_onto_a_panel_reads_the_menus checks the full app
waits over a panel and ends on release. cargo xtask ci: all 10 steps pass.
2026-10-21 15:42:37 +07:00
dotCooCoo a304cdb589 Tabs: a refused tab stop move leaves the ruler as it was
When text.tabs.set refused a new X (past 100,000 pt), the panel kept
the stops it had changed and sorted, so later in the same frame the
ruler painted the refused position and highlighted another stop.
apply_moved now moves and sorts a copy of the stops and keeps it only
when the stops are set.
2026-10-21 15:42:37 +07:00
dotCooCoo a810044e5a Tabs: a stop moved past another stays selected
A tab stop moved past another stop in the Tabs panel, by a drag on the
ruler or by a value typed in X, lost the selection. The panel sorts the
stops by position after the move but kept the selected index, so the
ruler highlighted another stop and the X, Leader and Align On fields
showed and changed that stop. With stops at 30 and 50 pt and the 50 pt
stop dragged to 10 pt, the panel selected the stop at 30 pt.

After a drag or an X value re-sorts the stops, the panel now selects
the moved stop at its new place, found by its position as a click that
adds a stop does. When text.tabs.set refuses the new position (an X
past 100,000 pt), the stops and the selection stay as they were.

Docs: docs/target-app-parity.md.
2026-10-21 15:42:37 +07:00
dotCooCoo 5660a15b75 Variables: the nothing-applied message clears when a later data set applies
After "nothing in data set A was applied: no object is bound to a
variable it has a value for" showed in the status bar, choosing a data
set that did apply, or stepping to one, left that message in place.
`run_now` set the status from a command's `warning` and cleared it only
for `file.new`, and the Variables panel test cleared the status by hand
before its last check.

`dataset.select`, `dataset.next` and `dataset.prev` now clear the status
bar when they succeed, and the warning shows when no object is bound to
a variable the new data set has a value for. The test no longer clears
the status by hand and checks all three commands.
2026-10-21 15:42:37 +07:00
dotCooCoo 14ec7f472f Brush Options: Pattern Scale and Art Width have sliders
Pattern Brush Options' Scale and Art Brush Options' Width were number
fields only. The reference app's Brushes page documents a Scale slider
for pattern brushes and a Width slider for art brushes.

Each row now has a slider beside its field, as the Bristle Brush
Options sliders do. The slider covers the value's whole range, 1% to
10000% for Scale and 1% to 1000% for Width, in whole percents. Screen
readers name the slider as its row and read the field's number and
text.

Docs: docs/target-app-parity.md.
2026-10-21 15:42:37 +07:00
dotCooCoo ba8c1e042c Pencil: Edit selected paths within 0 turns editing off
docs/control-protocol.md and the tool.options params text say that 0
turns the Pencil and Paintbrush options Edit selected paths within and
Close paths when ends are within off. A stroke that
started exactly on an end of a selected open path still continued that
path at 0, and a stroke that ended exactly where it started was still
closed at 0, because both checks compared the distance with <= 0.

At 0, a stroke that starts on an end of a selected open path now draws
a new path, as one that starts on the path away from its ends already
did, and a stroke that ends where it started stays open.

Docs: docs/target-app-parity.md.
2026-10-21 15:42:37 +07:00
Alexander Richardson 53569a883a [Bug Report] [VectorCraft] Envelope Distort 2026-10-21 15:42:37 +07:00
FractalEncrypt b9fa08f9e6 UI.P1: Persist Polygon and Star counts after exact-size placement 2026-10-21 15:42:37 +07:00
FractalEncrypt a5a9371979 TRACE.B1: Consolidate M12 roadmap rows while retaining distinct trace details 2026-10-21 15:42:37 +07:00
FractalEncrypt a994f1e416 TRACE.B1: Place the trace bounds milestone note under Image Trace 2026-10-21 15:42:36 +07:00
FractalEncrypt 240b0ca881 TRACE.B1: Exclude retained trace bitmaps from result bounds and highlights 2026-10-21 15:42:36 +07:00
Harley Hawk de3b94804d Contributors: credit odysseexp by their GitHub profile name 2026-10-21 15:42:36 +07:00
Harley Hawk 656562d114 SVG import tests: remove machine-specific fixture check 2026-10-21 15:42:36 +07:00
Harley Hawk cc0e7c4c67 SVG import: support feBlend filter primitive and transparent backdrops 2026-10-21 15:42:36 +07:00
dotCooCoo 787592ff0e Brushes: Hue Shift without a key uses the most-used art color
brush.new and brush.options refused a Hue Shift colorization given
without a key color ("invalid brush: missing field `key`"), and only
Brush Options picked a default. The reference app's Brushes page says
that by default the key color is the most prominent color in the art.

A Hue Shift colorization without a key now takes the solid color that
the art's fills and strokes use most, the first one found on a tie, or
black when the art has no solid color, as Brush Options already does.
Brush definitions read from a document get the same default.

brush.options replaced each field its params named, so an edit that
gave only the colorization method or only an art brush's start guide
was refused because the key or the end guide was missing. It now
merges colorization and an art brush's scale field by field.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-21 15:42:36 +07:00
dotCooCoo 1e4ebba1cc Document Info: a Brushes category lists the document's brushes
Document Info had no Brushes category, and document.info refused
category "brushes". The reference app's Document Info panel menu lists
Brushes after Graphic Styles.

Document Info now has a Brushes category after Graphic Styles. For the
whole document it lists the Brushes panel's brushes. With Selection
Only it lists the brushes that the selected objects' strokes use, each
once. document.info gives the names as brushNames, the panel's flyout
offers the category, and the text report that Document Info › Save…
and File › Package write has a BRUSHES section.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-21 15:42:36 +07:00
dotCooCoo d80024eff6 View: Show Live Paint Gaps highlights the gaps of the selected Live Paint groups
The reference app's View menu has Show Live Paint Gaps, which
highlights the gaps in the selected Live Paint group based on the gap
options set for that group. VectorCraft had no such item.

View > Show Live Paint Gaps (view.livePaintGaps) turns the highlight
on or off, and the item reads Hide Live Paint Gaps while it is on.
While it is on and edges show, the canvas draws the gaps of each
selected Live Paint group up to its Paint Stops At size, the gaps that
livePaint.info lists. It draws them as the Gap Options preview does,
as lines in the group's Gap Preview Color. The setting works as View >
Hide Text Threads does: one setting covers every document, it is saved
with the interface state, and Hide Edges hides the gaps. It is off by
default.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-21 15:42:36 +07:00
dotCooCoo 7fbd215e97 Brushes: Select All Unused selects the brushes the document does not use
The reference app's Brushes panel menu has Select All Unused, which
selects the brushes that are not used in the document. VectorCraft's
Brushes panel menu had no such item, and no command listed the unused
brushes.

brush.unused (Select All Unused) is a query that lists, in library
order, the brushes no stroke paints with. Strokes in the art and its
opacity masks, in symbols, graph designs, pattern tiles and graphic
styles count, and so do strokes in the art of a brush that is used.
The art of a brush that is not used does not count, so a brush whose
own art is the only art that paints with it is listed, and so is a
brush used only in the art of an unused brush. The Brushes panel
menu's Select All Unused selects these brushes, and Delete Brush and
Duplicate Brush then act on them.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-21 15:42:36 +07:00
dotCooCoo 4bcd14c9c2 Select: Bristle Brush Strokes selects objects by their brush's type
Select > Object > Bristle Brush Strokes selected the objects whose
brush name contains "bristle". It missed objects with a bristle brush
of any other name, and it selected objects whose brush is of another
type with "Bristle" in its name.

select.object.bristleBrushStrokes now looks up the brush of each
object's topmost stroke in the document's brush library and selects the
object when the brush is a bristle brush, whatever its name.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-21 15:42:36 +07:00
dotCooCoo 6bce3578ca Artboards: the panel menu's Convert to Artboards converts the selected objects
The Artboards panel menu showed Convert to Artboards disabled whatever
was selected, and choosing it did nothing, although Object › Artboards
› Convert to Artboards runs artboard.convertToArtboards on the
selection.

The panel menu's item now uses the same check as Object › Artboards ›
Convert to Artboards, so it is enabled when something is selected, and
it runs the same command. Each selected object that has an area becomes
a new artboard of its geometric bounds and is removed, in one undo step.

Docs: docs/target-app-parity.md.
2026-10-21 15:42:36 +07:00
dotCooCoo 156bd632e6 File: Cmd+Alt+N makes a new document without the New Document dialog
The reference app makes a document without opening the New Document
dialog with Ctrl+Alt+N on Windows and Option+Command+N on macOS.
VectorCraft had no default key for it.

file.newWithoutDialog (New Without Dialog, default Cmd+Alt+N) makes a
document with the settings New Document opens on: the first Recent
entry, or the Print category's Letter while Recent is empty, named
after the next untitled document. It fills the fields with the
dialog's own open, runs file.new with the params Create sends, and
does not show the dialog. A dialog that is already open stays open.
Keyboard Shortcuts lists it under File.

Docs: docs/mcp.md, docs/ui-parity.md, docs/target-app-parity.md.
2026-10-21 15:42:36 +07:00
dotCooCoo f43c6ff47d Appearance: Cmd+/ adds a new fill and Cmd+Alt+/ adds a new stroke
The reference app adds a new fill with Ctrl+/ on Windows and Command+/
on macOS, and it adds a new stroke with Ctrl+Alt+/ on Windows and
Option+Command+/ on macOS. VectorCraft had Add New Fill and Add New
Stroke on the Appearance panel, with no default key for either.

appearance.addFill now has the default Cmd+/ and appearance.addStroke
has Cmd+Alt+/, which are Ctrl+/ and Ctrl+Alt+/ on Windows and Linux.
Like the panel's buttons and menu items, each key runs its command with
no params and adds the item to every selected object. Keyboard Shortcuts
lists both under Window › Appearance with their keys, and press_key
runs them for agents.

Docs: docs/mcp.md, docs/ui-parity.md, docs/target-app-parity.md.
2026-10-21 15:42:36 +07:00
nrq6993-a11yandCursor 5ebef8282a G1: Anchor Point Shift-drag shows move distance in measurement labels
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 15:42:36 +07:00
nrq6993-a11yandCursor 87686e81f2 G1: Reshape tool shows offset readouts while dragging paths
Honor Show Measurement Labels with dX/dY labels during reshape previews.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 15:42:36 +07:00
nrq6993-a11yandCursor 32834a9135 G1: Place drag test disables measurement labels for marquee-only overlay check.
testutil: :cx defaults measurement_labels on, so the aspect-ratio drag assertion must not expect a lone Marquee overlay.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 15:42:36 +07:00
nrq6993-a11yandCursor 2927ff6e85 G1: Place tool shows placement size in measurement labels while dragging.
Honor Show Measurement Labels on the file Place cursor so sizing drags get W/H readouts like other draw tools.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 15:42:36 +07:00
nrq6993-a11yandCursor e76a9c1a45 G1: Area type drag shows W/H in measurement labels
Honor Smart Guides › Measurement Labels while drawing an area-type frame with the Type tool.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-21 15:42:35 +07:00
Taha-Mahmoodi 4e0cbfeede Fix #2820: images drawn small redraw from their kept mips while being moved
With image mips on (the canvas always sets them), drawing an image
fetched its full decoded pixels first, decoding them again when the
shared image store no longer held them, and only then looked up the mip
it draws. The full pixels are only peeked, so they are the least
recently used entries and the first the store drops over its budget
(1 GiB on macOS and Windows). Once a document's full-size images exceed
it (20 photos of 6000 x 4000 px are 1.9 GB as RGBA), every frame decoded
every image again, though only their small mips were drawn: moving one
image redrew seconds later.

The store now remembers the size each blob's pixels decode to, so the
mip level is found without the pixels, and a mip the store holds is
drawn (and marked used) without fetching or decoding them. Without the
mip, at full size, or with mips off (exports) drawing is unchanged.

Measured on an Apple M4 (load average 15-24 from other work), 20 noise
PNGs of 6000 x 4000 px fitted in a 2880 x 1800 view, one moved 5 pt per
frame: before, 20 decodes and 1.1-3.0 s every frame; after, the first
frame decodes 20 and the later ones none, in 3-5 ms.
2026-10-21 15:42:35 +07:00
NetterSilverandClaude Haiku 5.5 4444450a44 Keep stroke weight in document space on transformed type
Fixes #3241

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 15:42:35 +07:00
MarekSpodBiedry 92a9b91255 Keep the swatch link on gradient stops resampled for a blend (#3234) 2026-10-21 15:42:35 +07:00
MarekSpodBiedry ecd01ad6f1 Keep the swatch link when blending two tints of one swatch (#3234) 2026-10-21 15:42:35 +07:00
Taha-Mahmoodi fc644b9a9e Fix #3198: the status bar's fps is the rate the window draws at, not an average of idle bursts
The readout was a moving average of 1/dt over any two consecutive frames.
An idle window wakes now and then (a timer, a control request, a repaint
after input) and each wake-up draws two frames about 4 ms apart, the second
one not waiting for vsync because the swapchain has a free image. Every
wake-up pushed the average up by 0.1 x 240 and the idle gap pulled it down
by only 0.1 x 2, so at rest it climbed past the display's refresh rate
(105 fps on a 30 Hz monitor that was drawing 4 frames a second). As soon as
the pointer moved and frames became vsync-paced, it fell back to the real
rate, which looked like a slowdown.

- frame_rate: frames counted over the last second of continuous drawing.
  A gap more than 100 ms longer than the previous frame's own work means
  the window was idle and starts a new count. The readout changes only
  after half a second of drawing, so a wake-up's frames keep the last
  rate. Slow frames still read slow, because their gap is their own work.
- chrome: "– fps" until the window has drawn continuously, instead of 0.

Measured on an Apple M4 driving a 30 Hz 4K display: drawing continuously
reads 30; at rest with a wake-up every 0.5 s it stays 30 (before: 45 -> 105).
2026-10-21 15:42:35 +07:00
Taha-Mahmoodi 4fdb3e1f9b Fix #3188: arranging art inside a clipping mask keeps the clipping path at the bottom
In a clip group or a clipping layer the bottom child is the clipping path.
Object › Arrange moved the selected children with no regard for that slot:
Send to Back (and Send Backward from just above it) put the art under the
clipping path, so the art became the clipping path, lost its role as art,
and the group drew nothing. Bring Forward / to Front on the clipping path
itself would likewise have made art the clip. Layers-panel moves already
keep the clipping path at the bottom (layer.rs, `index.max(1)`); arrange
didn't.

- object.rs `arrange`: in a parent that clips, the clipping path is left
  where it is and the art is arranged within the slots above it (Back goes
  to just above the clipping path, Backward stops there).
- tests: Back, Backward, several objects, and Forward/Front on the
  clipping path in a clip group (with a render check); Send to Back in a
  clipping layer.
2026-10-21 15:42:35 +07:00
Mirko Kerezovic d2625ec108 Fix remaining Hide Guides rendering cases (#3219)
Include guide visibility in effect raster cache inputs, skip hidden guide paths in outline members, and clear selected guide paths when hiding guides. Add regression coverage for cached effects and selection overlays.\n\nThe cached-effect regression test is adapted from the overlapping #3264 implementation.\n\nCo-authored-by: Mahtab <smmalam9@gmail.com>
2026-10-21 15:42:35 +07:00
Mirko Kerezovic 2305ebb931 Fix Show and Hide Guides (#3219) 2026-10-21 15:42:35 +07:00
jordanlemcc-ship-itandClaude Opus 4.8 0782248cff ui-egui: surface erase errors in the batched type_text path
Address CodeRabbit review on #3307: the new `Seq::Erase` arm discarded
the `tool_key` Result, dropping an erase error silently, whereas the
old `route_type_input`/`text_key` path reported it via `app.status`.
Mirror that behaviour so a failed Backspace/Delete still surfaces in
the status bar. Behaviour otherwise unchanged; fmt/clippy clean and the
three IME tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-21 15:42:35 +07:00
jordanlemcc-ship-itandClaude Opus 4.8 17952be94a Fix lost diacritics with keyboard-hook IMEs (UniKey/OpenKey)
Hook IMEs recompose by injecting Backspace key events and VK_PACKET
characters, which egui surfaces as interleaved Event::Text and
Event::Key(Backspace) in one frame. type_text handled text/IME in one
pass and left Backspace to a later pass, so within a frame all text was
inserted before any delete and the diacritics were eaten (tiếng -> tieng).

Take Text, IME and plain Backspace/Delete in a single event-ordered pass,
removing handled keys so route_type_input does not repeat them. Modified
keys and composing frames are unchanged. Adds two canvas IME tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-21 15:42:35 +07:00
dotCooCoo 6e4fb6e4e7 Links: a link on a drive mapped to another share is not followed
craft-docpath follows a stored path only on this computer or on the
share the document is on, but its check is lexical and cannot see
mapped drives. fileio::document_path therefore followed a link stored
as Y:\img\logo.png, where Y: is mapped to \\server\other, from any
document: opening the document, links.check, Link Info and the
linked-file watch read or stamped the file on that server.

On Windows, document_path now reads the final path of the root folder
of a link's drive (std::fs::canonicalize). Windows opens that root
folder on the drive's server to answer, so the check still contacts
the server once per lookup; no file on it is read, stamped or watched.
When the final path names a share, the link is checked against the
document's path on its share, and a link on another share is refused
as on another computer, as a link stored with that share's own path
is. Links on this computer's drives and on the document's share are
followed as before, and a link on the document's own drive is not
looked up. A document opened as \\?\Z:\... is looked up on its share
as Z:\... is. Other systems are unchanged.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-21 15:42:35 +07:00
dotCooCoo 82a78e3d58 Save for Web: browser previews go only to a folder the current user owns
Preview in Browser wrote each preview into `VectorCraft Save for Web`,
and before each preview it removed every file and folder there that
was an hour old or older. Where the temporary folder is the shared
/tmp, another user could make that folder first, writable by everyone,
and the app wrote its previews inside it, in folders that user could
read and replace. Where the system follows other users' links in /tmp,
the folder could also be a link, and the app removed the old entries
of the folder it named.

A preview now uses that folder only when it is a folder and not a
link, and on Linux, macOS and FreeBSD only when the current user owns
it and no other user can write to it. On those systems the folder and
each preview's folder are made with mode 0700. The cleanup removes
only preview folders, on those systems only those the current user
owns. When the shared folder can't be used, the preview goes to a new
folder in the temporary folder. A later preview removes such folders
once they are an hour old.

Docs: docs/target-app-parity.md.
2026-10-21 15:42:35 +07:00
BFlat 0f39505d12 Merge pull request #3251 from MarekSpodBiedry/issue-3229
Warn on export when a font style is drawn in another style (#3229)
2026-10-21 15:41:57 +07:00
BFlat 31734a0dcb Merge pull request #3285 from dotCooCoo/fix/legacy-ai-radial-gradient-stop-order
Legacy .ai export: a radial gradient's color stops are written from ramp point 0 to 100
2026-10-21 15:41:38 +07:00
BFlat c5f6443e0c Merge pull request #3203 from rahul05ranjan/fix/offset-path-preview-memory
Offset Path's Preview checkbox keeps its last state across opens (#3195)
2026-10-21 15:41:23 +07:00
BFlat 0d387fb16c Merge pull request #3304 from HEOJUNFO/fix/pattern-guides-in-export
Leave the pattern tile edge and swatch bounds out of exports
2026-10-21 15:41:06 +07:00
BFlat 153bcb2ec7 Merge pull request #3284 from MMexicann/fix/css-swatch-stop-precision
M3.57: preserve fractional CSS gradient stop positions
2026-10-21 15:40:50 +07:00
BFlat d5682167ec Merge pull request #3331 from dotCooCoo/fix/user-library-file-panic-is-skipped
Libraries: a User Defined library file whose reading panics is skipped
2026-10-21 15:40:35 +07:00
BFlat 975db53e30 Merge pull request #3286 from lehni/fix/automation-args-timeout
Root-argument tests: give the app 30 seconds to exit, not 5
2026-10-21 15:40:18 +07:00
BFlat a06950b30c Merge pull request #3323 from Silvertree2010/fix/h1183-gpl-palette-with-a-blank-name-li
Keep fallback library name for blank GPL Name line
2026-10-21 15:40:02 +07:00
BFlat a437b9ae63 Merge pull request #3230 from Silvertree2010/fix/h857-measure-tool-shows-angle-0-0-for
Normalise negative zero in Measure tool angle
2026-10-21 15:39:44 +07:00