imagescript 3.2.1 → 3.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/README.md +8 -3
- package/THIRD_PARTY_NOTICES.md +131 -0
- package/dist/animation.d.ts +7 -0
- package/dist/animation.js +21 -11
- package/dist/codecs/crc.js +5 -0
- package/dist/codecs/gif.js +10 -35
- package/dist/codecs/jpeg.js +9 -27
- package/dist/codecs/png.js +22 -10
- package/dist/codecs/tga.js +3 -1
- package/dist/codecs/tiff.js +7 -2
- package/dist/codecs/vp8.js +3 -1
- package/dist/codecs/vp8l.js +126 -58
- package/dist/codecs/webp.js +11 -9
- package/dist/codecs/zlib.js +81 -69
- package/dist/frame.d.ts +1 -0
- package/dist/frame.js +4 -2
- package/dist/gif.d.ts +1 -0
- package/dist/gif.js +2 -2
- package/dist/image.d.ts +42 -7
- package/dist/image.js +217 -110
- package/dist/index.js +4 -4
- package/dist/render/bidi.js +350 -0
- package/dist/render/buffer.js +362 -0
- package/dist/render/color.js +164 -0
- package/dist/render/colr.js +523 -0
- package/dist/render/font.js +173 -241
- package/dist/render/hinting.js +12 -4
- package/dist/render/layout.js +1553 -0
- package/dist/render/linebreak.js +210 -0
- package/dist/render/segment.js +106 -0
- package/dist/render/shape.js +984 -0
- package/dist/render/shapers/arabic.js +284 -0
- package/dist/render/shapers/hangul.js +142 -0
- package/dist/render/shapers/hebrew.js +49 -0
- package/dist/render/shapers/index.js +52 -0
- package/dist/render/shapers/indic.js +579 -0
- package/dist/render/shapers/khmer.js +104 -0
- package/dist/render/shapers/machine.js +129 -0
- package/dist/render/shapers/myanmar.js +121 -0
- package/dist/render/shapers/syllabic.js +100 -0
- package/dist/render/shapers/thai.js +125 -0
- package/dist/render/shapers/use.js +235 -0
- package/dist/render/svg.js +101 -22
- package/dist/render/text.js +373 -103
- package/dist/render/unicode-data.js +63 -0
- package/dist/render/unicode.js +58 -0
- package/dist/text-layout.d.ts +4 -1
- package/dist/text-layout.js +2 -2
- package/dist/util/alpha.js +43 -0
- package/dist/util/blur.js +20 -33
- package/dist/util/bytes.js +31 -0
- package/dist/util/limits.js +34 -0
- package/dist/util/options.js +7 -0
- package/dist/util/resample.js +122 -5
- package/dist/util/rotate.js +43 -0
- package/dist/version.js +1 -1
- package/licenses/HarfBuzz.txt +42 -0
- package/licenses/Unicode-3.0.txt +39 -0
- package/package.json +1 -1
package/dist/render/font.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const MAX_COMMANDS = 100000;
|
|
1
2
|
export class Font {
|
|
2
3
|
data;
|
|
3
4
|
view;
|
|
@@ -13,7 +14,8 @@ export class Font {
|
|
|
13
14
|
cff = null;
|
|
14
15
|
cmap;
|
|
15
16
|
outlineCache;
|
|
16
|
-
|
|
17
|
+
kernTable;
|
|
18
|
+
variation;
|
|
17
19
|
constructor(data) {
|
|
18
20
|
this.data = data;
|
|
19
21
|
this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
@@ -53,11 +55,14 @@ export class Font {
|
|
|
53
55
|
}
|
|
54
56
|
else if (this.tables.CFF2)
|
|
55
57
|
throw new Error('Font: CFF2 outlines are not supported');
|
|
58
|
+
else if (this.tables.CBDT && this.tables.CBLC || this.tables.sbix)
|
|
59
|
+
this.outlines = 'none';
|
|
56
60
|
else
|
|
57
61
|
throw new Error('Font: no supported glyph outlines');
|
|
58
62
|
this.cmap = parseCmap(this, this.tables.cmap.offset);
|
|
59
63
|
this.outlineCache = new Map();
|
|
60
|
-
this.
|
|
64
|
+
this.kernTable = this.tables.kern && parseKern(this, this.tables.kern.offset) || null;
|
|
65
|
+
this.variation = parseVariations(this, this.tables.cmap.offset);
|
|
61
66
|
}
|
|
62
67
|
u8(o) { return this.data[o]; }
|
|
63
68
|
i8(o) { return this.view.getInt8(o); }
|
|
@@ -73,6 +78,28 @@ export class Font {
|
|
|
73
78
|
const index = Math.min(glyph, this.numberOfHMetrics - 1);
|
|
74
79
|
return this.u16(hmtx + index * 4);
|
|
75
80
|
}
|
|
81
|
+
variationGlyph(codepoint, selector) {
|
|
82
|
+
const glyph = this.variation ? this.variation(codepoint, selector) : 0;
|
|
83
|
+
return glyph < 0 ? this.glyphIndex(codepoint) : glyph;
|
|
84
|
+
}
|
|
85
|
+
extents(glyph) {
|
|
86
|
+
if (this.outlines === 'glyf' && glyph < this.numGlyphs) {
|
|
87
|
+
const o = this.glyfOffset(glyph);
|
|
88
|
+
if (o < 0)
|
|
89
|
+
return { xMin: 0, yMin: 0, xMax: 0, yMax: 0 };
|
|
90
|
+
return { xMin: this.i16(o + 2), yMin: this.i16(o + 4), xMax: this.i16(o + 6), yMax: this.i16(o + 8) };
|
|
91
|
+
}
|
|
92
|
+
let xMin = Infinity, yMin = Infinity, xMax = -Infinity, yMax = -Infinity;
|
|
93
|
+
for (const command of this.outline(glyph))
|
|
94
|
+
for (let i = 1; i < command.length; i += 2) {
|
|
95
|
+
const x = command[i], y = command[i + 1];
|
|
96
|
+
xMin = Math.min(xMin, x);
|
|
97
|
+
xMax = Math.max(xMax, x);
|
|
98
|
+
yMin = Math.min(yMin, y);
|
|
99
|
+
yMax = Math.max(yMax, y);
|
|
100
|
+
}
|
|
101
|
+
return xMax >= xMin ? { xMin, yMin, xMax, yMax } : { xMin: 0, yMin: 0, xMax: 0, yMax: 0 };
|
|
102
|
+
}
|
|
76
103
|
leftSideBearing(glyph) {
|
|
77
104
|
const hmtx = this.tables.hmtx.offset;
|
|
78
105
|
if (glyph < this.numberOfHMetrics)
|
|
@@ -82,63 +109,42 @@ export class Font {
|
|
|
82
109
|
glyfPoints(glyph) {
|
|
83
110
|
if (this.outlines !== 'glyf' || glyph >= this.numGlyphs)
|
|
84
111
|
return null;
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
const end = this.indexToLocFormat ? this.u32(loca + glyph * 4 + 4) : this.u16(loca + glyph * 2 + 2) * 2;
|
|
88
|
-
if (end <= start)
|
|
112
|
+
const offset = this.glyfOffset(glyph);
|
|
113
|
+
if (offset < 0)
|
|
89
114
|
return null;
|
|
90
|
-
const offset = this.tables.glyf.offset + start;
|
|
91
115
|
const contourCount = this.i16(offset);
|
|
92
116
|
const xMin = this.i16(offset + 2);
|
|
93
117
|
if (contourCount < 0) {
|
|
94
|
-
const components =
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
arg2 = flags & 2 ? this.i16(p + 2) : this.u16(p + 2);
|
|
104
|
-
p += 4;
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
arg1 = flags & 2 ? this.i8(p) : this.u8(p);
|
|
108
|
-
arg2 = flags & 2 ? this.i8(p + 1) : this.u8(p + 1);
|
|
109
|
-
p += 2;
|
|
110
|
-
}
|
|
111
|
-
let matrix = [1, 0, 0, 1];
|
|
112
|
-
const f2dot14 = (o) => this.i16(o) / 16384;
|
|
113
|
-
if (flags & 8) {
|
|
114
|
-
const s = f2dot14(p);
|
|
115
|
-
matrix = [s, 0, 0, s];
|
|
116
|
-
p += 2;
|
|
117
|
-
}
|
|
118
|
-
else if (flags & 0x40) {
|
|
119
|
-
matrix = [f2dot14(p), 0, 0, f2dot14(p + 2)];
|
|
120
|
-
p += 4;
|
|
121
|
-
}
|
|
122
|
-
else if (flags & 0x80) {
|
|
123
|
-
matrix = [f2dot14(p), f2dot14(p + 2), f2dot14(p + 4), f2dot14(p + 6)];
|
|
124
|
-
p += 8;
|
|
125
|
-
}
|
|
126
|
-
components.push({
|
|
127
|
-
glyph: component,
|
|
128
|
-
dx: flags & 2 ? arg1 : 0,
|
|
129
|
-
dy: flags & 2 ? arg2 : 0,
|
|
130
|
-
matrix,
|
|
131
|
-
round: (flags & 4) !== 0,
|
|
132
|
-
match: flags & 2 ? null : [arg1, arg2],
|
|
133
|
-
useMetrics: (flags & 0x200) !== 0
|
|
134
|
-
});
|
|
135
|
-
} while (flags & 0x20);
|
|
136
|
-
const instructions = flags & 0x100 ? this.data.subarray(p + 2, p + 2 + this.u16(p)) : new Uint8Array(0);
|
|
137
|
-
return { xs: [], ys: [], on: [], ends: [], instructions, xMin, components };
|
|
118
|
+
const { components, end } = this.components(offset);
|
|
119
|
+
const instructions = components[components.length - 1].flags & 0x100 ? this.data.subarray(end + 2, end + 2 + this.u16(end)) : new Uint8Array(0);
|
|
120
|
+
return {
|
|
121
|
+
xs: [], ys: [], on: [], ends: [], instructions, xMin,
|
|
122
|
+
components: components.map(({ glyph, flags, arg1, arg2, matrix }) => ({
|
|
123
|
+
glyph, dx: flags & 2 ? arg1 : 0, dy: flags & 2 ? arg2 : 0, matrix,
|
|
124
|
+
round: (flags & 4) !== 0, match: flags & 2 ? null : [arg1, arg2], useMetrics: (flags & 0x200) !== 0,
|
|
125
|
+
})),
|
|
126
|
+
};
|
|
138
127
|
}
|
|
128
|
+
const points = this.simplePoints(offset, contourCount);
|
|
129
|
+
if (!points)
|
|
130
|
+
return null;
|
|
131
|
+
const { xs, ys, flags, ends, instructions } = points;
|
|
132
|
+
return { xs: Array.from(xs), ys: Array.from(ys), on: Array.from(flags, f => (f & 1) === 1), ends, instructions, xMin, components: null };
|
|
133
|
+
}
|
|
134
|
+
glyfOffset(glyph) {
|
|
135
|
+
const loca = this.tables.loca.offset;
|
|
136
|
+
const start = this.indexToLocFormat ? this.u32(loca + glyph * 4) : this.u16(loca + glyph * 2) * 2;
|
|
137
|
+
const end = this.indexToLocFormat ? this.u32(loca + glyph * 4 + 4) : this.u16(loca + glyph * 2 + 2) * 2;
|
|
138
|
+
return end > start ? this.tables.glyf.offset + start : -1;
|
|
139
|
+
}
|
|
140
|
+
simplePoints(offset, contourCount) {
|
|
139
141
|
const ends = [];
|
|
140
|
-
for (let i = 0; i < contourCount; i++)
|
|
141
|
-
|
|
142
|
+
for (let i = 0; i < contourCount; i++) {
|
|
143
|
+
const end = this.u16(offset + 10 + i * 2);
|
|
144
|
+
if (i && end <= ends[i - 1])
|
|
145
|
+
return null;
|
|
146
|
+
ends.push(end);
|
|
147
|
+
}
|
|
142
148
|
const pointCount = contourCount ? ends[contourCount - 1] + 1 : 0;
|
|
143
149
|
let p = offset + 10 + contourCount * 2;
|
|
144
150
|
const instructionLength = this.u16(p);
|
|
@@ -152,7 +158,7 @@ export class Font {
|
|
|
152
158
|
for (let repeat = this.u8(p++); repeat > 0 && i < pointCount; repeat--)
|
|
153
159
|
flags[i++] = flag;
|
|
154
160
|
}
|
|
155
|
-
const xs =
|
|
161
|
+
const xs = new Int32Array(pointCount), ys = new Int32Array(pointCount);
|
|
156
162
|
for (const [coords, short, same] of [[xs, 2, 16], [ys, 4, 32]]) {
|
|
157
163
|
let value = 0;
|
|
158
164
|
for (let i = 0; i < pointCount; i++) {
|
|
@@ -165,113 +171,92 @@ export class Font {
|
|
|
165
171
|
value += this.i16(p);
|
|
166
172
|
p += 2;
|
|
167
173
|
}
|
|
168
|
-
coords
|
|
174
|
+
coords[i] = value;
|
|
169
175
|
}
|
|
170
176
|
}
|
|
171
|
-
return { xs, ys,
|
|
177
|
+
return { xs, ys, flags, ends, instructions };
|
|
178
|
+
}
|
|
179
|
+
components(offset) {
|
|
180
|
+
const components = [];
|
|
181
|
+
const f2dot14 = (o) => this.i16(o) / 16384;
|
|
182
|
+
let p = offset + 10, flags = 0;
|
|
183
|
+
do {
|
|
184
|
+
flags = this.u16(p);
|
|
185
|
+
const glyph = this.u16(p + 2);
|
|
186
|
+
p += 4;
|
|
187
|
+
let arg1, arg2;
|
|
188
|
+
if (flags & 1) {
|
|
189
|
+
arg1 = flags & 2 ? this.i16(p) : this.u16(p);
|
|
190
|
+
arg2 = flags & 2 ? this.i16(p + 2) : this.u16(p + 2);
|
|
191
|
+
p += 4;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
arg1 = flags & 2 ? this.i8(p) : this.u8(p);
|
|
195
|
+
arg2 = flags & 2 ? this.i8(p + 1) : this.u8(p + 1);
|
|
196
|
+
p += 2;
|
|
197
|
+
}
|
|
198
|
+
let matrix = [1, 0, 0, 1];
|
|
199
|
+
if (flags & 8) {
|
|
200
|
+
const scale = f2dot14(p);
|
|
201
|
+
matrix = [scale, 0, 0, scale];
|
|
202
|
+
p += 2;
|
|
203
|
+
}
|
|
204
|
+
else if (flags & 0x40) {
|
|
205
|
+
matrix = [f2dot14(p), 0, 0, f2dot14(p + 2)];
|
|
206
|
+
p += 4;
|
|
207
|
+
}
|
|
208
|
+
else if (flags & 0x80) {
|
|
209
|
+
matrix = [f2dot14(p), f2dot14(p + 2), f2dot14(p + 4), f2dot14(p + 6)];
|
|
210
|
+
p += 8;
|
|
211
|
+
}
|
|
212
|
+
components.push({ glyph, flags, arg1, arg2, matrix });
|
|
213
|
+
} while (flags & 0x20);
|
|
214
|
+
return { components, end: p };
|
|
172
215
|
}
|
|
173
216
|
outline(glyph) {
|
|
174
217
|
if (glyph >= this.numGlyphs)
|
|
175
218
|
glyph = 0;
|
|
176
219
|
let path = this.outlineCache.get(glyph);
|
|
177
220
|
if (!path) {
|
|
178
|
-
path = this.outlines === 'glyf' ? this.glyfOutline(glyph,
|
|
221
|
+
path = this.outlines === 'glyf' ? this.glyfOutline(glyph, { expanding: new Set(), budget: MAX_COMMANDS }) : this.outlines === 'cff' ? this.cffOutline(glyph) : [];
|
|
179
222
|
this.outlineCache.set(glyph, path);
|
|
180
223
|
}
|
|
181
224
|
return path;
|
|
182
225
|
}
|
|
183
|
-
glyfOutline(glyph,
|
|
184
|
-
if (
|
|
226
|
+
glyfOutline(glyph, expanding) {
|
|
227
|
+
if (expanding.expanding.has(glyph) || expanding.expanding.size > 8 || expanding.budget <= 0)
|
|
185
228
|
return [];
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
const end = this.indexToLocFormat ? this.u32(loca + glyph * 4 + 4) : this.u16(loca + glyph * 2 + 2) * 2;
|
|
189
|
-
if (end <= start)
|
|
229
|
+
const offset = this.glyfOffset(glyph);
|
|
230
|
+
if (offset < 0)
|
|
190
231
|
return [];
|
|
191
|
-
const offset = this.tables.glyf.offset + start;
|
|
192
232
|
const contours = this.i16(offset);
|
|
193
|
-
|
|
233
|
+
if (contours >= 0) {
|
|
234
|
+
const path = this.simpleGlyph(offset, contours);
|
|
235
|
+
expanding.budget -= path.length;
|
|
236
|
+
return expanding.budget >= 0 ? path : [];
|
|
237
|
+
}
|
|
238
|
+
expanding.expanding.add(glyph);
|
|
239
|
+
const path = this.compositeGlyph(offset, expanding);
|
|
240
|
+
expanding.expanding.delete(glyph);
|
|
241
|
+
return path;
|
|
194
242
|
}
|
|
195
243
|
simpleGlyph(offset, contourCount) {
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const pointCount = contourCount ? endPoints[contourCount - 1] + 1 : 0;
|
|
200
|
-
let p = offset + 10 + contourCount * 2;
|
|
201
|
-
p += 2 + this.u16(p);
|
|
202
|
-
const flags = new Uint8Array(pointCount);
|
|
203
|
-
for (let i = 0; i < pointCount;) {
|
|
204
|
-
const flag = this.u8(p++);
|
|
205
|
-
flags[i++] = flag;
|
|
206
|
-
if (flag & 8) {
|
|
207
|
-
for (let repeat = this.u8(p++); repeat > 0 && i < pointCount; repeat--)
|
|
208
|
-
flags[i++] = flag;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
const xs = new Int32Array(pointCount);
|
|
212
|
-
const ys = new Int32Array(pointCount);
|
|
213
|
-
for (const [coords, short, same] of [[xs, 2, 16], [ys, 4, 32]]) {
|
|
214
|
-
let value = 0;
|
|
215
|
-
for (let i = 0; i < pointCount; i++) {
|
|
216
|
-
const flag = flags[i];
|
|
217
|
-
if (flag & short) {
|
|
218
|
-
const delta = this.u8(p++);
|
|
219
|
-
value += flag & same ? delta : -delta;
|
|
220
|
-
}
|
|
221
|
-
else if (!(flag & same)) {
|
|
222
|
-
value += this.i16(p);
|
|
223
|
-
p += 2;
|
|
224
|
-
}
|
|
225
|
-
coords[i] = value;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
244
|
+
const points = this.simplePoints(offset, contourCount);
|
|
245
|
+
if (!points)
|
|
246
|
+
return [];
|
|
228
247
|
const path = [];
|
|
229
248
|
let first = 0;
|
|
230
|
-
for (const last of
|
|
231
|
-
contourToPath(path, xs, ys, flags, first, last);
|
|
249
|
+
for (const last of points.ends) {
|
|
250
|
+
contourToPath(path, points.xs, points.ys, points.flags, first, last);
|
|
232
251
|
first = last + 1;
|
|
233
252
|
}
|
|
234
253
|
return path;
|
|
235
254
|
}
|
|
236
|
-
compositeGlyph(offset,
|
|
255
|
+
compositeGlyph(offset, expanding) {
|
|
237
256
|
const path = [];
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
const glyph = this.u16(p + 2);
|
|
242
|
-
p += 4;
|
|
243
|
-
let dx, dy;
|
|
244
|
-
if (flags & 1) {
|
|
245
|
-
dx = flags & 2 ? this.i16(p) : this.u16(p);
|
|
246
|
-
dy = flags & 2 ? this.i16(p + 2) : this.u16(p + 2);
|
|
247
|
-
p += 4;
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
dx = flags & 2 ? this.i8(p) : this.u8(p);
|
|
251
|
-
dy = flags & 2 ? this.i8(p + 1) : this.u8(p + 1);
|
|
252
|
-
p += 2;
|
|
253
|
-
}
|
|
254
|
-
if (!(flags & 2))
|
|
255
|
-
dx = dy = 0;
|
|
256
|
-
let a = 1, b = 0, c = 0, d = 1;
|
|
257
|
-
const f2dot14 = (o) => this.i16(o) / 16384;
|
|
258
|
-
if (flags & 8) {
|
|
259
|
-
a = d = f2dot14(p);
|
|
260
|
-
p += 2;
|
|
261
|
-
}
|
|
262
|
-
else if (flags & 0x40) {
|
|
263
|
-
a = f2dot14(p);
|
|
264
|
-
d = f2dot14(p + 2);
|
|
265
|
-
p += 4;
|
|
266
|
-
}
|
|
267
|
-
else if (flags & 0x80) {
|
|
268
|
-
a = f2dot14(p);
|
|
269
|
-
b = f2dot14(p + 2);
|
|
270
|
-
c = f2dot14(p + 4);
|
|
271
|
-
d = f2dot14(p + 6);
|
|
272
|
-
p += 8;
|
|
273
|
-
}
|
|
274
|
-
for (const command of this.glyfOutline(glyph, depth + 1)) {
|
|
257
|
+
for (const { glyph, flags, arg1, arg2, matrix: [a, b, c, d] } of this.components(offset).components) {
|
|
258
|
+
const dx = flags & 2 ? arg1 : 0, dy = flags & 2 ? arg2 : 0;
|
|
259
|
+
for (const command of this.glyfOutline(glyph, expanding)) {
|
|
275
260
|
const transformed = [command[0]];
|
|
276
261
|
for (let i = 1; i < command.length; i += 2) {
|
|
277
262
|
const x = command[i], y = command[i + 1];
|
|
@@ -279,7 +264,6 @@ export class Font {
|
|
|
279
264
|
}
|
|
280
265
|
path.push(transformed);
|
|
281
266
|
}
|
|
282
|
-
more = !!(flags & 0x20);
|
|
283
267
|
}
|
|
284
268
|
return path;
|
|
285
269
|
}
|
|
@@ -424,115 +408,63 @@ function parseCmap(font, offset) {
|
|
|
424
408
|
}
|
|
425
409
|
return symbol ? (c) => lookup(c) || (c < 256 ? lookup(0xf000 + c) : 0) : lookup;
|
|
426
410
|
}
|
|
427
|
-
function
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const feature = featureList + font.u16(record + 4);
|
|
436
|
-
for (let j = 0, n = font.u16(feature + 2); j < n; j++)
|
|
437
|
-
lookups.add(font.u16(feature + 4 + j * 2));
|
|
438
|
-
}
|
|
439
|
-
const pairLookups = [];
|
|
440
|
-
for (const index of [...lookups].sort((a, b) => a - b)) {
|
|
441
|
-
if (index >= font.u16(lookupList))
|
|
442
|
-
continue;
|
|
443
|
-
const lookup = lookupList + font.u16(lookupList + 2 + index * 2);
|
|
444
|
-
const type = font.u16(lookup);
|
|
445
|
-
const subtables = [];
|
|
446
|
-
for (let i = 0, count = font.u16(lookup + 4); i < count; i++) {
|
|
447
|
-
let subtable = lookup + font.u16(lookup + 6 + i * 2);
|
|
448
|
-
if (type === 9) {
|
|
449
|
-
if (font.u16(subtable + 2) !== 2)
|
|
450
|
-
continue;
|
|
451
|
-
subtable += font.u32(subtable + 4);
|
|
452
|
-
}
|
|
453
|
-
else if (type !== 2)
|
|
454
|
-
continue;
|
|
455
|
-
const pair = parsePairPos(font, subtable);
|
|
456
|
-
if (pair)
|
|
457
|
-
subtables.push(pair);
|
|
411
|
+
function parseVariations(font, offset) {
|
|
412
|
+
let table = -1;
|
|
413
|
+
for (let i = 0, count = font.u16(offset + 2); i < count; i++) {
|
|
414
|
+
const record = offset + 4 + i * 8;
|
|
415
|
+
if (font.u16(record) === 0 && font.u16(record + 2) === 5) {
|
|
416
|
+
const t = offset + font.u32(record + 4);
|
|
417
|
+
if (font.u16(t) === 14)
|
|
418
|
+
table = t;
|
|
458
419
|
}
|
|
459
|
-
if (subtables.length)
|
|
460
|
-
pairLookups.push(subtables);
|
|
461
420
|
}
|
|
462
|
-
if (
|
|
421
|
+
if (table < 0)
|
|
463
422
|
return null;
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
423
|
+
const u24 = (o) => font.u8(o) << 16 | font.u16(o + 1);
|
|
424
|
+
const records = font.u32(table + 6);
|
|
425
|
+
return (codepoint, selector) => {
|
|
426
|
+
let lo = 0, hi = records - 1;
|
|
427
|
+
while (lo <= hi) {
|
|
428
|
+
const mid = lo + hi >> 1, record = table + 10 + mid * 11, vs = u24(record);
|
|
429
|
+
if (vs < selector)
|
|
430
|
+
lo = mid + 1;
|
|
431
|
+
else if (vs > selector)
|
|
432
|
+
hi = mid - 1;
|
|
433
|
+
else {
|
|
434
|
+
const defaults = font.u32(record + 3), others = font.u32(record + 7);
|
|
435
|
+
if (others) {
|
|
436
|
+
const o = table + others;
|
|
437
|
+
let l = 0, h = font.u32(o) - 1;
|
|
438
|
+
while (l <= h) {
|
|
439
|
+
const m = l + h >> 1, u = u24(o + 4 + m * 5);
|
|
440
|
+
if (u < codepoint)
|
|
441
|
+
l = m + 1;
|
|
442
|
+
else if (u > codepoint)
|
|
443
|
+
h = m - 1;
|
|
444
|
+
else
|
|
445
|
+
return font.u16(o + 4 + m * 5 + 3);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (defaults) {
|
|
449
|
+
const o = table + defaults;
|
|
450
|
+
let l = 0, h = font.u32(o) - 1;
|
|
451
|
+
while (l <= h) {
|
|
452
|
+
const m = l + h >> 1, start = u24(o + 4 + m * 4);
|
|
453
|
+
if (codepoint < start)
|
|
454
|
+
h = m - 1;
|
|
455
|
+
else if (codepoint > start + font.u8(o + 4 + m * 4 + 3))
|
|
456
|
+
l = m + 1;
|
|
457
|
+
else
|
|
458
|
+
return -1;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return 0;
|
|
473
462
|
}
|
|
474
463
|
}
|
|
475
|
-
return
|
|
464
|
+
return 0;
|
|
476
465
|
};
|
|
477
466
|
}
|
|
478
|
-
function
|
|
479
|
-
let size = 0, xAdvance = -1;
|
|
480
|
-
for (let bit = 0; bit < 8; bit++) {
|
|
481
|
-
if (!(format & 1 << bit))
|
|
482
|
-
continue;
|
|
483
|
-
if (bit === 2)
|
|
484
|
-
xAdvance = size;
|
|
485
|
-
size += 2;
|
|
486
|
-
}
|
|
487
|
-
return { size, xAdvance };
|
|
488
|
-
}
|
|
489
|
-
function parsePairPos(font, table) {
|
|
490
|
-
const format = font.u16(table);
|
|
491
|
-
const coverage = parseCoverage(font, table + font.u16(table + 2));
|
|
492
|
-
const first = valueRecord(font.u16(table + 4));
|
|
493
|
-
const second = valueRecord(font.u16(table + 6));
|
|
494
|
-
if (first.xAdvance === -1)
|
|
495
|
-
return null;
|
|
496
|
-
if (format === 1) {
|
|
497
|
-
const sets = font.u16(table + 8);
|
|
498
|
-
const recordSize = 2 + first.size + second.size;
|
|
499
|
-
return (left, right) => {
|
|
500
|
-
const index = coverage(left);
|
|
501
|
-
if (index === -1 || index >= sets)
|
|
502
|
-
return undefined;
|
|
503
|
-
const set = table + font.u16(table + 10 + index * 2);
|
|
504
|
-
let lo = 0, hi = font.u16(set) - 1;
|
|
505
|
-
while (lo <= hi) {
|
|
506
|
-
const mid = lo + hi >> 1;
|
|
507
|
-
const record = set + 2 + mid * recordSize;
|
|
508
|
-
const glyph = font.u16(record);
|
|
509
|
-
if (glyph < right)
|
|
510
|
-
lo = mid + 1;
|
|
511
|
-
else if (glyph > right)
|
|
512
|
-
hi = mid - 1;
|
|
513
|
-
else
|
|
514
|
-
return font.i16(record + 2 + first.xAdvance);
|
|
515
|
-
}
|
|
516
|
-
return undefined;
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
if (format === 2) {
|
|
520
|
-
const classes1 = parseClassDef(font, table + font.u16(table + 8));
|
|
521
|
-
const classes2 = parseClassDef(font, table + font.u16(table + 10));
|
|
522
|
-
const class1Count = font.u16(table + 12), class2Count = font.u16(table + 14);
|
|
523
|
-
const recordSize = first.size + second.size;
|
|
524
|
-
return (left, right) => {
|
|
525
|
-
if (coverage(left) === -1)
|
|
526
|
-
return undefined;
|
|
527
|
-
const c1 = classes1(left), c2 = classes2(right);
|
|
528
|
-
if (c1 >= class1Count || c2 >= class2Count)
|
|
529
|
-
return undefined;
|
|
530
|
-
return font.i16(table + 16 + (c1 * class2Count + c2) * recordSize + first.xAdvance);
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
return null;
|
|
534
|
-
}
|
|
535
|
-
function parseCoverage(font, table) {
|
|
467
|
+
export function parseCoverage(font, table) {
|
|
536
468
|
const format = font.u16(table), count = font.u16(table + 2);
|
|
537
469
|
return glyph => {
|
|
538
470
|
let lo = 0, hi = count - 1;
|
|
@@ -560,7 +492,7 @@ function parseCoverage(font, table) {
|
|
|
560
492
|
return -1;
|
|
561
493
|
};
|
|
562
494
|
}
|
|
563
|
-
function parseClassDef(font, table) {
|
|
495
|
+
export function parseClassDef(font, table) {
|
|
564
496
|
const format = font.u16(table);
|
|
565
497
|
if (format === 1) {
|
|
566
498
|
const start = font.u16(table + 2), count = font.u16(table + 4);
|
package/dist/render/hinting.js
CHANGED
|
@@ -28,6 +28,7 @@ function makeZone(count) {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
const MAX_INSTRUCTIONS = 1_000_000;
|
|
31
|
+
const MAX_POINTS = 65536;
|
|
31
32
|
export class Hinter {
|
|
32
33
|
font;
|
|
33
34
|
fpgm;
|
|
@@ -38,6 +39,8 @@ export class Hinter {
|
|
|
38
39
|
maxTwilight;
|
|
39
40
|
sizes = new Map();
|
|
40
41
|
scaleFixed = 0;
|
|
42
|
+
loading = new Set();
|
|
43
|
+
points = 0;
|
|
41
44
|
constructor(font) {
|
|
42
45
|
this.font = font;
|
|
43
46
|
const table = (tag) => {
|
|
@@ -98,6 +101,8 @@ export class Hinter {
|
|
|
98
101
|
if (state.broken)
|
|
99
102
|
return null;
|
|
100
103
|
try {
|
|
104
|
+
this.loading.clear();
|
|
105
|
+
this.points = 0;
|
|
101
106
|
const outline = this.load(glyph, state, 0);
|
|
102
107
|
if (!outline)
|
|
103
108
|
return { path: [], advance: Math.round(this.font.advance(glyph) * state.scale / 64) };
|
|
@@ -125,19 +130,23 @@ export class Hinter {
|
|
|
125
130
|
load(glyph, size, depth) {
|
|
126
131
|
if (depth > 8)
|
|
127
132
|
throw new HintError('composite glyphs nested too deep');
|
|
133
|
+
if (this.loading.has(glyph))
|
|
134
|
+
throw new HintError('composite glyph containing itself');
|
|
128
135
|
const font = this.font;
|
|
129
136
|
const data = font.glyfPoints(glyph);
|
|
130
137
|
if (!data)
|
|
131
138
|
return null;
|
|
139
|
+
this.points += data.xs.length;
|
|
140
|
+
if (this.points > MAX_POINTS)
|
|
141
|
+
throw new HintError('too many points');
|
|
132
142
|
const scale = size.scale, scaleFixed = this.scaleFixed = size.scaleFixed;
|
|
133
143
|
const lsb = font.leftSideBearing(glyph), advance = font.advance(glyph);
|
|
134
144
|
let count, zone;
|
|
135
145
|
if (data.components) {
|
|
136
|
-
const parts = [];
|
|
137
|
-
const placed = [];
|
|
138
146
|
let total = 0;
|
|
139
147
|
const xsAll = [], ysAll = [], on = [], ends = [];
|
|
140
148
|
let useMetrics = null;
|
|
149
|
+
this.loading.add(glyph);
|
|
141
150
|
for (const component of data.components) {
|
|
142
151
|
const part = this.load(component.glyph, size, depth + 1);
|
|
143
152
|
if (!part)
|
|
@@ -161,7 +170,6 @@ export class Hinter {
|
|
|
161
170
|
dy = Math.round(dy / 64) * 64;
|
|
162
171
|
}
|
|
163
172
|
}
|
|
164
|
-
placed.push({ dx, dy });
|
|
165
173
|
for (let i = 0; i < part.count; i++) {
|
|
166
174
|
const x = part.zone.curX[i], y = part.zone.curY[i];
|
|
167
175
|
xsAll.push(transformed ? a * x + c * y + dx : x + dx);
|
|
@@ -171,10 +179,10 @@ export class Hinter {
|
|
|
171
179
|
for (const end of part.zone.ends)
|
|
172
180
|
ends.push(end + total);
|
|
173
181
|
total += part.count;
|
|
174
|
-
parts.push(part);
|
|
175
182
|
if (component.useMetrics)
|
|
176
183
|
useMetrics = part;
|
|
177
184
|
}
|
|
185
|
+
this.loading.delete(glyph);
|
|
178
186
|
count = total;
|
|
179
187
|
zone = makeZone(count + 4);
|
|
180
188
|
zone.ends = ends;
|