Commit Graph
8382 Commits
Author SHA1 Message Date
dotCooCoo ee9cce43dc Symbols: an instance fill with no opacity keeps the art's swatch links
A symbol instance whose fill has an opacity of 0 no longer counts as
stained. Expand Appearance and the other places that build an
instance's art used to rebuild every solid paint as an unlinked color
even though the tint changed nothing, so the art lost its global and
spot swatch links.
2026-11-05 07:44:08 +07:00
BFlat 373e013670 Merge pull request #5712 from dotCooCoo/fix/glyph-cluster-order
Glyphs: a character's glyph is the first of its glyphs in its cluster
2026-11-05 07:43:53 +07:00
BFlat f9bb83c312 Merge pull request #5711 from dotCooCoo/fix/layers-pending-thumb-doc
Docs: describe what a Layers row shows while its first thumbnail renders
2026-11-05 07:43:37 +07:00
dotCooCoo e33be0eea9 Glyphs: a character's glyph is the first of its glyphs in its cluster
Ka, the halant, Ka again and the vowel sign I are shaped as one cluster:
the vowel sign, the first Ka's half form, then the second Ka's default
glyph. The glyph lookup looked for the character's default glyph across
the whole cluster before its alternates, so for the first Ka it found
the second Ka's default glyph instead of the half form. It again goes
through the cluster in order, and takes the first glyph that is the
character's default glyph or one of its alternates and is not the
default glyph of another character of the grapheme, so a vowel sign's
glyph that the font also lists as an alternate of the consonant is not
reported for the consonant.
2026-11-05 07:30:43 +07:00
dotCooCoo f619fcf64b Docs: describe what a Layers row shows while its first thumbnail renders
A row sent to the thumbnail thread because its opacity masks pass the
2,000-anchor limit still paints the boxes of its own paths until the
thumbnail is ready. The progress line said such a row shows no picture.

Follows #5705.
2026-11-05 07:30:40 +07:00
BFlat eb71a02723 Merge pull request #5716 from storytold/fix/control-graceful-close
Control server: close connections gracefully, so a reply is not lost to a reset
2026-11-05 07:30:23 +07:00
BFlat 49ec86b104 Merge pull request #5709 from dotCooCoo/fix/type-paste-refused-copy
Type: Paste keeps a copy the system clipboard refused
2026-11-05 07:30:07 +07:00
bflatastic 7b608b2dab Control server: close connections gracefully, so a reply is not lost to a reset
A connection ended (a junk line, an idle or stalled peer, the app going away) by dropping its socket. On Windows, a reset can follow and discard replies the peer has not read yet, which made tcp_connection_is_closed_after_junk fail at random in CI with ConnectionReset. serve_socket now shuts the write side down and reads what the peer still sends until it closes, for at most a second and 1 MiB, before dropping the socket. The test goes through serve_socket, the path the app uses.
2026-11-05 07:13:06 +07:00
dotCooCoo 59a3cad1dc Type: Paste keeps a copy the system clipboard refused
When a Copy in the Type tool cannot be written to the system clipboard
(another app holds it open, or a format fails after the clipboard was
emptied), Paste into type or a text field now inserts that copy, with
its style in type, instead of the text the clipboard kept. This covers
Edit > Paste, the paste key, and a paste that arrives while the copy is
still being written. When another app copies something, before or after
the failed write, Paste takes that.

The new SystemClipboard::change_count reports the clipboard's change
count (Windows' sequence number). Where a platform has none, a change
of the clipboard's text marks a new copy.
2026-11-05 07:13:03 +07:00
BFlat 8f1be2376f Merge pull request #5708 from dotCooCoo/fix/cheap-enabled-checks
Menus: Delete Empty Artboards and Save Gradient Preset decide their enabled state with less measuring
2026-11-05 07:11:04 +07:00
BFlat aaa59d4b98 Merge pull request #5707 from dotCooCoo/fix/glyph-alternates-overflow-probe
Glyphs: the Glyphs query reports the glyph of an overflowing character
2026-11-05 07:10:53 +07:00
dotCooCoo ea75150131 Menus: Delete Empty Artboards and Save Gradient Preset decide their enabled state with less measuring
The enabled check of artboard.deleteEmpty built the full list of empty
artboards, measuring the visual bounds of the art against every
artboard, each time the Artboards panel menu was drawn. It now returns
false at once in a document with one artboard (that artboard is never
deleted). With two or more, it asks the new
Document::any_artboard_without_art whether any unlocked artboard has no
art, which is the same answer as a non-empty list.

Document::any_artboard_without_art builds the symbol boxes once for all
the artboards and first tests each path and compound path by a quick
box: its control points' box grown by the farthest its strokes reach
(Appearance::outset), with a margin for rounding. An artboard that no
quick box meets is reported empty without measuring along any path.
Only when every artboard may hold art is each one measured.
Document::artboard_has_art also skips measuring an object whose quick
box misses the artboard; its answers are unchanged.

The check of gradient.presets.save found the proxy paints through
Node::proxy_paint, which measures the object's geometric bounds for a
gradient box the check does not use. Node::proxy_paint_only picks the
same paint without measuring, and the engine's proxy_paint uses it, so
the Fill and Stroke proxies and the mixed-paint test no longer measure
each object either.

With an open stroked path of 200,000 anchors selected, the two checks
take under 0.01 ms instead of 21.9 ms and 17.2 ms in a release build, and
Delete Empty Artboards' check takes 0.85 ms instead of 42.1 ms with a
second, empty artboard.

Tests compare each check with what its command does (one artboard, all
empty, one empty, locked empty, nested art; with and without a
gradient), compare any_empty_artboards with the previous check and with
measuring every object on 120 seeded documents, compare the quick boxes
with the visual bounds on seeded strokes, and compare proxy_paint_only
with proxy_paint on seeded objects.

Docs: docs/target-app-parity.md, one line under Delete Empty Artboards.
2026-11-05 06:55:59 +07:00
dotCooCoo 8f8f71f803 Glyphs: the Glyphs query reports the glyph of an overflowing character
text.glyphAlternates reported the default glyph as `chosen` for any
character that overflows its area. A character whose style substitutes
another glyph (`salt`, or `vert` in vertical type) was reported with a
glyph it is not drawn with. The query now shapes such a character alone,
with the rest of its grapheme in their own styles and in the text's
writing direction, by the same helper text.setGlyphAlternate uses.

The helper also shapes the character in the direction its paragraph
sets it in, from its bidi level, so a parenthesis set right to left is
reported with its mirrored glyph in both commands.
2026-11-05 06:55:55 +07:00
BFlat 259677711d Merge pull request #5706 from dotCooCoo/fix/render-path-rebuild-on-edit
Render: the frame after anchors of a long path move updates only the segments that touch them
2026-11-05 06:55:00 +07:00
dotCooCoo d9d33c9e4b Render: the frame after anchors of a long path move updates only the segments that touch them
The renderer keeps the path of a path or compound path of 1,024 anchors
or more by object id, with the bounds of its segments and of its miter
spikes in runs of 128 segments. When anchors move and the subpaths and
anchor counts stay the same, the next frame rewrites only the path
elements of the segments that start or end at a moved anchor, and
measures again only the runs that hold them and the runs of the
segments with length before them. Other edits make the path again as
before. The pixels and the cull bounds are the same.

- StrokeLayer::bounds_on is unchanged. The miter tip of one joint
  (miter_tip) and the stroke box with given tips (bounds_with_tips) are
  new functions beside it, and Appearance::stroked_bounds_by takes the
  box of each shaped stroke, so the cull bounds of a kept path use the
  kept tips.
- geom::bez::has_length is the predicate reach.rs used for segments
  that draw, made public.

Docs: docs/target-app-parity.md.
2026-11-05 06:28:22 +07:00
BFlat a9c1fe085b Merge pull request #5705 from dotCooCoo/fix/layers-thumbs-mask-anchors
Layers: opacity masks and edited objects count toward the thumbnail anchor limit
2026-11-05 06:17:04 +07:00
dotCooCoo f31869cf27 Layers: opacity masks and edited objects count toward the thumbnail anchor limit
The anchor count that sends a row's thumbnail to the thumbnail thread now
includes the art of opacity masks that are not disabled, since the
thumbnail draws that art. A row drawn before was rendered on the UI thread
whenever its last thumbnail had been quick, even when its object had since
become a long path; it now goes to the thumbnail thread when the object
has more than 2,000 anchors.
2026-11-05 06:07:55 +07:00
BFlat 5f3550735a Merge pull request #5704 from dotCooCoo/fix/symbol-break-hidden-tint
Keep symbol colors when expanding a hidden tint
2026-11-05 06:07:14 +07:00
dotCooCoo 1968077984 UI-1676: Ignore hidden symbol tint when expanding 2026-11-05 05:54:43 +07:00
BFlat 1fb0b4150d Merge pull request #5703 from dotCooCoo/fix/symbol-effect-expand-tint
Fix tinted symbol colors in Expand Appearance
2026-11-05 05:52:50 +07:00
dotCooCoo dcefceddf6 UI-1675: Preserve symbol tint when expanding effects 2026-11-05 05:52:34 +07:00
Dylan M. Taylor 632cb9d649 Pass the Linux signing key to the release workflow (#5702) 2026-11-04 17:45:03 -05:00
BFlat cd5e3b7a35 Merge pull request #5701 from dotCooCoo/fix/glyph-cluster-ligature
Glyphs: a consonant's ligature in a cluster with its vowel sign is its glyph
2026-11-05 05:32:35 +07:00
dotCooCoo 92ecb4a6b0 Glyphs: a consonant's ligature in a cluster with its vowel sign is its glyph
Ka with a nukta and the vowel sign I is shaped as one cluster: the vowel
sign's glyph, then the ligature of Ka and the nukta. The ligature is
neither Ka's default glyph nor an alternate, so the lookup found no
glyph for Ka: text.glyphAlternates reported the vowel sign's glyph and
text.setGlyphAlternate reported Ka's default glyph. When the cluster
holds neither, the lookup now takes the first glyph that is not drawn
for another character of the grapheme.
2026-11-05 05:20:03 +07:00
BFlat 961b28d420 Merge pull request #5700 from dotCooCoo/fix/layers-thumbs-long-paths-off-thread
Layers: thumbnails of long paths are drawn off the UI thread
2026-11-05 05:19:48 +07:00
dotCooCoo 570296b97a Layers: the painted thumbnail's comment states the per-frame cost of a long path
Measuring a long path's bounds takes time in proportion to its anchor count on every frame; it does not cost as much as rendering the path.
2026-11-05 05:10:41 +07:00
dotCooCoo b48c62f0cf Layers: area type and type on a path count their anchors toward the thumbnail limit
The first thumbnail of a row goes to the thumbnail thread when its paths hold
more than 2,000 anchors in all. The count now also takes in the frame of area
type and the path of type on a path, so a long frame or path no longer renders
on the UI thread, and the painted fallback thumbnail leaves such a row out.
2026-11-05 05:10:41 +07:00
dotCooCoo e73f51debc Layers: thumbnails of long paths are drawn off the UI thread
The Layers panel chose between the UI thread and the thumbnail thread
for a row's first thumbnail by counting objects only, so one path of
200,000 anchors was rendered on the UI thread. A row whose paths hold
more than 2,000 anchors in all now goes to the thumbnail thread, as a
row of more than 200 objects already did.

While such a row waits, the painted fallback thumbnail draws nothing
for it instead of measuring the path's bounds on every frame.

Opening the panel on one path of 200,000 anchors: the first frame takes
2.4 ms instead of 393 ms (release build, median of three runs).
2026-11-05 05:10:41 +07:00
BFlat efbdd6f40a Merge pull request #5677 from sadkotheguest/feat/find-font-replacement-rows
Find Font: Replace With Font lists family and style rows
2026-11-05 05:10:07 +07:00
Alex 31f05d3545 Find Font: a stand-in style is never the target, and the closest style comes from the catalog
A document font drawn in a substitute style stayed listed and could be chosen as the
replacement: the family is installed, so the old check passed, and Change sent the
unavailable style to text.replaceFont, which stored it and warned that the text is drawn
in a stand-in. The exact face now decides both the automatic choice and whether Change
is available; the row stays listed.

Listing System asked the font database for each family's style-less face, and for a
family not loaded yet that reads all its cataloged files, so opening the dialog could
load a whole font library. FontDb::closest_style answers from the catalog's weight and
slant instead, and the rows put that style first as before.

Tests: the dialog's 26 (the closest style is the face a style-less request resolves to,
a stand-in style is listed but not chosen) and the text crate's 474.
2026-11-05 04:47:34 +07:00
Alex 33cd7ef21a Find Font: every row names its style, and the search uses the catalog's label
The rows repeated each family: a nameless "closest style" row came first and then
the same style again with its name. A family now lists each of its styles once,
the closest style first under its own name, so every row reads "Family Style" as
the reference app's list does.

The search field used a label no translation catalog has; it now uses the font
menu's "Search", so the two fields read the same in every language.
2026-11-05 04:47:34 +07:00
Alex a53b58d7b7 Find Font: a new source picks a listed row, and confirming forgets the search
Switching Replace With Font From kept a family and style the new list might not
show, so Change could replace with a closest style of its own choosing instead of
a row on screen; the family and the style now both come from a row the list
shows, and the chosen font stays only when that exact pair is one of them.

Confirming the dialog with Enter closed it without clearing the search the Done
and Escape paths clear; all three now share the same cleanup.

Tests: a new source picks a row its list shows (family and style), and confirming
the dialog clears the replacement search.
2026-11-05 04:47:34 +07:00
Alex ad7cd71c39 Find Font: Replace With Font lists family and style rows
Choosing a replacement meant picking the family from one drop-down and its style
from another, and only then seeing what the style would be. Replace With Font now
lists rows of a family and one of its styles - the family's closest style first,
then each style it has - and one click sets both. A search above the list filters
the rows by family and style, as the Character panel's font menu searches.

Each list scrolls on its own: the document's fonts and the replacement rows keep
separate scroll states and scrollbars, and the replacement list is ten rows tall
at most, no taller than the rows it lists.

Review: the rows are rebuilt after a replacement, the search clears when the
dialog closes, and a double-click respects the same guard as the Change buttons;
the type-parity checklist has its status line and revision row.

Closes #4972.
2026-11-05 04:47:33 +07:00
BFlat c24c03c414 Merge pull request #5695 from FractalEncrypt/fix/transform-each-relative-absolute
Transform Each: Relative and Absolute scaling, the scale link, and the documented panel order
2026-11-05 04:38:19 +07:00
FractalEncrypt 77a86288e7 Transform Each: Relative and Absolute scaling, the scale link, and the documented panel order
Squashed from #5695 onto main.
2026-11-05 04:37:46 +07:00
BFlat b33d3e72f2 Merge pull request #5696 from dotCooCoo/fix/external-plain-text-paste-style
Use destination style for external same-text paste on Windows
2026-11-05 04:36:56 +07:00
BFlat 4ea3fc91b8 Merge pull request #5697 from dotCooCoo/fix/glyph-alternates-own-glyph
Glyphs: the Glyphs query reports a consonant's glyph, not its vowel sign's
2026-11-05 04:36:39 +07:00
dotCooCoo 8555ac42c4 UI-1661: Use Windows clipboard ownership for Type paste 2026-11-05 04:11:40 +07:00
dotCooCoo 67140d5e54 Glyphs: the Glyphs query reports a consonant's glyph, not its vowel sign's
text.glyphAlternates reported the first glyph of the character's
cluster as the chosen glyph. A vowel sign drawn before its consonant
(Devanagari Ka with the vowel sign I) comes first in that cluster, so
the query reported the vowel sign's glyph for Ka, while
text.setGlyphAlternate reported Ka's own glyph. Both commands now take
the character's glyph by the same rule, and so does the check in
text.setGlyphAlternate that the chosen glyph is drawn.

A character that overflows its area is shaped with its grapheme, and
each character of the grapheme now keeps its own style there, so a
nukta of another size no longer makes a ligature with its consonant
that the laid-out text does not draw. A test font with a Devanagari
nukta ligature checks both.
2026-11-05 04:11:37 +07:00
BFlat d8d86173e4 Merge pull request #5692 from dotCooCoo/fix/appearance-active-edits-each-target
Appearance: edits to the active row reach the matching row in each selected object
2026-11-05 04:09:08 +07:00
BFlat 4fd9df5898 Merge pull request #5691 from dotCooCoo/fix/perceptual-pdf-help-rgb-destination
Gradient: Perceptual PDF help says an RGB destination keeps RGB stops in RGB
2026-11-05 04:08:50 +07:00
BFlat 6a8c57ac0f Merge pull request #5690 from dotCooCoo/fix/render-long-path-frame
Render: a long dashed stroke is outlined only near the view after an edit
2026-11-05 04:08:33 +07:00
dotCooCoo af74427ef4 Appearance: edits to the active row reach the matching row in each selected object
Paint and effect edits matched the active row across all selected
objects at once, so one object without an equal row made every object
edit the row at the raw index. ItemTarget::per_target and
effect_targets now match each object against the first one on its own,
as items_per_target does. effect_targets also looks up the row in the
stack it belongs to when deciding whether an effect edit is
object-level, so an effect on a row that sits lower in another object's
stack no longer fails with "no appearance item".
2026-11-05 03:55:55 +07:00
dotCooCoo 0f99ca1895 Gradient: Perceptual PDF help says an RGB destination keeps RGB stops in RGB
The `method` text of `paint.editGradient` and two passages of
docs/mcp.md said a CMYK document writes RGB and Lab gradient stops in
CMYK. An export that converts colors to an RGB destination profile
converts them to that profile first, so in a CMYK document they stay
RGB and a Perceptual spot gradient with such stops is written as an
image with the spot warning. The texts now say so, and the test of RGB
stops in a CMYK document also checks the RGB destination case.
2026-11-05 03:55:52 +07:00
dotCooCoo b742c77596 Render: the dashed-stroke edit test moves a line anchor in the view
Anchor 1500 of the test zigzag is a curve, so the first edit left the path
unchanged. The test now moves anchor 1501, a line in the view, and fails
when either anchor it moves is not a line.
2026-11-05 03:55:48 +07:00
dotCooCoo ed3efcbd7d Render: a long dashed stroke is outlined only near the view after an edit
A dashed stroke of a path with more than 1,024 path elements was outlined
whole in every frame after an edit. It is now outlined in and around the
view like a solid stroke. dash_kept runs the pattern along every segment
and keeps only the dashes and dots on the segments near the view, so they
sit where dashing the whole path puts them and the pixels are the same.
A pattern too fine to draw is still drawn solid, and that solid stroke is
cropped too.

At 1600 x 1200 the frame after one anchor moves takes 14 ms instead of
102 ms on a filled closed path of 50,000 anchors with 6 pt dashes and
4 pt gaps. anchor_bench now includes that document.
2026-11-05 03:55:48 +07:00
BFlat a2e98ac57c Merge pull request #5686 from ruchirguitar/fix/5336-optical-kerning-reference-gap
Fix #5336: Optical kerning spaces pairs by the font's own stem spacing
2026-11-05 03:30:17 +07:00
BFlat b402013f06 Merge pull request #5685 from dotCooCoo/fix/quick-bounds-nested-nine-slice
Symbols: quick boxes hold art that nested 9-slice guides move
2026-11-05 03:30:00 +07:00
BFlat 34b1fc9f42 Merge pull request #5681 from dotCooCoo/feat/recovery-folder-choose
Preferences: Data Recovery's Folder gets a Choose button
2026-11-05 03:29:45 +07:00
BFlat 23f56b6cfa Merge pull request #5682 from dotCooCoo/fix/share-of-thousands-comma
Numeric fields: 1,000% in a size field is refused
2026-11-05 03:29:20 +07:00