@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.
- package/README.md +14 -1
- package/README_zh.md +6 -0
- package/dist/browser/modules/archive/zip/stream.d.ts +4 -0
- package/dist/browser/modules/archive/zip/stream.js +53 -0
- package/dist/browser/modules/pdf/core/crypto.d.ts +65 -0
- package/dist/browser/modules/pdf/core/crypto.js +637 -0
- package/dist/browser/modules/pdf/core/encryption.d.ts +23 -20
- package/dist/browser/modules/pdf/core/encryption.js +88 -261
- package/dist/browser/modules/pdf/core/pdf-writer.d.ts +6 -4
- package/dist/browser/modules/pdf/core/pdf-writer.js +19 -10
- package/dist/browser/modules/pdf/index.d.ts +23 -2
- package/dist/browser/modules/pdf/index.js +21 -3
- package/dist/browser/modules/pdf/reader/annotation-extractor.d.ts +63 -0
- package/dist/browser/modules/pdf/reader/annotation-extractor.js +155 -0
- package/dist/browser/modules/pdf/reader/cmap-parser.d.ts +70 -0
- package/dist/browser/modules/pdf/reader/cmap-parser.js +321 -0
- package/dist/browser/modules/pdf/reader/content-interpreter.d.ts +57 -0
- package/dist/browser/modules/pdf/reader/content-interpreter.js +715 -0
- package/dist/browser/modules/pdf/reader/font-decoder.d.ts +58 -0
- package/dist/browser/modules/pdf/reader/font-decoder.js +1513 -0
- package/dist/browser/modules/pdf/reader/form-extractor.d.ts +48 -0
- package/dist/browser/modules/pdf/reader/form-extractor.js +355 -0
- package/dist/browser/modules/pdf/reader/image-extractor.d.ts +55 -0
- package/dist/browser/modules/pdf/reader/image-extractor.js +220 -0
- package/dist/browser/modules/pdf/reader/metadata-reader.d.ts +56 -0
- package/dist/browser/modules/pdf/reader/metadata-reader.js +275 -0
- package/dist/browser/modules/pdf/reader/pdf-decrypt.d.ts +26 -0
- package/dist/browser/modules/pdf/reader/pdf-decrypt.js +443 -0
- package/dist/browser/modules/pdf/reader/pdf-document.d.ts +191 -0
- package/dist/browser/modules/pdf/reader/pdf-document.js +818 -0
- package/dist/browser/modules/pdf/reader/pdf-parser.d.ts +65 -0
- package/dist/browser/modules/pdf/reader/pdf-parser.js +285 -0
- package/dist/browser/modules/pdf/reader/pdf-reader.d.ts +143 -0
- package/dist/browser/modules/pdf/reader/pdf-reader.js +200 -0
- package/dist/browser/modules/pdf/reader/pdf-tokenizer.d.ts +101 -0
- package/dist/browser/modules/pdf/reader/pdf-tokenizer.js +543 -0
- package/dist/browser/modules/pdf/reader/reader-utils.d.ts +15 -0
- package/dist/browser/modules/pdf/reader/reader-utils.js +27 -0
- package/dist/browser/modules/pdf/reader/stream-filters.d.ts +20 -0
- package/dist/browser/modules/pdf/reader/stream-filters.js +456 -0
- package/dist/browser/modules/pdf/reader/text-reconstruction.d.ts +44 -0
- package/dist/browser/modules/pdf/reader/text-reconstruction.js +463 -0
- package/dist/cjs/modules/archive/zip/stream.js +53 -0
- package/dist/cjs/modules/pdf/core/crypto.js +649 -0
- package/dist/cjs/modules/pdf/core/encryption.js +88 -263
- package/dist/cjs/modules/pdf/core/pdf-writer.js +19 -10
- package/dist/cjs/modules/pdf/index.js +23 -4
- package/dist/cjs/modules/pdf/reader/annotation-extractor.js +158 -0
- package/dist/cjs/modules/pdf/reader/cmap-parser.js +326 -0
- package/dist/cjs/modules/pdf/reader/content-interpreter.js +718 -0
- package/dist/cjs/modules/pdf/reader/font-decoder.js +1518 -0
- package/dist/cjs/modules/pdf/reader/form-extractor.js +358 -0
- package/dist/cjs/modules/pdf/reader/image-extractor.js +223 -0
- package/dist/cjs/modules/pdf/reader/metadata-reader.js +278 -0
- package/dist/cjs/modules/pdf/reader/pdf-decrypt.js +447 -0
- package/dist/cjs/modules/pdf/reader/pdf-document.js +822 -0
- package/dist/cjs/modules/pdf/reader/pdf-parser.js +301 -0
- package/dist/cjs/modules/pdf/reader/pdf-reader.js +203 -0
- package/dist/cjs/modules/pdf/reader/pdf-tokenizer.js +517 -0
- package/dist/cjs/modules/pdf/reader/reader-utils.js +30 -0
- package/dist/cjs/modules/pdf/reader/stream-filters.js +459 -0
- package/dist/cjs/modules/pdf/reader/text-reconstruction.js +467 -0
- package/dist/esm/modules/archive/zip/stream.js +53 -0
- package/dist/esm/modules/pdf/core/crypto.js +637 -0
- package/dist/esm/modules/pdf/core/encryption.js +88 -261
- package/dist/esm/modules/pdf/core/pdf-writer.js +19 -10
- package/dist/esm/modules/pdf/index.js +21 -3
- package/dist/esm/modules/pdf/reader/annotation-extractor.js +155 -0
- package/dist/esm/modules/pdf/reader/cmap-parser.js +321 -0
- package/dist/esm/modules/pdf/reader/content-interpreter.js +715 -0
- package/dist/esm/modules/pdf/reader/font-decoder.js +1513 -0
- package/dist/esm/modules/pdf/reader/form-extractor.js +355 -0
- package/dist/esm/modules/pdf/reader/image-extractor.js +220 -0
- package/dist/esm/modules/pdf/reader/metadata-reader.js +275 -0
- package/dist/esm/modules/pdf/reader/pdf-decrypt.js +443 -0
- package/dist/esm/modules/pdf/reader/pdf-document.js +818 -0
- package/dist/esm/modules/pdf/reader/pdf-parser.js +285 -0
- package/dist/esm/modules/pdf/reader/pdf-reader.js +200 -0
- package/dist/esm/modules/pdf/reader/pdf-tokenizer.js +543 -0
- package/dist/esm/modules/pdf/reader/reader-utils.js +27 -0
- package/dist/esm/modules/pdf/reader/stream-filters.js +456 -0
- package/dist/esm/modules/pdf/reader/text-reconstruction.js +463 -0
- package/dist/iife/excelts.iife.js +703 -267
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +35 -35
- package/dist/types/modules/archive/zip/stream.d.ts +4 -0
- package/dist/types/modules/pdf/core/crypto.d.ts +65 -0
- package/dist/types/modules/pdf/core/encryption.d.ts +23 -20
- package/dist/types/modules/pdf/core/pdf-writer.d.ts +6 -4
- package/dist/types/modules/pdf/index.d.ts +23 -2
- package/dist/types/modules/pdf/reader/annotation-extractor.d.ts +63 -0
- package/dist/types/modules/pdf/reader/cmap-parser.d.ts +70 -0
- package/dist/types/modules/pdf/reader/content-interpreter.d.ts +57 -0
- package/dist/types/modules/pdf/reader/font-decoder.d.ts +58 -0
- package/dist/types/modules/pdf/reader/form-extractor.d.ts +48 -0
- package/dist/types/modules/pdf/reader/image-extractor.d.ts +55 -0
- package/dist/types/modules/pdf/reader/metadata-reader.d.ts +56 -0
- package/dist/types/modules/pdf/reader/pdf-decrypt.d.ts +26 -0
- package/dist/types/modules/pdf/reader/pdf-document.d.ts +191 -0
- package/dist/types/modules/pdf/reader/pdf-parser.d.ts +65 -0
- package/dist/types/modules/pdf/reader/pdf-reader.d.ts +143 -0
- package/dist/types/modules/pdf/reader/pdf-tokenizer.d.ts +101 -0
- package/dist/types/modules/pdf/reader/reader-utils.d.ts +15 -0
- package/dist/types/modules/pdf/reader/stream-filters.d.ts +20 -0
- package/dist/types/modules/pdf/reader/text-reconstruction.d.ts +44 -0
- 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
|
+
}
|