blob: 8eace85b520735a00ec3d8a3fa8993eeb3b7999f [file] [log] [blame]
// The schema exported by this module (as SCHEMA) has the following structure:
// {
// key1: Annotator,
// key2: Annotator,
// ...
// keyN: Annotator,
// }
// The above structure is known as a AnnotationMap.
// where each key is a key that could exist in the lottie object
// and Annotator is an Object that looks like:
// {
// _annotation: String|Function(val) the annotation for this node,
// or a function that returns a dynamic annotation based
// on the value of the node. For example, this allows
// the blend mode or 'bm' key to have an annotation of
// 'normal' if the associated value is 0, or something else
// for a non-zero value.
// _is_array: [optional] Boolean if this node represents an array.
// _children: [optional] AnnotationMap of any child keys.
// }
// or Annotator is a Function(val) that returns the above Object based
// on the value of the node. For example, this allows for a different
// set of annotations if a transform property is animated or not.
// This Schema is not complete, but full enough to show off the dynamic
// behavior that is involved in the lottie format.
// A little helper function to create an object with _annotation
// containing the value (either String or function(value) => String)
function leaf(v) {
return {
_annotation: v,
}
}
const SHAPES = {
'0': 'Precomputed',
'1': 'Solid',
'2': 'Image',
'3': 'Null',
'4': 'Shape',
'5': 'Text',
}
const BLEND_MODE = {
'0': 'normal',
'1': 'multiply',
'2': 'screen',
'3': 'overlay',
'4': 'darken',
'5': 'lighten',
'6': 'colorDodge',
'7': 'colorBurn',
'8': 'hardLight',
'9': 'overlay',
'10': 'difference',
'11': 'exclusion',
'12': 'hue',
'13': 'saturation',
'14': 'color',
'15': 'luminosity',
}
const flatColor = {
'a': leaf('Is animated flag'), // should be 0
'ix': leaf('Index. Used for expressions'),
'x': leaf('AE expression that modifies the value'),
'k': leaf('Components (R, G, B, A)'),
}
const fillShape = {
'c': {
'_annotation': 'Fill Color',
'_children': flatColor, // TODO(kjlubick): Does this need to be keyframed?
},
'hd': leaf('Hidden'),
'mn': leaf('Match Name. Used for expressions'),
'nm': leaf('Name. Used for expressions'),
'o': {
'_annotation': 'Opacity',
'_children': {
'a': leaf('Is animated flag'),
'ix': leaf('Index. Used for expression'),
'x': leaf('AE expression that modifies the value'),
'k': leaf('Opacity Value'),
},
},
'r': leaf('Unknown'),
'ty': leaf('Shape Type - fill'),
}
const mergeShape = {
'hd': leaf('Hidden'),
'mm': leaf('Unknown'),
'mn': leaf('Match Name. Used for expressions'),
'nm': leaf('Name. Used for expressions'),
'ty': leaf('Shape Type - merge'),
}
const genericShape = {
'ind': leaf('Unknown'),
'hd': leaf('Hidden'),
'd': leaf('Direction of how the shape is drawn'),
'ix': leaf('Index. Used for expressions'),
'mn': leaf('Match Name. Used for expressions'),
'nm': leaf('Name. Used for expressions'),
'np': leaf('Group number of properties. Used for expressions'),
'ty': leaf('Shape Type - Shape Content'),
'ks': {
'_annotation': 'Shape Vertices',
// TODO(kjlubick): flesh this out more
},
}
const shapeGroup = {
'cix': leaf('Unknown'),
'hd': leaf('Hidden'),
'ix': leaf('Index. Used for expressions'),
'mn': leaf('Match Name. Used for expressions'),
'nm': leaf('Name. Used for expressions'),
'np': leaf('Group number of properties. Used for expressions'),
'ty': leaf('Shape Type - Shape Group'),
'it': {
'_annotation': 'List of items in group',
'_is_array': true,
'_children': shapeAnnotator,
}
}
const transformShape = {
'nm': leaf('Name. Used for expressions'),
'ty': leaf('Shape Type - Transform'),
// TODO(kjlubick)
}
function shapeAnnotator(val) {
switch(val.ty) {
case 'fl':
return fillShape;
case 'gr':
return shapeGroup;
case 'mm':
return mergeShape;
case 'sh':
return genericShape;
case 'tr':
return transformShape;
default:
return leaf('UNKNOWN shape type');
}
}
const flatCoordinate = {
'a': leaf('Is animated flag'), // should be 0
'ix': leaf('Index. Used for expressions'),
'x': leaf('AE expression that modifies the value'),
'k': leaf('Coordinates (X, Y, Z)'),
}
const animatedCoordinate = {
'a': leaf('Is animated flag'), // should be 1
'ix': leaf('Index. Used for expressions'),
'x': leaf('AE expression that modifies the value'),
'k': {
_annotation: 'Keyframes',
'_is_array': true,
'_children': {
'e': leaf('End value of keyframe segment'),
'i': leaf('Bezier curve interpolation in value'),
'n': leaf('Bezier curve name. Used for caching'),
'o': leaf('Bezier curve interpolation out value'),
's': leaf('Start value of keyframe segment'),
't': leaf('start time of keyframe'),
}
}
}
function coordinateAnnotator(val) {
if (val.a == '1') {
return animatedCoordinate;
}
return flatCoordinate;
}
const transformAnnotator = {
'a': {
'_annotation': 'Anchor Point',
'_children': coordinateAnnotator, // or keyframed version
},
'p': {
'_annotation': 'Position',
'_children': coordinateAnnotator, // or keyframed version
},
'px': leaf('Position X'), // Likely used instead of 'p'
'py': leaf('Position Y'),
'o': {
'_annotation': 'Opacity',
'_children': {
'a': leaf('Is animated flag'),
'ix': leaf('Index. Used for expression'),
'x': leaf('AE expression that modifies the value'),
'k': leaf('Opacity Value'),
},
},
'r': {
'_annotation': 'Rotation',
'_children': coordinateAnnotator,
},
's': {
'_annotation': 'Scale',
'_children': coordinateAnnotator,
},
}
// TODO(kjlubick): break this apart to allow for nested objects
// Most of this data comes from https://github.com/airbnb/lottie-web/blob/699eb219f32a80718e8dc57ba18848fe2250d165/docs/json/animation.json
// or was other-wise reverse-engineered.
export const SCHEMA = {
'ddd': leaf('3d layer flag'),
'fr': leaf('Frame Rate (hz)'),
'h': leaf('Animation Height'),
'ip': leaf('In Point of the Time Ruler. Sets the initial Frame of the animation'),
'nm': leaf('Animation Name'),
'op': leaf('Out Point of the Time Ruler. Sets the final Frame of the animation'),
'v': leaf('Version of extension that created this animation'), // TODO(kjlubick): Do we need different schemas for different versions?
'w': leaf('Animation Width'),
'assets': {
'_annotation': 'source items that can be used in multiple places. E.g. Images'
},
'chars': {
'_annotation': 'source characters for text layers'
},
'layers': {
'_annotation': 'List of Composition Layers',
'_is_array': true,
'_children': {
'ao': leaf('Auto-Orient along path flag'),
'ip': leaf('In Point of layer. Sets the initial frame of the layer'),
'op': leaf('Out Point of layer. Sets the final frame of the layer'),
'st': leaf('Start Time of layer. Sets the start time of the layer'),
'sr': leaf('Layer Time Stretching (multiplier)'),
'ddd': leaf('3d layer flag'),
'ind': leaf('Layer index in AE. Used for parenting and expressions'),
'nm': leaf('Layer Name. Used for expressions'),
'bm': leaf((text) => {
let s = BLEND_MODE[text] || 'UNKNOWN/INVALID';
return `Blend Mode: ${s}`
}),
'ty': leaf((text) => {
let s = SHAPES[text] || 'UNKNOWN/INVALID';
return `Type of Layer: ${s}`
}),
'ks': {
'_annotation': 'Transform properties',
'_children': transformAnnotator,
},
'shapes': {
'_annotation': 'List of Shapes',
'_is_array': true,
'_children': shapeAnnotator,
}
}
}
}