Files
craft-libs/crates/lottie/README.md
Dylan M. Taylor bd79e32630 craft-lottie: the document model, with a bounded reader and a writer (#938)
`craft_lottie::model` is the Lottie document as the file has it: one
struct per object of the lottie-spec 1.0 object model (animation, assets,
layers by type, transform, masks, effects, shape items, text, markers,
fonts, glyphs), one field per key. `Animation::read` reads within
`Limits` (bytes, nesting, layers, assets, shape items); `to_json` writes.

Keys the model does not name, values of another type than expected, and
layers and shape items of an unknown type stay in `extra` and are written
back, so a document survives a read and a write. Animatable properties
stay JSON (`Animated`) and are read with `read_prop`.
2026-11-11 18:47:08 -05:00

5.7 KiB
Raw Permalink Blame History

craft-lottie

Lottie (Bodymovin) JSON: the document as the file has it, and the encodings its values use. Bytes in, a neutral model out, and back. Which layer, shape, effect and text feature becomes what in an application's document is the application's work.

No file access: callers pass bytes, so the crate builds for wasm32.

The document model

Animation::read(bytes) gives the document; to_json and to_vec write it. The model is the lottie-spec 1.0 object model, one struct per object and one field per key:

  • Animation: version, frame rate, frame range, size, assets, layers, markers, fonts, chars, meta.
  • Asset: an image (dir, path, embedded) or a precomposition (layers).
  • Layer: the keys every layer has (name, index, parent, timing, blend mode, transform, masks, effects, matte keys) and content, by ty: Precomp, Solid, Image, Null, Shape, Text, or Other for a type the model has no struct for.
  • Shape: the keys every shape item has and kind, by ty: group, rectangle, ellipse, polystar, path, fill, stroke, gradient fill and stroke, transform, trim, round corners, repeater, merge, pucker and bloat, twist, zig-zag, offset path, or Other.
  • Transform, Mask, Effect (an effect and its values), Marker, Fonts, Font, Char.
  • TextData: the documents over time (TextDocument), range animators (TextRange, TextSelector, TextStyle), alignment and path options.

Every field is an Option: files leave keys out, and a writer sets what it needs. An animatable property is an Animated, the property's JSON; Animated::read gives its value or keyframes (see below). What a value means depends on the property, so the model does not decode values.

Nothing a file holds is dropped because the model does not know it. A key the struct does not name, and a key whose value has another type than the struct expects, stay in the struct's extra and are written back. A layer or shape of an unknown ty keeps its ty and its keys. So a document read and written back is the same JSON, with three exceptions: an entry of a list that is not an object where objects are expected is left out, a whole number written 30.0 comes back 30, and a flag written 1 or 0 where the schema has a boolean comes back true or false.

Limits bounds what is read: bytes of JSON (256 MiB), nesting (128 levels, which is also where serde_json stops), layers of the document and its precompositions together (100,000), assets (20,000) and shape items (2,000,000). Passing one is LottieError::TooLarge. No reader panics on any input.

The encodings

These work on serde_json values, for the values the model leaves as JSON.

Part Read Write
Document JSON and header (w, h, fr, ip, op, nm) parse_document, Header::read the application builds the JSON
Animatable property ({a, k, x}) and keyframes read_prop write_prop, write_keys
Ease of a segment (o, i) and speed / influence Tangents::read, to_speed Tangents::from_speed
Bezier path ({c, v, i, o}) Bezier::from_json Bezier::to_json
Gradient stop list (g.k) Gradient::from_flat Gradient::to_flat
Numbers, colours, #rrggbb nums, color, hex_color round6, to_hex
Embedded images (data: URIs, RFC 4648 base64) base64::data_uri base64::to_data_uri, image_mime

Not here: dotLottie (.lottie), which is a ZIP archive around the same JSON, and layer styles (sy), which lottie-spec 1.0 does not define (they stay in a layer's extra).

What the readers accept

Files in the wild come from many Bodymovin versions and other exporters, so the readers are tolerant where players are:

  • a property that is a bare value ("o": 100), a property without a whose k is a key list, and a key value wrapped once more ([[x, y]]);
  • keys before 5.0: a segment ends with e and the last key has no s; the value of a key is then the e of the key before;
  • ease tangents whose x and y are a number or a list, per dimension; a missing x is a third of the way and a missing y is linear;
  • a path without c: Bodymovin 3.x and early 4.x keep the flag outside the path (cl on a mask, closed on an sh item), so the caller passes it; tangent lists shorter or longer than the vertex list;
  • colours written 0..=255 instead of 0..=1;
  • a gradient list without alpha pairs, and a colour count larger than the list.

Nothing is indexed by a number from the file, and no reader panics.

Ease

A Lottie keyframe holds the start value of a segment and the segment's normalised cubic Bezier ease: o (out of this key) and i (into the next), each with x (time fraction) and y (value progress fraction), per dimension. After Effects' temporal ease is speed (units per second) and influence (fraction of the segment). For a segment of duration d and value change Δv:

o.x = influence_out          o.y = speed_out · influence_out · d / Δv
i.x = 1 − influence_in       i.y = 1 − speed_in · influence_in · d / Δv

Tangents::from_speed and Tangents::to_speed are this formula and its inverse. A spatial property eases along its motion path: one dimension, with Δv the arc length, which the caller computes.

Sources and clean room

Implemented from the public Lottie format description (the lottie-spec 1.0 schema and the LottieFiles format documentation); no player or exporter source was used. The encodings come from EffectCraft's effectcraft-lottie crate; the document model is written here from the same description. The four fixtures in tests/fixtures are hand-written JSON (original work), not exported from any product.