- A VectorCraft or PhotoCraft `generator`, and a VectorCraft `preview` or
`pdf`, are read as they are parsed. A value of the wrong shape is
skipped element by element with `IgnoredAny` and none of it is kept;
before, serde's untagged enum buffered the whole value first, with no
limit on that buffer. The preview and PDF still read as `File::read`
reads them, the array form of `Image` included.
- A rendition after the JSON of a gzip file is counted against
`max_total` once, as it is decompressed. A 40 KiB PDF with `max_total`
at 64 KiB now reads compressed as it does uncompressed. A preview and
PDF that name the same bytes are counted as two copies.
- A repeated VectorCraft `version` is malformed, not too new, and
`renditions` refuses a missing or repeated `document`, as `File::read`
does.
- The README says that a VectorCraft file written as a JSON array is not
recognized by `sniff`.
- pcraft_layer_rgba builds a name-to-entry map of the bundle once per
call instead of walking the central directory for every tile.
- A tile that several references name is read, decoded and counted
against max_total once per call. A hash referenced at two pixel sizes
is malformed.
- Surfaces stored as RGB or grayscale are read in documents of every
mode, so bitmap, duotone, indexed and multichannel documents read. A
CMYK or Lab layer or mask gives Unsupported, and an RGB mask gives its
first channel.
- A canvas of zero width or height is malformed.
- The docs name video layers, the masks of layers without pixels, and
tile references counting against max_items.
- doc.yml runs the tests without features again, next to
--all-features.
pcraft_layer_rgba(bytes, layer, &Limits) decodes one layer of a .pcraft
bundle over the canvas: a raster layer's surface, or the rendered copy
saved with a text, shape or smart object layer, as RGBA at the layer's
8, 16 or 32-bit sample type, with the layer mask as its own plane.
Grayscale repeats the gray value, missing tiles hold the default pixel,
and tiles outside the canvas are not read. Other color modes give
Unsupported; groups, adjustment and fill layers give None.
Tiles are read with craft-pcraft-format's TileRef and tile paths,
decoded with ruzstd and checked against their BLAKE3 hash, as PhotoCraft
reads them. The decoded layer and mask count against max_entry and
max_total before they are allocated; a frame is decoded only up to its
tile's size, and a frame larger than the tile plus an eighth and 4 KiB,
or one that declares a window over 8 MiB, is refused.
The feature adds ruzstd and blake3, both pure Rust. The doc workflow
runs clippy and the tests with --all-features.
Part of #59.
- renditions checks where the last VectorCraft member after the JSON
ends against max_total before it inflates anything past the marker,
and keeps only the members' bytes, not the data before them.
- A ZIP archive whose manifest.json has no format_version gives
Unrecognized instead of Malformed.
- A PhotoCraft manifest that is a JSON array is refused, as
read_manifest refuses it.
- renditions and outline refuse a PhotoCraft manifest nested past
PhotoCraft's limit (manifest_depth(MAX_GROUP_DEPTH), 367 levels).
- The TooNew check reads only the format's own version member, so a
newer file that also has another format's member is TooNew, not
Malformed.
- A generator that is not a string reads as None. A repeated VectorCraft
generator, preview or pdf reads as its last value, as File::read
reads it.
- The VectorCraft marker must follow the JSON's closing }, ] or ",
through craft_vectorcraft_format::marker_at.
- sniff reads the names in a whole archive's central directory, which
info reads too; a head too short to reach it still uses the local
headers.
* Add craft-doc: read PhotoCraft, VectorCraft and EffectCraft documents without the apps
A new crate, craft-doc, reads the native files of three apps from bytes:
PhotoCraft .pcraft (a ZIP bundle), VectorCraft .vectorcraft (JSON, plain
or gzip, with the version 4 data after the JSON) and EffectCraft .ecproj
(JSON).
- sniff tells which app wrote a file from its first bytes.
- info gives the format version, the writer the file names, and whether
the version is one the crate reads.
- renditions returns VectorCraft's preview and PDF and PhotoCraft's
thumbnail and composite, when the file has them.
- outline returns PhotoCraft's canvas and layer tree, VectorCraft's
artboards and top-level layers, and EffectCraft's compositions with
their layers.
A file whose version is newer than the crate reads is refused with
Error::TooNew. Limits bound the JSON, each entry, the total bytes
decompressed and the JSON nesting, and each is checked before the bytes
it covers are allocated. ZIP entries are read by a small STORE and
DEFLATE reader in the crate. The crate depends on serde, serde_json,
thiserror and flate2 and builds for wasm32-unknown-unknown.
The tests build every document they read: JSON written by hand, PNG,
ZIP and gzip bytes assembled in the test. doc.yml runs fmt, clippy, the
tests and a wasm32 check.
* ci: do not keep the checkout token in the doc workflow
The checkout step sets persist-credentials: false, as jpeg.yml and media.yml do. No later step in the job uses the token.
* craft-doc: read each format's envelope through its format crate
craft-doc no longer keeps its own copies of the envelope types and
constants. It reads them with craft-pcraft-format (`MANIFEST`,
`FORMAT_VERSION`, `THUMB`, `COMPOSITE`, `check_manifest`),
craft-vectorcraft-format (`Head`, `Image`, `sniff`, `check`, `VERSION`,
`BLOB_MAGIC`, base64) and craft-ecproj-format (`Head::read`,
`SCHEMA_VERSION`). Its own base64 module is removed. A `.pcraft`
manifest is still read in one pass without building a
`serde_json::Value`, with the checks `read_manifest` applies. The
outlines are still read with craft-doc's read-only views of each
document, and the public API (`sniff`, `info`, `renditions`, `outline`,
`Limits`) does not change.
These inputs are now read as the format crates read them:
- A `.pcraft` manifest without `generator`, or with a `thumbnail` other
than `thumb.png` or a `composite` other than `composite/preview.png`,
is malformed for `renditions` and `outline`; `info` still reports its
version. A manifest with more than 4,194,304 JSON items is refused
with `LimitExceeded`.
- A `.vectorcraft` preview or PDF member without `mime` gives no
rendition.
- `sniff` takes `format` only from a top-level member, so
`{"meta":{"format":"vectorcraft"}}` is not a VectorCraft file.
tests/envelopes.rs writes a file of each format with the crates' types
and reads it through every call, and checks the inputs above.
doc.yml also runs when one of the three crates changes.
* craft-doc: sniff reads only top-level members of an EffectCraft project
`sniff` took any JSON object whose first member is `savedBy` for an
EffectCraft project when `"schema"` appeared anywhere in its first
bytes, so `{"savedBy":"1.0","metadata":{"schema":1}}` was claimed. It
now checks the top-level member names only, one at a time, without
keeping them.
* craft-doc: bound list elements with max_items; no rendition without a MIME type
Limits gains max_items (default 1,048,576): EffectCraft project items and
composition layers, VectorCraft artboards and layers, and PhotoCraft layers
at every level are counted before each one is read, and a file that lists
more gives Error::LimitExceeded. A list of empty objects within max_json no
longer expands into gigabytes of layers.
A VectorCraft preview or pdf member with an empty mime gives no rendition, as
one without a mime does; a preview may be PNG or JPEG. An EffectCraft
composition without a duration or a frame rate gets no timing instead of a
duration of 0.