pdf-parser.js 3.0.8 → 4.5.2
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 +105 -54
- package/dist/{afm-widths-Dxucrg7D.js → afm-widths-BTu0IDp2.cjs} +1517 -4
- package/dist/{afm-widths-DyDq56Ph.d.cts → afm-widths-CneRyu5l.d.cts} +1 -1
- package/dist/{afm-widths-DyDq56Ph.d.ts → afm-widths-CneRyu5l.d.ts} +1 -1
- package/dist/{afm-widths-BoeTOK2r.cjs → afm-widths-De_QMWgC.js} +1416 -51
- package/dist/afm-widths.cjs +1 -1
- package/dist/afm-widths.d.cts +1 -1
- package/dist/afm-widths.d.ts +1 -1
- package/dist/afm-widths.js +1 -1
- package/dist/annotations.cjs +104 -0
- package/dist/annotations.d.cts +9 -0
- package/dist/annotations.d.ts +9 -0
- package/dist/annotations.js +103 -0
- package/dist/attachments.cjs +65 -0
- package/dist/attachments.d.cts +8 -0
- package/dist/attachments.d.ts +8 -0
- package/dist/attachments.js +64 -0
- package/dist/builtin-encoding.cjs +221 -0
- package/dist/builtin-encoding.d.cts +8 -0
- package/dist/builtin-encoding.d.ts +8 -0
- package/dist/builtin-encoding.js +220 -0
- package/dist/bytes/flate.cjs +1 -1
- package/dist/bytes/flate.js +1 -1
- package/dist/cff.cjs +14 -0
- package/dist/cff.d.cts +4 -1
- package/dist/cff.d.ts +4 -1
- package/dist/cff.js +12 -1
- package/dist/cmap-table.cjs +76 -13
- package/dist/cmap-table.d.cts +9 -1
- package/dist/cmap-table.d.ts +9 -1
- package/dist/cmap-table.js +77 -15
- package/dist/codec.cjs +1 -1
- package/dist/codec.d.cts +134 -0
- package/dist/codec.d.ts +134 -0
- package/dist/codec.js +1 -1
- package/dist/content-read.cjs +1 -1
- package/dist/content-read.d.cts +3 -3
- package/dist/content-read.d.ts +3 -3
- package/dist/content-read.js +1 -1
- package/dist/content-write.cjs +32 -10
- package/dist/content-write.d.cts +12 -5
- package/dist/content-write.d.ts +12 -5
- package/dist/content-write.js +32 -10
- package/dist/crypto/random.cjs +9 -0
- package/dist/crypto/random.d.cts +4 -0
- package/dist/crypto/random.d.ts +4 -0
- package/dist/crypto/random.js +8 -0
- package/dist/diagnostics.d.cts +1 -1
- package/dist/diagnostics.d.ts +1 -1
- package/dist/document.cjs +12 -3
- package/dist/document.d.cts +3 -1
- package/dist/document.d.ts +3 -1
- package/dist/document.js +12 -3
- package/dist/{embedded-font-fREdbxnr.d.ts → embedded-font-B4p9a3O3.d.ts} +3 -1
- package/dist/{embedded-font-BU6Jgcof.d.cts → embedded-font-oc65VGlr.d.cts} +3 -1
- package/dist/embedded-font-write.cjs +1 -1
- package/dist/embedded-font-write.d.cts +3 -3
- package/dist/embedded-font-write.d.ts +3 -3
- package/dist/embedded-font-write.js +1 -1
- package/dist/embedded-font.cjs +46 -15
- package/dist/embedded-font.d.cts +1 -1
- package/dist/embedded-font.d.ts +1 -1
- package/dist/embedded-font.js +46 -15
- package/dist/encoding.cjs +10 -1
- package/dist/encoding.d.cts +10 -1
- package/dist/encoding.d.ts +10 -1
- package/dist/encoding.js +2 -2
- package/dist/encrypt-write.cjs +234 -0
- package/dist/encrypt-write.d.cts +31 -0
- package/dist/encrypt-write.d.ts +31 -0
- package/dist/encrypt-write.js +231 -0
- package/dist/encrypt.cjs +51 -21
- package/dist/encrypt.d.cts +19 -3
- package/dist/encrypt.d.ts +19 -3
- package/dist/encrypt.js +36 -22
- package/dist/filters.cjs +18 -14
- package/dist/filters.d.cts +1 -1
- package/dist/filters.d.ts +1 -1
- package/dist/filters.js +18 -14
- package/dist/font-read.cjs +68 -13
- package/dist/font-read.d.cts +1 -1
- package/dist/font-read.d.ts +1 -1
- package/dist/font-read.js +69 -14
- package/dist/font-registry.cjs +1 -1
- package/dist/font-registry.d.cts +4 -4
- package/dist/font-registry.d.ts +4 -4
- package/dist/font-registry.js +1 -1
- package/dist/font-substitutes.d.cts +1 -1
- package/dist/font-substitutes.d.ts +1 -1
- package/dist/font-tables.cjs +31 -0
- package/dist/font-tables.d.cts +3 -1
- package/dist/font-tables.d.ts +3 -1
- package/dist/font-tables.js +31 -2
- package/dist/fonts.d.cts +2 -2
- package/dist/fonts.d.ts +2 -2
- package/dist/form.cjs +138 -0
- package/dist/form.d.cts +9 -0
- package/dist/form.d.ts +9 -0
- package/dist/form.js +137 -0
- package/dist/gsub-table-BRTQ8EUW.d.ts +10 -0
- package/dist/gsub-table-URczLd85.d.cts +10 -0
- package/dist/gsub-table.cjs +225 -0
- package/dist/gsub-table.d.cts +2 -0
- package/dist/gsub-table.d.ts +2 -0
- package/dist/gsub-table.js +224 -0
- package/dist/image/ccitt-encode.cjs +119 -0
- package/dist/image/ccitt-encode.d.cts +9 -0
- package/dist/image/ccitt-encode.d.ts +9 -0
- package/dist/image/ccitt-encode.js +118 -0
- package/dist/image/ccitt.cjs +3 -0
- package/dist/image/ccitt.d.cts +4 -1
- package/dist/image/ccitt.d.ts +4 -1
- package/dist/image/ccitt.js +1 -1
- package/dist/image/jbig2-bitmap.d.cts +1 -1
- package/dist/image/jbig2-bitmap.d.ts +1 -1
- package/dist/image/jbig2.cjs +3 -1
- package/dist/image/jbig2.js +3 -1
- package/dist/image/jp2-boxes.d.cts +1 -1
- package/dist/image/jp2-boxes.d.ts +1 -1
- package/dist/image/jpeg2000-codestream.d.cts +3 -3
- package/dist/image/jpeg2000-codestream.d.ts +3 -3
- package/dist/image/jpeg2000-t1.d.cts +1 -1
- package/dist/image/jpeg2000-t1.d.ts +1 -1
- package/dist/image/jpeg2000.cjs +1 -0
- package/dist/image/jpeg2000.d.cts +3 -0
- package/dist/image/jpeg2000.d.ts +3 -0
- package/dist/image/jpeg2000.js +1 -0
- package/dist/image/png-decode.cjs +1 -1
- package/dist/image/png-decode.js +1 -1
- package/dist/image/png-filter.d.cts +1 -1
- package/dist/image/png-filter.d.ts +1 -1
- package/dist/images-read.cjs +18 -12
- package/dist/images-read.d.cts +2 -2
- package/dist/images-read.d.ts +2 -2
- package/dist/images-read.js +15 -9
- package/dist/index.cjs +17 -4
- package/dist/index.d.cts +7 -6
- package/dist/index.d.ts +7 -6
- package/dist/index.js +6 -5
- package/dist/interpret.cjs +178 -56
- package/dist/interpret.d.cts +27 -12
- package/dist/interpret.d.ts +27 -12
- package/dist/interpret.js +178 -56
- package/dist/layout.cjs +149 -2
- package/dist/layout.d.cts +394 -1
- package/dist/layout.d.ts +394 -1
- package/dist/layout.js +140 -4
- package/dist/lexer.d.cts +9 -9
- package/dist/lexer.d.ts +9 -9
- package/dist/math-content-write.cjs +1 -1
- package/dist/math-content-write.js +1 -1
- package/dist/math-font-write.cjs +1 -1
- package/dist/math-font-write.d.cts +1 -1
- package/dist/math-font-write.d.ts +1 -1
- package/dist/math-font-write.js +1 -1
- package/dist/math-font.cjs +2 -2
- package/dist/math-font.d.cts +1 -1
- package/dist/math-font.d.ts +1 -1
- package/dist/math-font.js +2 -2
- package/dist/{math-stretch-a_rHNRvc.d.ts → math-stretch-BbC2o4Br.d.ts} +1 -1
- package/dist/{math-stretch-E26z6byT.d.cts → math-stretch-C2JanDAz.d.cts} +1 -1
- package/dist/math-stretch.d.cts +1 -1
- package/dist/math-stretch.d.ts +1 -1
- package/dist/measure.cjs +1 -1
- package/dist/measure.d.cts +1 -1
- package/dist/measure.d.ts +1 -1
- package/dist/measure.js +1 -1
- package/dist/names.cjs +41 -0
- package/dist/names.d.cts +11 -0
- package/dist/names.d.ts +11 -0
- package/dist/names.js +40 -0
- package/dist/navigation.cjs +210 -0
- package/dist/navigation.d.cts +18 -0
- package/dist/navigation.d.ts +18 -0
- package/dist/navigation.js +207 -0
- package/dist/notes-annotation-author.cjs +5 -0
- package/dist/notes-annotation-author.d.cts +4 -0
- package/dist/notes-annotation-author.d.ts +4 -0
- package/dist/notes-annotation-author.js +4 -0
- package/dist/objects-B7HdMeWS.d.cts +59 -0
- package/dist/objects-B7HdMeWS.d.ts +59 -0
- package/dist/objects.d.cts +1 -58
- package/dist/objects.d.ts +1 -58
- package/dist/optional-content.cjs +63 -0
- package/dist/optional-content.d.cts +12 -0
- package/dist/optional-content.d.ts +12 -0
- package/dist/optional-content.js +62 -0
- package/dist/parse.cjs +1 -1
- package/dist/parse.d.cts +2 -2
- package/dist/parse.d.ts +2 -2
- package/dist/parse.js +1 -1
- package/dist/pdf-text.cjs +21 -0
- package/dist/pdf-text.d.cts +5 -0
- package/dist/pdf-text.d.ts +5 -0
- package/dist/pdf-text.js +19 -0
- package/dist/predictors.d.cts +1 -1
- package/dist/predictors.d.ts +1 -1
- package/dist/read.cjs +287 -72
- package/dist/read.d.cts +2 -4
- package/dist/read.d.ts +2 -4
- package/dist/read.js +284 -67
- package/dist/serialize.cjs +5 -1
- package/dist/serialize.d.cts +3 -2
- package/dist/serialize.d.ts +3 -2
- package/dist/serialize.js +5 -2
- package/dist/sfnt-subset.cjs +10 -3
- package/dist/sfnt-subset.d.cts +1 -1
- package/dist/sfnt-subset.d.ts +1 -1
- package/dist/sfnt-subset.js +10 -3
- package/dist/structure.cjs +184 -0
- package/dist/structure.d.cts +12 -0
- package/dist/structure.d.ts +12 -0
- package/dist/structure.js +183 -0
- package/dist/tounicode.cjs +8 -3
- package/dist/tounicode.d.cts +2 -2
- package/dist/tounicode.d.ts +2 -2
- package/dist/tounicode.js +8 -3
- package/dist/util/base64.cjs +4 -4
- package/dist/util/base64.js +4 -4
- package/dist/winansi.cjs +1 -1
- package/dist/winansi.d.cts +1 -1
- package/dist/winansi.d.ts +1 -1
- package/dist/winansi.js +1 -1
- package/dist/write.cjs +464 -21
- package/dist/write.d.cts +4 -3
- package/dist/write.d.ts +4 -3
- package/dist/write.js +464 -20
- package/dist/xmp.cjs +46 -0
- package/dist/xmp.d.cts +14 -0
- package/dist/xmp.d.ts +14 -0
- package/dist/xmp.js +45 -0
- package/dist/xref.cjs +2 -2
- package/dist/xref.d.cts +3 -3
- package/dist/xref.d.ts +3 -3
- package/dist/xref.js +2 -2
- package/package.json +46 -31
- package/dist/image/png-encode.cjs +0 -64
- package/dist/image/png-encode.d.cts +0 -8
- package/dist/image/png-encode.d.ts +0 -8
- package/dist/image/png-encode.js +0 -63
package/dist/form.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { asArray, asName, asNumber, dictGet } from "./objects.js";
|
|
2
|
+
import { decodePdfString } from "./pdf-text.js";
|
|
3
|
+
//#region src/form.ts
|
|
4
|
+
const FLAG_READ_ONLY = 1;
|
|
5
|
+
const FLAG_PUSHBUTTON = 4;
|
|
6
|
+
const FLAG_RADIO = 32768;
|
|
7
|
+
const FLAG_COMBO = 131072;
|
|
8
|
+
function readAcroForm(catalog, resolver, pageIndex, sink) {
|
|
9
|
+
const acroForm = resolver.resolveDict(dictGet(catalog, "AcroForm"));
|
|
10
|
+
if (acroForm === void 0) return [];
|
|
11
|
+
const fields = [];
|
|
12
|
+
const visited = /* @__PURE__ */ new Set();
|
|
13
|
+
for (const fieldRef of asArray(resolver.resolve(dictGet(acroForm, "Fields"))) ?? []) {
|
|
14
|
+
const fieldDict = resolver.resolveDict(fieldRef);
|
|
15
|
+
if (fieldDict !== void 0) readField(fieldDict, "", resolver, pageIndex, sink, fields, visited);
|
|
16
|
+
}
|
|
17
|
+
return fields;
|
|
18
|
+
}
|
|
19
|
+
function readField(node, parentName, resolver, pageIndex, sink, out, visited) {
|
|
20
|
+
if (visited.has(node)) {
|
|
21
|
+
sink({
|
|
22
|
+
code: "pdf/form-field-cycle",
|
|
23
|
+
severity: "warning",
|
|
24
|
+
message: "the AcroForm field tree contains a cycle; stopping descent at the repeated field"
|
|
25
|
+
});
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
visited.add(node);
|
|
29
|
+
const ownName = annotText(node, "T");
|
|
30
|
+
const name = ownName === void 0 ? parentName : parentName.length > 0 ? `${parentName}.${ownName}` : ownName;
|
|
31
|
+
const fieldObj = dictGet(node, "FT");
|
|
32
|
+
const fieldType = asName(fieldObj);
|
|
33
|
+
if (fieldType === void 0) {
|
|
34
|
+
const children = [];
|
|
35
|
+
for (const kidRef of asArray(resolver.resolve(dictGet(node, "Kids"))) ?? []) {
|
|
36
|
+
const kid = resolver.resolveDict(kidRef);
|
|
37
|
+
if (kid !== void 0) readField(kid, name, resolver, pageIndex, sink, children, visited);
|
|
38
|
+
}
|
|
39
|
+
out.push({
|
|
40
|
+
name,
|
|
41
|
+
fieldType: "group",
|
|
42
|
+
widgets: [],
|
|
43
|
+
children
|
|
44
|
+
});
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const flags = asNumber(resolver.resolve(dictGet(node, "Ff"))) ?? 0;
|
|
48
|
+
const value = fieldValue(node, resolver);
|
|
49
|
+
const options = fieldOptions(node, resolver);
|
|
50
|
+
const alias = annotText(node, "TU");
|
|
51
|
+
const widgets = [];
|
|
52
|
+
for (const kidRef of asArray(resolver.resolve(dictGet(node, "Kids"))) ?? []) {
|
|
53
|
+
const kid = resolver.resolveDict(kidRef);
|
|
54
|
+
if (kid === void 0 || dictGet(kid, "FT") !== void 0) continue;
|
|
55
|
+
const widget = widgetPlacement(kid, pageIndex);
|
|
56
|
+
if (widget !== void 0) widgets.push(widget);
|
|
57
|
+
}
|
|
58
|
+
if (widgets.length === 0 && dictGet(node, "Rect") !== void 0) {
|
|
59
|
+
const widget = widgetPlacement(node, pageIndex);
|
|
60
|
+
if (widget !== void 0) widgets.push(widget);
|
|
61
|
+
}
|
|
62
|
+
out.push({
|
|
63
|
+
name,
|
|
64
|
+
fieldType: mapFieldType(fieldType, flags),
|
|
65
|
+
...valueFields(fieldType, value),
|
|
66
|
+
...options !== void 0 ? { options } : {},
|
|
67
|
+
...alias !== void 0 ? { alias } : {},
|
|
68
|
+
...(flags & FLAG_READ_ONLY) !== 0 ? { readOnly: true } : {},
|
|
69
|
+
widgets,
|
|
70
|
+
children: []
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function mapFieldType(fieldType, flags) {
|
|
74
|
+
if (fieldType === "Tx") return "text";
|
|
75
|
+
if (fieldType === "Btn") {
|
|
76
|
+
if ((flags & FLAG_PUSHBUTTON) !== 0) return "button";
|
|
77
|
+
if ((flags & FLAG_RADIO) !== 0) return "radio";
|
|
78
|
+
return "checkbox";
|
|
79
|
+
}
|
|
80
|
+
if (fieldType === "Ch") return (flags & FLAG_COMBO) !== 0 ? "combobox" : "listbox";
|
|
81
|
+
return "signature";
|
|
82
|
+
}
|
|
83
|
+
function valueFields(fieldType, value) {
|
|
84
|
+
if (value === void 0) return {};
|
|
85
|
+
if (fieldType === "Btn") return {
|
|
86
|
+
checked: value !== "Off",
|
|
87
|
+
...value !== "Off" ? { value } : {}
|
|
88
|
+
};
|
|
89
|
+
if (fieldType === "Sig") return {};
|
|
90
|
+
return { value };
|
|
91
|
+
}
|
|
92
|
+
function fieldValue(node, resolver) {
|
|
93
|
+
const value = resolver.resolve(dictGet(node, "V"));
|
|
94
|
+
if (value?.kind === "string") return decodePdfString(value.bytes);
|
|
95
|
+
if (value?.kind === "name") return value.name;
|
|
96
|
+
if (value?.kind === "number") return String(value.value);
|
|
97
|
+
}
|
|
98
|
+
function fieldOptions(node, resolver) {
|
|
99
|
+
const opts = asArray(resolver.resolve(dictGet(node, "Opt")));
|
|
100
|
+
if (opts === void 0) return;
|
|
101
|
+
const values = [];
|
|
102
|
+
for (const opt of opts) {
|
|
103
|
+
const resolved = resolver.resolve(opt);
|
|
104
|
+
if (resolved?.kind === "string") values.push(decodePdfString(resolved.bytes));
|
|
105
|
+
else if (resolved?.kind === "array") {
|
|
106
|
+
const first = resolved.items[0];
|
|
107
|
+
const exportValue = first?.kind === "string" ? decodePdfString(first.bytes) : first?.kind === "name" ? first.name : void 0;
|
|
108
|
+
if (exportValue !== void 0) values.push(exportValue);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return values.length > 0 ? values : void 0;
|
|
112
|
+
}
|
|
113
|
+
function widgetPlacement(widget, pageIndex) {
|
|
114
|
+
const pageNo = pageIndex(dictGet(widget, "P"));
|
|
115
|
+
if (pageNo === void 0) return;
|
|
116
|
+
return rectWidget(asArray(dictGet(widget, "Rect")), pageNo);
|
|
117
|
+
}
|
|
118
|
+
function rectWidget(rect, pageNo) {
|
|
119
|
+
if (rect === void 0) return;
|
|
120
|
+
const x1 = asNumber(rect[0]) ?? 0;
|
|
121
|
+
const y1 = asNumber(rect[1]) ?? 0;
|
|
122
|
+
const x2 = asNumber(rect[2]) ?? 0;
|
|
123
|
+
const y2 = asNumber(rect[3]) ?? 0;
|
|
124
|
+
return {
|
|
125
|
+
pageIndex: pageNo,
|
|
126
|
+
xPt: Math.min(x1, x2),
|
|
127
|
+
yPt: Math.min(y1, y2),
|
|
128
|
+
widthPt: Math.abs(x2 - x1),
|
|
129
|
+
heightPt: Math.abs(y2 - y1)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function annotText(dict, key) {
|
|
133
|
+
const obj = dictGet(dict, key);
|
|
134
|
+
return obj?.kind === "string" ? decodePdfString(obj.bytes) : void 0;
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
export { readAcroForm };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { t as SfntFont } from "./sfnt-V08bb5ut.js";
|
|
2
|
+
//#region src/gsub-table.d.ts
|
|
3
|
+
interface GsubShaping {
|
|
4
|
+
readonly glyphIds: number[];
|
|
5
|
+
readonly spans: number[];
|
|
6
|
+
}
|
|
7
|
+
type GsubShaper = (glyphIds: readonly number[]) => GsubShaping;
|
|
8
|
+
declare function buildGsubShaper(font: SfntFont): GsubShaper | undefined;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { GsubShaping as n, buildGsubShaper as r, GsubShaper as t };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { t as SfntFont } from "./sfnt-V08bb5ut.cjs";
|
|
2
|
+
//#region src/gsub-table.d.ts
|
|
3
|
+
interface GsubShaping {
|
|
4
|
+
readonly glyphIds: number[];
|
|
5
|
+
readonly spans: number[];
|
|
6
|
+
}
|
|
7
|
+
type GsubShaper = (glyphIds: readonly number[]) => GsubShaping;
|
|
8
|
+
declare function buildGsubShaper(font: SfntFont): GsubShaper | undefined;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { GsubShaping as n, buildGsubShaper as r, GsubShaper as t };
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_sfnt = require("./sfnt.cjs");
|
|
3
|
+
const require_ot_layout_common = require("./ot-layout-common.cjs");
|
|
4
|
+
//#region src/gsub-table.ts
|
|
5
|
+
const LIGATURE_FEATURE_TAGS = ["liga", "rlig"];
|
|
6
|
+
const GSUB_HEADER_SIZE = 10;
|
|
7
|
+
const SCRIPT_RECORD_SIZE = 6;
|
|
8
|
+
const FEATURE_RECORD_SIZE = 6;
|
|
9
|
+
const LANG_SYS_HEADER_SIZE = 6;
|
|
10
|
+
const FEATURE_HEADER_SIZE = 4;
|
|
11
|
+
const LOOKUP_HEADER_SIZE = 6;
|
|
12
|
+
const LOOKUP_TYPE_SINGLE_SUBST = 1;
|
|
13
|
+
const LOOKUP_TYPE_LIGATURE_SUBST = 4;
|
|
14
|
+
const LOOKUP_TYPE_EXTENSION_SUBST = 7;
|
|
15
|
+
const EXTENSION_SUBST_HEADER_SIZE = 8;
|
|
16
|
+
const PREFERRED_SCRIPT_TAGS = ["latn", "DFLT"];
|
|
17
|
+
function readTag(bytes, offset) {
|
|
18
|
+
return String.fromCharCode(require_sfnt.u16(bytes, offset) >> 8, require_sfnt.u16(bytes, offset) & 255, require_sfnt.u16(bytes, offset + 2) >> 8, require_sfnt.u16(bytes, offset + 2) & 255);
|
|
19
|
+
}
|
|
20
|
+
const SINGLE_SUBST_FORMAT_1_SIZE = 6;
|
|
21
|
+
function parseSingleSubstFormat1(bytes, subtableOffset) {
|
|
22
|
+
if (!require_sfnt.hasBytes(bytes, subtableOffset, SINGLE_SUBST_FORMAT_1_SIZE)) return;
|
|
23
|
+
const coverage = require_ot_layout_common.parseCoverage(bytes, subtableOffset + require_sfnt.u16(bytes, subtableOffset + 2));
|
|
24
|
+
if (coverage === void 0) return;
|
|
25
|
+
const delta = require_sfnt.i16(bytes, subtableOffset + 4);
|
|
26
|
+
return {
|
|
27
|
+
kind: "single",
|
|
28
|
+
lookup: (glyphId) => {
|
|
29
|
+
if (coverage.coverageIndex(glyphId) === void 0) return;
|
|
30
|
+
return glyphId + delta;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const SINGLE_SUBST_FORMAT_2_HEADER_SIZE = 6;
|
|
35
|
+
function parseSingleSubstFormat2(bytes, subtableOffset) {
|
|
36
|
+
if (!require_sfnt.hasBytes(bytes, subtableOffset, SINGLE_SUBST_FORMAT_2_HEADER_SIZE)) return;
|
|
37
|
+
const coverage = require_ot_layout_common.parseCoverage(bytes, subtableOffset + require_sfnt.u16(bytes, subtableOffset + 2));
|
|
38
|
+
if (coverage === void 0) return;
|
|
39
|
+
const glyphCount = require_sfnt.u16(bytes, subtableOffset + 4);
|
|
40
|
+
const substitutesOffset = subtableOffset + SINGLE_SUBST_FORMAT_2_HEADER_SIZE;
|
|
41
|
+
if (!require_sfnt.hasBytes(bytes, substitutesOffset, glyphCount * 2)) return;
|
|
42
|
+
return {
|
|
43
|
+
kind: "single",
|
|
44
|
+
lookup: (glyphId) => {
|
|
45
|
+
const coverageIndex = coverage.coverageIndex(glyphId);
|
|
46
|
+
if (coverageIndex === void 0 || coverageIndex >= glyphCount) return;
|
|
47
|
+
return require_sfnt.u16(bytes, substitutesOffset + coverageIndex * 2);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const LIGATURE_SUBST_FORMAT_1_HEADER_SIZE = 6;
|
|
52
|
+
const LIGATURE_SET_HEADER_SIZE = 2;
|
|
53
|
+
const LIGATURE_HEADER_PREFIX_SIZE = 4;
|
|
54
|
+
function parseLigatureSubstFormat1(bytes, subtableOffset) {
|
|
55
|
+
if (!require_sfnt.hasBytes(bytes, subtableOffset, LIGATURE_SUBST_FORMAT_1_HEADER_SIZE)) return;
|
|
56
|
+
const coverage = require_ot_layout_common.parseCoverage(bytes, subtableOffset + require_sfnt.u16(bytes, subtableOffset + 2));
|
|
57
|
+
if (coverage === void 0) return;
|
|
58
|
+
const ligSetCount = require_sfnt.u16(bytes, subtableOffset + 4);
|
|
59
|
+
const ligSetOffsetsOffset = subtableOffset + LIGATURE_SUBST_FORMAT_1_HEADER_SIZE;
|
|
60
|
+
if (!require_sfnt.hasBytes(bytes, ligSetOffsetsOffset, ligSetCount * 2)) return;
|
|
61
|
+
return {
|
|
62
|
+
kind: "ligature",
|
|
63
|
+
match: (glyphIds, index) => {
|
|
64
|
+
const coverageIndex = coverage.coverageIndex(glyphIds[index]);
|
|
65
|
+
if (coverageIndex === void 0 || coverageIndex >= ligSetCount) return;
|
|
66
|
+
const ligSetOffset = subtableOffset + require_sfnt.u16(bytes, ligSetOffsetsOffset + coverageIndex * 2);
|
|
67
|
+
if (!require_sfnt.hasBytes(bytes, ligSetOffset, LIGATURE_SET_HEADER_SIZE)) return;
|
|
68
|
+
const ligatureCount = require_sfnt.u16(bytes, ligSetOffset);
|
|
69
|
+
const ligatureOffsetsOffset = ligSetOffset + LIGATURE_SET_HEADER_SIZE;
|
|
70
|
+
if (!require_sfnt.hasBytes(bytes, ligatureOffsetsOffset, ligatureCount * 2)) return;
|
|
71
|
+
for (let i = 0; i < ligatureCount; i++) {
|
|
72
|
+
const ligatureOffset = ligSetOffset + require_sfnt.u16(bytes, ligatureOffsetsOffset + i * 2);
|
|
73
|
+
if (!require_sfnt.hasBytes(bytes, ligatureOffset, LIGATURE_HEADER_PREFIX_SIZE)) continue;
|
|
74
|
+
const ligatureGlyph = require_sfnt.u16(bytes, ligatureOffset);
|
|
75
|
+
const componentCount = require_sfnt.u16(bytes, ligatureOffset + 2);
|
|
76
|
+
if (componentCount === 0) continue;
|
|
77
|
+
const componentsOffset = ligatureOffset + LIGATURE_HEADER_PREFIX_SIZE;
|
|
78
|
+
if (!require_sfnt.hasBytes(bytes, componentsOffset, (componentCount - 1) * 2)) continue;
|
|
79
|
+
if (index + componentCount > glyphIds.length) continue;
|
|
80
|
+
let matches = true;
|
|
81
|
+
for (let c = 1; c < componentCount; c++) if (glyphIds[index + c] !== require_sfnt.u16(bytes, componentsOffset + (c - 1) * 2)) {
|
|
82
|
+
matches = false;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
if (matches) return {
|
|
86
|
+
ligatureGlyph,
|
|
87
|
+
componentCount
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function parseSubtable(bytes, lookupType, subtableOffset) {
|
|
94
|
+
if (lookupType === LOOKUP_TYPE_EXTENSION_SUBST) {
|
|
95
|
+
if (!require_sfnt.hasBytes(bytes, subtableOffset, EXTENSION_SUBST_HEADER_SIZE) || require_sfnt.u16(bytes, subtableOffset) !== 1) return;
|
|
96
|
+
const extensionLookupType = require_sfnt.u16(bytes, subtableOffset + 2);
|
|
97
|
+
if (extensionLookupType === LOOKUP_TYPE_EXTENSION_SUBST) return;
|
|
98
|
+
return parseSubtable(bytes, extensionLookupType, subtableOffset + require_sfnt.u32(bytes, subtableOffset + 4));
|
|
99
|
+
}
|
|
100
|
+
if (lookupType === LOOKUP_TYPE_SINGLE_SUBST) {
|
|
101
|
+
if (!require_sfnt.hasBytes(bytes, subtableOffset, 2)) return;
|
|
102
|
+
const substFormat = require_sfnt.u16(bytes, subtableOffset);
|
|
103
|
+
if (substFormat === 1) return parseSingleSubstFormat1(bytes, subtableOffset);
|
|
104
|
+
if (substFormat === 2) return parseSingleSubstFormat2(bytes, subtableOffset);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (lookupType === LOOKUP_TYPE_LIGATURE_SUBST) {
|
|
108
|
+
if (!require_sfnt.hasBytes(bytes, subtableOffset, 2)) return;
|
|
109
|
+
if (require_sfnt.u16(bytes, subtableOffset) !== 1) return;
|
|
110
|
+
return parseLigatureSubstFormat1(bytes, subtableOffset);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function findScriptOffset(bytes, scriptListOffset) {
|
|
114
|
+
if (!require_sfnt.hasBytes(bytes, scriptListOffset, 2)) return;
|
|
115
|
+
const scriptCount = require_sfnt.u16(bytes, scriptListOffset);
|
|
116
|
+
const recordsOffset = scriptListOffset + 2;
|
|
117
|
+
if (!require_sfnt.hasBytes(bytes, recordsOffset, scriptCount * SCRIPT_RECORD_SIZE) || scriptCount === 0) return;
|
|
118
|
+
for (const wanted of PREFERRED_SCRIPT_TAGS) for (let i = 0; i < scriptCount; i++) {
|
|
119
|
+
const recordOffset = recordsOffset + i * SCRIPT_RECORD_SIZE;
|
|
120
|
+
if (readTag(bytes, recordOffset) === wanted) return scriptListOffset + require_sfnt.u16(bytes, recordOffset + 4);
|
|
121
|
+
}
|
|
122
|
+
return scriptListOffset + require_sfnt.u16(bytes, recordsOffset + 4);
|
|
123
|
+
}
|
|
124
|
+
function parseDefaultLangSysFeatureIndices(bytes, scriptOffset) {
|
|
125
|
+
if (!require_sfnt.hasBytes(bytes, scriptOffset, 2)) return [];
|
|
126
|
+
const defaultLangSysOffset = require_sfnt.u16(bytes, scriptOffset);
|
|
127
|
+
if (defaultLangSysOffset === 0) return [];
|
|
128
|
+
const langSysOffset = scriptOffset + defaultLangSysOffset;
|
|
129
|
+
if (!require_sfnt.hasBytes(bytes, langSysOffset, LANG_SYS_HEADER_SIZE)) return [];
|
|
130
|
+
const featureIndexCount = require_sfnt.u16(bytes, langSysOffset + 4);
|
|
131
|
+
const indicesOffset = langSysOffset + LANG_SYS_HEADER_SIZE;
|
|
132
|
+
if (!require_sfnt.hasBytes(bytes, indicesOffset, featureIndexCount * 2)) return [];
|
|
133
|
+
const indices = [];
|
|
134
|
+
for (let i = 0; i < featureIndexCount; i++) indices.push(require_sfnt.u16(bytes, indicesOffset + i * 2));
|
|
135
|
+
return indices;
|
|
136
|
+
}
|
|
137
|
+
function collectLigatureLookupIndices(bytes, featureListOffset, featureIndices) {
|
|
138
|
+
if (!require_sfnt.hasBytes(bytes, featureListOffset, 2)) return [];
|
|
139
|
+
const featureCount = require_sfnt.u16(bytes, featureListOffset);
|
|
140
|
+
const recordsOffset = featureListOffset + 2;
|
|
141
|
+
if (!require_sfnt.hasBytes(bytes, recordsOffset, featureCount * FEATURE_RECORD_SIZE)) return [];
|
|
142
|
+
const lookupIndices = [];
|
|
143
|
+
const seen = /* @__PURE__ */ new Set();
|
|
144
|
+
for (const featureIndex of featureIndices) {
|
|
145
|
+
if (featureIndex >= featureCount) continue;
|
|
146
|
+
const recordOffset = recordsOffset + featureIndex * FEATURE_RECORD_SIZE;
|
|
147
|
+
const tag = readTag(bytes, recordOffset);
|
|
148
|
+
if (!LIGATURE_FEATURE_TAGS.includes(tag)) continue;
|
|
149
|
+
const featureOffset = featureListOffset + require_sfnt.u16(bytes, recordOffset + 4);
|
|
150
|
+
if (!require_sfnt.hasBytes(bytes, featureOffset, FEATURE_HEADER_SIZE)) continue;
|
|
151
|
+
const lookupIndexCount = require_sfnt.u16(bytes, featureOffset + 2);
|
|
152
|
+
const indicesOffset = featureOffset + FEATURE_HEADER_SIZE;
|
|
153
|
+
if (!require_sfnt.hasBytes(bytes, indicesOffset, lookupIndexCount * 2)) continue;
|
|
154
|
+
for (let i = 0; i < lookupIndexCount; i++) {
|
|
155
|
+
const lookupIndex = require_sfnt.u16(bytes, indicesOffset + i * 2);
|
|
156
|
+
if (!seen.has(lookupIndex)) {
|
|
157
|
+
seen.add(lookupIndex);
|
|
158
|
+
lookupIndices.push(lookupIndex);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return lookupIndices;
|
|
163
|
+
}
|
|
164
|
+
function buildGsubShaper(font) {
|
|
165
|
+
const bytes = require_sfnt.sfntTableBytes(font, "GSUB");
|
|
166
|
+
if (bytes === void 0 || !require_sfnt.hasBytes(bytes, 0, GSUB_HEADER_SIZE) || require_sfnt.u16(bytes, 0) !== 1) return;
|
|
167
|
+
const scriptOffset = findScriptOffset(bytes, require_sfnt.u16(bytes, 4));
|
|
168
|
+
if (scriptOffset === void 0) return;
|
|
169
|
+
const lookupIndices = collectLigatureLookupIndices(bytes, require_sfnt.u16(bytes, 6), parseDefaultLangSysFeatureIndices(bytes, scriptOffset));
|
|
170
|
+
if (lookupIndices.length === 0) return;
|
|
171
|
+
const lookupListOffset = require_sfnt.u16(bytes, 8);
|
|
172
|
+
if (!require_sfnt.hasBytes(bytes, lookupListOffset, 2)) return;
|
|
173
|
+
const lookupCount = require_sfnt.u16(bytes, lookupListOffset);
|
|
174
|
+
const lookupOffsetsOffset = lookupListOffset + 2;
|
|
175
|
+
if (!require_sfnt.hasBytes(bytes, lookupOffsetsOffset, lookupCount * 2)) return;
|
|
176
|
+
const subtables = [];
|
|
177
|
+
for (const lookupIndex of lookupIndices) {
|
|
178
|
+
if (lookupIndex >= lookupCount) continue;
|
|
179
|
+
const lookupOffset = lookupListOffset + require_sfnt.u16(bytes, lookupOffsetsOffset + lookupIndex * 2);
|
|
180
|
+
if (!require_sfnt.hasBytes(bytes, lookupOffset, LOOKUP_HEADER_SIZE)) continue;
|
|
181
|
+
if (require_sfnt.u16(bytes, lookupOffset + 2) !== 0) continue;
|
|
182
|
+
const lookupType = require_sfnt.u16(bytes, lookupOffset);
|
|
183
|
+
const subTableCount = require_sfnt.u16(bytes, lookupOffset + 4);
|
|
184
|
+
const subtableOffsetsOffset = lookupOffset + LOOKUP_HEADER_SIZE;
|
|
185
|
+
if (!require_sfnt.hasBytes(bytes, subtableOffsetsOffset, subTableCount * 2)) continue;
|
|
186
|
+
for (let i = 0; i < subTableCount; i++) {
|
|
187
|
+
const subtable = parseSubtable(bytes, lookupType, lookupOffset + require_sfnt.u16(bytes, subtableOffsetsOffset + i * 2));
|
|
188
|
+
if (subtable !== void 0) subtables.push(subtable);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (subtables.length === 0) return;
|
|
192
|
+
return (glyphIds) => {
|
|
193
|
+
const shaped = [];
|
|
194
|
+
const spans = [];
|
|
195
|
+
let index = 0;
|
|
196
|
+
outer: while (index < glyphIds.length) {
|
|
197
|
+
for (const subtable of subtables) if (subtable.kind === "single") {
|
|
198
|
+
const substitute = subtable.lookup(glyphIds[index]);
|
|
199
|
+
if (substitute !== void 0) {
|
|
200
|
+
shaped.push(substitute);
|
|
201
|
+
spans.push(1);
|
|
202
|
+
index += 1;
|
|
203
|
+
continue outer;
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
const match = subtable.match(glyphIds, index);
|
|
207
|
+
if (match !== void 0) {
|
|
208
|
+
shaped.push(match.ligatureGlyph);
|
|
209
|
+
spans.push(match.componentCount);
|
|
210
|
+
index += match.componentCount;
|
|
211
|
+
continue outer;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
shaped.push(glyphIds[index]);
|
|
215
|
+
spans.push(1);
|
|
216
|
+
index += 1;
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
glyphIds: shaped,
|
|
220
|
+
spans
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
exports.buildGsubShaper = buildGsubShaper;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { hasBytes, i16, sfntTableBytes, u16, u32 } from "./sfnt.js";
|
|
2
|
+
import { parseCoverage } from "./ot-layout-common.js";
|
|
3
|
+
//#region src/gsub-table.ts
|
|
4
|
+
const LIGATURE_FEATURE_TAGS = ["liga", "rlig"];
|
|
5
|
+
const GSUB_HEADER_SIZE = 10;
|
|
6
|
+
const SCRIPT_RECORD_SIZE = 6;
|
|
7
|
+
const FEATURE_RECORD_SIZE = 6;
|
|
8
|
+
const LANG_SYS_HEADER_SIZE = 6;
|
|
9
|
+
const FEATURE_HEADER_SIZE = 4;
|
|
10
|
+
const LOOKUP_HEADER_SIZE = 6;
|
|
11
|
+
const LOOKUP_TYPE_SINGLE_SUBST = 1;
|
|
12
|
+
const LOOKUP_TYPE_LIGATURE_SUBST = 4;
|
|
13
|
+
const LOOKUP_TYPE_EXTENSION_SUBST = 7;
|
|
14
|
+
const EXTENSION_SUBST_HEADER_SIZE = 8;
|
|
15
|
+
const PREFERRED_SCRIPT_TAGS = ["latn", "DFLT"];
|
|
16
|
+
function readTag(bytes, offset) {
|
|
17
|
+
return String.fromCharCode(u16(bytes, offset) >> 8, u16(bytes, offset) & 255, u16(bytes, offset + 2) >> 8, u16(bytes, offset + 2) & 255);
|
|
18
|
+
}
|
|
19
|
+
const SINGLE_SUBST_FORMAT_1_SIZE = 6;
|
|
20
|
+
function parseSingleSubstFormat1(bytes, subtableOffset) {
|
|
21
|
+
if (!hasBytes(bytes, subtableOffset, SINGLE_SUBST_FORMAT_1_SIZE)) return;
|
|
22
|
+
const coverage = parseCoverage(bytes, subtableOffset + u16(bytes, subtableOffset + 2));
|
|
23
|
+
if (coverage === void 0) return;
|
|
24
|
+
const delta = i16(bytes, subtableOffset + 4);
|
|
25
|
+
return {
|
|
26
|
+
kind: "single",
|
|
27
|
+
lookup: (glyphId) => {
|
|
28
|
+
if (coverage.coverageIndex(glyphId) === void 0) return;
|
|
29
|
+
return glyphId + delta;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const SINGLE_SUBST_FORMAT_2_HEADER_SIZE = 6;
|
|
34
|
+
function parseSingleSubstFormat2(bytes, subtableOffset) {
|
|
35
|
+
if (!hasBytes(bytes, subtableOffset, SINGLE_SUBST_FORMAT_2_HEADER_SIZE)) return;
|
|
36
|
+
const coverage = parseCoverage(bytes, subtableOffset + u16(bytes, subtableOffset + 2));
|
|
37
|
+
if (coverage === void 0) return;
|
|
38
|
+
const glyphCount = u16(bytes, subtableOffset + 4);
|
|
39
|
+
const substitutesOffset = subtableOffset + SINGLE_SUBST_FORMAT_2_HEADER_SIZE;
|
|
40
|
+
if (!hasBytes(bytes, substitutesOffset, glyphCount * 2)) return;
|
|
41
|
+
return {
|
|
42
|
+
kind: "single",
|
|
43
|
+
lookup: (glyphId) => {
|
|
44
|
+
const coverageIndex = coverage.coverageIndex(glyphId);
|
|
45
|
+
if (coverageIndex === void 0 || coverageIndex >= glyphCount) return;
|
|
46
|
+
return u16(bytes, substitutesOffset + coverageIndex * 2);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const LIGATURE_SUBST_FORMAT_1_HEADER_SIZE = 6;
|
|
51
|
+
const LIGATURE_SET_HEADER_SIZE = 2;
|
|
52
|
+
const LIGATURE_HEADER_PREFIX_SIZE = 4;
|
|
53
|
+
function parseLigatureSubstFormat1(bytes, subtableOffset) {
|
|
54
|
+
if (!hasBytes(bytes, subtableOffset, LIGATURE_SUBST_FORMAT_1_HEADER_SIZE)) return;
|
|
55
|
+
const coverage = parseCoverage(bytes, subtableOffset + u16(bytes, subtableOffset + 2));
|
|
56
|
+
if (coverage === void 0) return;
|
|
57
|
+
const ligSetCount = u16(bytes, subtableOffset + 4);
|
|
58
|
+
const ligSetOffsetsOffset = subtableOffset + LIGATURE_SUBST_FORMAT_1_HEADER_SIZE;
|
|
59
|
+
if (!hasBytes(bytes, ligSetOffsetsOffset, ligSetCount * 2)) return;
|
|
60
|
+
return {
|
|
61
|
+
kind: "ligature",
|
|
62
|
+
match: (glyphIds, index) => {
|
|
63
|
+
const coverageIndex = coverage.coverageIndex(glyphIds[index]);
|
|
64
|
+
if (coverageIndex === void 0 || coverageIndex >= ligSetCount) return;
|
|
65
|
+
const ligSetOffset = subtableOffset + u16(bytes, ligSetOffsetsOffset + coverageIndex * 2);
|
|
66
|
+
if (!hasBytes(bytes, ligSetOffset, LIGATURE_SET_HEADER_SIZE)) return;
|
|
67
|
+
const ligatureCount = u16(bytes, ligSetOffset);
|
|
68
|
+
const ligatureOffsetsOffset = ligSetOffset + LIGATURE_SET_HEADER_SIZE;
|
|
69
|
+
if (!hasBytes(bytes, ligatureOffsetsOffset, ligatureCount * 2)) return;
|
|
70
|
+
for (let i = 0; i < ligatureCount; i++) {
|
|
71
|
+
const ligatureOffset = ligSetOffset + u16(bytes, ligatureOffsetsOffset + i * 2);
|
|
72
|
+
if (!hasBytes(bytes, ligatureOffset, LIGATURE_HEADER_PREFIX_SIZE)) continue;
|
|
73
|
+
const ligatureGlyph = u16(bytes, ligatureOffset);
|
|
74
|
+
const componentCount = u16(bytes, ligatureOffset + 2);
|
|
75
|
+
if (componentCount === 0) continue;
|
|
76
|
+
const componentsOffset = ligatureOffset + LIGATURE_HEADER_PREFIX_SIZE;
|
|
77
|
+
if (!hasBytes(bytes, componentsOffset, (componentCount - 1) * 2)) continue;
|
|
78
|
+
if (index + componentCount > glyphIds.length) continue;
|
|
79
|
+
let matches = true;
|
|
80
|
+
for (let c = 1; c < componentCount; c++) if (glyphIds[index + c] !== u16(bytes, componentsOffset + (c - 1) * 2)) {
|
|
81
|
+
matches = false;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
if (matches) return {
|
|
85
|
+
ligatureGlyph,
|
|
86
|
+
componentCount
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function parseSubtable(bytes, lookupType, subtableOffset) {
|
|
93
|
+
if (lookupType === LOOKUP_TYPE_EXTENSION_SUBST) {
|
|
94
|
+
if (!hasBytes(bytes, subtableOffset, EXTENSION_SUBST_HEADER_SIZE) || u16(bytes, subtableOffset) !== 1) return;
|
|
95
|
+
const extensionLookupType = u16(bytes, subtableOffset + 2);
|
|
96
|
+
if (extensionLookupType === LOOKUP_TYPE_EXTENSION_SUBST) return;
|
|
97
|
+
return parseSubtable(bytes, extensionLookupType, subtableOffset + u32(bytes, subtableOffset + 4));
|
|
98
|
+
}
|
|
99
|
+
if (lookupType === LOOKUP_TYPE_SINGLE_SUBST) {
|
|
100
|
+
if (!hasBytes(bytes, subtableOffset, 2)) return;
|
|
101
|
+
const substFormat = u16(bytes, subtableOffset);
|
|
102
|
+
if (substFormat === 1) return parseSingleSubstFormat1(bytes, subtableOffset);
|
|
103
|
+
if (substFormat === 2) return parseSingleSubstFormat2(bytes, subtableOffset);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (lookupType === LOOKUP_TYPE_LIGATURE_SUBST) {
|
|
107
|
+
if (!hasBytes(bytes, subtableOffset, 2)) return;
|
|
108
|
+
if (u16(bytes, subtableOffset) !== 1) return;
|
|
109
|
+
return parseLigatureSubstFormat1(bytes, subtableOffset);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function findScriptOffset(bytes, scriptListOffset) {
|
|
113
|
+
if (!hasBytes(bytes, scriptListOffset, 2)) return;
|
|
114
|
+
const scriptCount = u16(bytes, scriptListOffset);
|
|
115
|
+
const recordsOffset = scriptListOffset + 2;
|
|
116
|
+
if (!hasBytes(bytes, recordsOffset, scriptCount * SCRIPT_RECORD_SIZE) || scriptCount === 0) return;
|
|
117
|
+
for (const wanted of PREFERRED_SCRIPT_TAGS) for (let i = 0; i < scriptCount; i++) {
|
|
118
|
+
const recordOffset = recordsOffset + i * SCRIPT_RECORD_SIZE;
|
|
119
|
+
if (readTag(bytes, recordOffset) === wanted) return scriptListOffset + u16(bytes, recordOffset + 4);
|
|
120
|
+
}
|
|
121
|
+
return scriptListOffset + u16(bytes, recordsOffset + 4);
|
|
122
|
+
}
|
|
123
|
+
function parseDefaultLangSysFeatureIndices(bytes, scriptOffset) {
|
|
124
|
+
if (!hasBytes(bytes, scriptOffset, 2)) return [];
|
|
125
|
+
const defaultLangSysOffset = u16(bytes, scriptOffset);
|
|
126
|
+
if (defaultLangSysOffset === 0) return [];
|
|
127
|
+
const langSysOffset = scriptOffset + defaultLangSysOffset;
|
|
128
|
+
if (!hasBytes(bytes, langSysOffset, LANG_SYS_HEADER_SIZE)) return [];
|
|
129
|
+
const featureIndexCount = u16(bytes, langSysOffset + 4);
|
|
130
|
+
const indicesOffset = langSysOffset + LANG_SYS_HEADER_SIZE;
|
|
131
|
+
if (!hasBytes(bytes, indicesOffset, featureIndexCount * 2)) return [];
|
|
132
|
+
const indices = [];
|
|
133
|
+
for (let i = 0; i < featureIndexCount; i++) indices.push(u16(bytes, indicesOffset + i * 2));
|
|
134
|
+
return indices;
|
|
135
|
+
}
|
|
136
|
+
function collectLigatureLookupIndices(bytes, featureListOffset, featureIndices) {
|
|
137
|
+
if (!hasBytes(bytes, featureListOffset, 2)) return [];
|
|
138
|
+
const featureCount = u16(bytes, featureListOffset);
|
|
139
|
+
const recordsOffset = featureListOffset + 2;
|
|
140
|
+
if (!hasBytes(bytes, recordsOffset, featureCount * FEATURE_RECORD_SIZE)) return [];
|
|
141
|
+
const lookupIndices = [];
|
|
142
|
+
const seen = /* @__PURE__ */ new Set();
|
|
143
|
+
for (const featureIndex of featureIndices) {
|
|
144
|
+
if (featureIndex >= featureCount) continue;
|
|
145
|
+
const recordOffset = recordsOffset + featureIndex * FEATURE_RECORD_SIZE;
|
|
146
|
+
const tag = readTag(bytes, recordOffset);
|
|
147
|
+
if (!LIGATURE_FEATURE_TAGS.includes(tag)) continue;
|
|
148
|
+
const featureOffset = featureListOffset + u16(bytes, recordOffset + 4);
|
|
149
|
+
if (!hasBytes(bytes, featureOffset, FEATURE_HEADER_SIZE)) continue;
|
|
150
|
+
const lookupIndexCount = u16(bytes, featureOffset + 2);
|
|
151
|
+
const indicesOffset = featureOffset + FEATURE_HEADER_SIZE;
|
|
152
|
+
if (!hasBytes(bytes, indicesOffset, lookupIndexCount * 2)) continue;
|
|
153
|
+
for (let i = 0; i < lookupIndexCount; i++) {
|
|
154
|
+
const lookupIndex = u16(bytes, indicesOffset + i * 2);
|
|
155
|
+
if (!seen.has(lookupIndex)) {
|
|
156
|
+
seen.add(lookupIndex);
|
|
157
|
+
lookupIndices.push(lookupIndex);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return lookupIndices;
|
|
162
|
+
}
|
|
163
|
+
function buildGsubShaper(font) {
|
|
164
|
+
const bytes = sfntTableBytes(font, "GSUB");
|
|
165
|
+
if (bytes === void 0 || !hasBytes(bytes, 0, GSUB_HEADER_SIZE) || u16(bytes, 0) !== 1) return;
|
|
166
|
+
const scriptOffset = findScriptOffset(bytes, u16(bytes, 4));
|
|
167
|
+
if (scriptOffset === void 0) return;
|
|
168
|
+
const lookupIndices = collectLigatureLookupIndices(bytes, u16(bytes, 6), parseDefaultLangSysFeatureIndices(bytes, scriptOffset));
|
|
169
|
+
if (lookupIndices.length === 0) return;
|
|
170
|
+
const lookupListOffset = u16(bytes, 8);
|
|
171
|
+
if (!hasBytes(bytes, lookupListOffset, 2)) return;
|
|
172
|
+
const lookupCount = u16(bytes, lookupListOffset);
|
|
173
|
+
const lookupOffsetsOffset = lookupListOffset + 2;
|
|
174
|
+
if (!hasBytes(bytes, lookupOffsetsOffset, lookupCount * 2)) return;
|
|
175
|
+
const subtables = [];
|
|
176
|
+
for (const lookupIndex of lookupIndices) {
|
|
177
|
+
if (lookupIndex >= lookupCount) continue;
|
|
178
|
+
const lookupOffset = lookupListOffset + u16(bytes, lookupOffsetsOffset + lookupIndex * 2);
|
|
179
|
+
if (!hasBytes(bytes, lookupOffset, LOOKUP_HEADER_SIZE)) continue;
|
|
180
|
+
if (u16(bytes, lookupOffset + 2) !== 0) continue;
|
|
181
|
+
const lookupType = u16(bytes, lookupOffset);
|
|
182
|
+
const subTableCount = u16(bytes, lookupOffset + 4);
|
|
183
|
+
const subtableOffsetsOffset = lookupOffset + LOOKUP_HEADER_SIZE;
|
|
184
|
+
if (!hasBytes(bytes, subtableOffsetsOffset, subTableCount * 2)) continue;
|
|
185
|
+
for (let i = 0; i < subTableCount; i++) {
|
|
186
|
+
const subtable = parseSubtable(bytes, lookupType, lookupOffset + u16(bytes, subtableOffsetsOffset + i * 2));
|
|
187
|
+
if (subtable !== void 0) subtables.push(subtable);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (subtables.length === 0) return;
|
|
191
|
+
return (glyphIds) => {
|
|
192
|
+
const shaped = [];
|
|
193
|
+
const spans = [];
|
|
194
|
+
let index = 0;
|
|
195
|
+
outer: while (index < glyphIds.length) {
|
|
196
|
+
for (const subtable of subtables) if (subtable.kind === "single") {
|
|
197
|
+
const substitute = subtable.lookup(glyphIds[index]);
|
|
198
|
+
if (substitute !== void 0) {
|
|
199
|
+
shaped.push(substitute);
|
|
200
|
+
spans.push(1);
|
|
201
|
+
index += 1;
|
|
202
|
+
continue outer;
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
const match = subtable.match(glyphIds, index);
|
|
206
|
+
if (match !== void 0) {
|
|
207
|
+
shaped.push(match.ligatureGlyph);
|
|
208
|
+
spans.push(match.componentCount);
|
|
209
|
+
index += match.componentCount;
|
|
210
|
+
continue outer;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
shaped.push(glyphIds[index]);
|
|
214
|
+
spans.push(1);
|
|
215
|
+
index += 1;
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
glyphIds: shaped,
|
|
219
|
+
spans
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
export { buildGsubShaper };
|