When moving type or a symbol instance in perspective makes the
conjugated projection overflow, Homography::conjugated now forms the
product from factors scaled to a largest entry of 1 and returns it at
that scale, which maps every point to the same place. When even the
scaled result is not invertible, the transform removes the projection
and the art is drawn flat, instead of keeping the old projection while
the flat art moves.
A colorized grayscale or 1-bit image draws its white pixels transparent and is written to
PDF with the image as a soft mask. Node::shows_transparency did not count it, so a PDF 1.3,
PDF/X-1a or PDF/X-3 export whose only transparency was a colorized image was not flattened
and kept the soft mask. A legacy .ai save also wrote such an image as opaque.
- shows_transparency counts a visible image with a colorize paint.
- The legacy .ai writer flattens or leaves out colorized images like other see-through images.
Envelope Options stored Anti-Alias and Preserve Shape Using, but nothing that
draws an image read them: a distorted image was always smoothed and always
cut into clip groups.
- With Anti-Alias off, each piece of a distorted image is marked pixelated
(saved as `pixelated`). The renderer draws it with nearest-neighbor
sampling and without mips, SVG export writes `image-rendering: pixelated`,
and PDF export writes it without /Interpolate.
- With Preserve Shape Using Transparency, each piece is the image with a
white opacity mask in the piece's shape instead of a clip group. The
renderer paints an image whose mask is one plain white path straight into
that path, as it does for an image in a clip group, when the path lies
inside the image's frame.
Docs: docs/mcp.md, docs/target-app-parity.md.
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.