@file-viewer/capability-pdf-identity-repair 3.0.2 → 3.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/dist/index.js +479 -5
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,481 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { registerFileViewerPdfIdentityFontRepair } from "@file-viewer/renderer-pdf";
|
|
3
|
+
|
|
4
|
+
// ../../renderers/pdf/dist/pdfIdentityFontRepair.js
|
|
5
|
+
import { decodePDFRawStream, PDFArray, PDFDict, PDFDocument, PDFHexString, PDFName, PDFNumber, PDFRawStream, PDFString } from "pdf-lib";
|
|
6
|
+
|
|
7
|
+
// ../../renderers/pdf/dist/pdfIdentityFontName.js
|
|
8
|
+
var MAX_PDF_IDENTITY_FONT_NAME_CHARACTERS = 1024;
|
|
9
|
+
var PDF_FONT_STYLE_NAMES = /* @__PURE__ */ new Set([
|
|
10
|
+
"bold",
|
|
11
|
+
"regular",
|
|
12
|
+
"italic",
|
|
13
|
+
"oblique",
|
|
14
|
+
"medium",
|
|
15
|
+
"semibold",
|
|
16
|
+
"demibold",
|
|
17
|
+
"light",
|
|
18
|
+
"black",
|
|
19
|
+
"thin"
|
|
20
|
+
]);
|
|
21
|
+
var isFontNameSeparator = (character) => character === "," || character === "_" || character === "-" || character.trim() === "";
|
|
22
|
+
var hasSubsetPrefix = (value) => {
|
|
23
|
+
if (value.length < 7 || value[6] !== "+")
|
|
24
|
+
return false;
|
|
25
|
+
for (let index = 0; index < 6; index += 1) {
|
|
26
|
+
const codePoint = value.charCodeAt(index) | 32;
|
|
27
|
+
if (codePoint < 97 || codePoint > 122)
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
};
|
|
32
|
+
var stripFontStyleSuffix = (value) => {
|
|
33
|
+
let cursor = 0;
|
|
34
|
+
while (cursor < value.length) {
|
|
35
|
+
if (!isFontNameSeparator(value[cursor])) {
|
|
36
|
+
cursor += 1;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const separatorStart = cursor;
|
|
40
|
+
while (cursor < value.length && isFontNameSeparator(value[cursor]))
|
|
41
|
+
cursor += 1;
|
|
42
|
+
const tokenStart = cursor;
|
|
43
|
+
while (cursor < value.length && !isFontNameSeparator(value[cursor]))
|
|
44
|
+
cursor += 1;
|
|
45
|
+
const token = value.slice(tokenStart, cursor).toLowerCase();
|
|
46
|
+
const style = token.endsWith("mt") ? token.slice(0, -2) : token;
|
|
47
|
+
if (PDF_FONT_STYLE_NAMES.has(style))
|
|
48
|
+
return value.slice(0, separatorStart);
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
};
|
|
52
|
+
var normalizePdfIdentityFontFamily = (value) => {
|
|
53
|
+
if (!value || value.length > MAX_PDF_IDENTITY_FONT_NAME_CHARACTERS)
|
|
54
|
+
return "";
|
|
55
|
+
const withoutSubset = hasSubsetPrefix(value) ? value.slice(7) : value;
|
|
56
|
+
const withoutStyle = stripFontStyleSuffix(withoutSubset);
|
|
57
|
+
const normalizedCharacters = [];
|
|
58
|
+
for (const character of withoutStyle.normalize("NFKC").toLowerCase()) {
|
|
59
|
+
if (!isFontNameSeparator(character))
|
|
60
|
+
normalizedCharacters.push(character);
|
|
61
|
+
}
|
|
62
|
+
return normalizedCharacters.join("");
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// ../../renderers/pdf/dist/pdfIdentityFontRepair.js
|
|
66
|
+
var MAX_REPAIR_SOURCE_BYTES = 64 * 1024 * 1024;
|
|
67
|
+
var MAX_TTF_TABLES = 256;
|
|
68
|
+
var MAX_CMAP_GLYPHS = 65535;
|
|
69
|
+
var MAX_CMAP_CODEPOINT_VISITS = 65536;
|
|
70
|
+
var MAX_DECODED_FONT_BYTES = 64 * 1024 * 1024;
|
|
71
|
+
var CMAP_BFCHAR_CHUNK_SIZE = 100;
|
|
72
|
+
var BASE_FONT = PDFName.of("BaseFont");
|
|
73
|
+
var CID_TO_GID_MAP = PDFName.of("CIDToGIDMap");
|
|
74
|
+
var DESCENDANT_FONTS = PDFName.of("DescendantFonts");
|
|
75
|
+
var ENCODING = PDFName.of("Encoding");
|
|
76
|
+
var FONT = PDFName.of("Font");
|
|
77
|
+
var FONT_DESCRIPTOR = PDFName.of("FontDescriptor");
|
|
78
|
+
var FONT_FAMILY = PDFName.of("FontFamily");
|
|
79
|
+
var FONT_FILE_2 = PDFName.of("FontFile2");
|
|
80
|
+
var LENGTH_1 = PDFName.of("Length1");
|
|
81
|
+
var RESOURCES = PDFName.of("Resources");
|
|
82
|
+
var SUBTYPE = PDFName.of("Subtype");
|
|
83
|
+
var TO_UNICODE = PDFName.of("ToUnicode");
|
|
84
|
+
var X_OBJECT = PDFName.of("XObject");
|
|
85
|
+
var CJK_FONT_FAMILY_ALIASES = {
|
|
86
|
+
"\u5FAE\u8F6F\u96C5\u9ED1": "microsoftyahei",
|
|
87
|
+
"\u5B8B\u4F53": "simsun",
|
|
88
|
+
"\u9ED1\u4F53": "simhei",
|
|
89
|
+
"\u6977\u4F53": "kaiti",
|
|
90
|
+
"\u4EFF\u5B8B": "fangsong"
|
|
91
|
+
};
|
|
92
|
+
var CJK_FONT_FAMILY_MARKERS = [
|
|
93
|
+
"microsoftyahei",
|
|
94
|
+
"simsun",
|
|
95
|
+
"nsimsun",
|
|
96
|
+
"simhei",
|
|
97
|
+
"kaiti",
|
|
98
|
+
"fangsong",
|
|
99
|
+
"pingfang",
|
|
100
|
+
"songti",
|
|
101
|
+
"heiti",
|
|
102
|
+
"hiragino",
|
|
103
|
+
"notosanscjk",
|
|
104
|
+
"sourcehansans",
|
|
105
|
+
"sourcehanserif"
|
|
106
|
+
];
|
|
107
|
+
var readUint16 = (bytes, offset) => {
|
|
108
|
+
if (offset < 0 || offset + 2 > bytes.length) {
|
|
109
|
+
throw new Error("Unexpected end of TrueType font data.");
|
|
110
|
+
}
|
|
111
|
+
return bytes[offset] << 8 | bytes[offset + 1];
|
|
112
|
+
};
|
|
113
|
+
var readUint32 = (bytes, offset) => {
|
|
114
|
+
if (offset < 0 || offset + 4 > bytes.length) {
|
|
115
|
+
throw new Error("Unexpected end of TrueType font data.");
|
|
116
|
+
}
|
|
117
|
+
return bytes[offset] * 16777216 + (bytes[offset + 1] << 16) + (bytes[offset + 2] << 8) + bytes[offset + 3] >>> 0;
|
|
118
|
+
};
|
|
119
|
+
var readTag = (bytes, offset) => {
|
|
120
|
+
if (offset < 0 || offset + 4 > bytes.length) {
|
|
121
|
+
return "";
|
|
122
|
+
}
|
|
123
|
+
return String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);
|
|
124
|
+
};
|
|
125
|
+
var normalizeFontFamily = (value) => {
|
|
126
|
+
const normalized = normalizePdfIdentityFontFamily(value);
|
|
127
|
+
return CJK_FONT_FAMILY_ALIASES[normalized] || normalized;
|
|
128
|
+
};
|
|
129
|
+
var isCjkFontFamily = (family) => {
|
|
130
|
+
const key = normalizeFontFamily(family);
|
|
131
|
+
return CJK_FONT_FAMILY_MARKERS.some((marker) => key.includes(marker)) || /[\u3400-\u9fff]/.test(family);
|
|
132
|
+
};
|
|
133
|
+
var readFontName = (font) => {
|
|
134
|
+
var _a;
|
|
135
|
+
return ((_a = font.lookupMaybe(BASE_FONT, PDFName)) === null || _a === void 0 ? void 0 : _a.decodeText()) || "";
|
|
136
|
+
};
|
|
137
|
+
var readFontFamily = (descriptor) => {
|
|
138
|
+
var _a;
|
|
139
|
+
if (!descriptor) {
|
|
140
|
+
return "";
|
|
141
|
+
}
|
|
142
|
+
return ((_a = descriptor.lookupMaybe(FONT_FAMILY, PDFString, PDFHexString)) === null || _a === void 0 ? void 0 : _a.decodeText()) || "";
|
|
143
|
+
};
|
|
144
|
+
var getDescendantFont = (font) => {
|
|
145
|
+
var _a;
|
|
146
|
+
return (_a = font.lookupMaybe(DESCENDANT_FONTS, PDFArray)) === null || _a === void 0 ? void 0 : _a.lookupMaybe(0, PDFDict);
|
|
147
|
+
};
|
|
148
|
+
var getEmbeddedTrueTypeFont = (descriptor) => {
|
|
149
|
+
var _a;
|
|
150
|
+
if (!descriptor) {
|
|
151
|
+
return void 0;
|
|
152
|
+
}
|
|
153
|
+
const fontFile = descriptor.get(FONT_FILE_2);
|
|
154
|
+
if (!fontFile) {
|
|
155
|
+
return void 0;
|
|
156
|
+
}
|
|
157
|
+
const resolved = descriptor.context.lookup(fontFile);
|
|
158
|
+
if (!(resolved instanceof PDFRawStream)) {
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
const declaredLength = (_a = resolved.dict.lookupMaybe(LENGTH_1, PDFNumber)) === null || _a === void 0 ? void 0 : _a.asNumber();
|
|
162
|
+
if (typeof declaredLength !== "number" || !Number.isSafeInteger(declaredLength) || declaredLength < 1 || declaredLength > MAX_DECODED_FONT_BYTES || resolved.contents.byteLength > MAX_DECODED_FONT_BYTES) {
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
return resolved;
|
|
166
|
+
};
|
|
167
|
+
var createFontRecord = (font) => {
|
|
168
|
+
var _a;
|
|
169
|
+
const descendant = getDescendantFont(font);
|
|
170
|
+
if (!descendant) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const encoding = (_a = font.lookupMaybe(ENCODING, PDFName)) === null || _a === void 0 ? void 0 : _a.decodeText();
|
|
174
|
+
if (encoding !== "Identity-H" && encoding !== "Identity-V") {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const descriptor = descendant.lookupMaybe(FONT_DESCRIPTOR, PDFDict);
|
|
178
|
+
const baseFont = readFontName(font);
|
|
179
|
+
const descriptorFamily = readFontFamily(descriptor);
|
|
180
|
+
const cidToGidMapObject = descendant.get(CID_TO_GID_MAP);
|
|
181
|
+
const cidToGidMap = cidToGidMapObject ? descendant.context.lookup(cidToGidMapObject) : void 0;
|
|
182
|
+
const familyKeys = new Set([baseFont, descriptorFamily].filter(Boolean).map(normalizeFontFamily).filter(Boolean));
|
|
183
|
+
return {
|
|
184
|
+
font,
|
|
185
|
+
descendant,
|
|
186
|
+
familyKeys,
|
|
187
|
+
baseFont,
|
|
188
|
+
identityCidToGidMap: !cidToGidMap || cidToGidMap instanceof PDFName && cidToGidMap.decodeText() === "Identity",
|
|
189
|
+
embeddedFont: getEmbeddedTrueTypeFont(descriptor)
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
var collectFontRecords = (pdfDocument) => {
|
|
193
|
+
const records = /* @__PURE__ */ new Map();
|
|
194
|
+
const visitedResources = /* @__PURE__ */ new Set();
|
|
195
|
+
const visitedXObjects = /* @__PURE__ */ new Set();
|
|
196
|
+
const visitResources = (resources) => {
|
|
197
|
+
var _a;
|
|
198
|
+
if (!resources || visitedResources.has(resources)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
visitedResources.add(resources);
|
|
202
|
+
const fonts = resources.lookupMaybe(FONT, PDFDict);
|
|
203
|
+
for (const fontObject of (fonts === null || fonts === void 0 ? void 0 : fonts.values()) || []) {
|
|
204
|
+
const font = pdfDocument.context.lookup(fontObject);
|
|
205
|
+
if (!(font instanceof PDFDict) || records.has(font)) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const record = createFontRecord(font);
|
|
209
|
+
if (record) {
|
|
210
|
+
records.set(font, record);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const xObjects = resources.lookupMaybe(X_OBJECT, PDFDict);
|
|
214
|
+
for (const xObjectRef of (xObjects === null || xObjects === void 0 ? void 0 : xObjects.values()) || []) {
|
|
215
|
+
const xObject = pdfDocument.context.lookup(xObjectRef);
|
|
216
|
+
if (!(xObject instanceof PDFRawStream) || visitedXObjects.has(xObject)) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
visitedXObjects.add(xObject);
|
|
220
|
+
if (((_a = xObject.dict.lookupMaybe(SUBTYPE, PDFName)) === null || _a === void 0 ? void 0 : _a.decodeText()) !== "Form") {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
visitResources(xObject.dict.lookupMaybe(RESOURCES, PDFDict));
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
for (const page of pdfDocument.getPages()) {
|
|
227
|
+
visitResources(page.node.Resources());
|
|
228
|
+
}
|
|
229
|
+
return [...records.values()];
|
|
230
|
+
};
|
|
231
|
+
var findCmapTable = (fontBytes) => {
|
|
232
|
+
if (fontBytes.length < 12) {
|
|
233
|
+
throw new Error("Invalid TrueType font header.");
|
|
234
|
+
}
|
|
235
|
+
const tableCount = readUint16(fontBytes, 4);
|
|
236
|
+
if (tableCount < 1 || tableCount > MAX_TTF_TABLES) {
|
|
237
|
+
throw new Error(`Invalid TrueType table count: ${tableCount}.`);
|
|
238
|
+
}
|
|
239
|
+
let cmapOffset = -1;
|
|
240
|
+
for (let index = 0; index < tableCount; index += 1) {
|
|
241
|
+
const entryOffset = 12 + index * 16;
|
|
242
|
+
if (entryOffset + 16 > fontBytes.length) {
|
|
243
|
+
throw new Error("Invalid TrueType table directory.");
|
|
244
|
+
}
|
|
245
|
+
if (readTag(fontBytes, entryOffset) === "cmap") {
|
|
246
|
+
cmapOffset = readUint32(fontBytes, entryOffset + 8);
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (cmapOffset < 0 || cmapOffset + 4 > fontBytes.length) {
|
|
251
|
+
throw new Error("TrueType font does not contain a readable cmap table.");
|
|
252
|
+
}
|
|
253
|
+
const cmapCount = readUint16(fontBytes, cmapOffset + 2);
|
|
254
|
+
const tables = [];
|
|
255
|
+
for (let index = 0; index < cmapCount; index += 1) {
|
|
256
|
+
const recordOffset = cmapOffset + 4 + index * 8;
|
|
257
|
+
if (recordOffset + 8 > fontBytes.length) {
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
const offset = cmapOffset + readUint32(fontBytes, recordOffset + 4);
|
|
261
|
+
if (offset + 2 > fontBytes.length) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const format = readUint16(fontBytes, offset);
|
|
265
|
+
if (format === 4 || format === 12) {
|
|
266
|
+
tables.push({
|
|
267
|
+
offset,
|
|
268
|
+
format,
|
|
269
|
+
platformId: readUint16(fontBytes, recordOffset),
|
|
270
|
+
encodingId: readUint16(fontBytes, recordOffset + 2)
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const score = (table) => (table.format === 12 ? 100 : 0) + (table.platformId === 3 ? 20 : 0) + (table.platformId === 0 ? 10 : 0) + (table.encodingId === 10 ? 4 : 0) + (table.encodingId === 1 ? 2 : 0);
|
|
275
|
+
const selected = tables.sort((left, right) => score(right) - score(left))[0];
|
|
276
|
+
if (!selected) {
|
|
277
|
+
throw new Error("TrueType font does not contain a supported Unicode cmap.");
|
|
278
|
+
}
|
|
279
|
+
return selected;
|
|
280
|
+
};
|
|
281
|
+
var setGlyphMapping = (map, glyphId, codePoint) => {
|
|
282
|
+
if (glyphId > 0 && glyphId <= MAX_CMAP_GLYPHS && codePoint > 0 && codePoint <= 1114111 && !map.has(glyphId)) {
|
|
283
|
+
map.set(glyphId, codePoint);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
var parseFormat12Cmap = (fontBytes, table, glyphToUnicode) => {
|
|
287
|
+
const tableLength = readUint32(fontBytes, table.offset + 4);
|
|
288
|
+
const tableEnd = table.offset + tableLength;
|
|
289
|
+
const groupCount = readUint32(fontBytes, table.offset + 12);
|
|
290
|
+
if (tableLength < 16 || tableEnd > fontBytes.length || table.offset + 16 + groupCount * 12 > tableEnd) {
|
|
291
|
+
throw new Error("Invalid TrueType cmap format 12 groups.");
|
|
292
|
+
}
|
|
293
|
+
let visitedCodePoints = 0;
|
|
294
|
+
for (let index = 0; index < groupCount; index += 1) {
|
|
295
|
+
const offset = table.offset + 16 + index * 12;
|
|
296
|
+
const startCodePoint = readUint32(fontBytes, offset);
|
|
297
|
+
const endCodePoint = readUint32(fontBytes, offset + 4);
|
|
298
|
+
const startGlyphId = readUint32(fontBytes, offset + 8);
|
|
299
|
+
const length = Math.min(endCodePoint - startCodePoint + 1, MAX_CMAP_GLYPHS - startGlyphId + 1);
|
|
300
|
+
if (!Number.isSafeInteger(length) || length < 1) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
for (let innerIndex = 0; innerIndex < length; innerIndex += 1) {
|
|
304
|
+
if (visitedCodePoints >= MAX_CMAP_CODEPOINT_VISITS) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
visitedCodePoints += 1;
|
|
308
|
+
setGlyphMapping(glyphToUnicode, startGlyphId + innerIndex, startCodePoint + innerIndex);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
var parseFormat4Cmap = (fontBytes, table, glyphToUnicode) => {
|
|
313
|
+
const tableLength = readUint16(fontBytes, table.offset + 2);
|
|
314
|
+
const tableEnd = table.offset + tableLength;
|
|
315
|
+
const segmentCount = readUint16(fontBytes, table.offset + 6) / 2;
|
|
316
|
+
const endCodesOffset = table.offset + 14;
|
|
317
|
+
const startCodesOffset = endCodesOffset + segmentCount * 2 + 2;
|
|
318
|
+
const deltasOffset = startCodesOffset + segmentCount * 2;
|
|
319
|
+
const rangeOffsetsOffset = deltasOffset + segmentCount * 2;
|
|
320
|
+
if (!Number.isInteger(segmentCount) || segmentCount < 1 || tableLength < 16 || tableEnd > fontBytes.length || rangeOffsetsOffset + segmentCount * 2 > tableEnd) {
|
|
321
|
+
throw new Error("Invalid TrueType cmap format 4 segments.");
|
|
322
|
+
}
|
|
323
|
+
let visitedCodePoints = 0;
|
|
324
|
+
for (let index = 0; index < segmentCount; index += 1) {
|
|
325
|
+
const endCodePoint = readUint16(fontBytes, endCodesOffset + index * 2);
|
|
326
|
+
const startCodePoint = readUint16(fontBytes, startCodesOffset + index * 2);
|
|
327
|
+
const delta = readUint16(fontBytes, deltasOffset + index * 2);
|
|
328
|
+
const rangeOffset = readUint16(fontBytes, rangeOffsetsOffset + index * 2);
|
|
329
|
+
if (endCodePoint < startCodePoint) {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
for (let codePoint = startCodePoint; codePoint <= endCodePoint && codePoint !== 65535; codePoint += 1) {
|
|
333
|
+
if (visitedCodePoints >= MAX_CMAP_CODEPOINT_VISITS) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
visitedCodePoints += 1;
|
|
337
|
+
let glyphId = 0;
|
|
338
|
+
if (rangeOffset === 0) {
|
|
339
|
+
glyphId = codePoint + delta & 65535;
|
|
340
|
+
} else {
|
|
341
|
+
const glyphOffset = rangeOffsetsOffset + index * 2 + rangeOffset + (codePoint - startCodePoint) * 2;
|
|
342
|
+
if (glyphOffset + 2 <= tableEnd) {
|
|
343
|
+
glyphId = readUint16(fontBytes, glyphOffset);
|
|
344
|
+
if (glyphId) {
|
|
345
|
+
glyphId = glyphId + delta & 65535;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
setGlyphMapping(glyphToUnicode, glyphId, codePoint);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
var parseTrueTypeGlyphToUnicode = (fontBytes) => {
|
|
354
|
+
const selected = findCmapTable(fontBytes);
|
|
355
|
+
const glyphToUnicode = /* @__PURE__ */ new Map();
|
|
356
|
+
if (selected.format === 12) {
|
|
357
|
+
parseFormat12Cmap(fontBytes, selected, glyphToUnicode);
|
|
358
|
+
} else {
|
|
359
|
+
parseFormat4Cmap(fontBytes, selected, glyphToUnicode);
|
|
360
|
+
}
|
|
361
|
+
return glyphToUnicode;
|
|
362
|
+
};
|
|
363
|
+
var toHex = (value, minLength = 4) => value.toString(16).toUpperCase().padStart(minLength, "0");
|
|
364
|
+
var encodeUnicodeCodePoint = (codePoint) => {
|
|
365
|
+
if (codePoint <= 65535) {
|
|
366
|
+
return toHex(codePoint);
|
|
367
|
+
}
|
|
368
|
+
const offset = codePoint - 65536;
|
|
369
|
+
const highSurrogate = 55296 + (offset >> 10);
|
|
370
|
+
const lowSurrogate = 56320 + (offset & 1023);
|
|
371
|
+
return `${toHex(highSurrogate)}${toHex(lowSurrogate)}`;
|
|
372
|
+
};
|
|
373
|
+
var createToUnicodeCmap = (glyphToUnicode) => {
|
|
374
|
+
const entries = [...glyphToUnicode].filter(([glyphId, codePoint]) => glyphId > 0 && glyphId <= 65535 && codePoint > 0 && codePoint <= 1114111).sort(([left], [right]) => left - right);
|
|
375
|
+
const sections = [];
|
|
376
|
+
for (let index = 0; index < entries.length; index += CMAP_BFCHAR_CHUNK_SIZE) {
|
|
377
|
+
const chunk = entries.slice(index, index + CMAP_BFCHAR_CHUNK_SIZE);
|
|
378
|
+
sections.push([
|
|
379
|
+
`${chunk.length} beginbfchar`,
|
|
380
|
+
...chunk.map(([glyphId, codePoint]) => `<${toHex(glyphId)}> <${encodeUnicodeCodePoint(codePoint)}>`),
|
|
381
|
+
"endbfchar"
|
|
382
|
+
].join("\n"));
|
|
383
|
+
}
|
|
384
|
+
return [
|
|
385
|
+
"/CIDInit /ProcSet findresource begin",
|
|
386
|
+
"12 dict begin",
|
|
387
|
+
"begincmap",
|
|
388
|
+
"/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def",
|
|
389
|
+
"/CMapName /Adobe-Identity-UCS def",
|
|
390
|
+
"/CMapType 2 def",
|
|
391
|
+
"1 begincodespacerange",
|
|
392
|
+
"<0000> <FFFF>",
|
|
393
|
+
"endcodespacerange",
|
|
394
|
+
...sections,
|
|
395
|
+
"endcmap",
|
|
396
|
+
"CMapName currentdict /CMap defineresource pop",
|
|
397
|
+
"end",
|
|
398
|
+
"end"
|
|
399
|
+
].join("\n");
|
|
400
|
+
};
|
|
401
|
+
var familiesOverlap = (left, right) => {
|
|
402
|
+
for (const key of left) {
|
|
403
|
+
if (right.has(key)) {
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return false;
|
|
408
|
+
};
|
|
409
|
+
var repairMalformedIdentityCjkFonts = async (sourceBytes, candidateFamilies = []) => {
|
|
410
|
+
if (!sourceBytes.byteLength || sourceBytes.byteLength > MAX_REPAIR_SOURCE_BYTES) {
|
|
411
|
+
return { bytes: sourceBytes, repairedFonts: 0, repairedFamilies: [] };
|
|
412
|
+
}
|
|
413
|
+
const candidateKeys = new Set(candidateFamilies.map(normalizeFontFamily).filter(Boolean));
|
|
414
|
+
const pdfDocument = await PDFDocument.load(sourceBytes, {
|
|
415
|
+
updateMetadata: false
|
|
416
|
+
});
|
|
417
|
+
const records = collectFontRecords(pdfDocument);
|
|
418
|
+
const sourceMaps = /* @__PURE__ */ new Map();
|
|
419
|
+
const getSourceMap = (record) => {
|
|
420
|
+
const cached = sourceMaps.get(record);
|
|
421
|
+
if (cached) {
|
|
422
|
+
return cached;
|
|
423
|
+
}
|
|
424
|
+
if (!record.embeddedFont) {
|
|
425
|
+
return void 0;
|
|
426
|
+
}
|
|
427
|
+
const decoded = decodePDFRawStream(record.embeddedFont).decode();
|
|
428
|
+
if (decoded.byteLength > MAX_DECODED_FONT_BYTES) {
|
|
429
|
+
throw new Error("Embedded TrueType font exceeds the Identity repair limit.");
|
|
430
|
+
}
|
|
431
|
+
const glyphMap = parseTrueTypeGlyphToUnicode(decoded);
|
|
432
|
+
sourceMaps.set(record, glyphMap);
|
|
433
|
+
return glyphMap;
|
|
434
|
+
};
|
|
435
|
+
let repairedFonts = 0;
|
|
436
|
+
const repairedFamilies = /* @__PURE__ */ new Set();
|
|
437
|
+
for (const target of records) {
|
|
438
|
+
if (target.font.has(TO_UNICODE) || target.embeddedFont || !target.identityCidToGidMap || ![...target.familyKeys].some((key) => isCjkFontFamily(key)) || candidateKeys.size > 0 && ![...target.familyKeys].some((key) => candidateKeys.has(key))) {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
let bestMap;
|
|
442
|
+
for (const source of records) {
|
|
443
|
+
if (!source.embeddedFont || !familiesOverlap(target.familyKeys, source.familyKeys)) {
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
const sourceMap = getSourceMap(source);
|
|
448
|
+
if (sourceMap && (!bestMap || sourceMap.size > bestMap.size)) {
|
|
449
|
+
bestMap = sourceMap;
|
|
450
|
+
}
|
|
451
|
+
} catch {
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (!bestMap || bestMap.size < 2) {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
const cmap = createToUnicodeCmap(bestMap);
|
|
458
|
+
const cmapRef = pdfDocument.context.register(pdfDocument.context.flateStream(cmap));
|
|
459
|
+
target.font.set(TO_UNICODE, cmapRef);
|
|
460
|
+
repairedFonts += 1;
|
|
461
|
+
repairedFamilies.add(target.baseFont || [...target.familyKeys][0] || "CJK font");
|
|
462
|
+
}
|
|
463
|
+
if (!repairedFonts) {
|
|
464
|
+
return { bytes: sourceBytes, repairedFonts: 0, repairedFamilies: [] };
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
bytes: await pdfDocument.save({ useObjectStreams: true }),
|
|
468
|
+
repairedFonts,
|
|
469
|
+
repairedFamilies: [...repairedFamilies]
|
|
470
|
+
};
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// src/index.ts
|
|
474
|
+
var enableFileViewerPdfIdentityFontRepair = () => {
|
|
475
|
+
registerFileViewerPdfIdentityFontRepair(repairMalformedIdentityCjkFonts);
|
|
5
476
|
};
|
|
6
477
|
enableFileViewerPdfIdentityFontRepair();
|
|
7
|
-
export {
|
|
478
|
+
export {
|
|
479
|
+
enableFileViewerPdfIdentityFontRepair,
|
|
480
|
+
repairMalformedIdentityCjkFonts
|
|
481
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@file-viewer/capability-pdf-identity-repair",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Opt-in malformed Identity CJK font repair for the File Viewer PDF renderer.",
|
|
@@ -22,10 +22,11 @@
|
|
|
22
22
|
"README.en.md"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@file-viewer/renderer-pdf": "3.0
|
|
25
|
+
"@file-viewer/renderer-pdf": "3.1.0",
|
|
26
26
|
"pdf-lib": "1.17.1"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
+
"esbuild": "^0.28.2",
|
|
29
30
|
"typescript": "^6.0.3"
|
|
30
31
|
},
|
|
31
32
|
"fileViewer": {
|
|
@@ -42,7 +43,7 @@
|
|
|
42
43
|
},
|
|
43
44
|
"license": "Apache-2.0",
|
|
44
45
|
"scripts": {
|
|
45
|
-
"build": "tsc -b tsconfig.json",
|
|
46
|
+
"build": "tsc -b tsconfig.json && node scripts/build.mjs && node --test test/*.test.mjs",
|
|
46
47
|
"type-check": "tsc -b tsconfig.json"
|
|
47
48
|
}
|
|
48
49
|
}
|