Commit Graph
4892 Commits
Author SHA1 Message Date
dotCooCoo 84f528f5bb Character: Kerning reads (10+5) with a manual value too
Kerning read one pair of parentheses around its text only while it
showed an automatic value as (12), so typing (10+5) over a manual value
committed nothing. spin_kerning now reads that pair in both states;
the field still shows parentheses only for an automatic value, and
(10+5)*2 is still refused.
2026-11-04 21:37:54 +07:00
dotCooCoo 21be9e6b6e Tests: the opening queue tests wait on the worker's signal instead of the clock
The opening tests waited 400 or 500 ms of wall-clock time for files read off
the UI thread, and up to 30 s in loops that polled until the queue emptied.
Under load a read could take longer, and
a_file_that_opens_and_asks_holds_back_the_next_one_ready failed.

wait_read now blocks on each pending file's own result channel and keeps the
result for poll, and open_ready waits and then polls once. The background save
in long_operation_busy_while_a_file_opens_or_a_background_save_runs waits with
background::wait_all, and its job waits on the release channel without a
timeout. Shipped code is unchanged.
2026-11-04 21:37:51 +07:00
Don Omondi b8269d7bdf Native menu: read the menus again when the fonts change
On macOS the menu bar is native and is read again only when its state
hash changes. Type > Font now lists the loaded fonts until the installed
ones are scanned, so the hash takes the font generation and whether the
scan has ended. The tests that wait for process-wide generations to
hold still wait for the fonts too.
2026-11-04 21:37:48 +07:00
Don Omondi 0143e0f706 Fix #5483: a large font library no longer keeps the window from opening
The menu bar builds Type > Font in every frame, the first included, and
its family list waited for the scan of every installed font. When that
scan took longer than the 20 s first-frame watchdog (thousands of fonts
on a slow disk, or each file checked by antivirus), the app took the
graphics adapter for hung and started again without it, until none was
left and no window ever showed.

The menu now lists the fonts already loaded until the scan ends, then
all of them (FontDb::menu_family_list_now, installed_fonts_ready).
2026-11-04 21:37:48 +07:00
dotCooCoo be52e4e8af Tabs: setting the stops a paragraph already has records no undo step
text.tabs.set and text.tabs.clear return without an edit when every
targeted paragraph already holds the stops. A Ctrl-drag that the 0 pt
bound keeps from moving, or a drag released where it started, no longer
adds an undo step that changes nothing. When one targeted paragraph
holds other stops, the command still sets them all.
2026-11-04 20:51:10 +07:00
dotCooCoo 535d423d6c Appearance: Add New Fill and Add New Stroke params text match the code
appearance.addFill copies the active fill row only when the call has no
`ids`, no `id` and no `target: "contents"`; the params text now names
`id` too. appearance.addStroke gives the new stroke at least 1 pt, so a
0.5 pt top stroke gives a 1 pt stroke; the params text now says so.

Tests pin both: `id` alone leaves the active row alone and puts the new
fill on top, and Add New Stroke on a 0.5 pt stroke adds a 1 pt one.
2026-11-04 20:51:07 +07:00
dotCooCoo 88aeee8f8e Shape Builder Tool Options: a highlight color with an alpha part is refused
OK in Shape Builder Tool Options sent highlightColor to tool.setOption,
which keeps only #rrggbb, so #ff000080 was applied as #ff0000 with no
error. OK now gives the same error as the Live Paint tool options for a
hex color with an alpha part (#rrggbbaa or #rgba), the dialog stays open
and the tool's options do not change. The tool.options params text,
docs/mcp.md and the progress doc say so.
2026-11-04 20:51:03 +07:00
dotCooCoo db6a0d786e Shortcuts: an Escape that cancels a Tools or ruler move runs no Escape command
A command bound to plain Escape no longer runs when that Escape
cancels a floating Tools panel move or a ruler-origin drag, nor on the
key repeats while Escape stays held. The next separate Escape press
runs it as before. A test binds Select None to Escape and holds Escape
through both cancels.
2026-11-04 20:51:00 +07:00
dotCooCoo 49616e0a16 i18n: the package status stays English until the status bar shows it
File > Package stored its status line in the interface language, so after
a language switch the status bar kept the old language, and the control
channel and MCP read a translated message. The status line and its notes
are now English, and only the "Package created" dialog is translated when
it is shown. Each catalog gets message rows for the notes, so the status
bar translates them too. On the desktop, where the dialog names every font
left out, a long font list is cut short in the status line so the line
stays short enough to translate.
2026-11-04 20:50:55 +07:00
dotCooCoo 336b1271dd Type: Kerning menu checks neither method for a manual value
A number typed into the Kerning field after Optical keeps the Optical
flag on the type, and the manual value sets the spacing. The Kerning
menu still checked Optical. It now checks neither Auto nor Optical
while a manual value is set.
2026-11-04 20:50:50 +07:00
dotCooCoo dd41552c30 PDF export: warn about spot stops in freeform gradients without points
A freeform gradient whose field has no points of its own takes its
colors from the stops, so spot-linked stops in it are written in
process colors too. The export now warns in that case, as it does for
the other gradients drawn as images.
2026-11-04 20:36:11 +07:00
dotCooCoo 9a2f1e5eb7 Tests: the Render Threads restart test stays within the preference's range
VECTORCRAFT_RENDER_THREADS can make default_threads() return more than 64,
the largest value prefs.set accepts for renderThreads. The test now caps
the value at 64, so it passes with the variable set to 100.
2026-11-04 20:36:07 +07:00
dotCooCoo c5d65ed893 i18n: French rows keep the no-break space before punctuation 2026-11-04 20:12:14 +07:00
dotCooCoo 88f1e4e337 Actions: control requests run before a paced playback's next step
A Stop or a document switch sent through the control channel in the
frame a step comes due now takes effect before that step runs. An
action played through the control channel at Pause For starts its first
pause in the next frame, as the panel's Play does.
2026-11-04 20:12:14 +07:00
dotCooCoo e6e08af9ae Actions: test that closing the playing document ends a paced playback
Step by Step with the only document closed, and Pause For with the playing
document closed while another stays open: when the next step is due the
playback ends with its status and no further step runs.
2026-11-04 20:12:14 +07:00
dotCooCoo f06b8040eb Actions: Step by Step runs each step after the window shows the one before
A Step by Step playback now waits after each step until a UI pass that the window shows has drawn it. A logic-only iteration, a pass in a minimized or covered window and a discarded pass do not count, so a hidden window no longer runs the steps unseen and does not ask for frames while it waits.
2026-11-04 20:12:14 +07:00
dotCooCoo 7f79568233 Actions: Pause For starts each pause in the frame after the step 2026-11-04 20:12:14 +07:00
dotCooCoo 22f8be1c5d Actions: ui.action.play's doc comment names what each playback speed returns 2026-11-04 20:12:14 +07:00
dotCooCoo 64c9bfa720 Actions: a paced playback ends when a step comes due in another document
Step by Step and Pause For remember the document that was active after each step. When the next step is due and a different document is active (another one was made active, or that one was closed), the playback ends with a status and the rest of the steps do not run. A step that makes another document active itself does not end the playback.
2026-11-04 20:12:14 +07:00
dotCooCoo f5f3ca4286 Actions: Step by Step draws the first step, and recorded batches play as their steps
Step by Step holds the frame after Play, so the first step's result is drawn before the
second step runs, also when the panel's Play button runs after the canvas drew.

An action step recorded as command.batch (an action played while recording, including one
playing at Step by Step or Pause For, or a panel operation that runs as one batch) now plays
as its own steps in Play and Batch..., instead of failing with "batches cannot nest".
2026-11-04 20:12:13 +07:00
dotCooCoo 9d8be643f4 Actions: Playback Options plays actions accelerated, step by step or with a pause between steps
The Actions panel menu's Playback Options... sets how actions play (ui.action.playbackOptions {speed?, seconds?}). Accelerated, the default, plays every step at once as one undo step, as before. Step by Step plays one step per frame so the canvas draws each step's result, and Pause For waits 1 to 60 seconds between steps; each paced step is its own undo step. Stop Playing/Recording ends a paced playback, and another action does not play while one is paced. The setting is kept with the UI preferences. Batch... plays at full speed.
2026-11-04 20:12:13 +07:00
Don Omondi 33b57fb581 Batch Trace Folder: move to Object › Plug-ins as an add-on
Batch Trace Folder replaces a script, so it is now a Document Tools
add-on command, addon.document.batchTraceFolder, under Object ›
Plug-ins › Document Tools (as Export All Open Documents is), and no
longer in Object › Image Trace. Behaviour, caps and the refusal of a
folder too big to list in full are unchanged; the tests move with it.

The status messages have @msgplural rows in every catalog, as does the
menu label. When some images trace and some don't, the status is one
counted message ("Traced {n} images; not traced: {failed}") so it
translates as a whole.
2026-11-04 20:12:07 +07:00
Don Omondi 7790c37eb3 Batch Trace Folder: refuse a folder too big to list in full
list_dir cuts its listing before sorting, so a cut listing could skip any image. The batch now
stops with an error instead of tracing part of the folder.
2026-11-04 20:12:06 +07:00
Don Omondi 7c04fd8973 Fix #4921: Image Trace > Batch Trace Folder
imageTrace.batchFolder opens each image in a folder, traces it with a
preset (optionally expanded), saves it into a destination folder under
its own name (vectorcraft, svg, pdf, ai or eps; taken names numbered so
nothing is replaced) and closes it again whether it traced or not; the
active document stays active. Object > Image Trace > Batch Trace Folder
asks for both folders. Not on the web.
2026-11-04 20:12:05 +07:00
Don Omondi ed634a78f4 Guide clipboard: Paste after a guides-only Copy takes the guides, not older system clipboard content
A Copy of ruler guides alone left the system clipboard as it was, so the
next Paste took whatever another app had copied before instead of the
guides. The guides-only Copy now empties the system clipboard (on the
web, empty text through egui with Copy As SVG on), as any Copy replaces
what the clipboard held, and drops an earlier Copy's formats not written
yet. What another app copies afterwards still pastes.
2026-11-04 20:12:01 +07:00
Don Omondi ad64852255 Guide clipboard: guides alone stay off the system clipboard; test two pastes and layers
Copying only ruler guides published an empty SVG and text to the system clipboard, replacing
what other apps put there. The clipboard now offers other apps nothing without art. Tests cover
two pastes adding guides of their own on the current layer (following its visibility) and the
SVG carrying no guides.
2026-11-04 20:12:00 +07:00
Don Omondi 2402f8e1aa Copy and paste guides: Cut with direct-selected anchors takes the guides too
With anchors and ruler guides selected, Cut deletes the anchors and the
selected guides in one undo step (edit.clear's guides: true reaches the
anchor-deleting path).
2026-11-04 20:11:59 +07:00
Don Omondi 484a78f812 Copy and paste guides: Cut takes the selected guides with the objects
Cut with objects and ruler guides selected now removes both, in one
undo step: edit.clear takes guides: true beside ids to delete the
selected guides with the objects it names.
2026-11-04 20:11:59 +07:00
Don Omondi f3faa115c6 Fix #3226: copy and paste ruler guides
Edit > Copy and Cut put the selected ruler guides on the clipboard,
with the selected objects or on their own. Pasting puts them on the
layer new art goes to, selected, in the same undo step: with objects
they move as the objects do; alone they keep their places, while Paste
in Place / in Front / in Back put them on the active artboard as they
lay on theirs, and Paste on All Artboards on every artboard.
2026-11-04 20:11:59 +07:00
dotCooCoo ac0ca081bb Commands: a color param with an alpha part is refused instead of losing its alpha
Squashed from #5603 onto main.
2026-11-04 20:08:42 +07:00
Don Omondi 2b10694f99 Fix #3759: vertical alignment buttons for area type in the Properties and Control panels
Squashed from #5100 onto main.
2026-11-04 20:08:06 +07:00
Don Omondi 1348f827d0 Area Type Options: applying to several frames keeps each frame's own options
text.areaOptions given for several area type frames (ids, or a selection of
several) copied the first frame's options onto all of them, so setting one
option (say the vertical alignment) also replaced the others' columns, gutter,
inset and fit. Each frame now takes only the options given over its own, and
fit resolves against each frame's own setting, so fitMinPercent alone changes
only frames that already shrink text to fit. Options that change nothing add
no undo step and keep redo.
2026-11-04 20:07:58 +07:00
dotCooCoo 9275b902a9 PDF/.ai import: a file whose type all comes from its text document opens without its PDF part
A PDF-compatible .ai (or a PDF saved with the editing data) now opens
from its layers without its PDF part being imported, and without being
compared with its page, when:

- its text document makes the type of every text object, hidden ones
  included, at least one text object shows, and none is drawn by the
  appearance of the layer or group it is in;
- its layers hold no unread object, no placed file with a second
  placement and no live envelope with drawn art to fall back on, and
  no image too large to read;
- it declares no large canvas, and its artboards are its pages' trim
  boxes one for one, in points or in the first page's UserUnit.

A file whose layers draw differently from its page then opens from its
layers instead of its PDF part. The bleed comes from the pages' bleed
boxes. The check runs only when no pages are picked, layers and the
editing data are on, the crop box is asked for and type comes in as
text. Files saved without PDF compatibility, files whose PDF part
draws only type or nothing, and files whose PDF part's content can't
be read take the same path when they qualify: their bleed comes from
the pages' bleed boxes, pages in units larger than a point draw them
at their size in points, and the note that the PDF part couldn't be
read is not given, since its content is not read.

Files that don't qualify open as before, and the editing data decoded
and read for the check is reused after the PDF part is imported
(layered_ai_from).

When a file whose PDF part can't be read is scaled up to its size in
points, its text wraps are now laid out again at that size, and the
default graphic styles of a file without graphic styles of its own are
no longer scaled. The same applies on the new path.
2026-11-04 19:41:56 +07:00
dotCooCoo 3f9545c962 Native files: documents from before 0.21.0 keep their Twist turning as saved
#5139 (in 0.21.0) made a positive Twist angle turn clockwise, but
documents saved by earlier releases stored their angles with the other
sign and reopened twisted the other way.

The document version (FORMAT_VERSION) goes from 1 to 2. Opening a
version 1 document negates its Twist angles (in layers, opacity-mask
art, Shaper edits, symbols, graph designs, pattern swatches, graphic
styles and brush definitions), unless the file is of the current file
version and its generator is VectorCraft 0.21.0 or later. The document
opens and saves as version 2. A save for an older file version, which
only apps before 0.21.0 read, writes a version 1 document with the
angles negated back. Graphic style libraries (.vcstyles) go to version
2: a version 1 library's Twist angles are negated when it is read.
Readers do not check either version, so older builds open the new files
as before.
2026-11-04 19:41:51 +07:00
dotCooCoo d9499bc0a3 Handle font wheel input in native viewports 2026-11-04 19:41:47 +07:00
dotCooCoo 27fc855246 Appearance: Add New Fill takes the active row's paint, as before
Add New Fill again copies only the active fill row's paint; Duplicate
Item copies a whole row. The params text of appearance.addFill and
appearance.addStroke now says the new fill or stroke takes the paint
(and for a stroke, the weight) of the row it starts from, and a test
pins the paint-only copy.
2026-11-04 19:41:42 +07:00
dotCooCoo db468928d4 Appearance: Add New Fill copies the whole active fill row
With a fill row active, Add New Fill copied only the row's paint and
reset its opacity, blend mode, visibility, overprint and effects. The
new row is now a copy of the whole active row, as the command's
description says.
2026-11-04 19:41:42 +07:00
dotCooCoo 5626a1614c Gradient: method help names the PDF Separation and DeviceN shadings
The `method` text of `paint.editGradient` and the Method paragraph in
docs/mcp.md said SVG and PDF exports draw a Perceptual gradient as an
image. PDF writes an eligible one natively: tints of one spot ink as a
Separation shading in that ink, and spot inks mixed with other spot,
CMYK or gray inks as a DeviceN shading. Gradients without spot stops,
with RGB or Lab stops, dithered fills and curves with too many stops
still become images there. Both texts now say so.
2026-11-04 19:41:38 +07:00
dotCooCoo 95b3fbfec9 Type: the font warning leaves out characters Missing Glyph Protection kept
text.setStyle and text.setRangeStyle collected the fonts for their
warning after Missing Glyph Protection had run. A character the new font
has no glyph for keeps its earlier font, and when that font was not
available the warning named it, although the command did not set it.

protect_missing_glyphs now returns the byte ranges that kept their font,
and both commands leave those ranges out of the warning's fonts.
2026-11-04 19:19:04 +07:00
dotCooCoo bc2af8df35 Appearance: the active row is matched in each selected object on its own
Reduce to Basic Appearance and Add New Fill matched the Appearance
panel's active row only when every selected object had an equal row.
With three objects whose third had a different lower fill, the second
object lost the match too: Reduce kept its top fill instead of the
active one, and Add New Fill put the copy on top instead of above the
active row.

items_per_target now matches each object against the first one
separately, so an object keeps the match whenever it has an equal row.
2026-11-04 19:19:00 +07:00
dotCooCoo 7a3c09c05d Keyboard Shortcuts: the backtick key's label ignores the next key's text on the web
eframe's web build sends each key's text before its press. When several
keys arrive in one frame, the editor paired the backtick press with the
text typed by the key after it, so a shortcut on that key could show the
next key's character. The editor now pairs each text with its press
through `shortcuts::typing_presses`, which shortcut dispatch already
uses, for both event orders.
2026-11-04 18:56:43 +07:00
dotCooCoo af14fe3d59 Align Text Tweaker ranges to character boundaries 2026-11-04 18:56:40 +07:00
dotCooCoo a7266dd10a Character: Kerning reads (10+5)*2 as nothing instead of 10+5*2
Numeric fields dropped every parenthesis before reading a value, so
(10+5)*2 typed into a field committed 20. Only the Kerning field, which
shows its automatic value as (12), now drops one pair of parentheses
around the whole text. Parentheses anywhere else are refused, and fields
without that display refuse them too.
2026-11-04 18:56:36 +07:00
dotCooCoo 6fa0d6e4e0 Navigator: cached raster effects follow Draw Dashed Lines as Solid Lines
The raster-effect cache key now includes the setting, so a renderer that
draws the same art with it on and then off no longer reuses a drop
shadow or other raster effect made with the other setting.
2026-11-04 18:29:29 +07:00
dotCooCoo ddf3373cf0 Navigator: translate the Light Red view box preset
Add a Light Red row next to Light Blue in all 26 catalogs, so the preset
name in Panel Options shows in the interface language.
2026-11-04 18:29:28 +07:00
dotCooCoo 30f903bf60 Navigator: Panel Options sets the view box color and draws dashed lines as solid
The Navigator panel menu's Panel Options… is enabled and opens a dialog with
the view box color (a preset or a custom color; Light Red, today's color, is
the default) and Draw Dashed Lines as Solid Lines, off by default, which draws
dashed strokes without their dashes in the panel's thumbnail only. The
options are kept with the UI state, and ui.navigatorPanelOptions sets them.

RenderOptions::solid_dashes draws dashed strokes as solid lines, including
type strokes and the silhouettes of stroke effects.
2026-11-04 18:29:28 +07:00
dotCooCoo a403e75802 Validate character style numeric dimensions
Squashed from #5540 onto main.
2026-11-04 18:29:25 +07:00
dotCooCoo d79f5ccb75 Layers: a layer or gap color with an alpha part is refused instead of losing its alpha
layer.setProps, layer.new, layer.newSublayer and livePaint.gapOptions read
an 8-digit hex color such as #ff000080 as #ff0000 and set it without its
alpha. They now refuse it with the error a bad color already gets, and
change nothing. #rgba was already refused. #rrggbb, #rgb, a preset index
and a preset name are read as before.
2026-11-04 18:29:17 +07:00
dotCooCoo 12508e6c60 Dialogs: a color with an alpha part is refused instead of losing its alpha
`Color::from_hex` reads `#rrggbbaa` and keeps only its first six digits, so
a `color` set to `#ff000080` through `ui.dialog.set` was applied as
`#ff0000` without an error by Tile Edge Color, Live Paint Selection Tool
Options, Live Paint Bucket Options and Gap Options.

- A new `checked` next to `resolve` in `dialogs/tile_edge_color.rs` gives
  the existing "is not a colour" error for an 8-digit hex color, and
  otherwise reads the color as `resolve` does. `resolve` is unchanged, so
  the swatches the dialogs draw with it show such a color by its RGB as
  before.
- Each of the four dialogs' OK checks the color with `checked` first, and
  leaves the dialog open with nothing changed. Tile Edge Color and Gap
  Options already refused `#rgba`, which `from_hex` does not read. Live
  Paint Selection and Live Paint Bucket used to ignore a color they could
  not read, such as `#f008`, and apply their other fields; their OK now
  refuses it.
- With Preview on, Gap Options sends no color for a `#rrggbbaa` value, so
  the groups keep their own color, and the previewed gaps draw in black.

Docs: the params text, docs/mcp.md, docs/control-protocol.md and
docs/target-app-parity.md.
2026-11-04 18:29:13 +07:00
dotCooCoo 8762242dfe Select: describe which object Same Symbol Instance compares with
The params text of select.same.symbolInstance, docs/mcp.md and
docs/target-app-parity.md now say that the object compared with can be
of any kind (a group member selected on its own, or an object that is
not a symbol instance, which leaves the selection empty), and that new
art on a locked artboard is not passed over while it stays selected.
A test covers the three cases.
2026-11-04 18:29:09 +07:00