pdfnative 1.4.0 → 1.5.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 +54 -16
- package/dist/index.cjs +550 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +289 -2
- package/dist/index.d.ts +289 -2
- package/dist/index.js +546 -15
- package/dist/index.js.map +1 -1
- package/dist/tools/index.cjs +657 -0
- package/dist/tools/index.cjs.map +1 -0
- package/dist/tools/index.d.cts +110 -0
- package/dist/tools/index.d.ts +110 -0
- package/dist/tools/index.js +654 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/worker/index.cjs +18 -0
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.js +18 -0
- package/dist/worker/index.js.map +1 -1
- package/fonts/noto-sans-math-data.d.ts +13 -0
- package/fonts/noto-sans-math-data.js +64 -0
- package/package.json +29 -3
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
// src/tools/font-compiler.ts
|
|
2
|
+
var TTFReader = class {
|
|
3
|
+
constructor(bytes) {
|
|
4
|
+
this.pos = 0;
|
|
5
|
+
this.bytes = bytes;
|
|
6
|
+
this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
7
|
+
}
|
|
8
|
+
seek(offset) {
|
|
9
|
+
this.pos = offset;
|
|
10
|
+
}
|
|
11
|
+
skip(n) {
|
|
12
|
+
this.pos += n;
|
|
13
|
+
}
|
|
14
|
+
readUint8() {
|
|
15
|
+
return this.view.getUint8(this.pos++);
|
|
16
|
+
}
|
|
17
|
+
readUint16() {
|
|
18
|
+
const v = this.view.getUint16(this.pos);
|
|
19
|
+
this.pos += 2;
|
|
20
|
+
return v;
|
|
21
|
+
}
|
|
22
|
+
readInt16() {
|
|
23
|
+
const v = this.view.getInt16(this.pos);
|
|
24
|
+
this.pos += 2;
|
|
25
|
+
return v;
|
|
26
|
+
}
|
|
27
|
+
readUint32() {
|
|
28
|
+
const v = this.view.getUint32(this.pos);
|
|
29
|
+
this.pos += 4;
|
|
30
|
+
return v;
|
|
31
|
+
}
|
|
32
|
+
readTag() {
|
|
33
|
+
let t = "";
|
|
34
|
+
for (let k = 0; k < 4; k++) t += String.fromCharCode(this.bytes[this.pos + k]);
|
|
35
|
+
this.pos += 4;
|
|
36
|
+
return t;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
function readCoverageTable(r, absOffset) {
|
|
40
|
+
const glyphs = [];
|
|
41
|
+
r.seek(absOffset);
|
|
42
|
+
const format = r.readUint16();
|
|
43
|
+
if (format === 1) {
|
|
44
|
+
const count = r.readUint16();
|
|
45
|
+
for (let i = 0; i < count; i++) glyphs.push(r.readUint16());
|
|
46
|
+
} else if (format === 2) {
|
|
47
|
+
const rangeCount = r.readUint16();
|
|
48
|
+
for (let i = 0; i < rangeCount; i++) {
|
|
49
|
+
const start = r.readUint16();
|
|
50
|
+
const end = r.readUint16();
|
|
51
|
+
r.skip(2);
|
|
52
|
+
for (let g = start; g <= end; g++) glyphs.push(g);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return glyphs;
|
|
56
|
+
}
|
|
57
|
+
function parseGSUBSingle(r, tables) {
|
|
58
|
+
const gsub = {};
|
|
59
|
+
if (!tables["GSUB"]) return gsub;
|
|
60
|
+
try {
|
|
61
|
+
const base = tables["GSUB"].offset;
|
|
62
|
+
r.seek(base);
|
|
63
|
+
r.skip(4);
|
|
64
|
+
r.skip(2);
|
|
65
|
+
r.skip(2);
|
|
66
|
+
const lookupListOffset = r.readUint16();
|
|
67
|
+
r.seek(base + lookupListOffset);
|
|
68
|
+
const lookupCount = r.readUint16();
|
|
69
|
+
const lookupOffsets = [];
|
|
70
|
+
for (let i = 0; i < lookupCount; i++) lookupOffsets.push(r.readUint16());
|
|
71
|
+
for (let li = 0; li < lookupCount; li++) {
|
|
72
|
+
r.seek(base + lookupListOffset + lookupOffsets[li]);
|
|
73
|
+
const lookupType = r.readUint16();
|
|
74
|
+
r.skip(2);
|
|
75
|
+
const subtableCount = r.readUint16();
|
|
76
|
+
const subtableOffsets = [];
|
|
77
|
+
for (let si = 0; si < subtableCount; si++) subtableOffsets.push(r.readUint16());
|
|
78
|
+
if (lookupType !== 1) continue;
|
|
79
|
+
for (const stOffset of subtableOffsets) {
|
|
80
|
+
const stBase = base + lookupListOffset + lookupOffsets[li] + stOffset;
|
|
81
|
+
r.seek(stBase);
|
|
82
|
+
const substFormat = r.readUint16();
|
|
83
|
+
const coverageOffset = r.readUint16();
|
|
84
|
+
const coverageGlyphs = readCoverageTable(r, stBase + coverageOffset);
|
|
85
|
+
if (substFormat === 1) {
|
|
86
|
+
const delta = r.readInt16();
|
|
87
|
+
for (const gid of coverageGlyphs) {
|
|
88
|
+
const sub = gid + delta & 65535;
|
|
89
|
+
if (sub > 0) gsub[gid] = sub;
|
|
90
|
+
}
|
|
91
|
+
} else if (substFormat === 2) {
|
|
92
|
+
const glyphCount = r.readUint16();
|
|
93
|
+
for (let gi = 0; gi < glyphCount && gi < coverageGlyphs.length; gi++) {
|
|
94
|
+
const sub = r.readUint16();
|
|
95
|
+
if (sub > 0) gsub[coverageGlyphs[gi]] = sub;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
return gsub;
|
|
103
|
+
}
|
|
104
|
+
function parseGSUBLigatures(r, tables) {
|
|
105
|
+
const ligatures = {};
|
|
106
|
+
if (!tables["GSUB"]) return ligatures;
|
|
107
|
+
try {
|
|
108
|
+
const base = tables["GSUB"].offset;
|
|
109
|
+
r.seek(base);
|
|
110
|
+
r.skip(4);
|
|
111
|
+
r.skip(2);
|
|
112
|
+
r.skip(2);
|
|
113
|
+
const lookupListOffset = r.readUint16();
|
|
114
|
+
r.seek(base + lookupListOffset);
|
|
115
|
+
const lookupCount = r.readUint16();
|
|
116
|
+
const lookupOffsets = [];
|
|
117
|
+
for (let i = 0; i < lookupCount; i++) lookupOffsets.push(r.readUint16());
|
|
118
|
+
for (let li = 0; li < lookupCount; li++) {
|
|
119
|
+
const lookupBase = base + lookupListOffset + lookupOffsets[li];
|
|
120
|
+
r.seek(lookupBase);
|
|
121
|
+
const lookupType = r.readUint16();
|
|
122
|
+
r.skip(2);
|
|
123
|
+
const subtableCount = r.readUint16();
|
|
124
|
+
const subtableOffsets = [];
|
|
125
|
+
for (let si = 0; si < subtableCount; si++) subtableOffsets.push(r.readUint16());
|
|
126
|
+
if (lookupType !== 4) continue;
|
|
127
|
+
for (const stOffset of subtableOffsets) {
|
|
128
|
+
const stBase = lookupBase + stOffset;
|
|
129
|
+
r.seek(stBase);
|
|
130
|
+
const substFormat = r.readUint16();
|
|
131
|
+
if (substFormat !== 1) continue;
|
|
132
|
+
const coverageOffset = r.readUint16();
|
|
133
|
+
const ligSetCount = r.readUint16();
|
|
134
|
+
const ligSetOffsets = [];
|
|
135
|
+
for (let lsi = 0; lsi < ligSetCount; lsi++) ligSetOffsets.push(r.readUint16());
|
|
136
|
+
const coverageGlyphs = readCoverageTable(r, stBase + coverageOffset);
|
|
137
|
+
for (let lsi = 0; lsi < ligSetCount && lsi < coverageGlyphs.length; lsi++) {
|
|
138
|
+
const firstGid = coverageGlyphs[lsi];
|
|
139
|
+
const ligSetBase = stBase + ligSetOffsets[lsi];
|
|
140
|
+
r.seek(ligSetBase);
|
|
141
|
+
const ligCount = r.readUint16();
|
|
142
|
+
const ligOffsets = [];
|
|
143
|
+
for (let lgi = 0; lgi < ligCount; lgi++) ligOffsets.push(r.readUint16());
|
|
144
|
+
for (const ligOff of ligOffsets) {
|
|
145
|
+
r.seek(ligSetBase + ligOff);
|
|
146
|
+
const ligatureGlyph = r.readUint16();
|
|
147
|
+
const componentCount = r.readUint16();
|
|
148
|
+
const components = [];
|
|
149
|
+
for (let ci = 0; ci < componentCount - 1; ci++) components.push(r.readUint16());
|
|
150
|
+
if (!ligatures[firstGid]) ligatures[firstGid] = [];
|
|
151
|
+
ligatures[firstGid].push([ligatureGlyph, ...components]);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const gid of Object.keys(ligatures)) {
|
|
157
|
+
ligatures[gid].sort((a, b) => b.length - a.length);
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
161
|
+
return ligatures;
|
|
162
|
+
}
|
|
163
|
+
function parseGPOS(r, tables) {
|
|
164
|
+
const result = { marks: {}, bases: {}, mark2mark: { mark1Anchors: {}, mark2Classes: {} } };
|
|
165
|
+
if (!tables["GPOS"]) return result;
|
|
166
|
+
try {
|
|
167
|
+
const base = tables["GPOS"].offset;
|
|
168
|
+
r.seek(base);
|
|
169
|
+
r.skip(4);
|
|
170
|
+
r.skip(2);
|
|
171
|
+
r.skip(2);
|
|
172
|
+
const lookupListOffset = r.readUint16();
|
|
173
|
+
r.seek(base + lookupListOffset);
|
|
174
|
+
const lookupCount = r.readUint16();
|
|
175
|
+
const lookupOffsets = [];
|
|
176
|
+
for (let i = 0; i < lookupCount; i++) lookupOffsets.push(r.readUint16());
|
|
177
|
+
for (let li = 0; li < lookupCount; li++) {
|
|
178
|
+
r.seek(base + lookupListOffset + lookupOffsets[li]);
|
|
179
|
+
const lookupType = r.readUint16();
|
|
180
|
+
r.skip(2);
|
|
181
|
+
const subtableCount = r.readUint16();
|
|
182
|
+
const stOffsets = [];
|
|
183
|
+
for (let si = 0; si < subtableCount; si++) stOffsets.push(r.readUint16());
|
|
184
|
+
if (lookupType !== 4 && lookupType !== 6) continue;
|
|
185
|
+
for (const stOff of stOffsets) {
|
|
186
|
+
const stBase = base + lookupListOffset + lookupOffsets[li] + stOff;
|
|
187
|
+
r.seek(stBase);
|
|
188
|
+
r.skip(2);
|
|
189
|
+
const mark1CoverageOffset = r.readUint16();
|
|
190
|
+
const mark2CoverageOffset = r.readUint16();
|
|
191
|
+
const markClassCount = r.readUint16();
|
|
192
|
+
const mark1ArrayOffset = r.readUint16();
|
|
193
|
+
const mark2ArrayOffset = r.readUint16();
|
|
194
|
+
const mark1Glyphs = readCoverageTable(r, stBase + mark1CoverageOffset);
|
|
195
|
+
const mark2Glyphs = readCoverageTable(r, stBase + mark2CoverageOffset);
|
|
196
|
+
r.seek(stBase + mark1ArrayOffset);
|
|
197
|
+
const markCount = r.readUint16();
|
|
198
|
+
const mark1Entries = [];
|
|
199
|
+
for (let mi = 0; mi < markCount && mi < mark1Glyphs.length; mi++) {
|
|
200
|
+
const markClass = r.readUint16();
|
|
201
|
+
const anchorOffset = r.readUint16();
|
|
202
|
+
const savedPos = r.pos;
|
|
203
|
+
r.seek(stBase + mark1ArrayOffset + anchorOffset);
|
|
204
|
+
const anchorFormat = r.readUint16();
|
|
205
|
+
const ax = anchorFormat >= 1 ? r.readInt16() : 0;
|
|
206
|
+
const ay = anchorFormat >= 1 ? r.readInt16() : 0;
|
|
207
|
+
r.pos = savedPos;
|
|
208
|
+
mark1Entries.push({ gid: mark1Glyphs[mi], classIdx: markClass, x: ax, y: ay });
|
|
209
|
+
}
|
|
210
|
+
r.seek(stBase + mark2ArrayOffset);
|
|
211
|
+
const baseCount = r.readUint16();
|
|
212
|
+
const baseRecords = [];
|
|
213
|
+
for (let bi = 0; bi < baseCount; bi++) {
|
|
214
|
+
const recs = [];
|
|
215
|
+
for (let mc = 0; mc < markClassCount; mc++) recs.push(r.readUint16());
|
|
216
|
+
baseRecords.push(recs);
|
|
217
|
+
}
|
|
218
|
+
if (lookupType === 4) {
|
|
219
|
+
for (const md of mark1Entries) {
|
|
220
|
+
result.marks[md.gid] = { classIdx: md.classIdx, x: md.x, y: md.y };
|
|
221
|
+
}
|
|
222
|
+
for (let bi = 0; bi < baseCount && bi < mark2Glyphs.length; bi++) {
|
|
223
|
+
const baseGid = mark2Glyphs[bi];
|
|
224
|
+
result.bases[baseGid] = {};
|
|
225
|
+
for (let mc = 0; mc < markClassCount; mc++) {
|
|
226
|
+
const anchorOff = baseRecords[bi][mc];
|
|
227
|
+
if (!anchorOff) continue;
|
|
228
|
+
r.seek(stBase + mark2ArrayOffset + anchorOff);
|
|
229
|
+
r.skip(2);
|
|
230
|
+
const bx = r.readInt16();
|
|
231
|
+
const by = r.readInt16();
|
|
232
|
+
result.bases[baseGid][mc] = { x: bx, y: by };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
for (const md of mark1Entries) {
|
|
237
|
+
result.mark2mark.mark2Classes[md.gid] = { classIdx: md.classIdx, x: md.x, y: md.y };
|
|
238
|
+
}
|
|
239
|
+
for (let bi = 0; bi < baseCount && bi < mark2Glyphs.length; bi++) {
|
|
240
|
+
const m1Gid = mark2Glyphs[bi];
|
|
241
|
+
result.mark2mark.mark1Anchors[m1Gid] = {};
|
|
242
|
+
for (let mc = 0; mc < markClassCount; mc++) {
|
|
243
|
+
const anchorOff = baseRecords[bi][mc];
|
|
244
|
+
if (!anchorOff) continue;
|
|
245
|
+
r.seek(stBase + mark2ArrayOffset + anchorOff);
|
|
246
|
+
r.skip(2);
|
|
247
|
+
const mx = r.readInt16();
|
|
248
|
+
const my = r.readInt16();
|
|
249
|
+
result.mark2mark.mark1Anchors[m1Gid][mc] = { x: mx, y: my };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
function parseName(r, tables) {
|
|
260
|
+
if (!tables["name"]) return void 0;
|
|
261
|
+
try {
|
|
262
|
+
const base = tables["name"].offset;
|
|
263
|
+
r.seek(base);
|
|
264
|
+
r.skip(2);
|
|
265
|
+
const count = r.readUint16();
|
|
266
|
+
const stringOffset = r.readUint16();
|
|
267
|
+
let psName;
|
|
268
|
+
let fullName;
|
|
269
|
+
let familyName;
|
|
270
|
+
for (let i = 0; i < count; i++) {
|
|
271
|
+
const platformID = r.readUint16();
|
|
272
|
+
const encodingID = r.readUint16();
|
|
273
|
+
r.skip(2);
|
|
274
|
+
const nameID = r.readUint16();
|
|
275
|
+
const length = r.readUint16();
|
|
276
|
+
const offset = r.readUint16();
|
|
277
|
+
const savedPos = r.pos;
|
|
278
|
+
let value = "";
|
|
279
|
+
r.seek(base + stringOffset + offset);
|
|
280
|
+
if (platformID === 3 || platformID === 0) {
|
|
281
|
+
for (let k = 0; k < length; k += 2) value += String.fromCharCode(r.readUint16());
|
|
282
|
+
} else {
|
|
283
|
+
for (let k = 0; k < length; k++) value += String.fromCharCode(r.readUint8());
|
|
284
|
+
}
|
|
285
|
+
r.pos = savedPos;
|
|
286
|
+
void encodingID;
|
|
287
|
+
if (nameID === 6) psName = value;
|
|
288
|
+
else if (nameID === 4) fullName = value;
|
|
289
|
+
else if (nameID === 1) familyName = value;
|
|
290
|
+
}
|
|
291
|
+
return psName ?? fullName ?? familyName;
|
|
292
|
+
} catch {
|
|
293
|
+
return void 0;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function parseTTFRaw(bytes) {
|
|
297
|
+
const r = new TTFReader(bytes);
|
|
298
|
+
const sfVersion = r.readUint32();
|
|
299
|
+
if (sfVersion !== 65536 && sfVersion !== 1953658213 && sfVersion !== 1330926671) {
|
|
300
|
+
throw new Error(`Not a TrueType/OpenType font (sfVersion: 0x${sfVersion.toString(16)})`);
|
|
301
|
+
}
|
|
302
|
+
const numTables = r.readUint16();
|
|
303
|
+
r.skip(6);
|
|
304
|
+
const tables = {};
|
|
305
|
+
for (let i = 0; i < numTables; i++) {
|
|
306
|
+
const tag = r.readTag();
|
|
307
|
+
r.skip(4);
|
|
308
|
+
const offset = r.readUint32();
|
|
309
|
+
const length = r.readUint32();
|
|
310
|
+
tables[tag] = { offset, length };
|
|
311
|
+
}
|
|
312
|
+
if (!tables["head"]) throw new Error("Missing head table");
|
|
313
|
+
r.seek(tables["head"].offset);
|
|
314
|
+
r.skip(18);
|
|
315
|
+
const unitsPerEm = r.readUint16();
|
|
316
|
+
r.skip(16);
|
|
317
|
+
const xMin = r.readInt16();
|
|
318
|
+
const yMin = r.readInt16();
|
|
319
|
+
const xMax = r.readInt16();
|
|
320
|
+
const yMax = r.readInt16();
|
|
321
|
+
if (!tables["hhea"]) throw new Error("Missing hhea table");
|
|
322
|
+
r.seek(tables["hhea"].offset);
|
|
323
|
+
r.skip(4);
|
|
324
|
+
const ascent = r.readInt16();
|
|
325
|
+
const descent = r.readInt16();
|
|
326
|
+
r.skip(26);
|
|
327
|
+
const numberOfHMetrics = r.readUint16();
|
|
328
|
+
if (!tables["maxp"]) throw new Error("Missing maxp table");
|
|
329
|
+
r.seek(tables["maxp"].offset);
|
|
330
|
+
r.skip(4);
|
|
331
|
+
const numGlyphs = r.readUint16();
|
|
332
|
+
let capHeight = Math.round(ascent * 0.7);
|
|
333
|
+
let stemV = 80;
|
|
334
|
+
if (tables["OS/2"]) {
|
|
335
|
+
r.seek(tables["OS/2"].offset);
|
|
336
|
+
const os2Version = r.readUint16();
|
|
337
|
+
r.seek(tables["OS/2"].offset + 68);
|
|
338
|
+
r.readInt16();
|
|
339
|
+
r.readInt16();
|
|
340
|
+
r.skip(4);
|
|
341
|
+
if (os2Version >= 2 && tables["OS/2"].length >= 90) {
|
|
342
|
+
r.seek(tables["OS/2"].offset + 88);
|
|
343
|
+
capHeight = r.readInt16();
|
|
344
|
+
}
|
|
345
|
+
r.seek(tables["OS/2"].offset + 4);
|
|
346
|
+
const weightClass = r.readUint16();
|
|
347
|
+
stemV = Math.round(weightClass * 0.12);
|
|
348
|
+
}
|
|
349
|
+
if (!tables["hmtx"]) throw new Error("Missing hmtx table");
|
|
350
|
+
r.seek(tables["hmtx"].offset);
|
|
351
|
+
const widths = {};
|
|
352
|
+
let lastWidth = 0;
|
|
353
|
+
for (let i = 0; i < numberOfHMetrics; i++) {
|
|
354
|
+
const advanceWidth = r.readUint16();
|
|
355
|
+
r.skip(2);
|
|
356
|
+
widths[i] = advanceWidth;
|
|
357
|
+
lastWidth = advanceWidth;
|
|
358
|
+
}
|
|
359
|
+
for (let i = numberOfHMetrics; i < numGlyphs; i++) widths[i] = lastWidth;
|
|
360
|
+
if (!tables["cmap"]) throw new Error("Missing cmap table");
|
|
361
|
+
const cmapOffset = tables["cmap"].offset;
|
|
362
|
+
r.seek(cmapOffset);
|
|
363
|
+
r.skip(2);
|
|
364
|
+
const numSubtables = r.readUint16();
|
|
365
|
+
let bestSubtableOffset = -1;
|
|
366
|
+
let bestFormat = 0;
|
|
367
|
+
for (let i = 0; i < numSubtables; i++) {
|
|
368
|
+
const platformID = r.readUint16();
|
|
369
|
+
const encodingID = r.readUint16();
|
|
370
|
+
const subtableOffset = r.readUint32();
|
|
371
|
+
if (platformID === 3 && (encodingID === 1 || encodingID === 10) || platformID === 0 && (encodingID === 3 || encodingID === 4)) {
|
|
372
|
+
const savedPos = r.pos;
|
|
373
|
+
r.seek(cmapOffset + subtableOffset);
|
|
374
|
+
const format = r.readUint16();
|
|
375
|
+
r.pos = savedPos;
|
|
376
|
+
if (format === 12 && bestFormat < 12) {
|
|
377
|
+
bestSubtableOffset = cmapOffset + subtableOffset;
|
|
378
|
+
bestFormat = 12;
|
|
379
|
+
} else if (format === 4 && bestFormat < 4) {
|
|
380
|
+
bestSubtableOffset = cmapOffset + subtableOffset;
|
|
381
|
+
bestFormat = 4;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (bestSubtableOffset === -1) throw new Error("No suitable cmap subtable found (need format 4 or 12)");
|
|
386
|
+
const cmap = {};
|
|
387
|
+
if (bestFormat === 12) {
|
|
388
|
+
r.seek(bestSubtableOffset);
|
|
389
|
+
r.skip(2);
|
|
390
|
+
r.skip(2);
|
|
391
|
+
r.skip(4);
|
|
392
|
+
r.skip(4);
|
|
393
|
+
const numGroups = r.readUint32();
|
|
394
|
+
for (let i = 0; i < numGroups; i++) {
|
|
395
|
+
const startCharCode = r.readUint32();
|
|
396
|
+
const endCharCode = r.readUint32();
|
|
397
|
+
const startGlyphID = r.readUint32();
|
|
398
|
+
for (let c = startCharCode; c <= endCharCode; c++) {
|
|
399
|
+
const gid = startGlyphID + (c - startCharCode);
|
|
400
|
+
if (gid > 0 && gid < numGlyphs) cmap[c] = gid;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
} else {
|
|
404
|
+
r.seek(bestSubtableOffset);
|
|
405
|
+
r.skip(2);
|
|
406
|
+
r.skip(2);
|
|
407
|
+
r.skip(2);
|
|
408
|
+
const segCountX2 = r.readUint16();
|
|
409
|
+
const segCount = segCountX2 / 2;
|
|
410
|
+
r.skip(6);
|
|
411
|
+
const endCodes = [];
|
|
412
|
+
for (let i = 0; i < segCount; i++) endCodes.push(r.readUint16());
|
|
413
|
+
r.skip(2);
|
|
414
|
+
const startCodes = [];
|
|
415
|
+
for (let i = 0; i < segCount; i++) startCodes.push(r.readUint16());
|
|
416
|
+
const idDeltas = [];
|
|
417
|
+
for (let i = 0; i < segCount; i++) idDeltas.push(r.readInt16());
|
|
418
|
+
const idRangeOffsetPos = r.pos;
|
|
419
|
+
const idRangeOffsets = [];
|
|
420
|
+
for (let i = 0; i < segCount; i++) idRangeOffsets.push(r.readUint16());
|
|
421
|
+
for (let i = 0; i < segCount; i++) {
|
|
422
|
+
if (startCodes[i] === 65535) break;
|
|
423
|
+
for (let c = startCodes[i]; c <= endCodes[i]; c++) {
|
|
424
|
+
let gid;
|
|
425
|
+
if (idRangeOffsets[i] === 0) {
|
|
426
|
+
gid = c + idDeltas[i] & 65535;
|
|
427
|
+
} else {
|
|
428
|
+
const offset = idRangeOffsetPos + i * 2 + idRangeOffsets[i] + (c - startCodes[i]) * 2;
|
|
429
|
+
r.seek(offset);
|
|
430
|
+
gid = r.readUint16();
|
|
431
|
+
if (gid !== 0) gid = gid + idDeltas[i] & 65535;
|
|
432
|
+
}
|
|
433
|
+
if (gid > 0 && gid < numGlyphs) cmap[c] = gid;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const spaceGid = cmap[32] || 0;
|
|
438
|
+
const defaultWidth = widths[spaceGid] || widths[0] || 600;
|
|
439
|
+
const gsub = parseGSUBSingle(r, tables);
|
|
440
|
+
const ligatures = parseGSUBLigatures(r, tables);
|
|
441
|
+
const markAnchors = parseGPOS(r, tables);
|
|
442
|
+
const name = parseName(r, tables);
|
|
443
|
+
return {
|
|
444
|
+
metrics: {
|
|
445
|
+
unitsPerEm,
|
|
446
|
+
ascent,
|
|
447
|
+
descent,
|
|
448
|
+
capHeight,
|
|
449
|
+
stemV,
|
|
450
|
+
bbox: [xMin, yMin, xMax, yMax],
|
|
451
|
+
defaultWidth,
|
|
452
|
+
numGlyphs
|
|
453
|
+
},
|
|
454
|
+
cmap,
|
|
455
|
+
widths,
|
|
456
|
+
gsub,
|
|
457
|
+
ligatures,
|
|
458
|
+
markAnchors,
|
|
459
|
+
name
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
var B64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
463
|
+
function encodeBase64(bytes) {
|
|
464
|
+
let out = "";
|
|
465
|
+
const len = bytes.length;
|
|
466
|
+
let i = 0;
|
|
467
|
+
for (; i + 2 < len; i += 3) {
|
|
468
|
+
const n = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
|
|
469
|
+
out += B64_ALPHABET[n >> 18 & 63] + B64_ALPHABET[n >> 12 & 63] + B64_ALPHABET[n >> 6 & 63] + B64_ALPHABET[n & 63];
|
|
470
|
+
}
|
|
471
|
+
const rem = len - i;
|
|
472
|
+
if (rem === 1) {
|
|
473
|
+
const n = bytes[i] << 16;
|
|
474
|
+
out += B64_ALPHABET[n >> 18 & 63] + B64_ALPHABET[n >> 12 & 63] + "==";
|
|
475
|
+
} else if (rem === 2) {
|
|
476
|
+
const n = bytes[i] << 16 | bytes[i + 1] << 8;
|
|
477
|
+
out += B64_ALPHABET[n >> 18 & 63] + B64_ALPHABET[n >> 12 & 63] + B64_ALPHABET[n >> 6 & 63] + "=";
|
|
478
|
+
}
|
|
479
|
+
return out;
|
|
480
|
+
}
|
|
481
|
+
function buildPDFWidthArray(widths, numGlyphs, defaultWidth) {
|
|
482
|
+
const allWidths = [];
|
|
483
|
+
for (let i2 = 0; i2 < numGlyphs; i2++) allWidths.push(widths[i2] !== void 0 ? widths[i2] : defaultWidth);
|
|
484
|
+
const parts = [];
|
|
485
|
+
let i = 0;
|
|
486
|
+
while (i < numGlyphs) {
|
|
487
|
+
if (allWidths[i] === defaultWidth) {
|
|
488
|
+
i++;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
let j = i;
|
|
492
|
+
while (j < numGlyphs && allWidths[j] !== defaultWidth) j++;
|
|
493
|
+
const ws = allWidths.slice(i, j).join(" ");
|
|
494
|
+
parts.push(`${i} [${ws}]`);
|
|
495
|
+
i = j;
|
|
496
|
+
}
|
|
497
|
+
return parts.join(" ");
|
|
498
|
+
}
|
|
499
|
+
function sanitizeFontName(name) {
|
|
500
|
+
return name.replace(/[^A-Za-z0-9-]/g, "");
|
|
501
|
+
}
|
|
502
|
+
function generateEsmModule(fontName, parsed, ttfBase64) {
|
|
503
|
+
const { metrics, cmap, widths, gsub, ligatures, markAnchors } = parsed;
|
|
504
|
+
const cmapEntries = Object.entries(cmap).map(([k, v]) => `${k}:${v}`).join(",");
|
|
505
|
+
const defaultW = metrics.defaultWidth;
|
|
506
|
+
const widthEntries = Object.entries(widths).filter(([, w]) => w !== defaultW).map(([k, v]) => `${k}:${v}`).join(",");
|
|
507
|
+
const gsubEntries = Object.entries(gsub || {}).map(([k, v]) => `${k}:${v}`).join(",");
|
|
508
|
+
const ligaturesEntries = Object.entries(ligatures || {}).map(([gid, ligs]) => `${gid}:[${ligs.map((lig) => `[${lig.join(",")}]`).join(",")}]`).join(",");
|
|
509
|
+
const marksEntries = Object.entries(markAnchors.marks || {}).map(([gid, a]) => `${gid}:[${a.classIdx},${a.x},${a.y}]`).join(",");
|
|
510
|
+
const basesEntries = Object.entries(markAnchors.bases || {}).map(([gid, anchors]) => `${gid}:{${Object.entries(anchors).map(([mc, a]) => `${mc}:[${a.x},${a.y}]`).join(",")}}`).join(",");
|
|
511
|
+
const m2m = markAnchors.mark2mark || { mark1Anchors: {}, mark2Classes: {} };
|
|
512
|
+
const m2mMark1Entries = Object.entries(m2m.mark1Anchors).map(([gid, anchors]) => `${gid}:{${Object.entries(anchors).map(([mc, a]) => `${mc}:[${a.x},${a.y}]`).join(",")}}`).join(",");
|
|
513
|
+
const m2mMark2Entries = Object.entries(m2m.mark2Classes).map(([gid, a]) => `${gid}:[${a.classIdx},${a.x},${a.y}]`).join(",");
|
|
514
|
+
const wArray = buildPDFWidthArray(widths, metrics.numGlyphs, defaultW);
|
|
515
|
+
return `/**
|
|
516
|
+
* PRE-BUILT FONT DATA \u2014 ${fontName}
|
|
517
|
+
* ===================================
|
|
518
|
+
* Generated by: scripts/build-font-data.cjs
|
|
519
|
+
* Source: ${fontName}.ttf
|
|
520
|
+
* License: SIL Open Font License 1.1
|
|
521
|
+
*
|
|
522
|
+
* DO NOT EDIT \u2014 Regenerate with:
|
|
523
|
+
* node scripts/build-font-data.cjs assets/fonts/${fontName}.ttf assets/fonts/${fontName.toLowerCase().replace(/[^a-z0-9]/g, "-")}-data.js
|
|
524
|
+
*/
|
|
525
|
+
|
|
526
|
+
// Font metrics
|
|
527
|
+
export const metrics = ${JSON.stringify(metrics)};
|
|
528
|
+
|
|
529
|
+
// Font name for PDF /BaseFont
|
|
530
|
+
export const fontName = '${fontName.replace(/[^A-Za-z0-9-]/g, "")}';
|
|
531
|
+
|
|
532
|
+
// Unicode codepoint \u2192 Glyph ID mapping (sparse object, ~O(1) lookup)
|
|
533
|
+
export const cmap = {${cmapEntries}};
|
|
534
|
+
|
|
535
|
+
// Glyph ID \u2192 Advance Width (only non-default widths; default = ${defaultW})
|
|
536
|
+
export const defaultWidth = ${defaultW};
|
|
537
|
+
export const widths = {${widthEntries}};
|
|
538
|
+
|
|
539
|
+
// GSUB SingleSubst: fromGid \u2192 substituteGid
|
|
540
|
+
// Used by the Thai mini-shaper to select below-clash variants of consonants.
|
|
541
|
+
export const gsub = {${gsubEntries}};
|
|
542
|
+
|
|
543
|
+
// GSUB LigatureSubst: firstGid \u2192 [[resultGid, comp1, comp2, ...], ...]
|
|
544
|
+
// Used by Indic shapers for conjunct formation (C + Halant + C \u2192 ligature).
|
|
545
|
+
// Entries sorted longest-first for greedy matching.
|
|
546
|
+
export const ligatures = {${ligaturesEntries}};
|
|
547
|
+
|
|
548
|
+
// GPOS MarkToBase anchors \u2014 used by the Thai mini-shaper for mark positioning.
|
|
549
|
+
// marks[gid] = [classIdx, anchorX, anchorY] (design units)
|
|
550
|
+
// bases[gid] = { classIdx: [anchorX, anchorY] }
|
|
551
|
+
export const markAnchors = {
|
|
552
|
+
marks: {${marksEntries}},
|
|
553
|
+
bases: {${basesEntries}}
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
// GPOS MarkToMark anchors \u2014 used for Thai vowel+tone stacking.
|
|
557
|
+
// mark1Anchors[mark1Gid] = { classIdx: [anchorX, anchorY] } (base mark, e.g. above vowel)
|
|
558
|
+
// mark2Classes[mark2Gid] = [classIdx, anchorX, anchorY] (combining mark, e.g. tone)
|
|
559
|
+
export const mark2mark = {
|
|
560
|
+
mark1Anchors: {${m2mMark1Entries}},
|
|
561
|
+
mark2Classes: {${m2mMark2Entries}}
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
// PDF /W array string (pre-formatted for CIDFont object)
|
|
565
|
+
export const pdfWidthArray = '${wArray}';
|
|
566
|
+
|
|
567
|
+
// Raw TTF binary as base64 (for PDF FontFile2 embedding)
|
|
568
|
+
export const ttfBase64 = '${ttfBase64}';
|
|
569
|
+
|
|
570
|
+
// Utility: get glyph width
|
|
571
|
+
export function getGlyphWidth(glyphId) {
|
|
572
|
+
return widths[glyphId] !== undefined ? widths[glyphId] : ${defaultW};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Utility: get glyph ID for unicode code point
|
|
576
|
+
export function getGlyphId(codePoint) {
|
|
577
|
+
return cmap[codePoint] || 0;
|
|
578
|
+
}
|
|
579
|
+
`;
|
|
580
|
+
}
|
|
581
|
+
function generateCjsModule(fontName, parsed, ttfBase64) {
|
|
582
|
+
const esm = generateEsmModule(fontName, parsed, ttfBase64);
|
|
583
|
+
const names = [
|
|
584
|
+
"metrics",
|
|
585
|
+
"fontName",
|
|
586
|
+
"cmap",
|
|
587
|
+
"defaultWidth",
|
|
588
|
+
"widths",
|
|
589
|
+
"gsub",
|
|
590
|
+
"ligatures",
|
|
591
|
+
"markAnchors",
|
|
592
|
+
"mark2mark",
|
|
593
|
+
"pdfWidthArray",
|
|
594
|
+
"ttfBase64",
|
|
595
|
+
"getGlyphWidth",
|
|
596
|
+
"getGlyphId"
|
|
597
|
+
];
|
|
598
|
+
const body = esm.replace(/export const /g, "const ").replace(/export function /g, "function ");
|
|
599
|
+
return `${body}
|
|
600
|
+
module.exports = { ${names.join(", ")} };
|
|
601
|
+
`;
|
|
602
|
+
}
|
|
603
|
+
function parseFontData(buffer, opts = {}) {
|
|
604
|
+
const parsed = parseTTFRaw(buffer);
|
|
605
|
+
const fontName = sanitizeFontName(opts.fontName ?? parsed.name ?? "CustomFont") || "CustomFont";
|
|
606
|
+
const ttfBase64 = encodeBase64(buffer);
|
|
607
|
+
const marks = {};
|
|
608
|
+
for (const [gid, a] of Object.entries(parsed.markAnchors.marks)) {
|
|
609
|
+
marks[gid] = [a.classIdx, a.x, a.y];
|
|
610
|
+
}
|
|
611
|
+
const bases = {};
|
|
612
|
+
for (const [gid, anchors] of Object.entries(parsed.markAnchors.bases)) {
|
|
613
|
+
const inner = {};
|
|
614
|
+
for (const [mc, a] of Object.entries(anchors)) inner[mc] = [a.x, a.y];
|
|
615
|
+
bases[gid] = inner;
|
|
616
|
+
}
|
|
617
|
+
const mark1Anchors = {};
|
|
618
|
+
for (const [gid, anchors] of Object.entries(parsed.markAnchors.mark2mark.mark1Anchors)) {
|
|
619
|
+
const inner = {};
|
|
620
|
+
for (const [mc, a] of Object.entries(anchors)) inner[mc] = [a.x, a.y];
|
|
621
|
+
mark1Anchors[gid] = inner;
|
|
622
|
+
}
|
|
623
|
+
const mark2Classes = {};
|
|
624
|
+
for (const [gid, a] of Object.entries(parsed.markAnchors.mark2mark.mark2Classes)) {
|
|
625
|
+
mark2Classes[gid] = [a.classIdx, a.x, a.y];
|
|
626
|
+
}
|
|
627
|
+
const filteredWidths = {};
|
|
628
|
+
for (const [gid, w] of Object.entries(parsed.widths)) {
|
|
629
|
+
if (w !== parsed.metrics.defaultWidth) filteredWidths[gid] = w;
|
|
630
|
+
}
|
|
631
|
+
return {
|
|
632
|
+
metrics: parsed.metrics,
|
|
633
|
+
fontName,
|
|
634
|
+
cmap: parsed.cmap,
|
|
635
|
+
defaultWidth: parsed.metrics.defaultWidth,
|
|
636
|
+
widths: filteredWidths,
|
|
637
|
+
gsub: parsed.gsub,
|
|
638
|
+
ligatures: parsed.ligatures,
|
|
639
|
+
markAnchors: { marks, bases },
|
|
640
|
+
mark2mark: { mark1Anchors, mark2Classes },
|
|
641
|
+
pdfWidthArray: buildPDFWidthArray(parsed.widths, parsed.metrics.numGlyphs, parsed.metrics.defaultWidth),
|
|
642
|
+
ttfBase64
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function compileFontData(buffer, opts = {}) {
|
|
646
|
+
const parsed = parseTTFRaw(buffer);
|
|
647
|
+
const fontName = opts.fontName ?? parsed.name ?? "CustomFont";
|
|
648
|
+
const ttfBase64 = encodeBase64(buffer);
|
|
649
|
+
return opts.format === "cjs" ? generateCjsModule(fontName, parsed, ttfBase64) : generateEsmModule(fontName, parsed, ttfBase64);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
export { compileFontData, parseFontData };
|
|
653
|
+
//# sourceMappingURL=index.js.map
|
|
654
|
+
//# sourceMappingURL=index.js.map
|