@cj-tech-master/excelts 8.0.0 → 8.1.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.
Files changed (106) hide show
  1. package/README.md +14 -1
  2. package/README_zh.md +6 -0
  3. package/dist/browser/modules/archive/zip/stream.d.ts +4 -0
  4. package/dist/browser/modules/archive/zip/stream.js +53 -0
  5. package/dist/browser/modules/pdf/core/crypto.d.ts +65 -0
  6. package/dist/browser/modules/pdf/core/crypto.js +637 -0
  7. package/dist/browser/modules/pdf/core/encryption.d.ts +23 -20
  8. package/dist/browser/modules/pdf/core/encryption.js +88 -261
  9. package/dist/browser/modules/pdf/core/pdf-writer.d.ts +6 -4
  10. package/dist/browser/modules/pdf/core/pdf-writer.js +19 -10
  11. package/dist/browser/modules/pdf/index.d.ts +23 -2
  12. package/dist/browser/modules/pdf/index.js +21 -3
  13. package/dist/browser/modules/pdf/reader/annotation-extractor.d.ts +63 -0
  14. package/dist/browser/modules/pdf/reader/annotation-extractor.js +155 -0
  15. package/dist/browser/modules/pdf/reader/cmap-parser.d.ts +70 -0
  16. package/dist/browser/modules/pdf/reader/cmap-parser.js +321 -0
  17. package/dist/browser/modules/pdf/reader/content-interpreter.d.ts +57 -0
  18. package/dist/browser/modules/pdf/reader/content-interpreter.js +715 -0
  19. package/dist/browser/modules/pdf/reader/font-decoder.d.ts +58 -0
  20. package/dist/browser/modules/pdf/reader/font-decoder.js +1513 -0
  21. package/dist/browser/modules/pdf/reader/form-extractor.d.ts +48 -0
  22. package/dist/browser/modules/pdf/reader/form-extractor.js +355 -0
  23. package/dist/browser/modules/pdf/reader/image-extractor.d.ts +55 -0
  24. package/dist/browser/modules/pdf/reader/image-extractor.js +220 -0
  25. package/dist/browser/modules/pdf/reader/metadata-reader.d.ts +56 -0
  26. package/dist/browser/modules/pdf/reader/metadata-reader.js +275 -0
  27. package/dist/browser/modules/pdf/reader/pdf-decrypt.d.ts +26 -0
  28. package/dist/browser/modules/pdf/reader/pdf-decrypt.js +443 -0
  29. package/dist/browser/modules/pdf/reader/pdf-document.d.ts +191 -0
  30. package/dist/browser/modules/pdf/reader/pdf-document.js +818 -0
  31. package/dist/browser/modules/pdf/reader/pdf-parser.d.ts +65 -0
  32. package/dist/browser/modules/pdf/reader/pdf-parser.js +285 -0
  33. package/dist/browser/modules/pdf/reader/pdf-reader.d.ts +143 -0
  34. package/dist/browser/modules/pdf/reader/pdf-reader.js +200 -0
  35. package/dist/browser/modules/pdf/reader/pdf-tokenizer.d.ts +101 -0
  36. package/dist/browser/modules/pdf/reader/pdf-tokenizer.js +543 -0
  37. package/dist/browser/modules/pdf/reader/reader-utils.d.ts +15 -0
  38. package/dist/browser/modules/pdf/reader/reader-utils.js +27 -0
  39. package/dist/browser/modules/pdf/reader/stream-filters.d.ts +20 -0
  40. package/dist/browser/modules/pdf/reader/stream-filters.js +456 -0
  41. package/dist/browser/modules/pdf/reader/text-reconstruction.d.ts +44 -0
  42. package/dist/browser/modules/pdf/reader/text-reconstruction.js +463 -0
  43. package/dist/cjs/modules/archive/zip/stream.js +53 -0
  44. package/dist/cjs/modules/pdf/core/crypto.js +649 -0
  45. package/dist/cjs/modules/pdf/core/encryption.js +88 -263
  46. package/dist/cjs/modules/pdf/core/pdf-writer.js +19 -10
  47. package/dist/cjs/modules/pdf/index.js +23 -4
  48. package/dist/cjs/modules/pdf/reader/annotation-extractor.js +158 -0
  49. package/dist/cjs/modules/pdf/reader/cmap-parser.js +326 -0
  50. package/dist/cjs/modules/pdf/reader/content-interpreter.js +718 -0
  51. package/dist/cjs/modules/pdf/reader/font-decoder.js +1518 -0
  52. package/dist/cjs/modules/pdf/reader/form-extractor.js +358 -0
  53. package/dist/cjs/modules/pdf/reader/image-extractor.js +223 -0
  54. package/dist/cjs/modules/pdf/reader/metadata-reader.js +278 -0
  55. package/dist/cjs/modules/pdf/reader/pdf-decrypt.js +447 -0
  56. package/dist/cjs/modules/pdf/reader/pdf-document.js +822 -0
  57. package/dist/cjs/modules/pdf/reader/pdf-parser.js +301 -0
  58. package/dist/cjs/modules/pdf/reader/pdf-reader.js +203 -0
  59. package/dist/cjs/modules/pdf/reader/pdf-tokenizer.js +517 -0
  60. package/dist/cjs/modules/pdf/reader/reader-utils.js +30 -0
  61. package/dist/cjs/modules/pdf/reader/stream-filters.js +459 -0
  62. package/dist/cjs/modules/pdf/reader/text-reconstruction.js +467 -0
  63. package/dist/esm/modules/archive/zip/stream.js +53 -0
  64. package/dist/esm/modules/pdf/core/crypto.js +637 -0
  65. package/dist/esm/modules/pdf/core/encryption.js +88 -261
  66. package/dist/esm/modules/pdf/core/pdf-writer.js +19 -10
  67. package/dist/esm/modules/pdf/index.js +21 -3
  68. package/dist/esm/modules/pdf/reader/annotation-extractor.js +155 -0
  69. package/dist/esm/modules/pdf/reader/cmap-parser.js +321 -0
  70. package/dist/esm/modules/pdf/reader/content-interpreter.js +715 -0
  71. package/dist/esm/modules/pdf/reader/font-decoder.js +1513 -0
  72. package/dist/esm/modules/pdf/reader/form-extractor.js +355 -0
  73. package/dist/esm/modules/pdf/reader/image-extractor.js +220 -0
  74. package/dist/esm/modules/pdf/reader/metadata-reader.js +275 -0
  75. package/dist/esm/modules/pdf/reader/pdf-decrypt.js +443 -0
  76. package/dist/esm/modules/pdf/reader/pdf-document.js +818 -0
  77. package/dist/esm/modules/pdf/reader/pdf-parser.js +285 -0
  78. package/dist/esm/modules/pdf/reader/pdf-reader.js +200 -0
  79. package/dist/esm/modules/pdf/reader/pdf-tokenizer.js +543 -0
  80. package/dist/esm/modules/pdf/reader/reader-utils.js +27 -0
  81. package/dist/esm/modules/pdf/reader/stream-filters.js +456 -0
  82. package/dist/esm/modules/pdf/reader/text-reconstruction.js +463 -0
  83. package/dist/iife/excelts.iife.js +703 -267
  84. package/dist/iife/excelts.iife.js.map +1 -1
  85. package/dist/iife/excelts.iife.min.js +35 -35
  86. package/dist/types/modules/archive/zip/stream.d.ts +4 -0
  87. package/dist/types/modules/pdf/core/crypto.d.ts +65 -0
  88. package/dist/types/modules/pdf/core/encryption.d.ts +23 -20
  89. package/dist/types/modules/pdf/core/pdf-writer.d.ts +6 -4
  90. package/dist/types/modules/pdf/index.d.ts +23 -2
  91. package/dist/types/modules/pdf/reader/annotation-extractor.d.ts +63 -0
  92. package/dist/types/modules/pdf/reader/cmap-parser.d.ts +70 -0
  93. package/dist/types/modules/pdf/reader/content-interpreter.d.ts +57 -0
  94. package/dist/types/modules/pdf/reader/font-decoder.d.ts +58 -0
  95. package/dist/types/modules/pdf/reader/form-extractor.d.ts +48 -0
  96. package/dist/types/modules/pdf/reader/image-extractor.d.ts +55 -0
  97. package/dist/types/modules/pdf/reader/metadata-reader.d.ts +56 -0
  98. package/dist/types/modules/pdf/reader/pdf-decrypt.d.ts +26 -0
  99. package/dist/types/modules/pdf/reader/pdf-document.d.ts +191 -0
  100. package/dist/types/modules/pdf/reader/pdf-parser.d.ts +65 -0
  101. package/dist/types/modules/pdf/reader/pdf-reader.d.ts +143 -0
  102. package/dist/types/modules/pdf/reader/pdf-tokenizer.d.ts +101 -0
  103. package/dist/types/modules/pdf/reader/reader-utils.d.ts +15 -0
  104. package/dist/types/modules/pdf/reader/stream-filters.d.ts +20 -0
  105. package/dist/types/modules/pdf/reader/text-reconstruction.d.ts +44 -0
  106. package/package.json +1 -1
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ /**
3
+ * PDF annotation extractor.
4
+ *
5
+ * Extracts annotations from a PDF page's `/Annots` array.
6
+ * Supports all standard annotation subtypes defined in PDF Reference 1.7, §12.5.
7
+ *
8
+ * Common annotation types:
9
+ * - **Link** — Hyperlinks (URI, GoTo, GoToR)
10
+ * - **Text** — Sticky notes / comments
11
+ * - **FreeText** — Inline text annotations
12
+ * - **Highlight / Underline / StrikeOut / Squiggly** — Text markup
13
+ * - **Stamp** — Rubber stamp annotations
14
+ * - **Popup** — Associated popup windows
15
+ * - **Widget** — Form field widgets (handled separately by form-extractor)
16
+ *
17
+ * @see PDF Reference 1.7, §12.5 - Annotations
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.extractAnnotationsFromPage = extractAnnotationsFromPage;
21
+ const pdf_parser_1 = require("./pdf-parser");
22
+ const reader_utils_1 = require("./reader-utils");
23
+ // =============================================================================
24
+ // Public API
25
+ // =============================================================================
26
+ /**
27
+ * Extract annotations from a PDF page.
28
+ *
29
+ * Skips Widget annotations (form fields) — those are handled by the form extractor.
30
+ *
31
+ * @param pageDict - The page dictionary
32
+ * @param doc - The PDF document for resolving references
33
+ * @returns Array of extracted annotations
34
+ */
35
+ function extractAnnotationsFromPage(pageDict, doc) {
36
+ const annotsObj = pageDict.get("Annots");
37
+ if (!annotsObj) {
38
+ return [];
39
+ }
40
+ // Resolve the Annots array (may be an indirect reference)
41
+ const annotsResolved = doc.deref(annotsObj);
42
+ if (!(0, pdf_parser_1.isPdfArray)(annotsResolved)) {
43
+ return [];
44
+ }
45
+ const annotations = [];
46
+ for (const annotRef of annotsResolved) {
47
+ try {
48
+ const annotDict = doc.derefDict(annotRef);
49
+ if (!annotDict) {
50
+ continue;
51
+ }
52
+ const subtype = (0, pdf_parser_1.dictGetName)(annotDict, "Subtype") ?? "";
53
+ // Skip Widget annotations — handled by form-extractor
54
+ if (subtype === "Widget") {
55
+ continue;
56
+ }
57
+ // Skip Popup annotations — they are auxiliary
58
+ if (subtype === "Popup") {
59
+ continue;
60
+ }
61
+ const annotation = parseAnnotation(annotDict, subtype, doc);
62
+ if (annotation) {
63
+ annotations.push(annotation);
64
+ }
65
+ }
66
+ catch {
67
+ // Skip malformed annotations
68
+ }
69
+ }
70
+ return annotations;
71
+ }
72
+ // =============================================================================
73
+ // Parsing
74
+ // =============================================================================
75
+ function parseAnnotation(dict, subtype, doc) {
76
+ const rect = parseRect(dict.get("Rect"), doc);
77
+ if (!rect) {
78
+ return null;
79
+ }
80
+ const contents = (0, reader_utils_1.getDictStringValue)(dict, "Contents", doc);
81
+ const author = (0, reader_utils_1.getDictStringValue)(dict, "T", doc);
82
+ const subject = (0, reader_utils_1.getDictStringValue)(dict, "Subj", doc);
83
+ const modifiedDate = (0, reader_utils_1.getDictStringValue)(dict, "M", doc);
84
+ const flags = (0, pdf_parser_1.dictGetNumber)(dict, "F") ?? 0;
85
+ const color = parseColorArray(dict.get("C"), doc);
86
+ // Extract link-specific fields
87
+ let uri = "";
88
+ let destination = "";
89
+ if (subtype === "Link") {
90
+ const actionObj = doc.derefDict(dict.get("A"));
91
+ if (actionObj) {
92
+ const actionType = (0, pdf_parser_1.dictGetName)(actionObj, "S");
93
+ if (actionType === "URI") {
94
+ uri = (0, reader_utils_1.getDictStringValue)(actionObj, "URI", doc);
95
+ }
96
+ else if (actionType === "GoTo") {
97
+ const dest = actionObj.get("D");
98
+ if (typeof dest === "string") {
99
+ destination = dest;
100
+ }
101
+ }
102
+ else if (actionType === "GoToR") {
103
+ uri = (0, reader_utils_1.getDictStringValue)(actionObj, "F", doc);
104
+ }
105
+ }
106
+ // Check /Dest directly (older PDFs use this instead of /A)
107
+ if (!uri && !destination) {
108
+ const destObj = dict.get("Dest");
109
+ if (destObj) {
110
+ const resolved = doc.deref(destObj);
111
+ if (typeof resolved === "string") {
112
+ destination = resolved;
113
+ }
114
+ else if (resolved instanceof Uint8Array) {
115
+ destination = (0, pdf_parser_1.decodePdfStringBytes)(resolved);
116
+ }
117
+ }
118
+ }
119
+ }
120
+ return {
121
+ subtype,
122
+ rect,
123
+ contents,
124
+ author,
125
+ subject,
126
+ modifiedDate,
127
+ uri,
128
+ destination,
129
+ flags,
130
+ color
131
+ };
132
+ }
133
+ function parseRect(obj, doc) {
134
+ if (!obj) {
135
+ return null;
136
+ }
137
+ const resolved = doc.deref(obj);
138
+ if (!(0, pdf_parser_1.isPdfArray)(resolved) || resolved.length < 4) {
139
+ return null;
140
+ }
141
+ const nums = resolved.map(v => (typeof v === "number" ? v : 0));
142
+ return {
143
+ x1: nums[0],
144
+ y1: nums[1],
145
+ x2: nums[2],
146
+ y2: nums[3]
147
+ };
148
+ }
149
+ function parseColorArray(obj, doc) {
150
+ if (!obj) {
151
+ return [];
152
+ }
153
+ const resolved = doc.deref(obj);
154
+ if (!(0, pdf_parser_1.isPdfArray)(resolved)) {
155
+ return [];
156
+ }
157
+ return resolved.map(v => (typeof v === "number" ? v : 0));
158
+ }
@@ -0,0 +1,326 @@
1
+ "use strict";
2
+ /**
3
+ * CMap parser for PDF text extraction.
4
+ *
5
+ * Parses /ToUnicode CMap programs to build character code → Unicode mappings.
6
+ * This is essential for extracting text from PDFs that use CIDFonts or
7
+ * custom encodings.
8
+ *
9
+ * Supports:
10
+ * - beginbfchar / endbfchar (single character mappings)
11
+ * - beginbfrange / endbfrange (range mappings, including array form)
12
+ * - begincodespacerange / endcodespacerange
13
+ * - Multi-byte character codes (1-4 bytes)
14
+ * - UTF-16BE encoded target strings (including surrogate pairs)
15
+ *
16
+ * @see PDF Reference 1.7, §5.9 - ToUnicode CMaps
17
+ * @see Adobe Technical Note #5411 - CMap Resources
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.CMap = void 0;
21
+ exports.parseCMap = parseCMap;
22
+ const pdf_tokenizer_1 = require("./pdf-tokenizer");
23
+ // =============================================================================
24
+ // Public API
25
+ // =============================================================================
26
+ /**
27
+ * A parsed CMap that maps character codes to Unicode strings.
28
+ */
29
+ class CMap {
30
+ constructor() {
31
+ this.codeSpaceRanges = [];
32
+ this.bfChars = new Map();
33
+ this.bfRanges = [];
34
+ this.bytesPerCode = 1;
35
+ }
36
+ /**
37
+ * Look up the Unicode string for a character code.
38
+ * Uses binary search over sorted bfRanges for efficient lookup.
39
+ */
40
+ lookup(code) {
41
+ // Check bfchar mappings first (exact match)
42
+ const charMapping = this.bfChars.get(code);
43
+ if (charMapping !== undefined) {
44
+ return charMapping;
45
+ }
46
+ // Check bfrange mappings using binary search
47
+ const ranges = this.bfRanges;
48
+ let lo = 0;
49
+ let hi = ranges.length - 1;
50
+ while (lo <= hi) {
51
+ const mid = (lo + hi) >>> 1;
52
+ const range = ranges[mid];
53
+ if (code < range.low) {
54
+ hi = mid - 1;
55
+ }
56
+ else if (code > range.high) {
57
+ lo = mid + 1;
58
+ }
59
+ else {
60
+ // code is within this range
61
+ if (typeof range.mapping === "string") {
62
+ // Single base string — offset the code point
63
+ const offset = code - range.low;
64
+ const baseCode = stringToCodePoint(range.mapping);
65
+ return String.fromCodePoint(baseCode + offset);
66
+ }
67
+ // Array mapping
68
+ const index = code - range.low;
69
+ if (index < range.mapping.length) {
70
+ return range.mapping[index];
71
+ }
72
+ return undefined;
73
+ }
74
+ }
75
+ return undefined;
76
+ }
77
+ /**
78
+ * Add a code space range.
79
+ */
80
+ addCodeSpaceRange(low, high, bytes) {
81
+ this.codeSpaceRanges.push({ low, high, bytes });
82
+ if (bytes > this.bytesPerCode) {
83
+ this.bytesPerCode = bytes;
84
+ }
85
+ }
86
+ /**
87
+ * Add a bfchar mapping.
88
+ */
89
+ addBfChar(code, unicode) {
90
+ this.bfChars.set(code, unicode);
91
+ }
92
+ /**
93
+ * Add a bfrange mapping.
94
+ */
95
+ addBfRange(low, high, mapping) {
96
+ this.bfRanges.push({ low, high, mapping });
97
+ }
98
+ /**
99
+ * Sort bfRanges by low value for binary search.
100
+ * Should be called after all ranges have been added.
101
+ */
102
+ sortRanges() {
103
+ this.bfRanges.sort((a, b) => a.low - b.low);
104
+ }
105
+ /**
106
+ * Determine the code length (in bytes) for a given first byte,
107
+ * using the codespace ranges. When multiple ranges match (e.g. a 1-byte
108
+ * range covering 0x00-0xFF and a 2-byte range whose first byte overlaps),
109
+ * returns the longest match per the PDF spec's greedy matching rule.
110
+ * Falls back to bytesPerCode if no range matches.
111
+ */
112
+ getCodeLength(firstByte) {
113
+ let bestLen = 0;
114
+ for (const range of this.codeSpaceRanges) {
115
+ if (range.bytes === 1) {
116
+ if (firstByte >= (range.low & 0xff) && firstByte <= (range.high & 0xff)) {
117
+ if (bestLen < 1) {
118
+ bestLen = 1;
119
+ }
120
+ }
121
+ }
122
+ else if (range.bytes === 2) {
123
+ const highByteLow = (range.low >> 8) & 0xff;
124
+ const highByteHigh = (range.high >> 8) & 0xff;
125
+ if (firstByte >= highByteLow && firstByte <= highByteHigh) {
126
+ if (bestLen < 2) {
127
+ bestLen = 2;
128
+ }
129
+ }
130
+ }
131
+ else {
132
+ // For multi-byte ranges (3+ bytes), check the high byte
133
+ const hiLow = range.low >>> ((range.bytes - 1) * 8);
134
+ const hiHigh = range.high >>> ((range.bytes - 1) * 8);
135
+ if (firstByte >= hiLow && firstByte <= hiHigh) {
136
+ if (range.bytes > bestLen) {
137
+ bestLen = range.bytes;
138
+ }
139
+ }
140
+ }
141
+ }
142
+ return bestLen > 0 ? bestLen : this.bytesPerCode; // fallback
143
+ }
144
+ /**
145
+ * Check if this CMap has any mappings.
146
+ */
147
+ get isEmpty() {
148
+ return this.bfChars.size === 0 && this.bfRanges.length === 0;
149
+ }
150
+ /**
151
+ * Check if this CMap has codespace ranges defined.
152
+ */
153
+ get hasCodeSpaceRanges() {
154
+ return this.codeSpaceRanges.length > 0;
155
+ }
156
+ }
157
+ exports.CMap = CMap;
158
+ // =============================================================================
159
+ // CMap Parser
160
+ // =============================================================================
161
+ /**
162
+ * Parse a CMap program (typically from a /ToUnicode stream).
163
+ */
164
+ function parseCMap(data) {
165
+ const cmap = new CMap();
166
+ const tokenizer = new pdf_tokenizer_1.PdfTokenizer(data);
167
+ while (true) {
168
+ const token = tokenizer.next();
169
+ if (token.type === 11 /* TokenType.EOF */) {
170
+ break;
171
+ }
172
+ if (token.type === 6 /* TokenType.Keyword */) {
173
+ const kw = token.strValue;
174
+ if (kw === "begincodespacerange") {
175
+ parseCodeSpaceRange(tokenizer, cmap);
176
+ }
177
+ else if (kw === "beginbfchar") {
178
+ parseBfChar(tokenizer, cmap);
179
+ }
180
+ else if (kw === "beginbfrange") {
181
+ parseBfRange(tokenizer, cmap);
182
+ }
183
+ }
184
+ }
185
+ // Sort bfRanges for binary search lookup
186
+ cmap.sortRanges();
187
+ return cmap;
188
+ }
189
+ /**
190
+ * Parse codespacerange section.
191
+ */
192
+ function parseCodeSpaceRange(tokenizer, cmap) {
193
+ while (true) {
194
+ const token = tokenizer.next();
195
+ if (token.type === 11 /* TokenType.EOF */) {
196
+ break;
197
+ }
198
+ if (token.type === 6 /* TokenType.Keyword */ && token.strValue === "endcodespacerange") {
199
+ break;
200
+ }
201
+ // Expect two hex strings: low high
202
+ if (token.type === 2 /* TokenType.HexString */) {
203
+ const lowBytes = token.rawBytes;
204
+ const highToken = tokenizer.next();
205
+ if (highToken.type === 2 /* TokenType.HexString */) {
206
+ const highBytes = highToken.rawBytes;
207
+ const low = bytesToInt(lowBytes);
208
+ const high = bytesToInt(highBytes);
209
+ cmap.addCodeSpaceRange(low, high, lowBytes.length);
210
+ }
211
+ }
212
+ }
213
+ }
214
+ /**
215
+ * Parse bfchar section.
216
+ * Format: <srcCode> <dstString>
217
+ */
218
+ function parseBfChar(tokenizer, cmap) {
219
+ while (true) {
220
+ const token = tokenizer.next();
221
+ if (token.type === 11 /* TokenType.EOF */) {
222
+ break;
223
+ }
224
+ if (token.type === 6 /* TokenType.Keyword */ && token.strValue === "endbfchar") {
225
+ break;
226
+ }
227
+ if (token.type === 2 /* TokenType.HexString */) {
228
+ const code = bytesToInt(token.rawBytes);
229
+ const target = tokenizer.next();
230
+ if (target.type === 2 /* TokenType.HexString */) {
231
+ const unicode = decodeUtf16BE(target.rawBytes);
232
+ cmap.addBfChar(code, unicode);
233
+ }
234
+ }
235
+ }
236
+ }
237
+ /**
238
+ * Parse bfrange section.
239
+ * Formats:
240
+ * <low> <high> <dstString> — sequential mapping
241
+ * <low> <high> [<str1> <str2> ...] — array mapping
242
+ */
243
+ function parseBfRange(tokenizer, cmap) {
244
+ while (true) {
245
+ const token = tokenizer.next();
246
+ if (token.type === 11 /* TokenType.EOF */) {
247
+ break;
248
+ }
249
+ if (token.type === 6 /* TokenType.Keyword */ && token.strValue === "endbfrange") {
250
+ break;
251
+ }
252
+ if (token.type === 2 /* TokenType.HexString */) {
253
+ const low = bytesToInt(token.rawBytes);
254
+ const highToken = tokenizer.next();
255
+ if (highToken.type !== 2 /* TokenType.HexString */) {
256
+ continue;
257
+ }
258
+ const high = bytesToInt(highToken.rawBytes);
259
+ const mappingToken = tokenizer.next();
260
+ if (mappingToken.type === 2 /* TokenType.HexString */) {
261
+ // Sequential mapping from base string
262
+ const unicode = decodeUtf16BE(mappingToken.rawBytes);
263
+ cmap.addBfRange(low, high, unicode);
264
+ }
265
+ else if (mappingToken.type === 9 /* TokenType.ArrayBegin */) {
266
+ // Array of individual mappings
267
+ const mappings = [];
268
+ while (true) {
269
+ const elem = tokenizer.next();
270
+ if (elem.type === 10 /* TokenType.ArrayEnd */ || elem.type === 11 /* TokenType.EOF */) {
271
+ break;
272
+ }
273
+ if (elem.type === 2 /* TokenType.HexString */) {
274
+ mappings.push(decodeUtf16BE(elem.rawBytes));
275
+ }
276
+ }
277
+ cmap.addBfRange(low, high, mappings);
278
+ }
279
+ }
280
+ }
281
+ }
282
+ // =============================================================================
283
+ // Helpers
284
+ // =============================================================================
285
+ /**
286
+ * Convert a byte array to a big-endian integer.
287
+ * Uses multiplication instead of bitshift to avoid overflow for large codes.
288
+ */
289
+ function bytesToInt(bytes) {
290
+ let result = 0;
291
+ for (let i = 0; i < bytes.length; i++) {
292
+ result = result * 256 + bytes[i];
293
+ }
294
+ return result;
295
+ }
296
+ /**
297
+ * Decode a UTF-16BE byte array to a JavaScript string.
298
+ */
299
+ function decodeUtf16BE(bytes) {
300
+ let result = "";
301
+ for (let i = 0; i + 1 < bytes.length; i += 2) {
302
+ const code = (bytes[i] << 8) | bytes[i + 1];
303
+ // Handle surrogate pairs
304
+ if (code >= 0xd800 && code <= 0xdbff && i + 3 < bytes.length) {
305
+ const low = (bytes[i + 2] << 8) | bytes[i + 3];
306
+ if (low >= 0xdc00 && low <= 0xdfff) {
307
+ const cp = 0x10000 + ((code - 0xd800) << 10) + (low - 0xdc00);
308
+ result += String.fromCodePoint(cp);
309
+ i += 2;
310
+ continue;
311
+ }
312
+ }
313
+ result += String.fromCharCode(code);
314
+ }
315
+ // Single-byte code: treat as direct character code
316
+ if (bytes.length === 1) {
317
+ return String.fromCharCode(bytes[0]);
318
+ }
319
+ return result;
320
+ }
321
+ /**
322
+ * Get the first code point from a string.
323
+ */
324
+ function stringToCodePoint(str) {
325
+ return str.codePointAt(0) ?? 0;
326
+ }