reamkit 1.29.0 → 1.30.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/esm/core/document-model/types.d.ts +2 -0
- package/dist/esm/core/drawingml/shape-render.js +13 -1
- package/dist/esm/core/fonts/index.d.ts +1 -1
- package/dist/esm/core/fonts/remote-fonts.d.ts +8 -0
- package/dist/esm/core/fonts/remote-fonts.js +100 -17
- package/dist/esm/core/ir/flow.d.ts +17 -0
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/pdf-reader/annot-draw.js +93 -1
- package/dist/esm/pdf-reader/annots.d.ts +18 -0
- package/dist/esm/pdf-reader/annots.js +86 -9
- package/dist/esm/pdf-reader/cmap.js +5 -2
- package/dist/esm/pdf-reader/content.d.ts +17 -4
- package/dist/esm/pdf-reader/content.js +163 -11
- package/dist/esm/pdf-reader/display.js +16 -0
- package/dist/esm/pdf-reader/embedded-fonts.d.ts +24 -0
- package/dist/esm/pdf-reader/embedded-fonts.js +48 -9
- package/dist/esm/pdf-reader/flow-build.d.ts +96 -5
- package/dist/esm/pdf-reader/flow-build.js +210 -23
- package/dist/esm/pdf-reader/font.d.ts +27 -1
- package/dist/esm/pdf-reader/font.js +313 -29
- package/dist/esm/pdf-reader/glyf-outline.js +13 -2
- package/dist/esm/pdf-reader/glyph-shapes.d.ts +18 -0
- package/dist/esm/pdf-reader/glyph-shapes.js +57 -0
- package/dist/esm/pdf-reader/image-decode.js +70 -4
- package/dist/esm/pdf-reader/images.d.ts +5 -0
- package/dist/esm/pdf-reader/images.js +4 -2
- package/dist/esm/pdf-reader/jbig2.d.ts +40 -1
- package/dist/esm/pdf-reader/jbig2.js +78 -16
- package/dist/esm/pdf-reader/jpeg.d.ts +6 -3
- package/dist/esm/pdf-reader/jpeg.js +21 -1
- package/dist/esm/pdf-reader/layout.d.ts +26 -2
- package/dist/esm/pdf-reader/layout.js +638 -92
- package/dist/esm/pdf-reader/lexer.d.ts +10 -0
- package/dist/esm/pdf-reader/lexer.js +17 -0
- package/dist/esm/pdf-reader/pattern-tint.d.ts +11 -1
- package/dist/esm/pdf-reader/pattern-tint.js +21 -3
- package/dist/esm/pdf-reader/regions.d.ts +25 -0
- package/dist/esm/pdf-reader/regions.js +167 -0
- package/dist/esm/pdf-reader/shading.d.ts +58 -2
- package/dist/esm/pdf-reader/shading.js +181 -11
- package/dist/esm/pdf-reader/struct-tree.js +112 -8
- package/dist/esm/pdf-reader/tagged.js +27 -7
- package/dist/esm/pdf-reader/text-rules.js +1 -1
- package/dist/esm/pdf-reader/text.js +9 -14
- package/dist/esm/pdf-reader/vector.d.ts +8 -2
- package/dist/esm/pdf-reader/vector.js +34 -5
- package/dist/esm/word/docx-writer.js +137 -36
- package/dist/esm/word/drawing-parser.js +7 -2
- package/dist/esm/word/paragraph-properties.js +2 -0
- package/package.json +5 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveFamilyStyle } from "../core/fonts/remote-fonts.js";
|
|
1
|
+
import { knowsFamily, resolveFamilyStyle } from "../core/fonts/remote-fonts.js";
|
|
2
2
|
import { parseTtf } from "../core/font/ttf-parser.js";
|
|
3
3
|
import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
|
|
4
4
|
import { parseToUnicodeCMap } from "./cmap.js";
|
|
@@ -7,10 +7,10 @@ import { isZapfDingbats, zapfDingbatsChar } from "./dingbats.js";
|
|
|
7
7
|
import { textForGlyphName } from "./glyph-names.js";
|
|
8
8
|
import { cffCidToGid, cffNameToGid, cffOutlineSource, openTypeCff } from "./cff-outline.js";
|
|
9
9
|
import { type1Font } from "./type1-outline.js";
|
|
10
|
-
import { baseEncodingTable, isStandardLatinFace, standardEncodingTable } from "./encodings.js";
|
|
10
|
+
import { baseEncodingTable, isStandardLatinFace, macGlyphName, standardEncodingTable } from "./encodings.js";
|
|
11
11
|
import { outlineSource, postGlyphNames } from "./glyf-outline.js";
|
|
12
12
|
import { standardFace, standardWidth } from "./standard-widths.js";
|
|
13
|
-
import { embeddedFontName, hasLiftableProgram } from "./embedded-fonts.js";
|
|
13
|
+
import { eachPageFont, embeddedFontName, hasLiftableProgram, programStyle } from "./embedded-fonts.js";
|
|
14
14
|
//#region src/pdf-reader/font.ts
|
|
15
15
|
/** §9.10.2 — what a composite code nothing can answer for comes to. */
|
|
16
16
|
var UNANSWERABLE = "�";
|
|
@@ -40,6 +40,8 @@ function buildContentFont(file, fontDict) {
|
|
|
40
40
|
if (isType0 && !encodingNamed) codeBytes = parsed.codeBytes;
|
|
41
41
|
}
|
|
42
42
|
const fromProgram = isType0 && toUnicode.size === 0 ? embeddedCmap(file, fontDict) : void 0;
|
|
43
|
+
const fromCids = isType0 && toUnicode.size === 0 && fromProgram === void 0 ? unicodeCids(file, fontDict) : void 0;
|
|
44
|
+
const fromCore = isType0 && toUnicode.size === 0 && fromProgram === void 0 && fromCids === void 0 ? coreGlyphOrder(file, fontDict) : void 0;
|
|
43
45
|
let programFallback = null;
|
|
44
46
|
const fromProgramFor = (code) => {
|
|
45
47
|
if (!isType0 || toUnicode.size === 0) return void 0;
|
|
@@ -47,16 +49,27 @@ function buildContentFont(file, fontDict) {
|
|
|
47
49
|
return programFallback?.get(code);
|
|
48
50
|
};
|
|
49
51
|
const glyphs = isType0 ? void 0 : simpleGlyphs(file, fontDict);
|
|
52
|
+
let byIndex = null;
|
|
53
|
+
const numbering = isType0 ? void 0 : numberingOf(differences(file, fontDict).values());
|
|
54
|
+
const fromIndex = (name) => {
|
|
55
|
+
const gid = numberedGlyph(name, numbering);
|
|
56
|
+
if (gid === void 0) return void 0;
|
|
57
|
+
byIndex ??= programCharacters(file, fontDict);
|
|
58
|
+
return byIndex?.get(gid);
|
|
59
|
+
};
|
|
50
60
|
const fromNames = !isType0 ? namedGlyphs(file, fontDict, toUnicode.size > 0, {
|
|
51
61
|
draws: (name) => glyphs?.byName(name) !== void 0,
|
|
52
|
-
blank: (name) => glyphs?.blank(name) === true
|
|
62
|
+
blank: (name) => glyphs?.blank(name) === true,
|
|
63
|
+
character: fromIndex
|
|
53
64
|
}) : void 0;
|
|
54
65
|
const glyphNames = isType0 ? /* @__PURE__ */ new Map() : differences(file, fontDict);
|
|
55
|
-
const unicode = fromProgram ?? (toUnicode.size > 0 ? toUnicode : fromNames ?? toUnicode);
|
|
66
|
+
const unicode = fromProgram ?? fromCids ?? fromCore ?? (toUnicode.size > 0 ? toUnicode : fromNames ?? toUnicode);
|
|
56
67
|
const bytesPerCode = codeBytes;
|
|
57
68
|
const indices = !isType0 && glyphs?.indexed === true && fromNames === void 0 && toUnicode.size === 0 && glyphNames.size === 0;
|
|
58
69
|
const dingbats = !isType0 && isZapfDingbats(asName(file.resolve(fontDict.get("BaseFont") ?? PDF_NULL)));
|
|
59
|
-
const
|
|
70
|
+
const stated = file.resolve(fontDict.get("Encoding") ?? PDF_NULL);
|
|
71
|
+
const statesEncoding = stated instanceof Map || stated instanceof PdfName && baseEncodingTable(stated.value) !== void 0;
|
|
72
|
+
const baseNames = isType0 ? void 0 : statesEncoding ? baseEncoding(file, fontDict) : glyphs?.builtIn ?? baseEncoding(file, fontDict);
|
|
60
73
|
const fromBase = /* @__PURE__ */ new Map();
|
|
61
74
|
for (const [code, glyph] of baseNames ?? []) {
|
|
62
75
|
const text = textForGlyphName(glyph);
|
|
@@ -66,7 +79,7 @@ function buildContentFont(file, fontDict) {
|
|
|
66
79
|
const style = faceStyle(file, fontDict, isType0);
|
|
67
80
|
const name = runFontName(file, fontDict, isType0);
|
|
68
81
|
const type3 = asName(file.resolve(fontDict.get("Subtype") ?? PDF_NULL)) === "Type3" ? type3Face(file, fontDict) : void 0;
|
|
69
|
-
const decodeOne = (code) => (indices ? UNANSWERABLE : void 0) ?? unicode.get(code) ?? fromProgramFor(code) ?? (named ? decodePredefined(named, code) : void 0) ?? fromNames?.get(code) ?? (dingbats ? zapfDingbatsChar(code) : void 0) ?? fromBase.get(code) ?? (bytesPerCode === 1 ? latin1(code) : UNANSWERABLE);
|
|
82
|
+
const decodeOne = (code) => (indices ? UNANSWERABLE : void 0) ?? unicode.get(code) ?? fromProgramFor(code) ?? (named ? decodePredefined(named, code) : void 0) ?? fromNames?.get(code) ?? (glyphNames.get(code) === NOTDEF ? "" : void 0) ?? (dingbats ? zapfDingbatsChar(code) : void 0) ?? fromBase.get(code) ?? (bytesPerCode === 1 ? latin1(code) : UNANSWERABLE);
|
|
70
83
|
const simple = simpleWidths(file, fontDict, decodeOne);
|
|
71
84
|
const width = isType0 ? cidWidths(file, fontDict) : type3 ? (code) => simple(code) * type3.matrix[0] * 1e3 : simple;
|
|
72
85
|
return {
|
|
@@ -76,7 +89,7 @@ function buildContentFont(file, fontDict) {
|
|
|
76
89
|
...named?.vertical ? { verticalAdvance: cidVerticalAdvance(file, fontDict) } : {},
|
|
77
90
|
...type3 ? { type3 } : {},
|
|
78
91
|
...name !== void 0 ? { name } : {},
|
|
79
|
-
decode: (codes) => codes.map((c) => readable(decodeOne(c))).join(""),
|
|
92
|
+
decode: (codes) => codes.map((c) => lettersOf(readable(decodeOne(c)))).join(""),
|
|
80
93
|
width,
|
|
81
94
|
...style
|
|
82
95
|
};
|
|
@@ -114,6 +127,32 @@ function readable(text) {
|
|
|
114
127
|
}
|
|
115
128
|
return out;
|
|
116
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Unicode's presentation forms of the Latin ligatures, U+FB00–U+FB06, as the
|
|
132
|
+
* letters they join.
|
|
133
|
+
*
|
|
134
|
+
* A producer maps a ligature glyph to its compatibility character, and carried
|
|
135
|
+
* as that the word neither searches nor edits as the letters it is — and a
|
|
136
|
+
* face with no such form sets it in another's, wider: copy_paste_ligatures.pdf
|
|
137
|
+
* is one line in Times, and its "fffiflffifflſtst" pushed the line off its page. The long
|
|
138
|
+
* s of `ſt` is kept: it is a letter of its own.
|
|
139
|
+
*/
|
|
140
|
+
function lettersOf(text) {
|
|
141
|
+
return LIGATURE.test(text) ? text.replace(LIGATURES, (c) => LATIN_LIGATURES.get(c) ?? c) : text;
|
|
142
|
+
}
|
|
143
|
+
var LIGATURE = /[\uFB00-\uFB06]/u;
|
|
144
|
+
var LIGATURES = /[\uFB00-\uFB06]/gu;
|
|
145
|
+
var LATIN_LIGATURES = new Map([
|
|
146
|
+
["ff", "ff"],
|
|
147
|
+
["fi", "fi"],
|
|
148
|
+
["fl", "fl"],
|
|
149
|
+
["ffi", "ffi"],
|
|
150
|
+
["ffl", "ffl"],
|
|
151
|
+
["ſt", "ſt"],
|
|
152
|
+
["st", "st"]
|
|
153
|
+
]);
|
|
154
|
+
/** The glyph name that selects no glyph (§9.6.6.1). */
|
|
155
|
+
var NOTDEF = ".notdef";
|
|
117
156
|
/** The last Unicode code point in the BMP, and the surrogate block inside it. */
|
|
118
157
|
var BMP_END = 65535;
|
|
119
158
|
var SURROGATE_FIRST = 55296;
|
|
@@ -133,10 +172,130 @@ var SURROGATE_LAST = 57343;
|
|
|
133
172
|
*/
|
|
134
173
|
function embeddedCmap(file, fontDict) {
|
|
135
174
|
const cidFont = descendantFont(file, fontDict);
|
|
175
|
+
const byGlyph = programCharacters(file, cidFont);
|
|
176
|
+
if (byGlyph === void 0) return void 0;
|
|
177
|
+
const cidToGid = readCidToGid(file, cidFont);
|
|
178
|
+
if (!cidToGid) return byGlyph;
|
|
179
|
+
const out = /* @__PURE__ */ new Map();
|
|
180
|
+
cidToGid.forEach((gid, cid) => {
|
|
181
|
+
const text = byGlyph.get(gid);
|
|
182
|
+
if (text !== void 0) out.set(cid, text);
|
|
183
|
+
});
|
|
184
|
+
return out.size > 0 ? out : void 0;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* §9.7.4 — the characters of a composite font that embeds no program, names a
|
|
188
|
+
* core face and states no `/ToUnicode`: its codes are glyph indices into the
|
|
189
|
+
* face the reader's machine carries under that name, and the core faces put
|
|
190
|
+
* their first 258 glyphs in the standard Macintosh order (the `post` table's
|
|
191
|
+
* format 1 names).
|
|
192
|
+
*
|
|
193
|
+
* issue11242_reduced.pdf shows "VAT Code" in a Verdana it does not embed.
|
|
194
|
+
* Nothing in the file says what its codes are but the face's own order, and
|
|
195
|
+
* read as characters they are "9$7&RGH"; read as nothing, the page came back
|
|
196
|
+
* blank. Only faces known to keep that order are read this way — a face's
|
|
197
|
+
* order is its own, and Calibri's is not this one.
|
|
198
|
+
*
|
|
199
|
+
* @param file The owning file.
|
|
200
|
+
* @param fontDict The `/Type0` font dictionary.
|
|
201
|
+
* @returns Code → character, or `undefined` where the font is not such a one.
|
|
202
|
+
*/
|
|
203
|
+
function coreGlyphOrder(file, fontDict) {
|
|
204
|
+
const encoding = asName(file.resolve(fontDict.get("Encoding") ?? PDF_NULL));
|
|
205
|
+
if (encoding !== "Identity-H" && encoding !== "Identity-V") return void 0;
|
|
206
|
+
const cidFont = descendantFont(file, fontDict);
|
|
207
|
+
if (asName(file.resolve(cidFont.get("Subtype") ?? PDF_NULL)) !== "CIDFontType2") return void 0;
|
|
208
|
+
const map = file.resolve(cidFont.get("CIDToGIDMap") ?? PDF_NULL);
|
|
209
|
+
if (map !== PDF_NULL && !(map instanceof PdfName && map.value === "Identity")) return void 0;
|
|
210
|
+
const descriptor = file.resolve(cidFont.get("FontDescriptor") ?? PDF_NULL);
|
|
211
|
+
if (descriptor instanceof Map && [
|
|
212
|
+
"FontFile",
|
|
213
|
+
"FontFile2",
|
|
214
|
+
"FontFile3"
|
|
215
|
+
].some((k) => descriptor.has(k))) return void 0;
|
|
216
|
+
const family = familyOfFace(asName(file.resolve(fontDict.get("BaseFont") ?? PDF_NULL)));
|
|
217
|
+
if (!CORE_FACES.has(family.toLowerCase().replace(/[^a-z]/gu, ""))) return void 0;
|
|
218
|
+
const out = /* @__PURE__ */ new Map();
|
|
219
|
+
for (let gid = 0; gid < MAC_GLYPHS; gid++) {
|
|
220
|
+
const name = macGlyphName(gid);
|
|
221
|
+
const text = name === void 0 ? void 0 : textForGlyphName(name);
|
|
222
|
+
if (text !== void 0 && text !== "") out.set(gid, text);
|
|
223
|
+
}
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
/** The core faces whose first glyphs stand in the standard Macintosh order. */
|
|
227
|
+
var CORE_FACES = new Set([
|
|
228
|
+
"arial",
|
|
229
|
+
"timesnewroman",
|
|
230
|
+
"couriernew",
|
|
231
|
+
"verdana",
|
|
232
|
+
"tahoma",
|
|
233
|
+
"georgia",
|
|
234
|
+
"trebuchetms"
|
|
235
|
+
]);
|
|
236
|
+
/** How many glyphs the standard Macintosh order names. */
|
|
237
|
+
var MAC_GLYPHS = 258;
|
|
238
|
+
/**
|
|
239
|
+
* §9.7.4.2 — the CIDs of a composite font that ARE characters.
|
|
240
|
+
*
|
|
241
|
+
* A `/CIDToGIDMap` stream says the CID is not the glyph index, and TCPDF —
|
|
242
|
+
* with tFPDF and mPDF after it — makes it the character's own Unicode value,
|
|
243
|
+
* mapping every character of the face to its glyph whether the subset kept
|
|
244
|
+
* the glyph or not. Without a `/ToUnicode`, and with a subset that carries no
|
|
245
|
+
* `cmap`, that was the only statement of what the glyphs are and it went
|
|
246
|
+
* unread: bug1650302_reduced.pdf's "Výbava na přání" came back as drawings,
|
|
247
|
+
* the `ř` missing from them. Read as characters, the words come back.
|
|
248
|
+
*
|
|
249
|
+
* Only where the map itself says so. A producer that keeps the ORIGINAL
|
|
250
|
+
* glyph index as the CID routes it through a stream too, and read as Unicode
|
|
251
|
+
* complex_ttf_font.pdf's Arabic would come back as `$&')`. What tells the two
|
|
252
|
+
* apart is the space: a map of characters sends U+0020 to a glyph that draws
|
|
253
|
+
* nothing and no control character anywhere, where a map of indices sends 3 —
|
|
254
|
+
* the space in the fonts they come from — and 32 to a glyph with ink on it.
|
|
255
|
+
*/
|
|
256
|
+
function unicodeCids(file, fontDict) {
|
|
257
|
+
const encoding = asName(file.resolve(fontDict.get("Encoding") ?? PDF_NULL));
|
|
258
|
+
if (encoding !== "Identity-H" && encoding !== "Identity-V") return void 0;
|
|
259
|
+
const cidFont = descendantFont(file, fontDict);
|
|
260
|
+
const cidToGid = readCidToGid(file, cidFont);
|
|
261
|
+
if (!cidToGid || cidToGid.slice(0, SPACE).some((gid) => gid !== 0)) return void 0;
|
|
262
|
+
const space = cidToGid[SPACE];
|
|
263
|
+
if (space === void 0 || space === 0) return void 0;
|
|
136
264
|
const descriptor = file.resolve(cidFont.get("FontDescriptor") ?? PDF_NULL);
|
|
137
265
|
if (!(descriptor instanceof Map)) return void 0;
|
|
138
266
|
const program = file.resolve(descriptor.get("FontFile2") ?? PDF_NULL);
|
|
139
267
|
if (!(program instanceof PdfStream)) return void 0;
|
|
268
|
+
let blank;
|
|
269
|
+
try {
|
|
270
|
+
const glyf = outlineSource(file.streamData(program));
|
|
271
|
+
blank = glyf !== void 0 && space < glyf.count && glyf.path(space) === void 0;
|
|
272
|
+
} catch {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (!blank) return void 0;
|
|
276
|
+
const out = /* @__PURE__ */ new Map();
|
|
277
|
+
cidToGid.forEach((gid, cid) => {
|
|
278
|
+
if (gid !== 0 && !(cid >= SURROGATE_FIRST && cid <= SURROGATE_LAST)) out.set(cid, String.fromCharCode(cid));
|
|
279
|
+
});
|
|
280
|
+
return out;
|
|
281
|
+
}
|
|
282
|
+
/** U+0020, the one character whose glyph every text face leaves blank. */
|
|
283
|
+
var SPACE = 32;
|
|
284
|
+
/**
|
|
285
|
+
* The character each glyph of a font's embedded TrueType program stands for:
|
|
286
|
+
* its `cmap` read backwards.
|
|
287
|
+
*
|
|
288
|
+
* @param file The document.
|
|
289
|
+
* @param owner The dictionary that holds the descriptor — the font itself, or
|
|
290
|
+
* a composite font's descendant.
|
|
291
|
+
* @returns Glyph index → character, or `undefined` where there is no program
|
|
292
|
+
* or it maps nothing.
|
|
293
|
+
*/
|
|
294
|
+
function programCharacters(file, owner) {
|
|
295
|
+
const descriptor = file.resolve(owner.get("FontDescriptor") ?? PDF_NULL);
|
|
296
|
+
if (!(descriptor instanceof Map)) return void 0;
|
|
297
|
+
const program = file.resolve(descriptor.get("FontFile2") ?? PDF_NULL);
|
|
298
|
+
if (!(program instanceof PdfStream)) return void 0;
|
|
140
299
|
let glyphOf;
|
|
141
300
|
try {
|
|
142
301
|
glyphOf = parseTtf(file.streamData(program)).glyphForCodepoint;
|
|
@@ -154,15 +313,7 @@ function embeddedCmap(file, fontDict) {
|
|
|
154
313
|
}
|
|
155
314
|
if (gid > 0 && !byGlyph.has(gid)) byGlyph.set(gid, String.fromCodePoint(cp));
|
|
156
315
|
}
|
|
157
|
-
|
|
158
|
-
const cidToGid = readCidToGid(file, cidFont);
|
|
159
|
-
if (!cidToGid) return byGlyph;
|
|
160
|
-
const out = /* @__PURE__ */ new Map();
|
|
161
|
-
cidToGid.forEach((gid, cid) => {
|
|
162
|
-
const text = byGlyph.get(gid);
|
|
163
|
-
if (text !== void 0) out.set(cid, text);
|
|
164
|
-
});
|
|
165
|
-
return out.size > 0 ? out : void 0;
|
|
316
|
+
return byGlyph.size > 0 ? byGlyph : void 0;
|
|
166
317
|
}
|
|
167
318
|
/**
|
|
168
319
|
* §9.6.6 — the outline a code draws, for a code that stands for no character.
|
|
@@ -278,6 +429,7 @@ function simpleGlyphs(file, fontDict) {
|
|
|
278
429
|
const typeOne = file.resolve(descriptor.get("FontFile") ?? PDF_NULL);
|
|
279
430
|
const truetype = file.resolve(descriptor.get("FontFile2") ?? PDF_NULL);
|
|
280
431
|
const compact = file.resolve(descriptor.get("FontFile3") ?? PDF_NULL);
|
|
432
|
+
const numbering = numberingOf(differences(file, fontDict).values());
|
|
281
433
|
try {
|
|
282
434
|
if (typeOne instanceof PdfStream) {
|
|
283
435
|
const face = type1Font(file.streamData(typeOne));
|
|
@@ -295,7 +447,7 @@ function simpleGlyphs(file, fontDict) {
|
|
|
295
447
|
if (glyf) {
|
|
296
448
|
const named = postGlyphNames(bytes);
|
|
297
449
|
const gidOf = (name) => {
|
|
298
|
-
const gid = named?.get(name) ?? numberedGlyph(name);
|
|
450
|
+
const gid = named?.get(name) ?? numberedGlyph(name, numbering);
|
|
299
451
|
return gid !== void 0 && gid < glyf.count ? gid : void 0;
|
|
300
452
|
};
|
|
301
453
|
return {
|
|
@@ -316,7 +468,7 @@ function simpleGlyphs(file, fontDict) {
|
|
|
316
468
|
if (!outlines) return void 0;
|
|
317
469
|
const names = cffNameToGid(cff);
|
|
318
470
|
const gidOf = (name) => {
|
|
319
|
-
const gid = names?.get(name) ?? numberedGlyph(name);
|
|
471
|
+
const gid = names?.get(name) ?? numberedGlyph(name, numbering);
|
|
320
472
|
return gid !== void 0 && gid < outlines.count ? gid : void 0;
|
|
321
473
|
};
|
|
322
474
|
return {
|
|
@@ -340,9 +492,13 @@ function simpleGlyphs(file, fontDict) {
|
|
|
340
492
|
* INDEX — `g24`, `glyph24`, `index24`, `cid24` — and that name is then the only
|
|
341
493
|
* way back to the outline. bug1151216.pdf names them `g24`, `g381`, `g3`, and
|
|
342
494
|
* its three lines of prices are drawn from nothing else.
|
|
495
|
+
*
|
|
496
|
+
* @param name The glyph's name.
|
|
497
|
+
* @param numbering How the font writes the number, where its names say (see
|
|
498
|
+
* {@link numberingOf}); four digits are hexadecimal otherwise.
|
|
343
499
|
*/
|
|
344
|
-
function numberedGlyph(name) {
|
|
345
|
-
const hex = /^g([0-9a-f]{4})$/u.exec(name);
|
|
500
|
+
function numberedGlyph(name, numbering) {
|
|
501
|
+
const hex = numbering !== "decimal" ? /^g([0-9a-f]{4})$/u.exec(name) : null;
|
|
346
502
|
if (hex) return Number.parseInt(hex[1], 16);
|
|
347
503
|
const m = /^(?:g|glyph|index|cid|G)(\d+)$/u.exec(name);
|
|
348
504
|
const n = m ? Number(m[1]) : NaN;
|
|
@@ -350,6 +506,33 @@ function numberedGlyph(name) {
|
|
|
350
506
|
}
|
|
351
507
|
/** No font holds more glyphs than this; a bigger number is not an index. */
|
|
352
508
|
var MAX_GLYPH_INDEX = 65536;
|
|
509
|
+
/**
|
|
510
|
+
* How a font's index names write their numbers, where its names say.
|
|
511
|
+
*
|
|
512
|
+
* A subsetter that pads the number to four digits writes it in hexadecimal —
|
|
513
|
+
* bug1027533.pdf's `g0024` is glyph 36 — and one that does not pad writes it
|
|
514
|
+
* in decimal. One name cannot tell the two apart: bug1151216.pdf names its
|
|
515
|
+
* glyphs `g24`, `g381` and `g1004`, and `g1004` is four digits either way.
|
|
516
|
+
* Read as hexadecimal it is glyph 4100, which no subset of a thousand glyphs
|
|
517
|
+
* holds: five of the file's codes drew nothing, fell back to Latin-1, and its
|
|
518
|
+
* prices came back "$@'' for 1" set over the glyphs that were drawn. The rest
|
|
519
|
+
* of the font's names say which it is — a leading zero or a letter is
|
|
520
|
+
* hexadecimal, a number of any length but four is decimal.
|
|
521
|
+
*
|
|
522
|
+
* @param names The glyph names the font's `/Differences` gives.
|
|
523
|
+
* @returns The numbering, or `undefined` where the names do not say.
|
|
524
|
+
*/
|
|
525
|
+
function numberingOf(names) {
|
|
526
|
+
let hex = false;
|
|
527
|
+
let decimal = false;
|
|
528
|
+
for (const name of names) {
|
|
529
|
+
const digits = /^g([0-9a-f]+)$/u.exec(name)?.[1];
|
|
530
|
+
if (digits === void 0) continue;
|
|
531
|
+
if (/[a-f]/u.test(digits) || digits.length === 4 && digits.startsWith("0")) hex = true;
|
|
532
|
+
else if (digits.length !== 4) decimal = true;
|
|
533
|
+
}
|
|
534
|
+
return hex === decimal ? void 0 : hex ? "hex" : "decimal";
|
|
535
|
+
}
|
|
353
536
|
/** §9.7.4.2 `/CIDToGIDMap` — a stream of two-byte glyph indices, CID by CID. */
|
|
354
537
|
function readCidToGid(file, cidFont) {
|
|
355
538
|
const map = file.resolve(cidFont.get("CIDToGIDMap") ?? PDF_NULL);
|
|
@@ -419,7 +602,7 @@ function namedGlyphs(file, fontDict, statesUnicode, glyph) {
|
|
|
419
602
|
if (names.size === 0) return void 0;
|
|
420
603
|
const out = /* @__PURE__ */ new Map();
|
|
421
604
|
for (const [code, name] of names) {
|
|
422
|
-
const text = textForGlyphName(name);
|
|
605
|
+
const text = textForGlyphName(name) ?? glyph.character(name);
|
|
423
606
|
if (text !== void 0) {
|
|
424
607
|
out.set(code, text);
|
|
425
608
|
continue;
|
|
@@ -532,7 +715,8 @@ function differences(file, fontDict) {
|
|
|
532
715
|
function runFontName(file, fontDict, isType0) {
|
|
533
716
|
const name = embeddedFontName(file, fontDict);
|
|
534
717
|
if (name === void 0 || hasLiftableProgram(file, fontDict)) return name;
|
|
535
|
-
|
|
718
|
+
const face = familyOfFace(asName(file.resolve(fontDict.get("BaseFont") ?? PDF_NULL)));
|
|
719
|
+
if (knowsFamily(name) || knowsFamily(face)) return name;
|
|
536
720
|
const owner = isType0 ? descendantFont(file, fontDict) : fontDict;
|
|
537
721
|
const descriptor = file.resolve(owner.get("FontDescriptor") ?? PDF_NULL);
|
|
538
722
|
if (!(descriptor instanceof Map)) return name;
|
|
@@ -541,6 +725,101 @@ function runFontName(file, fontDict, isType0) {
|
|
|
541
725
|
if ((flags & FLAG_SERIF) !== 0) return `${name} serif`;
|
|
542
726
|
return name;
|
|
543
727
|
}
|
|
728
|
+
/**
|
|
729
|
+
* For every face a run may name, the family a word processor knows it by —
|
|
730
|
+
* keyed by the name the run carries (see {@link FaceFamily}).
|
|
731
|
+
*
|
|
732
|
+
* A PDF names the FACE and a .docx names a family: written as the face,
|
|
733
|
+
* `inter-semibold` was a font no reader has, and LibreOffice set the whole of
|
|
734
|
+
* an invoice drawn in Inter in its default serif. The family is the one the
|
|
735
|
+
* descriptor states (§9.8.1 `/FontFamily`) or, where it states none, the one
|
|
736
|
+
* the PostScript name is made of.
|
|
737
|
+
*
|
|
738
|
+
* @param file The document.
|
|
739
|
+
* @param pages The pages whose faces are wanted.
|
|
740
|
+
* @returns Run font name → its family.
|
|
741
|
+
*/
|
|
742
|
+
function collectFaceFamilies(file, pages) {
|
|
743
|
+
const out = /* @__PURE__ */ new Map();
|
|
744
|
+
eachPageFont(file, pages, (fontDict) => {
|
|
745
|
+
const isType0 = asName(file.resolve(fontDict.get("Subtype") ?? PDF_NULL)) === "Type0";
|
|
746
|
+
const key = runFontName(file, fontDict, isType0);
|
|
747
|
+
if (key === void 0 || out.has(key)) return;
|
|
748
|
+
const owner = isType0 ? descendantFont(file, fontDict) : fontDict;
|
|
749
|
+
const descriptor = file.resolve(owner.get("FontDescriptor") ?? PDF_NULL);
|
|
750
|
+
const stated = descriptor instanceof Map ? file.resolve(descriptor.get("FontFamily") ?? PDF_NULL) : PDF_NULL;
|
|
751
|
+
const family = typeof stated === "string" && /^[\x20-\x7e]+$/u.test(stated.trim()) ? stated.trim() : familyOfFace(cidFontName(file, fontDict, isType0));
|
|
752
|
+
if (family.length === 0) return;
|
|
753
|
+
const flags = descriptor instanceof Map ? asNumber(file.resolve(descriptor.get("Flags") ?? PDF_NULL), 0) : 0;
|
|
754
|
+
out.set(key, {
|
|
755
|
+
family,
|
|
756
|
+
generic: genericOf(family, flags)
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
return out;
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* §9.7.6.1 — a composite font's `/BaseFont` is its CIDFont's name, a hyphen and
|
|
763
|
+
* the name of the CMap it is encoded by (`HeiseiMin-W3-UniJIS-UCS2-H`); the
|
|
764
|
+
* face is the part before the CMap's.
|
|
765
|
+
*/
|
|
766
|
+
function cidFontName(file, fontDict, isType0) {
|
|
767
|
+
const base = asName(file.resolve(fontDict.get("BaseFont") ?? PDF_NULL));
|
|
768
|
+
const cmap = isType0 ? asName(file.resolve(fontDict.get("Encoding") ?? PDF_NULL)) : "";
|
|
769
|
+
return cmap !== "" && base.endsWith(`-${cmap}`) ? base.slice(0, -cmap.length - 1) : base;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* The family a PostScript face name is made of: `Inter-SemiBold` → `Inter`,
|
|
773
|
+
* `ArialMT` → `Arial`, `TimesNewRomanPS-BoldMT` → `Times New Roman`.
|
|
774
|
+
*
|
|
775
|
+
* §9.6.2.1 names a face `Family-Style` (Word writes `Family,Style`), with the
|
|
776
|
+
* family's words run together. What follows the separator is a style only if
|
|
777
|
+
* it is made of style words — `MS-Mincho` is a family of its own — and the
|
|
778
|
+
* words come apart where the capitals say they do.
|
|
779
|
+
*/
|
|
780
|
+
function familyOfFace(baseFont) {
|
|
781
|
+
const name = plainFace(baseFont);
|
|
782
|
+
const cut = /^(.+?)[-,]([^-,]+)$/u.exec(name);
|
|
783
|
+
const whole = cut && STYLE_WORDS.test(cut[2]) ? cut[1] : cut ? name.replace(/-/gu, " ") : GLUED_STYLE.exec(name)?.[1] ?? name;
|
|
784
|
+
return (whole.replace(/(?:PSMT|PS|MT)$/u, "") || whole).replace(/([a-z])([A-Z])/gu, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/gu, "$1 $2").replace(/\bDeja Vu\b/u, "DejaVu").replace(/\s+/gu, " ").trim();
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* A face name with what producers write around it taken off: the subset tag
|
|
788
|
+
* (§9.6.4) and, after the face, the charset Acrobat's Office export appends
|
|
789
|
+
* (`Arial,Bold-WinCharSetFFFF`, `-H2`), the encoding a CIDFont was cut for
|
|
790
|
+
* (`-Identity-H`, `-OneByteIdentityH`), a serial number, and the slant or
|
|
791
|
+
* stretch an interpreter synthesised (`-Slant_167`, `-Extend_850`). Left on,
|
|
792
|
+
* bug900822.pdf's .docx asked for "Courier New,Bold Win Char Set FFFF", which
|
|
793
|
+
* no machine has, over the Courier New every machine does.
|
|
794
|
+
*/
|
|
795
|
+
function plainFace(baseFont) {
|
|
796
|
+
let name = baseFont.replace(/^[A-Z]{6}\+/u, "").replace(/^[!-/:-@[-`{-~]+/u, "").trim();
|
|
797
|
+
for (let next = name.replace(PRODUCER_TAIL, ""); next !== name;) {
|
|
798
|
+
name = next;
|
|
799
|
+
next = name.replace(PRODUCER_TAIL, "");
|
|
800
|
+
}
|
|
801
|
+
return name;
|
|
802
|
+
}
|
|
803
|
+
/** One piece of producer debris at the end of a face name (see `plainFace`). */
|
|
804
|
+
var PRODUCER_TAIL = /[-_](?:WinCharSet[0-9A-F]+|Identity-[HV]|OneByteIdentity[HV]|H\d+|\d+|(?:Slant|Extend)_\d+)$/iu;
|
|
805
|
+
/**
|
|
806
|
+
* A style run on to a family with no separator — `CalibriBold` — which is the
|
|
807
|
+
* family's name only with the style taken off. Whether the face IS bold is the
|
|
808
|
+
* program's to say (see `faceStyle`): `NewBasrahBold` is a family of its own.
|
|
809
|
+
*/
|
|
810
|
+
var GLUED_STYLE = /^(.*?[a-z])((?:Semi|Demi|Extra|Ultra)?(?:Bold|Black|Heavy)(?:Italic|Oblique)?|Italic|Oblique)$/u;
|
|
811
|
+
/** A PostScript style part, made of nothing but style words (`SemiBoldItalic`, `BoldMT`, `Regu`). */
|
|
812
|
+
var STYLE_WORDS = /^(?:regular|regu|roman|book|normal|plain|medium|medi|light|thin|hairline|extra|ultra|semi|demi|bold|bd|black|heavy|italic|ital|it|oblique|obl|condensed|cond|narrow|compressed|extended|mt|ps)+$/iu;
|
|
813
|
+
/** §17.8.3.10 — the kind of face a family is, from the descriptor or, failing that, its name. */
|
|
814
|
+
function genericOf(family, flags) {
|
|
815
|
+
if (!knowsFamily(family)) {
|
|
816
|
+
if ((flags & FLAG_FIXED_PITCH) !== 0) return "modern";
|
|
817
|
+
if ((flags & FLAG_SERIF) !== 0) return "roman";
|
|
818
|
+
}
|
|
819
|
+
const key = resolveFamilyStyle(family).key;
|
|
820
|
+
if (key === "cousine") return "modern";
|
|
821
|
+
return key === "tinos" || key === "caladea" ? "roman" : "swiss";
|
|
822
|
+
}
|
|
544
823
|
/** §9.8.2 `/Flags` — bit 1 is FixedPitch, bit 2 Serif (bits numbered from 1). */
|
|
545
824
|
var FLAG_FIXED_PITCH = 1;
|
|
546
825
|
var FLAG_SERIF = 2;
|
|
@@ -575,8 +854,11 @@ function faceStyle(file, fontDict, isType0) {
|
|
|
575
854
|
const flags = asNumber(file.resolve(descriptor.get("Flags") ?? PDF_NULL), 0);
|
|
576
855
|
const weightVal = file.resolve(descriptor.get("FontWeight") ?? PDF_NULL);
|
|
577
856
|
const slant = asNumber(file.resolve(descriptor.get("ItalicAngle") ?? PDF_NULL), 0);
|
|
578
|
-
const
|
|
579
|
-
const
|
|
857
|
+
const stated = typeof weightVal === "number" && weightVal >= LIGHTEST_WEIGHT;
|
|
858
|
+
const own = programStyle(file, fontDict);
|
|
859
|
+
const forced = (flags & FLAG_FORCE_BOLD) !== 0 && !named.light;
|
|
860
|
+
const bold = stated ? asNumber(weightVal, 0) >= BOLD_WEIGHT : forced || own?.bold === true || named.bold;
|
|
861
|
+
const italic = slant !== 0 || (flags & FLAG_ITALIC) !== 0 || own?.italic === true || named.italic;
|
|
580
862
|
return {
|
|
581
863
|
...bold ? { bold: true } : {},
|
|
582
864
|
...italic ? { italic: true } : {}
|
|
@@ -597,11 +879,13 @@ function faceStyle(file, fontDict, isType0) {
|
|
|
597
879
|
* set two lines heavy that no reader sets heavy. `Times-Bold` is.
|
|
598
880
|
*/
|
|
599
881
|
function styleFromName(baseFont) {
|
|
600
|
-
const name = baseFont
|
|
882
|
+
const name = plainFace(baseFont);
|
|
601
883
|
const style = /[-,]([A-Za-z]+)$/u.exec(name)?.[1] ?? "";
|
|
884
|
+
const bold = /bold|black|heavy|semib|demi/iu.test(style);
|
|
602
885
|
return {
|
|
603
|
-
bold
|
|
604
|
-
italic: /italic|oblique/iu.test(style)
|
|
886
|
+
bold,
|
|
887
|
+
italic: /italic|oblique/iu.test(style),
|
|
888
|
+
light: !bold && /light|thin|hairline|book|regular|roman|normal|medium/iu.test(style)
|
|
605
889
|
};
|
|
606
890
|
}
|
|
607
891
|
/** §9.7.4 — a `/Type0` font's one descendant CIDFont, which owns the descriptor. */
|
|
@@ -661,4 +945,4 @@ function asNumber(v, dflt) {
|
|
|
661
945
|
return typeof v === "number" ? v : dflt;
|
|
662
946
|
}
|
|
663
947
|
//#endregion
|
|
664
|
-
export { buildContentFont };
|
|
948
|
+
export { buildContentFont, collectFaceFamilies };
|
|
@@ -29,8 +29,19 @@ function outlineSource(program) {
|
|
|
29
29
|
if (at + stride * 2 > loca.offset + loca.length) return void 0;
|
|
30
30
|
const read = (o) => longLoca ? safeUint32(view, o) : safeUint16(view, o) * 2;
|
|
31
31
|
const start = read(at);
|
|
32
|
-
|
|
33
|
-
if (end
|
|
32
|
+
let end = read(at + stride);
|
|
33
|
+
if (end === start) return void 0;
|
|
34
|
+
if (end < start) {
|
|
35
|
+
end = glyf.length;
|
|
36
|
+
for (let o = at + stride * 2; o + stride <= loca.offset + loca.length; o += stride) {
|
|
37
|
+
const next = read(o);
|
|
38
|
+
if (next > start) {
|
|
39
|
+
end = Math.min(next, glyf.length);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (end <= start) return void 0;
|
|
44
|
+
}
|
|
34
45
|
if (glyf.offset + end > program.length) return void 0;
|
|
35
46
|
return {
|
|
36
47
|
start: glyf.offset + start,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PathSeg } from './content.js';
|
|
2
|
+
/** A glyph's contours, and the box they fill, in the page's y-up space. */
|
|
3
|
+
export interface GlyphShape {
|
|
4
|
+
readonly segs: ReadonlyArray<PathSeg>;
|
|
5
|
+
readonly minX: number;
|
|
6
|
+
readonly minY: number;
|
|
7
|
+
readonly maxX: number;
|
|
8
|
+
readonly maxY: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The punctuation mark a glyph's shape is, where it can be only one.
|
|
12
|
+
*
|
|
13
|
+
* @param glyph The glyph as the page draws it.
|
|
14
|
+
* @param baseline The y of the line it stands on.
|
|
15
|
+
* @param size The size the line is set at, which the shape is measured in.
|
|
16
|
+
* @returns The character, or `undefined` where the shape does not say.
|
|
17
|
+
*/
|
|
18
|
+
export declare function punctuationOf(glyph: GlyphShape, baseline: number, size: number): string | undefined;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//#region src/pdf-reader/glyph-shapes.ts
|
|
2
|
+
/**
|
|
3
|
+
* The punctuation mark a glyph's shape is, where it can be only one.
|
|
4
|
+
*
|
|
5
|
+
* @param glyph The glyph as the page draws it.
|
|
6
|
+
* @param baseline The y of the line it stands on.
|
|
7
|
+
* @param size The size the line is set at, which the shape is measured in.
|
|
8
|
+
* @returns The character, or `undefined` where the shape does not say.
|
|
9
|
+
*/
|
|
10
|
+
function punctuationOf(glyph, baseline, size) {
|
|
11
|
+
if (!(size > 0)) return void 0;
|
|
12
|
+
const width = (glyph.maxX - glyph.minX) / size;
|
|
13
|
+
const height = (glyph.maxY - glyph.minY) / size;
|
|
14
|
+
const bottom = (glyph.minY - baseline) / size;
|
|
15
|
+
const top = (glyph.maxY - baseline) / size;
|
|
16
|
+
const contours = glyph.segs.filter((s) => s.op === "move").length;
|
|
17
|
+
if (contours === 1 && height <= FLAT_EM && width >= height * 2.5) {
|
|
18
|
+
if (bottom < .15 || top > .6) return void 0;
|
|
19
|
+
if (width < HYPHEN_LONGEST_EM) return "-";
|
|
20
|
+
return width < EN_DASH_LONGEST_EM ? "–" : "—";
|
|
21
|
+
}
|
|
22
|
+
if (contours === 2 && width <= DOT_WIDEST_EM && height >= .3 && height <= .8) {
|
|
23
|
+
if (bottom > .15) return void 0;
|
|
24
|
+
return bottom < -.05 ? ";" : ":";
|
|
25
|
+
}
|
|
26
|
+
if (contours === 1 && width <= DOT_WIDEST_EM && height <= .4 && top <= .3) {
|
|
27
|
+
if (bottom >= -.05 && bottom <= .05 && height <= DOT_WIDEST_EM) return ".";
|
|
28
|
+
return bottom < -.05 ? "," : void 0;
|
|
29
|
+
}
|
|
30
|
+
if (contours === 1 && width <= .45 && height >= .75 && bottom <= -.05 && top >= .6) {
|
|
31
|
+
const peak = highestPoint(glyph.segs);
|
|
32
|
+
if (peak === void 0) return void 0;
|
|
33
|
+
return peak > (glyph.minX + glyph.maxX) / 2 ? "(" : ")";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** The x of the highest point a glyph's contours pass through. */
|
|
37
|
+
function highestPoint(segs) {
|
|
38
|
+
let best;
|
|
39
|
+
for (const s of segs) {
|
|
40
|
+
if (s.op === "close") continue;
|
|
41
|
+
if (best === void 0 || s.y > best.y) best = {
|
|
42
|
+
x: s.x,
|
|
43
|
+
y: s.y
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return best?.x;
|
|
47
|
+
}
|
|
48
|
+
/** The tallest a bar may be, in ems, and still be a dash rather than a block. */
|
|
49
|
+
var FLAT_EM = .15;
|
|
50
|
+
/** The longest a hyphen is, in ems; past it the bar is a dash. */
|
|
51
|
+
var HYPHEN_LONGEST_EM = .42;
|
|
52
|
+
/** The longest an en dash is, in ems; past it, an em dash. */
|
|
53
|
+
var EN_DASH_LONGEST_EM = .7;
|
|
54
|
+
/** The widest a dot is, in ems. */
|
|
55
|
+
var DOT_WIDEST_EM = .22;
|
|
56
|
+
//#endregion
|
|
57
|
+
export { punctuationOf };
|