@babylonjs/lottie-player 9.2.0 → 9.2.2
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/maths/boundingBox.d.ts.map +1 -1
- package/maths/boundingBox.js +32 -3
- package/maths/boundingBox.js.map +1 -1
- package/nodes/controlNode.d.ts +2 -1
- package/nodes/controlNode.d.ts.map +1 -1
- package/nodes/controlNode.js +4 -2
- package/nodes/controlNode.js.map +1 -1
- package/nodes/node.d.ts +1 -0
- package/nodes/node.d.ts.map +1 -1
- package/nodes/node.js +12 -5
- package/nodes/node.js.map +1 -1
- package/package.json +1 -1
- package/parsing/parser.d.ts +2 -0
- package/parsing/parser.d.ts.map +1 -1
- package/parsing/parser.js +16 -6
- package/parsing/parser.js.map +1 -1
- package/parsing/rawPropertyHelpers.d.ts +27 -0
- package/parsing/rawPropertyHelpers.d.ts.map +1 -0
- package/parsing/rawPropertyHelpers.js +41 -0
- package/parsing/rawPropertyHelpers.js.map +1 -0
- package/parsing/rawTypes.d.ts +6 -1
- package/parsing/rawTypes.d.ts.map +1 -1
- package/parsing/spritePacker.d.ts +25 -13
- package/parsing/spritePacker.d.ts.map +1 -1
- package/parsing/spritePacker.js +251 -127
- package/parsing/spritePacker.js.map +1 -1
- package/rendering/animationController.d.ts.map +1 -1
- package/rendering/animationController.js +9 -5
- package/rendering/animationController.js.map +1 -1
- package/rendering/renderingManager.d.ts +20 -6
- package/rendering/renderingManager.d.ts.map +1 -1
- package/rendering/renderingManager.js +75 -8
- package/rendering/renderingManager.js.map +1 -1
package/parsing/spritePacker.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import "@babylonjs/core/Engines/Extensions/engine.dynamicTexture.js";
|
|
2
2
|
import { ThinTexture } from "@babylonjs/core/Materials/Textures/thinTexture.js";
|
|
3
|
+
import { GetInitialScalarValue, GetInitialVectorValues, GetInitialBezierData } from "./rawPropertyHelpers.js";
|
|
3
4
|
import { GetShapesBoundingBox, GetTextBoundingBox } from "../maths/boundingBox.js";
|
|
4
5
|
/**
|
|
5
6
|
* SpritePacker is a class that handles the packing of sprites into a texture atlas.
|
|
7
|
+
* If sprites exceed the capacity of a single atlas texture, additional atlas pages are created.
|
|
6
8
|
*/
|
|
7
9
|
export class SpritePacker {
|
|
8
10
|
/**
|
|
9
|
-
* Gets the
|
|
10
|
-
* @returns
|
|
11
|
+
* Gets the textures for all atlas pages.
|
|
12
|
+
* @returns An array of textures, one per atlas page.
|
|
11
13
|
*/
|
|
12
|
-
get
|
|
13
|
-
return this.
|
|
14
|
+
get textures() {
|
|
15
|
+
return this._pages.map((p) => p.texture);
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* Sets the fonts that will be used to render text in the sprite atlas.
|
|
@@ -29,28 +31,11 @@ export class SpritePacker {
|
|
|
29
31
|
*/
|
|
30
32
|
constructor(engine, isHtmlCanvas, atlasScale, variables, configuration) {
|
|
31
33
|
this._engine = engine;
|
|
34
|
+
this._isHtmlCanvas = isHtmlCanvas;
|
|
32
35
|
this._atlasScale = atlasScale;
|
|
33
36
|
this._variables = variables;
|
|
34
37
|
this._configuration = configuration;
|
|
35
|
-
this.
|
|
36
|
-
this._currentX = this._configuration.gapSize;
|
|
37
|
-
this._currentY = this._configuration.gapSize;
|
|
38
|
-
this._maxRowHeight = 0;
|
|
39
|
-
if (isHtmlCanvas) {
|
|
40
|
-
this._spritesCanvas = document.createElement("canvas");
|
|
41
|
-
this._spritesCanvas.width = this._configuration.spriteAtlasWidth;
|
|
42
|
-
this._spritesCanvas.height = this._configuration.spriteAtlasHeight;
|
|
43
|
-
this._spritesCanvasContext = this._spritesCanvas.getContext("2d");
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
this._spritesCanvas = new OffscreenCanvas(this._configuration.spriteAtlasWidth, this._configuration.spriteAtlasHeight);
|
|
47
|
-
this._spritesCanvasContext = this._spritesCanvas.getContext("2d");
|
|
48
|
-
}
|
|
49
|
-
this._spritesInternalTexture = this._engine.createDynamicTexture(this._configuration.spriteAtlasWidth, this._configuration.spriteAtlasHeight, false, 2); // Linear filtering
|
|
50
|
-
this._engine.updateDynamicTexture(this._spritesInternalTexture, this._spritesCanvas, false);
|
|
51
|
-
this._spritesTexture = new ThinTexture(this._spritesInternalTexture);
|
|
52
|
-
this._spritesTexture.wrapU = 0; // Disable wrapping
|
|
53
|
-
this._spritesTexture.wrapV = 0; // Disable wrapping
|
|
38
|
+
this._pages = [this._createPage()];
|
|
54
39
|
this._spriteAtlasInfo = {
|
|
55
40
|
uOffset: 0,
|
|
56
41
|
vOffset: 0,
|
|
@@ -60,6 +45,7 @@ export class SpritePacker {
|
|
|
60
45
|
heightPx: 0,
|
|
61
46
|
centerX: 0,
|
|
62
47
|
centerY: 0,
|
|
48
|
+
atlasIndex: 0,
|
|
63
49
|
};
|
|
64
50
|
}
|
|
65
51
|
/**
|
|
@@ -74,28 +60,25 @@ export class SpritePacker {
|
|
|
74
60
|
scalingFactor.y = scalingFactor.y * this._atlasScale * this._configuration.devicePixelRatio;
|
|
75
61
|
// Calculate the size of the sprite in the atlas in pixels
|
|
76
62
|
// This takes into account the scaling factor so in the call to _drawVectorShape the canvas will be scaled when rendering
|
|
77
|
-
this._spriteAtlasInfo.cellWidth = boundingBox.width * scalingFactor.x;
|
|
78
|
-
this._spriteAtlasInfo.cellHeight = boundingBox.height * scalingFactor.y;
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
// Add a gap between sprites to avoid bleeding issues
|
|
82
|
-
this._currentX = this._configuration.gapSize;
|
|
83
|
-
this._currentY += this._maxRowHeight + this._configuration.gapSize;
|
|
84
|
-
this._maxRowHeight = 0;
|
|
85
|
-
}
|
|
63
|
+
this._spriteAtlasInfo.cellWidth = this._getAtlasCellDimension(boundingBox.width * scalingFactor.x);
|
|
64
|
+
this._spriteAtlasInfo.cellHeight = this._getAtlasCellDimension(boundingBox.height * scalingFactor.y);
|
|
65
|
+
// Get (or create) the page that has room for this sprite
|
|
66
|
+
const page = this._getPageWithRoom(this._spriteAtlasInfo.cellWidth, this._spriteAtlasInfo.cellHeight);
|
|
86
67
|
// Draw the shape in the canvas
|
|
87
|
-
this._drawVectorShape(rawElements, boundingBox, scalingFactor);
|
|
88
|
-
this.
|
|
68
|
+
this._drawVectorShape(rawElements, boundingBox, scalingFactor, page);
|
|
69
|
+
this._extrudeSpriteEdges(page, page.currentX, page.currentY, this._spriteAtlasInfo.cellWidth, this._spriteAtlasInfo.cellHeight);
|
|
70
|
+
page.isDirty = true;
|
|
89
71
|
// Get the rest of the sprite information required to render the shape
|
|
90
|
-
this._spriteAtlasInfo.uOffset =
|
|
91
|
-
this._spriteAtlasInfo.vOffset =
|
|
72
|
+
this._spriteAtlasInfo.uOffset = page.currentX / this._configuration.spriteAtlasWidth;
|
|
73
|
+
this._spriteAtlasInfo.vOffset = page.currentY / this._configuration.spriteAtlasHeight;
|
|
92
74
|
this._spriteAtlasInfo.widthPx = boundingBox.width;
|
|
93
75
|
this._spriteAtlasInfo.heightPx = boundingBox.height;
|
|
94
76
|
this._spriteAtlasInfo.centerX = boundingBox.offsetX;
|
|
95
77
|
this._spriteAtlasInfo.centerY = boundingBox.offsetY;
|
|
78
|
+
this._spriteAtlasInfo.atlasIndex = this._pages.indexOf(page);
|
|
96
79
|
// Advance the current position for the next sprite
|
|
97
|
-
|
|
98
|
-
|
|
80
|
+
page.currentX += this._spriteAtlasInfo.cellWidth + this._configuration.gapSize; // Add a gap between sprites to avoid bleeding
|
|
81
|
+
page.maxRowHeight = Math.max(page.maxRowHeight, this._spriteAtlasInfo.cellHeight);
|
|
99
82
|
return this._spriteAtlasInfo;
|
|
100
83
|
}
|
|
101
84
|
/**
|
|
@@ -109,7 +92,7 @@ export class SpritePacker {
|
|
|
109
92
|
return undefined;
|
|
110
93
|
}
|
|
111
94
|
// If the text information is malformed and we can't get the bounding box, then just return
|
|
112
|
-
const boundingBox = GetTextBoundingBox(this.
|
|
95
|
+
const boundingBox = GetTextBoundingBox(this._pages[this._pages.length - 1].context, textData, this._rawFonts, this._variables);
|
|
113
96
|
if (boundingBox === undefined) {
|
|
114
97
|
return undefined;
|
|
115
98
|
}
|
|
@@ -117,106 +100,234 @@ export class SpritePacker {
|
|
|
117
100
|
scalingFactor.y = scalingFactor.y * this._atlasScale * this._configuration.devicePixelRatio;
|
|
118
101
|
// Calculate the size of the sprite in the atlas in pixels
|
|
119
102
|
// This takes into account the scaling factor so in the call to _drawText the canvas will be scaled when rendering
|
|
120
|
-
this._spriteAtlasInfo.cellWidth = boundingBox.width * scalingFactor.x;
|
|
121
|
-
this._spriteAtlasInfo.cellHeight = boundingBox.height * scalingFactor.y;
|
|
122
|
-
//
|
|
123
|
-
|
|
124
|
-
if (this._currentX + this._spriteAtlasInfo.cellWidth > this._configuration.spriteAtlasWidth) {
|
|
125
|
-
// Add a gap between sprites to avoid bleeding issues
|
|
126
|
-
this._currentX = this._configuration.gapSize;
|
|
127
|
-
this._currentY += this._maxRowHeight + this._configuration.gapSize;
|
|
128
|
-
this._maxRowHeight = 0;
|
|
129
|
-
}
|
|
103
|
+
this._spriteAtlasInfo.cellWidth = this._getAtlasCellDimension(boundingBox.width * scalingFactor.x);
|
|
104
|
+
this._spriteAtlasInfo.cellHeight = this._getAtlasCellDimension(boundingBox.height * scalingFactor.y);
|
|
105
|
+
// Get (or create) the page that has room for this sprite
|
|
106
|
+
const page = this._getPageWithRoom(this._spriteAtlasInfo.cellWidth, this._spriteAtlasInfo.cellHeight);
|
|
130
107
|
// Draw the text in the canvas
|
|
131
|
-
this._drawText(textData, boundingBox, scalingFactor);
|
|
132
|
-
this.
|
|
108
|
+
this._drawText(textData, boundingBox, scalingFactor, page);
|
|
109
|
+
this._extrudeSpriteEdges(page, page.currentX, page.currentY, this._spriteAtlasInfo.cellWidth, this._spriteAtlasInfo.cellHeight);
|
|
110
|
+
page.isDirty = true;
|
|
133
111
|
// Get the rest of the sprite information required to render the text
|
|
134
|
-
this._spriteAtlasInfo.uOffset =
|
|
135
|
-
this._spriteAtlasInfo.vOffset =
|
|
112
|
+
this._spriteAtlasInfo.uOffset = page.currentX / this._configuration.spriteAtlasWidth;
|
|
113
|
+
this._spriteAtlasInfo.vOffset = page.currentY / this._configuration.spriteAtlasHeight;
|
|
136
114
|
this._spriteAtlasInfo.widthPx = boundingBox.width;
|
|
137
115
|
this._spriteAtlasInfo.heightPx = boundingBox.height;
|
|
138
116
|
this._spriteAtlasInfo.centerX = boundingBox.offsetX;
|
|
139
117
|
this._spriteAtlasInfo.centerY = boundingBox.offsetY;
|
|
118
|
+
this._spriteAtlasInfo.atlasIndex = this._pages.indexOf(page);
|
|
140
119
|
// Advance the current position for the next sprite
|
|
141
|
-
|
|
142
|
-
|
|
120
|
+
page.currentX += this._spriteAtlasInfo.cellWidth + this._configuration.gapSize; // Add a gap between sprites to avoid bleeding
|
|
121
|
+
page.maxRowHeight = Math.max(page.maxRowHeight, this._spriteAtlasInfo.cellHeight);
|
|
143
122
|
return this._spriteAtlasInfo;
|
|
144
123
|
}
|
|
145
124
|
/**
|
|
146
|
-
* Updates
|
|
125
|
+
* Updates all dirty atlas page textures with the latest canvas content.
|
|
147
126
|
*/
|
|
148
127
|
updateAtlasTexture() {
|
|
149
|
-
|
|
150
|
-
|
|
128
|
+
for (const page of this._pages) {
|
|
129
|
+
if (!page.isDirty) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
this._engine.updateDynamicTexture(page.internalTexture, page.canvas, false);
|
|
133
|
+
page.isDirty = false;
|
|
151
134
|
}
|
|
152
|
-
// Update the internal texture with the new canvas content
|
|
153
|
-
this._engine.updateDynamicTexture(this._spritesInternalTexture, this._spritesCanvas, false);
|
|
154
|
-
this._isDirty = false;
|
|
155
135
|
}
|
|
156
136
|
/**
|
|
157
|
-
* Releases the
|
|
137
|
+
* Releases the canvases and their contexts to allow garbage collection.
|
|
158
138
|
*/
|
|
159
139
|
releaseCanvas() {
|
|
160
|
-
|
|
161
|
-
|
|
140
|
+
for (const page of this._pages) {
|
|
141
|
+
page.context = undefined;
|
|
142
|
+
page.canvas = undefined;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
_createPage() {
|
|
146
|
+
let canvas;
|
|
147
|
+
let context;
|
|
148
|
+
if (this._isHtmlCanvas) {
|
|
149
|
+
canvas = document.createElement("canvas");
|
|
150
|
+
canvas.width = this._configuration.spriteAtlasWidth;
|
|
151
|
+
canvas.height = this._configuration.spriteAtlasHeight;
|
|
152
|
+
context = canvas.getContext("2d");
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
canvas = new OffscreenCanvas(this._configuration.spriteAtlasWidth, this._configuration.spriteAtlasHeight);
|
|
156
|
+
context = canvas.getContext("2d");
|
|
157
|
+
}
|
|
158
|
+
const internalTexture = this._engine.createDynamicTexture(this._configuration.spriteAtlasWidth, this._configuration.spriteAtlasHeight, false, 2);
|
|
159
|
+
this._engine.updateDynamicTexture(internalTexture, canvas, false);
|
|
160
|
+
const texture = new ThinTexture(internalTexture);
|
|
161
|
+
texture.wrapU = 0;
|
|
162
|
+
texture.wrapV = 0;
|
|
163
|
+
return {
|
|
164
|
+
canvas,
|
|
165
|
+
context,
|
|
166
|
+
internalTexture,
|
|
167
|
+
texture,
|
|
168
|
+
isDirty: false,
|
|
169
|
+
currentX: this._configuration.gapSize,
|
|
170
|
+
currentY: this._configuration.gapSize,
|
|
171
|
+
maxRowHeight: 0,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Returns a page with room for a sprite of the given size. Wraps to the next row if needed,
|
|
176
|
+
* and creates a new page if the current page is full.
|
|
177
|
+
* @param cellWidth The width of the sprite cell in pixels.
|
|
178
|
+
* @param cellHeight The height of the sprite cell in pixels.
|
|
179
|
+
* @returns An atlas page with enough room for the sprite.
|
|
180
|
+
*/
|
|
181
|
+
_getPageWithRoom(cellWidth, cellHeight) {
|
|
182
|
+
let page = this._pages[this._pages.length - 1];
|
|
183
|
+
// Clamp oversized cells to fit within a single atlas page
|
|
184
|
+
const maxCellWidth = this._configuration.spriteAtlasWidth - 2 * this._configuration.gapSize;
|
|
185
|
+
const maxCellHeight = this._configuration.spriteAtlasHeight - 2 * this._configuration.gapSize;
|
|
186
|
+
if (cellWidth > maxCellWidth || cellHeight > maxCellHeight) {
|
|
187
|
+
// eslint-disable-next-line no-console
|
|
188
|
+
console.warn(`[SpritePacker] Sprite cell (${cellWidth}x${cellHeight}) exceeds atlas page (${this._configuration.spriteAtlasWidth}x${this._configuration.spriteAtlasHeight}). Clamping to fit.`);
|
|
189
|
+
this._spriteAtlasInfo.cellWidth = Math.min(cellWidth, maxCellWidth);
|
|
190
|
+
this._spriteAtlasInfo.cellHeight = Math.min(cellHeight, maxCellHeight);
|
|
191
|
+
cellWidth = this._spriteAtlasInfo.cellWidth;
|
|
192
|
+
cellHeight = this._spriteAtlasInfo.cellHeight;
|
|
193
|
+
}
|
|
194
|
+
// Check if the sprite fits in the current row
|
|
195
|
+
if (page.currentX + cellWidth > this._configuration.spriteAtlasWidth) {
|
|
196
|
+
// Move to the next row
|
|
197
|
+
page.currentX = this._configuration.gapSize;
|
|
198
|
+
page.currentY += page.maxRowHeight + this._configuration.gapSize;
|
|
199
|
+
page.maxRowHeight = 0;
|
|
200
|
+
}
|
|
201
|
+
// Check if the sprite fits vertically on this page
|
|
202
|
+
if (page.currentY + cellHeight > this._configuration.spriteAtlasHeight) {
|
|
203
|
+
// Current page is full — create a new one
|
|
204
|
+
page = this._createPage();
|
|
205
|
+
this._pages.push(page);
|
|
206
|
+
}
|
|
207
|
+
return page;
|
|
208
|
+
}
|
|
209
|
+
_getAtlasCellDimension(size) {
|
|
210
|
+
return Math.max(1, Math.ceil(size));
|
|
162
211
|
}
|
|
163
|
-
|
|
164
|
-
this.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
212
|
+
_extrudeSpriteEdges(page, x, y, width, height) {
|
|
213
|
+
const padding = Math.min(2, Math.floor(this._configuration.gapSize / 2));
|
|
214
|
+
const pixelX = Math.floor(x);
|
|
215
|
+
const pixelY = Math.floor(y);
|
|
216
|
+
const pixelWidth = Math.ceil(width);
|
|
217
|
+
const pixelHeight = Math.ceil(height);
|
|
218
|
+
if (padding <= 0 || pixelWidth <= 0 || pixelHeight <= 0) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
for (let offset = 1; offset <= padding; offset++) {
|
|
222
|
+
// Left edge
|
|
223
|
+
if (pixelX - offset >= 0) {
|
|
224
|
+
page.context.drawImage(page.canvas, pixelX, pixelY, 1, pixelHeight, pixelX - offset, pixelY, 1, pixelHeight);
|
|
225
|
+
}
|
|
226
|
+
// Right edge
|
|
227
|
+
if (pixelX + pixelWidth - 1 + offset < this._configuration.spriteAtlasWidth) {
|
|
228
|
+
page.context.drawImage(page.canvas, pixelX + pixelWidth - 1, pixelY, 1, pixelHeight, pixelX + pixelWidth - 1 + offset, pixelY, 1, pixelHeight);
|
|
229
|
+
}
|
|
230
|
+
// Top edge
|
|
231
|
+
if (pixelY - offset >= 0) {
|
|
232
|
+
page.context.drawImage(page.canvas, pixelX, pixelY, pixelWidth, 1, pixelX, pixelY - offset, pixelWidth, 1);
|
|
233
|
+
}
|
|
234
|
+
// Bottom edge
|
|
235
|
+
if (pixelY + pixelHeight - 1 + offset < this._configuration.spriteAtlasHeight) {
|
|
236
|
+
page.context.drawImage(page.canvas, pixelX, pixelY + pixelHeight - 1, pixelWidth, 1, pixelX, pixelY + pixelHeight - 1 + offset, pixelWidth, 1);
|
|
237
|
+
}
|
|
238
|
+
// Top-left corner
|
|
239
|
+
if (pixelX - offset >= 0 && pixelY - offset >= 0) {
|
|
240
|
+
page.context.drawImage(page.canvas, pixelX, pixelY, 1, 1, pixelX - offset, pixelY - offset, 1, 1);
|
|
241
|
+
}
|
|
242
|
+
// Top-right corner
|
|
243
|
+
if (pixelX + pixelWidth - 1 + offset < this._configuration.spriteAtlasWidth && pixelY - offset >= 0) {
|
|
244
|
+
page.context.drawImage(page.canvas, pixelX + pixelWidth - 1, pixelY, 1, 1, pixelX + pixelWidth - 1 + offset, pixelY - offset, 1, 1);
|
|
245
|
+
}
|
|
246
|
+
// Bottom-left corner
|
|
247
|
+
if (pixelX - offset >= 0 && pixelY + pixelHeight - 1 + offset < this._configuration.spriteAtlasHeight) {
|
|
248
|
+
page.context.drawImage(page.canvas, pixelX, pixelY + pixelHeight - 1, 1, 1, pixelX - offset, pixelY + pixelHeight - 1 + offset, 1, 1);
|
|
249
|
+
}
|
|
250
|
+
// Bottom-right corner
|
|
251
|
+
if (pixelX + pixelWidth - 1 + offset < this._configuration.spriteAtlasWidth && pixelY + pixelHeight - 1 + offset < this._configuration.spriteAtlasHeight) {
|
|
252
|
+
page.context.drawImage(page.canvas, pixelX + pixelWidth - 1, pixelY + pixelHeight - 1, 1, 1, pixelX + pixelWidth - 1 + offset, pixelY + pixelHeight - 1 + offset, 1, 1);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
_drawVectorShape(rawElements, boundingBox, scalingFactor, page) {
|
|
257
|
+
page.context.save();
|
|
258
|
+
page.context.globalCompositeOperation = "destination-over";
|
|
259
|
+
page.context.translate(page.currentX + Math.ceil(boundingBox.strokeInset / 2), page.currentY + Math.ceil(boundingBox.strokeInset / 2));
|
|
260
|
+
page.context.scale(scalingFactor.x, scalingFactor.y);
|
|
261
|
+
page.context.beginPath();
|
|
262
|
+
page.context.rect(0, 0, boundingBox.width, boundingBox.height);
|
|
263
|
+
page.context.clip();
|
|
264
|
+
page.context.beginPath();
|
|
169
265
|
for (let i = 0; i < rawElements.length; i++) {
|
|
170
266
|
const shape = rawElements[i];
|
|
171
267
|
switch (shape.ty) {
|
|
172
268
|
case "rc":
|
|
173
|
-
this._drawRectangle(shape, boundingBox);
|
|
269
|
+
this._drawRectangle(shape, boundingBox, page.context);
|
|
270
|
+
break;
|
|
271
|
+
case "el":
|
|
272
|
+
this._drawEllipse(shape, boundingBox, page.context);
|
|
174
273
|
break;
|
|
175
274
|
case "sh":
|
|
176
|
-
this._drawPath(shape, boundingBox);
|
|
275
|
+
this._drawPath(shape, boundingBox, page.context);
|
|
177
276
|
break;
|
|
178
277
|
case "fl":
|
|
179
|
-
this._drawFill(shape);
|
|
278
|
+
this._drawFill(shape, page.context);
|
|
180
279
|
break;
|
|
181
280
|
case "st":
|
|
182
|
-
this._drawStroke(shape);
|
|
281
|
+
this._drawStroke(shape, page.context);
|
|
183
282
|
break;
|
|
184
283
|
case "gf":
|
|
185
|
-
this._drawGradientFill(shape, boundingBox);
|
|
284
|
+
this._drawGradientFill(shape, boundingBox, page.context);
|
|
186
285
|
break;
|
|
187
286
|
case "tr":
|
|
188
287
|
break; // Nothing needed with transforms
|
|
189
288
|
}
|
|
190
289
|
}
|
|
191
|
-
|
|
290
|
+
page.context.restore();
|
|
192
291
|
}
|
|
193
|
-
|
|
194
|
-
// we need to setup the canvas context with the correct font and styles, so we don't set them up here
|
|
195
|
-
// again, but we still need to make sure to restore the context when we are done
|
|
196
|
-
_drawText(textData, boundingBox, scalingFactor) {
|
|
292
|
+
_drawText(textData, boundingBox, scalingFactor, page) {
|
|
197
293
|
if (this._rawFonts === undefined) {
|
|
198
|
-
this._spritesCanvasContext.restore();
|
|
199
294
|
return;
|
|
200
295
|
}
|
|
201
|
-
this._spritesCanvasContext.translate(this._currentX, this._currentY);
|
|
202
|
-
this._spritesCanvasContext.scale(scalingFactor.x, scalingFactor.y);
|
|
203
296
|
const textInfo = textData.d.k[0].s;
|
|
297
|
+
const fontFamily = textInfo.f;
|
|
298
|
+
const finalFont = this._rawFonts.get(fontFamily);
|
|
299
|
+
if (!finalFont) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
page.context.save();
|
|
303
|
+
page.context.translate(page.currentX, page.currentY);
|
|
304
|
+
page.context.scale(scalingFactor.x, scalingFactor.y);
|
|
305
|
+
// Set up font (same setup as GetTextBoundingBox for measurement consistency)
|
|
306
|
+
const weight = finalFont.fWeight || "400";
|
|
307
|
+
page.context.font = `${weight} ${textInfo.s}px ${finalFont.fFamily}`;
|
|
308
|
+
if (textInfo.sc !== undefined && textInfo.sc.length >= 3 && textInfo.sw !== undefined && textInfo.sw > 0) {
|
|
309
|
+
page.context.lineWidth = textInfo.sw;
|
|
310
|
+
}
|
|
311
|
+
// Clip to cell bounds to prevent text overdraw into adjacent cells
|
|
312
|
+
page.context.beginPath();
|
|
313
|
+
page.context.rect(0, 0, boundingBox.width, boundingBox.height);
|
|
314
|
+
page.context.clip();
|
|
204
315
|
if (textInfo.fc !== undefined && textInfo.fc.length >= 3) {
|
|
205
316
|
const rawFillStyle = textInfo.fc;
|
|
206
317
|
if (Array.isArray(rawFillStyle)) {
|
|
207
318
|
// If the fill style is an array, we assume it's a color array
|
|
208
|
-
|
|
319
|
+
page.context.fillStyle = this._lottieColorToCSSColor(rawFillStyle, 1);
|
|
209
320
|
}
|
|
210
321
|
else {
|
|
211
322
|
// If it's a string, we need to get the value from the variables map
|
|
212
323
|
const variableFillStyle = this._variables.get(rawFillStyle);
|
|
213
324
|
if (variableFillStyle !== undefined) {
|
|
214
|
-
|
|
325
|
+
page.context.fillStyle = variableFillStyle;
|
|
215
326
|
}
|
|
216
327
|
}
|
|
217
328
|
}
|
|
218
329
|
if (textInfo.sc !== undefined && textInfo.sc.length >= 3 && textInfo.sw !== undefined && textInfo.sw > 0) {
|
|
219
|
-
|
|
330
|
+
page.context.strokeStyle = this._lottieColorToCSSColor(textInfo.sc, 1);
|
|
220
331
|
}
|
|
221
332
|
// Text is supported as a possible variable (for localization for example)
|
|
222
333
|
// Check if the text is a variable and replace it if it is
|
|
@@ -225,43 +336,56 @@ export class SpritePacker {
|
|
|
225
336
|
if (variableText !== undefined) {
|
|
226
337
|
text = variableText;
|
|
227
338
|
}
|
|
228
|
-
|
|
339
|
+
page.context.fillText(text, 0, boundingBox.actualBoundingBoxAscent);
|
|
229
340
|
if (textInfo.sc !== undefined && textInfo.sc.length >= 3 && textInfo.sw !== undefined && textInfo.sw > 0 && textInfo.of === true) {
|
|
230
|
-
|
|
341
|
+
page.context.strokeText(text, 0, boundingBox.actualBoundingBoxAscent);
|
|
231
342
|
}
|
|
232
|
-
|
|
343
|
+
page.context.restore();
|
|
233
344
|
}
|
|
234
|
-
_drawRectangle(shape, boundingBox) {
|
|
235
|
-
const size = shape.s
|
|
236
|
-
const position = shape.p
|
|
237
|
-
const radius = shape.r
|
|
345
|
+
_drawRectangle(shape, boundingBox, ctx) {
|
|
346
|
+
const size = GetInitialVectorValues(shape.s);
|
|
347
|
+
const position = GetInitialVectorValues(shape.p);
|
|
348
|
+
const radius = GetInitialScalarValue(shape.r);
|
|
238
349
|
// Translate to the correct position within the atlas cell, same as paths use centerX/centerY
|
|
239
350
|
const x = position[0] - size[0] / 2 + boundingBox.centerX - Math.ceil(boundingBox.strokeInset);
|
|
240
351
|
const y = position[1] - size[1] / 2 + boundingBox.centerY - Math.ceil(boundingBox.strokeInset);
|
|
241
352
|
if (radius <= 0) {
|
|
242
|
-
|
|
353
|
+
ctx.rect(x, y, size[0], size[1]);
|
|
243
354
|
}
|
|
244
355
|
else {
|
|
245
|
-
|
|
356
|
+
ctx.roundRect(x, y, size[0], size[1], radius);
|
|
246
357
|
}
|
|
247
358
|
}
|
|
248
|
-
|
|
359
|
+
_drawEllipse(shape, boundingBox, ctx) {
|
|
360
|
+
const size = GetInitialVectorValues(shape.s);
|
|
361
|
+
const position = GetInitialVectorValues(shape.p);
|
|
362
|
+
const centerX = position[0] + boundingBox.centerX - Math.ceil(boundingBox.strokeInset);
|
|
363
|
+
const centerY = position[1] + boundingBox.centerY - Math.ceil(boundingBox.strokeInset);
|
|
364
|
+
const radiusX = size[0] / 2;
|
|
365
|
+
const radiusY = size[1] / 2;
|
|
366
|
+
ctx.moveTo(centerX + radiusX, centerY);
|
|
367
|
+
ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2);
|
|
368
|
+
}
|
|
369
|
+
_drawPath(shape, boundingBox, ctx) {
|
|
249
370
|
// The path data has to be translated to the center of the bounding box
|
|
250
371
|
// If the paths have stroke, we need to account for the stroke width
|
|
251
|
-
const pathData = shape.ks
|
|
372
|
+
const pathData = GetInitialBezierData(shape.ks);
|
|
373
|
+
if (!pathData) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
252
376
|
const xTranslate = boundingBox.centerX - Math.ceil(boundingBox.strokeInset);
|
|
253
377
|
const yTranslate = boundingBox.centerY - Math.ceil(boundingBox.strokeInset);
|
|
254
378
|
const vertices = pathData.v;
|
|
255
379
|
const inTangents = pathData.i;
|
|
256
380
|
const outTangents = pathData.o;
|
|
257
381
|
if (vertices.length > 0) {
|
|
258
|
-
|
|
382
|
+
ctx.moveTo(vertices[0][0] + xTranslate, vertices[0][1] + yTranslate);
|
|
259
383
|
for (let i = 0; i < vertices.length - 1; i++) {
|
|
260
384
|
const start = vertices[i];
|
|
261
385
|
const end = vertices[i + 1];
|
|
262
386
|
const outTangent = outTangents[i];
|
|
263
387
|
const inTangent = inTangents[i + 1];
|
|
264
|
-
|
|
388
|
+
ctx.bezierCurveTo(start[0] + xTranslate + outTangent[0], start[1] + yTranslate + outTangent[1], end[0] + xTranslate + inTangent[0], end[1] + yTranslate + inTangent[1], end[0] + xTranslate, end[1] + yTranslate);
|
|
265
389
|
}
|
|
266
390
|
if (pathData.c) {
|
|
267
391
|
// Close path with curve from last to first point
|
|
@@ -269,34 +393,34 @@ export class SpritePacker {
|
|
|
269
393
|
const end = vertices[0];
|
|
270
394
|
const outTangent = outTangents[vertices.length - 1];
|
|
271
395
|
const inTangent = inTangents[0];
|
|
272
|
-
|
|
273
|
-
|
|
396
|
+
ctx.bezierCurveTo(start[0] + xTranslate + outTangent[0], start[1] + yTranslate + outTangent[1], end[0] + xTranslate + inTangent[0], end[1] + yTranslate + inTangent[1], end[0] + xTranslate, end[1] + yTranslate);
|
|
397
|
+
ctx.closePath();
|
|
274
398
|
}
|
|
275
399
|
}
|
|
276
400
|
}
|
|
277
|
-
_drawFill(fill) {
|
|
401
|
+
_drawFill(fill, ctx) {
|
|
278
402
|
const color = this._lottieColorToCSSColor(fill.c.k, fill.o.k / 100);
|
|
279
|
-
|
|
280
|
-
|
|
403
|
+
ctx.fillStyle = color;
|
|
404
|
+
ctx.fill();
|
|
281
405
|
}
|
|
282
|
-
_drawStroke(stroke) {
|
|
406
|
+
_drawStroke(stroke, ctx) {
|
|
283
407
|
// Color and opacity
|
|
284
408
|
const opacity = stroke.o?.k ?? 100;
|
|
285
409
|
const color = this._lottieColorToCSSColor(stroke.c?.k ?? [0, 0, 0], opacity / 100);
|
|
286
|
-
|
|
410
|
+
ctx.strokeStyle = color;
|
|
287
411
|
// Width
|
|
288
412
|
const width = stroke.w?.k ?? 1;
|
|
289
|
-
|
|
413
|
+
ctx.lineWidth = width;
|
|
290
414
|
// Line cap
|
|
291
415
|
switch (stroke.lc) {
|
|
292
416
|
case 1:
|
|
293
|
-
|
|
417
|
+
ctx.lineCap = "butt";
|
|
294
418
|
break;
|
|
295
419
|
case 2:
|
|
296
|
-
|
|
420
|
+
ctx.lineCap = "round";
|
|
297
421
|
break;
|
|
298
422
|
case 3:
|
|
299
|
-
|
|
423
|
+
ctx.lineCap = "square";
|
|
300
424
|
break;
|
|
301
425
|
default:
|
|
302
426
|
// leave default
|
|
@@ -305,13 +429,13 @@ export class SpritePacker {
|
|
|
305
429
|
// Line join
|
|
306
430
|
switch (stroke.lj) {
|
|
307
431
|
case 1:
|
|
308
|
-
|
|
432
|
+
ctx.lineJoin = "miter";
|
|
309
433
|
break;
|
|
310
434
|
case 2:
|
|
311
|
-
|
|
435
|
+
ctx.lineJoin = "round";
|
|
312
436
|
break;
|
|
313
437
|
case 3:
|
|
314
|
-
|
|
438
|
+
ctx.lineJoin = "bevel";
|
|
315
439
|
break;
|
|
316
440
|
default:
|
|
317
441
|
// leave default
|
|
@@ -319,7 +443,7 @@ export class SpritePacker {
|
|
|
319
443
|
}
|
|
320
444
|
// Miter limit
|
|
321
445
|
if (stroke.ml !== undefined) {
|
|
322
|
-
|
|
446
|
+
ctx.miterLimit = stroke.ml;
|
|
323
447
|
}
|
|
324
448
|
// Dash pattern
|
|
325
449
|
const dashes = stroke.d;
|
|
@@ -330,35 +454,35 @@ export class SpritePacker {
|
|
|
330
454
|
lineDashes.push(dashes[i].v.k);
|
|
331
455
|
}
|
|
332
456
|
}
|
|
333
|
-
|
|
457
|
+
ctx.setLineDash(lineDashes);
|
|
334
458
|
}
|
|
335
|
-
|
|
459
|
+
ctx.stroke();
|
|
336
460
|
}
|
|
337
|
-
_drawGradientFill(fill, boundingBox) {
|
|
461
|
+
_drawGradientFill(fill, boundingBox, ctx) {
|
|
338
462
|
switch (fill.t) {
|
|
339
463
|
case 1: {
|
|
340
|
-
this._drawLinearGradientFill(fill, boundingBox);
|
|
464
|
+
this._drawLinearGradientFill(fill, boundingBox, ctx);
|
|
341
465
|
break;
|
|
342
466
|
}
|
|
343
467
|
case 2: {
|
|
344
|
-
this._drawRadialGradientFill(fill, boundingBox);
|
|
468
|
+
this._drawRadialGradientFill(fill, boundingBox, ctx);
|
|
345
469
|
break;
|
|
346
470
|
}
|
|
347
471
|
}
|
|
348
472
|
}
|
|
349
|
-
_drawLinearGradientFill(fill, boundingBox) {
|
|
473
|
+
_drawLinearGradientFill(fill, boundingBox, ctx) {
|
|
350
474
|
// We need to translate the gradient to the center of the bounding box
|
|
351
475
|
const xTranslate = boundingBox.centerX;
|
|
352
476
|
const yTranslate = boundingBox.centerY;
|
|
353
477
|
// Create the gradient
|
|
354
478
|
const startPoint = fill.s.k;
|
|
355
479
|
const endPoint = fill.e.k;
|
|
356
|
-
const gradient =
|
|
480
|
+
const gradient = ctx.createLinearGradient(startPoint[0] + xTranslate, startPoint[1] + yTranslate, endPoint[0] + xTranslate, endPoint[1] + yTranslate);
|
|
357
481
|
this._addColorStops(gradient, fill);
|
|
358
|
-
|
|
359
|
-
|
|
482
|
+
ctx.fillStyle = gradient;
|
|
483
|
+
ctx.fill();
|
|
360
484
|
}
|
|
361
|
-
_drawRadialGradientFill(fill, boundingBox) {
|
|
485
|
+
_drawRadialGradientFill(fill, boundingBox, ctx) {
|
|
362
486
|
// We need to translate the gradient to the center of the bounding box
|
|
363
487
|
const xTranslate = boundingBox.centerX;
|
|
364
488
|
const yTranslate = boundingBox.centerY;
|
|
@@ -368,10 +492,10 @@ export class SpritePacker {
|
|
|
368
492
|
const centerX = startPoint[0] + xTranslate;
|
|
369
493
|
const centerY = startPoint[1] + yTranslate;
|
|
370
494
|
const outerRadius = Math.hypot(endPoint[0] - startPoint[0], endPoint[1] - startPoint[1]);
|
|
371
|
-
const gradient =
|
|
495
|
+
const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, outerRadius);
|
|
372
496
|
this._addColorStops(gradient, fill);
|
|
373
|
-
|
|
374
|
-
|
|
497
|
+
ctx.fillStyle = gradient;
|
|
498
|
+
ctx.fill();
|
|
375
499
|
}
|
|
376
500
|
_addColorStops(gradient, fill) {
|
|
377
501
|
const stops = fill.g.p;
|