@flighthq/scene2d-formats 0.3.0 → 0.3.1-next.320.fe56fc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lottieDocument.d.ts.map +1 -1
- package/dist/lottieDocument.js +244 -34
- package/dist/lottieDocument.js.map +1 -1
- package/dist/riveScene2D.js +7 -4
- package/dist/riveScene2D.js.map +1 -1
- package/dist/riveShapePath.d.ts +2 -2
- package/dist/riveShapePath.d.ts.map +1 -1
- package/dist/riveShapePath.js +14 -3
- package/dist/riveShapePath.js.map +1 -1
- package/dist/svgDocument.js +15 -10
- package/dist/svgDocument.js.map +1 -1
- package/package.json +21 -21
- package/src/lottieDocumentConformance.test.ts +205 -6
- package/src/riveShapePath.test.ts +41 -1
- package/src/svgDocumentConformance.test.ts +34 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lottieDocument.d.ts","sourceRoot":"","sources":["../src/lottieDocument.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"lottieDocument.d.ts","sourceRoot":"","sources":["../src/lottieDocument.ts"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EAEV,aAAa,EAOb,gBAAgB,EAKhB,cAAc,EACd,2BAA2B,EAC3B,0BAA0B,EAsB3B,MAAM,0BAA0B,CAAC;AAKlC,wBAAgB,kCAAkC,CAAC,IAAI,EAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAQpG;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,EACzC,WAAW,CAAC,EAAE,gBAAgB,EAAE,EAChC,OAAO,CAAC,EAAE,QAAQ,CAAC,2BAA2B,CAAC,GAC9C,0BAA0B,CAsC5B"}
|
package/dist/lottieDocument.js
CHANGED
|
@@ -4,8 +4,8 @@ import { packColor } from '@flighthq/color/contract';
|
|
|
4
4
|
import { easeCubicBezier } from '@flighthq/easing/contract';
|
|
5
5
|
import { createGradientTransformMatrix } from '@flighthq/geometry/contract';
|
|
6
6
|
import { reportImportDiagnostic } from '@flighthq/importdiagnostics/contract';
|
|
7
|
-
import { addNodeChild } from '@flighthq/node/contract';
|
|
8
|
-
import { appendPathCubicCurveTo, appendPathEllipse, appendPathLineTo, appendPathMoveTo, appendPathPolygon, appendPathRectangle, appendPathRoundRectangle, createPath, dashPath, getPathLength, } from '@flighthq/path/contract';
|
|
7
|
+
import { addNodeChild, invalidateNodeLocalTransform } from '@flighthq/node/contract';
|
|
8
|
+
import { appendPathCubicCurveTo, appendPathEllipse, appendPathLineTo, appendPathMoveTo, appendPathPolygon, appendPathRectangle, appendPathRoundRectangle, createPath, dashPath, getPathLength, reversePath, } from '@flighthq/path/contract';
|
|
9
9
|
import { applyAnimationClipToNode2D, createSprite, createDisplayObject } from '@flighthq/scene2d/contract';
|
|
10
10
|
import { appendShapeBeginFill, appendShapeBeginGradientFill, appendShapeEndFill, appendShapeLineGradientStyle, appendShapeLineStyle, appendShapePath, clearShapeCommands, createShape, } from '@flighthq/shape/contract';
|
|
11
11
|
import { createTextLabel } from '@flighthq/text/contract';
|
|
@@ -20,7 +20,7 @@ export function applyAnimationClipToLottieDocument(clip, time) {
|
|
|
20
20
|
if (target === null || typeof target !== 'object' || target.lottieApply === undefined)
|
|
21
21
|
continue;
|
|
22
22
|
sampleAnimationTrack(_sampleScratch, channel.track, time);
|
|
23
|
-
target.lottieApply(_sampleScratch);
|
|
23
|
+
target.lottieApply(_sampleScratch, time);
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
@@ -77,10 +77,16 @@ function appendLottieLayers(root, layers, context) {
|
|
|
77
77
|
}
|
|
78
78
|
function createLottieLayerNode(layer, context) {
|
|
79
79
|
const container = createDisplayObject({ name: layer.nm ?? null });
|
|
80
|
-
|
|
80
|
+
const hidden = layer.hd === true;
|
|
81
|
+
// A hidden layer still contributes its spatial transform when another layer parents to it. Its
|
|
82
|
+
// opacity, visibility window, paint, and masks affect only its own content, so none may leak onto
|
|
83
|
+
// the referencing child through Flight's hierarchy.
|
|
84
|
+
applyLottieTransform(container, layer.ks, context, !hidden, layer.ao === 1);
|
|
85
|
+
reportLottieExpression(layer.ks, context);
|
|
86
|
+
if (hidden)
|
|
87
|
+
return container;
|
|
81
88
|
applyLottieLayerVisibility(container, layer, context);
|
|
82
89
|
applyLottieBlendMode(container, layer, context);
|
|
83
|
-
reportLottieExpression(layer.ks, context);
|
|
84
90
|
if (layer.ty === 0)
|
|
85
91
|
appendLottiePrecomposition(container, layer, context);
|
|
86
92
|
else if (layer.ty === 1)
|
|
@@ -102,7 +108,7 @@ function createLottieLayerNode(layer, context) {
|
|
|
102
108
|
applyLottieMasks(container, layer.masksProperties ?? [], context);
|
|
103
109
|
return container;
|
|
104
110
|
}
|
|
105
|
-
function applyLottieTransform(target, transform, context) {
|
|
111
|
+
function applyLottieTransform(target, transform, context, includeOpacity = true, autoOrient = false) {
|
|
106
112
|
if (transform === undefined)
|
|
107
113
|
return;
|
|
108
114
|
if (isSeparatedPosition(transform.p)) {
|
|
@@ -118,10 +124,13 @@ function applyLottieTransform(target, transform, context) {
|
|
|
118
124
|
// pass through unconverted. The radians live below the seam, where nodeTransform2d applies
|
|
119
125
|
// DEG_TO_RAD.
|
|
120
126
|
applyScalarProperty(target, transform.r ?? transform.rz, 'Rotation', (value) => value, context);
|
|
121
|
-
|
|
127
|
+
if (includeOpacity)
|
|
128
|
+
applyScalarProperty(target, transform.o, 'Alpha', (value) => value / 100, context);
|
|
122
129
|
if (transform.sk !== undefined) {
|
|
123
130
|
applyScalarProperty(target, transform.sk, 'SkewX', (value) => value, context);
|
|
124
131
|
}
|
|
132
|
+
if (autoOrient)
|
|
133
|
+
appendLottieAutoOrientation(target, transform.p, transform.r ?? transform.rz, context);
|
|
125
134
|
}
|
|
126
135
|
function applyVectorProperty(target, property, vectorPath, scalarPaths, components, convert, context) {
|
|
127
136
|
if (property === undefined)
|
|
@@ -133,7 +142,7 @@ function applyVectorProperty(target, property, vectorPath, scalarPaths, componen
|
|
|
133
142
|
appendNumericPropertyChannels(property, components, (component) => ({
|
|
134
143
|
node: target,
|
|
135
144
|
path: component === null ? vectorPath : scalarPaths[component],
|
|
136
|
-
}), convert, context);
|
|
145
|
+
}), convert, context, vectorPath === 'Position');
|
|
137
146
|
}
|
|
138
147
|
function applyScalarProperty(target, property, path, convert, context) {
|
|
139
148
|
if (property === undefined)
|
|
@@ -143,20 +152,112 @@ function applyScalarProperty(target, property, path, convert, context) {
|
|
|
143
152
|
return;
|
|
144
153
|
appendNumericPropertyChannels(property, 1, () => ({ node: target, path }), convert, context);
|
|
145
154
|
}
|
|
146
|
-
function appendNumericPropertyChannels(property, components, target, convert, context) {
|
|
155
|
+
function appendNumericPropertyChannels(property, components, target, convert, context, spatialTangents = false) {
|
|
147
156
|
if (!isAnimatedProperty(property))
|
|
148
157
|
return;
|
|
149
158
|
const keyframes = property.k;
|
|
150
159
|
if (keyframes.length === 0)
|
|
151
160
|
return;
|
|
152
|
-
const
|
|
161
|
+
const spatial = spatialTangents && hasSpatialTangents(keyframes);
|
|
162
|
+
const componentSpecific = !spatial && hasComponentSpecificEasing(keyframes, components);
|
|
153
163
|
if (componentSpecific && components > 1) {
|
|
154
164
|
for (let component = 0; component < components; component++) {
|
|
155
165
|
context.channels.push(createAnimationChannel(createLottieTrack(keyframes, 1, context, (value) => [convert(numericValue(value, components)[component], component)], component), target(component)));
|
|
156
166
|
}
|
|
157
167
|
return;
|
|
158
168
|
}
|
|
159
|
-
context.channels.push(createAnimationChannel(createLottieTrack(keyframes, components, context, (value) => numericValue(value, components).map(convert), 0), target(null)));
|
|
169
|
+
context.channels.push(createAnimationChannel(createLottieTrack(keyframes, components, context, (value) => numericValue(value, components).map(convert), 0, spatial, (value, component) => convert(value, component) - convert(0, component)), target(null)));
|
|
170
|
+
}
|
|
171
|
+
function appendLottieAutoOrientation(target, property, rotation, context) {
|
|
172
|
+
const sampler = createLottiePositionSampler(property, context);
|
|
173
|
+
if (sampler === null)
|
|
174
|
+
return;
|
|
175
|
+
const baseRotation = target.rotation;
|
|
176
|
+
const rotationTrack = rotation !== undefined && isAnimatedProperty(rotation)
|
|
177
|
+
? createLottieTrack(rotation.k, 1, context, (value) => numericValue(value, 1), 0)
|
|
178
|
+
: null;
|
|
179
|
+
const previous = [0, 0];
|
|
180
|
+
const current = [0, 0];
|
|
181
|
+
const frameStep = Math.abs(context.frameScale) / context.document.fr;
|
|
182
|
+
const applyOrientation = (time) => {
|
|
183
|
+
let previousTime;
|
|
184
|
+
let currentTime;
|
|
185
|
+
if (time <= sampler.firstTime) {
|
|
186
|
+
previousTime = sampler.firstTime;
|
|
187
|
+
currentTime = sampler.firstTime + frameStep * 0.01;
|
|
188
|
+
}
|
|
189
|
+
else if (time >= sampler.lastTime) {
|
|
190
|
+
previousTime = sampler.lastTime - frameStep * (sampler.separated ? 0.01 : 0.05);
|
|
191
|
+
currentTime = sampler.lastTime;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
previousTime = time - frameStep * 0.01;
|
|
195
|
+
currentTime = time;
|
|
196
|
+
}
|
|
197
|
+
sampler.sample(previous, previousTime);
|
|
198
|
+
sampler.sample(current, currentTime);
|
|
199
|
+
const orientation = (Math.atan2(current[1] - previous[1], current[0] - previous[0]) * 180) / Math.PI;
|
|
200
|
+
if (rotationTrack !== null)
|
|
201
|
+
sampleAnimationTrack(_autoOrientRotationScratch, rotationTrack, time);
|
|
202
|
+
target.rotation = (rotationTrack === null ? baseRotation : _autoOrientRotationScratch[0]) + orientation;
|
|
203
|
+
invalidateNodeLocalTransform(target);
|
|
204
|
+
};
|
|
205
|
+
applyOrientation(sampler.firstTime);
|
|
206
|
+
context.channels.push(createAnimationChannel(sampler.carrier, {
|
|
207
|
+
lottieApply(_sample, time) {
|
|
208
|
+
applyOrientation(time);
|
|
209
|
+
},
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
function createLottiePositionSampler(property, context) {
|
|
213
|
+
if (property === undefined)
|
|
214
|
+
return null;
|
|
215
|
+
if (isSeparatedPosition(property)) {
|
|
216
|
+
const xTrack = isAnimatedProperty(property.x)
|
|
217
|
+
? createLottieTrack(property.x.k, 1, context, (value) => numericValue(value, 1), 0)
|
|
218
|
+
: null;
|
|
219
|
+
const yTrack = isAnimatedProperty(property.y)
|
|
220
|
+
? createLottieTrack(property.y.k, 1, context, (value) => numericValue(value, 1), 0)
|
|
221
|
+
: null;
|
|
222
|
+
const carrier = xTrack ?? yTrack;
|
|
223
|
+
if (carrier === null)
|
|
224
|
+
return null;
|
|
225
|
+
const x = numericValue(initialLottieValue(property.x), 1)[0];
|
|
226
|
+
const y = numericValue(initialLottieValue(property.y), 1)[0];
|
|
227
|
+
const tracks = [xTrack, yTrack].filter((track) => track !== null && track.times.length > 0);
|
|
228
|
+
if (tracks.length === 0)
|
|
229
|
+
return null;
|
|
230
|
+
return {
|
|
231
|
+
carrier,
|
|
232
|
+
firstTime: Math.min(...tracks.map((track) => track.times[0])),
|
|
233
|
+
lastTime: Math.max(...tracks.map((track) => track.times[track.times.length - 1])),
|
|
234
|
+
sample(out, time) {
|
|
235
|
+
out[0] = x;
|
|
236
|
+
out[1] = y;
|
|
237
|
+
if (xTrack !== null)
|
|
238
|
+
sampleAnimationTrack(out, xTrack, time);
|
|
239
|
+
if (yTrack !== null) {
|
|
240
|
+
sampleAnimationTrack(_autoOrientScalarScratch, yTrack, time);
|
|
241
|
+
out[1] = _autoOrientScalarScratch[0];
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
separated: true,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (!isAnimatedProperty(property))
|
|
248
|
+
return null;
|
|
249
|
+
const track = createLottieTrack(property.k, 2, context, (value) => numericValue(value, 2), 0, hasSpatialTangents(property.k));
|
|
250
|
+
if (track.times.length === 0)
|
|
251
|
+
return null;
|
|
252
|
+
return {
|
|
253
|
+
carrier: track,
|
|
254
|
+
firstTime: track.times[0],
|
|
255
|
+
lastTime: track.times[track.times.length - 1],
|
|
256
|
+
sample(out, time) {
|
|
257
|
+
sampleAnimationTrack(out, track, time);
|
|
258
|
+
},
|
|
259
|
+
separated: false,
|
|
260
|
+
};
|
|
160
261
|
}
|
|
161
262
|
function bindMutableNumericProperty(property, current, convert, onChange, context) {
|
|
162
263
|
if (!isAnimatedProperty(property))
|
|
@@ -174,9 +275,9 @@ function bindMutableNumericProperty(property, current, convert, onChange, contex
|
|
|
174
275
|
},
|
|
175
276
|
}), convert, context);
|
|
176
277
|
}
|
|
177
|
-
function createLottieTrack(keyframes, components, context, valueOf, easingComponent) {
|
|
278
|
+
function createLottieTrack(keyframes, components, context, valueOf, easingComponent, spatialTangents = false, tangentOf = (value) => value) {
|
|
178
279
|
const times = [];
|
|
179
|
-
const
|
|
280
|
+
const samples = [];
|
|
180
281
|
const retained = [];
|
|
181
282
|
for (let index = 0; index < keyframes.length; index++) {
|
|
182
283
|
const keyframe = keyframes[index];
|
|
@@ -186,26 +287,102 @@ function createLottieTrack(keyframes, components, context, valueOf, easingCompon
|
|
|
186
287
|
times.push(time);
|
|
187
288
|
retained.push(keyframe);
|
|
188
289
|
const source = keyframe.s ?? keyframes[index - 1]?.e;
|
|
189
|
-
|
|
290
|
+
samples.push(valueOf(source, index));
|
|
190
291
|
}
|
|
292
|
+
const spatial = spatialTangents && hasSpatialTangents(retained);
|
|
293
|
+
const values = spatial
|
|
294
|
+
? createLottieSpatialTrackValues(retained, samples, times, components, tangentOf)
|
|
295
|
+
: samples.flat();
|
|
191
296
|
const segmentEasings = [];
|
|
192
297
|
for (let index = 0; index < retained.length - 1; index++) {
|
|
193
298
|
const keyframe = retained[index];
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
299
|
+
const temporal = keyframe.h === 1 ? _holdEasing : createLottieSegmentEasing(keyframe.o, keyframe.i, easingComponent);
|
|
300
|
+
const outgoing = spatialTangent(keyframe.to, components, tangentOf);
|
|
301
|
+
const incoming = spatialTangent(keyframe.ti, components, tangentOf);
|
|
302
|
+
segmentEasings.push(spatial && outgoing !== null && incoming !== null
|
|
303
|
+
? createLottieSpatialSegmentEasing(temporal, samples[index], samples[index + 1], outgoing, incoming)
|
|
304
|
+
: temporal);
|
|
200
305
|
}
|
|
201
306
|
return createAnimationTrack({
|
|
202
307
|
components,
|
|
203
|
-
interpolation: 'Linear',
|
|
308
|
+
interpolation: spatial ? 'Cubic' : 'Linear',
|
|
204
309
|
segmentEasings,
|
|
205
310
|
times,
|
|
206
311
|
values,
|
|
207
312
|
});
|
|
208
313
|
}
|
|
314
|
+
function createLottieSpatialTrackValues(keyframes, samples, times, components, tangentOf) {
|
|
315
|
+
const incoming = samples.map(() => new Array(components).fill(0));
|
|
316
|
+
const outgoing = samples.map(() => new Array(components).fill(0));
|
|
317
|
+
for (let index = 0; index < samples.length - 1; index++) {
|
|
318
|
+
const dt = times[index + 1] - times[index];
|
|
319
|
+
const spatialOut = spatialTangent(keyframes[index].to, components, tangentOf);
|
|
320
|
+
const spatialIn = spatialTangent(keyframes[index].ti, components, tangentOf);
|
|
321
|
+
for (let component = 0; component < components; component++) {
|
|
322
|
+
if (spatialOut !== null && spatialIn !== null) {
|
|
323
|
+
outgoing[index][component] = (3 * spatialOut[component]) / dt;
|
|
324
|
+
incoming[index + 1][component] = (-3 * spatialIn[component]) / dt;
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
const slope = (samples[index + 1][component] - samples[index][component]) / dt;
|
|
328
|
+
outgoing[index][component] = slope;
|
|
329
|
+
incoming[index + 1][component] = slope;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const values = [];
|
|
334
|
+
for (let index = 0; index < samples.length; index++) {
|
|
335
|
+
values.push(...incoming[index], ...samples[index], ...outgoing[index]);
|
|
336
|
+
}
|
|
337
|
+
return values;
|
|
338
|
+
}
|
|
339
|
+
function spatialTangent(value, components, tangentOf) {
|
|
340
|
+
if (!Array.isArray(value))
|
|
341
|
+
return null;
|
|
342
|
+
return numericValue(value, components).map(tangentOf);
|
|
343
|
+
}
|
|
344
|
+
const LOTTIE_SPATIAL_CURVE_SAMPLES = 150;
|
|
345
|
+
function createLottieSpatialSegmentEasing(temporal, start, end, outgoing, incoming) {
|
|
346
|
+
const lengths = new Array(LOTTIE_SPATIAL_CURVE_SAMPLES).fill(0);
|
|
347
|
+
let previous = start;
|
|
348
|
+
for (let index = 1; index < LOTTIE_SPATIAL_CURVE_SAMPLES; index++) {
|
|
349
|
+
const point = sampleLottieSpatialBezier(start, end, outgoing, incoming, index / (LOTTIE_SPATIAL_CURVE_SAMPLES - 1));
|
|
350
|
+
let distanceSquared = 0;
|
|
351
|
+
for (let component = 0; component < start.length; component++) {
|
|
352
|
+
distanceSquared += (point[component] - previous[component]) ** 2;
|
|
353
|
+
}
|
|
354
|
+
lengths[index] = lengths[index - 1] + Math.sqrt(distanceSquared);
|
|
355
|
+
previous = point;
|
|
356
|
+
}
|
|
357
|
+
const total = lengths[lengths.length - 1];
|
|
358
|
+
return (alpha) => {
|
|
359
|
+
const distanceFraction = temporal?.(alpha) ?? alpha;
|
|
360
|
+
if (distanceFraction <= 0 || total === 0)
|
|
361
|
+
return 0;
|
|
362
|
+
if (distanceFraction >= 1)
|
|
363
|
+
return 1;
|
|
364
|
+
const distance = total * distanceFraction;
|
|
365
|
+
let low = 0;
|
|
366
|
+
let high = lengths.length - 1;
|
|
367
|
+
while (low + 1 < high) {
|
|
368
|
+
const middle = (low + high) >> 1;
|
|
369
|
+
if (lengths[middle] <= distance)
|
|
370
|
+
low = middle;
|
|
371
|
+
else
|
|
372
|
+
high = middle;
|
|
373
|
+
}
|
|
374
|
+
const span = lengths[high] - lengths[low];
|
|
375
|
+
const fraction = span > 0 ? (distance - lengths[low]) / span : 0;
|
|
376
|
+
return (low + fraction) / (LOTTIE_SPATIAL_CURVE_SAMPLES - 1);
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function sampleLottieSpatialBezier(start, end, outgoing, incoming, time) {
|
|
380
|
+
const inverse = 1 - time;
|
|
381
|
+
return start.map((value, component) => inverse ** 3 * value +
|
|
382
|
+
3 * inverse ** 2 * time * (value + outgoing[component]) +
|
|
383
|
+
3 * inverse * time ** 2 * (end[component] + incoming[component]) +
|
|
384
|
+
time ** 3 * end[component]);
|
|
385
|
+
}
|
|
209
386
|
function createLottieSegmentEasing(outgoing, incoming, component) {
|
|
210
387
|
if (outgoing === undefined || incoming === undefined)
|
|
211
388
|
return null;
|
|
@@ -318,6 +495,9 @@ function appendLottieShapeItems(parent, items, context, name = null) {
|
|
|
318
495
|
const color = numericValue(initialLottieValue(stroke.c), 3);
|
|
319
496
|
const opacity = [numericValue(initialLottieValue(stroke.o), 1)[0] / 100];
|
|
320
497
|
const width = [numericValue(initialLottieValue(stroke.w), 1)[0]];
|
|
498
|
+
const miterLimit = [
|
|
499
|
+
stroke.ml2 === undefined ? (stroke.ml ?? 4) : numericValue(initialLottieValue(stroke.ml2), 1)[0],
|
|
500
|
+
];
|
|
321
501
|
const dashEntries = stroke.d ?? [];
|
|
322
502
|
const hasAnimatedDash = dashEntries.some((entry) => isAnimatedProperty(entry.v));
|
|
323
503
|
const dash = hasAnimatedDash
|
|
@@ -338,7 +518,7 @@ function appendLottieShapeItems(parent, items, context, name = null) {
|
|
|
338
518
|
dashOffset,
|
|
339
519
|
joints: mapLottieLineJoin(stroke.lj),
|
|
340
520
|
kind: 'stroke',
|
|
341
|
-
miterLimit:
|
|
521
|
+
miterLimit: miterLimit[0],
|
|
342
522
|
opacity: opacity[0],
|
|
343
523
|
width: width[0],
|
|
344
524
|
};
|
|
@@ -352,6 +532,12 @@ function appendLottieShapeItems(parent, items, context, name = null) {
|
|
|
352
532
|
paint.width = width[0];
|
|
353
533
|
rerender();
|
|
354
534
|
}, context);
|
|
535
|
+
if (stroke.ml2 !== undefined) {
|
|
536
|
+
bindMutableNumericProperty(stroke.ml2, miterLimit, (value) => value, () => {
|
|
537
|
+
paint.miterLimit = miterLimit[0];
|
|
538
|
+
rerender();
|
|
539
|
+
}, context);
|
|
540
|
+
}
|
|
355
541
|
}
|
|
356
542
|
else if (item.ty === 'gf' || item.ty === 'gs') {
|
|
357
543
|
const gradient = item;
|
|
@@ -361,6 +547,9 @@ function appendLottieShapeItems(parent, items, context, name = null) {
|
|
|
361
547
|
const end = numericValue(initialLottieValue(gradient.e), 2);
|
|
362
548
|
const opacity = [gradient.o === undefined ? 100 : numericValue(initialLottieValue(gradient.o), 1)[0]];
|
|
363
549
|
const width = [gradient.w === undefined ? 1 : numericValue(initialLottieValue(gradient.w), 1)[0]];
|
|
550
|
+
const miterLimit = [
|
|
551
|
+
gradient.ml2 === undefined ? (gradient.ml ?? 4) : numericValue(initialLottieValue(gradient.ml2), 1)[0],
|
|
552
|
+
];
|
|
364
553
|
const dashEntries = gradient.d ?? [];
|
|
365
554
|
const hasAnimatedDash = dashEntries.some((entry) => isAnimatedProperty(entry.v));
|
|
366
555
|
const dash = hasAnimatedDash
|
|
@@ -382,13 +571,14 @@ function appendLottieShapeItems(parent, items, context, name = null) {
|
|
|
382
571
|
end,
|
|
383
572
|
joints: mapLottieLineJoin(gradient.lj),
|
|
384
573
|
kind: 'gradient',
|
|
385
|
-
miterLimit:
|
|
574
|
+
miterLimit: miterLimit[0],
|
|
386
575
|
opacity: opacity[0] / 100,
|
|
387
576
|
shape: gradient.t,
|
|
388
577
|
start,
|
|
389
578
|
type: gradient.ty,
|
|
390
579
|
values,
|
|
391
580
|
width: width[0],
|
|
581
|
+
winding: gradient.r === 2 ? 'evenOdd' : 'nonZero',
|
|
392
582
|
};
|
|
393
583
|
state.paints.push(paint);
|
|
394
584
|
bindMutableNumericProperty(gradient.g.k, values, (value) => value, rerender, context);
|
|
@@ -406,6 +596,12 @@ function appendLottieShapeItems(parent, items, context, name = null) {
|
|
|
406
596
|
rerender();
|
|
407
597
|
}, context);
|
|
408
598
|
}
|
|
599
|
+
if (gradient.ml2 !== undefined) {
|
|
600
|
+
bindMutableNumericProperty(gradient.ml2, miterLimit, (value) => value, () => {
|
|
601
|
+
paint.miterLimit = miterLimit[0];
|
|
602
|
+
rerender();
|
|
603
|
+
}, context);
|
|
604
|
+
}
|
|
409
605
|
}
|
|
410
606
|
else if (item.ty === 'tm') {
|
|
411
607
|
const trim = item;
|
|
@@ -433,7 +629,7 @@ function createLottieShapeItemPath(item) {
|
|
|
433
629
|
const value = toLottieShapePath(initialLottieValue(shapePath.ks));
|
|
434
630
|
if (value === undefined)
|
|
435
631
|
return null;
|
|
436
|
-
return createLottieBezierPath(value);
|
|
632
|
+
return applyLottieShapeDirection(createLottieBezierPath(value), shapePath.d);
|
|
437
633
|
}
|
|
438
634
|
const path = createPath();
|
|
439
635
|
if (item.ty === 'rc') {
|
|
@@ -447,14 +643,14 @@ function createLottieShapeItemPath(item) {
|
|
|
447
643
|
else {
|
|
448
644
|
appendPathRectangle(path, position[0] - size[0] / 2, position[1] - size[1] / 2, size[0], size[1]);
|
|
449
645
|
}
|
|
450
|
-
return path;
|
|
646
|
+
return applyLottieShapeDirection(path, rectangle.d);
|
|
451
647
|
}
|
|
452
648
|
if (item.ty === 'el') {
|
|
453
649
|
const ellipse = item;
|
|
454
650
|
const position = numericValue(initialLottieValue(ellipse.p), 2);
|
|
455
651
|
const size = numericValue(initialLottieValue(ellipse.s), 2);
|
|
456
652
|
appendPathEllipse(path, position[0], position[1], size[0] / 2, size[1] / 2);
|
|
457
|
-
return path;
|
|
653
|
+
return applyLottieShapeDirection(path, ellipse.d);
|
|
458
654
|
}
|
|
459
655
|
if (item.ty === 'sr') {
|
|
460
656
|
const polystar = item;
|
|
@@ -465,7 +661,7 @@ function createLottieShapeItemPath(item) {
|
|
|
465
661
|
const rotation = numericValue(initialLottieValue(polystar.r), 1)[0];
|
|
466
662
|
const outerRoundness = polystar.os === undefined ? 0 : numericValue(initialLottieValue(polystar.os), 1)[0];
|
|
467
663
|
const innerRoundness = polystar.sy === 1 && polystar.is !== undefined ? numericValue(initialLottieValue(polystar.is), 1)[0] : 0;
|
|
468
|
-
return createLottiePolystarPath(polystar.sy, center, points, outer, inner, rotation, outerRoundness, innerRoundness);
|
|
664
|
+
return applyLottieShapeDirection(createLottiePolystarPath(polystar.sy, center, points, outer, inner, rotation, outerRoundness, innerRoundness), polystar.d);
|
|
469
665
|
}
|
|
470
666
|
return null;
|
|
471
667
|
}
|
|
@@ -513,7 +709,7 @@ function createLottiePolystarPath(kind, center, pointCount, outer, inner, rotati
|
|
|
513
709
|
}
|
|
514
710
|
function bindLottieGeometryItem(item, state, pathIndex, rerender, context) {
|
|
515
711
|
const rebuild = (path) => {
|
|
516
|
-
state.paths[pathIndex] = path;
|
|
712
|
+
state.paths[pathIndex] = applyLottieShapeDirection(path, item.d);
|
|
517
713
|
rerender();
|
|
518
714
|
};
|
|
519
715
|
if (item.ty === 'sh') {
|
|
@@ -705,9 +901,10 @@ function createLottieBezierPath(value) {
|
|
|
705
901
|
}
|
|
706
902
|
return path;
|
|
707
903
|
}
|
|
708
|
-
//
|
|
709
|
-
//
|
|
710
|
-
//
|
|
904
|
+
// The current representation restates every local path for every local paint. This preserves
|
|
905
|
+
// multiple paints when all paths precede all styles, but it does not yet implement Lottie's general
|
|
906
|
+
// render stack: styles scope only over preceding shapes (including shapes in nested groups), and
|
|
907
|
+
// repeated styles render in reverse order. That needs a scoped stack rather than another field here.
|
|
711
908
|
function renderLottieShapeState(state) {
|
|
712
909
|
clearShapeCommands(state.shape);
|
|
713
910
|
if (state.paths.length === 0)
|
|
@@ -728,7 +925,7 @@ function renderLottieShapeState(state) {
|
|
|
728
925
|
}
|
|
729
926
|
else if (paint.type === 'gf') {
|
|
730
927
|
appendLottieGradientFill(state.shape, paint);
|
|
731
|
-
appendLottieShapePaths(state,
|
|
928
|
+
appendLottieShapePaths(state, paint.winding);
|
|
732
929
|
appendShapeEndFill(state.shape);
|
|
733
930
|
}
|
|
734
931
|
else {
|
|
@@ -756,6 +953,13 @@ function appendLottieGradientStroke(shape, paint) {
|
|
|
756
953
|
appendShapeLineStyle(shape, paint.width, 0, 1, false, 'normal', paint.caps, paint.joints, paint.miterLimit);
|
|
757
954
|
appendShapeLineGradientStyle(shape, paint.shape === 2 ? 'radial' : 'linear', gradient.colors, gradient.alphas, gradient.ratios, createLottieGradientMatrix(paint.start, paint.end));
|
|
758
955
|
}
|
|
956
|
+
function applyLottieShapeDirection(path, direction) {
|
|
957
|
+
if (direction !== 3)
|
|
958
|
+
return path;
|
|
959
|
+
const reversed = createPath(path.winding);
|
|
960
|
+
reversePath(path, reversed);
|
|
961
|
+
return reversed;
|
|
962
|
+
}
|
|
759
963
|
function applyLottieMasks(target, masks, context) {
|
|
760
964
|
const active = masks.filter((mask) => mask.mode !== 'n');
|
|
761
965
|
if (active.length === 0)
|
|
@@ -915,8 +1119,7 @@ function hasComponentSpecificEasing(keyframes, components) {
|
|
|
915
1119
|
return false;
|
|
916
1120
|
for (let index = 0; index < keyframes.length - 1; index++) {
|
|
917
1121
|
const current = keyframes[index];
|
|
918
|
-
const
|
|
919
|
-
for (const handle of [current.o, next.i]) {
|
|
1122
|
+
for (const handle of [current.o, current.i]) {
|
|
920
1123
|
if (handle === undefined)
|
|
921
1124
|
continue;
|
|
922
1125
|
if (handleVaries(handle.x, components) || handleVaries(handle.y, components))
|
|
@@ -925,6 +1128,11 @@ function hasComponentSpecificEasing(keyframes, components) {
|
|
|
925
1128
|
}
|
|
926
1129
|
return false;
|
|
927
1130
|
}
|
|
1131
|
+
function hasSpatialTangents(keyframes) {
|
|
1132
|
+
return keyframes.some((keyframe, index) => {
|
|
1133
|
+
return index < keyframes.length - 1 && Array.isArray(keyframe.to) && Array.isArray(keyframe.ti);
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
928
1136
|
function handleVaries(value, components) {
|
|
929
1137
|
if (!Array.isArray(value) || value.length < 2)
|
|
930
1138
|
return false;
|
|
@@ -1029,6 +1237,8 @@ function reportLottieDrop(context, kind, detail) {
|
|
|
1029
1237
|
reportImportDiagnostic(context.diagnostics, ImportDiagnosticSeverity.Drop, kind, 'lottieDocument', detail);
|
|
1030
1238
|
}
|
|
1031
1239
|
const _sampleScratch = new Array(256).fill(0);
|
|
1240
|
+
const _autoOrientRotationScratch = [0];
|
|
1241
|
+
const _autoOrientScalarScratch = [0];
|
|
1032
1242
|
const _holdEasing = () => 0;
|
|
1033
1243
|
// Bodymovin's own numbering. The five that fold into blend state:
|
|
1034
1244
|
const _lottieFixedBlendModes = new Map([
|