@shopify/klint 0.2.0 → 0.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/bin/create-sandbox +3 -3
- package/dist/Klint-CsVzll4n.d.cts +1410 -0
- package/dist/Klint-CsVzll4n.d.ts +1410 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/index.d.cts +4 -1409
- package/dist/index.d.ts +4 -1409
- package/dist/index.js +3 -6
- package/dist/plugins/index.cjs +2518 -0
- package/dist/plugins/index.d.cts +560 -0
- package/dist/plugins/index.d.ts +560 -0
- package/dist/plugins/index.js +2489 -0
- package/package.json +11 -7
|
@@ -0,0 +1,2518 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/plugins/index.tsx
|
|
21
|
+
var plugins_exports = {};
|
|
22
|
+
__export(plugins_exports, {
|
|
23
|
+
CatmullRom: () => CatmullRom,
|
|
24
|
+
Delaunay: () => Delaunay,
|
|
25
|
+
FontParser: () => FontParser,
|
|
26
|
+
Sprites: () => Sprites,
|
|
27
|
+
Things: () => Things
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(plugins_exports);
|
|
30
|
+
|
|
31
|
+
// src/plugins/FontParser.tsx
|
|
32
|
+
var BinaryReader = class {
|
|
33
|
+
constructor() {
|
|
34
|
+
const ab = new ArrayBuffer(8);
|
|
35
|
+
this.t = {
|
|
36
|
+
buff: ab,
|
|
37
|
+
int8: new Int8Array(ab),
|
|
38
|
+
uint8: new Uint8Array(ab),
|
|
39
|
+
int16: new Int16Array(ab),
|
|
40
|
+
uint16: new Uint16Array(ab),
|
|
41
|
+
int32: new Int32Array(ab),
|
|
42
|
+
uint32: new Uint32Array(ab)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
readFixed(data, o) {
|
|
46
|
+
return (data[o] << 8 | data[o + 1]) + (data[o + 2] << 8 | data[o + 3]) / (256 * 256 + 4);
|
|
47
|
+
}
|
|
48
|
+
readF2dot14(data, o) {
|
|
49
|
+
const num = this.readShort(data, o);
|
|
50
|
+
return num / 16384;
|
|
51
|
+
}
|
|
52
|
+
readInt(buff, p) {
|
|
53
|
+
const a = this.t.uint8;
|
|
54
|
+
a[0] = buff[p + 3];
|
|
55
|
+
a[1] = buff[p + 2];
|
|
56
|
+
a[2] = buff[p + 1];
|
|
57
|
+
a[3] = buff[p];
|
|
58
|
+
return this.t.int32[0];
|
|
59
|
+
}
|
|
60
|
+
readInt8(buff, p) {
|
|
61
|
+
const a = this.t.uint8;
|
|
62
|
+
a[0] = buff[p];
|
|
63
|
+
return this.t.int8[0];
|
|
64
|
+
}
|
|
65
|
+
readShort(buff, p) {
|
|
66
|
+
const a = this.t.uint16;
|
|
67
|
+
a[0] = buff[p] << 8 | buff[p + 1];
|
|
68
|
+
return this.t.int16[0];
|
|
69
|
+
}
|
|
70
|
+
readUshort(buff, p) {
|
|
71
|
+
return buff[p] << 8 | buff[p + 1];
|
|
72
|
+
}
|
|
73
|
+
readUshorts(buff, p, len) {
|
|
74
|
+
const arr = [];
|
|
75
|
+
for (let i = 0; i < len; i++) {
|
|
76
|
+
arr.push(this.readUshort(buff, p + i * 2));
|
|
77
|
+
}
|
|
78
|
+
return arr;
|
|
79
|
+
}
|
|
80
|
+
readUint(buff, p) {
|
|
81
|
+
const a = this.t.uint8;
|
|
82
|
+
a[3] = buff[p];
|
|
83
|
+
a[2] = buff[p + 1];
|
|
84
|
+
a[1] = buff[p + 2];
|
|
85
|
+
a[0] = buff[p + 3];
|
|
86
|
+
return this.t.uint32[0];
|
|
87
|
+
}
|
|
88
|
+
readUint64(buff, p) {
|
|
89
|
+
return this.readUint(buff, p) * (4294967295 + 1) + this.readUint(buff, p + 4);
|
|
90
|
+
}
|
|
91
|
+
readASCII(buff, p, l) {
|
|
92
|
+
let s = "";
|
|
93
|
+
for (let i = 0; i < l; i++) s += String.fromCharCode(buff[p + i]);
|
|
94
|
+
return s;
|
|
95
|
+
}
|
|
96
|
+
readBytes(buff, p, l) {
|
|
97
|
+
const arr = [];
|
|
98
|
+
for (let i = 0; i < l; i++) arr.push(buff[p + i]);
|
|
99
|
+
return arr;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
var bin = new BinaryReader();
|
|
103
|
+
function findTable(data, tab, foff = 0) {
|
|
104
|
+
const numTables = bin.readUshort(data, foff + 4);
|
|
105
|
+
let offset = foff + 12;
|
|
106
|
+
for (let i = 0; i < numTables; i++) {
|
|
107
|
+
const tag = bin.readASCII(data, offset, 4);
|
|
108
|
+
const checkSum = bin.readUint(data, offset + 4);
|
|
109
|
+
const toffset = bin.readUint(data, offset + 8);
|
|
110
|
+
const length = bin.readUint(data, offset + 12);
|
|
111
|
+
if (tag === tab) return [toffset, length];
|
|
112
|
+
offset += 16;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
var Tables = {
|
|
117
|
+
head: {
|
|
118
|
+
parseTab(data, offset, length) {
|
|
119
|
+
const obj = {};
|
|
120
|
+
const tableVersion = bin.readFixed(data, offset);
|
|
121
|
+
offset += 4;
|
|
122
|
+
obj.fontRevision = bin.readFixed(data, offset);
|
|
123
|
+
offset += 4;
|
|
124
|
+
const checkSumAdjustment = bin.readUint(data, offset);
|
|
125
|
+
offset += 4;
|
|
126
|
+
const magicNumber = bin.readUint(data, offset);
|
|
127
|
+
offset += 4;
|
|
128
|
+
obj.flags = bin.readUshort(data, offset);
|
|
129
|
+
offset += 2;
|
|
130
|
+
obj.unitsPerEm = bin.readUshort(data, offset);
|
|
131
|
+
offset += 2;
|
|
132
|
+
obj.created = bin.readUint64(data, offset);
|
|
133
|
+
offset += 8;
|
|
134
|
+
obj.modified = bin.readUint64(data, offset);
|
|
135
|
+
offset += 8;
|
|
136
|
+
obj.xMin = bin.readShort(data, offset);
|
|
137
|
+
offset += 2;
|
|
138
|
+
obj.yMin = bin.readShort(data, offset);
|
|
139
|
+
offset += 2;
|
|
140
|
+
obj.xMax = bin.readShort(data, offset);
|
|
141
|
+
offset += 2;
|
|
142
|
+
obj.yMax = bin.readShort(data, offset);
|
|
143
|
+
offset += 2;
|
|
144
|
+
obj.macStyle = bin.readUshort(data, offset);
|
|
145
|
+
offset += 2;
|
|
146
|
+
obj.lowestRecPPEM = bin.readUshort(data, offset);
|
|
147
|
+
offset += 2;
|
|
148
|
+
obj.fontDirectionHint = bin.readShort(data, offset);
|
|
149
|
+
offset += 2;
|
|
150
|
+
obj.indexToLocFormat = bin.readShort(data, offset);
|
|
151
|
+
offset += 2;
|
|
152
|
+
obj.glyphDataFormat = bin.readShort(data, offset);
|
|
153
|
+
offset += 2;
|
|
154
|
+
return obj;
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
maxp: {
|
|
158
|
+
parseTab(data, offset, length) {
|
|
159
|
+
const obj = {};
|
|
160
|
+
const ver = bin.readUint(data, offset);
|
|
161
|
+
offset += 4;
|
|
162
|
+
obj.numGlyphs = bin.readUshort(data, offset);
|
|
163
|
+
offset += 2;
|
|
164
|
+
return obj;
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
hhea: {
|
|
168
|
+
parseTab(data, offset, length) {
|
|
169
|
+
const obj = {};
|
|
170
|
+
const tableVersion = bin.readFixed(data, offset);
|
|
171
|
+
offset += 4;
|
|
172
|
+
const keys = [
|
|
173
|
+
"ascender",
|
|
174
|
+
"descender",
|
|
175
|
+
"lineGap",
|
|
176
|
+
"advanceWidthMax",
|
|
177
|
+
"minLeftSideBearing",
|
|
178
|
+
"minRightSideBearing",
|
|
179
|
+
"xMaxExtent",
|
|
180
|
+
"caretSlopeRise",
|
|
181
|
+
"caretSlopeRun",
|
|
182
|
+
"caretOffset",
|
|
183
|
+
"res0",
|
|
184
|
+
"res1",
|
|
185
|
+
"res2",
|
|
186
|
+
"res3",
|
|
187
|
+
"metricDataFormat",
|
|
188
|
+
"numberOfHMetrics"
|
|
189
|
+
];
|
|
190
|
+
for (let i = 0; i < keys.length; i++) {
|
|
191
|
+
const key = keys[i];
|
|
192
|
+
const func = key === "advanceWidthMax" || key === "numberOfHMetrics" ? bin.readUshort : bin.readShort;
|
|
193
|
+
obj[key] = func.call(bin, data, offset + i * 2);
|
|
194
|
+
}
|
|
195
|
+
return obj;
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
hmtx: {
|
|
199
|
+
parseTab(data, offset, length, font) {
|
|
200
|
+
const aWidth = [];
|
|
201
|
+
const lsBearing = [];
|
|
202
|
+
const nG = font.maxp.numGlyphs;
|
|
203
|
+
const nH = font.hhea.numberOfHMetrics;
|
|
204
|
+
let aw = 0, lsb = 0, i = 0;
|
|
205
|
+
while (i < nH) {
|
|
206
|
+
aw = bin.readUshort(data, offset + (i << 2));
|
|
207
|
+
lsb = bin.readShort(data, offset + (i << 2) + 2);
|
|
208
|
+
aWidth.push(aw);
|
|
209
|
+
lsBearing.push(lsb);
|
|
210
|
+
i++;
|
|
211
|
+
}
|
|
212
|
+
while (i < nG) {
|
|
213
|
+
aWidth.push(aw);
|
|
214
|
+
lsBearing.push(lsb);
|
|
215
|
+
i++;
|
|
216
|
+
}
|
|
217
|
+
return { aWidth, lsBearing };
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
loca: {
|
|
221
|
+
parseTab(data, offset, length, font) {
|
|
222
|
+
const obj = [];
|
|
223
|
+
const ver = font.head.indexToLocFormat;
|
|
224
|
+
const len = font.maxp.numGlyphs + 1;
|
|
225
|
+
if (ver === 0) {
|
|
226
|
+
for (let i = 0; i < len; i++) {
|
|
227
|
+
obj.push(bin.readUshort(data, offset + (i << 1)) << 1);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (ver === 1) {
|
|
231
|
+
for (let i = 0; i < len; i++) {
|
|
232
|
+
obj.push(bin.readUint(data, offset + (i << 2)));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return obj;
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
kern: {
|
|
239
|
+
parseTab(data, offset, length, font) {
|
|
240
|
+
const version = bin.readUshort(data, offset);
|
|
241
|
+
if (version === 1) return this.parseV1(data, offset, length, font);
|
|
242
|
+
const nTables = bin.readUshort(data, offset + 2);
|
|
243
|
+
offset += 4;
|
|
244
|
+
const map = { glyph1: [], rval: [] };
|
|
245
|
+
for (let i = 0; i < nTables; i++) {
|
|
246
|
+
offset += 2;
|
|
247
|
+
const length2 = bin.readUshort(data, offset);
|
|
248
|
+
offset += 2;
|
|
249
|
+
const coverage = bin.readUshort(data, offset);
|
|
250
|
+
offset += 2;
|
|
251
|
+
let format = coverage >>> 8;
|
|
252
|
+
format &= 15;
|
|
253
|
+
if (format === 0) offset = this.readFormat0(data, offset, map);
|
|
254
|
+
}
|
|
255
|
+
return map;
|
|
256
|
+
},
|
|
257
|
+
parseV1(data, offset, length, font) {
|
|
258
|
+
const version = bin.readFixed(data, offset);
|
|
259
|
+
const nTables = bin.readUint(data, offset + 4);
|
|
260
|
+
offset += 8;
|
|
261
|
+
const map = { glyph1: [], rval: [] };
|
|
262
|
+
for (let i = 0; i < nTables; i++) {
|
|
263
|
+
const length2 = bin.readUint(data, offset);
|
|
264
|
+
offset += 4;
|
|
265
|
+
const coverage = bin.readUshort(data, offset);
|
|
266
|
+
offset += 2;
|
|
267
|
+
const tupleIndex = bin.readUshort(data, offset);
|
|
268
|
+
offset += 2;
|
|
269
|
+
const format = coverage & 255;
|
|
270
|
+
if (format === 0) offset = this.readFormat0(data, offset, map);
|
|
271
|
+
}
|
|
272
|
+
return map;
|
|
273
|
+
},
|
|
274
|
+
readFormat0(data, offset, map) {
|
|
275
|
+
let pleft = -1;
|
|
276
|
+
const nPairs = bin.readUshort(data, offset);
|
|
277
|
+
const searchRange = bin.readUshort(data, offset + 2);
|
|
278
|
+
const entrySelector = bin.readUshort(data, offset + 4);
|
|
279
|
+
const rangeShift = bin.readUshort(data, offset + 6);
|
|
280
|
+
offset += 8;
|
|
281
|
+
for (let j = 0; j < nPairs; j++) {
|
|
282
|
+
const left = bin.readUshort(data, offset);
|
|
283
|
+
offset += 2;
|
|
284
|
+
const right = bin.readUshort(data, offset);
|
|
285
|
+
offset += 2;
|
|
286
|
+
const value = bin.readShort(data, offset);
|
|
287
|
+
offset += 2;
|
|
288
|
+
if (left !== pleft) {
|
|
289
|
+
map.glyph1.push(left);
|
|
290
|
+
map.rval.push({ glyph2: [], vals: [] });
|
|
291
|
+
}
|
|
292
|
+
const rval = map.rval[map.rval.length - 1];
|
|
293
|
+
rval.glyph2.push(right);
|
|
294
|
+
rval.vals.push(value);
|
|
295
|
+
pleft = left;
|
|
296
|
+
}
|
|
297
|
+
return offset;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
var cmap = {
|
|
302
|
+
parseTab(data, offset, length) {
|
|
303
|
+
const obj = { tables: [], ids: {}, off: offset };
|
|
304
|
+
data = new Uint8Array(data.buffer, offset, length);
|
|
305
|
+
offset = 0;
|
|
306
|
+
const version = bin.readUshort(data, offset);
|
|
307
|
+
offset += 2;
|
|
308
|
+
const numTables = bin.readUshort(data, offset);
|
|
309
|
+
offset += 2;
|
|
310
|
+
const offs = [];
|
|
311
|
+
for (let i = 0; i < numTables; i++) {
|
|
312
|
+
const platformID = bin.readUshort(data, offset);
|
|
313
|
+
offset += 2;
|
|
314
|
+
const encodingID = bin.readUshort(data, offset);
|
|
315
|
+
offset += 2;
|
|
316
|
+
const noffset = bin.readUint(data, offset);
|
|
317
|
+
offset += 4;
|
|
318
|
+
const id = "p" + platformID + "e" + encodingID;
|
|
319
|
+
let tind = offs.indexOf(noffset);
|
|
320
|
+
if (tind === -1) {
|
|
321
|
+
tind = obj.tables.length;
|
|
322
|
+
let subt = {};
|
|
323
|
+
offs.push(noffset);
|
|
324
|
+
const format = subt.format = bin.readUshort(data, noffset);
|
|
325
|
+
if (format === 0) subt = this.parse0(data, noffset, subt);
|
|
326
|
+
else if (format === 4) subt = this.parse4(data, noffset, subt);
|
|
327
|
+
else if (format === 6) subt = this.parse6(data, noffset, subt);
|
|
328
|
+
else if (format === 12) subt = this.parse12(data, noffset, subt);
|
|
329
|
+
obj.tables.push(subt);
|
|
330
|
+
}
|
|
331
|
+
obj.ids[id] = tind;
|
|
332
|
+
}
|
|
333
|
+
return obj;
|
|
334
|
+
},
|
|
335
|
+
parse0(data, offset, obj) {
|
|
336
|
+
const startOffset = offset;
|
|
337
|
+
const format = bin.readUshort(data, offset);
|
|
338
|
+
offset += 2;
|
|
339
|
+
const length = bin.readUshort(data, offset);
|
|
340
|
+
offset += 2;
|
|
341
|
+
const language = bin.readUshort(data, offset);
|
|
342
|
+
offset += 2;
|
|
343
|
+
obj.map = [];
|
|
344
|
+
for (let i = 0; i < 256; i++) obj.map.push(data[offset + i]);
|
|
345
|
+
return obj;
|
|
346
|
+
},
|
|
347
|
+
parse4(data, offset, obj) {
|
|
348
|
+
const startOffset = offset;
|
|
349
|
+
const format = bin.readUshort(data, offset);
|
|
350
|
+
offset += 2;
|
|
351
|
+
const length = bin.readUshort(data, offset);
|
|
352
|
+
offset += 2;
|
|
353
|
+
const language = bin.readUshort(data, offset);
|
|
354
|
+
offset += 2;
|
|
355
|
+
const segCountX2 = bin.readUshort(data, offset);
|
|
356
|
+
offset += 2;
|
|
357
|
+
const segCount = segCountX2 >>> 1;
|
|
358
|
+
obj.searchRange = bin.readUshort(data, offset);
|
|
359
|
+
offset += 2;
|
|
360
|
+
obj.entrySelector = bin.readUshort(data, offset);
|
|
361
|
+
offset += 2;
|
|
362
|
+
obj.rangeShift = bin.readUshort(data, offset);
|
|
363
|
+
offset += 2;
|
|
364
|
+
obj.endCount = bin.readUshorts(data, offset, segCount);
|
|
365
|
+
offset += segCount * 2;
|
|
366
|
+
offset += 2;
|
|
367
|
+
obj.startCount = bin.readUshorts(data, offset, segCount);
|
|
368
|
+
offset += segCount * 2;
|
|
369
|
+
obj.idDelta = [];
|
|
370
|
+
for (let i = 0; i < segCount; i++) {
|
|
371
|
+
obj.idDelta.push(bin.readShort(data, offset));
|
|
372
|
+
offset += 2;
|
|
373
|
+
}
|
|
374
|
+
obj.idRangeOffset = bin.readUshorts(data, offset, segCount);
|
|
375
|
+
offset += segCount * 2;
|
|
376
|
+
obj.glyphIdArray = bin.readUshorts(
|
|
377
|
+
data,
|
|
378
|
+
offset,
|
|
379
|
+
startOffset + length - offset >> 1
|
|
380
|
+
);
|
|
381
|
+
return obj;
|
|
382
|
+
},
|
|
383
|
+
parse6(data, offset, obj) {
|
|
384
|
+
const startOffset = offset;
|
|
385
|
+
const format = bin.readUshort(data, offset);
|
|
386
|
+
offset += 2;
|
|
387
|
+
const length = bin.readUshort(data, offset);
|
|
388
|
+
offset += 2;
|
|
389
|
+
const language = bin.readUshort(data, offset);
|
|
390
|
+
offset += 2;
|
|
391
|
+
obj.firstCode = bin.readUshort(data, offset);
|
|
392
|
+
offset += 2;
|
|
393
|
+
obj.entryCount = bin.readUshort(data, offset);
|
|
394
|
+
offset += 2;
|
|
395
|
+
obj.glyphIdArray = bin.readUshorts(data, offset, obj.entryCount);
|
|
396
|
+
return obj;
|
|
397
|
+
},
|
|
398
|
+
parse12(data, offset, obj) {
|
|
399
|
+
const startOffset = offset;
|
|
400
|
+
offset += 4;
|
|
401
|
+
const length = bin.readUint(data, offset);
|
|
402
|
+
offset += 4;
|
|
403
|
+
const language = bin.readUint(data, offset);
|
|
404
|
+
offset += 4;
|
|
405
|
+
const nGroups = bin.readUint(data, offset) * 3;
|
|
406
|
+
offset += 4;
|
|
407
|
+
obj.groups = new Uint32Array(nGroups);
|
|
408
|
+
for (let i = 0; i < nGroups; i += 3) {
|
|
409
|
+
obj.groups[i] = bin.readUint(data, offset + (i << 2));
|
|
410
|
+
obj.groups[i + 1] = bin.readUint(data, offset + (i << 2) + 4);
|
|
411
|
+
obj.groups[i + 2] = bin.readUint(data, offset + (i << 2) + 8);
|
|
412
|
+
}
|
|
413
|
+
return obj;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
var glyf = {
|
|
417
|
+
parseTab(data, offset, length, font) {
|
|
418
|
+
const obj = [];
|
|
419
|
+
const ng = font.maxp.numGlyphs;
|
|
420
|
+
for (let g = 0; g < ng; g++) obj.push(null);
|
|
421
|
+
return obj;
|
|
422
|
+
},
|
|
423
|
+
parseGlyf(font, g) {
|
|
424
|
+
const data = font._data;
|
|
425
|
+
const loca = font.loca;
|
|
426
|
+
if (loca[g] === loca[g + 1]) return null;
|
|
427
|
+
const offset = findTable(data, "glyf", font._offset)[0] + loca[g];
|
|
428
|
+
const gl = {};
|
|
429
|
+
gl.noc = bin.readShort(data, offset);
|
|
430
|
+
let off = offset + 2;
|
|
431
|
+
gl.xMin = bin.readShort(data, off);
|
|
432
|
+
off += 2;
|
|
433
|
+
gl.yMin = bin.readShort(data, off);
|
|
434
|
+
off += 2;
|
|
435
|
+
gl.xMax = bin.readShort(data, off);
|
|
436
|
+
off += 2;
|
|
437
|
+
gl.yMax = bin.readShort(data, off);
|
|
438
|
+
off += 2;
|
|
439
|
+
if (gl.xMin >= gl.xMax || gl.yMin >= gl.yMax) return null;
|
|
440
|
+
if (gl.noc > 0) {
|
|
441
|
+
gl.endPts = [];
|
|
442
|
+
for (let i = 0; i < gl.noc; i++) {
|
|
443
|
+
gl.endPts.push(bin.readUshort(data, off));
|
|
444
|
+
off += 2;
|
|
445
|
+
}
|
|
446
|
+
const instructionLength = bin.readUshort(data, off);
|
|
447
|
+
off += 2;
|
|
448
|
+
if (data.length - off < instructionLength) return null;
|
|
449
|
+
gl.instructions = bin.readBytes(data, off, instructionLength);
|
|
450
|
+
off += instructionLength;
|
|
451
|
+
const crdnum = gl.endPts[gl.noc - 1] + 1;
|
|
452
|
+
gl.flags = [];
|
|
453
|
+
for (let i = 0; i < crdnum; i++) {
|
|
454
|
+
const flag = data[off];
|
|
455
|
+
off++;
|
|
456
|
+
gl.flags.push(flag);
|
|
457
|
+
if ((flag & 8) !== 0) {
|
|
458
|
+
const rep = data[off];
|
|
459
|
+
off++;
|
|
460
|
+
for (let j = 0; j < rep; j++) {
|
|
461
|
+
gl.flags.push(flag);
|
|
462
|
+
i++;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
gl.xs = [];
|
|
467
|
+
for (let i = 0; i < crdnum; i++) {
|
|
468
|
+
const i8 = (gl.flags[i] & 2) !== 0;
|
|
469
|
+
const same = (gl.flags[i] & 16) !== 0;
|
|
470
|
+
if (i8) {
|
|
471
|
+
gl.xs.push(same ? data[off] : -data[off]);
|
|
472
|
+
off++;
|
|
473
|
+
} else {
|
|
474
|
+
if (same) gl.xs.push(0);
|
|
475
|
+
else {
|
|
476
|
+
gl.xs.push(bin.readShort(data, off));
|
|
477
|
+
off += 2;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
gl.ys = [];
|
|
482
|
+
for (let i = 0; i < crdnum; i++) {
|
|
483
|
+
const i8 = (gl.flags[i] & 4) !== 0;
|
|
484
|
+
const same = (gl.flags[i] & 32) !== 0;
|
|
485
|
+
if (i8) {
|
|
486
|
+
gl.ys.push(same ? data[off] : -data[off]);
|
|
487
|
+
off++;
|
|
488
|
+
} else {
|
|
489
|
+
if (same) gl.ys.push(0);
|
|
490
|
+
else {
|
|
491
|
+
gl.ys.push(bin.readShort(data, off));
|
|
492
|
+
off += 2;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
let x = 0, y = 0;
|
|
497
|
+
for (let i = 0; i < crdnum; i++) {
|
|
498
|
+
x += gl.xs[i];
|
|
499
|
+
y += gl.ys[i];
|
|
500
|
+
gl.xs[i] = x;
|
|
501
|
+
gl.ys[i] = y;
|
|
502
|
+
}
|
|
503
|
+
} else {
|
|
504
|
+
const ARG_1_AND_2_ARE_WORDS = 1;
|
|
505
|
+
const ARGS_ARE_XY_VALUES = 2;
|
|
506
|
+
const WE_HAVE_A_SCALE = 8;
|
|
507
|
+
const MORE_COMPONENTS = 32;
|
|
508
|
+
const WE_HAVE_AN_X_AND_Y_SCALE = 64;
|
|
509
|
+
const WE_HAVE_A_TWO_BY_TWO = 128;
|
|
510
|
+
const WE_HAVE_INSTRUCTIONS = 256;
|
|
511
|
+
gl.parts = [];
|
|
512
|
+
let flags;
|
|
513
|
+
do {
|
|
514
|
+
flags = bin.readUshort(data, off);
|
|
515
|
+
off += 2;
|
|
516
|
+
const part = {
|
|
517
|
+
m: { a: 1, b: 0, c: 0, d: 1, tx: 0, ty: 0 },
|
|
518
|
+
p1: -1,
|
|
519
|
+
p2: -1
|
|
520
|
+
};
|
|
521
|
+
gl.parts.push(part);
|
|
522
|
+
part.glyphIndex = bin.readUshort(data, off);
|
|
523
|
+
off += 2;
|
|
524
|
+
let arg1, arg2;
|
|
525
|
+
if (flags & ARG_1_AND_2_ARE_WORDS) {
|
|
526
|
+
arg1 = bin.readShort(data, off);
|
|
527
|
+
off += 2;
|
|
528
|
+
arg2 = bin.readShort(data, off);
|
|
529
|
+
off += 2;
|
|
530
|
+
} else {
|
|
531
|
+
arg1 = bin.readInt8(data, off);
|
|
532
|
+
off++;
|
|
533
|
+
arg2 = bin.readInt8(data, off);
|
|
534
|
+
off++;
|
|
535
|
+
}
|
|
536
|
+
if (flags & ARGS_ARE_XY_VALUES) {
|
|
537
|
+
part.m.tx = arg1;
|
|
538
|
+
part.m.ty = arg2;
|
|
539
|
+
} else {
|
|
540
|
+
part.p1 = arg1;
|
|
541
|
+
part.p2 = arg2;
|
|
542
|
+
}
|
|
543
|
+
if (flags & WE_HAVE_A_SCALE) {
|
|
544
|
+
part.m.a = part.m.d = bin.readF2dot14(data, off);
|
|
545
|
+
off += 2;
|
|
546
|
+
} else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
|
|
547
|
+
part.m.a = bin.readF2dot14(data, off);
|
|
548
|
+
off += 2;
|
|
549
|
+
part.m.d = bin.readF2dot14(data, off);
|
|
550
|
+
off += 2;
|
|
551
|
+
} else if (flags & WE_HAVE_A_TWO_BY_TWO) {
|
|
552
|
+
part.m.a = bin.readF2dot14(data, off);
|
|
553
|
+
off += 2;
|
|
554
|
+
part.m.b = bin.readF2dot14(data, off);
|
|
555
|
+
off += 2;
|
|
556
|
+
part.m.c = bin.readF2dot14(data, off);
|
|
557
|
+
off += 2;
|
|
558
|
+
part.m.d = bin.readF2dot14(data, off);
|
|
559
|
+
off += 2;
|
|
560
|
+
}
|
|
561
|
+
} while (flags & MORE_COMPONENTS);
|
|
562
|
+
if (flags & WE_HAVE_INSTRUCTIONS) {
|
|
563
|
+
const numInstr = bin.readUshort(data, off);
|
|
564
|
+
off += 2;
|
|
565
|
+
gl.instr = [];
|
|
566
|
+
for (let i = 0; i < numInstr; i++) {
|
|
567
|
+
gl.instr.push(data[off]);
|
|
568
|
+
off++;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return gl;
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
var VariableTables = {
|
|
576
|
+
fvar: {
|
|
577
|
+
parseTab(data, offset, length, obj) {
|
|
578
|
+
const name = obj.name;
|
|
579
|
+
let off = offset;
|
|
580
|
+
const axes = [];
|
|
581
|
+
const inst = [];
|
|
582
|
+
off += 8;
|
|
583
|
+
const acnt = bin.readUshort(data, off);
|
|
584
|
+
off += 2;
|
|
585
|
+
off += 2;
|
|
586
|
+
const icnt = bin.readUshort(data, off);
|
|
587
|
+
off += 2;
|
|
588
|
+
const isiz = bin.readUshort(data, off);
|
|
589
|
+
off += 2;
|
|
590
|
+
for (let i = 0; i < acnt; i++) {
|
|
591
|
+
const tag = bin.readASCII(data, off, 4);
|
|
592
|
+
const min = bin.readFixed(data, off + 4);
|
|
593
|
+
const def = bin.readFixed(data, off + 8);
|
|
594
|
+
const max = bin.readFixed(data, off + 12);
|
|
595
|
+
const flg = bin.readUshort(data, off + 16);
|
|
596
|
+
const nid = bin.readUshort(data, off + 18);
|
|
597
|
+
axes.push([tag, min, def, max, flg, name ? name["_" + nid] : null]);
|
|
598
|
+
off += 20;
|
|
599
|
+
}
|
|
600
|
+
for (let i = 0; i < icnt; i++) {
|
|
601
|
+
const snid = bin.readUshort(data, off);
|
|
602
|
+
let pnid = null;
|
|
603
|
+
const flg = bin.readUshort(data, off + 2);
|
|
604
|
+
const crd = [];
|
|
605
|
+
for (let j = 0; j < acnt; j++) {
|
|
606
|
+
crd.push(bin.readFixed(data, off + 4 + j * 4));
|
|
607
|
+
}
|
|
608
|
+
off += 4 + acnt * 4;
|
|
609
|
+
if ((isiz & 3) === 2) {
|
|
610
|
+
pnid = bin.readUshort(data, off);
|
|
611
|
+
off += 2;
|
|
612
|
+
}
|
|
613
|
+
inst.push([name ? name["_" + snid] : null, flg, crd, pnid]);
|
|
614
|
+
}
|
|
615
|
+
return [axes, inst];
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
avar: {
|
|
619
|
+
parseTab(data, offset, length, obj) {
|
|
620
|
+
let off = offset;
|
|
621
|
+
const out = [];
|
|
622
|
+
off += 6;
|
|
623
|
+
const acnt = bin.readUshort(data, off);
|
|
624
|
+
off += 2;
|
|
625
|
+
for (let ai = 0; ai < acnt; ai++) {
|
|
626
|
+
const cnt = bin.readUshort(data, off);
|
|
627
|
+
off += 2;
|
|
628
|
+
const poly = [];
|
|
629
|
+
out.push(poly);
|
|
630
|
+
for (let i = 0; i < cnt; i++) {
|
|
631
|
+
const x = bin.readF2dot14(data, off);
|
|
632
|
+
const y = bin.readF2dot14(data, off + 2);
|
|
633
|
+
off += 4;
|
|
634
|
+
poly.push(x, y);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return out;
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
gvar: {
|
|
641
|
+
parseTab(data, offset, length, obj) {
|
|
642
|
+
const EMBEDDED_PEAK_TUPLE = 32768;
|
|
643
|
+
const INTERMEDIATE_REGION = 16384;
|
|
644
|
+
const PRIVATE_POINT_NUMBERS = 8192;
|
|
645
|
+
const DELTAS_ARE_ZERO = 128;
|
|
646
|
+
const DELTAS_ARE_WORDS = 64;
|
|
647
|
+
const POINTS_ARE_WORDS = 128;
|
|
648
|
+
const SHARED_POINT_NUMBERS = 32768;
|
|
649
|
+
function readTuple(data2, o, acnt2) {
|
|
650
|
+
const tup = [];
|
|
651
|
+
for (let j = 0; j < acnt2; j++)
|
|
652
|
+
tup.push(bin.readF2dot14(data2, o + j * 2));
|
|
653
|
+
return tup;
|
|
654
|
+
}
|
|
655
|
+
function readTupleVarHeader(data2, off2, vcnt, acnt2, eoff) {
|
|
656
|
+
const out = [];
|
|
657
|
+
for (let j = 0; j < vcnt; j++) {
|
|
658
|
+
const dsiz = bin.readUshort(data2, off2);
|
|
659
|
+
off2 += 2;
|
|
660
|
+
const tind = bin.readUshort(data2, off2);
|
|
661
|
+
const flag = tind & 61440;
|
|
662
|
+
const tupleIndex = tind & 4095;
|
|
663
|
+
off2 += 2;
|
|
664
|
+
let peak = null, start = null, end = null;
|
|
665
|
+
if (flag & EMBEDDED_PEAK_TUPLE) {
|
|
666
|
+
peak = readTuple(data2, off2, acnt2);
|
|
667
|
+
off2 += acnt2 * 2;
|
|
668
|
+
}
|
|
669
|
+
if (flag & INTERMEDIATE_REGION) {
|
|
670
|
+
start = readTuple(data2, off2, acnt2);
|
|
671
|
+
off2 += acnt2 * 2;
|
|
672
|
+
}
|
|
673
|
+
if (flag & INTERMEDIATE_REGION) {
|
|
674
|
+
end = readTuple(data2, off2, acnt2);
|
|
675
|
+
off2 += acnt2 * 2;
|
|
676
|
+
}
|
|
677
|
+
out.push([dsiz, tupleIndex, flag, start, peak, end]);
|
|
678
|
+
}
|
|
679
|
+
return out;
|
|
680
|
+
}
|
|
681
|
+
function readPointNumbers(data2, off2, gid) {
|
|
682
|
+
let cnt = data2[off2];
|
|
683
|
+
off2++;
|
|
684
|
+
if (cnt === 0) return [[], off2];
|
|
685
|
+
if (127 < cnt) {
|
|
686
|
+
cnt = (cnt & 127) << 8 | data2[off2++];
|
|
687
|
+
}
|
|
688
|
+
const pts = [];
|
|
689
|
+
let last = 0;
|
|
690
|
+
while (pts.length < cnt) {
|
|
691
|
+
const v = data2[off2];
|
|
692
|
+
off2++;
|
|
693
|
+
const wds = (v & POINTS_ARE_WORDS) !== 0;
|
|
694
|
+
const runLength = (v & 127) + 1;
|
|
695
|
+
for (let i = 0; i < runLength; i++) {
|
|
696
|
+
let dif = 0;
|
|
697
|
+
if (wds) {
|
|
698
|
+
dif = bin.readUshort(data2, off2);
|
|
699
|
+
off2 += 2;
|
|
700
|
+
} else {
|
|
701
|
+
dif = data2[off2];
|
|
702
|
+
off2++;
|
|
703
|
+
}
|
|
704
|
+
last += dif;
|
|
705
|
+
pts.push(last);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return [pts, off2];
|
|
709
|
+
}
|
|
710
|
+
let off = offset + 4;
|
|
711
|
+
const acnt = bin.readUshort(data, off);
|
|
712
|
+
off += 2;
|
|
713
|
+
const tcnt = bin.readUshort(data, off);
|
|
714
|
+
off += 2;
|
|
715
|
+
const toff = bin.readUint(data, off);
|
|
716
|
+
off += 4;
|
|
717
|
+
const gcnt = bin.readUshort(data, off);
|
|
718
|
+
off += 2;
|
|
719
|
+
const flgs = bin.readUshort(data, off);
|
|
720
|
+
off += 2;
|
|
721
|
+
const goff = bin.readUint(data, off);
|
|
722
|
+
off += 4;
|
|
723
|
+
const offs = [];
|
|
724
|
+
for (let i = 0; i < gcnt + 1; i++) {
|
|
725
|
+
offs.push(bin.readUint(data, off + i * 4));
|
|
726
|
+
}
|
|
727
|
+
const tups = [], mins = [], maxs = [];
|
|
728
|
+
off = offset + toff;
|
|
729
|
+
for (let i = 0; i < tcnt; i++) {
|
|
730
|
+
const peak = readTuple(data, off + i * acnt * 2, acnt);
|
|
731
|
+
const imin = [], imax = [];
|
|
732
|
+
tups.push(peak);
|
|
733
|
+
mins.push(imin);
|
|
734
|
+
maxs.push(imax);
|
|
735
|
+
for (let k = 0; k < acnt; k++) {
|
|
736
|
+
imin[k] = Math.min(peak[k], 0);
|
|
737
|
+
imax[k] = Math.max(peak[k], 0);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
const i8 = new Int8Array(data.buffer);
|
|
741
|
+
const tabs = [];
|
|
742
|
+
for (let i = 0; i < gcnt; i++) {
|
|
743
|
+
off = offset + goff + offs[i];
|
|
744
|
+
let vcnt = bin.readUshort(data, off);
|
|
745
|
+
off += 2;
|
|
746
|
+
const snum = vcnt & SHARED_POINT_NUMBERS;
|
|
747
|
+
vcnt &= 4095;
|
|
748
|
+
const soff = bin.readUshort(data, off);
|
|
749
|
+
off += 2;
|
|
750
|
+
const hdr = readTupleVarHeader(
|
|
751
|
+
data,
|
|
752
|
+
off,
|
|
753
|
+
vcnt,
|
|
754
|
+
acnt,
|
|
755
|
+
offset + goff + offs[i + 1]
|
|
756
|
+
);
|
|
757
|
+
const tab = [];
|
|
758
|
+
tabs.push(tab);
|
|
759
|
+
off = offset + goff + offs[i] + soff;
|
|
760
|
+
let sind = [];
|
|
761
|
+
if (snum) {
|
|
762
|
+
const oo = readPointNumbers(data, off, i);
|
|
763
|
+
sind = oo[0];
|
|
764
|
+
off = oo[1];
|
|
765
|
+
}
|
|
766
|
+
for (let j = 0; j < vcnt; j++) {
|
|
767
|
+
const vr = hdr[j];
|
|
768
|
+
const end = off + vr[0];
|
|
769
|
+
let ind = sind;
|
|
770
|
+
if (vr[2] & PRIVATE_POINT_NUMBERS) {
|
|
771
|
+
const oo = readPointNumbers(data, off, i);
|
|
772
|
+
ind = oo[0];
|
|
773
|
+
off = oo[1];
|
|
774
|
+
}
|
|
775
|
+
const ds = [];
|
|
776
|
+
while (off < end) {
|
|
777
|
+
const cb = data[off++];
|
|
778
|
+
const cnt = (cb & 63) + 1;
|
|
779
|
+
if (cb & DELTAS_ARE_ZERO) {
|
|
780
|
+
for (let k = 0; k < cnt; k++) ds.push(0);
|
|
781
|
+
} else if (cb & DELTAS_ARE_WORDS) {
|
|
782
|
+
for (let k = 0; k < cnt; k++) {
|
|
783
|
+
ds.push(bin.readShort(data, off + k * 2));
|
|
784
|
+
}
|
|
785
|
+
off += cnt * 2;
|
|
786
|
+
} else {
|
|
787
|
+
for (let k = 0; k < cnt; k++) ds.push(i8[off + k]);
|
|
788
|
+
off += cnt;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
const ti = vr[1];
|
|
792
|
+
tab.push([
|
|
793
|
+
[
|
|
794
|
+
vr[3] ? vr[3] : mins[ti],
|
|
795
|
+
vr[4] ? vr[4] : tups[ti],
|
|
796
|
+
vr[5] ? vr[5] : maxs[ti]
|
|
797
|
+
],
|
|
798
|
+
ds,
|
|
799
|
+
ind.length == 0 ? null : ind
|
|
800
|
+
]);
|
|
801
|
+
if (ind.length != 0 && ind.length * 2 != ds.length) throw "e";
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return tabs;
|
|
805
|
+
}
|
|
806
|
+
},
|
|
807
|
+
HVAR: {
|
|
808
|
+
parseTab(data, offset, length, obj) {
|
|
809
|
+
let off = offset;
|
|
810
|
+
const oo = offset;
|
|
811
|
+
off += 4;
|
|
812
|
+
const varO = bin.readUint(data, off);
|
|
813
|
+
off += 4;
|
|
814
|
+
const advO = bin.readUint(data, off);
|
|
815
|
+
off += 4;
|
|
816
|
+
const lsbO = bin.readUint(data, off);
|
|
817
|
+
off += 4;
|
|
818
|
+
const rsbO = bin.readUint(data, off);
|
|
819
|
+
off += 4;
|
|
820
|
+
if (lsbO !== 0 || rsbO !== 0) throw lsbO;
|
|
821
|
+
off = oo + varO;
|
|
822
|
+
const ioff = off;
|
|
823
|
+
const fmt = bin.readUshort(data, off);
|
|
824
|
+
off += 2;
|
|
825
|
+
if (fmt !== 1) throw "e";
|
|
826
|
+
const vregO = bin.readUint(data, off);
|
|
827
|
+
off += 4;
|
|
828
|
+
const vcnt = bin.readUshort(data, off);
|
|
829
|
+
off += 2;
|
|
830
|
+
const offs = [];
|
|
831
|
+
for (let i = 0; i < vcnt; i++) offs.push(bin.readUint(data, off + i * 4));
|
|
832
|
+
off += vcnt * 4;
|
|
833
|
+
off = ioff + vregO;
|
|
834
|
+
const acnt = bin.readUshort(data, off);
|
|
835
|
+
off += 2;
|
|
836
|
+
const rcnt = bin.readUshort(data, off);
|
|
837
|
+
off += 2;
|
|
838
|
+
const regs = [];
|
|
839
|
+
for (let i = 0; i < rcnt; i++) {
|
|
840
|
+
const crd = [[], [], []];
|
|
841
|
+
regs.push(crd);
|
|
842
|
+
for (let j = 0; j < acnt; j++) {
|
|
843
|
+
crd[0].push(bin.readF2dot14(data, off + 0));
|
|
844
|
+
crd[1].push(bin.readF2dot14(data, off + 2));
|
|
845
|
+
crd[2].push(bin.readF2dot14(data, off + 4));
|
|
846
|
+
off += 6;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
const i8 = new Int8Array(data.buffer);
|
|
850
|
+
const varStore = [];
|
|
851
|
+
for (let i = 0; i < offs.length; i++) {
|
|
852
|
+
off = oo + varO + offs[i];
|
|
853
|
+
const vdata = [];
|
|
854
|
+
varStore.push(vdata);
|
|
855
|
+
const icnt = bin.readUshort(data, off);
|
|
856
|
+
off += 2;
|
|
857
|
+
const dcnt = bin.readUshort(data, off);
|
|
858
|
+
off += 2;
|
|
859
|
+
if (dcnt & 32768) throw "e";
|
|
860
|
+
const rcnt2 = bin.readUshort(data, off);
|
|
861
|
+
off += 2;
|
|
862
|
+
const ixs = [];
|
|
863
|
+
for (let j = 0; j < rcnt2; j++) {
|
|
864
|
+
ixs.push(bin.readUshort(data, off + j * 2));
|
|
865
|
+
}
|
|
866
|
+
off += rcnt2 * 2;
|
|
867
|
+
for (let k = 0; k < icnt; k++) {
|
|
868
|
+
const deltaData = [];
|
|
869
|
+
for (let ri = 0; ri < rcnt2; ri++) {
|
|
870
|
+
deltaData.push(ri < dcnt ? bin.readShort(data, off) : i8[off]);
|
|
871
|
+
off += ri < dcnt ? 2 : 1;
|
|
872
|
+
}
|
|
873
|
+
const dd = new Array(regs.length);
|
|
874
|
+
dd.fill(0);
|
|
875
|
+
vdata.push(dd);
|
|
876
|
+
for (let j = 0; j < ixs.length; j++) dd[ixs[j]] = deltaData[j];
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
off = oo + advO;
|
|
880
|
+
const fmt2 = data[off++];
|
|
881
|
+
if (fmt2 !== 0) throw "e";
|
|
882
|
+
const entryFormat = data[off++];
|
|
883
|
+
const mapCount = bin.readUshort(data, off);
|
|
884
|
+
off += 2;
|
|
885
|
+
const INNER_INDEX_BIT_COUNT_MASK = 15;
|
|
886
|
+
const MAP_ENTRY_SIZE_MASK = 48;
|
|
887
|
+
const entrySize = ((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1;
|
|
888
|
+
const dfs = [];
|
|
889
|
+
for (let i = 0; i < mapCount; i++) {
|
|
890
|
+
let entry = 0;
|
|
891
|
+
if (entrySize === 1) entry = data[off++];
|
|
892
|
+
else {
|
|
893
|
+
entry = bin.readUshort(data, off);
|
|
894
|
+
off += 2;
|
|
895
|
+
}
|
|
896
|
+
const outerIndex = entry >> (entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1;
|
|
897
|
+
const innerIndex = entry & (1 << (entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1) - 1;
|
|
898
|
+
dfs.push(varStore[outerIndex][innerIndex]);
|
|
899
|
+
}
|
|
900
|
+
return [regs, dfs];
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
var PathBuilder = {
|
|
905
|
+
MoveTo(p, x, y) {
|
|
906
|
+
p.cmds.push("M");
|
|
907
|
+
p.crds.push(x, y);
|
|
908
|
+
},
|
|
909
|
+
LineTo(p, x, y) {
|
|
910
|
+
p.cmds.push("L");
|
|
911
|
+
p.crds.push(x, y);
|
|
912
|
+
},
|
|
913
|
+
qCurveTo(p, a, b, c, d) {
|
|
914
|
+
p.cmds.push("Q");
|
|
915
|
+
p.crds.push(a, b, c, d);
|
|
916
|
+
},
|
|
917
|
+
ClosePath(p) {
|
|
918
|
+
p.cmds.push("Z");
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
function codeToGlyph(font, code) {
|
|
922
|
+
if (font._ctab == null) {
|
|
923
|
+
const cmap2 = font.cmap;
|
|
924
|
+
let tind = -1;
|
|
925
|
+
const pps = [
|
|
926
|
+
"p3e10",
|
|
927
|
+
"p0e4",
|
|
928
|
+
"p3e1",
|
|
929
|
+
"p1e0",
|
|
930
|
+
"p0e3",
|
|
931
|
+
"p0e1",
|
|
932
|
+
"p3e0",
|
|
933
|
+
"p3e5"
|
|
934
|
+
];
|
|
935
|
+
for (let i = 0; i < pps.length; i++) {
|
|
936
|
+
if (cmap2.ids[pps[i]] != null) {
|
|
937
|
+
tind = cmap2.ids[pps[i]];
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (tind === -1) throw "no familiar platform and encoding!";
|
|
942
|
+
font._ctab = cmap2.tables[tind];
|
|
943
|
+
}
|
|
944
|
+
const tab = font._ctab;
|
|
945
|
+
const fmt = tab.format;
|
|
946
|
+
let gid = -1;
|
|
947
|
+
if (fmt === 0) {
|
|
948
|
+
if (code >= tab.map.length) gid = 0;
|
|
949
|
+
else gid = tab.map[code];
|
|
950
|
+
} else if (fmt === 4) {
|
|
951
|
+
const ec = tab.endCount;
|
|
952
|
+
gid = 0;
|
|
953
|
+
if (code <= ec[ec.length - 1]) {
|
|
954
|
+
let sind = arrSearch(ec, 1, code);
|
|
955
|
+
if (ec[sind] < code) sind++;
|
|
956
|
+
if (code >= tab.startCount[sind]) {
|
|
957
|
+
let gli = 0;
|
|
958
|
+
if (tab.idRangeOffset[sind] !== 0) {
|
|
959
|
+
gli = tab.glyphIdArray[code - tab.startCount[sind] + (tab.idRangeOffset[sind] >> 1) - (tab.idRangeOffset.length - sind)];
|
|
960
|
+
} else {
|
|
961
|
+
gli = code + tab.idDelta[sind];
|
|
962
|
+
}
|
|
963
|
+
gid = gli & 65535;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
} else if (fmt === 6) {
|
|
967
|
+
const off = code - tab.firstCode;
|
|
968
|
+
const arr = tab.glyphIdArray;
|
|
969
|
+
if (off < 0 || off >= arr.length) gid = 0;
|
|
970
|
+
else gid = arr[off];
|
|
971
|
+
} else if (fmt === 12) {
|
|
972
|
+
const grp = tab.groups;
|
|
973
|
+
gid = 0;
|
|
974
|
+
if (code <= grp[grp.length - 2]) {
|
|
975
|
+
const i = arrSearch(grp, 3, code);
|
|
976
|
+
if (grp[i] <= code && code <= grp[i + 1]) {
|
|
977
|
+
gid = grp[i + 2] + (code - grp[i]);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
} else {
|
|
981
|
+
throw "unknown cmap table format " + tab.format;
|
|
982
|
+
}
|
|
983
|
+
return gid;
|
|
984
|
+
}
|
|
985
|
+
function arrSearch(arr, k, v) {
|
|
986
|
+
let l = 0;
|
|
987
|
+
let r = Math.floor(arr.length / k);
|
|
988
|
+
while (l + 1 !== r) {
|
|
989
|
+
const mid = l + (r - l >>> 1);
|
|
990
|
+
if (arr[mid * k] <= v) l = mid;
|
|
991
|
+
else r = mid;
|
|
992
|
+
}
|
|
993
|
+
return l * k;
|
|
994
|
+
}
|
|
995
|
+
function getGlyphPosition(font, gls, i1) {
|
|
996
|
+
const g1 = gls[i1];
|
|
997
|
+
const g2 = gls[i1 + 1];
|
|
998
|
+
const kern = font.kern;
|
|
999
|
+
if (kern) {
|
|
1000
|
+
const ind1 = kern.glyph1.indexOf(g1);
|
|
1001
|
+
if (ind1 !== -1) {
|
|
1002
|
+
const ind2 = kern.rval[ind1].glyph2.indexOf(g2);
|
|
1003
|
+
if (ind2 !== -1) return [0, 0, kern.rval[ind1].vals[ind2], 0];
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return [0, 0, 0, 0];
|
|
1007
|
+
}
|
|
1008
|
+
function normalizeAxis(font, vv) {
|
|
1009
|
+
const fvar = font.fvar;
|
|
1010
|
+
const avar = font.avar;
|
|
1011
|
+
const fv = fvar ? fvar[0] : null;
|
|
1012
|
+
const nv = [];
|
|
1013
|
+
for (let i = 0; i < fv.length; i++) {
|
|
1014
|
+
const min = fv[i][1];
|
|
1015
|
+
const def = fv[i][2];
|
|
1016
|
+
const max = fv[i][3];
|
|
1017
|
+
const v = Math.max(min, Math.min(max, vv[i]));
|
|
1018
|
+
if (v < def) nv[i] = (def - v) / (min - def);
|
|
1019
|
+
else if (v > def) nv[i] = (v - def) / (max - def);
|
|
1020
|
+
else nv[i] = 0;
|
|
1021
|
+
if (avar && nv[i] !== -1) {
|
|
1022
|
+
const av = avar[i];
|
|
1023
|
+
let j = 0;
|
|
1024
|
+
for (; j < av.length; j += 2) if (av[j] >= nv[i]) break;
|
|
1025
|
+
const f = (nv[i] - av[j - 2]) / (av[j] - av[j - 2]);
|
|
1026
|
+
nv[i] = f * av[j + 1] + (1 - f) * av[j - 1];
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return nv;
|
|
1030
|
+
}
|
|
1031
|
+
function shape(font, str, prm = {}) {
|
|
1032
|
+
let axs = prm.axs;
|
|
1033
|
+
if (font.fvar && axs == null) axs = font.fvar[1][font._index || 0][2];
|
|
1034
|
+
const HVAR = font.HVAR;
|
|
1035
|
+
if (axs && HVAR) {
|
|
1036
|
+
axs = normalizeAxis(font, axs);
|
|
1037
|
+
}
|
|
1038
|
+
const gls = [];
|
|
1039
|
+
for (let i = 0; i < str.length; i++) {
|
|
1040
|
+
const cc = str.codePointAt(i);
|
|
1041
|
+
if (cc > 65535) i++;
|
|
1042
|
+
gls.push(codeToGlyph(font, cc));
|
|
1043
|
+
}
|
|
1044
|
+
const shape2 = [];
|
|
1045
|
+
let x = 0, y = 0;
|
|
1046
|
+
for (let i = 0; i < gls.length; i++) {
|
|
1047
|
+
const padj = getGlyphPosition(font, gls, i);
|
|
1048
|
+
const gid = gls[i];
|
|
1049
|
+
let ax = font.hmtx.aWidth[gid] + padj[2];
|
|
1050
|
+
if (HVAR && HVAR[1][gid]) {
|
|
1051
|
+
const difs = HVAR[1][gid];
|
|
1052
|
+
for (let j = 0; j < HVAR[0].length; j++) {
|
|
1053
|
+
ax += _interpolate(HVAR[0][j], axs) * difs[j];
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
shape2.push({ g: gid, cl: i, dx: 0, dy: 0, ax, ay: 0 });
|
|
1057
|
+
x += ax;
|
|
1058
|
+
}
|
|
1059
|
+
return shape2;
|
|
1060
|
+
}
|
|
1061
|
+
function glyphToPath(font, gid, noColor, axs) {
|
|
1062
|
+
const path = { cmds: [], crds: [] };
|
|
1063
|
+
if (font.fvar) {
|
|
1064
|
+
if (axs == null) axs = font.fvar[1][font._index || 0][2];
|
|
1065
|
+
axs = normalizeAxis(font, axs);
|
|
1066
|
+
}
|
|
1067
|
+
if (font.glyf) {
|
|
1068
|
+
drawGlyf(gid, font, path, axs);
|
|
1069
|
+
}
|
|
1070
|
+
return { cmds: path.cmds, crds: path.crds };
|
|
1071
|
+
}
|
|
1072
|
+
function drawGlyf(gid, font, path, axs) {
|
|
1073
|
+
let gl = font.glyf[gid];
|
|
1074
|
+
if (gl == null) {
|
|
1075
|
+
gl = font.glyf[gid] = glyf.parseGlyf(font, gid);
|
|
1076
|
+
}
|
|
1077
|
+
if (gl != null) {
|
|
1078
|
+
if (gl.noc > -1) simpleGlyph(gl, font, gid, path, axs);
|
|
1079
|
+
else compoGlyph(gl, font, gid, path, axs);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
function simpleGlyph(gl, font, gid, p, axs) {
|
|
1083
|
+
let xs = gl.xs;
|
|
1084
|
+
let ys = gl.ys;
|
|
1085
|
+
if (font.fvar && axs) {
|
|
1086
|
+
xs = xs.slice(0);
|
|
1087
|
+
ys = ys.slice(0);
|
|
1088
|
+
const gvar = font.gvar;
|
|
1089
|
+
const gv = gvar ? gvar[gid] : null;
|
|
1090
|
+
if (gv) {
|
|
1091
|
+
for (let vi = 0; vi < gv.length; vi++) {
|
|
1092
|
+
const axv = gv[vi][0];
|
|
1093
|
+
const S = _interpolate(axv, axs);
|
|
1094
|
+
if (S < 1e-9) continue;
|
|
1095
|
+
let dfs = gv[vi][1];
|
|
1096
|
+
const ind = gv[vi][2];
|
|
1097
|
+
if (ind) {
|
|
1098
|
+
dfs = gv[vi][1] = interpolateDeltas(dfs, ind, xs, ys, gl.endPts);
|
|
1099
|
+
gv[vi][2] = null;
|
|
1100
|
+
}
|
|
1101
|
+
if (dfs.length === xs.length * 2 + 8) {
|
|
1102
|
+
for (let i = 0; i < xs.length; i++) {
|
|
1103
|
+
xs[i] += S * dfs[i];
|
|
1104
|
+
ys[i] += S * dfs[i + xs.length + 4];
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
for (let c = 0; c < gl.noc; c++) {
|
|
1111
|
+
const i0 = c === 0 ? 0 : gl.endPts[c - 1] + 1;
|
|
1112
|
+
const il = gl.endPts[c];
|
|
1113
|
+
for (let i = i0; i <= il; i++) {
|
|
1114
|
+
const pr = i === i0 ? il : i - 1;
|
|
1115
|
+
const nx = i === il ? i0 : i + 1;
|
|
1116
|
+
const onCurve = gl.flags[i] & 1;
|
|
1117
|
+
const prOnCurve = gl.flags[pr] & 1;
|
|
1118
|
+
const nxOnCurve = gl.flags[nx] & 1;
|
|
1119
|
+
const x = xs[i], y = ys[i];
|
|
1120
|
+
if (i === i0) {
|
|
1121
|
+
if (onCurve) {
|
|
1122
|
+
if (prOnCurve) PathBuilder.MoveTo(p, xs[pr], ys[pr]);
|
|
1123
|
+
else {
|
|
1124
|
+
PathBuilder.MoveTo(p, x, y);
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
} else {
|
|
1128
|
+
if (prOnCurve) PathBuilder.MoveTo(p, xs[pr], ys[pr]);
|
|
1129
|
+
else
|
|
1130
|
+
PathBuilder.MoveTo(
|
|
1131
|
+
p,
|
|
1132
|
+
Math.floor((xs[pr] + x) * 0.5),
|
|
1133
|
+
Math.floor((ys[pr] + y) * 0.5)
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
if (onCurve) {
|
|
1138
|
+
if (prOnCurve) PathBuilder.LineTo(p, x, y);
|
|
1139
|
+
} else {
|
|
1140
|
+
if (nxOnCurve) PathBuilder.qCurveTo(p, x, y, xs[nx], ys[nx]);
|
|
1141
|
+
else
|
|
1142
|
+
PathBuilder.qCurveTo(
|
|
1143
|
+
p,
|
|
1144
|
+
x,
|
|
1145
|
+
y,
|
|
1146
|
+
Math.floor((x + xs[nx]) * 0.5),
|
|
1147
|
+
Math.floor((y + ys[nx]) * 0.5)
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
PathBuilder.ClosePath(p);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
function compoGlyph(gl, font, gid, p, axs) {
|
|
1155
|
+
const dx = [0, 0, 0, 0, 0, 0];
|
|
1156
|
+
const dy = [0, 0, 0, 0, 0, 0];
|
|
1157
|
+
const ccnt = gl.parts.length;
|
|
1158
|
+
if (font.fvar && axs) {
|
|
1159
|
+
const gvar = font.gvar;
|
|
1160
|
+
const gv = gvar ? gvar[gid] : null;
|
|
1161
|
+
if (gv) {
|
|
1162
|
+
for (let vi = 0; vi < gv.length; vi++) {
|
|
1163
|
+
const axv = gv[vi][0];
|
|
1164
|
+
const S = _interpolate(axv, axs);
|
|
1165
|
+
if (S < 1e-6) continue;
|
|
1166
|
+
const dfs = gv[vi][1];
|
|
1167
|
+
const ind = gv[vi][2];
|
|
1168
|
+
if (ind == null) {
|
|
1169
|
+
for (let i = 0; i < ccnt; i++) {
|
|
1170
|
+
dx[i] += S * dfs[i];
|
|
1171
|
+
dy[i] += S * dfs[i + ccnt + 4];
|
|
1172
|
+
}
|
|
1173
|
+
} else {
|
|
1174
|
+
for (let j = 0; j < ind.length; j++) {
|
|
1175
|
+
const i = ind[j];
|
|
1176
|
+
dx[i] += S * dfs[0];
|
|
1177
|
+
dy[i] += S * dfs[0 + ccnt];
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
for (let j = 0; j < ccnt; j++) {
|
|
1184
|
+
const path = { cmds: [], crds: [] };
|
|
1185
|
+
const prt = gl.parts[j];
|
|
1186
|
+
drawGlyf(prt.glyphIndex, font, path, axs);
|
|
1187
|
+
const m = prt.m;
|
|
1188
|
+
const tx = m.tx + dx[j];
|
|
1189
|
+
const ty = m.ty + dy[j];
|
|
1190
|
+
for (let i = 0; i < path.crds.length; i += 2) {
|
|
1191
|
+
const x = path.crds[i];
|
|
1192
|
+
const y = path.crds[i + 1];
|
|
1193
|
+
p.crds.push(x * m.a + y * m.c + tx);
|
|
1194
|
+
p.crds.push(x * m.b + y * m.d + ty);
|
|
1195
|
+
}
|
|
1196
|
+
for (let i = 0; i < path.cmds.length; i++) p.cmds.push(path.cmds[i]);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
function _interpolate(axs, v) {
|
|
1200
|
+
var acnt = v.length, S = 1;
|
|
1201
|
+
var s = axs[0];
|
|
1202
|
+
var p = axs[1];
|
|
1203
|
+
var e = axs[2];
|
|
1204
|
+
for (var i = 0; i < v.length; i++) {
|
|
1205
|
+
var AS = 1;
|
|
1206
|
+
if (s[i] > p[i] || p[i] > e[i]) AS = 1;
|
|
1207
|
+
else if (s[i] < 0 && e[i] > 0 && p[i] != 0) AS = 1;
|
|
1208
|
+
else if (p[i] == 0) AS = 1;
|
|
1209
|
+
else if (v[i] < s[i] || v[i] > e[i]) AS = 0;
|
|
1210
|
+
else {
|
|
1211
|
+
if (v[i] == p[i]) AS = 1;
|
|
1212
|
+
else if (v[i] < p[i]) AS = (v[i] - s[i]) / (p[i] - s[i]);
|
|
1213
|
+
else AS = (e[i] - v[i]) / (e[i] - p[i]);
|
|
1214
|
+
}
|
|
1215
|
+
S = S * AS;
|
|
1216
|
+
}
|
|
1217
|
+
return S;
|
|
1218
|
+
}
|
|
1219
|
+
function interpolateDeltas(dfs, ind, xs, ys, endPts) {
|
|
1220
|
+
const N = xs.length;
|
|
1221
|
+
const ndfs = new Array(N * 2 + 8);
|
|
1222
|
+
ndfs.fill(0);
|
|
1223
|
+
for (let i = 0; i < N; i++) {
|
|
1224
|
+
let dx = 0, dy = 0;
|
|
1225
|
+
const ii = ind.indexOf(i);
|
|
1226
|
+
if (ii !== -1) {
|
|
1227
|
+
dx = dfs[ii];
|
|
1228
|
+
dy = dfs[ind.length + ii];
|
|
1229
|
+
} else {
|
|
1230
|
+
let cmp = 0;
|
|
1231
|
+
while (endPts[cmp] < i) cmp++;
|
|
1232
|
+
const cmp0 = cmp === 0 ? 0 : endPts[cmp - 1] + 1;
|
|
1233
|
+
const cmp1 = endPts[cmp];
|
|
1234
|
+
let i0 = -1, i1 = -1;
|
|
1235
|
+
for (let j = 0; j < ind.length; j++) {
|
|
1236
|
+
const v = ind[j];
|
|
1237
|
+
if (v < cmp0 || v > cmp1 || v >= N) continue;
|
|
1238
|
+
i0 = j;
|
|
1239
|
+
if (i1 === -1) i1 = j;
|
|
1240
|
+
}
|
|
1241
|
+
for (let j = 0; j < ind.length; j++) {
|
|
1242
|
+
const v = ind[j];
|
|
1243
|
+
if (v < cmp0 || v > cmp1 || v >= N) continue;
|
|
1244
|
+
if (v < i) i0 = j;
|
|
1245
|
+
if (i < v) {
|
|
1246
|
+
i1 = j;
|
|
1247
|
+
break;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
for (let ax = 0; ax < 2; ax++) {
|
|
1251
|
+
const crd = ax === 0 ? xs : ys;
|
|
1252
|
+
const ofs = ax * ind.length;
|
|
1253
|
+
let dlt = 0;
|
|
1254
|
+
const c0 = crd[ind[i0]];
|
|
1255
|
+
const c1 = crd[ind[i1]];
|
|
1256
|
+
const cC = crd[i];
|
|
1257
|
+
const d0 = dfs[ofs + i0];
|
|
1258
|
+
const d1 = dfs[ofs + i1];
|
|
1259
|
+
if (c0 === c1) {
|
|
1260
|
+
if (d0 === d1) dlt = d0;
|
|
1261
|
+
else dlt = 0;
|
|
1262
|
+
} else {
|
|
1263
|
+
if (cC <= Math.min(c0, c1)) {
|
|
1264
|
+
if (c0 < c1) dlt = d0;
|
|
1265
|
+
else dlt = d1;
|
|
1266
|
+
} else if (Math.max(c0, c1) <= cC) {
|
|
1267
|
+
if (c0 < c1) dlt = d1;
|
|
1268
|
+
else dlt = d0;
|
|
1269
|
+
} else {
|
|
1270
|
+
const prop = (cC - c0) / (c1 - c0);
|
|
1271
|
+
dlt = prop * d1 + (1 - prop) * d0;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
if (ax === 0) dx = dlt;
|
|
1275
|
+
else dy = dlt;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
ndfs[i] = dx;
|
|
1279
|
+
ndfs[N + 4 + i] = dy;
|
|
1280
|
+
}
|
|
1281
|
+
return ndfs;
|
|
1282
|
+
}
|
|
1283
|
+
function shapeToPath(font, shape2, prm = {}) {
|
|
1284
|
+
const tpath = { cmds: [], crds: [] };
|
|
1285
|
+
let x = 0, y = 0;
|
|
1286
|
+
const axs = prm.axs;
|
|
1287
|
+
for (let i = 0; i < shape2.length; i++) {
|
|
1288
|
+
const it = shape2[i];
|
|
1289
|
+
const path = glyphToPath(font, it.g, false, axs);
|
|
1290
|
+
const crds = path.crds;
|
|
1291
|
+
for (let j = 0; j < crds.length; j += 2) {
|
|
1292
|
+
tpath.crds.push(crds[j] + x + it.dx);
|
|
1293
|
+
tpath.crds.push(crds[j + 1] + y + it.dy);
|
|
1294
|
+
}
|
|
1295
|
+
for (let j = 0; j < path.cmds.length; j++) {
|
|
1296
|
+
tpath.cmds.push(path.cmds[j]);
|
|
1297
|
+
}
|
|
1298
|
+
x += it.ax;
|
|
1299
|
+
y += it.ay;
|
|
1300
|
+
}
|
|
1301
|
+
return { cmds: tpath.cmds, crds: tpath.crds };
|
|
1302
|
+
}
|
|
1303
|
+
var FontUtils = class {
|
|
1304
|
+
static parse(buff) {
|
|
1305
|
+
const data = new Uint8Array(buff);
|
|
1306
|
+
const tmap = {};
|
|
1307
|
+
const font = readFont(data, 0, 0, tmap);
|
|
1308
|
+
const fvar = font.fvar;
|
|
1309
|
+
if (fvar) {
|
|
1310
|
+
const out = [font];
|
|
1311
|
+
for (let i = 0; i < fvar[1].length; i++) {
|
|
1312
|
+
const fv = fvar[1][i];
|
|
1313
|
+
const obj = {};
|
|
1314
|
+
out.push(obj);
|
|
1315
|
+
for (const p in font) obj[p] = font[p];
|
|
1316
|
+
obj._index = i;
|
|
1317
|
+
const name = obj.name = JSON.parse(
|
|
1318
|
+
JSON.stringify(obj.name || {})
|
|
1319
|
+
);
|
|
1320
|
+
name.fontSubfamily = fv[0];
|
|
1321
|
+
if (fv[3] == null) {
|
|
1322
|
+
fv[3] = ((name.fontFamily || "Font") + "-" + (name.fontSubfamily || "Regular")).replace(/ /g, "");
|
|
1323
|
+
}
|
|
1324
|
+
name.postScriptName = fv[3];
|
|
1325
|
+
}
|
|
1326
|
+
return out;
|
|
1327
|
+
}
|
|
1328
|
+
return [font];
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
FontUtils.U = {
|
|
1332
|
+
shape,
|
|
1333
|
+
shapeToPath,
|
|
1334
|
+
glyphToPath,
|
|
1335
|
+
codeToGlyph
|
|
1336
|
+
};
|
|
1337
|
+
function readFont(data, idx, offset, tmap) {
|
|
1338
|
+
const parsers = {
|
|
1339
|
+
cmap,
|
|
1340
|
+
head: Tables.head,
|
|
1341
|
+
hhea: Tables.hhea,
|
|
1342
|
+
maxp: Tables.maxp,
|
|
1343
|
+
hmtx: Tables.hmtx,
|
|
1344
|
+
loca: Tables.loca,
|
|
1345
|
+
kern: Tables.kern,
|
|
1346
|
+
glyf,
|
|
1347
|
+
fvar: VariableTables.fvar,
|
|
1348
|
+
gvar: VariableTables.gvar,
|
|
1349
|
+
avar: VariableTables.avar,
|
|
1350
|
+
HVAR: VariableTables.HVAR
|
|
1351
|
+
};
|
|
1352
|
+
const obj = { _data: data, _index: idx, _offset: offset };
|
|
1353
|
+
for (const t in parsers) {
|
|
1354
|
+
const tab = findTable(data, t, offset);
|
|
1355
|
+
if (tab) {
|
|
1356
|
+
const off = tab[0];
|
|
1357
|
+
let tobj = tmap[off];
|
|
1358
|
+
if (tobj == null) {
|
|
1359
|
+
tobj = parsers[t].parseTab(data, off, tab[1], obj);
|
|
1360
|
+
}
|
|
1361
|
+
obj[t] = tmap[off] = tobj;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
if (!obj.name) {
|
|
1365
|
+
obj.name = { fontFamily: "Unknown", postScriptName: "Unknown" };
|
|
1366
|
+
}
|
|
1367
|
+
return obj;
|
|
1368
|
+
}
|
|
1369
|
+
var FontParser = class {
|
|
1370
|
+
constructor() {
|
|
1371
|
+
this.fonts = /* @__PURE__ */ new Map();
|
|
1372
|
+
}
|
|
1373
|
+
async load(url) {
|
|
1374
|
+
const ext = url.split(".").pop()?.toLowerCase();
|
|
1375
|
+
if (ext === "otf") {
|
|
1376
|
+
console.warn("OTF not supported. Convert: http://convertio.co/otf-ttf/");
|
|
1377
|
+
throw new Error("OTF not supported");
|
|
1378
|
+
}
|
|
1379
|
+
if (ext === "woff") {
|
|
1380
|
+
console.warn(
|
|
1381
|
+
"WOFF not supported. Convert: http://convertio.co/woff-ttf/"
|
|
1382
|
+
);
|
|
1383
|
+
throw new Error("WOFF not supported");
|
|
1384
|
+
}
|
|
1385
|
+
if (ext === "woff2") {
|
|
1386
|
+
console.warn(
|
|
1387
|
+
"WOFF2 not supported. Convert: http://convertio.co/woff2-ttf/"
|
|
1388
|
+
);
|
|
1389
|
+
throw new Error("WOFF2 not supported");
|
|
1390
|
+
}
|
|
1391
|
+
const response = await fetch(url);
|
|
1392
|
+
if (!response.ok) throw new Error("Failed to load font");
|
|
1393
|
+
const buffer = await response.arrayBuffer();
|
|
1394
|
+
return this.loadFromBuffer(buffer);
|
|
1395
|
+
}
|
|
1396
|
+
loadFromBuffer(buffer) {
|
|
1397
|
+
const fonts = FontUtils.parse(buffer);
|
|
1398
|
+
return this.createAPI(fonts[0]);
|
|
1399
|
+
}
|
|
1400
|
+
createAPI(font) {
|
|
1401
|
+
return {
|
|
1402
|
+
// For compatibility with the test - expose the font directly
|
|
1403
|
+
...font,
|
|
1404
|
+
// Real API implementation
|
|
1405
|
+
toPaths(text, size = 100, options = {}) {
|
|
1406
|
+
const layout = this.layoutText(font, text, size, options);
|
|
1407
|
+
const letters = [];
|
|
1408
|
+
const scale = size / font.head.unitsPerEm;
|
|
1409
|
+
const axisValues = options.axisValues || (font.fvar ? font.fvar[1][font._index || 0][2] : null);
|
|
1410
|
+
for (const letter of layout.letters) {
|
|
1411
|
+
const path2D = new Path2D();
|
|
1412
|
+
const glyphPath = FontUtils.U.glyphToPath(
|
|
1413
|
+
font,
|
|
1414
|
+
letter.glyph.g,
|
|
1415
|
+
false,
|
|
1416
|
+
axisValues
|
|
1417
|
+
);
|
|
1418
|
+
let cmdIndex = 0;
|
|
1419
|
+
for (let i = 0; i < glyphPath.cmds.length; i++) {
|
|
1420
|
+
const cmd = glyphPath.cmds[i];
|
|
1421
|
+
if (cmd === "M") {
|
|
1422
|
+
path2D.moveTo(
|
|
1423
|
+
glyphPath.crds[cmdIndex] * scale,
|
|
1424
|
+
-glyphPath.crds[cmdIndex + 1] * scale
|
|
1425
|
+
);
|
|
1426
|
+
cmdIndex += 2;
|
|
1427
|
+
} else if (cmd === "L") {
|
|
1428
|
+
path2D.lineTo(
|
|
1429
|
+
glyphPath.crds[cmdIndex] * scale,
|
|
1430
|
+
-glyphPath.crds[cmdIndex + 1] * scale
|
|
1431
|
+
);
|
|
1432
|
+
cmdIndex += 2;
|
|
1433
|
+
} else if (cmd === "Q") {
|
|
1434
|
+
path2D.quadraticCurveTo(
|
|
1435
|
+
glyphPath.crds[cmdIndex] * scale,
|
|
1436
|
+
-glyphPath.crds[cmdIndex + 1] * scale,
|
|
1437
|
+
glyphPath.crds[cmdIndex + 2] * scale,
|
|
1438
|
+
-glyphPath.crds[cmdIndex + 3] * scale
|
|
1439
|
+
);
|
|
1440
|
+
cmdIndex += 4;
|
|
1441
|
+
} else if (cmd === "Z") {
|
|
1442
|
+
path2D.closePath();
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
letters.push({
|
|
1446
|
+
path: path2D,
|
|
1447
|
+
letterIndex: letter.letterIndex,
|
|
1448
|
+
wordIndex: letter.wordIndex,
|
|
1449
|
+
lineIndex: letter.lineIndex,
|
|
1450
|
+
width: letter.width,
|
|
1451
|
+
height: letter.height,
|
|
1452
|
+
center: { x: letter.x, y: letter.y }
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
return { letters, block: layout.block };
|
|
1456
|
+
},
|
|
1457
|
+
toPoints(text, size = 100, options = {}) {
|
|
1458
|
+
const layout = this.layoutText(font, text, size, options);
|
|
1459
|
+
const letters = [];
|
|
1460
|
+
const sampling = options.sampling || 0.25;
|
|
1461
|
+
const scale = size / font.head.unitsPerEm;
|
|
1462
|
+
const axisValues = options.axisValues || (font.fvar ? font.fvar[1][font._index || 0][2] : null);
|
|
1463
|
+
for (const letter of layout.letters) {
|
|
1464
|
+
const glyphPath = FontUtils.U.glyphToPath(
|
|
1465
|
+
font,
|
|
1466
|
+
letter.glyph.g,
|
|
1467
|
+
false,
|
|
1468
|
+
axisValues
|
|
1469
|
+
);
|
|
1470
|
+
const rawPoints = this.samplePathPoints(glyphPath, sampling);
|
|
1471
|
+
const points = rawPoints.map((point) => ({
|
|
1472
|
+
x: point.x * scale,
|
|
1473
|
+
y: -point.y * scale,
|
|
1474
|
+
contour: point.contour
|
|
1475
|
+
// Preserve contour information!
|
|
1476
|
+
}));
|
|
1477
|
+
letters.push({
|
|
1478
|
+
shape: points,
|
|
1479
|
+
center: { x: letter.x, y: letter.y },
|
|
1480
|
+
letterIndex: letter.letterIndex,
|
|
1481
|
+
wordIndex: letter.wordIndex,
|
|
1482
|
+
lineIndex: letter.lineIndex,
|
|
1483
|
+
width: letter.width,
|
|
1484
|
+
height: letter.height
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
return { letters, block: layout.block };
|
|
1488
|
+
},
|
|
1489
|
+
// Helper methods
|
|
1490
|
+
layoutText(font2, text, size, options = {}) {
|
|
1491
|
+
const lines = text.split(/\r?\n/);
|
|
1492
|
+
const scale = size / font2.head.unitsPerEm;
|
|
1493
|
+
const letters = [];
|
|
1494
|
+
const letterSpacing = options.letterSpacing || 0;
|
|
1495
|
+
const wordSpacing = options.wordSpacing || 0;
|
|
1496
|
+
const lineSpacing = options.lineSpacing || 0;
|
|
1497
|
+
const axisValues = options.axisValues || (font2.fvar ? font2.fvar[1][font2._index || 0][2] : null);
|
|
1498
|
+
const ascender = font2.hhea.ascender * scale;
|
|
1499
|
+
const descender = font2.hhea.descender * scale;
|
|
1500
|
+
const lineGap = (font2.hhea.lineGap || 0) * scale;
|
|
1501
|
+
const lineHeight = size * 1.2 + lineSpacing;
|
|
1502
|
+
let letterIndex = 0;
|
|
1503
|
+
let wordIndex = 0;
|
|
1504
|
+
const shapeOptions = { ltr: true };
|
|
1505
|
+
if (axisValues) {
|
|
1506
|
+
shapeOptions.axs = axisValues;
|
|
1507
|
+
}
|
|
1508
|
+
const lineWidths = lines.map((line) => {
|
|
1509
|
+
const shaped = FontUtils.U.shape(font2, line, shapeOptions);
|
|
1510
|
+
let width = 0;
|
|
1511
|
+
for (let i = 0; i < shaped.length; i++) {
|
|
1512
|
+
const glyph = shaped[i];
|
|
1513
|
+
const char = line.charAt(i);
|
|
1514
|
+
width += (glyph.ax || 0) * scale;
|
|
1515
|
+
if (char === " ") {
|
|
1516
|
+
width += wordSpacing;
|
|
1517
|
+
} else if (i < shaped.length - 1) {
|
|
1518
|
+
width += letterSpacing;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
return width;
|
|
1522
|
+
});
|
|
1523
|
+
const blockWidth = Math.max(...lineWidths, 0);
|
|
1524
|
+
const blockHeight = lineHeight * lines.length;
|
|
1525
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
1526
|
+
const line = lines[lineIndex];
|
|
1527
|
+
const shaped = FontUtils.U.shape(font2, line, shapeOptions);
|
|
1528
|
+
const lineWidth = lineWidths[lineIndex];
|
|
1529
|
+
let x = 0;
|
|
1530
|
+
if (options.align === "center") {
|
|
1531
|
+
x = (blockWidth - lineWidth) / 2;
|
|
1532
|
+
} else if (options.align === "right") {
|
|
1533
|
+
x = blockWidth - lineWidth;
|
|
1534
|
+
}
|
|
1535
|
+
if (options.anchor === "center" && options.align === "center") {
|
|
1536
|
+
x = -lineWidth / 2;
|
|
1537
|
+
}
|
|
1538
|
+
let y;
|
|
1539
|
+
if (options.baseline === "center") {
|
|
1540
|
+
if (lines.length === 1) {
|
|
1541
|
+
y = 0;
|
|
1542
|
+
} else {
|
|
1543
|
+
y = -blockHeight / 2 + lineHeight / 2 + lineIndex * lineHeight;
|
|
1544
|
+
}
|
|
1545
|
+
} else if (options.baseline === "top") {
|
|
1546
|
+
y = -lineHeight / 2 + lineIndex * lineHeight;
|
|
1547
|
+
} else {
|
|
1548
|
+
y = -lineHeight / 2 + lineIndex * lineHeight;
|
|
1549
|
+
}
|
|
1550
|
+
const middleToBaseline = (ascender + descender) / 2;
|
|
1551
|
+
y += middleToBaseline;
|
|
1552
|
+
for (let i = 0; i < shaped.length; i++) {
|
|
1553
|
+
const glyph = shaped[i];
|
|
1554
|
+
const char = line.charAt(i);
|
|
1555
|
+
const advanceWidth = (glyph.ax || 0) * scale;
|
|
1556
|
+
letters.push({
|
|
1557
|
+
glyph,
|
|
1558
|
+
char,
|
|
1559
|
+
x,
|
|
1560
|
+
y,
|
|
1561
|
+
letterIndex: letterIndex++,
|
|
1562
|
+
wordIndex,
|
|
1563
|
+
lineIndex,
|
|
1564
|
+
width: advanceWidth,
|
|
1565
|
+
height: size
|
|
1566
|
+
});
|
|
1567
|
+
x += advanceWidth;
|
|
1568
|
+
if (char === " ") {
|
|
1569
|
+
x += wordSpacing;
|
|
1570
|
+
wordIndex++;
|
|
1571
|
+
} else if (i < shaped.length - 1) {
|
|
1572
|
+
x += letterSpacing;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
if (line.includes(" ")) wordIndex++;
|
|
1576
|
+
}
|
|
1577
|
+
let offsetX = 0, offsetY = 0;
|
|
1578
|
+
if (options.anchor === "center") {
|
|
1579
|
+
if (options.align !== "center") {
|
|
1580
|
+
offsetX = -blockWidth / 2;
|
|
1581
|
+
}
|
|
1582
|
+
if (options.baseline !== "center") {
|
|
1583
|
+
offsetY = -blockHeight / 2;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
letters.forEach((letter) => {
|
|
1587
|
+
letter.x += offsetX;
|
|
1588
|
+
letter.y += offsetY;
|
|
1589
|
+
});
|
|
1590
|
+
return { letters, block: { width: blockWidth, height: blockHeight } };
|
|
1591
|
+
},
|
|
1592
|
+
samplePathPoints(glyphPath, sampling) {
|
|
1593
|
+
const rawContours = this.parseContours(glyphPath);
|
|
1594
|
+
const contoursWithLength = rawContours.map(
|
|
1595
|
+
(contour, index) => ({
|
|
1596
|
+
contour,
|
|
1597
|
+
originalIndex: index,
|
|
1598
|
+
length: this.calculateContourLength(contour)
|
|
1599
|
+
})
|
|
1600
|
+
);
|
|
1601
|
+
contoursWithLength.sort((a, b) => b.length - a.length);
|
|
1602
|
+
const allPoints = [];
|
|
1603
|
+
for (let i = 0; i < contoursWithLength.length; i++) {
|
|
1604
|
+
const { contour } = contoursWithLength[i];
|
|
1605
|
+
const contourPoints = this.sampleContour(contour, sampling, i);
|
|
1606
|
+
allPoints.push(...contourPoints);
|
|
1607
|
+
}
|
|
1608
|
+
return allPoints;
|
|
1609
|
+
},
|
|
1610
|
+
// Calculate total length of a contour
|
|
1611
|
+
calculateContourLength(contour) {
|
|
1612
|
+
let totalLength = 0;
|
|
1613
|
+
let currentX = contour.startX;
|
|
1614
|
+
let currentY = contour.startY;
|
|
1615
|
+
for (const seg of contour.segments) {
|
|
1616
|
+
if (seg.cmd === "L") {
|
|
1617
|
+
const endX = seg.coords[0];
|
|
1618
|
+
const endY = seg.coords[1];
|
|
1619
|
+
totalLength += Math.sqrt(
|
|
1620
|
+
(endX - currentX) ** 2 + (endY - currentY) ** 2
|
|
1621
|
+
);
|
|
1622
|
+
currentX = endX;
|
|
1623
|
+
currentY = endY;
|
|
1624
|
+
} else if (seg.cmd === "Q") {
|
|
1625
|
+
const controlX = seg.coords[0];
|
|
1626
|
+
const controlY = seg.coords[1];
|
|
1627
|
+
const endX = seg.coords[2];
|
|
1628
|
+
const endY = seg.coords[3];
|
|
1629
|
+
totalLength += this.approximateQuadraticLength(
|
|
1630
|
+
currentX,
|
|
1631
|
+
currentY,
|
|
1632
|
+
controlX,
|
|
1633
|
+
controlY,
|
|
1634
|
+
endX,
|
|
1635
|
+
endY
|
|
1636
|
+
);
|
|
1637
|
+
currentX = endX;
|
|
1638
|
+
currentY = endY;
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
return totalLength;
|
|
1642
|
+
},
|
|
1643
|
+
// Parse glyph path into separate contours
|
|
1644
|
+
parseContours(glyphPath) {
|
|
1645
|
+
const contours = [];
|
|
1646
|
+
let currentContour = null;
|
|
1647
|
+
let cmdIndex = 0;
|
|
1648
|
+
for (let i = 0; i < glyphPath.cmds.length; i++) {
|
|
1649
|
+
const cmd = glyphPath.cmds[i];
|
|
1650
|
+
if (cmd === "M") {
|
|
1651
|
+
if (currentContour) {
|
|
1652
|
+
contours.push(currentContour);
|
|
1653
|
+
}
|
|
1654
|
+
currentContour = {
|
|
1655
|
+
startX: glyphPath.crds[cmdIndex],
|
|
1656
|
+
startY: glyphPath.crds[cmdIndex + 1],
|
|
1657
|
+
segments: []
|
|
1658
|
+
};
|
|
1659
|
+
cmdIndex += 2;
|
|
1660
|
+
} else if (cmd === "L" || cmd === "Q") {
|
|
1661
|
+
if (currentContour) {
|
|
1662
|
+
currentContour.segments.push({
|
|
1663
|
+
cmd,
|
|
1664
|
+
coords: glyphPath.crds.slice(
|
|
1665
|
+
cmdIndex,
|
|
1666
|
+
cmdIndex + (cmd === "L" ? 2 : 4)
|
|
1667
|
+
)
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
cmdIndex += cmd === "L" ? 2 : 4;
|
|
1671
|
+
} else if (cmd === "Z") {
|
|
1672
|
+
if (currentContour) {
|
|
1673
|
+
contours.push(currentContour);
|
|
1674
|
+
currentContour = null;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
if (currentContour) {
|
|
1679
|
+
contours.push(currentContour);
|
|
1680
|
+
}
|
|
1681
|
+
return contours;
|
|
1682
|
+
},
|
|
1683
|
+
// Sample a single contour uniformly
|
|
1684
|
+
sampleContour(contour, sampling, contourIndex) {
|
|
1685
|
+
const segments = [];
|
|
1686
|
+
let currentX = contour.startX;
|
|
1687
|
+
let currentY = contour.startY;
|
|
1688
|
+
let totalLength = 0;
|
|
1689
|
+
for (const seg of contour.segments) {
|
|
1690
|
+
if (seg.cmd === "L") {
|
|
1691
|
+
const endX = seg.coords[0];
|
|
1692
|
+
const endY = seg.coords[1];
|
|
1693
|
+
const length = Math.sqrt(
|
|
1694
|
+
(endX - currentX) ** 2 + (endY - currentY) ** 2
|
|
1695
|
+
);
|
|
1696
|
+
segments.push({
|
|
1697
|
+
type: "L",
|
|
1698
|
+
startX: currentX,
|
|
1699
|
+
startY: currentY,
|
|
1700
|
+
endX,
|
|
1701
|
+
endY,
|
|
1702
|
+
length,
|
|
1703
|
+
startLength: totalLength
|
|
1704
|
+
});
|
|
1705
|
+
totalLength += length;
|
|
1706
|
+
currentX = endX;
|
|
1707
|
+
currentY = endY;
|
|
1708
|
+
} else if (seg.cmd === "Q") {
|
|
1709
|
+
const controlX = seg.coords[0];
|
|
1710
|
+
const controlY = seg.coords[1];
|
|
1711
|
+
const endX = seg.coords[2];
|
|
1712
|
+
const endY = seg.coords[3];
|
|
1713
|
+
const length = this.approximateQuadraticLength(
|
|
1714
|
+
currentX,
|
|
1715
|
+
currentY,
|
|
1716
|
+
controlX,
|
|
1717
|
+
controlY,
|
|
1718
|
+
endX,
|
|
1719
|
+
endY
|
|
1720
|
+
);
|
|
1721
|
+
segments.push({
|
|
1722
|
+
type: "Q",
|
|
1723
|
+
startX: currentX,
|
|
1724
|
+
startY: currentY,
|
|
1725
|
+
controlX,
|
|
1726
|
+
controlY,
|
|
1727
|
+
endX,
|
|
1728
|
+
endY,
|
|
1729
|
+
length,
|
|
1730
|
+
startLength: totalLength
|
|
1731
|
+
});
|
|
1732
|
+
totalLength += length;
|
|
1733
|
+
currentX = endX;
|
|
1734
|
+
currentY = endY;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
const points = [];
|
|
1738
|
+
const targetPoints = Math.max(
|
|
1739
|
+
5,
|
|
1740
|
+
Math.floor(totalLength * sampling * 0.1)
|
|
1741
|
+
);
|
|
1742
|
+
for (let i = 0; i <= targetPoints; i++) {
|
|
1743
|
+
const targetLength = i / targetPoints * totalLength;
|
|
1744
|
+
const pointData = this.getPointAtLengthInContour(
|
|
1745
|
+
segments,
|
|
1746
|
+
targetLength
|
|
1747
|
+
);
|
|
1748
|
+
if (pointData) {
|
|
1749
|
+
points.push({
|
|
1750
|
+
x: pointData.x,
|
|
1751
|
+
y: pointData.y,
|
|
1752
|
+
contour: contourIndex
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return points;
|
|
1757
|
+
},
|
|
1758
|
+
// Get point at length within a single contour
|
|
1759
|
+
getPointAtLengthInContour(segments, targetLength) {
|
|
1760
|
+
for (const segment of segments) {
|
|
1761
|
+
if (targetLength >= segment.startLength && targetLength <= segment.startLength + segment.length) {
|
|
1762
|
+
const localT = (targetLength - segment.startLength) / segment.length;
|
|
1763
|
+
if (segment.type === "L") {
|
|
1764
|
+
return {
|
|
1765
|
+
x: segment.startX + (segment.endX - segment.startX) * localT,
|
|
1766
|
+
y: segment.startY + (segment.endY - segment.startY) * localT
|
|
1767
|
+
};
|
|
1768
|
+
} else if (segment.type === "Q") {
|
|
1769
|
+
const t = localT;
|
|
1770
|
+
return {
|
|
1771
|
+
x: (1 - t) * (1 - t) * segment.startX + 2 * (1 - t) * t * segment.controlX + t * t * segment.endX,
|
|
1772
|
+
y: (1 - t) * (1 - t) * segment.startY + 2 * (1 - t) * t * segment.controlY + t * t * segment.endY
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
return null;
|
|
1778
|
+
},
|
|
1779
|
+
// Helper function to approximate quadratic curve length
|
|
1780
|
+
approximateQuadraticLength(x0, y0, x1, y1, x2, y2) {
|
|
1781
|
+
const chordLength = Math.sqrt((x2 - x0) ** 2 + (y2 - y0) ** 2);
|
|
1782
|
+
const controlLength = Math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) + Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
|
|
1783
|
+
return (chordLength + controlLength) / 2;
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
};
|
|
1788
|
+
|
|
1789
|
+
// src/plugins/Delaunay.tsx
|
|
1790
|
+
var Delaunay = class {
|
|
1791
|
+
/**
|
|
1792
|
+
* Perform Delaunay triangulation on a set of points
|
|
1793
|
+
*/
|
|
1794
|
+
static triangulate(points) {
|
|
1795
|
+
if (points.length < 3) {
|
|
1796
|
+
return [];
|
|
1797
|
+
}
|
|
1798
|
+
const triangles = [];
|
|
1799
|
+
for (let i = 0; i < points.length - 2; i++) {
|
|
1800
|
+
triangles.push({
|
|
1801
|
+
p1: points[i],
|
|
1802
|
+
p2: points[i + 1],
|
|
1803
|
+
p3: points[i + 2]
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
return triangles;
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Draw triangles to the canvas
|
|
1810
|
+
*/
|
|
1811
|
+
static drawTriangles(ctx, triangles, options) {
|
|
1812
|
+
const { fill = true, stroke = true } = options || {};
|
|
1813
|
+
triangles.forEach((triangle) => {
|
|
1814
|
+
ctx.beginPath();
|
|
1815
|
+
ctx.moveTo(triangle.p1.x, triangle.p1.y);
|
|
1816
|
+
ctx.lineTo(triangle.p2.x, triangle.p2.y);
|
|
1817
|
+
ctx.lineTo(triangle.p3.x, triangle.p3.y);
|
|
1818
|
+
ctx.closePath();
|
|
1819
|
+
if (fill) {
|
|
1820
|
+
if (options?.fillStyle) {
|
|
1821
|
+
const prevFill = ctx.fillStyle;
|
|
1822
|
+
ctx.fillStyle = options.fillStyle;
|
|
1823
|
+
ctx.fill();
|
|
1824
|
+
ctx.fillStyle = prevFill;
|
|
1825
|
+
} else {
|
|
1826
|
+
ctx.fill();
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
if (stroke) {
|
|
1830
|
+
if (options?.strokeStyle) {
|
|
1831
|
+
const prevStroke = ctx.strokeStyle;
|
|
1832
|
+
ctx.strokeStyle = options.strokeStyle;
|
|
1833
|
+
ctx.stroke();
|
|
1834
|
+
ctx.strokeStyle = prevStroke;
|
|
1835
|
+
} else {
|
|
1836
|
+
ctx.stroke();
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
/**
|
|
1842
|
+
* Calculate circumcenter of a triangle
|
|
1843
|
+
*/
|
|
1844
|
+
static circumcenter(triangle) {
|
|
1845
|
+
const { p1, p2, p3 } = triangle;
|
|
1846
|
+
const ax = p1.x, ay = p1.y;
|
|
1847
|
+
const bx = p2.x, by = p2.y;
|
|
1848
|
+
const cx = p3.x, cy = p3.y;
|
|
1849
|
+
const d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
|
|
1850
|
+
if (Math.abs(d) < 1e-6) {
|
|
1851
|
+
return { x: (ax + bx + cx) / 3, y: (ay + by + cy) / 3 };
|
|
1852
|
+
}
|
|
1853
|
+
const ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d;
|
|
1854
|
+
const uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d;
|
|
1855
|
+
return { x: ux, y: uy };
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Check if a point is inside a triangle's circumcircle
|
|
1859
|
+
*/
|
|
1860
|
+
static inCircumcircle(point, triangle) {
|
|
1861
|
+
const center = this.circumcenter(triangle);
|
|
1862
|
+
const radius = Math.sqrt(
|
|
1863
|
+
Math.pow(triangle.p1.x - center.x, 2) + Math.pow(triangle.p1.y - center.y, 2)
|
|
1864
|
+
);
|
|
1865
|
+
const distance = Math.sqrt(
|
|
1866
|
+
Math.pow(point.x - center.x, 2) + Math.pow(point.y - center.y, 2)
|
|
1867
|
+
);
|
|
1868
|
+
return distance < radius;
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
|
|
1872
|
+
// src/plugins/Catmull.tsx
|
|
1873
|
+
var CatmullRom = class {
|
|
1874
|
+
/**
|
|
1875
|
+
* Interpolate points using Catmull-Rom spline
|
|
1876
|
+
*/
|
|
1877
|
+
static interpolate(points, tension = 0.5, segments = 20) {
|
|
1878
|
+
if (points.length < 2) return points;
|
|
1879
|
+
const result = [];
|
|
1880
|
+
const extendedPoints = [points[0], ...points, points[points.length - 1]];
|
|
1881
|
+
for (let i = 1; i < extendedPoints.length - 2; i++) {
|
|
1882
|
+
const p0 = extendedPoints[i - 1];
|
|
1883
|
+
const p1 = extendedPoints[i];
|
|
1884
|
+
const p2 = extendedPoints[i + 1];
|
|
1885
|
+
const p3 = extendedPoints[i + 2];
|
|
1886
|
+
for (let t = 0; t < 1; t += 1 / segments) {
|
|
1887
|
+
const t2 = t * t;
|
|
1888
|
+
const t3 = t2 * t;
|
|
1889
|
+
const v0 = tension * (p2.x - p0.x);
|
|
1890
|
+
const v1 = tension * (p3.x - p1.x);
|
|
1891
|
+
const x = p1.x + v0 * t + (3 * (p2.x - p1.x) - 2 * v0 - v1) * t2 + (2 * (p1.x - p2.x) + v0 + v1) * t3;
|
|
1892
|
+
const v0y = tension * (p2.y - p0.y);
|
|
1893
|
+
const v1y = tension * (p3.y - p1.y);
|
|
1894
|
+
const y = p1.y + v0y * t + (3 * (p2.y - p1.y) - 2 * v0y - v1y) * t2 + (2 * (p1.y - p2.y) + v0y + v1y) * t3;
|
|
1895
|
+
result.push({ x, y });
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
result.push(points[points.length - 1]);
|
|
1899
|
+
return result;
|
|
1900
|
+
}
|
|
1901
|
+
/**
|
|
1902
|
+
* Draw interpolated curve to canvas
|
|
1903
|
+
*/
|
|
1904
|
+
static draw(ctx, points, options) {
|
|
1905
|
+
const { tension = 0.5, segments = 20, closed = false } = options || {};
|
|
1906
|
+
const interpolated = this.interpolate(
|
|
1907
|
+
closed ? [...points, points[0]] : points,
|
|
1908
|
+
tension,
|
|
1909
|
+
segments
|
|
1910
|
+
);
|
|
1911
|
+
ctx.save();
|
|
1912
|
+
if (options?.strokeStyle) {
|
|
1913
|
+
ctx.strokeStyle = options.strokeStyle;
|
|
1914
|
+
}
|
|
1915
|
+
if (options?.lineWidth) {
|
|
1916
|
+
ctx.lineWidth = options.lineWidth;
|
|
1917
|
+
}
|
|
1918
|
+
ctx.beginPath();
|
|
1919
|
+
ctx.moveTo(interpolated[0].x, interpolated[0].y);
|
|
1920
|
+
for (let i = 1; i < interpolated.length; i++) {
|
|
1921
|
+
ctx.lineTo(interpolated[i].x, interpolated[i].y);
|
|
1922
|
+
}
|
|
1923
|
+
if (closed) {
|
|
1924
|
+
ctx.closePath();
|
|
1925
|
+
}
|
|
1926
|
+
ctx.stroke();
|
|
1927
|
+
ctx.restore();
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* Get path as a Path2D object
|
|
1931
|
+
*/
|
|
1932
|
+
static toPath2D(points, options) {
|
|
1933
|
+
const { tension = 0.5, segments = 20, closed = false } = options || {};
|
|
1934
|
+
const interpolated = this.interpolate(
|
|
1935
|
+
closed ? [...points, points[0]] : points,
|
|
1936
|
+
tension,
|
|
1937
|
+
segments
|
|
1938
|
+
);
|
|
1939
|
+
const path = new Path2D();
|
|
1940
|
+
path.moveTo(interpolated[0].x, interpolated[0].y);
|
|
1941
|
+
for (let i = 1; i < interpolated.length; i++) {
|
|
1942
|
+
path.lineTo(interpolated[i].x, interpolated[i].y);
|
|
1943
|
+
}
|
|
1944
|
+
if (closed) {
|
|
1945
|
+
path.closePath();
|
|
1946
|
+
}
|
|
1947
|
+
return path;
|
|
1948
|
+
}
|
|
1949
|
+
};
|
|
1950
|
+
|
|
1951
|
+
// src/plugins/Things.tsx
|
|
1952
|
+
var Things = class {
|
|
1953
|
+
/**
|
|
1954
|
+
* Configure the plugin
|
|
1955
|
+
*/
|
|
1956
|
+
static configure(config) {
|
|
1957
|
+
this.config = { ...this.config, ...config };
|
|
1958
|
+
}
|
|
1959
|
+
/**
|
|
1960
|
+
* Create a new thing
|
|
1961
|
+
*/
|
|
1962
|
+
static create(options = {}) {
|
|
1963
|
+
if (this.things.size >= (this.config.maxThings || 1e3)) {
|
|
1964
|
+
throw new Error(
|
|
1965
|
+
`Maximum number of things (${this.config.maxThings}) reached`
|
|
1966
|
+
);
|
|
1967
|
+
}
|
|
1968
|
+
const thing = {
|
|
1969
|
+
id: options.id || `thing_${this.idCounter++}`,
|
|
1970
|
+
x: options.x || 0,
|
|
1971
|
+
y: options.y || 0,
|
|
1972
|
+
width: options.width || this.config.defaultSize || 50,
|
|
1973
|
+
height: options.height || this.config.defaultSize || 50,
|
|
1974
|
+
rotation: options.rotation || 0,
|
|
1975
|
+
scale: options.scale || 1,
|
|
1976
|
+
color: options.color || this.config.defaultColor || "#ffffff",
|
|
1977
|
+
data: options.data || {}
|
|
1978
|
+
};
|
|
1979
|
+
this.things.set(thing.id, thing);
|
|
1980
|
+
return thing;
|
|
1981
|
+
}
|
|
1982
|
+
/**
|
|
1983
|
+
* Get a thing by ID
|
|
1984
|
+
*/
|
|
1985
|
+
static get(id) {
|
|
1986
|
+
return this.things.get(id);
|
|
1987
|
+
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Get all things
|
|
1990
|
+
*/
|
|
1991
|
+
static getAll() {
|
|
1992
|
+
return Array.from(this.things.values());
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Update a thing's properties
|
|
1996
|
+
*/
|
|
1997
|
+
static update(id, updates) {
|
|
1998
|
+
const thing = this.things.get(id);
|
|
1999
|
+
if (thing) {
|
|
2000
|
+
Object.assign(thing, updates);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
/**
|
|
2004
|
+
* Remove a thing
|
|
2005
|
+
*/
|
|
2006
|
+
static remove(id) {
|
|
2007
|
+
return this.things.delete(id);
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Clear all things
|
|
2011
|
+
*/
|
|
2012
|
+
static clear() {
|
|
2013
|
+
this.things.clear();
|
|
2014
|
+
this.idCounter = 0;
|
|
2015
|
+
}
|
|
2016
|
+
/**
|
|
2017
|
+
* Move a thing
|
|
2018
|
+
*/
|
|
2019
|
+
static move(id, dx, dy) {
|
|
2020
|
+
const thing = this.things.get(id);
|
|
2021
|
+
if (thing) {
|
|
2022
|
+
thing.x += dx;
|
|
2023
|
+
thing.y += dy;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Rotate a thing
|
|
2028
|
+
*/
|
|
2029
|
+
static rotate(id, angle) {
|
|
2030
|
+
const thing = this.things.get(id);
|
|
2031
|
+
if (thing) {
|
|
2032
|
+
thing.rotation += angle;
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
/**
|
|
2036
|
+
* Scale a thing
|
|
2037
|
+
*/
|
|
2038
|
+
static scale(id, factor) {
|
|
2039
|
+
const thing = this.things.get(id);
|
|
2040
|
+
if (thing) {
|
|
2041
|
+
thing.scale *= factor;
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
/**
|
|
2045
|
+
* Find things within a radius
|
|
2046
|
+
*/
|
|
2047
|
+
static findNear(x, y, radius) {
|
|
2048
|
+
const near = [];
|
|
2049
|
+
const radiusSq = radius * radius;
|
|
2050
|
+
this.things.forEach((thing) => {
|
|
2051
|
+
const dx = thing.x - x;
|
|
2052
|
+
const dy = thing.y - y;
|
|
2053
|
+
if (dx * dx + dy * dy <= radiusSq) {
|
|
2054
|
+
near.push(thing);
|
|
2055
|
+
}
|
|
2056
|
+
});
|
|
2057
|
+
return near;
|
|
2058
|
+
}
|
|
2059
|
+
/**
|
|
2060
|
+
* Find things that overlap with a rectangle
|
|
2061
|
+
*/
|
|
2062
|
+
static findInRect(x, y, width, height) {
|
|
2063
|
+
const found = [];
|
|
2064
|
+
this.things.forEach((thing) => {
|
|
2065
|
+
const halfW = thing.width * thing.scale / 2;
|
|
2066
|
+
const halfH = thing.height * thing.scale / 2;
|
|
2067
|
+
if (thing.x + halfW >= x && thing.x - halfW <= x + width && thing.y + halfH >= y && thing.y - halfH <= y + height) {
|
|
2068
|
+
found.push(thing);
|
|
2069
|
+
}
|
|
2070
|
+
});
|
|
2071
|
+
return found;
|
|
2072
|
+
}
|
|
2073
|
+
/**
|
|
2074
|
+
* Apply a function to all things
|
|
2075
|
+
*/
|
|
2076
|
+
static forEach(fn) {
|
|
2077
|
+
this.things.forEach(fn);
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Map things to a new array
|
|
2081
|
+
*/
|
|
2082
|
+
static map(fn) {
|
|
2083
|
+
return Array.from(this.things.values()).map(fn);
|
|
2084
|
+
}
|
|
2085
|
+
/**
|
|
2086
|
+
* Filter things
|
|
2087
|
+
*/
|
|
2088
|
+
static filter(fn) {
|
|
2089
|
+
return Array.from(this.things.values()).filter(fn);
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Sort things by a property or function
|
|
2093
|
+
*/
|
|
2094
|
+
static sort(fn) {
|
|
2095
|
+
return Array.from(this.things.values()).sort(fn);
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Draw all things
|
|
2099
|
+
*/
|
|
2100
|
+
static draw(ctx, options) {
|
|
2101
|
+
const thingsToDraw = options?.filter ? this.filter(options.filter) : this.getAll();
|
|
2102
|
+
thingsToDraw.forEach((thing) => {
|
|
2103
|
+
if (options?.customDraw) {
|
|
2104
|
+
options.customDraw(ctx, thing);
|
|
2105
|
+
} else {
|
|
2106
|
+
this.drawThing(ctx, thing);
|
|
2107
|
+
}
|
|
2108
|
+
});
|
|
2109
|
+
}
|
|
2110
|
+
/**
|
|
2111
|
+
* Default drawing method for a thing
|
|
2112
|
+
*/
|
|
2113
|
+
static drawThing(ctx, thing) {
|
|
2114
|
+
ctx.save();
|
|
2115
|
+
ctx.translate(thing.x, thing.y);
|
|
2116
|
+
ctx.rotate(thing.rotation);
|
|
2117
|
+
ctx.scale(thing.scale, thing.scale);
|
|
2118
|
+
ctx.fillStyle = thing.color;
|
|
2119
|
+
ctx.fillRect(
|
|
2120
|
+
-thing.width / 2,
|
|
2121
|
+
-thing.height / 2,
|
|
2122
|
+
thing.width,
|
|
2123
|
+
thing.height
|
|
2124
|
+
);
|
|
2125
|
+
ctx.restore();
|
|
2126
|
+
}
|
|
2127
|
+
/**
|
|
2128
|
+
* Animate things with a simple physics update
|
|
2129
|
+
*/
|
|
2130
|
+
static animatePhysics(deltaTime, options) {
|
|
2131
|
+
const gravity = options?.gravity || 0;
|
|
2132
|
+
const friction = options?.friction || 0.99;
|
|
2133
|
+
const bounds = options?.bounds;
|
|
2134
|
+
this.things.forEach((thing) => {
|
|
2135
|
+
if (!thing.data.vx) thing.data.vx = 0;
|
|
2136
|
+
if (!thing.data.vy) thing.data.vy = 0;
|
|
2137
|
+
thing.data.vy += gravity;
|
|
2138
|
+
thing.data.vx *= friction;
|
|
2139
|
+
thing.data.vy *= friction;
|
|
2140
|
+
thing.x += thing.data.vx;
|
|
2141
|
+
thing.y += thing.data.vy;
|
|
2142
|
+
if (bounds) {
|
|
2143
|
+
const halfW = thing.width * thing.scale / 2;
|
|
2144
|
+
const halfH = thing.height * thing.scale / 2;
|
|
2145
|
+
if (thing.x - halfW < bounds.x) {
|
|
2146
|
+
thing.x = bounds.x + halfW;
|
|
2147
|
+
thing.data.vx *= -0.8;
|
|
2148
|
+
}
|
|
2149
|
+
if (thing.x + halfW > bounds.x + bounds.width) {
|
|
2150
|
+
thing.x = bounds.x + bounds.width - halfW;
|
|
2151
|
+
thing.data.vx *= -0.8;
|
|
2152
|
+
}
|
|
2153
|
+
if (thing.y - halfH < bounds.y) {
|
|
2154
|
+
thing.y = bounds.y + halfH;
|
|
2155
|
+
thing.data.vy *= -0.8;
|
|
2156
|
+
}
|
|
2157
|
+
if (thing.y + halfH > bounds.y + bounds.height) {
|
|
2158
|
+
thing.y = bounds.y + bounds.height - halfH;
|
|
2159
|
+
thing.data.vy *= -0.8;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
/**
|
|
2165
|
+
* Get the count of things
|
|
2166
|
+
*/
|
|
2167
|
+
static count() {
|
|
2168
|
+
return this.things.size;
|
|
2169
|
+
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Check if a thing exists
|
|
2172
|
+
*/
|
|
2173
|
+
static has(id) {
|
|
2174
|
+
return this.things.has(id);
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Utility: Get distance between two things
|
|
2178
|
+
*/
|
|
2179
|
+
static distance(id1, id2) {
|
|
2180
|
+
const thing1 = this.things.get(id1);
|
|
2181
|
+
const thing2 = this.things.get(id2);
|
|
2182
|
+
if (!thing1 || !thing2) return Infinity;
|
|
2183
|
+
const dx = thing2.x - thing1.x;
|
|
2184
|
+
const dy = thing2.y - thing1.y;
|
|
2185
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Utility: Check collision between two things
|
|
2189
|
+
*/
|
|
2190
|
+
static collides(id1, id2) {
|
|
2191
|
+
const thing1 = this.things.get(id1);
|
|
2192
|
+
const thing2 = this.things.get(id2);
|
|
2193
|
+
if (!thing1 || !thing2) return false;
|
|
2194
|
+
const halfW1 = thing1.width * thing1.scale / 2;
|
|
2195
|
+
const halfH1 = thing1.height * thing1.scale / 2;
|
|
2196
|
+
const halfW2 = thing2.width * thing2.scale / 2;
|
|
2197
|
+
const halfH2 = thing2.height * thing2.scale / 2;
|
|
2198
|
+
return Math.abs(thing1.x - thing2.x) < halfW1 + halfW2 && Math.abs(thing1.y - thing2.y) < halfH1 + halfH2;
|
|
2199
|
+
}
|
|
2200
|
+
};
|
|
2201
|
+
Things.things = /* @__PURE__ */ new Map();
|
|
2202
|
+
Things.config = {
|
|
2203
|
+
maxThings: 1e3,
|
|
2204
|
+
defaultSize: 50,
|
|
2205
|
+
defaultColor: "#ffffff"
|
|
2206
|
+
};
|
|
2207
|
+
Things.idCounter = 0;
|
|
2208
|
+
|
|
2209
|
+
// src/plugins/Sprites.tsx
|
|
2210
|
+
var Sprites = class {
|
|
2211
|
+
/**
|
|
2212
|
+
* Load one or more spritesheets
|
|
2213
|
+
* @param configs - Sprite configuration(s) to load
|
|
2214
|
+
* @returns Promise that resolves when all sprites are loaded
|
|
2215
|
+
*/
|
|
2216
|
+
static async load(...configs) {
|
|
2217
|
+
const promises = configs.map((config) => this.loadSingle(config));
|
|
2218
|
+
await Promise.all(promises);
|
|
2219
|
+
}
|
|
2220
|
+
/**
|
|
2221
|
+
* Load a single spritesheet
|
|
2222
|
+
* @param config - Sprite configuration
|
|
2223
|
+
* @returns Promise that resolves when sprite is loaded
|
|
2224
|
+
*/
|
|
2225
|
+
static async loadSingle(config) {
|
|
2226
|
+
const { name, url, spriteWidth, spriteHeight, gap = 0 } = config;
|
|
2227
|
+
if (this.loadingPromises.has(name)) {
|
|
2228
|
+
return this.loadingPromises.get(name);
|
|
2229
|
+
}
|
|
2230
|
+
if (this.spritesheets.has(name)) {
|
|
2231
|
+
return Promise.resolve();
|
|
2232
|
+
}
|
|
2233
|
+
const loadPromise = new Promise((resolve, reject) => {
|
|
2234
|
+
const img = new Image();
|
|
2235
|
+
img.onload = () => {
|
|
2236
|
+
const cols = Math.floor((img.width + gap) / (spriteWidth + gap));
|
|
2237
|
+
const rows = Math.floor((img.height + gap) / (spriteHeight + gap));
|
|
2238
|
+
const numSprites = cols * rows;
|
|
2239
|
+
const spritesheet = {
|
|
2240
|
+
image: img,
|
|
2241
|
+
srcNaturalWidth: img.width,
|
|
2242
|
+
srcNaturalHeight: img.height,
|
|
2243
|
+
numSprites,
|
|
2244
|
+
spriteWidth,
|
|
2245
|
+
spriteHeight,
|
|
2246
|
+
gap,
|
|
2247
|
+
cols,
|
|
2248
|
+
rows
|
|
2249
|
+
};
|
|
2250
|
+
this.spritesheets.set(name, spritesheet);
|
|
2251
|
+
this.loadingPromises.delete(name);
|
|
2252
|
+
resolve();
|
|
2253
|
+
};
|
|
2254
|
+
img.onerror = () => {
|
|
2255
|
+
this.loadingPromises.delete(name);
|
|
2256
|
+
reject(new Error(`Failed to load sprite: ${url}`));
|
|
2257
|
+
};
|
|
2258
|
+
img.crossOrigin = "anonymous";
|
|
2259
|
+
img.src = url;
|
|
2260
|
+
});
|
|
2261
|
+
this.loadingPromises.set(name, loadPromise);
|
|
2262
|
+
return loadPromise;
|
|
2263
|
+
}
|
|
2264
|
+
/**
|
|
2265
|
+
* Get spritesheet information
|
|
2266
|
+
* @param name - Name of the spritesheet
|
|
2267
|
+
* @returns Spritesheet data or undefined if not loaded
|
|
2268
|
+
*/
|
|
2269
|
+
static sheet(name) {
|
|
2270
|
+
return this.spritesheets.get(name);
|
|
2271
|
+
}
|
|
2272
|
+
/**
|
|
2273
|
+
* Draw a sprite from a spritesheet
|
|
2274
|
+
* @param ctx - Klint context
|
|
2275
|
+
* @param sheetName - Name of the spritesheet
|
|
2276
|
+
* @param sprite - Sprite index to draw
|
|
2277
|
+
* @param x - X position to draw at
|
|
2278
|
+
* @param y - Y position to draw at
|
|
2279
|
+
* @param options - Drawing options
|
|
2280
|
+
*/
|
|
2281
|
+
static draw(ctx, sheetName, sprite, x, y, options) {
|
|
2282
|
+
const sheet = this.spritesheets.get(sheetName);
|
|
2283
|
+
if (!sheet) {
|
|
2284
|
+
console.warn(`Spritesheet '${sheetName}' not loaded`);
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
const spriteIndex = Math.floor(sprite) % sheet.numSprites;
|
|
2288
|
+
const col = spriteIndex % sheet.cols;
|
|
2289
|
+
const row = Math.floor(spriteIndex / sheet.cols);
|
|
2290
|
+
const sx = col * (sheet.spriteWidth + sheet.gap);
|
|
2291
|
+
const sy = row * (sheet.spriteHeight + sheet.gap);
|
|
2292
|
+
const drawWidth = options?.width || sheet.spriteWidth;
|
|
2293
|
+
const drawHeight = options?.height || sheet.spriteHeight;
|
|
2294
|
+
const scale = options?.scale || 1;
|
|
2295
|
+
const rotation = options?.rotation || 0;
|
|
2296
|
+
const flipX = options?.flipX || false;
|
|
2297
|
+
const flipY = options?.flipY || false;
|
|
2298
|
+
const alpha = options?.alpha !== void 0 ? options.alpha : 1;
|
|
2299
|
+
ctx.save();
|
|
2300
|
+
ctx.translate(x, y);
|
|
2301
|
+
if (rotation !== 0) {
|
|
2302
|
+
ctx.rotate(rotation);
|
|
2303
|
+
}
|
|
2304
|
+
if (flipX || flipY) {
|
|
2305
|
+
ctx.scale(flipX ? -1 : 1, flipY ? -1 : 1);
|
|
2306
|
+
}
|
|
2307
|
+
if (scale !== 1) {
|
|
2308
|
+
ctx.scale(scale, scale);
|
|
2309
|
+
}
|
|
2310
|
+
if (alpha !== 1) {
|
|
2311
|
+
ctx.globalAlpha = alpha;
|
|
2312
|
+
}
|
|
2313
|
+
ctx.drawImage(
|
|
2314
|
+
sheet.image,
|
|
2315
|
+
sx,
|
|
2316
|
+
sy,
|
|
2317
|
+
sheet.spriteWidth,
|
|
2318
|
+
sheet.spriteHeight,
|
|
2319
|
+
-drawWidth / 2,
|
|
2320
|
+
-drawHeight / 2,
|
|
2321
|
+
drawWidth,
|
|
2322
|
+
drawHeight
|
|
2323
|
+
);
|
|
2324
|
+
ctx.restore();
|
|
2325
|
+
}
|
|
2326
|
+
/**
|
|
2327
|
+
* Draw a sprite at its actual position without centering
|
|
2328
|
+
* @param ctx - Klint context
|
|
2329
|
+
* @param sheetName - Name of the spritesheet
|
|
2330
|
+
* @param sprite - Sprite index to draw
|
|
2331
|
+
* @param x - X position to draw at
|
|
2332
|
+
* @param y - Y position to draw at
|
|
2333
|
+
* @param width - Width to draw (optional)
|
|
2334
|
+
* @param height - Height to draw (optional)
|
|
2335
|
+
*/
|
|
2336
|
+
static drawCorner(ctx, sheetName, sprite, x, y, width, height) {
|
|
2337
|
+
const sheet = this.spritesheets.get(sheetName);
|
|
2338
|
+
if (!sheet) {
|
|
2339
|
+
console.warn(`Spritesheet '${sheetName}' not loaded`);
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
const spriteIndex = Math.floor(sprite) % sheet.numSprites;
|
|
2343
|
+
const col = spriteIndex % sheet.cols;
|
|
2344
|
+
const row = Math.floor(spriteIndex / sheet.cols);
|
|
2345
|
+
const sx = col * (sheet.spriteWidth + sheet.gap);
|
|
2346
|
+
const sy = row * (sheet.spriteHeight + sheet.gap);
|
|
2347
|
+
const drawWidth = width || sheet.spriteWidth;
|
|
2348
|
+
const drawHeight = height || sheet.spriteHeight;
|
|
2349
|
+
ctx.drawImage(
|
|
2350
|
+
sheet.image,
|
|
2351
|
+
sx,
|
|
2352
|
+
sy,
|
|
2353
|
+
sheet.spriteWidth,
|
|
2354
|
+
sheet.spriteHeight,
|
|
2355
|
+
x,
|
|
2356
|
+
y,
|
|
2357
|
+
drawWidth,
|
|
2358
|
+
drawHeight
|
|
2359
|
+
);
|
|
2360
|
+
}
|
|
2361
|
+
/**
|
|
2362
|
+
* Create an animation from a range of sprites
|
|
2363
|
+
* @param sheetName - Name of the spritesheet
|
|
2364
|
+
* @param startSprite - Starting sprite index
|
|
2365
|
+
* @param endSprite - Ending sprite index
|
|
2366
|
+
* @param frameDuration - Duration of each frame in milliseconds
|
|
2367
|
+
* @returns Animation object
|
|
2368
|
+
*/
|
|
2369
|
+
static animation(sheetName, startSprite, endSprite, frameDuration = 100) {
|
|
2370
|
+
return new SpriteAnimation(
|
|
2371
|
+
sheetName,
|
|
2372
|
+
startSprite,
|
|
2373
|
+
endSprite,
|
|
2374
|
+
frameDuration
|
|
2375
|
+
);
|
|
2376
|
+
}
|
|
2377
|
+
/**
|
|
2378
|
+
* Check if a spritesheet is loaded
|
|
2379
|
+
* @param name - Name of the spritesheet
|
|
2380
|
+
* @returns True if loaded, false otherwise
|
|
2381
|
+
*/
|
|
2382
|
+
static hasSheet(name) {
|
|
2383
|
+
return this.spritesheets.has(name);
|
|
2384
|
+
}
|
|
2385
|
+
/**
|
|
2386
|
+
* Unload a spritesheet
|
|
2387
|
+
* @param name - Name of the spritesheet to unload
|
|
2388
|
+
*/
|
|
2389
|
+
static unload(name) {
|
|
2390
|
+
this.spritesheets.delete(name);
|
|
2391
|
+
this.loadingPromises.delete(name);
|
|
2392
|
+
}
|
|
2393
|
+
/**
|
|
2394
|
+
* Unload all spritesheets
|
|
2395
|
+
*/
|
|
2396
|
+
static clear() {
|
|
2397
|
+
this.spritesheets.clear();
|
|
2398
|
+
this.loadingPromises.clear();
|
|
2399
|
+
}
|
|
2400
|
+
/**
|
|
2401
|
+
* Get the number of loaded spritesheets
|
|
2402
|
+
* @returns Number of loaded spritesheets
|
|
2403
|
+
*/
|
|
2404
|
+
static count() {
|
|
2405
|
+
return this.spritesheets.size;
|
|
2406
|
+
}
|
|
2407
|
+
/**
|
|
2408
|
+
* Get all loaded spritesheet names
|
|
2409
|
+
* @returns Array of spritesheet names
|
|
2410
|
+
*/
|
|
2411
|
+
static getSheetNames() {
|
|
2412
|
+
return Array.from(this.spritesheets.keys());
|
|
2413
|
+
}
|
|
2414
|
+
};
|
|
2415
|
+
Sprites.spritesheets = /* @__PURE__ */ new Map();
|
|
2416
|
+
Sprites.loadingPromises = /* @__PURE__ */ new Map();
|
|
2417
|
+
var SpriteAnimation = class {
|
|
2418
|
+
constructor(sheetName, startSprite, endSprite, frameDuration) {
|
|
2419
|
+
this.currentFrame = 0;
|
|
2420
|
+
this.lastFrameTime = 0;
|
|
2421
|
+
this.playing = true;
|
|
2422
|
+
this.loop = true;
|
|
2423
|
+
this.sheetName = sheetName;
|
|
2424
|
+
this.startSprite = startSprite;
|
|
2425
|
+
this.endSprite = endSprite;
|
|
2426
|
+
this.frameDuration = frameDuration;
|
|
2427
|
+
this.currentFrame = startSprite;
|
|
2428
|
+
}
|
|
2429
|
+
/**
|
|
2430
|
+
* Update animation frame
|
|
2431
|
+
* @param deltaTime - Time since last update in milliseconds
|
|
2432
|
+
*/
|
|
2433
|
+
update(deltaTime) {
|
|
2434
|
+
if (!this.playing) return;
|
|
2435
|
+
this.lastFrameTime += deltaTime;
|
|
2436
|
+
if (this.lastFrameTime >= this.frameDuration) {
|
|
2437
|
+
this.lastFrameTime = 0;
|
|
2438
|
+
this.currentFrame++;
|
|
2439
|
+
if (this.currentFrame > this.endSprite) {
|
|
2440
|
+
if (this.loop) {
|
|
2441
|
+
this.currentFrame = this.startSprite;
|
|
2442
|
+
} else {
|
|
2443
|
+
this.currentFrame = this.endSprite;
|
|
2444
|
+
this.playing = false;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
/**
|
|
2450
|
+
* Draw the current animation frame
|
|
2451
|
+
* @param ctx - Klint context
|
|
2452
|
+
* @param x - X position
|
|
2453
|
+
* @param y - Y position
|
|
2454
|
+
* @param options - Drawing options
|
|
2455
|
+
*/
|
|
2456
|
+
draw(ctx, x, y, options) {
|
|
2457
|
+
Sprites.draw(ctx, this.sheetName, this.currentFrame, x, y, options);
|
|
2458
|
+
}
|
|
2459
|
+
/**
|
|
2460
|
+
* Play the animation
|
|
2461
|
+
*/
|
|
2462
|
+
play() {
|
|
2463
|
+
this.playing = true;
|
|
2464
|
+
}
|
|
2465
|
+
/**
|
|
2466
|
+
* Pause the animation
|
|
2467
|
+
*/
|
|
2468
|
+
pause() {
|
|
2469
|
+
this.playing = false;
|
|
2470
|
+
}
|
|
2471
|
+
/**
|
|
2472
|
+
* Stop the animation and reset to start
|
|
2473
|
+
*/
|
|
2474
|
+
stop() {
|
|
2475
|
+
this.playing = false;
|
|
2476
|
+
this.currentFrame = this.startSprite;
|
|
2477
|
+
this.lastFrameTime = 0;
|
|
2478
|
+
}
|
|
2479
|
+
/**
|
|
2480
|
+
* Set whether the animation should loop
|
|
2481
|
+
* @param loop - True to loop, false to play once
|
|
2482
|
+
*/
|
|
2483
|
+
setLoop(loop) {
|
|
2484
|
+
this.loop = loop;
|
|
2485
|
+
}
|
|
2486
|
+
/**
|
|
2487
|
+
* Get the current frame
|
|
2488
|
+
* @returns Current frame index
|
|
2489
|
+
*/
|
|
2490
|
+
getCurrentFrame() {
|
|
2491
|
+
return this.currentFrame;
|
|
2492
|
+
}
|
|
2493
|
+
/**
|
|
2494
|
+
* Set the current frame
|
|
2495
|
+
* @param frame - Frame index to set
|
|
2496
|
+
*/
|
|
2497
|
+
setFrame(frame) {
|
|
2498
|
+
this.currentFrame = Math.max(
|
|
2499
|
+
this.startSprite,
|
|
2500
|
+
Math.min(this.endSprite, frame)
|
|
2501
|
+
);
|
|
2502
|
+
}
|
|
2503
|
+
/**
|
|
2504
|
+
* Check if animation is playing
|
|
2505
|
+
* @returns True if playing, false otherwise
|
|
2506
|
+
*/
|
|
2507
|
+
isPlaying() {
|
|
2508
|
+
return this.playing;
|
|
2509
|
+
}
|
|
2510
|
+
};
|
|
2511
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2512
|
+
0 && (module.exports = {
|
|
2513
|
+
CatmullRom,
|
|
2514
|
+
Delaunay,
|
|
2515
|
+
FontParser,
|
|
2516
|
+
Sprites,
|
|
2517
|
+
Things
|
|
2518
|
+
});
|