imagescript 3.2.1 → 3.4.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 +35 -10
- package/THIRD_PARTY_NOTICES.md +298 -2
- package/dist/animation.d.ts +25 -8
- package/dist/animation.js +44 -13
- package/dist/codecs/av1/cdef.js +197 -0
- package/dist/codecs/av1/data.js +141 -0
- package/dist/codecs/av1/decode.js +1769 -0
- package/dist/codecs/av1/filmgrain.js +219 -0
- package/dist/codecs/av1/frame.js +114 -0
- package/dist/codecs/av1/ipred.js +491 -0
- package/dist/codecs/av1/itx.js +950 -0
- package/dist/codecs/av1/loopfilter.js +149 -0
- package/dist/codecs/av1/msac.js +161 -0
- package/dist/codecs/av1/obu.js +516 -0
- package/dist/codecs/av1/postfilter.js +28 -0
- package/dist/codecs/av1/restoration.js +193 -0
- package/dist/codecs/av1/tables.js +160 -0
- package/dist/codecs/avif.js +581 -0
- package/dist/codecs/bmp.js +317 -0
- package/dist/codecs/crc.js +5 -0
- package/dist/codecs/gif.js +10 -35
- package/dist/codecs/hdr.d.ts +25 -0
- package/dist/codecs/hdr.js +201 -0
- package/dist/codecs/ico.js +48 -0
- package/dist/codecs/jpeg.js +11 -28
- package/dist/codecs/jxl/bits.js +126 -0
- package/dist/codecs/jxl/blend.js +117 -0
- package/dist/codecs/jxl/color.js +116 -0
- package/dist/codecs/jxl/dct.js +404 -0
- package/dist/codecs/jxl/entropy.js +556 -0
- package/dist/codecs/jxl/features.js +325 -0
- package/dist/codecs/jxl/filters.js +119 -0
- package/dist/codecs/jxl/frame-header.js +195 -0
- package/dist/codecs/jxl/frame.js +319 -0
- package/dist/codecs/jxl/headers.js +249 -0
- package/dist/codecs/jxl/idct.js +484 -0
- package/dist/codecs/jxl/modular.js +688 -0
- package/dist/codecs/jxl/quant.js +219 -0
- package/dist/codecs/jxl/upsample.js +52 -0
- package/dist/codecs/jxl/vardct.js +472 -0
- package/dist/codecs/jxl.js +89 -0
- package/dist/codecs/netpbm.js +239 -0
- package/dist/codecs/png.js +22 -10
- package/dist/codecs/qoi.js +150 -0
- package/dist/codecs/tga.js +3 -1
- package/dist/codecs/tiff.js +7 -2
- package/dist/codecs/vp8-enc-dsp.js +932 -0
- package/dist/codecs/vp8-enc-tables.js +78 -0
- package/dist/codecs/vp8-enc.js +1930 -0
- package/dist/codecs/vp8.js +3 -1
- package/dist/codecs/vp8l.js +126 -58
- package/dist/codecs/webp.js +47 -11
- 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-type.d.ts +15 -1
- package/dist/image-type.js +45 -2
- package/dist/image.d.ts +89 -13
- package/dist/image.js +266 -113
- package/dist/index.d.ts +3 -2
- 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 +38 -22
- 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 +102 -23
- package/dist/render/text.js +373 -103
- package/dist/render/unicode-data.js +63 -0
- package/dist/render/unicode.js +50 -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 +40 -0
- package/dist/util/limits.js +34 -0
- package/dist/util/options.js +7 -0
- package/dist/util/resample.js +126 -7
- 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 +25 -2
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
|
@@ -12,6 +12,12 @@ class SkipInstruction extends Error {
|
|
|
12
12
|
}
|
|
13
13
|
const roundHalfAway = (v) => v < 0 ? -Math.round(-v) : Math.round(v);
|
|
14
14
|
const mulFix = (a, b) => roundHalfAway(a * b / 65536);
|
|
15
|
+
const mulDiv = (a, b, c) => {
|
|
16
|
+
if (!c)
|
|
17
|
+
return a * b < 0 ? -0x7fffffff : 0x7fffffff;
|
|
18
|
+
const d = Math.floor((Math.abs(a * b) + Math.abs(c) / 2) / Math.abs(c));
|
|
19
|
+
return a * b * c < 0 ? -d : d;
|
|
20
|
+
};
|
|
15
21
|
const divFix = (a, b) => b === 0 ? 0 : roundHalfAway(a * 65536 / b);
|
|
16
22
|
const ROUND_GRID = { period: 64, phase: 0, threshold: 32 };
|
|
17
23
|
const defaultState = () => ({
|
|
@@ -28,6 +34,7 @@ function makeZone(count) {
|
|
|
28
34
|
};
|
|
29
35
|
}
|
|
30
36
|
const MAX_INSTRUCTIONS = 1_000_000;
|
|
37
|
+
const MAX_POINTS = 65536;
|
|
31
38
|
export class Hinter {
|
|
32
39
|
font;
|
|
33
40
|
fpgm;
|
|
@@ -38,6 +45,8 @@ export class Hinter {
|
|
|
38
45
|
maxTwilight;
|
|
39
46
|
sizes = new Map();
|
|
40
47
|
scaleFixed = 0;
|
|
48
|
+
loading = new Set();
|
|
49
|
+
points = 0;
|
|
41
50
|
constructor(font) {
|
|
42
51
|
this.font = font;
|
|
43
52
|
const table = (tag) => {
|
|
@@ -98,6 +107,8 @@ export class Hinter {
|
|
|
98
107
|
if (state.broken)
|
|
99
108
|
return null;
|
|
100
109
|
try {
|
|
110
|
+
this.loading.clear();
|
|
111
|
+
this.points = 0;
|
|
101
112
|
const outline = this.load(glyph, state, 0);
|
|
102
113
|
if (!outline)
|
|
103
114
|
return { path: [], advance: Math.round(this.font.advance(glyph) * state.scale / 64) };
|
|
@@ -125,19 +136,23 @@ export class Hinter {
|
|
|
125
136
|
load(glyph, size, depth) {
|
|
126
137
|
if (depth > 8)
|
|
127
138
|
throw new HintError('composite glyphs nested too deep');
|
|
139
|
+
if (this.loading.has(glyph))
|
|
140
|
+
throw new HintError('composite glyph containing itself');
|
|
128
141
|
const font = this.font;
|
|
129
142
|
const data = font.glyfPoints(glyph);
|
|
130
143
|
if (!data)
|
|
131
144
|
return null;
|
|
145
|
+
this.points += data.xs.length;
|
|
146
|
+
if (this.points > MAX_POINTS)
|
|
147
|
+
throw new HintError('too many points');
|
|
132
148
|
const scale = size.scale, scaleFixed = this.scaleFixed = size.scaleFixed;
|
|
133
149
|
const lsb = font.leftSideBearing(glyph), advance = font.advance(glyph);
|
|
134
150
|
let count, zone;
|
|
135
151
|
if (data.components) {
|
|
136
|
-
const parts = [];
|
|
137
|
-
const placed = [];
|
|
138
152
|
let total = 0;
|
|
139
153
|
const xsAll = [], ysAll = [], on = [], ends = [];
|
|
140
154
|
let useMetrics = null;
|
|
155
|
+
this.loading.add(glyph);
|
|
141
156
|
for (const component of data.components) {
|
|
142
157
|
const part = this.load(component.glyph, size, depth + 1);
|
|
143
158
|
if (!part)
|
|
@@ -161,7 +176,6 @@ export class Hinter {
|
|
|
161
176
|
dy = Math.round(dy / 64) * 64;
|
|
162
177
|
}
|
|
163
178
|
}
|
|
164
|
-
placed.push({ dx, dy });
|
|
165
179
|
for (let i = 0; i < part.count; i++) {
|
|
166
180
|
const x = part.zone.curX[i], y = part.zone.curY[i];
|
|
167
181
|
xsAll.push(transformed ? a * x + c * y + dx : x + dx);
|
|
@@ -171,10 +185,10 @@ export class Hinter {
|
|
|
171
185
|
for (const end of part.zone.ends)
|
|
172
186
|
ends.push(end + total);
|
|
173
187
|
total += part.count;
|
|
174
|
-
parts.push(part);
|
|
175
188
|
if (component.useMetrics)
|
|
176
189
|
useMetrics = part;
|
|
177
190
|
}
|
|
191
|
+
this.loading.delete(glyph);
|
|
178
192
|
count = total;
|
|
179
193
|
zone = makeZone(count + 4);
|
|
180
194
|
zone.ends = ends;
|
|
@@ -339,10 +353,10 @@ class Execution {
|
|
|
339
353
|
}
|
|
340
354
|
lineVector(p1, p2, perpendicular, original) {
|
|
341
355
|
const z1 = this.zone(this.gs.zp1), z2 = this.zone(this.gs.zp2);
|
|
342
|
-
this.point(z1,
|
|
343
|
-
this.point(z2,
|
|
344
|
-
let x = (original ? z1.orgX[
|
|
345
|
-
let y = (original ? z1.orgY[
|
|
356
|
+
this.point(z1, p1);
|
|
357
|
+
this.point(z2, p2);
|
|
358
|
+
let x = (original ? z1.orgX[p1] - z2.orgX[p2] : z1.curX[p1] - z2.curX[p2]);
|
|
359
|
+
let y = (original ? z1.orgY[p1] - z2.orgY[p2] : z1.curY[p1] - z2.curY[p2]);
|
|
346
360
|
if (x === 0 && y === 0) {
|
|
347
361
|
x = 1;
|
|
348
362
|
y = 0;
|
|
@@ -467,18 +481,19 @@ class Execution {
|
|
|
467
481
|
[a0, a1].forEach(i => this.point(za, i));
|
|
468
482
|
[b0, b1].forEach(i => this.point(zb, i));
|
|
469
483
|
this.point(z, p);
|
|
470
|
-
const ax0 = za.curX[a0], ay0 = za.curY[a0], ax1 = za.curX[a1], ay1 = za.curY[a1];
|
|
471
|
-
const bx0 = zb.curX[b0], by0 = zb.curY[b0], bx1 = zb.curX[b1], by1 = zb.curY[b1];
|
|
484
|
+
const ax0 = Math.round(za.curX[a0]), ay0 = Math.round(za.curY[a0]), ax1 = Math.round(za.curX[a1]), ay1 = Math.round(za.curY[a1]);
|
|
485
|
+
const bx0 = Math.round(zb.curX[b0]), by0 = Math.round(zb.curY[b0]), bx1 = Math.round(zb.curX[b1]), by1 = Math.round(zb.curY[b1]);
|
|
472
486
|
const dax = ax1 - ax0, day = ay1 - ay0, dbx = bx1 - bx0, dby = by1 - by0;
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
487
|
+
const discriminant = mulDiv(dax, -dby, 64) + mulDiv(day, dbx, 64);
|
|
488
|
+
const dotProduct = mulDiv(dax, dbx, 64) + mulDiv(day, dby, 64);
|
|
489
|
+
if (19 * Math.abs(discriminant) > Math.abs(dotProduct)) {
|
|
490
|
+
const v = mulDiv(bx0 - ax0, -dby, 64) + mulDiv(by0 - ay0, dbx, 64);
|
|
491
|
+
z.curX[p] = ax0 + mulDiv(v, dax, discriminant);
|
|
492
|
+
z.curY[p] = ay0 + mulDiv(v, day, discriminant);
|
|
477
493
|
}
|
|
478
494
|
else {
|
|
479
|
-
|
|
480
|
-
z.
|
|
481
|
-
z.curY[p] = ay0 + t * day;
|
|
495
|
+
z.curX[p] = Math.trunc((ax0 + ax1 + bx0 + bx1) / 4);
|
|
496
|
+
z.curY[p] = Math.trunc((ay0 + ay1 + by0 + by1) / 4);
|
|
482
497
|
}
|
|
483
498
|
z.touchX[p] = z.touchY[p] = 1;
|
|
484
499
|
break;
|
|
@@ -532,11 +547,12 @@ class Execution {
|
|
|
532
547
|
case 0x76:
|
|
533
548
|
case 0x77: {
|
|
534
549
|
const n = this.pop();
|
|
535
|
-
const grid = op === 0x76 ?
|
|
536
|
-
const period = [grid
|
|
537
|
-
const phase = [0, period
|
|
550
|
+
const grid = op === 0x76 ? 0x4000 : 0x2d41;
|
|
551
|
+
const period = [grid >> 1, grid, grid * 2, grid][n >> 6 & 3];
|
|
552
|
+
const phase = [0, period >> 2, period >> 1, Math.trunc(period * 3 / 4)][n >> 4 & 3];
|
|
538
553
|
const t = n & 15;
|
|
539
|
-
|
|
554
|
+
const threshold = t === 0 ? period - 1 : Math.trunc((t - 4) * period / 8);
|
|
555
|
+
gs.round = { period: period >> 8, phase: phase >> 8, threshold: threshold >> 8 };
|
|
540
556
|
break;
|
|
541
557
|
}
|
|
542
558
|
case 0x1a:
|
|
@@ -1016,7 +1032,7 @@ class Execution {
|
|
|
1016
1032
|
const z0 = this.zone(gs.zp0), z1 = this.zone(gs.zp1);
|
|
1017
1033
|
this.point(z0, p2);
|
|
1018
1034
|
this.point(z1, p1);
|
|
1019
|
-
const d = this.project(z0.curX[p2] - z1.curX[p1], z0.curY[p2] - z1.curY[p1]) / 2;
|
|
1035
|
+
const d = Math.trunc(Math.round(this.project(z0.curX[p2] - z1.curX[p1], z0.curY[p2] - z1.curY[p1])) / 2);
|
|
1020
1036
|
this.move(z1, p1, d);
|
|
1021
1037
|
this.move(z0, p2, -d);
|
|
1022
1038
|
break;
|