The panels and dialogs draw in their own egui families (`ui`,
`ui-semibold`, `mono`), and egui panics drawing text in a family no
font is bound to. Only `theme::install_fonts` binds them, and only
`VectorcraftApp::logic` calls it, on its first frame. An embedder that
draws the panels or dialogs itself (`dialogs::show`, a panel's `ui`)
on its own context, without `logic`, panics on the first semibold
title it draws, an effect dialog's for one. Calling `install_fonts`
avoids that, but it embeds the interface's five faces (about 2 MB)
whatever the embedder wants: today such an embedder keeps them out of
its binary only by never calling it, so that the linker drops them.
vectorcraft-ui-egui gets an `interface-fonts` feature, on by default,
that embeds those faces. Without it `install_fonts` binds the same
three families to egui's built-in fonts, so an embedder can always
call it. Those lack some symbols the panels show (→ ▾ ● ✓), which the
docs say. With the feature, `install_fonts` installs exactly what it
did before: the same font data and the same family stacks.
`tests/install_fonts.rs` draws an effect dialog on a context only
`install_fonts` prepared, and checks the face at the head of each
family in both configurations. `cargo xtask ci`'s `no-bundled-fonts`
step runs it without default features and lints the crate that way.
Without the `install_fonts` call, or without the semibold binding,
both tests fail with egui's "FontFamily::Name(\"ui-semibold\") is not
bound to any fonts". The two lib tests about the interface faces are
adjusted: the graphics-loss test checks a family install_fonts binds,
and the catalogs' glyph coverage test needs `interface-fonts`.
docs/development.md says when an embedder calls `install_fonts`, and
that it replaces the context's font definitions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review feedback on #3498: dimmed neighbours share one opacity composite
before the centre tile and one after, preserving overlap paint order
without per-copy compositing.
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
## Summary
In pattern editing mode, preview copies were drawn in row-major order and the
centre tile was always composited on top, so Pattern Options Overlap had no
visible effect when negative spacing made tiles overlap (as in Illustrator).
Preview copies now paint in the same order as tiled pattern fills.
## Tests
- vectorcraft-doc: preview_paint_order_respects_overlap
Closesstorytold/vectorcraft#3480
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
Add the 13 Japanese rows the Keyboard Shortcuts dialog (File > Keyboard
Shortcuts...) and the Print dialog's overprint note were missing, so they no
longer fall back to English. Wording follows the catalog's existing terms
(初期設定, 競合, ショートカット).
Drafted with AI assistance; native-speaker review welcome.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5NRZNWfaSLBWVzV8hivyS
Exercise Selection-tool busy state before takeover and verify it is idle after tab activation, drop, Escape and outside release. Session::set_active already reconstructs the tool via reset_tool_for_doc_switch, so the reported missing-Up busy state does not reproduce and no additional synthetic release is needed.
Narrow documentation to the internal clipboard asserted by the regressions and correct the stationary-pointer sentence. All 10 cargo xtask ci gates pass.
With the Artboard tool, a Ctrl/Cmd double-click on the name of an
artboard that was not selected added it with the first click and took
it out again with the second, so the name field renamed that artboard
alone. The panel menu's Rename opened the field on the active artboard
even when it was not selected. Paste with the Artboard tool leaves it
outside the selection: it selects the pasted artboards and keeps the
active one. Rename then renamed the active artboard alone.
The second click of a Ctrl/Cmd double-click now leaves the row's
artboard selected, and the field renames the selection with it. A
Ctrl/Cmd double-click on a selected row works as before. When the
active artboard is selected, Rename opens the field on its row;
otherwise Rename opens the field on the first selected row.
Docs: docs/target-app-parity.md.
path.create read closed only when it drew from anchors. Given path
data, {"d": "M0 0 L10 0 L10 10", "closed": "true"} drew the path and
recorded an undo step, while the same closed with anchors fails with
"`closed` must be true or false". shape.rectangularGrid and
shape.polarGrid read rows, columns, concentric and radial given as text
as the count left out, so {"rows": "3"} drew the default 5 horizontal
dividers instead of 3.
path.create now checks closed before it reads d or anchors, and the
grid commands refuse such a count with "`rows` must be a number, not
"3"". Neither command changes the document or its undo history then.
With d, a closed of true, false or null still leaves the path as its
path data draws it.
Docs: docs/mcp.md, docs/target-app-parity.md.
Add the three Turkish catalog entries requested by the maintainer on #3479. Preserve upstream catalog additions and place existing tab-drag entries beside stable menu labels to avoid append conflicts. Update feature progress notes for all 24 catalogs.
Simplify an equivalent Effect Gallery selection predicate required by the Rust 1.95 clippy gate. Full cargo xtask ci passed all 10 checks, including tab-drag regressions and wasm.
## Summary
Linux file portals grey out files that do not match the open dialog's default
filter. Other Library… and Locate Library… used File › Open filters, whose
first entry is All readable files and omits swatch-library extensions such as
.ase. Add library_open_filters with the library formats first (matching
Illustrator's Swatch Libraries › Other Library…) and use it from swatch and
graphic style library pickers.
## Tests
- engine: library_open_filters_list_library_formats_first
- ui-egui: other_swatch_library_asks_off_the_ui_thread asserts Swatch libraries
is the default filter and includes ase
Closesstorytold/vectorcraft#3465
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
## Summary
Dragging the Fill or Stroke proxy onto the other square applies that paint
there, like dropping a swatch on art. Matches Illustrator's fill/stroke
chips accepting a dragged color from the other.
## Tests
- panels::tests::dropping_a_proxy_paint_on_the_other_proxy_applies_it
Closesstorytold/vectorcraft#3467
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
- Add crates/ui-egui/src/i18n/tr.tsv covering all menu strings, commands, UI labels, and status/error messages with standard Turkish vector software vocabulary.
- Register Turkish in LANGUAGES with plural_none and complete_menus: true.
- Support tr, tr-TR, tr_TR.UTF-8, tr-CY locale mapping in lang_from_tag.
- Add Turkish typographic quotation marks in crates/doc/src/setup.rs.
- Add turkish_reads_as_turkish unit test and register tr in KEEPS_MENU_NAMES and COMPLETE_MESSAGES.
- Include RTF diagnostic message with exact TSV escaping.
the_reply_names_each_expanded_copy_by_its_own_id checked that no id in
the effect.expandAppearance reply repeats and that each one names a
node in the document. Only for group copies did it check which ids the
reply holds. In the other four cases a reply that left out the
expanded object, or held no ids at all, passed.
The test now checks in every case that the reply starts with the id of
the selected object, and keeps the exact check of the group and the
two copies of the square for group copies.
ui.libraryPanel.locate checked only that the panel was open. Run on an
open panel whose library was there, such as the built-in Pastels, it
loaded the file given or picked and showed that library in the panel
in place of Pastels.
It now answers "library panel `{given}` isn't missing its library" for
such a panel, before it asks for a file or loads one, and the panel
keeps its library. A persistent panel whose library's file is gone
locates as before.
Docs: docs/mcp.md (ui.libraryPanel.locate).
Remove hash-coloured thumbnails; preview the selection with the applied
filter stack on the canvas (Cancel rolls back). OK commits via batch.
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
Limit gallery folders to the six Photoshop-style sets and edit stack params via the existing effect dialog body before apply.
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds Effect → Effect Gallery with category browsing, procedural thumbnails, and a reorderable stack applied via command.batch as separate appearance effects.
Co-authored-by: Cursor <cursoragent@cursor.com>
Follow-ups to the review of the exporter plug-ins PR (#3171):
- Export As opens its format's options once the save panel has picked
the file. The Enter that closed the panel, still held, confirmed
them at once: Save As marks such a dialog (dialogs::AFTER_PANEL) so
it waits for Enter to be let go, Export As didn't. Every options
dialog it opens after the panel is marked now, an exporter plug-in's
included, through one helper (dialogs::opened_by_panel) that Save As
uses too.
- io::export with options that aren't a JSON object dropped the path
and format it adds for the encoder: an exporter plug-in was told the
document's name and its first extension instead of the file it
writes. Such options now count as none.
- The exporter UI tests remove the plug-ins they install, so no other
test's Export As lists them.
tests_export.rs reproduces the held Enter for every options dialog
(PNG, SVG, Save PDF, Text, DXF, EPS and an exporter's), and
tests_plugin_export.rs the options that aren't an object; both failed
before the fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
object.transformEach refused reflectX, reflectY, random and copy given
as text, but read strokes, corners, objects and patterns given as text
as the key left out. With Scale Strokes & Effects on,
{"scaleH": 200, "scaleV": 200, "strokes": "false"} scaled the stroke,
recorded an undo step and kept the params for Transform Again, which
repeated the call. artboard.rearrange, artboard.move and
artboard.duplicate read their patterns option the same way, and
artboard.setProps reads its patterns, strokes and corners options the
same way.
These commands now refuse such a value with "`<key>` must be true or
false" before they change anything. So do the other options and
numbers of artboard.setProps and artboard.move, the art of Duplicate,
Copy and Cut Artboard, the shape commands and path.create, path.close,
path.setHandle, path.moveAnchors, the Pencil, Curvature, Reshape
Segment and Blob Brush options, text.create, text.createInPath,
text.setRangeStyle, text.editRange, text.getRange, text.touchType,
text.areaOptions, type.step, the font size steps,
type.opticalMarginAlignment, type.systemLayout, type.fractionalWidths,
type.ruby, the symbol and swatch commands, the Appearance panel's
commands, the gradient and freeform gradient commands, Shape Builder,
Live Paint, Image Trace, the opacity of object.setProps, the radius,
sides and relativeRadii of object.setLiveShape, the reference point of
object.setBounds and the artboard of Align, Distribute and Distribute
Spacing. They refuse a number given as text with "`<key>` must be a
number, not <value>", and a gradient stop's tint with "stop <n>:
`tint` must be a number". A null still reads as the key left out.
Docs: docs/mcp.md, docs/target-app-parity.md.
* Use chrono for UTC/calendar conversion instead of hand-rolled copies
Replaces the hand-rolled Howard Hinnant civil/days-from-civil copies and the
leap-year/days-in-month tables with chrono as a workspace dependency:
- apps/vectorcraft/src/logging.rs: timestamp (log line time)
- crates/doc/src/metadata.rs: civil (Unix seconds -> [y,m,d,h,mi,s]) and iso8601
- crates/pdf/src/file_info.rs: unix (date -> seconds) and in_month, used by the
PDF/XMP date parsers (pdf_date, xmp_date)
Output formats and parsing semantics are unchanged and pinned by the existing
tests (PDF D:... and XMP dates, offsets, leap-year rejection, year clamp 0-9999,
pre-epoch clamp). chrono uses only std, so wasm stays safe. AGENTS.md/CLAUDE.md
now say time goes through chrono.
Follow-up to storytold/cadcraft#1785
* Never panic on a clock outside chrono's range; keep 5-digit years unprefixed
chrono's From<SystemTime> unwraps, so a clock past year 262143 panicked
inside the logger. timestamp now uses the fallible DateTime::from_timestamp
and reads such a clock (or a pre-epoch one) as the epoch instead. Years past
9999 are written with {:04} like before, because %Y would add a '+'. Tests
cover the out-of-range and 5-digit-year cases.
* Don't build an unrepresentable SystemTime in the far-future test
UNIX_EPOCH + Duration::from_secs(u64::MAX / 2) overflows Windows'
SystemTime and panicked before timestamp() ran, failing the Windows
test job. Build it with checked_add and only assert where it exists.
* time: preserve old out-of-range date conversion (no epoch fallback)
`DateTime::from_timestamp(...).unwrap_or_default()` silently reset any instant
past chrono's year range to 1970-01-01. Restore the old conversion domain with
a cycle-based helper that routes the calendar through chrono (the calendar
repeats every 400 years = 146_097 days, so month/day/time come from chrono and
only the year is carried in i128, keeping every i64 exact).
- apps/vectorcraft/src/logging.rs: `timestamp` via `utc_parts`; keep the
pre-1970 epoch clamp; u64 secs past i64::MAX saturate, not epoch.
- crates/doc/src/metadata.rs: `civil` cycle-based, keeping its clamp EXACTLY
as the old code had it: the input is clamped to 0000-01-01..9999-12-31 first
and the year is clamped to 0..=9999 afterwards. (With the input clamp the old
`unwrap_or_default()` was already unreachable; this drops it and makes the
conversion explicit, with identical output.)
Pinned old outputs in tests:
logging u64::MAX/2 s -> 292277026596-12-04T15:30:07.000Z (was epoch),
10_000_000_000_000 s -> 318857-05-20T17:46:40.000Z
metadata civil(i64::MIN)[0]=0, civil(i64::MAX)[0]=9999;
iso8601(i64::MIN)=0000-01-01T00:00:00Z,
iso8601(i64::MAX)=9999-12-31T23:59:59Z (year clamp, as before)
* Don't build a year-318857 SystemTime unchecked in the logging test
Windows' SystemTime only reaches about year 30828, so
UNIX_EPOCH + Duration::from_secs(10_000_000_000_000) panicked before
timestamp() ran and failed the Windows test job. Build it with
checked_add and assert where the platform can hold it. The year-10000
case fits everywhere and stays unconditional.
* Name the 400-year range extension as the one exception to the time rule
The rule said never to hand-roll calendar math, but the range fix
(shifting by whole 400-year Gregorian cycles so dates past chrono's year
range keep their old output) is cycle arithmetic, so the rule and the code
disagreed. The rule now names that exception: the library still computes
month, day and time, and only whole cycles are carried in the year.
* Say the library computes month and day; time-of-day is plain arithmetic
Narrows the range-extension wording: the date library computes the
calendar fields, and splitting seconds into hours, minutes and seconds
is ordinary arithmetic, which is what the code does.
## Summary
Live ellipses already support pie start/end angles, canvas widgets and Invert Pie
like Illustrator. Label the Properties panel Transform section with Ellipse
Properties so the pie fields match the Transform panel and are easier to find.
## Tests
- `an_ellipse_shows_its_pie_properties` in `panels/transform` tests.
Closesstorytold/vectorcraft#3451
Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
An image whose data is empty failed to decode before the size check, so a
version 7 .ai said it couldn't be decoded. The writer now checks for empty
data first and gives "an image that is empty or too large for legacy .ai
was left out". docs/mcp.md now says the DXF decode note applies to R13 and
later; R12 leaves out every image with its own note.