The renderer's pixels and egui's are both premultiplied RGBA8, so color_image copies the bytes in one go. At 3456×2234 that takes 0.9 ms instead of 3.4 ms per update, at 5120×2880 1.6 ms instead of 6.8 ms. Every rendered image the UI shows (canvas, thumbnails, previews) goes through it.
complete_languages_translate_every_message read a message literal only
right after a marker such as Err( or Other(. It did not read the
message given to the engine's bad(cmd, ...), so this test did not
require a row for a parameter error. It also did not read the messages
given to the engine's err() and error(), to the PDF writer's bad() and
failed(), or to an error enum's variant such as PsError::Limit(...).
1,122 of these messages have no row in any complete catalog.
The scan now reads those messages, a literal or a format! string, in
the same template form as the catalogs' rows. It skips PsError::Ps and
EngineError::UnknownCommand, whose first argument is a PostScript error
name or a command id. The 1,122 messages are listed in
untranslated_messages.txt next to the tests, together with the 71 that
pull requests open at 009384b add without rows, and the test lets a
listed message through whether or not it has rows. Every other message
needs a row in each complete catalog, so a new message made this way
fails the test until it has its rows.
VECTORCRAFT_I18N_DUMP_UNTRANSLATED=<file> writes the messages in the
sources that lack a row. No catalog rows change.
Docs: docs/development.md, docs/localization-parity.md.
Document Info had no Repeat Objects category, and document.info
refused category "repeats". The reference app's Document Info panel
menu lists Repeat Objects after Font Details.
Document Info now has a Repeat Objects category after Embedded Images.
It lists each radial, grid and mirror Repeat object by name with its
kind and the number of instances it makes. For a grid it gives the
rows and columns instead, followed by the number of instances it
draws when that is fewer. The count comes from the new
RepeatSpec::instances, which gives the number of instances a repeat
draws. That number is smaller than its options give when repeats
nested in its source would pass the instance budget. With Selection
Only the category lists the Repeat objects in the selection.
document.info gives them as repeatObjects, the panel's flyout offers
the category, and the text report that Document Info › Save… and
File › Package write has a REPEAT OBJECTS section. The title has a row
in every catalog.
Docs: docs/mcp.md, docs/target-app-parity.md.
With one clip group selected, the Properties panel has Edit Clipping
Path and Edit Contents at its top, under the Clip Group heading, as the
reference app does: "With the clip group selected, click the Edit
Clipping Path button at the top of the Properties panel to select and
edit the mask, or click the Edit Contents button to select and edit the
masked content"
(https://www.adobe.com/learn/illustrator/web/crop-using-masks). They
run the existing object.clippingMask.editMask and editContents, which
select the clipping path or the clipped art for the selection tools, as
Object > Clipping Mask does. Edit Contents shows only when the group
clips something. With several objects selected they don't show (the
tutorial describes one selected clip group); the Contextual Task Bar
does the same. Both labels are already in every catalog.
Test: panels::properties::tests::one_clip_group_has_edit_clipping_path_and_edit_contents_at_the_top
(first under the heading, each selecting its part; Edit Clipping Path
alone once the clipped art is deleted; neither for a clip group
selected with another object in either order, or for a plain group).
Documented in docs/target-app-parity.md, docs/ui-parity.md and
docs/gaps.md.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Switch to the Touch built-in workspace when the preference is on and a touch
screen is present; restore the prior workspace when it is turned off.
Co-authored-by: Cursor <cursoragent@cursor.com>
Mixed left/right/first-line indents and space before/after now follow the same shared-value rules as alignment buttons.
Co-authored-by: Cursor <cursoragent@cursor.com>
After scaling groups, type.scaleTextAttributes matches panel sizes to what draws on canvas without leaving scale in the affine.
Co-authored-by: Cursor <cursoragent@cursor.com>
Store corner radii as fractions of each corner's max so rounding scales with the shape; expose via Corners dialog and object.setLiveShape relativeRadii.
Co-authored-by: Cursor <cursoragent@cursor.com>
Hue Shift colorization can sample key color from canvas art via the
eyedropper next to the swatches, matching Illustrator's brush workflow.
Co-authored-by: Cursor <cursoragent@cursor.com>
Users can pick an image in the Texturizer dialog; it is stored as relief heights and survives save/open like Glass textures.
Co-authored-by: Cursor <cursoragent@cursor.com>
Route dock and floating strip reorders through shared logic, sync layout.floating order, and assert reorder via window.panel.move in the harness test.
Co-authored-by: Cursor <cursoragent@cursor.com>
Route floating strip drags through the same command path as the dock strip and centralize group tab index moves in one helper.
Co-authored-by: Cursor <cursoragent@cursor.com>
Reuse catalogued window.panel.move errors, keep legacy dock drags from clearing docking on Escape, reorder floating tabs locally without reconcile re-entry, and fix dock_tab_order test setup.
Co-authored-by: Cursor <cursoragent@cursor.com>
Legacy and floating strip drags update order via the panel.move command; partial saved orders append missing tabs, with command-level tests and Illustrator workspace doc link.
Co-authored-by: Cursor <cursoragent@cursor.com>
Rows two PRs of the batch both changed came out twice in localization-parity, target-app-parity and type-parity; the newest of each is kept. prefs_dialog.rs is formatted.
A click on the Tabs panel's ruler added a stop, and a drag moved one,
at the fraction of a point under the pointer. The reference app's Tabs
page says that the tab ruler does not set stops in increments smaller
than 1, and that the X or Y field sets them as finely as 0.01 point.
The page does not name the unit of 1. The ruler here uses 1 point, the
unit the page gives for the X or Y field, in every General unit.
With Snap to Unit off and Shift not held, a stop placed or dragged on
the ruler now lands on the nearest whole point. With Snap to Unit on,
or Shift held, it still lands on the ruler's nearest tick, which on a
millimeter ruler is a fraction of a point. A position typed in the X
or Y field keeps its hundredths of a point.
Docs: docs/target-app-parity.md.
The Print dialog's Media list only had Letter/Legal/Tabloid and the ISO A
and B papers, so Japanese papers had to be entered as a custom size, and
"B4"/"B5" were ISO 216 (250 × 353 and 176 × 250 mm) although Japanese
printing uses the JIS sizes (257 × 364 and 182 × 257 mm, JIS X 6310).
Media gains JIS B4, B5 and B6 next to the ISO ones, and はがき
(japanese postcard, 100 × 148 mm), 往復はがき (japanese double postcard,
148 × 200 mm), 封筒 長形3号/長形4号 (japanese envelope #3/#4, 120 × 235
and 90 × 225 mm), 角形2号/角形3号 (kaku #2/#3, 240 × 332 and 216 × 277 mm),
四六判 (yonroku-ban, 127 × 188 mm) and A6, all in millimetres at 72/25.4.
b4 and b5 keep their ISO 216 dimensions so print settings and presets
saved before the Japanese papers existed keep their paper; the dialog
labels say "B4 (ISO)"/"B5 (ISO)" beside "B4 (JIS)"/"B5 (JIS)".
A paper is named by its JSON id or by its dialog label, any case and
spacing ("b5 (jis)", "B5 (JIS)" and "b5jis" all mean the JIS B5), so
agents and the control channel need not know the id spelling; an unknown
name is still refused. Media is written out instead of through the
choice! macro to carry that lookup.
The print.setup/file.print parameter docs and docs/mcp.md list the new
papers; the Print dialog picks them up from Choice::ALL with no UI code
change, and the Japanese labels are translated in ja.tsv (はがき,
往復はがき, 長形3号, 長形4号, 角形2号, 角形3号, 四六判).
Tests: japanese_paper_sizes checks every added size in points, that the
ISO B4/B5 values did not change, name resolution and id/label round
trips, and that each paper prints a PDF page with its own MediaBox size.
Validation: cargo fmt --all -- --check clean; cargo clippy
-p vectorcraft-pdf --all-targets -- -D warnings clean; cargo test
-p vectorcraft-pdf -> 330 passed, 0 failed (328 lib + 2 integration).
Scatter, Art and Pattern Brush Options had no Tips button. The
reference app's Brushes page has one for Colorization, with information
and samples about each method.
A Tips button beside Method now opens Colorization Tips. For each
method it shows the name, what the method does, and a sample of the
brush as OK would make it, painted with that method on a blue stroke.
Hue Shift's sample uses the dialog's Key Color. A second click on Tips,
Escape or a click outside the popover closes it, and the dialog field
tips opens it from a command. Screen readers name the button, the
popover and each sample.
In every dialog, Escape with a dropdown list open now closes the list
and leaves the dialog open. Before, it canceled the dialog and
discarded its edits.
The stroke preview and the samples no longer panic when ui.dialog.set
writes a brush definition (__def) that is not a JSON object.
Docs: docs/target-app-parity.md.
The reference app creates a new symbol with F8 on Windows and macOS.
VectorCraft opened Symbol Options for a new symbol from the Symbols
panel's New Symbol button, from the panel menu's New Symbol… item and
from Help › Search Commands…, but no key ran it, and F8 did nothing.
ui.symbolOptions (Symbol Options…) now has the default F8 on every
platform. With a selection the key opens Symbol Options for a new
symbol, as the New Symbol button does: OK makes the symbol and puts an
instance of it in place of the art, and Cancel makes none. With nothing
selected the key opens no dialog, and the status bar says that nothing
is selected. Shift+F8, Cmd+F8 and Cmd+Shift+F8 keep their commands.
Keyboard Shortcuts now lists the command with its key.
Docs: docs/ui-parity.md, docs/target-app-parity.md,
docs/control-protocol.md.
A dialog's slider could take the keyboard focus from Tab, but it
responded only to the pointer, so an arrow key moved the focus to the
nearest widget in the arrow's direction and a keyboard user could not
move the slider. Screen readers could not step it either.
These dialog sliders, each beside a number field, now respond to the
keyboard. A click or Tab gives a slider the focus, which an outline
shows. Then the Right and Up arrows step the field's number up to the
next whole number and the Left and Down arrows step it down to the
next whole number. Shift with an arrow, Page Up and Page Down step it
by ten, and Home and End set the slider's first and last value. No
step goes past either end, and a number past the slider's range steps
from its nearer end. Screen readers' increment and decrement actions
step the slider as the arrows do. A step sets the field's number in
the field's own unit, so a Move slider in a millimeter document steps
one millimeter. When the field holds a number past the slider's range,
Home, End or a drag to that end of a form slider now sets the end's
number; before, such a drag left the field's number as it was.
The form sliders get this through the new color_slider_stepped, next
to color_slider_valued, and so do the Pencil, Paintbrush and Smooth
Fidelity, the Perspective Grid's Opacity, the 3D dialogs' sliders and
the flattener's balance, which the Flattener Preview panel also shows.
The Color panel, Swatch Options, the Color Picker's channel slider,
the Navigator and the Character panel keep their sliders as they were.
Docs: docs/target-app-parity.md.
The reference app reorders a brush that is dragged to a new place in
its Brushes panel, among the brushes of its type only. VectorCraft's
Brushes panel could not reorder brushes, and no command moved one.
brush.move {name | names, to?, type?} moves brushes to index to among
the brushes of their type, in brush.list order, as one undo step. A
move that keeps the order records no step. Brushes of different types,
or a type other than theirs, are refused with "brushes move only within
their type". In the Brushes panel, a brush dragged onto another brush
of its type goes before or after it by the half of the tile or row
under the pointer, and a bar marks the slot while it is held there. A
brush dropped on a brush of another type stays where it is, and the
status bar shows the refusal.
Docs: docs/mcp.md, docs/target-app-parity.md.
At startup, a persistent swatch or graphic style library panel whose
file could not be read was closed and left out of the preferences,
with only a status-bar note. The next preferences save dropped it, so
moving or renaming the file once lost the panel and its settings.
Such a panel now reopens where the saved layout had it and stays in
the preferences. Its tab shows the file's name without the extension,
and its body shows "Missing:" with the file's path and a Locate
Library… button. The new command ui.libraryPanel.locate {panel,
path?} loads the library from the file given, or from one picked in
an open dialog, and shows it in the same panel, which keeps its view,
Show Find Field and Persistent. When the file's library is open in
another panel, the missing panel closes and that panel is brought
forward and made persistent.
A persistent panel whose User Defined library file is deleted while
the app runs shows the library as missing the same way; other panels
still close. window.swatchLibrary and window.graphicStyleLibrary take
a missing library's id, so a closed missing panel opens again.
Previous Library and Next Library are dimmed in a missing panel's
menu.
Docs: docs/mcp.md, docs/ui-parity.md, docs/target-app-parity.md.