@doccloak/core 0.12.1 → 0.13.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/CHANGELOG.md +333 -0
- package/NOTICE +5 -0
- package/README.md +131 -9
- package/dist/dom/errors.d.ts +1 -1
- package/dist/dom/ocr.d.ts +33 -7
- package/dist/dom/ocr.js +98 -4
- package/dist/dom/office.d.ts +20 -6
- package/dist/dom/office.js +44 -11
- package/dist/engine.js +4 -5
- package/dist/env.d.ts +3 -3
- package/dist/pdf/cmap.d.ts +83 -0
- package/dist/pdf/cmap.js +659 -0
- package/dist/pdf/encodings.d.ts +49 -0
- package/dist/pdf/encodings.js +181 -0
- package/dist/pdf/extract.d.ts +106 -0
- package/dist/pdf/extract.js +1064 -0
- package/dist/pdf/fallback-fonts.d.ts +146 -0
- package/dist/pdf/fallback-fonts.js +312 -0
- package/dist/pdf/fit.d.ts +35 -0
- package/dist/pdf/fit.js +60 -0
- package/dist/pdf/fonts.d.ts +82 -0
- package/dist/pdf/fonts.js +465 -0
- package/dist/pdf/glyphlist.d.ts +37 -0
- package/dist/pdf/glyphlist.js +485 -0
- package/dist/pdf/index.d.ts +21 -0
- package/dist/pdf/index.js +7 -0
- package/dist/pdf/lexer.d.ts +61 -0
- package/dist/pdf/lexer.js +554 -0
- package/dist/pdf/objects.d.ts +57 -0
- package/dist/pdf/objects.js +217 -0
- package/dist/pdf/oracle.d.ts +149 -0
- package/dist/pdf/oracle.js +384 -0
- package/dist/pdf/raster.d.ts +45 -0
- package/dist/pdf/raster.js +89 -0
- package/dist/pdf/read.d.ts +36 -0
- package/dist/pdf/read.js +65 -0
- package/dist/pdf/serializer.d.ts +21 -0
- package/dist/pdf/serializer.js +106 -0
- package/dist/pdf/standard-metrics.d.ts +27 -0
- package/dist/pdf/standard-metrics.js +72 -0
- package/dist/pdf/surgery.d.ts +131 -0
- package/dist/pdf/surgery.js +847 -0
- package/dist/pdf/textstate.d.ts +77 -0
- package/dist/pdf/textstate.js +280 -0
- package/dist/pdf/types.d.ts +191 -0
- package/dist/pdf/types.js +11 -0
- package/dist/pdf/verify.d.ts +33 -0
- package/dist/pdf/verify.js +446 -0
- package/dist/pdf/write.d.ts +52 -0
- package/dist/pdf/write.js +644 -0
- package/dist/pipeline.d.ts +15 -0
- package/dist/pipeline.js +123 -8
- package/dist/providers/bardsai.d.ts +1 -1
- package/dist/providers/bardsai.js +5 -1
- package/dist/providers/gliner-base.d.ts +1 -1
- package/dist/providers/gliner-base.js +5 -1
- package/dist/providers/gliner.d.ts +1 -1
- package/dist/providers/gliner.js +5 -1
- package/dist/regex/rules.data.js +13 -8
- package/dist/types.d.ts +17 -2
- package/dist/types.js +17 -0
- package/dist/worker-protocol.d.ts +11 -0
- package/dist/worker-protocol.js +41 -8
- package/fonts/liberation/LICENSE +102 -0
- package/fonts/liberation/LiberationMono-Bold.ttf +0 -0
- package/fonts/liberation/LiberationMono-BoldItalic.ttf +0 -0
- package/fonts/liberation/LiberationMono-Italic.ttf +0 -0
- package/fonts/liberation/LiberationMono-Regular.ttf +0 -0
- package/fonts/liberation/LiberationSans-Bold.ttf +0 -0
- package/fonts/liberation/LiberationSans-BoldItalic.ttf +0 -0
- package/fonts/liberation/LiberationSans-Italic.ttf +0 -0
- package/fonts/liberation/LiberationSans-Regular.ttf +0 -0
- package/fonts/liberation/LiberationSerif-Bold.ttf +0 -0
- package/fonts/liberation/LiberationSerif-BoldItalic.ttf +0 -0
- package/fonts/liberation/LiberationSerif-Italic.ttf +0 -0
- package/fonts/liberation/LiberationSerif-Regular.ttf +0 -0
- package/package.json +11 -2
- package/rules/pl.json +7 -3
- package/rules/universal.json +6 -5
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @doccloak/core/pdf - font model (T209).
|
|
3
|
+
*
|
|
4
|
+
* A LoadedFont turns the bytes of a text-showing operand into glyphs (code,
|
|
5
|
+
* Unicode, advance width) and, in the other direction, tells whether a
|
|
6
|
+
* placeholder can be written with the same font and how to encode it.
|
|
7
|
+
*
|
|
8
|
+
* Decoding order (as pdf.js and the spec, 9.10.2): ToUnicode CMap first,
|
|
9
|
+
* then the encoding (base encoding + /Differences) through the Adobe Glyph
|
|
10
|
+
* List, then for composite fonts the CID-to-Unicode of Identity orderings is
|
|
11
|
+
* unknown, so a code with no ToUnicode entry yields '' (notdef for our
|
|
12
|
+
* purposes) and the page is later cross-checked against pdf.js.
|
|
13
|
+
*
|
|
14
|
+
* Encodability (placeholders in the original font): a character is
|
|
15
|
+
* showable when a code maps to it AND that code is known to have a glyph:
|
|
16
|
+
* either the code was shown somewhere in the document with this font (the
|
|
17
|
+
* subset contains it) or the font is a non-embedded standard 14 face whose
|
|
18
|
+
* AFM lists the glyph. This rule never guesses about subset contents.
|
|
19
|
+
*/
|
|
20
|
+
import { PDFName, PDFNumber, PDFArray, PDFStream, PDFDict, decodeStream, getArray, getDict, getName, getNumber, getStream, itemsOf, numbersOf, refKey, refOf, resolve, } from "./objects.js";
|
|
21
|
+
import { STANDARD_ENCODING, WIN_ANSI_ENCODING, MAC_ROMAN_ENCODING, SYMBOL_ENCODING, ZAPF_DINGBATS_ENCODING, encodingByName, } from "./encodings.js";
|
|
22
|
+
import { glyphNameToUnicode, unicodeToGlyphName } from "./glyphlist.js";
|
|
23
|
+
import { parseCMap, predefinedCMap, splitCodes, cidOf, unicodeOf, CMapParseError } from "./cmap.js";
|
|
24
|
+
import { foldGlyphText } from "./oracle.js";
|
|
25
|
+
import { loadStandardMetrics, standardFontFor } from "./standard-metrics.js";
|
|
26
|
+
import { styleFromFontName } from "./fallback-fonts.js";
|
|
27
|
+
const STANDARD_SUBTYPES = new Set(['Type1', 'MMType1', 'TrueType', 'Type3', 'Type0']);
|
|
28
|
+
/** Load (and cache by ref) the font at /Resources /Font /<name>. */
|
|
29
|
+
export function loadFont(ctx, fontsDict, resourceName, cache) {
|
|
30
|
+
if (!fontsDict)
|
|
31
|
+
return undefined;
|
|
32
|
+
const ref = refOf(fontsDict, resourceName);
|
|
33
|
+
const dictObj = resolve(ctx, fontsDict.get(PDFName.of(resourceName)));
|
|
34
|
+
const dict = dictObj instanceof PDFStream ? dictObj.dict : dictObj;
|
|
35
|
+
if (!(dict && typeof dict.get === 'function'))
|
|
36
|
+
return undefined;
|
|
37
|
+
const key = ref ? refKey(ref) : `direct:${resourceName}:${dict.toString()}`;
|
|
38
|
+
const cached = cache.get(key);
|
|
39
|
+
if (cached)
|
|
40
|
+
return cached;
|
|
41
|
+
const font = buildFont(ctx, dict, key);
|
|
42
|
+
cache.set(key, font);
|
|
43
|
+
return font;
|
|
44
|
+
}
|
|
45
|
+
function buildFont(ctx, dict, key) {
|
|
46
|
+
const subtypeName = getName(ctx, dict, 'Subtype') ?? '';
|
|
47
|
+
const subtype = STANDARD_SUBTYPES.has(subtypeName) ? subtypeName : 'unknown';
|
|
48
|
+
const baseFont = getName(ctx, dict, 'BaseFont') ?? '';
|
|
49
|
+
const problems = [];
|
|
50
|
+
if (subtype === 'unknown')
|
|
51
|
+
problems.push({ code: 'unknown-subtype', message: `font subtype /${subtypeName || '?'} is not supported` });
|
|
52
|
+
const composite = subtype === 'Type0';
|
|
53
|
+
const type3 = subtype === 'Type3';
|
|
54
|
+
const descendant = composite ? firstDescendant(ctx, dict) : undefined;
|
|
55
|
+
const descriptor = getDict(ctx, descendant ?? dict, 'FontDescriptor');
|
|
56
|
+
const flags = getNumber(ctx, descriptor, 'Flags') ?? 0;
|
|
57
|
+
const symbolic = (flags & 4) !== 0 && (flags & 32) === 0;
|
|
58
|
+
const embedded = !!(descriptor && (getStream(ctx, descriptor, 'FontFile') || getStream(ctx, descriptor, 'FontFile2') || getStream(ctx, descriptor, 'FontFile3')));
|
|
59
|
+
const style = styleFromFontName(baseFont, flags, getNumber(ctx, descriptor, 'ItalicAngle'), getNumber(ctx, descriptor, 'StemV'), getNumber(ctx, descriptor, 'FontWeight'));
|
|
60
|
+
const ascent = getNumber(ctx, descriptor, 'Ascent') ?? 750;
|
|
61
|
+
const descent = getNumber(ctx, descriptor, 'Descent') ?? -250;
|
|
62
|
+
// ToUnicode
|
|
63
|
+
let toUnicode = null;
|
|
64
|
+
const tuStream = getStream(ctx, dict, 'ToUnicode');
|
|
65
|
+
if (tuStream) {
|
|
66
|
+
try {
|
|
67
|
+
toUnicode = parseCMap(decodeStream(ctx, tuStream));
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
const usecmap = err instanceof CMapParseError && /usecmap/.test(err.message);
|
|
71
|
+
problems.push({ code: usecmap ? 'usecmap' : 'bad-cmap', message: `ToUnicode CMap unreadable: ${err.message}` });
|
|
72
|
+
toUnicode = null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// A Type3 font draws its glyphs from /CharProcs, whatever its /BaseFont says: standard-14 metrics never apply.
|
|
76
|
+
const standard = !embedded && !type3 ? standardFontFor(baseFont) : undefined;
|
|
77
|
+
const metrics = standard ? loadStandardMetrics(standard) : undefined;
|
|
78
|
+
if (composite) {
|
|
79
|
+
return buildCompositeFont(ctx, dict, descendant, key, { subtype, baseFont, problems, style, embedded, symbolic, toUnicode, ascent, descent, flags, metrics });
|
|
80
|
+
}
|
|
81
|
+
// ---- simple fonts (Type1, TrueType, Type3) ----
|
|
82
|
+
const fontMatrix = type3 ? matrixOf(numbersOf(ctx, getArray(ctx, dict, 'FontMatrix'))) : null;
|
|
83
|
+
const builtin = symbolic && embedded && (subtype === 'Type1' || subtype === 'MMType1') ? type1BuiltinEncoding(ctx, descriptor) : undefined;
|
|
84
|
+
const { names: encodingNames, base: baseEncodingNames } = simpleEncoding(ctx, dict, { subtype, symbolic, nonsymbolic: (flags & 32) !== 0, embedded, baseFont, metrics, type3, builtin });
|
|
85
|
+
// Widths
|
|
86
|
+
const firstChar = getNumber(ctx, dict, 'FirstChar');
|
|
87
|
+
const widthsArr = numbersOf(ctx, getArray(ctx, dict, 'Widths'));
|
|
88
|
+
const missingWidth = getNumber(ctx, descriptor, 'MissingWidth') ?? 0;
|
|
89
|
+
const scale = type3 && fontMatrix ? fontMatrix[0] * 1000 : 1;
|
|
90
|
+
// AFM width of the glyph at a code; a /Differences name the AFM does not know (/.notdef, /gNN)
|
|
91
|
+
// advances like the base encoding's glyph at that code, as pdf.js draws it.
|
|
92
|
+
const metricWidth = (code) => {
|
|
93
|
+
if (!metrics)
|
|
94
|
+
return undefined;
|
|
95
|
+
const g = encodingNames[code];
|
|
96
|
+
const w = g ? metrics.widthOfGlyph(g) : undefined;
|
|
97
|
+
if (w !== undefined)
|
|
98
|
+
return w;
|
|
99
|
+
const b = baseEncodingNames[code];
|
|
100
|
+
return b ? metrics.widthOfGlyph(b) : undefined;
|
|
101
|
+
};
|
|
102
|
+
const widthOfCode = (code) => {
|
|
103
|
+
if (widthsArr.length > 0 && firstChar !== undefined) {
|
|
104
|
+
const i = code - firstChar;
|
|
105
|
+
if (i >= 0 && i < widthsArr.length) {
|
|
106
|
+
const w = widthsArr[i];
|
|
107
|
+
// Some producers write zero widths for glyphs they never use; keep zero (spec: advance 0).
|
|
108
|
+
return w * scale;
|
|
109
|
+
}
|
|
110
|
+
if (type3)
|
|
111
|
+
return 0;
|
|
112
|
+
if (metrics)
|
|
113
|
+
return metricWidth(code) ?? missingWidth;
|
|
114
|
+
return missingWidth;
|
|
115
|
+
}
|
|
116
|
+
if (metrics)
|
|
117
|
+
return metricWidth(code) ?? missingWidth;
|
|
118
|
+
return missingWidth * scale || 0;
|
|
119
|
+
};
|
|
120
|
+
const unicodeForCode = (code) => {
|
|
121
|
+
if (toUnicode) {
|
|
122
|
+
// An empty destination (<41> <>) is no mapping: pdf.js falls through to the encoding.
|
|
123
|
+
const u = unicodeOf(toUnicode, code);
|
|
124
|
+
if (u !== undefined && u !== '')
|
|
125
|
+
return foldGlyphText(u);
|
|
126
|
+
}
|
|
127
|
+
const g = encodingNames[code];
|
|
128
|
+
if (g) {
|
|
129
|
+
const u = glyphNameToUnicode(g);
|
|
130
|
+
if (u !== undefined)
|
|
131
|
+
return foldGlyphText(u);
|
|
132
|
+
}
|
|
133
|
+
// Non-symbolic fonts without an entry: codes in the printable ASCII range mean themselves.
|
|
134
|
+
if (!symbolic && !toUnicode && code >= 0x20 && code <= 0x7e && encodingNames[code] === undefined) {
|
|
135
|
+
return String.fromCharCode(code);
|
|
136
|
+
}
|
|
137
|
+
return '';
|
|
138
|
+
};
|
|
139
|
+
const usedCodes = new Set();
|
|
140
|
+
const decode = (bytes) => {
|
|
141
|
+
const out = [];
|
|
142
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
143
|
+
const code = bytes[i];
|
|
144
|
+
out.push({ code, length: 1, offset: i, unicode: unicodeForCode(code), width: widthOfCode(code), isSpaceCode: code === 32, notdef: encodingNames[code] === '.notdef' });
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
};
|
|
148
|
+
// Reverse map for encoding placeholders: unicode -> code, lowest code wins. A viewer draws a
|
|
149
|
+
// code through the ENCODING (glyph name), never through ToUnicode, so the reverse map is built
|
|
150
|
+
// from the glyph names; a code whose ToUnicode entry disagrees with its glyph is never reused
|
|
151
|
+
// (the placeholder would read one way in the text layer and look another way on the page).
|
|
152
|
+
const consistent = (code, u) => {
|
|
153
|
+
if (!toUnicode)
|
|
154
|
+
return true;
|
|
155
|
+
const tu = unicodeOf(toUnicode, code);
|
|
156
|
+
return tu === undefined || tu === '' || tu === u;
|
|
157
|
+
};
|
|
158
|
+
const reverse = new Map();
|
|
159
|
+
for (let code = 0; code < 256; code++) {
|
|
160
|
+
const g = encodingNames[code];
|
|
161
|
+
const u = g ? glyphNameToUnicode(g) : undefined;
|
|
162
|
+
if (!u || !consistent(code, u))
|
|
163
|
+
continue;
|
|
164
|
+
if (!reverse.has(u))
|
|
165
|
+
reverse.set(u, code);
|
|
166
|
+
}
|
|
167
|
+
const glyphKnown = (code) => {
|
|
168
|
+
if (usedCodes.has(code))
|
|
169
|
+
return true;
|
|
170
|
+
if (metrics && !embedded) {
|
|
171
|
+
const g = encodingNames[code];
|
|
172
|
+
return !!g && metrics.hasGlyph(g);
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
};
|
|
176
|
+
const encode = (text) => {
|
|
177
|
+
const codes = [];
|
|
178
|
+
let width = 0;
|
|
179
|
+
for (const ch of text) {
|
|
180
|
+
let code = reverse.get(ch);
|
|
181
|
+
if (code === undefined && metrics && !embedded) {
|
|
182
|
+
// Non-embedded standard font: any glyph of the AFM reachable through the encoding.
|
|
183
|
+
const g = unicodeToGlyphName(ch);
|
|
184
|
+
if (g) {
|
|
185
|
+
const idx = encodingNames.indexOf(g);
|
|
186
|
+
if (idx >= 0 && consistent(idx, ch))
|
|
187
|
+
code = idx;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (code === undefined || !glyphKnown(code))
|
|
191
|
+
return null;
|
|
192
|
+
// A code the font advances by nothing (a /Widths hole) would pile the placeholder up.
|
|
193
|
+
const w = widthOfCode(code);
|
|
194
|
+
if (w <= 0 && !/\p{M}/u.test(ch))
|
|
195
|
+
return null;
|
|
196
|
+
codes.push(code);
|
|
197
|
+
width += w;
|
|
198
|
+
}
|
|
199
|
+
return { bytes: Uint8Array.from(codes), width, codes };
|
|
200
|
+
};
|
|
201
|
+
return {
|
|
202
|
+
key, dict, subtype, baseFont, composite: false, type3, fontMatrix, style, embedded, symbolic, vertical: false,
|
|
203
|
+
problems, usedCodes, encodingNames, toUnicode, decode, widthOfCode, encode, codeLength: () => 1, ascent, descent,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function buildCompositeFont(ctx, dict, descendant, key, c) {
|
|
207
|
+
const { problems, toUnicode } = c;
|
|
208
|
+
// Encoding CMap: name (predefined) or stream (embedded)
|
|
209
|
+
const encObj = resolve(ctx, dict.get(PDFName.of('Encoding')));
|
|
210
|
+
let cmap = null;
|
|
211
|
+
if (encObj instanceof PDFName) {
|
|
212
|
+
const name = encObj.decodeText();
|
|
213
|
+
cmap = predefinedCMap(name) ?? null;
|
|
214
|
+
if (!cmap)
|
|
215
|
+
problems.push({ code: 'predefined-cmap', message: `predefined CMap /${name} is not available` });
|
|
216
|
+
}
|
|
217
|
+
else if (encObj instanceof PDFStream) {
|
|
218
|
+
try {
|
|
219
|
+
cmap = parseCMap(decodeStream(ctx, encObj));
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
const usecmap = err instanceof CMapParseError && /usecmap/.test(err.message);
|
|
223
|
+
problems.push({ code: usecmap ? 'usecmap' : 'bad-cmap', message: `encoding CMap unreadable: ${err.message}` });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
problems.push({ code: 'bad-cmap', message: 'Type0 font without /Encoding' });
|
|
228
|
+
}
|
|
229
|
+
if (cmap?.vertical)
|
|
230
|
+
problems.push({ code: 'vertical', message: 'vertical writing mode is not supported' });
|
|
231
|
+
// Widths: /W [ c [w1 w2 ...] | cfirst clast w ], /DW default 1000
|
|
232
|
+
const dw = getNumber(ctx, descendant, 'DW') ?? 1000;
|
|
233
|
+
const widthByCid = new Map();
|
|
234
|
+
const wArr = getArray(ctx, descendant, 'W');
|
|
235
|
+
if (wArr) {
|
|
236
|
+
const items = itemsOf(ctx, wArr);
|
|
237
|
+
let i = 0;
|
|
238
|
+
while (i < items.length) {
|
|
239
|
+
const a = items[i];
|
|
240
|
+
if (!(a instanceof PDFNumber)) {
|
|
241
|
+
i++;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const b = items[i + 1];
|
|
245
|
+
if (b instanceof PDFArray) {
|
|
246
|
+
const ws = numbersOf(ctx, b);
|
|
247
|
+
const start = a.asNumber();
|
|
248
|
+
for (let k = 0; k < ws.length; k++)
|
|
249
|
+
widthByCid.set(start + k, ws[k]);
|
|
250
|
+
i += 2;
|
|
251
|
+
}
|
|
252
|
+
else if (b instanceof PDFNumber && items[i + 2] instanceof PDFNumber) {
|
|
253
|
+
const first = a.asNumber();
|
|
254
|
+
const last = b.asNumber();
|
|
255
|
+
const w = items[i + 2].asNumber();
|
|
256
|
+
if (last - first <= 65535)
|
|
257
|
+
for (let cid = first; cid <= last; cid++)
|
|
258
|
+
widthByCid.set(cid, w);
|
|
259
|
+
i += 3;
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
i++;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const widthOfCode = (code) => {
|
|
267
|
+
if (!cmap)
|
|
268
|
+
return dw;
|
|
269
|
+
const cid = cidOf(cmap, code);
|
|
270
|
+
return widthByCid.get(cid) ?? dw;
|
|
271
|
+
};
|
|
272
|
+
const unicodeForCode = (code) => {
|
|
273
|
+
if (toUnicode) {
|
|
274
|
+
const u = unicodeOf(toUnicode, code);
|
|
275
|
+
if (u !== undefined && u !== '')
|
|
276
|
+
return foldGlyphText(u);
|
|
277
|
+
}
|
|
278
|
+
return '';
|
|
279
|
+
};
|
|
280
|
+
if (!toUnicode)
|
|
281
|
+
problems.push({ code: 'no-unicode', message: 'composite font without ToUnicode: its text cannot be read' });
|
|
282
|
+
const usedCodes = new Set();
|
|
283
|
+
const decode = (bytes) => {
|
|
284
|
+
if (!cmap) {
|
|
285
|
+
// Unknown code length: treat as 2-byte (the overwhelmingly common case) but the font is already flagged.
|
|
286
|
+
const out = [];
|
|
287
|
+
for (let i = 0; i + 1 < bytes.length; i += 2) {
|
|
288
|
+
const code = (bytes[i] << 8) | bytes[i + 1];
|
|
289
|
+
out.push({ code, length: 2, offset: i, unicode: unicodeForCode(code), width: dw, isSpaceCode: false });
|
|
290
|
+
}
|
|
291
|
+
return out;
|
|
292
|
+
}
|
|
293
|
+
return splitCodes(cmap, bytes).map((d) => ({
|
|
294
|
+
code: d.code,
|
|
295
|
+
length: d.length,
|
|
296
|
+
offset: d.offset,
|
|
297
|
+
unicode: unicodeForCode(d.code),
|
|
298
|
+
width: widthOfCode(d.code),
|
|
299
|
+
isSpaceCode: d.length === 1 && d.code === 32,
|
|
300
|
+
}));
|
|
301
|
+
};
|
|
302
|
+
// Reverse ToUnicode: unicode -> (code, length). Lowest code wins. The byte length of a code is
|
|
303
|
+
// the encoding CMap's (Identity-H: always 2), never the ToUnicode CMap's, which some producers
|
|
304
|
+
// declare with a 1-byte codespace.
|
|
305
|
+
const reverse = new Map();
|
|
306
|
+
if (toUnicode?.unicodeMap && cmap) {
|
|
307
|
+
const entries = [...toUnicode.unicodeMap.entries()].sort((x, y) => x[0] - y[0]);
|
|
308
|
+
for (const [code, u] of entries) {
|
|
309
|
+
if (u && !reverse.has(u))
|
|
310
|
+
reverse.set(u, { code, length: codeLengthFor(cmap, code) });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
const encode = (text) => {
|
|
314
|
+
if (!cmap)
|
|
315
|
+
return null;
|
|
316
|
+
const bytes = [];
|
|
317
|
+
const codes = [];
|
|
318
|
+
let width = 0;
|
|
319
|
+
for (const ch of text) {
|
|
320
|
+
const hit = reverse.get(ch);
|
|
321
|
+
if (!hit || !usedCodes.has(hit.code))
|
|
322
|
+
return null;
|
|
323
|
+
const w = widthOfCode(hit.code);
|
|
324
|
+
if (w <= 0 && !/\p{M}/u.test(ch))
|
|
325
|
+
return null;
|
|
326
|
+
for (let k = hit.length - 1; k >= 0; k--)
|
|
327
|
+
bytes.push((hit.code >> (8 * k)) & 0xff);
|
|
328
|
+
codes.push(hit.code);
|
|
329
|
+
width += w;
|
|
330
|
+
}
|
|
331
|
+
return { bytes: Uint8Array.from(bytes), width, codes };
|
|
332
|
+
};
|
|
333
|
+
return {
|
|
334
|
+
key, dict, subtype: c.subtype, baseFont: c.baseFont, composite: true, type3: false, fontMatrix: null, style: c.style,
|
|
335
|
+
embedded: c.embedded, symbolic: c.symbolic, vertical: !!cmap?.vertical, problems, usedCodes, encodingNames: null,
|
|
336
|
+
toUnicode, decode, widthOfCode, encode, ascent: c.ascent, descent: c.descent,
|
|
337
|
+
codeLength: (code) => (cmap ? codeLengthFor(cmap, code) : toUnicode ? codeLengthFor(toUnicode, code) : 2),
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function codeLengthFor(cmap, code) {
|
|
341
|
+
for (const r of cmap.codespace) {
|
|
342
|
+
if (code >= r.low && code <= r.high)
|
|
343
|
+
return r.numBytes;
|
|
344
|
+
}
|
|
345
|
+
if (cmap.codeLengths.size === 1)
|
|
346
|
+
return [...cmap.codeLengths][0];
|
|
347
|
+
return code > 0xff ? 2 : 1;
|
|
348
|
+
}
|
|
349
|
+
function firstDescendant(ctx, dict) {
|
|
350
|
+
const arr = getArray(ctx, dict, 'DescendantFonts');
|
|
351
|
+
const first = arr ? resolve(ctx, arr.get(0)) : undefined;
|
|
352
|
+
if (first instanceof PDFStream)
|
|
353
|
+
return first.dict;
|
|
354
|
+
return first && typeof first.get === 'function' ? first : undefined;
|
|
355
|
+
}
|
|
356
|
+
function matrixOf(nums) {
|
|
357
|
+
if (nums.length !== 6 || nums.some((n) => !Number.isFinite(n)))
|
|
358
|
+
return null;
|
|
359
|
+
return [nums[0], nums[1], nums[2], nums[3], nums[4], nums[5]];
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* The /Encoding of a Type1 font program's cleartext part (before `eexec`): either
|
|
363
|
+
* `/Encoding StandardEncoding def` or a list of `dup <code> /<name> put`. pdfTeX embeds its
|
|
364
|
+
* Computer Modern fonts this way with no /Encoding in the font dictionary, so without this the
|
|
365
|
+
* text of most LaTeX documents would be unreadable to us.
|
|
366
|
+
*/
|
|
367
|
+
function type1BuiltinEncoding(ctx, descriptor) {
|
|
368
|
+
const file = getStream(ctx, descriptor, 'FontFile');
|
|
369
|
+
if (!file)
|
|
370
|
+
return undefined;
|
|
371
|
+
let bytes;
|
|
372
|
+
try {
|
|
373
|
+
bytes = decodeStream(ctx, file);
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
return undefined;
|
|
377
|
+
}
|
|
378
|
+
// PFB segment header (0x80 0x01 <len32>) before the cleartext, if the producer left it in.
|
|
379
|
+
let start = 0;
|
|
380
|
+
if (bytes[0] === 0x80 && bytes[1] === 0x01)
|
|
381
|
+
start = 6;
|
|
382
|
+
const limit = Math.min(bytes.length, start + 65536);
|
|
383
|
+
let text = '';
|
|
384
|
+
for (let i = start; i < limit; i++)
|
|
385
|
+
text += String.fromCharCode(bytes[i]);
|
|
386
|
+
const eexec = text.indexOf('eexec');
|
|
387
|
+
if (eexec >= 0)
|
|
388
|
+
text = text.slice(0, eexec);
|
|
389
|
+
const encAt = text.indexOf('/Encoding');
|
|
390
|
+
if (encAt < 0)
|
|
391
|
+
return undefined;
|
|
392
|
+
const section = text.slice(encAt);
|
|
393
|
+
if (/^\/Encoding\s+StandardEncoding\s+def/.test(section))
|
|
394
|
+
return STANDARD_ENCODING;
|
|
395
|
+
const table = new Array(256).fill(undefined);
|
|
396
|
+
let any = false;
|
|
397
|
+
const re = /dup\s+(\d+)\s*\/([^\s/]+)\s+put/g;
|
|
398
|
+
for (let m = re.exec(section); m !== null; m = re.exec(section)) {
|
|
399
|
+
const code = Number(m[1]);
|
|
400
|
+
if (code >= 0 && code < 256) {
|
|
401
|
+
table[code] = m[2];
|
|
402
|
+
any = true;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return any ? table : undefined;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Code -> glyph name table for a simple font: base encoding (named, or the
|
|
409
|
+
* font's built-in one, or Standard) overlaid with /Differences.
|
|
410
|
+
*/
|
|
411
|
+
function simpleEncoding(ctx, dict, c) {
|
|
412
|
+
const table = new Array(256).fill(undefined);
|
|
413
|
+
const std = c.metrics?.name;
|
|
414
|
+
let base;
|
|
415
|
+
if (std === 'Symbol')
|
|
416
|
+
base = SYMBOL_ENCODING;
|
|
417
|
+
else if (std === 'ZapfDingbats')
|
|
418
|
+
base = ZAPF_DINGBATS_ENCODING;
|
|
419
|
+
const encObj = resolve(ctx, dict.get(PDFName.of('Encoding')));
|
|
420
|
+
let differences = [];
|
|
421
|
+
if (encObj instanceof PDFName) {
|
|
422
|
+
base = encodingByName(encObj.decodeText()) ?? base;
|
|
423
|
+
}
|
|
424
|
+
else if (encObj && typeof encObj.get === 'function') {
|
|
425
|
+
const encDict = encObj;
|
|
426
|
+
const baseName = getName(ctx, encDict, 'BaseEncoding');
|
|
427
|
+
if (baseName)
|
|
428
|
+
base = encodingByName(baseName) ?? base;
|
|
429
|
+
differences = itemsOf(ctx, getArray(ctx, encDict, 'Differences'));
|
|
430
|
+
}
|
|
431
|
+
if (!base) {
|
|
432
|
+
// No /Encoding: the defaults pdf.js applies (evaluator.js extractDataStructures). Standard;
|
|
433
|
+
// TrueType without the Nonsymbolic flag: WinAnsi; Symbolic flag: MacRoman for non-embedded
|
|
434
|
+
// fonts (Symbol / ZapfDingbats by name), and the font's built-in encoding, unknown to us,
|
|
435
|
+
// for embedded ones (the page is then cross-checked against pdf.js).
|
|
436
|
+
if (c.type3)
|
|
437
|
+
base = [];
|
|
438
|
+
else if (c.symbolic && c.embedded && c.subtype !== 'TrueType')
|
|
439
|
+
base = c.builtin ?? []; // Type1 built-in encoding, read from the font program when it is Type1 cleartext
|
|
440
|
+
else if (std === 'Symbol')
|
|
441
|
+
base = SYMBOL_ENCODING;
|
|
442
|
+
else if (std === 'ZapfDingbats')
|
|
443
|
+
base = ZAPF_DINGBATS_ENCODING;
|
|
444
|
+
else if (c.symbolic)
|
|
445
|
+
base = MAC_ROMAN_ENCODING;
|
|
446
|
+
else if (c.subtype === 'TrueType' && !c.nonsymbolic)
|
|
447
|
+
base = WIN_ANSI_ENCODING;
|
|
448
|
+
else
|
|
449
|
+
base = STANDARD_ENCODING;
|
|
450
|
+
}
|
|
451
|
+
for (let i = 0; i < 256; i++)
|
|
452
|
+
table[i] = base[i];
|
|
453
|
+
const baseNames = [...table];
|
|
454
|
+
let code = 0;
|
|
455
|
+
for (const item of differences) {
|
|
456
|
+
if (item instanceof PDFNumber)
|
|
457
|
+
code = item.asNumber();
|
|
458
|
+
else if (item instanceof PDFName) {
|
|
459
|
+
if (code >= 0 && code < 256)
|
|
460
|
+
table[code] = item.decodeText();
|
|
461
|
+
code++;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return { names: table, base: baseNames };
|
|
465
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @doccloak/core/pdf - Adobe Glyph List (T207).
|
|
3
|
+
*
|
|
4
|
+
* Glyph name -> Unicode for simple fonts whose encoding is expressed as glyph
|
|
5
|
+
* names (Annex D tables, /Differences arrays, Type 1 built-in encodings), and
|
|
6
|
+
* the reverse lookup used to check whether a placeholder can be encoded in a
|
|
7
|
+
* document font.
|
|
8
|
+
*
|
|
9
|
+
* The table is a subset of the Adobe Glyph List 2.0 (aglfn + AGL) chosen for
|
|
10
|
+
* European text: Basic Latin, Latin-1, Latin Extended-A/B and Additional,
|
|
11
|
+
* spacing modifiers, Greek, Cyrillic (afii names), general punctuation,
|
|
12
|
+
* super/subscripts, currency, letterlike, number forms, arrows, mathematical
|
|
13
|
+
* operators, technical, blocks, geometric shapes, miscellaneous symbols and
|
|
14
|
+
* dingbats, the fi/fl/ff/ffi/ffl/st presentation forms, the Adobe corporate
|
|
15
|
+
* use area (small caps, superiors, Symbol font extenders), every name used by
|
|
16
|
+
* the Annex D encodings, and the ITC Zapf Dingbats list (a1..a191). Hebrew,
|
|
17
|
+
* Arabic, Thai, CJK and Hangul names are left out on purpose.
|
|
18
|
+
*
|
|
19
|
+
* Name resolution follows the AGL specification (section 3 "Mapping glyph
|
|
20
|
+
* names to Unicode"): strip everything from the first period, split ligature
|
|
21
|
+
* components on underscore, then per component: AGL lookup, `uniXXXX[YYYY...]`
|
|
22
|
+
* (4-digit groups, BMP, no surrogates), `uXXXX`..`uXXXXXX`. Anything else
|
|
23
|
+
* (`g12`, `cid12`, `glyph12`, `G12`, `.notdef`) yields undefined.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Maps a glyph name to the text it represents, or undefined when the name
|
|
27
|
+
* carries no Unicode meaning. The result may be several code points
|
|
28
|
+
* (`uni00660069`, `f_i`).
|
|
29
|
+
*/
|
|
30
|
+
export declare function glyphNameToUnicode(name: string): string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Reverse of the AGL table for a single code point: the glyph name a simple
|
|
33
|
+
* font is expected to use for `ch`. Names used by the Annex D encodings take
|
|
34
|
+
* precedence, then the first AGL name in list order. Undefined for input that
|
|
35
|
+
* is not exactly one code point or has no AGL name.
|
|
36
|
+
*/
|
|
37
|
+
export declare function unicodeToGlyphName(ch: string): string | undefined;
|