reamkit 1.29.0 → 1.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +60 -24
  2. package/dist/esm/core/converter/project.js +3 -1
  3. package/dist/esm/core/crypto/offcrypto.js +1 -1
  4. package/dist/esm/core/document-model/index.d.ts +1 -1
  5. package/dist/esm/core/document-model/types.d.ts +65 -0
  6. package/dist/esm/core/drawingml/shape-render.js +13 -1
  7. package/dist/esm/core/font/index.d.ts +2 -0
  8. package/dist/esm/core/font/ttf-build.d.ts +113 -0
  9. package/dist/esm/core/font/ttf-build.js +1224 -0
  10. package/dist/esm/core/font/ttf-subset.d.ts +19 -0
  11. package/dist/esm/core/font/ttf-subset.js +14 -1
  12. package/dist/esm/core/fonts/index.d.ts +1 -1
  13. package/dist/esm/core/fonts/provider.d.ts +17 -0
  14. package/dist/esm/core/fonts/provider.js +27 -2
  15. package/dist/esm/core/fonts/remote-fonts.d.ts +8 -0
  16. package/dist/esm/core/fonts/remote-fonts.js +108 -18
  17. package/dist/esm/core/ir/flow.d.ts +88 -1
  18. package/dist/esm/core/numbering/index.d.ts +1 -1
  19. package/dist/esm/core/numbering/state.d.ts +11 -1
  20. package/dist/esm/core/numbering/state.js +10 -1
  21. package/dist/esm/core/style-cascade/resolver.js +35 -4
  22. package/dist/esm/core/style-cascade/types.d.ts +19 -1
  23. package/dist/esm/core/style-cascade/types.js +3 -0
  24. package/dist/esm/index.d.ts +2 -2
  25. package/dist/esm/layout/page-doc.d.ts +10 -3
  26. package/dist/esm/layout/styled-layout.d.ts +48 -7
  27. package/dist/esm/layout/styled-layout.js +881 -119
  28. package/dist/esm/layout/turned-section.d.ts +38 -0
  29. package/dist/esm/layout/turned-section.js +193 -0
  30. package/dist/esm/pdf/styled-page-emitter.js +78 -2
  31. package/dist/esm/pdf-reader/annot-draw.js +93 -1
  32. package/dist/esm/pdf-reader/annots.d.ts +18 -0
  33. package/dist/esm/pdf-reader/annots.js +86 -9
  34. package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
  35. package/dist/esm/pdf-reader/cff-outline.js +169 -21
  36. package/dist/esm/pdf-reader/cmap.js +5 -2
  37. package/dist/esm/pdf-reader/content.d.ts +70 -4
  38. package/dist/esm/pdf-reader/content.js +172 -12
  39. package/dist/esm/pdf-reader/display.d.ts +36 -0
  40. package/dist/esm/pdf-reader/display.js +82 -1
  41. package/dist/esm/pdf-reader/document.js +5 -1
  42. package/dist/esm/pdf-reader/embedded-fonts.d.ts +25 -0
  43. package/dist/esm/pdf-reader/embedded-fonts.js +78 -9
  44. package/dist/esm/pdf-reader/encodings.d.ts +8 -0
  45. package/dist/esm/pdf-reader/encodings.js +25 -3
  46. package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
  47. package/dist/esm/pdf-reader/face-outlines.js +362 -0
  48. package/dist/esm/pdf-reader/figures.d.ts +52 -0
  49. package/dist/esm/pdf-reader/figures.js +433 -0
  50. package/dist/esm/pdf-reader/flow-build.d.ts +224 -7
  51. package/dist/esm/pdf-reader/flow-build.js +545 -39
  52. package/dist/esm/pdf-reader/font.d.ts +27 -1
  53. package/dist/esm/pdf-reader/font.js +632 -63
  54. package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
  55. package/dist/esm/pdf-reader/glyf-outline.js +148 -3
  56. package/dist/esm/pdf-reader/glyph-names.js +154 -1
  57. package/dist/esm/pdf-reader/glyph-shapes.d.ts +18 -0
  58. package/dist/esm/pdf-reader/glyph-shapes.js +57 -0
  59. package/dist/esm/pdf-reader/image-decode.js +70 -4
  60. package/dist/esm/pdf-reader/images.d.ts +5 -0
  61. package/dist/esm/pdf-reader/images.js +4 -2
  62. package/dist/esm/pdf-reader/jbig2.d.ts +40 -1
  63. package/dist/esm/pdf-reader/jbig2.js +78 -16
  64. package/dist/esm/pdf-reader/jpeg.d.ts +6 -3
  65. package/dist/esm/pdf-reader/jpeg.js +21 -1
  66. package/dist/esm/pdf-reader/layout.d.ts +119 -2
  67. package/dist/esm/pdf-reader/layout.js +2219 -184
  68. package/dist/esm/pdf-reader/lexer.d.ts +10 -0
  69. package/dist/esm/pdf-reader/lexer.js +17 -0
  70. package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
  71. package/dist/esm/pdf-reader/page-numbers.js +167 -0
  72. package/dist/esm/pdf-reader/pattern-tint.d.ts +11 -1
  73. package/dist/esm/pdf-reader/pattern-tint.js +21 -3
  74. package/dist/esm/pdf-reader/regions.d.ts +25 -0
  75. package/dist/esm/pdf-reader/regions.js +167 -0
  76. package/dist/esm/pdf-reader/shading.d.ts +58 -2
  77. package/dist/esm/pdf-reader/shading.js +181 -11
  78. package/dist/esm/pdf-reader/struct-tree.js +112 -8
  79. package/dist/esm/pdf-reader/tagged.js +207 -28
  80. package/dist/esm/pdf-reader/text-rules.js +1 -1
  81. package/dist/esm/pdf-reader/text.d.ts +6 -3
  82. package/dist/esm/pdf-reader/text.js +106 -22
  83. package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
  84. package/dist/esm/pdf-reader/type1-outline.js +63 -8
  85. package/dist/esm/pdf-reader/vector.d.ts +8 -2
  86. package/dist/esm/pdf-reader/vector.js +105 -6
  87. package/dist/esm/word/doc/doc-reader.js +6 -2
  88. package/dist/esm/word/doc/doc-text.d.ts +6 -0
  89. package/dist/esm/word/doc/doc-text.js +19 -1
  90. package/dist/esm/word/document-parser.d.ts +2 -2
  91. package/dist/esm/word/document-parser.js +11 -1
  92. package/dist/esm/word/docx-reader.js +5 -3
  93. package/dist/esm/word/docx-writer.js +341 -54
  94. package/dist/esm/word/drawing-parser.d.ts +5 -3
  95. package/dist/esm/word/drawing-parser.js +56 -10
  96. package/dist/esm/word/font-embed.d.ts +30 -0
  97. package/dist/esm/word/font-embed.js +173 -0
  98. package/dist/esm/word/font-table.d.ts +10 -0
  99. package/dist/esm/word/font-table.js +13 -1
  100. package/dist/esm/word/index.js +1 -1
  101. package/dist/esm/word/numbering-parser.d.ts +3 -1
  102. package/dist/esm/word/numbering-parser.js +2 -1
  103. package/dist/esm/word/paragraph-properties.d.ts +7 -6
  104. package/dist/esm/word/paragraph-properties.js +16 -2
  105. package/dist/esm/word/run-properties.js +26 -0
  106. package/package.json +12 -5
@@ -1,16 +1,16 @@
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";
5
5
  import { decodePredefined, predefinedCMap, splitPredefined } from "./predefined-cmap.js";
6
6
  import { isZapfDingbats, zapfDingbatsChar } from "./dingbats.js";
7
7
  import { textForGlyphName } from "./glyph-names.js";
8
- import { cffCidToGid, cffNameToGid, cffOutlineSource, openTypeCff } from "./cff-outline.js";
8
+ import { baseEncodingTable, isStandardLatinFace, macGlyphName, standardEncodingTable, winAnsiLatinName } from "./encodings.js";
9
+ import { cffCidToGid, cffFsType, cffNameToGid, cffOutlineSource, cffSpaceAdvance, openTypeCff } from "./cff-outline.js";
9
10
  import { type1Font } from "./type1-outline.js";
10
- import { baseEncodingTable, isStandardLatinFace, standardEncodingTable } from "./encodings.js";
11
- import { outlineSource, postGlyphNames } from "./glyf-outline.js";
11
+ import { cmapSubtables, outlineSource, postGlyphNames, sfntFsType, sfntSpaceAdvance } 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,28 @@ 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 baseNames = isType0 ? void 0 : baseEncoding(file, fontDict);
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 numbered = !isType0 && toUnicode.size === 0 && glyphNames.size === 0 && stated === PDF_NULL && glyphs?.numbersGlyphs?.() === true;
73
+ const baseNames = isType0 ? void 0 : statesEncoding ? baseEncoding(file, fontDict) : glyphs?.builtIn ?? baseEncoding(file, fontDict);
60
74
  const fromBase = /* @__PURE__ */ new Map();
61
75
  for (const [code, glyph] of baseNames ?? []) {
62
76
  const text = textForGlyphName(glyph);
@@ -66,18 +80,24 @@ function buildContentFont(file, fontDict) {
66
80
  const style = faceStyle(file, fontDict, isType0);
67
81
  const name = runFontName(file, fontDict, isType0);
68
82
  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);
83
+ const decodeOne = (code) => (indices || numbered ? 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);
84
+ const composite = isType0 ? compositeGlyphs(file, fontDict) : void 0;
70
85
  const simple = simpleWidths(file, fontDict, decodeOne);
71
- const width = isType0 ? cidWidths(file, fontDict) : type3 ? (code) => simple(code) * type3.matrix[0] * 1e3 : simple;
86
+ const width = isType0 ? cidWidths(file, fontDict) : type3 ? (code) => simple.width(code) * type3.matrix[0] * 1e3 : simple.width;
87
+ const spaceWidth = isType0 || type3 || namesOf.get(32) !== "space" && unicode.get(32) !== " " ? void 0 : simple.stated(32);
88
+ const fixedPitch = !isType0 && !type3 && fixedPitchOf(file, fontDict);
72
89
  return {
73
90
  bytesPerCode,
74
91
  ...named ? { splitCodes: (b) => splitPredefined(named, b) } : {},
75
- ...isType0 ? outlineOf(file, fontDict, decodeOne) : simpleOutlineOf(decodeOne, namesOf, glyphs),
92
+ ...isType0 ? outlineOf(composite, decodeOne) : simpleOutlineOf(decodeOne, namesOf, glyphs, numbered),
93
+ ...name !== void 0 && !type3 ? programOf(file, fontDict, isType0, isType0 ? compositeGlyph(composite, fontDict, file) : simpleGlyph(glyphs, namesOf, baseNames === baseEncodingTable("WinAnsiEncoding")), isType0 ? composite : glyphs) : {},
76
94
  ...named?.vertical ? { verticalAdvance: cidVerticalAdvance(file, fontDict) } : {},
77
95
  ...type3 ? { type3 } : {},
78
96
  ...name !== void 0 ? { name } : {},
79
- decode: (codes) => codes.map((c) => readable(decodeOne(c))).join(""),
97
+ decode: (codes) => codes.map((c) => lettersOf(readable(decodeOne(c)))).join(""),
80
98
  width,
99
+ ...spaceWidth !== void 0 ? { spaceWidth } : {},
100
+ ...fixedPitch ? { fixedPitch: true } : {},
81
101
  ...style
82
102
  };
83
103
  }
@@ -114,6 +134,32 @@ function readable(text) {
114
134
  }
115
135
  return out;
116
136
  }
137
+ /**
138
+ * Unicode's presentation forms of the Latin ligatures, U+FB00–U+FB06, as the
139
+ * letters they join.
140
+ *
141
+ * A producer maps a ligature glyph to its compatibility character, and carried
142
+ * as that the word neither searches nor edits as the letters it is — and a
143
+ * face with no such form sets it in another's, wider: copy_paste_ligatures.pdf
144
+ * is one line in Times, and its "fffiflffifflſtst" pushed the line off its page. The long
145
+ * s of `ſt` is kept: it is a letter of its own.
146
+ */
147
+ function lettersOf(text) {
148
+ return LIGATURE.test(text) ? text.replace(LIGATURES, (c) => LATIN_LIGATURES.get(c) ?? c) : text;
149
+ }
150
+ var LIGATURE = /[\uFB00-\uFB06]/u;
151
+ var LIGATURES = /[\uFB00-\uFB06]/gu;
152
+ var LATIN_LIGATURES = new Map([
153
+ ["ff", "ff"],
154
+ ["fi", "fi"],
155
+ ["fl", "fl"],
156
+ ["ffi", "ffi"],
157
+ ["ffl", "ffl"],
158
+ ["ſt", "ſt"],
159
+ ["st", "st"]
160
+ ]);
161
+ /** The glyph name that selects no glyph (§9.6.6.1). */
162
+ var NOTDEF = ".notdef";
117
163
  /** The last Unicode code point in the BMP, and the surrogate block inside it. */
118
164
  var BMP_END = 65535;
119
165
  var SURROGATE_FIRST = 55296;
@@ -133,10 +179,130 @@ var SURROGATE_LAST = 57343;
133
179
  */
134
180
  function embeddedCmap(file, fontDict) {
135
181
  const cidFont = descendantFont(file, fontDict);
182
+ const byGlyph = programCharacters(file, cidFont);
183
+ if (byGlyph === void 0) return void 0;
184
+ const cidToGid = readCidToGid(file, cidFont);
185
+ if (!cidToGid) return byGlyph;
186
+ const out = /* @__PURE__ */ new Map();
187
+ cidToGid.forEach((gid, cid) => {
188
+ const text = byGlyph.get(gid);
189
+ if (text !== void 0) out.set(cid, text);
190
+ });
191
+ return out.size > 0 ? out : void 0;
192
+ }
193
+ /**
194
+ * §9.7.4 — the characters of a composite font that embeds no program, names a
195
+ * core face and states no `/ToUnicode`: its codes are glyph indices into the
196
+ * face the reader's machine carries under that name, and the core faces put
197
+ * their first 258 glyphs in the standard Macintosh order (the `post` table's
198
+ * format 1 names).
199
+ *
200
+ * issue11242_reduced.pdf shows "VAT Code" in a Verdana it does not embed.
201
+ * Nothing in the file says what its codes are but the face's own order, and
202
+ * read as characters they are "9$7&RGH"; read as nothing, the page came back
203
+ * blank. Only faces known to keep that order are read this way — a face's
204
+ * order is its own, and Calibri's is not this one.
205
+ *
206
+ * @param file The owning file.
207
+ * @param fontDict The `/Type0` font dictionary.
208
+ * @returns Code → character, or `undefined` where the font is not such a one.
209
+ */
210
+ function coreGlyphOrder(file, fontDict) {
211
+ const encoding = asName(file.resolve(fontDict.get("Encoding") ?? PDF_NULL));
212
+ if (encoding !== "Identity-H" && encoding !== "Identity-V") return void 0;
213
+ const cidFont = descendantFont(file, fontDict);
214
+ if (asName(file.resolve(cidFont.get("Subtype") ?? PDF_NULL)) !== "CIDFontType2") return void 0;
215
+ const map = file.resolve(cidFont.get("CIDToGIDMap") ?? PDF_NULL);
216
+ if (map !== PDF_NULL && !(map instanceof PdfName && map.value === "Identity")) return void 0;
217
+ const descriptor = file.resolve(cidFont.get("FontDescriptor") ?? PDF_NULL);
218
+ if (descriptor instanceof Map && [
219
+ "FontFile",
220
+ "FontFile2",
221
+ "FontFile3"
222
+ ].some((k) => descriptor.has(k))) return void 0;
223
+ const family = familyOfFace(asName(file.resolve(fontDict.get("BaseFont") ?? PDF_NULL)));
224
+ if (!CORE_FACES.has(family.toLowerCase().replace(/[^a-z]/gu, ""))) return void 0;
225
+ const out = /* @__PURE__ */ new Map();
226
+ for (let gid = 0; gid < MAC_GLYPHS; gid++) {
227
+ const name = macGlyphName(gid);
228
+ const text = name === void 0 ? void 0 : textForGlyphName(name);
229
+ if (text !== void 0 && text !== "") out.set(gid, text);
230
+ }
231
+ return out;
232
+ }
233
+ /** The core faces whose first glyphs stand in the standard Macintosh order. */
234
+ var CORE_FACES = new Set([
235
+ "arial",
236
+ "timesnewroman",
237
+ "couriernew",
238
+ "verdana",
239
+ "tahoma",
240
+ "georgia",
241
+ "trebuchetms"
242
+ ]);
243
+ /** How many glyphs the standard Macintosh order names. */
244
+ var MAC_GLYPHS = 258;
245
+ /**
246
+ * §9.7.4.2 — the CIDs of a composite font that ARE characters.
247
+ *
248
+ * A `/CIDToGIDMap` stream says the CID is not the glyph index, and TCPDF —
249
+ * with tFPDF and mPDF after it — makes it the character's own Unicode value,
250
+ * mapping every character of the face to its glyph whether the subset kept
251
+ * the glyph or not. Without a `/ToUnicode`, and with a subset that carries no
252
+ * `cmap`, that was the only statement of what the glyphs are and it went
253
+ * unread: bug1650302_reduced.pdf's "Výbava na přání" came back as drawings,
254
+ * the `ř` missing from them. Read as characters, the words come back.
255
+ *
256
+ * Only where the map itself says so. A producer that keeps the ORIGINAL
257
+ * glyph index as the CID routes it through a stream too, and read as Unicode
258
+ * complex_ttf_font.pdf's Arabic would come back as `$&')`. What tells the two
259
+ * apart is the space: a map of characters sends U+0020 to a glyph that draws
260
+ * nothing and no control character anywhere, where a map of indices sends 3 —
261
+ * the space in the fonts they come from — and 32 to a glyph with ink on it.
262
+ */
263
+ function unicodeCids(file, fontDict) {
264
+ const encoding = asName(file.resolve(fontDict.get("Encoding") ?? PDF_NULL));
265
+ if (encoding !== "Identity-H" && encoding !== "Identity-V") return void 0;
266
+ const cidFont = descendantFont(file, fontDict);
267
+ const cidToGid = readCidToGid(file, cidFont);
268
+ if (!cidToGid || cidToGid.slice(0, SPACE).some((gid) => gid !== 0)) return void 0;
269
+ const space = cidToGid[SPACE];
270
+ if (space === void 0 || space === 0) return void 0;
136
271
  const descriptor = file.resolve(cidFont.get("FontDescriptor") ?? PDF_NULL);
137
272
  if (!(descriptor instanceof Map)) return void 0;
138
273
  const program = file.resolve(descriptor.get("FontFile2") ?? PDF_NULL);
139
274
  if (!(program instanceof PdfStream)) return void 0;
275
+ let blank;
276
+ try {
277
+ const glyf = outlineSource(file.streamData(program));
278
+ blank = glyf !== void 0 && space < glyf.count && glyf.path(space) === void 0;
279
+ } catch {
280
+ return;
281
+ }
282
+ if (!blank) return void 0;
283
+ const out = /* @__PURE__ */ new Map();
284
+ cidToGid.forEach((gid, cid) => {
285
+ if (gid !== 0 && !(cid >= SURROGATE_FIRST && cid <= SURROGATE_LAST)) out.set(cid, String.fromCharCode(cid));
286
+ });
287
+ return out;
288
+ }
289
+ /** U+0020, the one character whose glyph every text face leaves blank. */
290
+ var SPACE = 32;
291
+ /**
292
+ * The character each glyph of a font's embedded TrueType program stands for:
293
+ * its `cmap` read backwards.
294
+ *
295
+ * @param file The document.
296
+ * @param owner The dictionary that holds the descriptor — the font itself, or
297
+ * a composite font's descendant.
298
+ * @returns Glyph index → character, or `undefined` where there is no program
299
+ * or it maps nothing.
300
+ */
301
+ function programCharacters(file, owner) {
302
+ const descriptor = file.resolve(owner.get("FontDescriptor") ?? PDF_NULL);
303
+ if (!(descriptor instanceof Map)) return void 0;
304
+ const program = file.resolve(descriptor.get("FontFile2") ?? PDF_NULL);
305
+ if (!(program instanceof PdfStream)) return void 0;
140
306
  let glyphOf;
141
307
  try {
142
308
  glyphOf = parseTtf(file.streamData(program)).glyphForCodepoint;
@@ -154,57 +320,80 @@ function embeddedCmap(file, fontDict) {
154
320
  }
155
321
  if (gid > 0 && !byGlyph.has(gid)) byGlyph.set(gid, String.fromCodePoint(cp));
156
322
  }
157
- if (byGlyph.size === 0) return void 0;
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;
323
+ return byGlyph.size > 0 ? byGlyph : void 0;
166
324
  }
167
325
  /**
168
- * §9.6.6 — the outline a code draws, for a code that stands for no character.
326
+ * §9.7.4 — a composite font's embedded program, read by glyph: the code is a
327
+ * CID, `/CIDToGIDMap` (or, for a CID-keyed CFF, its charset) turns that into a
328
+ * glyph index, and the program holds the contours.
169
329
  *
170
- * The glyph is there even when the character is not: `/Encoding /Identity-H`
171
- * makes the code a CID, `/CIDToGIDMap` turns that into a glyph index, and the
172
- * embedded program holds the contours. complex_ttf_font.pdf is eight lines of
173
- * Arabic in a subset with no `cmap` and no `/ToUnicode`, and every one of them
174
- * was dropped.
175
- *
176
- * Deliberately NOT a fallback for text: a code the font CAN answer for is set
177
- * as type, and only the unanswerable ones are traced.
178
- *
179
- * @param file The owning file.
180
- * @param fontDict The Type 0 font dictionary.
181
- * @param decodeOne What one code comes to, to tell the two cases apart.
182
- * @returns The `outline` field of a {@link ContentFont}, or nothing where the
183
- * program carries no outlines this reads.
330
+ * @param file The owning file.
331
+ * @param fontDict The Type 0 font dictionary.
332
+ * @returns The program's outlines and how a CID reaches them, or `undefined`
333
+ * where the font embeds no program this reads.
184
334
  */
185
- function outlineOf(file, fontDict, decodeOne) {
335
+ function compositeGlyphs(file, fontDict) {
186
336
  const cidFont = descendantFont(file, fontDict);
187
337
  const descriptor = file.resolve(cidFont.get("FontDescriptor") ?? PDF_NULL);
188
- if (!(descriptor instanceof Map)) return {};
338
+ if (!(descriptor instanceof Map)) return void 0;
189
339
  const truetype = file.resolve(descriptor.get("FontFile2") ?? PDF_NULL);
190
340
  const compact = file.resolve(descriptor.get("FontFile3") ?? PDF_NULL);
191
341
  const program = truetype instanceof PdfStream ? truetype : compact;
192
- if (!(program instanceof PdfStream)) return {};
342
+ if (!(program instanceof PdfStream)) return void 0;
193
343
  let source;
194
344
  let charsetCids;
345
+ let fsType;
346
+ let spaceAdvance;
195
347
  try {
196
348
  const bytes = file.streamData(program);
197
349
  source = outlineSource(bytes);
350
+ fsType = sfntFsType(bytes);
351
+ spaceAdvance = sfntSpaceAdvance(bytes);
198
352
  if (!source) {
199
353
  const cff = openTypeCff(bytes) ?? bytes;
200
354
  source = cffOutlineSource(cff);
201
355
  charsetCids = source ? cffCidToGid(cff) : void 0;
356
+ fsType ??= cffFsType(cff);
202
357
  }
203
358
  } catch {
204
- return {};
359
+ return;
205
360
  }
206
- if (!source) return {};
361
+ if (!source) return void 0;
207
362
  const cidToGid = readCidToGid(file, cidFont);
363
+ return {
364
+ source,
365
+ glyphOf: (cid) => {
366
+ const mapped = cidToGid ? cidToGid[cid] : cid;
367
+ if (mapped === void 0) return void 0;
368
+ return charsetCids ? charsetCids.get(mapped) : mapped;
369
+ },
370
+ looseGlyphOf: (cid) => {
371
+ const mapped = cidToGid ? cidToGid[cid] ?? 0 : cid;
372
+ return charsetCids ? charsetCids.get(mapped) ?? mapped : mapped;
373
+ },
374
+ ...fsType !== void 0 ? { fsType } : {},
375
+ ...spaceAdvance !== void 0 ? { spaceAdvance } : {}
376
+ };
377
+ }
378
+ /**
379
+ * §9.6.6 — the outline a code draws, for a code that stands for no character.
380
+ *
381
+ * The glyph is there even when the character is not: `/Encoding /Identity-H`
382
+ * makes the code a CID, `/CIDToGIDMap` turns that into a glyph index, and the
383
+ * embedded program holds the contours. complex_ttf_font.pdf is eight lines of
384
+ * Arabic in a subset with no `cmap` and no `/ToUnicode`, and every one of them
385
+ * was dropped.
386
+ *
387
+ * Deliberately NOT a fallback for text: a code the font CAN answer for is set
388
+ * as type, and only the unanswerable ones are traced.
389
+ *
390
+ * @param glyphs The font's program, read by glyph (see `compositeGlyphs`).
391
+ * @param decodeOne What one code comes to, to tell the two cases apart.
392
+ * @returns The `outline` field of a {@link ContentFont}, or nothing where the
393
+ * program carries no outlines this reads.
394
+ */
395
+ function outlineOf(glyphs, decodeOne) {
396
+ if (!glyphs) return {};
208
397
  return { outline: {
209
398
  matrix: [
210
399
  1,
@@ -216,13 +405,28 @@ function outlineOf(file, fontDict, decodeOne) {
216
405
  ],
217
406
  path: (code) => {
218
407
  if (readable(decodeOne(code)) !== UNANSWERABLE) return void 0;
219
- const mapped = cidToGid ? cidToGid[code] ?? 0 : code;
220
- const gid = charsetCids ? charsetCids.get(mapped) ?? mapped : mapped;
221
- return source.path(gid);
408
+ return glyphs.source.path(glyphs.looseGlyphOf(code));
222
409
  }
223
410
  } };
224
411
  }
225
412
  /**
413
+ * §9.7.5.2 — the glyph ANY code of a composite font draws, for a writer that
414
+ * embeds the face.
415
+ *
416
+ * Only under `Identity-H` or `Identity-V`, where the code is the CID: any other
417
+ * CMap turns codes into CIDs by a table of its own, and a glyph looked up by
418
+ * the code there would be some other character's.
419
+ */
420
+ function compositeGlyph(glyphs, fontDict, file) {
421
+ const encoding = asName(file.resolve(fontDict.get("Encoding") ?? PDF_NULL));
422
+ if (!glyphs || encoding !== "Identity-H" && encoding !== "Identity-V") return void 0;
423
+ return (code) => {
424
+ const gid = glyphs.glyphOf(code);
425
+ if (gid === void 0 || gid <= 0 || gid >= glyphs.source.count) return void 0;
426
+ return glyphs.source.path(gid) ?? [];
427
+ };
428
+ }
429
+ /**
226
430
  * §9.6.6 — the outline a SIMPLE font's code draws, for a code that stands for
227
431
  * no character.
228
432
  *
@@ -237,9 +441,12 @@ function outlineOf(file, fontDict, decodeOne) {
237
441
  * @param fontDict The simple font's dictionary.
238
442
  * @param decodeOne What one code comes to, to tell the two cases apart.
239
443
  * @param nameOf The glyph name a code selects, where the font states one.
444
+ * @param numbered Whether the program numbers its glyphs (see
445
+ * {@link numbersGlyphs}), which its `cmap` then reaches by
446
+ * the code.
240
447
  * @returns The `outline` field of a {@link ContentFont}, or nothing.
241
448
  */
242
- function simpleOutlineOf(decodeOne, nameOf, source) {
449
+ function simpleOutlineOf(decodeOne, nameOf, source, numbered) {
243
450
  if (!source) return {};
244
451
  return { outline: {
245
452
  matrix: [
@@ -252,6 +459,10 @@ function simpleOutlineOf(decodeOne, nameOf, source) {
252
459
  ],
253
460
  path: (code) => {
254
461
  if (readable(decodeOne(code)) !== UNANSWERABLE) return void 0;
462
+ if (numbered) {
463
+ const segs = source.glyph(code, void 0);
464
+ return segs !== void 0 && segs.length > 0 ? [...segs] : void 0;
465
+ }
255
466
  const name = nameOf.get(code) ?? source.builtIn?.get(code);
256
467
  if (name === void 0) return source.byIndex?.(code);
257
468
  return source.byName(name);
@@ -278,6 +489,7 @@ function simpleGlyphs(file, fontDict) {
278
489
  const typeOne = file.resolve(descriptor.get("FontFile") ?? PDF_NULL);
279
490
  const truetype = file.resolve(descriptor.get("FontFile2") ?? PDF_NULL);
280
491
  const compact = file.resolve(descriptor.get("FontFile3") ?? PDF_NULL);
492
+ const numbering = numberingOf(differences(file, fontDict).values());
281
493
  try {
282
494
  if (typeOne instanceof PdfStream) {
283
495
  const face = type1Font(file.streamData(typeOne));
@@ -285,20 +497,34 @@ function simpleGlyphs(file, fontDict) {
285
497
  return {
286
498
  byName: face.path,
287
499
  blank: (name) => face.has(name) && face.path(name) === void 0,
288
- ...face.encoding ? { builtIn: face.encoding } : {}
500
+ ...face.encoding ? { builtIn: face.encoding } : {},
501
+ glyph: (_code, name) => name === void 0 || name === NOTDEF || !face.has(name) ? void 0 : face.path(name) ?? [],
502
+ ...face.fsType !== void 0 ? { fsType: face.fsType } : {},
503
+ ...spaceOf(face.advance("space"))
289
504
  };
290
505
  }
291
506
  const stream = compact instanceof PdfStream ? compact : truetype;
292
507
  if (!(stream instanceof PdfStream)) return void 0;
293
508
  const bytes = file.streamData(stream);
294
509
  const glyf = outlineSource(bytes);
510
+ const fsType = sfntFsType(bytes);
295
511
  if (glyf) {
296
512
  const named = postGlyphNames(bytes);
297
513
  const gidOf = (name) => {
298
- const gid = named?.get(name) ?? numberedGlyph(name);
514
+ const gid = named?.get(name) ?? numberedGlyph(name, numbering);
299
515
  return gid !== void 0 && gid < glyf.count ? gid : void 0;
300
516
  };
517
+ const flags = asNumber(file.resolve(descriptor.get("Flags") ?? PDF_NULL), 0);
518
+ const encoding = file.resolve(fontDict.get("Encoding") ?? PDF_NULL);
519
+ const byCode = trueTypeGlyphs(bytes, (flags & FLAG_SYMBOLIC) !== 0 && (flags & FLAG_NONSYMBOLIC) === 0, encoding instanceof Map || encoding instanceof PdfName, gidOf);
301
520
  return {
521
+ glyph: (code, name) => {
522
+ const gid = byCode(code, name);
523
+ if (gid === void 0 || gid <= 0 || gid >= glyf.count) return void 0;
524
+ return glyf.path(gid) ?? [];
525
+ },
526
+ ...fsType !== void 0 ? { fsType } : {},
527
+ ...spaceOf(sfntSpaceAdvance(bytes)),
302
528
  byName: (name) => {
303
529
  const gid = gidOf(name);
304
530
  return gid === void 0 ? void 0 : glyf.path(gid);
@@ -308,7 +534,8 @@ function simpleGlyphs(file, fontDict) {
308
534
  return gid !== void 0 && glyf.path(gid) === void 0;
309
535
  },
310
536
  byIndex: (gid) => gid < glyf.count ? glyf.path(gid) : void 0,
311
- indexed: !glyf.cmap
537
+ indexed: !glyf.cmap,
538
+ numbersGlyphs: () => numbersGlyphs(bytes, glyf, named, (code) => byCode(code, void 0))
312
539
  };
313
540
  }
314
541
  const cff = openTypeCff(bytes) ?? bytes;
@@ -316,10 +543,17 @@ function simpleGlyphs(file, fontDict) {
316
543
  if (!outlines) return void 0;
317
544
  const names = cffNameToGid(cff);
318
545
  const gidOf = (name) => {
319
- const gid = names?.get(name) ?? numberedGlyph(name);
546
+ const gid = names?.get(name) ?? numberedGlyph(name, numbering);
320
547
  return gid !== void 0 && gid < outlines.count ? gid : void 0;
321
548
  };
549
+ const stated = fsType ?? cffFsType(cff);
322
550
  return {
551
+ glyph: (_code, name) => {
552
+ const gid = name === void 0 || name === NOTDEF ? void 0 : gidOf(name);
553
+ return gid === void 0 || gid === 0 ? void 0 : outlines.path(gid) ?? [];
554
+ },
555
+ ...stated !== void 0 ? { fsType: stated } : {},
556
+ ...spaceOf(sfntSpaceAdvance(bytes) ?? cffSpaceAdvance(cff)),
323
557
  byName: (name) => {
324
558
  const gid = gidOf(name);
325
559
  return gid === void 0 ? void 0 : outlines.path(gid);
@@ -333,6 +567,190 @@ function simpleGlyphs(file, fontDict) {
333
567
  return;
334
568
  }
335
569
  }
570
+ /** The `spaceAdvance` field, where a program states a sensible one. */
571
+ function spaceOf(advance) {
572
+ return advance !== void 0 && advance > 0 && advance < 1e3 ? { spaceAdvance: advance } : {};
573
+ }
574
+ /**
575
+ * §9.6.6.4 — how a SIMPLE TrueType font's code reaches a glyph: through the
576
+ * program's `cmap`, and which subtable decides what the code is looked up as.
577
+ *
578
+ * A face that states an encoding turns the code into its glyph NAME, and the
579
+ * name into a character for the Unicode subtable (3,1), a Mac Roman code for
580
+ * (1,0), or an entry of the program's own `post` names — and past ASCII, only
581
+ * that: the same byte is another letter in Mac Roman, and a glyph found by the
582
+ * code there is the wrong one. A symbolic face with a Windows symbol subtable
583
+ * (3,0), and a face that states no encoding, is looked up by the CODE: in
584
+ * (3,0), where it stands in one of four ranges (itself, or behind 0xF000,
585
+ * 0xF100 or 0xF200), or byte for byte in (1,0) — its names the fallback, as a
586
+ * viewer tries them. A program with no `cmap` at all is read by index: the
587
+ * code IS the glyph.
588
+ *
589
+ * @param program The raw `/FontFile2` bytes.
590
+ * @param symbolic Whether `/Flags` call the face symbolic (bit 3, not bit 6).
591
+ * @param hasEncoding Whether the font dictionary states an `/Encoding`.
592
+ * @param byName The glyph a name selects through `post` or the
593
+ * numbered-name convention (see {@link numberedGlyph}).
594
+ * @returns Code (and the name the encoding gives it) → glyph index, or
595
+ * `undefined` where nothing reaches one.
596
+ */
597
+ function trueTypeGlyphs(program, symbolic, hasEncoding, byName) {
598
+ let tables;
599
+ return (code, name) => {
600
+ tables ??= cmapSubtables(program);
601
+ const unicode = tables.get("3,1") ?? tables.get("0,3") ?? tables.get("0,1") ?? tables.get("0,0");
602
+ const symbol = tables.get("3,0");
603
+ const roman = tables.get("1,0");
604
+ const bySymbol = () => {
605
+ if (symbol) {
606
+ for (const high of SYMBOL_RANGES) {
607
+ const gid = symbol(high | code);
608
+ if (gid > 0) return gid;
609
+ }
610
+ return 0;
611
+ }
612
+ return roman ? roman(code) : 0;
613
+ };
614
+ const byGlyphName = () => {
615
+ if (name === void 0 || name === NOTDEF) return 0;
616
+ const text = textForGlyphName(name);
617
+ const cp = text !== void 0 && [...text].length === 1 ? text.codePointAt(0) : void 0;
618
+ const viaUnicode = unicode && cp !== void 0 ? unicode(cp) : 0;
619
+ if (viaUnicode > 0) return viaUnicode;
620
+ const mac = macRomanCode(name);
621
+ const viaRoman = roman && mac !== void 0 ? roman(mac) : 0;
622
+ if (viaRoman > 0) return viaRoman;
623
+ return byName(name) ?? 0;
624
+ };
625
+ const gid = symbolic && symbol !== void 0 ? bySymbol() || byGlyphName() : hasEncoding ? byGlyphName() || (code < ASCII_END ? bySymbol() : 0) : bySymbol() || byGlyphName() || (unicode ? unicode(code) : 0);
626
+ if (gid > 0) return gid;
627
+ return tables.size === 0 ? code : void 0;
628
+ };
629
+ }
630
+ /** Below this, a byte is the same character in every encoding a simple font uses. */
631
+ var ASCII_END = 128;
632
+ /**
633
+ * §9.6.6.4 — whether a TrueType program NUMBERS its glyphs, where a font would
634
+ * code characters with them: its codes are the order some producer came to
635
+ * each glyph in, and read as Latin-1 they spell nothing.
636
+ *
637
+ * Nothing in such a program says what its codes are — no Unicode subtable, no
638
+ * glyph names — and its SPACE says what they are not. A text face's blank
639
+ * glyph is its space, and Latin-1 puts the space at 32. A program that reaches
640
+ * no glyph at 32, and a blank one where Latin-1 reads a mark, is not coded in
641
+ * Latin-1: comments.pdf's chart faces put their spaces at 35 and 34, "#" and
642
+ * `"`.
643
+ *
644
+ * @param program The raw `/FontFile2` bytes.
645
+ * @param glyf Its outlines.
646
+ * @param named The glyphs its `post` table names.
647
+ * @param gidOf The glyph a code reaches, looked up as the text is.
648
+ */
649
+ function numbersGlyphs(program, glyf, named, gidOf) {
650
+ if (named !== void 0 && named.size > 0) return false;
651
+ const tables = cmapSubtables(program);
652
+ if (UNICODE_SUBTABLES.some((key) => tables.has(key))) return false;
653
+ if (!tables.has("1,0") && !tables.has("3,0")) return false;
654
+ const reached = (code) => {
655
+ const gid = gidOf(code);
656
+ return gid !== void 0 && gid > 0 && gid < glyf.count ? gid : void 0;
657
+ };
658
+ if (reached(SPACE) !== void 0) return false;
659
+ const blanks = [];
660
+ for (let code = 0; code <= 255; code++) {
661
+ const gid = reached(code);
662
+ if (gid !== void 0 && glyf.path(gid) === void 0) blanks.push(code);
663
+ }
664
+ return blanks.length > 0 && blanks.length <= MOST_BLANKS && blanks.every(inkedInLatin1);
665
+ }
666
+ /** The `cmap` subtables that key a program's glyphs by character. */
667
+ var UNICODE_SUBTABLES = [
668
+ "3,1",
669
+ "0,3",
670
+ "0,1",
671
+ "0,0"
672
+ ];
673
+ /**
674
+ * How many blank glyphs a text face has: its space and, beside it, a no-break
675
+ * one. A program with more has emptied the glyphs it does not use, and its
676
+ * codes may be Latin-1 for all its blanks say.
677
+ */
678
+ var MOST_BLANKS = 2;
679
+ /** Whether Latin-1 reads a code as a mark: not a control, a space or a soft hyphen. */
680
+ function inkedInLatin1(code) {
681
+ return code > 32 && code < 127 || code > 160 && code <= 255 && code !== 173;
682
+ }
683
+ /** §9.6.6.4 — where a symbolic face's (3,0) subtable may put a one-byte code. */
684
+ var SYMBOL_RANGES = [
685
+ 0,
686
+ 61440,
687
+ 61696,
688
+ 61952
689
+ ];
690
+ /** §9.8.2 `/Flags` — bit 3 is Symbolic, bit 6 Nonsymbolic (bits numbered from 1). */
691
+ var FLAG_SYMBOLIC = 4;
692
+ var FLAG_NONSYMBOLIC = 32;
693
+ /** The Mac Roman code of a glyph name, which is what a (1,0) `cmap` is keyed by. */
694
+ function macRomanCode(name) {
695
+ if (!macRomanCodes) {
696
+ macRomanCodes = /* @__PURE__ */ new Map();
697
+ for (const [code, glyph] of baseEncodingTable("MacRomanEncoding") ?? []) if (!macRomanCodes.has(glyph)) macRomanCodes.set(glyph, code);
698
+ }
699
+ return macRomanCodes.get(name);
700
+ }
701
+ var macRomanCodes;
702
+ /**
703
+ * §9.6.6 — the glyph ANY code of a simple font draws, for a writer that embeds
704
+ * the face: the name the encoding gives the code, as the outline path reads it.
705
+ *
706
+ * WinAnsiEncoding's Latin-1 half is named too (Annex D.2), though the text
707
+ * reads it as Latin-1 without a name: unnamed, an é in a program addressed by
708
+ * name selected nothing — and in a TrueType one, the Mac Roman subtable's È.
709
+ *
710
+ * @param glyphs The program, read by name and code.
711
+ * @param nameOf The name each code has: `/Differences` over the base encoding.
712
+ * @param winAnsi Whether that base encoding is WinAnsiEncoding.
713
+ */
714
+ function simpleGlyph(glyphs, nameOf, winAnsi) {
715
+ if (!glyphs) return void 0;
716
+ return (code) => glyphs.glyph(code, nameOf.get(code) ?? (winAnsi ? winAnsiLatinName(code) : void 0) ?? glyphs.builtIn?.get(code));
717
+ }
718
+ /**
719
+ * §9.9 — the {@link FaceProgram} of a font whose program this reads: the glyph
720
+ * lookup, the licence, and what the descriptor states (§9.8.1).
721
+ *
722
+ * @param file The owning file.
723
+ * @param fontDict The font dictionary.
724
+ * @param isType0 Whether it is a composite font, whose descendant owns the
725
+ * descriptor.
726
+ * @param glyph The glyph each code draws, where the program can be read.
727
+ * @param facts What the program states of itself: its licence and its space.
728
+ * @returns The `program` field of a {@link ContentFont}, or nothing.
729
+ */
730
+ function programOf(file, fontDict, isType0, glyph, facts) {
731
+ if (!glyph) return {};
732
+ const owner = isType0 ? descendantFont(file, fontDict) : fontDict;
733
+ const descriptor = file.resolve(owner.get("FontDescriptor") ?? PDF_NULL);
734
+ if (!(descriptor instanceof Map)) return {};
735
+ const stated = (key) => {
736
+ const v = file.resolve(descriptor.get(key) ?? PDF_NULL);
737
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
738
+ };
739
+ const flags = stated("Flags") ?? 0;
740
+ const capHeight = stated("CapHeight");
741
+ const xHeight = stated("XHeight");
742
+ const base = asName(file.resolve(owner.get("BaseFont") ?? fontDict.get("BaseFont") ?? PDF_NULL));
743
+ return { program: {
744
+ glyph,
745
+ ...facts?.fsType !== void 0 ? { fsType: facts.fsType } : {},
746
+ ...facts?.spaceAdvance !== void 0 ? { spaceAdvance: facts.spaceAdvance } : {},
747
+ postScriptName: base.replace(/^[A-Z]{6}\+/u, ""),
748
+ ...capHeight !== void 0 && capHeight > 0 ? { capHeight } : {},
749
+ ...xHeight !== void 0 && xHeight > 0 ? { xHeight } : {},
750
+ italicAngle: stated("ItalicAngle") ?? 0,
751
+ fixedPitch: (flags & FLAG_FIXED_PITCH) !== 0
752
+ } };
753
+ }
336
754
  /**
337
755
  * The glyph index a name carries, for the names that carry one.
338
756
  *
@@ -340,9 +758,13 @@ function simpleGlyphs(file, fontDict) {
340
758
  * INDEX — `g24`, `glyph24`, `index24`, `cid24` — and that name is then the only
341
759
  * way back to the outline. bug1151216.pdf names them `g24`, `g381`, `g3`, and
342
760
  * its three lines of prices are drawn from nothing else.
761
+ *
762
+ * @param name The glyph's name.
763
+ * @param numbering How the font writes the number, where its names say (see
764
+ * {@link numberingOf}); four digits are hexadecimal otherwise.
343
765
  */
344
- function numberedGlyph(name) {
345
- const hex = /^g([0-9a-f]{4})$/u.exec(name);
766
+ function numberedGlyph(name, numbering) {
767
+ const hex = numbering !== "decimal" ? /^g([0-9a-f]{4})$/u.exec(name) : null;
346
768
  if (hex) return Number.parseInt(hex[1], 16);
347
769
  const m = /^(?:g|glyph|index|cid|G)(\d+)$/u.exec(name);
348
770
  const n = m ? Number(m[1]) : NaN;
@@ -350,6 +772,33 @@ function numberedGlyph(name) {
350
772
  }
351
773
  /** No font holds more glyphs than this; a bigger number is not an index. */
352
774
  var MAX_GLYPH_INDEX = 65536;
775
+ /**
776
+ * How a font's index names write their numbers, where its names say.
777
+ *
778
+ * A subsetter that pads the number to four digits writes it in hexadecimal —
779
+ * bug1027533.pdf's `g0024` is glyph 36 — and one that does not pad writes it
780
+ * in decimal. One name cannot tell the two apart: bug1151216.pdf names its
781
+ * glyphs `g24`, `g381` and `g1004`, and `g1004` is four digits either way.
782
+ * Read as hexadecimal it is glyph 4100, which no subset of a thousand glyphs
783
+ * holds: five of the file's codes drew nothing, fell back to Latin-1, and its
784
+ * prices came back "$@'' for 1" set over the glyphs that were drawn. The rest
785
+ * of the font's names say which it is — a leading zero or a letter is
786
+ * hexadecimal, a number of any length but four is decimal.
787
+ *
788
+ * @param names The glyph names the font's `/Differences` gives.
789
+ * @returns The numbering, or `undefined` where the names do not say.
790
+ */
791
+ function numberingOf(names) {
792
+ let hex = false;
793
+ let decimal = false;
794
+ for (const name of names) {
795
+ const digits = /^g([0-9a-f]+)$/u.exec(name)?.[1];
796
+ if (digits === void 0) continue;
797
+ if (/[a-f]/u.test(digits) || digits.length === 4 && digits.startsWith("0")) hex = true;
798
+ else if (digits.length !== 4) decimal = true;
799
+ }
800
+ return hex === decimal ? void 0 : hex ? "hex" : "decimal";
801
+ }
353
802
  /** §9.7.4.2 `/CIDToGIDMap` — a stream of two-byte glyph indices, CID by CID. */
354
803
  function readCidToGid(file, cidFont) {
355
804
  const map = file.resolve(cidFont.get("CIDToGIDMap") ?? PDF_NULL);
@@ -419,7 +868,7 @@ function namedGlyphs(file, fontDict, statesUnicode, glyph) {
419
868
  if (names.size === 0) return void 0;
420
869
  const out = /* @__PURE__ */ new Map();
421
870
  for (const [code, name] of names) {
422
- const text = textForGlyphName(name);
871
+ const text = textForGlyphName(name) ?? glyph.character(name);
423
872
  if (text !== void 0) {
424
873
  out.set(code, text);
425
874
  continue;
@@ -532,7 +981,8 @@ function differences(file, fontDict) {
532
981
  function runFontName(file, fontDict, isType0) {
533
982
  const name = embeddedFontName(file, fontDict);
534
983
  if (name === void 0 || hasLiftableProgram(file, fontDict)) return name;
535
- if (resolveFamilyStyle(name).key !== "arimo") return name;
984
+ const face = familyOfFace(asName(file.resolve(fontDict.get("BaseFont") ?? PDF_NULL)));
985
+ if (knowsFamily(name) || knowsFamily(face)) return name;
536
986
  const owner = isType0 ? descendantFont(file, fontDict) : fontDict;
537
987
  const descriptor = file.resolve(owner.get("FontDescriptor") ?? PDF_NULL);
538
988
  if (!(descriptor instanceof Map)) return name;
@@ -541,6 +991,101 @@ function runFontName(file, fontDict, isType0) {
541
991
  if ((flags & FLAG_SERIF) !== 0) return `${name} serif`;
542
992
  return name;
543
993
  }
994
+ /**
995
+ * For every face a run may name, the family a word processor knows it by —
996
+ * keyed by the name the run carries (see {@link FaceFamily}).
997
+ *
998
+ * A PDF names the FACE and a .docx names a family: written as the face,
999
+ * `inter-semibold` was a font no reader has, and LibreOffice set the whole of
1000
+ * an invoice drawn in Inter in its default serif. The family is the one the
1001
+ * descriptor states (§9.8.1 `/FontFamily`) or, where it states none, the one
1002
+ * the PostScript name is made of.
1003
+ *
1004
+ * @param file The document.
1005
+ * @param pages The pages whose faces are wanted.
1006
+ * @returns Run font name → its family.
1007
+ */
1008
+ function collectFaceFamilies(file, pages) {
1009
+ const out = /* @__PURE__ */ new Map();
1010
+ eachPageFont(file, pages, (fontDict) => {
1011
+ const isType0 = asName(file.resolve(fontDict.get("Subtype") ?? PDF_NULL)) === "Type0";
1012
+ const key = runFontName(file, fontDict, isType0);
1013
+ if (key === void 0 || out.has(key)) return;
1014
+ const owner = isType0 ? descendantFont(file, fontDict) : fontDict;
1015
+ const descriptor = file.resolve(owner.get("FontDescriptor") ?? PDF_NULL);
1016
+ const stated = descriptor instanceof Map ? file.resolve(descriptor.get("FontFamily") ?? PDF_NULL) : PDF_NULL;
1017
+ const family = typeof stated === "string" && /^[\x20-\x7e]+$/u.test(stated.trim()) ? stated.trim() : familyOfFace(cidFontName(file, fontDict, isType0));
1018
+ if (family.length === 0) return;
1019
+ const flags = descriptor instanceof Map ? asNumber(file.resolve(descriptor.get("Flags") ?? PDF_NULL), 0) : 0;
1020
+ out.set(key, {
1021
+ family,
1022
+ generic: genericOf(family, flags)
1023
+ });
1024
+ });
1025
+ return out;
1026
+ }
1027
+ /**
1028
+ * §9.7.6.1 — a composite font's `/BaseFont` is its CIDFont's name, a hyphen and
1029
+ * the name of the CMap it is encoded by (`HeiseiMin-W3-UniJIS-UCS2-H`); the
1030
+ * face is the part before the CMap's.
1031
+ */
1032
+ function cidFontName(file, fontDict, isType0) {
1033
+ const base = asName(file.resolve(fontDict.get("BaseFont") ?? PDF_NULL));
1034
+ const cmap = isType0 ? asName(file.resolve(fontDict.get("Encoding") ?? PDF_NULL)) : "";
1035
+ return cmap !== "" && base.endsWith(`-${cmap}`) ? base.slice(0, -cmap.length - 1) : base;
1036
+ }
1037
+ /**
1038
+ * The family a PostScript face name is made of: `Inter-SemiBold` → `Inter`,
1039
+ * `ArialMT` → `Arial`, `TimesNewRomanPS-BoldMT` → `Times New Roman`.
1040
+ *
1041
+ * §9.6.2.1 names a face `Family-Style` (Word writes `Family,Style`), with the
1042
+ * family's words run together. What follows the separator is a style only if
1043
+ * it is made of style words — `MS-Mincho` is a family of its own — and the
1044
+ * words come apart where the capitals say they do.
1045
+ */
1046
+ function familyOfFace(baseFont) {
1047
+ const name = plainFace(baseFont);
1048
+ const cut = /^(.+?)[-,]([^-,]+)$/u.exec(name);
1049
+ const whole = cut && STYLE_WORDS.test(cut[2]) ? cut[1] : cut ? name.replace(/-/gu, " ") : GLUED_STYLE.exec(name)?.[1] ?? name;
1050
+ 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();
1051
+ }
1052
+ /**
1053
+ * A face name with what producers write around it taken off: the subset tag
1054
+ * (§9.6.4) and, after the face, the charset Acrobat's Office export appends
1055
+ * (`Arial,Bold-WinCharSetFFFF`, `-H2`), the encoding a CIDFont was cut for
1056
+ * (`-Identity-H`, `-OneByteIdentityH`), a serial number, and the slant or
1057
+ * stretch an interpreter synthesised (`-Slant_167`, `-Extend_850`). Left on,
1058
+ * bug900822.pdf's .docx asked for "Courier New,Bold Win Char Set FFFF", which
1059
+ * no machine has, over the Courier New every machine does.
1060
+ */
1061
+ function plainFace(baseFont) {
1062
+ let name = baseFont.replace(/^[A-Z]{6}\+/u, "").replace(/^[!-/:-@[-`{-~]+/u, "").trim();
1063
+ for (let next = name.replace(PRODUCER_TAIL, ""); next !== name;) {
1064
+ name = next;
1065
+ next = name.replace(PRODUCER_TAIL, "");
1066
+ }
1067
+ return name;
1068
+ }
1069
+ /** One piece of producer debris at the end of a face name (see `plainFace`). */
1070
+ var PRODUCER_TAIL = /[-_](?:WinCharSet[0-9A-F]+|Identity-[HV]|OneByteIdentity[HV]|H\d+|\d+|(?:Slant|Extend)_\d+)$/iu;
1071
+ /**
1072
+ * A style run on to a family with no separator — `CalibriBold` — which is the
1073
+ * family's name only with the style taken off. Whether the face IS bold is the
1074
+ * program's to say (see `faceStyle`): `NewBasrahBold` is a family of its own.
1075
+ */
1076
+ var GLUED_STYLE = /^(.*?[a-z])((?:Semi|Demi|Extra|Ultra)?(?:Bold|Black|Heavy)(?:Italic|Oblique)?|Italic|Oblique)$/u;
1077
+ /** A PostScript style part, made of nothing but style words (`SemiBoldItalic`, `BoldMT`, `Regu`). */
1078
+ 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;
1079
+ /** §17.8.3.10 — the kind of face a family is, from the descriptor or, failing that, its name. */
1080
+ function genericOf(family, flags) {
1081
+ if (!knowsFamily(family)) {
1082
+ if ((flags & FLAG_FIXED_PITCH) !== 0) return "modern";
1083
+ if ((flags & FLAG_SERIF) !== 0) return "roman";
1084
+ }
1085
+ const key = resolveFamilyStyle(family).key;
1086
+ if (key === "cousine") return "modern";
1087
+ return key === "tinos" || key === "caladea" ? "roman" : "swiss";
1088
+ }
544
1089
  /** §9.8.2 `/Flags` — bit 1 is FixedPitch, bit 2 Serif (bits numbered from 1). */
545
1090
  var FLAG_FIXED_PITCH = 1;
546
1091
  var FLAG_SERIF = 2;
@@ -575,8 +1120,11 @@ function faceStyle(file, fontDict, isType0) {
575
1120
  const flags = asNumber(file.resolve(descriptor.get("Flags") ?? PDF_NULL), 0);
576
1121
  const weightVal = file.resolve(descriptor.get("FontWeight") ?? PDF_NULL);
577
1122
  const slant = asNumber(file.resolve(descriptor.get("ItalicAngle") ?? PDF_NULL), 0);
578
- const bold = typeof weightVal === "number" && weightVal >= LIGHTEST_WEIGHT ? asNumber(weightVal, 0) >= BOLD_WEIGHT : (flags & FLAG_FORCE_BOLD) !== 0 || named.bold;
579
- const italic = slant !== 0 || (flags & FLAG_ITALIC) !== 0 || named.italic;
1123
+ const stated = typeof weightVal === "number" && weightVal >= LIGHTEST_WEIGHT;
1124
+ const own = programStyle(file, fontDict);
1125
+ const forced = (flags & FLAG_FORCE_BOLD) !== 0 && !named.light;
1126
+ const bold = stated ? asNumber(weightVal, 0) >= BOLD_WEIGHT : forced || own?.bold === true || named.bold;
1127
+ const italic = slant !== 0 || (flags & FLAG_ITALIC) !== 0 || own?.italic === true || named.italic;
580
1128
  return {
581
1129
  ...bold ? { bold: true } : {},
582
1130
  ...italic ? { italic: true } : {}
@@ -597,11 +1145,13 @@ function faceStyle(file, fontDict, isType0) {
597
1145
  * set two lines heavy that no reader sets heavy. `Times-Bold` is.
598
1146
  */
599
1147
  function styleFromName(baseFont) {
600
- const name = baseFont.replace(/^[A-Z]{6}\+/u, "");
1148
+ const name = plainFace(baseFont);
601
1149
  const style = /[-,]([A-Za-z]+)$/u.exec(name)?.[1] ?? "";
1150
+ const bold = /bold|black|heavy|semib|demi/iu.test(style);
602
1151
  return {
603
- bold: /bold|black|heavy|semib|demi/iu.test(style),
604
- italic: /italic|oblique/iu.test(style)
1152
+ bold,
1153
+ italic: /italic|oblique/iu.test(style),
1154
+ light: !bold && /light|thin|hairline|book|regular|roman|normal|medium/iu.test(style)
605
1155
  };
606
1156
  }
607
1157
  /** §9.7.4 — a `/Type0` font's one descendant CIDFont, which owns the descriptor. */
@@ -610,6 +1160,23 @@ function descendantFont(file, fontDict) {
610
1160
  const first = Array.isArray(descFonts) ? file.resolve(descFonts[0] ?? PDF_NULL) : PDF_NULL;
611
1161
  return first instanceof Map ? first : /* @__PURE__ */ new Map();
612
1162
  }
1163
+ /**
1164
+ * §9.8.2 — whether every glyph of a simple font is as wide as the next: the
1165
+ * descriptor's FixedPitch flag, the standard Courier, or a /Widths array that
1166
+ * says so itself. pdfTeX sets no flag on Computer Modern's typewriter face,
1167
+ * and its widths do: all of them 525. Ten widths at the least — a face that
1168
+ * shows four digits shows four equal widths and is no typewriter's.
1169
+ */
1170
+ function fixedPitchOf(file, fontDict) {
1171
+ const descriptor = file.resolve(fontDict.get("FontDescriptor") ?? PDF_NULL);
1172
+ if (((descriptor instanceof Map ? asNumber(file.resolve(descriptor.get("Flags") ?? PDF_NULL), 0) : 0) & FLAG_FIXED_PITCH) !== 0) return true;
1173
+ if (/^courier/iu.test(plainFace(asName(file.resolve(fontDict.get("BaseFont") ?? PDF_NULL))))) return true;
1174
+ const stated = file.resolve(fontDict.get("Widths") ?? PDF_NULL);
1175
+ const widths = (Array.isArray(stated) ? stated : []).map((w) => file.resolve(w)).filter((w) => typeof w === "number" && w > 0);
1176
+ return widths.length >= FIXED_PITCH_WIDTHS && widths.every((w) => Math.abs(w - widths[0]) < .5);
1177
+ }
1178
+ /** How many widths it takes to say every glyph is as wide as the next. */
1179
+ var FIXED_PITCH_WIDTHS = 10;
613
1180
  function simpleWidths(file, fontDict, decodeOne) {
614
1181
  const first = asNumber(file.resolve(fontDict.get("FirstChar") ?? PDF_NULL), 0);
615
1182
  const widthsVal = file.resolve(fontDict.get("Widths") ?? PDF_NULL);
@@ -617,12 +1184,14 @@ function simpleWidths(file, fontDict, decodeOne) {
617
1184
  const descriptor = file.resolve(fontDict.get("FontDescriptor") ?? PDF_NULL);
618
1185
  const missing = descriptor instanceof Map ? asNumber(file.resolve(descriptor.get("MissingWidth") ?? PDF_NULL), 0) : 0;
619
1186
  const face = standardFace(asName(file.resolve(fontDict.get("BaseFont") ?? PDF_NULL)));
620
- return (code) => {
1187
+ const stated = (code) => {
621
1188
  const w = widths[code - first];
622
1189
  if (typeof w === "number") return w;
623
- const built = face === void 0 ? void 0 : standardWidth(face, code, decodeOne(code));
624
- if (built !== void 0) return built;
625
- return missing > 0 ? missing : 500;
1190
+ return face === void 0 ? void 0 : standardWidth(face, code, decodeOne(code));
1191
+ };
1192
+ return {
1193
+ width: (code) => stated(code) ?? (missing > 0 ? missing : 500),
1194
+ stated
626
1195
  };
627
1196
  }
628
1197
  function cidWidths(file, fontDict) {
@@ -661,4 +1230,4 @@ function asNumber(v, dflt) {
661
1230
  return typeof v === "number" ? v : dflt;
662
1231
  }
663
1232
  //#endregion
664
- export { buildContentFont };
1233
+ export { buildContentFont, collectFaceFamilies };