imagescript 1.3.1 → 3.0.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/LICENSE +4 -1
- package/README.md +64 -26
- package/THIRD_PARTY_NOTICES.md +74 -0
- package/dist/codecs/crc.d.ts +9 -0
- package/dist/codecs/crc.js +29 -0
- package/dist/codecs/exif.d.ts +11 -0
- package/dist/codecs/exif.js +27 -0
- package/dist/codecs/gif.d.ts +44 -0
- package/dist/codecs/gif.js +401 -0
- package/dist/codecs/jpeg.d.ts +35 -0
- package/dist/codecs/jpeg.js +980 -0
- package/dist/codecs/png.d.ts +44 -0
- package/dist/codecs/png.js +442 -0
- package/dist/codecs/quantize.d.ts +25 -0
- package/dist/codecs/quantize.js +302 -0
- package/dist/codecs/tiff.d.ts +6 -0
- package/dist/codecs/tiff.js +546 -0
- package/dist/codecs/zlib.d.ts +22 -0
- package/dist/codecs/zlib.js +607 -0
- package/dist/frame.d.ts +57 -0
- package/dist/frame.js +65 -0
- package/dist/gif.d.ts +44 -0
- package/dist/gif.js +93 -0
- package/dist/image-type.d.ts +20 -0
- package/dist/image-type.js +49 -0
- package/dist/image.d.ts +521 -0
- package/dist/image.js +1118 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +13 -0
- package/dist/package.json +1 -0
- package/dist/render/font.d.ts +63 -0
- package/dist/render/font.js +857 -0
- package/dist/render/svg.d.ts +11 -0
- package/dist/render/svg.js +1717 -0
- package/dist/render/text.d.ts +61 -0
- package/dist/render/text.js +317 -0
- package/dist/text-layout.d.ts +46 -0
- package/dist/text-layout.js +35 -0
- package/dist/util/blur.d.ts +13 -0
- package/dist/util/blur.js +146 -0
- package/dist/util/mem.d.ts +10 -0
- package/dist/util/mem.js +23 -0
- package/dist/util/resample.d.ts +12 -0
- package/dist/util/resample.js +99 -0
- package/dist/util/utf8.d.ts +5 -0
- package/dist/util/utf8.js +55 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/licenses/Apache-2.0.txt +202 -0
- package/licenses/README.ijg +260 -0
- package/package.json +31 -7
- package/ImageScript.d.ts +0 -954
- package/ImageScript.js +0 -1669
- package/bench/_.mjs +0 -166
- package/bench/all.mjs +0 -4
- package/bench/blur.mjs +0 -59
- package/bench/flip.mjs +0 -63
- package/bench/overlay.mjs +0 -35
- package/bench/package.json +0 -7
- package/bench/readme.md +0 -64
- package/bench/resize.mjs +0 -52
- package/codecs/LICENSE +0 -21
- package/codecs/lib.d.ts +0 -50
- package/codecs/lib.js +0 -6
- package/codecs/lib.mjs +0 -1
- package/codecs/node/bin/arm64-darwin.node +0 -0
- package/codecs/node/bin/arm64-linux.node +0 -0
- package/codecs/node/bin/x64-darwin.node +0 -0
- package/codecs/node/bin/x64-linux.node +0 -0
- package/codecs/node/bin/x64-win32.node +0 -0
- package/codecs/node/index.js +0 -3
- package/codecs/node/index.mjs +0 -6
- package/codecs/package.json +0 -14
- package/codecs/readme.md +0 -38
- package/codecs/wasm/bin/codecs.wasm +0 -0
- package/codecs/wasm/bin/simd.wasm +0 -0
- package/codecs/wasm/bundle/browser.js +0 -1
- package/codecs/wasm/index.js +0 -1
- package/codecs/wasm/index.mjs +0 -1
- package/png/README.md +0 -1
- package/png/node.js +0 -1137
- package/png/src/crc.mjs +0 -49
- package/png/src/mem.mjs +0 -24
- package/png/src/png.mjs +0 -254
- package/png/src/zlib.mjs +0 -618
- package/tsconfig.json +0 -21
- package/utils/mem.js +0 -26
- package/v2/codecs/magic.js +0 -25
- package/v2/framebuffer.d.ts +0 -67
- package/v2/framebuffer.js +0 -2352
- package/v2/framebuffer.mjs +0 -126
- package/v2/framebuffer.rs +0 -1264
- package/v2/index.d.ts +0 -84
- package/v2/index.js +0 -261
- package/v2/ops/blur.mjs +0 -303
- package/v2/ops/color.mjs +0 -277
- package/v2/ops/crop.mjs +0 -64
- package/v2/ops/fill.mjs +0 -33
- package/v2/ops/flip.mjs +0 -33
- package/v2/ops/index.mjs +0 -9
- package/v2/ops/iterator.mjs +0 -34
- package/v2/ops/overlay.mjs +0 -63
- package/v2/ops/resize.mjs +0 -166
- package/v2/ops/rotate.mjs +0 -117
- package/v2/util/mem.mjs +0 -24
- package/wasm/any/font.wasm +0 -0
- package/wasm/any/gif.wasm +0 -0
- package/wasm/any/jpeg.wasm +0 -0
- package/wasm/any/png.wasm +0 -0
- package/wasm/any/svg.wasm +0 -0
- package/wasm/any/tiff.wasm +0 -0
- package/wasm/node/font.js +0 -142
- package/wasm/node/gif.js +0 -76
- package/wasm/node/jpeg.js +0 -92
- package/wasm/node/png.js +0 -49
- package/wasm/node/svg.js +0 -56
- package/wasm/node/tiff.js +0 -47
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
export class Font {
|
|
2
|
+
data;
|
|
3
|
+
view;
|
|
4
|
+
tables;
|
|
5
|
+
unitsPerEm;
|
|
6
|
+
indexToLocFormat;
|
|
7
|
+
ascender;
|
|
8
|
+
descender;
|
|
9
|
+
lineGap;
|
|
10
|
+
numberOfHMetrics;
|
|
11
|
+
numGlyphs;
|
|
12
|
+
outlines;
|
|
13
|
+
cff = null;
|
|
14
|
+
cmap;
|
|
15
|
+
outlineCache;
|
|
16
|
+
kerning;
|
|
17
|
+
constructor(data) {
|
|
18
|
+
this.data = data;
|
|
19
|
+
this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
20
|
+
let offset = 0;
|
|
21
|
+
const signature = this.u32(0);
|
|
22
|
+
if (signature === 0x74746366)
|
|
23
|
+
offset = this.u32(12);
|
|
24
|
+
else if (signature === 0x774f4646 || signature === 0x774f4632)
|
|
25
|
+
throw new Error('Font: WOFF fonts are not supported');
|
|
26
|
+
const version = this.u32(offset);
|
|
27
|
+
if (version !== 0x00010000 && version !== 0x4f54544f && version !== 0x74727565)
|
|
28
|
+
throw new Error('Font: invalid font file');
|
|
29
|
+
this.tables = {};
|
|
30
|
+
const count = this.u16(offset + 4);
|
|
31
|
+
for (let i = 0; i < count; i++) {
|
|
32
|
+
const record = offset + 12 + i * 16;
|
|
33
|
+
const tag = String.fromCharCode(...data.subarray(record, record + 4));
|
|
34
|
+
this.tables[tag] = { offset: this.u32(record + 8), length: this.u32(record + 12) };
|
|
35
|
+
}
|
|
36
|
+
for (const tag of ['head', 'hhea', 'hmtx', 'maxp', 'cmap'])
|
|
37
|
+
if (!this.tables[tag])
|
|
38
|
+
throw new Error(`Font: missing ${tag} table`);
|
|
39
|
+
const head = this.tables.head.offset;
|
|
40
|
+
this.unitsPerEm = this.u16(head + 18);
|
|
41
|
+
this.indexToLocFormat = this.i16(head + 50);
|
|
42
|
+
const hhea = this.tables.hhea.offset;
|
|
43
|
+
this.ascender = this.i16(hhea + 4);
|
|
44
|
+
this.descender = this.i16(hhea + 6);
|
|
45
|
+
this.lineGap = this.i16(hhea + 8);
|
|
46
|
+
this.numberOfHMetrics = this.u16(hhea + 34);
|
|
47
|
+
this.numGlyphs = this.u16(this.tables.maxp.offset + 4);
|
|
48
|
+
if (this.tables.glyf && this.tables.loca)
|
|
49
|
+
this.outlines = 'glyf';
|
|
50
|
+
else if (this.tables['CFF ']) {
|
|
51
|
+
this.outlines = 'cff';
|
|
52
|
+
this.cff = parseCFF(this, this.tables['CFF '].offset);
|
|
53
|
+
}
|
|
54
|
+
else if (this.tables.CFF2)
|
|
55
|
+
throw new Error('Font: CFF2 outlines are not supported');
|
|
56
|
+
else
|
|
57
|
+
throw new Error('Font: no supported glyph outlines');
|
|
58
|
+
this.cmap = parseCmap(this, this.tables.cmap.offset);
|
|
59
|
+
this.outlineCache = new Map();
|
|
60
|
+
this.kerning = (this.tables.GPOS && parseGPOSKerning(this, this.tables.GPOS.offset)) || (this.tables.kern && parseKern(this, this.tables.kern.offset)) || null;
|
|
61
|
+
}
|
|
62
|
+
u8(o) { return this.data[o]; }
|
|
63
|
+
i8(o) { return this.view.getInt8(o); }
|
|
64
|
+
u16(o) { return this.view.getUint16(o); }
|
|
65
|
+
i16(o) { return this.view.getInt16(o); }
|
|
66
|
+
u32(o) { return this.view.getUint32(o); }
|
|
67
|
+
i32(o) { return this.view.getInt32(o); }
|
|
68
|
+
glyphIndex(codepoint) {
|
|
69
|
+
return this.cmap(codepoint) || 0;
|
|
70
|
+
}
|
|
71
|
+
advance(glyph) {
|
|
72
|
+
const hmtx = this.tables.hmtx.offset;
|
|
73
|
+
const index = Math.min(glyph, this.numberOfHMetrics - 1);
|
|
74
|
+
return this.u16(hmtx + index * 4);
|
|
75
|
+
}
|
|
76
|
+
outline(glyph) {
|
|
77
|
+
if (glyph >= this.numGlyphs)
|
|
78
|
+
glyph = 0;
|
|
79
|
+
let path = this.outlineCache.get(glyph);
|
|
80
|
+
if (!path) {
|
|
81
|
+
path = this.outlines === 'glyf' ? this.glyfOutline(glyph, 0) : this.cffOutline(glyph);
|
|
82
|
+
this.outlineCache.set(glyph, path);
|
|
83
|
+
}
|
|
84
|
+
return path;
|
|
85
|
+
}
|
|
86
|
+
glyfOutline(glyph, depth) {
|
|
87
|
+
if (depth > 8)
|
|
88
|
+
return [];
|
|
89
|
+
const loca = this.tables.loca.offset;
|
|
90
|
+
const start = this.indexToLocFormat ? this.u32(loca + glyph * 4) : this.u16(loca + glyph * 2) * 2;
|
|
91
|
+
const end = this.indexToLocFormat ? this.u32(loca + glyph * 4 + 4) : this.u16(loca + glyph * 2 + 2) * 2;
|
|
92
|
+
if (end <= start)
|
|
93
|
+
return [];
|
|
94
|
+
const offset = this.tables.glyf.offset + start;
|
|
95
|
+
const contours = this.i16(offset);
|
|
96
|
+
return contours >= 0 ? this.simpleGlyph(offset, contours) : this.compositeGlyph(offset, depth);
|
|
97
|
+
}
|
|
98
|
+
simpleGlyph(offset, contourCount) {
|
|
99
|
+
const endPoints = [];
|
|
100
|
+
for (let i = 0; i < contourCount; i++)
|
|
101
|
+
endPoints.push(this.u16(offset + 10 + i * 2));
|
|
102
|
+
const pointCount = contourCount ? endPoints[contourCount - 1] + 1 : 0;
|
|
103
|
+
let p = offset + 10 + contourCount * 2;
|
|
104
|
+
p += 2 + this.u16(p);
|
|
105
|
+
const flags = new Uint8Array(pointCount);
|
|
106
|
+
for (let i = 0; i < pointCount;) {
|
|
107
|
+
const flag = this.u8(p++);
|
|
108
|
+
flags[i++] = flag;
|
|
109
|
+
if (flag & 8) {
|
|
110
|
+
for (let repeat = this.u8(p++); repeat > 0 && i < pointCount; repeat--)
|
|
111
|
+
flags[i++] = flag;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const xs = new Int32Array(pointCount);
|
|
115
|
+
const ys = new Int32Array(pointCount);
|
|
116
|
+
for (const [coords, short, same] of [[xs, 2, 16], [ys, 4, 32]]) {
|
|
117
|
+
let value = 0;
|
|
118
|
+
for (let i = 0; i < pointCount; i++) {
|
|
119
|
+
const flag = flags[i];
|
|
120
|
+
if (flag & short) {
|
|
121
|
+
const delta = this.u8(p++);
|
|
122
|
+
value += flag & same ? delta : -delta;
|
|
123
|
+
}
|
|
124
|
+
else if (!(flag & same)) {
|
|
125
|
+
value += this.i16(p);
|
|
126
|
+
p += 2;
|
|
127
|
+
}
|
|
128
|
+
coords[i] = value;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const path = [];
|
|
132
|
+
let first = 0;
|
|
133
|
+
for (const last of endPoints) {
|
|
134
|
+
contourToPath(path, xs, ys, flags, first, last);
|
|
135
|
+
first = last + 1;
|
|
136
|
+
}
|
|
137
|
+
return path;
|
|
138
|
+
}
|
|
139
|
+
compositeGlyph(offset, depth) {
|
|
140
|
+
const path = [];
|
|
141
|
+
let p = offset + 10;
|
|
142
|
+
for (let more = true; more;) {
|
|
143
|
+
const flags = this.u16(p);
|
|
144
|
+
const glyph = this.u16(p + 2);
|
|
145
|
+
p += 4;
|
|
146
|
+
let dx, dy;
|
|
147
|
+
if (flags & 1) {
|
|
148
|
+
dx = flags & 2 ? this.i16(p) : this.u16(p);
|
|
149
|
+
dy = flags & 2 ? this.i16(p + 2) : this.u16(p + 2);
|
|
150
|
+
p += 4;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
dx = flags & 2 ? this.i8(p) : this.u8(p);
|
|
154
|
+
dy = flags & 2 ? this.i8(p + 1) : this.u8(p + 1);
|
|
155
|
+
p += 2;
|
|
156
|
+
}
|
|
157
|
+
if (!(flags & 2))
|
|
158
|
+
dx = dy = 0;
|
|
159
|
+
let a = 1, b = 0, c = 0, d = 1;
|
|
160
|
+
const f2dot14 = (o) => this.i16(o) / 16384;
|
|
161
|
+
if (flags & 8) {
|
|
162
|
+
a = d = f2dot14(p);
|
|
163
|
+
p += 2;
|
|
164
|
+
}
|
|
165
|
+
else if (flags & 0x40) {
|
|
166
|
+
a = f2dot14(p);
|
|
167
|
+
d = f2dot14(p + 2);
|
|
168
|
+
p += 4;
|
|
169
|
+
}
|
|
170
|
+
else if (flags & 0x80) {
|
|
171
|
+
a = f2dot14(p);
|
|
172
|
+
b = f2dot14(p + 2);
|
|
173
|
+
c = f2dot14(p + 4);
|
|
174
|
+
d = f2dot14(p + 6);
|
|
175
|
+
p += 8;
|
|
176
|
+
}
|
|
177
|
+
for (const command of this.glyfOutline(glyph, depth + 1)) {
|
|
178
|
+
const transformed = [command[0]];
|
|
179
|
+
for (let i = 1; i < command.length; i += 2) {
|
|
180
|
+
const x = command[i], y = command[i + 1];
|
|
181
|
+
transformed.push(a * x + c * y + dx, b * x + d * y + dy);
|
|
182
|
+
}
|
|
183
|
+
path.push(transformed);
|
|
184
|
+
}
|
|
185
|
+
more = !!(flags & 0x20);
|
|
186
|
+
}
|
|
187
|
+
return path;
|
|
188
|
+
}
|
|
189
|
+
cffOutline(glyph) {
|
|
190
|
+
return cffCharString(this.cff, glyph);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function contourToPath(path, xs, ys, flags, first, last) {
|
|
194
|
+
if (last < first)
|
|
195
|
+
return;
|
|
196
|
+
const count = last - first + 1;
|
|
197
|
+
const on = (i) => flags[first + (i % count)] & 1;
|
|
198
|
+
const x = (i) => xs[first + (i % count)];
|
|
199
|
+
const y = (i) => ys[first + (i % count)];
|
|
200
|
+
let start = -1;
|
|
201
|
+
for (let i = 0; i < count; i++)
|
|
202
|
+
if (on(i)) {
|
|
203
|
+
start = i;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
let sx, sy;
|
|
207
|
+
if (start === -1) {
|
|
208
|
+
sx = (x(0) + x(1)) / 2;
|
|
209
|
+
sy = (y(0) + y(1)) / 2;
|
|
210
|
+
start = 1;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
sx = x(start);
|
|
214
|
+
sy = y(start);
|
|
215
|
+
start++;
|
|
216
|
+
}
|
|
217
|
+
path.push(['M', sx, sy]);
|
|
218
|
+
let cx = 0, cy = 0, pending = false;
|
|
219
|
+
for (let n = 0; n < count; n++) {
|
|
220
|
+
const i = start + n;
|
|
221
|
+
const px = x(i), py = y(i);
|
|
222
|
+
if (on(i)) {
|
|
223
|
+
if (!pending)
|
|
224
|
+
path.push(['L', px, py]);
|
|
225
|
+
else {
|
|
226
|
+
path.push(['Q', cx, cy, px, py]);
|
|
227
|
+
pending = false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
if (pending) {
|
|
232
|
+
const mx = (cx + px) / 2, my = (cy + py) / 2;
|
|
233
|
+
path.push(['Q', cx, cy, mx, my]);
|
|
234
|
+
}
|
|
235
|
+
cx = px;
|
|
236
|
+
cy = py;
|
|
237
|
+
pending = true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (pending)
|
|
241
|
+
path.push(['Q', cx, cy, sx, sy]);
|
|
242
|
+
path.push(['Z']);
|
|
243
|
+
}
|
|
244
|
+
function parseCmap(font, offset) {
|
|
245
|
+
const count = font.u16(offset + 2);
|
|
246
|
+
const subtables = [];
|
|
247
|
+
for (let i = 0; i < count; i++) {
|
|
248
|
+
const record = offset + 4 + i * 8;
|
|
249
|
+
const platform = font.u16(record);
|
|
250
|
+
const encoding = font.u16(record + 2);
|
|
251
|
+
const table = offset + font.u32(record + 4);
|
|
252
|
+
subtables.push({ platform, encoding, table, format: font.u16(table) });
|
|
253
|
+
}
|
|
254
|
+
const rank = ({ platform, encoding, format }) => {
|
|
255
|
+
if (![0, 4, 6, 12].includes(format))
|
|
256
|
+
return -1;
|
|
257
|
+
if (platform === 3 && encoding === 10)
|
|
258
|
+
return 6;
|
|
259
|
+
if (platform === 0 && (encoding === 4 || encoding === 6))
|
|
260
|
+
return 5;
|
|
261
|
+
if (platform === 3 && encoding === 1)
|
|
262
|
+
return 4;
|
|
263
|
+
if (platform === 0)
|
|
264
|
+
return 3;
|
|
265
|
+
if (platform === 3 && encoding === 0)
|
|
266
|
+
return 2;
|
|
267
|
+
if (platform === 1 && encoding === 0)
|
|
268
|
+
return 1;
|
|
269
|
+
return -1;
|
|
270
|
+
};
|
|
271
|
+
const best = subtables.filter(s => rank(s) >= 0).sort((a, b) => rank(b) - rank(a))[0];
|
|
272
|
+
if (!best)
|
|
273
|
+
throw new Error('Font: no supported character map');
|
|
274
|
+
const { table, format, platform, encoding } = best;
|
|
275
|
+
const symbol = platform === 3 && encoding === 0;
|
|
276
|
+
let lookup;
|
|
277
|
+
if (format === 0)
|
|
278
|
+
lookup = (c) => c < 256 ? font.u8(table + 6 + c) : 0;
|
|
279
|
+
else if (format === 6) {
|
|
280
|
+
const first = font.u16(table + 6), entries = font.u16(table + 8);
|
|
281
|
+
lookup = (c) => c >= first && c < first + entries ? font.u16(table + 10 + (c - first) * 2) : 0;
|
|
282
|
+
}
|
|
283
|
+
else if (format === 4) {
|
|
284
|
+
const segments = font.u16(table + 6) / 2;
|
|
285
|
+
const ends = table + 14;
|
|
286
|
+
const starts = ends + segments * 2 + 2;
|
|
287
|
+
const deltas = starts + segments * 2;
|
|
288
|
+
const rangeOffsets = deltas + segments * 2;
|
|
289
|
+
lookup = (c) => {
|
|
290
|
+
if (c > 0xffff)
|
|
291
|
+
return 0;
|
|
292
|
+
let lo = 0, hi = segments - 1;
|
|
293
|
+
while (lo < hi) {
|
|
294
|
+
const mid = lo + hi >> 1;
|
|
295
|
+
if (font.u16(ends + mid * 2) < c)
|
|
296
|
+
lo = mid + 1;
|
|
297
|
+
else
|
|
298
|
+
hi = mid;
|
|
299
|
+
}
|
|
300
|
+
const start = font.u16(starts + lo * 2);
|
|
301
|
+
if (c < start || c > font.u16(ends + lo * 2))
|
|
302
|
+
return 0;
|
|
303
|
+
const delta = font.u16(deltas + lo * 2);
|
|
304
|
+
const rangeOffset = font.u16(rangeOffsets + lo * 2);
|
|
305
|
+
if (!rangeOffset)
|
|
306
|
+
return c + delta & 0xffff;
|
|
307
|
+
const glyph = font.u16(rangeOffsets + lo * 2 + rangeOffset + (c - start) * 2);
|
|
308
|
+
return glyph ? glyph + delta & 0xffff : 0;
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
const groups = font.u32(table + 12);
|
|
313
|
+
lookup = (c) => {
|
|
314
|
+
let lo = 0, hi = groups - 1;
|
|
315
|
+
while (lo <= hi) {
|
|
316
|
+
const mid = lo + hi >> 1;
|
|
317
|
+
const group = table + 16 + mid * 12;
|
|
318
|
+
if (c < font.u32(group))
|
|
319
|
+
hi = mid - 1;
|
|
320
|
+
else if (c > font.u32(group + 4))
|
|
321
|
+
lo = mid + 1;
|
|
322
|
+
else
|
|
323
|
+
return font.u32(group + 8) + c - font.u32(group);
|
|
324
|
+
}
|
|
325
|
+
return 0;
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
return symbol ? (c) => lookup(c) || (c < 256 ? lookup(0xf000 + c) : 0) : lookup;
|
|
329
|
+
}
|
|
330
|
+
function parseGPOSKerning(font, gpos) {
|
|
331
|
+
const featureList = gpos + font.u16(gpos + 6);
|
|
332
|
+
const lookupList = gpos + font.u16(gpos + 8);
|
|
333
|
+
const lookups = new Set();
|
|
334
|
+
for (let i = 0, count = font.u16(featureList); i < count; i++) {
|
|
335
|
+
const record = featureList + 2 + i * 6;
|
|
336
|
+
if (font.u32(record) !== 0x6b65726e)
|
|
337
|
+
continue;
|
|
338
|
+
const feature = featureList + font.u16(record + 4);
|
|
339
|
+
for (let j = 0, n = font.u16(feature + 2); j < n; j++)
|
|
340
|
+
lookups.add(font.u16(feature + 4 + j * 2));
|
|
341
|
+
}
|
|
342
|
+
const pairLookups = [];
|
|
343
|
+
for (const index of [...lookups].sort((a, b) => a - b)) {
|
|
344
|
+
if (index >= font.u16(lookupList))
|
|
345
|
+
continue;
|
|
346
|
+
const lookup = lookupList + font.u16(lookupList + 2 + index * 2);
|
|
347
|
+
const type = font.u16(lookup);
|
|
348
|
+
const subtables = [];
|
|
349
|
+
for (let i = 0, count = font.u16(lookup + 4); i < count; i++) {
|
|
350
|
+
let subtable = lookup + font.u16(lookup + 6 + i * 2);
|
|
351
|
+
if (type === 9) {
|
|
352
|
+
if (font.u16(subtable + 2) !== 2)
|
|
353
|
+
continue;
|
|
354
|
+
subtable += font.u32(subtable + 4);
|
|
355
|
+
}
|
|
356
|
+
else if (type !== 2)
|
|
357
|
+
continue;
|
|
358
|
+
const pair = parsePairPos(font, subtable);
|
|
359
|
+
if (pair)
|
|
360
|
+
subtables.push(pair);
|
|
361
|
+
}
|
|
362
|
+
if (subtables.length)
|
|
363
|
+
pairLookups.push(subtables);
|
|
364
|
+
}
|
|
365
|
+
if (!pairLookups.length)
|
|
366
|
+
return null;
|
|
367
|
+
return (left, right) => {
|
|
368
|
+
let total = 0;
|
|
369
|
+
for (const subtables of pairLookups) {
|
|
370
|
+
for (const subtable of subtables) {
|
|
371
|
+
const value = subtable(left, right);
|
|
372
|
+
if (value === undefined)
|
|
373
|
+
continue;
|
|
374
|
+
total += value;
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return total;
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function valueRecord(format) {
|
|
382
|
+
let size = 0, xAdvance = -1;
|
|
383
|
+
for (let bit = 0; bit < 8; bit++) {
|
|
384
|
+
if (!(format & 1 << bit))
|
|
385
|
+
continue;
|
|
386
|
+
if (bit === 2)
|
|
387
|
+
xAdvance = size;
|
|
388
|
+
size += 2;
|
|
389
|
+
}
|
|
390
|
+
return { size, xAdvance };
|
|
391
|
+
}
|
|
392
|
+
function parsePairPos(font, table) {
|
|
393
|
+
const format = font.u16(table);
|
|
394
|
+
const coverage = parseCoverage(font, table + font.u16(table + 2));
|
|
395
|
+
const first = valueRecord(font.u16(table + 4));
|
|
396
|
+
const second = valueRecord(font.u16(table + 6));
|
|
397
|
+
if (first.xAdvance === -1)
|
|
398
|
+
return null;
|
|
399
|
+
if (format === 1) {
|
|
400
|
+
const sets = font.u16(table + 8);
|
|
401
|
+
const recordSize = 2 + first.size + second.size;
|
|
402
|
+
return (left, right) => {
|
|
403
|
+
const index = coverage(left);
|
|
404
|
+
if (index === -1 || index >= sets)
|
|
405
|
+
return undefined;
|
|
406
|
+
const set = table + font.u16(table + 10 + index * 2);
|
|
407
|
+
let lo = 0, hi = font.u16(set) - 1;
|
|
408
|
+
while (lo <= hi) {
|
|
409
|
+
const mid = lo + hi >> 1;
|
|
410
|
+
const record = set + 2 + mid * recordSize;
|
|
411
|
+
const glyph = font.u16(record);
|
|
412
|
+
if (glyph < right)
|
|
413
|
+
lo = mid + 1;
|
|
414
|
+
else if (glyph > right)
|
|
415
|
+
hi = mid - 1;
|
|
416
|
+
else
|
|
417
|
+
return font.i16(record + 2 + first.xAdvance);
|
|
418
|
+
}
|
|
419
|
+
return undefined;
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
if (format === 2) {
|
|
423
|
+
const classes1 = parseClassDef(font, table + font.u16(table + 8));
|
|
424
|
+
const classes2 = parseClassDef(font, table + font.u16(table + 10));
|
|
425
|
+
const class1Count = font.u16(table + 12), class2Count = font.u16(table + 14);
|
|
426
|
+
const recordSize = first.size + second.size;
|
|
427
|
+
return (left, right) => {
|
|
428
|
+
if (coverage(left) === -1)
|
|
429
|
+
return undefined;
|
|
430
|
+
const c1 = classes1(left), c2 = classes2(right);
|
|
431
|
+
if (c1 >= class1Count || c2 >= class2Count)
|
|
432
|
+
return undefined;
|
|
433
|
+
return font.i16(table + 16 + (c1 * class2Count + c2) * recordSize + first.xAdvance);
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
function parseCoverage(font, table) {
|
|
439
|
+
const format = font.u16(table), count = font.u16(table + 2);
|
|
440
|
+
return glyph => {
|
|
441
|
+
let lo = 0, hi = count - 1;
|
|
442
|
+
while (lo <= hi) {
|
|
443
|
+
const mid = lo + hi >> 1;
|
|
444
|
+
if (format === 1) {
|
|
445
|
+
const value = font.u16(table + 4 + mid * 2);
|
|
446
|
+
if (value < glyph)
|
|
447
|
+
lo = mid + 1;
|
|
448
|
+
else if (value > glyph)
|
|
449
|
+
hi = mid - 1;
|
|
450
|
+
else
|
|
451
|
+
return mid;
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
const range = table + 4 + mid * 6;
|
|
455
|
+
if (font.u16(range + 2) < glyph)
|
|
456
|
+
lo = mid + 1;
|
|
457
|
+
else if (font.u16(range) > glyph)
|
|
458
|
+
hi = mid - 1;
|
|
459
|
+
else
|
|
460
|
+
return font.u16(range + 4) + glyph - font.u16(range);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return -1;
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function parseClassDef(font, table) {
|
|
467
|
+
const format = font.u16(table);
|
|
468
|
+
if (format === 1) {
|
|
469
|
+
const start = font.u16(table + 2), count = font.u16(table + 4);
|
|
470
|
+
return glyph => glyph >= start && glyph < start + count ? font.u16(table + 6 + (glyph - start) * 2) : 0;
|
|
471
|
+
}
|
|
472
|
+
const count = font.u16(table + 2);
|
|
473
|
+
return glyph => {
|
|
474
|
+
let lo = 0, hi = count - 1;
|
|
475
|
+
while (lo <= hi) {
|
|
476
|
+
const mid = lo + hi >> 1;
|
|
477
|
+
const range = table + 4 + mid * 6;
|
|
478
|
+
if (font.u16(range + 2) < glyph)
|
|
479
|
+
lo = mid + 1;
|
|
480
|
+
else if (font.u16(range) > glyph)
|
|
481
|
+
hi = mid - 1;
|
|
482
|
+
else
|
|
483
|
+
return font.u16(range + 4);
|
|
484
|
+
}
|
|
485
|
+
return 0;
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function parseKern(font, kern) {
|
|
489
|
+
if (font.u16(kern) !== 0)
|
|
490
|
+
return null;
|
|
491
|
+
const tables = [];
|
|
492
|
+
let offset = kern + 4;
|
|
493
|
+
for (let i = 0, count = font.u16(kern + 2); i < count; i++) {
|
|
494
|
+
const coverage = font.u16(offset + 4);
|
|
495
|
+
const length = coverage >> 8 === 0 ? 14 + font.u16(offset + 6) * 6 : font.u16(offset + 2);
|
|
496
|
+
if ((coverage & 0xff0f) === 1)
|
|
497
|
+
tables.push({ pairs: font.u16(offset + 6), start: offset + 14 });
|
|
498
|
+
offset += length;
|
|
499
|
+
}
|
|
500
|
+
if (!tables.length)
|
|
501
|
+
return null;
|
|
502
|
+
return (left, right) => {
|
|
503
|
+
const key = (left << 16 | right) >>> 0;
|
|
504
|
+
let total = 0;
|
|
505
|
+
for (const { pairs, start } of tables) {
|
|
506
|
+
let lo = 0, hi = pairs - 1;
|
|
507
|
+
while (lo <= hi) {
|
|
508
|
+
const mid = lo + hi >> 1;
|
|
509
|
+
const pair = start + mid * 6;
|
|
510
|
+
const value = font.u32(pair);
|
|
511
|
+
if (value < key)
|
|
512
|
+
lo = mid + 1;
|
|
513
|
+
else if (value > key)
|
|
514
|
+
hi = mid - 1;
|
|
515
|
+
else {
|
|
516
|
+
total += font.i16(pair + 4);
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return total;
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
function parseCFF(font, base) {
|
|
525
|
+
const data = font.data;
|
|
526
|
+
const readIndex = (offset) => {
|
|
527
|
+
const count = font.u16(offset);
|
|
528
|
+
if (count === 0)
|
|
529
|
+
return { items: [], end: offset + 2 };
|
|
530
|
+
const offSize = data[offset + 2];
|
|
531
|
+
const readOffset = (o) => {
|
|
532
|
+
let v = 0;
|
|
533
|
+
for (let i = 0; i < offSize; i++)
|
|
534
|
+
v = v * 256 + data[o + i];
|
|
535
|
+
return v;
|
|
536
|
+
};
|
|
537
|
+
const dataStart = offset + 3 + (count + 1) * offSize - 1;
|
|
538
|
+
const items = [];
|
|
539
|
+
for (let i = 0; i < count; i++) {
|
|
540
|
+
const start = dataStart + readOffset(offset + 3 + i * offSize);
|
|
541
|
+
const end = dataStart + readOffset(offset + 3 + (i + 1) * offSize);
|
|
542
|
+
items.push(data.subarray(start, end));
|
|
543
|
+
}
|
|
544
|
+
return { items, end: dataStart + readOffset(offset + 3 + count * offSize) };
|
|
545
|
+
};
|
|
546
|
+
const headerSize = data[base + 2];
|
|
547
|
+
const names = readIndex(base + headerSize);
|
|
548
|
+
const topDicts = readIndex(names.end);
|
|
549
|
+
const strings = readIndex(topDicts.end);
|
|
550
|
+
const globalSubrs = readIndex(strings.end).items;
|
|
551
|
+
const top = parseDict(topDicts.items[0]);
|
|
552
|
+
if (!top[17])
|
|
553
|
+
throw new Error('Font: CFF font has no charstrings');
|
|
554
|
+
const charStrings = readIndex(base + top[17][0]).items;
|
|
555
|
+
const readPrivate = (entry) => {
|
|
556
|
+
if (!entry)
|
|
557
|
+
return { subrs: [] };
|
|
558
|
+
const [size, offset] = entry;
|
|
559
|
+
const dict = parseDict(data.subarray(base + offset, base + offset + size));
|
|
560
|
+
const subrs = dict[19] ? readIndex(base + offset + dict[19][0]).items : [];
|
|
561
|
+
return { subrs };
|
|
562
|
+
};
|
|
563
|
+
let privateFor;
|
|
564
|
+
if (top[1236]) {
|
|
565
|
+
const fdArray = readIndex(base + top[1236][0]).items.map(dict => readPrivate(parseDict(dict)[18]));
|
|
566
|
+
const fdSelect = base + top[1237][0];
|
|
567
|
+
const format = data[fdSelect];
|
|
568
|
+
privateFor = glyph => {
|
|
569
|
+
if (format === 0)
|
|
570
|
+
return fdArray[data[fdSelect + 1 + glyph]];
|
|
571
|
+
const ranges = font.u16(fdSelect + 1);
|
|
572
|
+
for (let i = 0; i < ranges; i++) {
|
|
573
|
+
const r = fdSelect + 3 + i * 3;
|
|
574
|
+
const next = font.u16(r + 3);
|
|
575
|
+
if (glyph >= font.u16(r) && glyph < next)
|
|
576
|
+
return fdArray[data[r + 2]];
|
|
577
|
+
}
|
|
578
|
+
return fdArray[0];
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
const shared = readPrivate(top[18]);
|
|
583
|
+
privateFor = () => shared;
|
|
584
|
+
}
|
|
585
|
+
return { charStrings, globalSubrs, privateFor };
|
|
586
|
+
}
|
|
587
|
+
function parseDict(bytes) {
|
|
588
|
+
const dict = {};
|
|
589
|
+
const operands = [];
|
|
590
|
+
for (let i = 0; i < bytes.length;) {
|
|
591
|
+
const b0 = bytes[i++];
|
|
592
|
+
if (b0 <= 21) {
|
|
593
|
+
const op = b0 === 12 ? 1200 + bytes[i++] : b0;
|
|
594
|
+
dict[op] = operands.splice(0);
|
|
595
|
+
}
|
|
596
|
+
else if (b0 === 28) {
|
|
597
|
+
operands.push((bytes[i] << 24 | bytes[i + 1] << 16) >> 16);
|
|
598
|
+
i += 2;
|
|
599
|
+
}
|
|
600
|
+
else if (b0 === 29) {
|
|
601
|
+
operands.push(bytes[i] << 24 | bytes[i + 1] << 16 | bytes[i + 2] << 8 | bytes[i + 3]);
|
|
602
|
+
i += 4;
|
|
603
|
+
}
|
|
604
|
+
else if (b0 === 30) {
|
|
605
|
+
let s = '';
|
|
606
|
+
for (let done = false; !done;) {
|
|
607
|
+
const byte = bytes[i++];
|
|
608
|
+
for (const nibble of [byte >> 4, byte & 15]) {
|
|
609
|
+
if (nibble === 15) {
|
|
610
|
+
done = true;
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
s += ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', '', '-'][nibble];
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
operands.push(parseFloat(s));
|
|
617
|
+
}
|
|
618
|
+
else if (b0 >= 32 && b0 <= 246)
|
|
619
|
+
operands.push(b0 - 139);
|
|
620
|
+
else if (b0 >= 247 && b0 <= 250)
|
|
621
|
+
operands.push((b0 - 247) * 256 + bytes[i++] + 108);
|
|
622
|
+
else if (b0 >= 251 && b0 <= 254)
|
|
623
|
+
operands.push(-(b0 - 251) * 256 - bytes[i++] - 108);
|
|
624
|
+
}
|
|
625
|
+
return dict;
|
|
626
|
+
}
|
|
627
|
+
function subrBias(count) {
|
|
628
|
+
return count < 1240 ? 107 : count < 33900 ? 1131 : 32768;
|
|
629
|
+
}
|
|
630
|
+
function cffCharString(cff, glyph) {
|
|
631
|
+
const code = cff.charStrings[glyph];
|
|
632
|
+
if (!code)
|
|
633
|
+
return [];
|
|
634
|
+
const { subrs } = cff.privateFor(glyph);
|
|
635
|
+
const globalBias = subrBias(cff.globalSubrs.length);
|
|
636
|
+
const localBias = subrBias(subrs.length);
|
|
637
|
+
const path = [];
|
|
638
|
+
const stack = [];
|
|
639
|
+
let x = 0, y = 0;
|
|
640
|
+
let stems = 0;
|
|
641
|
+
let open = false;
|
|
642
|
+
let widthParsed = false;
|
|
643
|
+
let depth = 0;
|
|
644
|
+
const moveTo = (dx, dy) => {
|
|
645
|
+
if (open)
|
|
646
|
+
path.push(['Z']);
|
|
647
|
+
x += dx;
|
|
648
|
+
y += dy;
|
|
649
|
+
path.push(['M', x, y]);
|
|
650
|
+
open = true;
|
|
651
|
+
};
|
|
652
|
+
const lineTo = (dx, dy) => {
|
|
653
|
+
x += dx;
|
|
654
|
+
y += dy;
|
|
655
|
+
path.push(['L', x, y]);
|
|
656
|
+
};
|
|
657
|
+
const curveTo = (dx1, dy1, dx2, dy2, dx3, dy3) => {
|
|
658
|
+
const c1x = x + dx1, c1y = y + dy1;
|
|
659
|
+
const c2x = c1x + dx2, c2y = c1y + dy2;
|
|
660
|
+
x = c2x + dx3;
|
|
661
|
+
y = c2y + dy3;
|
|
662
|
+
path.push(['C', c1x, c1y, c2x, c2y, x, y]);
|
|
663
|
+
};
|
|
664
|
+
const stripWidth = (expectedEven) => {
|
|
665
|
+
if (!widthParsed && (stack.length % 2 === 1) === expectedEven)
|
|
666
|
+
stack.shift();
|
|
667
|
+
widthParsed = true;
|
|
668
|
+
};
|
|
669
|
+
const run = (code) => {
|
|
670
|
+
if (++depth > 10)
|
|
671
|
+
return true;
|
|
672
|
+
for (let i = 0; i < code.length;) {
|
|
673
|
+
const b0 = code[i++];
|
|
674
|
+
if (b0 >= 32 || b0 === 28) {
|
|
675
|
+
if (b0 === 28) {
|
|
676
|
+
stack.push((code[i] << 24 | code[i + 1] << 16) >> 16);
|
|
677
|
+
i += 2;
|
|
678
|
+
}
|
|
679
|
+
else if (b0 <= 246)
|
|
680
|
+
stack.push(b0 - 139);
|
|
681
|
+
else if (b0 <= 250)
|
|
682
|
+
stack.push((b0 - 247) * 256 + code[i++] + 108);
|
|
683
|
+
else if (b0 <= 254)
|
|
684
|
+
stack.push(-(b0 - 251) * 256 - code[i++] - 108);
|
|
685
|
+
else {
|
|
686
|
+
stack.push((code[i] << 24 | code[i + 1] << 16 | code[i + 2] << 8 | code[i + 3]) / 65536);
|
|
687
|
+
i += 4;
|
|
688
|
+
}
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
switch (b0) {
|
|
692
|
+
case 1:
|
|
693
|
+
case 3:
|
|
694
|
+
case 18:
|
|
695
|
+
case 23:
|
|
696
|
+
stripWidth(true);
|
|
697
|
+
stems += stack.length >> 1;
|
|
698
|
+
stack.length = 0;
|
|
699
|
+
break;
|
|
700
|
+
case 19:
|
|
701
|
+
case 20:
|
|
702
|
+
stripWidth(true);
|
|
703
|
+
stems += stack.length >> 1;
|
|
704
|
+
stack.length = 0;
|
|
705
|
+
i += stems + 7 >> 3;
|
|
706
|
+
break;
|
|
707
|
+
case 21:
|
|
708
|
+
stripWidth(true);
|
|
709
|
+
moveTo(stack[0], stack[1]);
|
|
710
|
+
stack.length = 0;
|
|
711
|
+
break;
|
|
712
|
+
case 22:
|
|
713
|
+
stripWidth(false);
|
|
714
|
+
moveTo(stack[0], 0);
|
|
715
|
+
stack.length = 0;
|
|
716
|
+
break;
|
|
717
|
+
case 4:
|
|
718
|
+
stripWidth(false);
|
|
719
|
+
moveTo(0, stack[0]);
|
|
720
|
+
stack.length = 0;
|
|
721
|
+
break;
|
|
722
|
+
case 5:
|
|
723
|
+
for (let j = 0; j + 1 < stack.length; j += 2)
|
|
724
|
+
lineTo(stack[j], stack[j + 1]);
|
|
725
|
+
stack.length = 0;
|
|
726
|
+
break;
|
|
727
|
+
case 6:
|
|
728
|
+
case 7: {
|
|
729
|
+
let horizontal = b0 === 6;
|
|
730
|
+
for (const v of stack) {
|
|
731
|
+
if (horizontal)
|
|
732
|
+
lineTo(v, 0);
|
|
733
|
+
else
|
|
734
|
+
lineTo(0, v);
|
|
735
|
+
horizontal = !horizontal;
|
|
736
|
+
}
|
|
737
|
+
stack.length = 0;
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
case 8:
|
|
741
|
+
for (let j = 0; j + 5 < stack.length; j += 6)
|
|
742
|
+
curveTo(stack[j], stack[j + 1], stack[j + 2], stack[j + 3], stack[j + 4], stack[j + 5]);
|
|
743
|
+
stack.length = 0;
|
|
744
|
+
break;
|
|
745
|
+
case 24: {
|
|
746
|
+
let j = 0;
|
|
747
|
+
for (; j + 5 < stack.length - 2; j += 6)
|
|
748
|
+
curveTo(stack[j], stack[j + 1], stack[j + 2], stack[j + 3], stack[j + 4], stack[j + 5]);
|
|
749
|
+
lineTo(stack[j], stack[j + 1]);
|
|
750
|
+
stack.length = 0;
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
case 25: {
|
|
754
|
+
let j = 0;
|
|
755
|
+
for (; j + 1 < stack.length - 6; j += 2)
|
|
756
|
+
lineTo(stack[j], stack[j + 1]);
|
|
757
|
+
curveTo(stack[j], stack[j + 1], stack[j + 2], stack[j + 3], stack[j + 4], stack[j + 5]);
|
|
758
|
+
stack.length = 0;
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
case 26: {
|
|
762
|
+
let j = 0, dx1 = 0;
|
|
763
|
+
if (stack.length % 4)
|
|
764
|
+
dx1 = stack[j++];
|
|
765
|
+
for (; j + 3 < stack.length; j += 4) {
|
|
766
|
+
curveTo(dx1, stack[j], stack[j + 1], stack[j + 2], 0, stack[j + 3]);
|
|
767
|
+
dx1 = 0;
|
|
768
|
+
}
|
|
769
|
+
stack.length = 0;
|
|
770
|
+
break;
|
|
771
|
+
}
|
|
772
|
+
case 27: {
|
|
773
|
+
let j = 0, dy1 = 0;
|
|
774
|
+
if (stack.length % 4)
|
|
775
|
+
dy1 = stack[j++];
|
|
776
|
+
for (; j + 3 < stack.length; j += 4) {
|
|
777
|
+
curveTo(stack[j], dy1, stack[j + 1], stack[j + 2], stack[j + 3], 0);
|
|
778
|
+
dy1 = 0;
|
|
779
|
+
}
|
|
780
|
+
stack.length = 0;
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
case 30:
|
|
784
|
+
case 31: {
|
|
785
|
+
let horizontal = b0 === 31;
|
|
786
|
+
for (let j = 0; j + 3 < stack.length; j += 4) {
|
|
787
|
+
const last = j + 5 === stack.length ? stack[j + 4] : 0;
|
|
788
|
+
if (horizontal)
|
|
789
|
+
curveTo(stack[j], 0, stack[j + 1], stack[j + 2], last, stack[j + 3]);
|
|
790
|
+
else
|
|
791
|
+
curveTo(0, stack[j], stack[j + 1], stack[j + 2], stack[j + 3], last);
|
|
792
|
+
horizontal = !horizontal;
|
|
793
|
+
}
|
|
794
|
+
stack.length = 0;
|
|
795
|
+
break;
|
|
796
|
+
}
|
|
797
|
+
case 10:
|
|
798
|
+
case 29: {
|
|
799
|
+
const local = b0 === 10;
|
|
800
|
+
const index = stack.pop() + (local ? localBias : globalBias);
|
|
801
|
+
const subr = (local ? subrs : cff.globalSubrs)[index];
|
|
802
|
+
if (subr && run(subr))
|
|
803
|
+
return true;
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
case 11:
|
|
807
|
+
depth--;
|
|
808
|
+
return false;
|
|
809
|
+
case 14:
|
|
810
|
+
stripWidth(true);
|
|
811
|
+
if (open)
|
|
812
|
+
path.push(['Z']);
|
|
813
|
+
open = false;
|
|
814
|
+
return true;
|
|
815
|
+
case 12: {
|
|
816
|
+
const op = code[i++];
|
|
817
|
+
const s = stack;
|
|
818
|
+
if (op === 35) {
|
|
819
|
+
curveTo(s[0], s[1], s[2], s[3], s[4], s[5]);
|
|
820
|
+
curveTo(s[6], s[7], s[8], s[9], s[10], s[11]);
|
|
821
|
+
}
|
|
822
|
+
else if (op === 34) {
|
|
823
|
+
const startY = y;
|
|
824
|
+
curveTo(s[0], 0, s[1], s[2], s[3], 0);
|
|
825
|
+
curveTo(s[4], 0, s[5], startY - y, s[6], 0);
|
|
826
|
+
}
|
|
827
|
+
else if (op === 36) {
|
|
828
|
+
const startY = y;
|
|
829
|
+
curveTo(s[0], s[1], s[2], s[3], s[4], 0);
|
|
830
|
+
curveTo(s[5], 0, s[6], s[7], s[8], startY - (y + s[7]));
|
|
831
|
+
}
|
|
832
|
+
else if (op === 37) {
|
|
833
|
+
const startX = x, startY = y;
|
|
834
|
+
let dx = 0, dy = 0;
|
|
835
|
+
for (let j = 0; j < 10; j += 2) {
|
|
836
|
+
dx += s[j];
|
|
837
|
+
dy += s[j + 1];
|
|
838
|
+
}
|
|
839
|
+
curveTo(s[0], s[1], s[2], s[3], s[4], s[5]);
|
|
840
|
+
const lastX = Math.abs(dx) > Math.abs(dy);
|
|
841
|
+
curveTo(s[6], s[7], s[8], s[9], lastX ? s[10] : startX - (x + s[6] + s[8]), lastX ? startY - (y + s[7] + s[9]) : s[10]);
|
|
842
|
+
}
|
|
843
|
+
stack.length = 0;
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
default:
|
|
847
|
+
stack.length = 0;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
depth--;
|
|
851
|
+
return false;
|
|
852
|
+
};
|
|
853
|
+
run(code);
|
|
854
|
+
if (open)
|
|
855
|
+
path.push(['Z']);
|
|
856
|
+
return path;
|
|
857
|
+
}
|