The reference app keeps the inner radius of a star constant when Ctrl
or Command is held after the drag starts. VectorCraft's Star tool took
the held radius from the first drag event that carried the key, using
that event's outer radius. The canvas sends a drag only once the
pointer moves, so a key pressed with the pointer still scaled the
inner radius with the next move before holding it.
Ctrl/Cmd now holds the inner radius the star showed at the last drag
position before the key went down, with Alt's straight shoulders when
Alt was held. A new press and Escape clear the held radius. Before,
after a drag canceled with Escape, the next star took the canceled
star's inner radius when Ctrl/Cmd went down before that star's first
drag event.
Docs: docs/target-app-parity.md.
Escape did not cancel a drag with the Anchor Point, Mesh, Shape Builder
and Measure tools, or a Direct Selection drag of a mesh point, a corner
widget or the brackets of type on a path: the drag went on until the
mouse was released. Escape cancels every other Direct Selection drag
and a drag with the Free Transform, Rotate, Reflect, Scale, Shear and
Gradient tools.
Escape now cancels an Anchor Point drag, a Mesh tool drag of a mesh
point or handle, and a Direct Selection drag of a mesh point or handle,
a corner widget or the brackets of type on a path. The art goes back to
how the press found it, no undo step is added, and the rest of the drag
and the release send nothing. Escape during a Shape Builder drag ends
it, so the release merges and deletes nothing; the tool takes Escape
only during the drag. Escape during a Measure tool drag ends it and
brings back the measurement shown before the press. Escape after an
Anchor Point or Direct Selection press that has not moved yet drops the
press. Otherwise, outside a drag, Escape does what it did before.
Docs: docs/mcp.md, docs/target-app-parity.md.
drop_beyond_canvas removed objects but left what names them: saved
selections, Asset Export entries and variable bindings kept their ids,
and layer guides of a removed sublayer named a layer that was gone. It now
runs tidy_saved_selections, prune_assets and prune_variable_bindings when
it removes objects, as edits do, and removes the layer guides of the
layers it removed.
A layer's bounds leave out its hidden objects and a clipping container's
are its clipping path's, so objects beyond the canvas inside them were
kept. Layers and groups are now looked into object by object.
A clipping path beyond the canvas goes with the objects it clips, so no
other object becomes the clip; a top-level clipping layer is emptied and
stops clipping. A removed artboard's ruler origin goes and its id is
retired, as deleting it does.
Edits refuse to leave anything beyond MAX_COORD (doc_sane checks the
artboards, the guides, the ruler origin and the objects an edit touches),
but the native loader didn't check them. A file with one object, artboard,
guide or ruler origin out there opened without a warning, and then every
move failed with "result would exceed the canvas", in-range objects
included; saving kept it that way.
Document::drop_beyond_canvas removes objects whose geometric bounds aren't
finite or reach past MAX_COORD (a layer or non-clipping group keeps its
children within it), artboards with their artboard guides, and guides out
there, and puts the ruler origin back at the origin. load_file runs it
after the depth limit and reports what it did with the other load notes; a
file whose artboards all lie beyond the canvas is refused.
NodeKind's reader buffers the fields that come before `type` as
serde_json::Values. VectorCraft writes `type` first, but a crafted file
can put a long array there: a group whose children held 50M numbers before
its type (a 96 KB gzip) peaked at 1.6 GB, and the cost grows with the
array up to MAX_JSON, before any object in it is counted.
The buffer is read through a counting visitor: each JSON value counts
against MAX_BUFFERED_VALUES (32M, 4M on wasm32) as it is read, and values
replayed from the buffer aren't counted twice. load_file reads within it;
the same file is now refused at a 1.1 GB peak.
Opening a native document now reads at most MAX_READ_NODES objects (4M,
512K on wasm32) and refuses the file past that with a message naming the
budget. Paths already read within MAX_READ_ANCHORS and the web build's JSON
cap is 256 MB, but the number of objects had no bound: a gzip'd document of
empty groups costs about 270 bytes per object once read against some 50 in
its JSON, so 10M groups in a 24 MB file took 2.7 GB to open.
NodeWire's id is read through counted_id, so each object counts once as it
is read. load_file reads through a private load_file_within so a test can
exercise the budget without a 4M-object document.