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