The key of a raster made for content that shows a symbol includes the
font generation, and since c348b384 (#1723) the key for content
painted with a pattern includes it too. FontDb::add_font increases
that generation, and the six tests in tests_fontchange.rs call it. Two
tests in fx.rs draw such content and count the rasters moved between
frames: moved_rasters_need_the_same_content_and_effects and
a_moved_group_draws_the_shadow_of_the_instance_art_in_it_while_interacting.
Neither held fonts_steady(). When a font test added a font between
their frames, the renderer made the raster again and the test counted
0 rasters moved instead of 1. Under CPU load on Windows, with the font
tests in the same process, the first test failed in 12 of 100 runs and
the second failed in 36 of 70.
Both tests now hold fonts_steady().
translucent_blended_and_shadowed_type_redraws_when_its_font_becomes_available
added its font without the font lock that fonts_steady() holds shared,
and it now takes adding_fonts(), as the other five font tests do. With
the change, the first test passed in all 200 runs and the second
passed in all 120 runs under the same load.
file.new stamps metadata.created from the clock in whole seconds. Four
Alt-drag tests in tests_xform.rs compare the document under test with
one that a second session builds, and both sessions start from
session()'s file.new. When the clock reached a new second between the
two calls, the documents differed in created, and the test failed.
With a temporary change that made each clock read on a thread at least
one second later than the one before, all four failed.
session() in tests_xform.rs and persp_session() in
tests_persp_select.rs now pass "created": null to file.new, as #1894
did for doc_with_rect() in tests_ids_without_selection.rs. Two new
tests in #2067 compare two documents built on persp_session().
Keep Type tool from inserting Text when Cmd/Ctrl/Alt is held so the
chord outlines instead of typing, and cover Selection and Type-tool
paths so File › Open and the Artboard tool cannot steal it.
Co-authored-by: Cursor <cursoragent@cursor.com>
Transform Each had Reflect X and Reflect Y swapped relative to Effect › Distort ›
Transform and the Transform panel. Properties flip buttons also used inverted
tooltips. Align them so Horizontal is left-right and Vertical is top-bottom.
Co-authored-by: Cursor <cursoragent@cursor.com>
Found while testing crates/doc/src/lib.rs:~298 (parse_measure, picas+points branch)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found while testing crates/doc/src/lib.rs:269-297 (parse_measure, used by Unit::parse and parse_number)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The catalog holds the 4,789 rows the other complete catalogs hold: the whole
interface (every menu string and `tl!` literal) and the status and error
messages, in the Bulgarian vocabulary of vector illustration, following
Adobe Illustrator's Bulgarian conventions; one/other plural forms.
Clean-room from VectorCraft's English meaning, drafted with AI assistance
(Gemini); a native speaker has not reviewed it yet. `bg-BG` and every other
`bg-*` locale resolve here.
`cargo test -p vectorcraft-ui-egui i18n::tests` passes (46 tests, including
complete menu, literal, message and font coverage).
The Cmd-drag part of #1881 landed in #1897. This covers the two other
paths: an edit from outside the typing session (the Transform panel) and
a new interaction such as a dialog's preview now end the typing first.
With Align to Artboard chosen, the Align panel's six Distribute Objects
buttons and its two Distribute Spacing buttons acted on the selection.
object.distribute and object.distributeSpacing took no to or artboard
param. Distribute Objects kept the outermost objects where they were
and did nothing with two objects, and Distribute Spacing kept the first
object's left or top edge and the last object's right or bottom edge
where they were.
Both commands now take to ("selection", "artboard" or "key") and
artboard, and the panel sends its Align To choice as to. When to is
"artboard", the first object's left or top edge goes to the artboard's
left or top edge, and the last object's right or bottom edge goes to
the artboard's right or bottom edge. Distribute Objects spaces the
other objects evenly by its button's line, and two objects are enough.
Distribute Spacing without a spacing value makes the gaps between the
objects equal. Both commands use the same artboard as object.align.
The app passes the active artboard to both commands, and recorded
actions leave it out, so an action plays on the artboard that is
active when it plays. With "selection" or "key", or without to, both
commands work as before. Distribute Spacing with a spacing value still
keeps the key object in place with each of the three to values. With
anchor points selected, both commands still space the points among
themselves and do not use to.
Docs: docs/mcp.md, docs/target-app-parity.md.
Place flattened every top-level layer into its objects, so an SVG's clipped top-level group lost
its clip and its clipping path came in as a loose, invisible path; sublayers lost their clips the
same way. A clipping layer is now placed as one clip group, for SVG/DXF and for the art placed from
an artboard (PDF, EPS, placed documents) alike.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
every_pathfinder_operation_is_pinned built its stars with shapes::star,
which calls the platform's cos and sin. At the star angle
2.204350845268838, cos on macOS x86_64 (measured under Rosetta on
macOS 27.0.1) returns 0xbfe2f1c59f37cbf4, where the correctly rounded
value, which macOS aarch64 returns, is 0xbfe2f1c59f37cbf5. In the set
of five shapes with seed 7, this moves one star vertex by one ulp, and
the set's Unite hashes to 60181664961792917, where PINNED has
9388160212451732406. Under Rosetta, 21 of the 40 hashes differ.
The test now builds each star from a table of the correctly rounded
cosine and sine of the star's ten angles, written as f64 bit patterns,
with the arithmetic of shapes::star. PINNED is unchanged. With these
inputs, all 40 hashes equal PINNED on aarch64 and under Rosetta.
Rust 1.95 clippy -D warnings rejects the nested if under TypedInstruction::Shading
(collapsible_match). Fold the shading lookup into the match arm guard so the Ubuntu
xtask ci gate can pass on the same toolchain Actions pins.
Co-authored-by: Cursor <cursoragent@cursor.com>
Documents are now at most vectorcraft_doc::MAX_DEPTH (32) deep, so the writer's MAX_DEPTH of 256
and its warning for art nested deeper can go; symbols and live objects nested in one another keep
their limit.
The file had an empty prolog, so a PostScript interpreter stopped at its first operator: it
couldn't be printed, placed or previewed by anything that runs PostScript. The prolog's
procedure set (VectorCraft_Legacy, written from the PostScript Language Reference and the
version 7 specification) now gives every operator the writer emits its meaning: paths and their
painting with separate fill and stroke colours and overprints, the fill rule, compound paths
painted as one path, clipping groups and masks, guides that don't print, layers that are hidden
or don't print marking nothing, and images drawn from the hex samples on the comment lines after
XI. Gradients fill with the colour set before them (the first stop's), and the type and encoding
operators take their operands and mark nothing.
VectorCraft opens a file with that prolog from its program, as before: its page has flat
gradients and no type.
Every file the writer's tests write is now run through import_with(bytes, false) and must draw
what the document it was written from draws: its paths, fills, strokes and images in the same
places and colours, with type left out (type the writer outlines is compared as its glyphs'
outlines), gradients as their first stop, layers that don't print left out and hidden objects
shown when the options write them visible. A new test prints the sample with every layer shown.
The engine's flattened-image test also opens the file as PostScript.
The page trailer the version 7 specification gives (section 3, Script Setup) is
`gsave annotatepage grestore showpage`, and annotatepage is a procedure of the app's own
prolog, which the file didn't have. The file now carries a procedure set of the writer's own
(VectorCraft_Legacy 1 0, listed in %%DocumentSuppliedResources) in a dictionary the setup begins
and the trailer ends, which defines annotatepage as marking nothing.
The Legacy .ai Options dialog gains Hidden Objects (Hidden Layer, Omit, Make Visible) and Text
(Editable, Outlines), translated in every catalog. Docs: type and hidden objects in the legacy
.ai paragraphs, the restored revision rows and roadmap row.
Type is written as version 7 text objects (§15): point type, area type and type on a path, with
fonts by PostScript name re-encoded to Windows-1252, and each run's paint, size, scaling,
tracking, leading, alignment and indents; type 8-bit text can't hold (or all, with outlineText)
is outlines. The format hides only layers, so hidden objects go to a hidden layer above their
own (hiddenObjects "layer", the default), are left out ("omit") or show ("visible"). The .ai
reader reads legacy type (To … TO, TE, %AI3_BeginEncoding) and ignores Ar; the import fuzzer
gets legacy type tokens.
The .ai options dialog offers Legacy (version 7). In that mode it shows Transparent Images
(Flatten, Omit) and, with more than one artboard, thumbnails to check with the file each is
saved to (long names keep their end). OK is greyed out until an artboard is checked. A save
panel's Enter no longer confirms the options dialog it opens.
aiVersion 7 saves through the legacy writer, one file per artboard; range picks the artboards
and document.save writes them again. Appearances are expanded first. Transparent images
(see-through pixels, opacity or a blending mode) are flattened with the art under them into
opaque images clipped to where they change it (transparentImages "flatten", the default), or
left out ("omit"); both warn.
export_legacy_ai writes a document as a PostScript-form .ai of version 7 (one artboard per
file), keeping every layer (name, order, Hidden, Locked) and its groups, compound paths and
clip groups. Closed paths end on their start point, so they keep their corner. The .ai reader
learns what the writer emits, and the tests read every file back.
Drag out from a segment, corner, circle or arc to place a dimension; plain clicks pick two points or two lines. Radial dimensions on perfect arcs, picas, Away/Between and Left/Center/Right text placement. The Properties panel edits a selected dimension, with Apply to All (dimension.applyToAll) and Set as Default. Dimension Selected Paths, the Arc/R/Ø prefixes and the custom defaults are left out. The refresh returns at once in documents without dimensions (Document::dimensions); a linked dimension moved on its own keeps its place; modes, units, line types, arrows and text placement are enums; the tool has its own icon.
A pen reports far more often than frames come (an Apple Pencil up to 240 times
a second): the canvas now hands the tool each move of the frame with its
pressure instead of the last one only, and the web shell feeds Safari's
coalesced pen events. Mouse input is unchanged.
Read pen and finger Pointer Events in the web shell (capture phase) instead of
eframe's touch handlers, which can't tell a pen from a finger and see every
touch twice. The pen drives the pointer with its pressure and hovers; fingers
are touch contacts for pinch, two-finger pan and undo/redo taps. Once a pen
has been seen (remembered in localStorage), a finger or a resting palm never
presses a tool on the canvas, and a pen landing cancels fingers already down.
A long press drops the tool press and opens the context menu.
The page no longer scrolls, zooms or selects under touch, and the canvas
stays inside the safe areas of a full-screen app.
Use automatic vector smooth shading without a separate control.
Enable Cap by default and keep continuous rim curves and bounded seam joins.
Integrate the shared dialog with the other three 3D effects.