pdf-codec 5.8.1 → 5.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -334,6 +334,10 @@ A TrueType subset can lose its glyph names and its `cmap` both and keep nothing
334
334
 
335
335
  A `/ToUnicode` CMap that a producer writes for the Symbol or Wingdings font (Word does, for a bullet, a tick or an arrow it draws in them) gives each glyph the private-use code point its 8-bit code makes in the 0xF000 range, which identifies the glyph in that one font and nothing else: 0xFC is a check mark in Wingdings and a piece of a right brace in Symbol. `symbol-font-private-use.ts` reads such a code point as the character the named font draws at that code, applied to the text a `/ToUnicode` CMap gave for a Symbol, SymbolMT or Wingdings font, by name, and to no other font: Symbol through its published encoding (the assembly pieces of a tall bracket and the integral extension as their Unicode characters), Wingdings through `wingdings-characters.ts`, the table of Unicode's own Wingdings document. A code the font has no character at, and Wingdings 2 and 3, Webdings and ZapfDingbats, stay as the producer wrote them.
336
336
 
337
+ Two ways a producer names a core face other than the PostScript name the face's own tables carry are read through the same carried glyph order. A TrueType program Windows-era producers embed under the face's Windows name (`TimesNewRoman`, `Arial,Bold`) is the face `TimesNewRomanPSMT` or `Arial-BoldMT`, still accepted glyph by glyph against the program's own advance widths. And Adobe Acrobat's optimiser rewrites a TrueType face as a CFF subset that names each glyph `G` followed by its glyph id in the face (`G36` is the capital A of Times New Roman) under a font name carrying a size suffix (`Times.New.Roman092`, `Arial.Bold0133`): `coreFaceTextsOfNumberedGlyphs` reads those names through the face's order, but only for a font whose dictionary widths agree with the face's advances over at least five numbered glyphs and nine in ten of them, so a subset of another face, or one too small to tell, is read as before. The Symbol face and the Type 3 fonts such producers also write are not covered: the first has no carried order, and a Type 3 glyph is a drawing procedure with no character.
338
+
339
+ Some producers write the font dictionary's own tables wrong for a symbol subset: a typesetter's Type 1C math fonts give each glyph an arbitrary name, assign the names to codes with `/Differences`, and have a `/ToUnicode` CMap read each name as the character it spells, so an equals sign is read as `¼` and an omega as `!`, from tables that are valid and wrong. `verified-glyph-tables.ts` holds tables for the exact subsets whose glyphs were rendered from the embedded program and checked one by one (`docs/verified-glyph-tables/`), each keyed by the SHA-256 of the decoded font program; a glyph of such a subset is read as what it draws, before the font dictionary's tables, and every other subset, family and code is read as before.
340
+
337
341
  **Where nothing states an answer, the answer is the replacement character plus a `text/unmapped-encoding` diagnostic, never a guess.** Cases that reach it in practice: a symbolic font with no embedded program and no `/ToUnicode`; a subsetted font that both strips its glyph names and maps its glyphs only from private-use code points; a nameless, `cmap`-less subset of a face whose glyph order this package does not carry, or of a carried face at a glyph its recorded releases do not number; and a composite font whose `/Encoding` is a CMap other than Identity-H or Identity-V, whose codes reach CIDs this reader does not map. A private-use code point identifies a glyph inside one font and says nothing about the character it draws, so it is treated as no answer rather than a wrong one.
338
342
 
339
343
  ## Grouping text runs into lines and words
@@ -92,7 +92,7 @@ function coreFaceSource(font, fontName) {
92
92
  const unitsPerEm = require_font_tables.parseHead(font)?.unitsPerEm;
93
93
  const advanceOf = advanceWidths(font);
94
94
  if (name === void 0 || unitsPerEm === void 0 || advanceOf === void 0) return;
95
- const order = require_core_font_glyph_orders.coreFaceGlyphOrder(require_font_style.withoutSubsetTag(name), unitsPerEm);
95
+ const order = require_core_font_glyph_orders.coreFaceGlyphOrder(require_core_font_glyph_orders.coreFaceNameOfFontName(name) ?? require_font_style.withoutSubsetTag(name), unitsPerEm);
96
96
  if (order === void 0) return;
97
97
  return (glyphId) => order(glyphId, advanceOf(glyphId));
98
98
  }
@@ -2,7 +2,7 @@ import { standardGlyphName } from "./encoding-standard-tables.js";
2
2
  import { hasBytes, parseSfnt, sfntTableBytes, u16, u8 } from "./sfnt.js";
3
3
  import { cffStringForSid, parseCffDict, readCffIndex } from "./cff.js";
4
4
  import { readCmapSubtables } from "./cmap-table.js";
5
- import { coreFaceGlyphOrder } from "./core-font-glyph-orders.js";
5
+ import { coreFaceGlyphOrder, coreFaceNameOfFontName } from "./core-font-glyph-orders.js";
6
6
  import { withoutSubsetTag } from "./font-style.js";
7
7
  import { parseHead, parseMaxp, parseName, parsePostGlyphNames } from "./font-tables.js";
8
8
  import { glyphNameToUnicode } from "./glyph-names.js";
@@ -91,7 +91,7 @@ function coreFaceSource(font, fontName) {
91
91
  const unitsPerEm = parseHead(font)?.unitsPerEm;
92
92
  const advanceOf = advanceWidths(font);
93
93
  if (name === void 0 || unitsPerEm === void 0 || advanceOf === void 0) return;
94
- const order = coreFaceGlyphOrder(withoutSubsetTag(name), unitsPerEm);
94
+ const order = coreFaceGlyphOrder(coreFaceNameOfFontName(name) ?? withoutSubsetTag(name), unitsPerEm);
95
95
  if (order === void 0) return;
96
96
  return (glyphId) => order(glyphId, advanceOf(glyphId));
97
97
  }
@@ -58,5 +58,102 @@ function coreFaceGlyphOrder(postScriptName, unitsPerEm) {
58
58
  if (face?.unitsPerEm !== unitsPerEm) return;
59
59
  return (glyphId, advanceWidth) => face.advances[glyphId] === advanceWidth ? face.texts[glyphId] : void 0;
60
60
  }
61
+ /** The PostScript names of the core faces, by the family and style a font name spells with its punctuation, subset tag and Distiller size suffix taken off. */
62
+ const CORE_FACE_BY_SPELLING = /* @__PURE__ */ new Map([
63
+ ["arial", "ArialMT"],
64
+ ["arialmt", "ArialMT"],
65
+ ["arialbold", "Arial-BoldMT"],
66
+ ["arialboldmt", "Arial-BoldMT"],
67
+ ["arialitalic", "Arial-ItalicMT"],
68
+ ["arialitalicmt", "Arial-ItalicMT"],
69
+ ["arialbolditalic", "Arial-BoldItalicMT"],
70
+ ["arialbolditalicmt", "Arial-BoldItalicMT"],
71
+ ["timesnewroman", "TimesNewRomanPSMT"],
72
+ ["timesnewromanpsmt", "TimesNewRomanPSMT"],
73
+ ["timesnewromanbold", "TimesNewRomanPS-BoldMT"],
74
+ ["timesnewromanpsboldmt", "TimesNewRomanPS-BoldMT"],
75
+ ["timesnewromanitalic", "TimesNewRomanPS-ItalicMT"],
76
+ ["timesnewromanpsitalicmt", "TimesNewRomanPS-ItalicMT"],
77
+ ["timesnewromanbolditalic", "TimesNewRomanPS-BoldItalicMT"],
78
+ ["timesnewromanpsbolditalicmt", "TimesNewRomanPS-BoldItalicMT"],
79
+ ["couriernew", "CourierNewPSMT"],
80
+ ["couriernewpsmt", "CourierNewPSMT"],
81
+ ["couriernewbold", "CourierNewPS-BoldMT"],
82
+ ["couriernewpsboldmt", "CourierNewPS-BoldMT"],
83
+ ["couriernewitalic", "CourierNewPS-ItalicMT"],
84
+ ["couriernewpsitalicmt", "CourierNewPS-ItalicMT"],
85
+ ["couriernewbolditalic", "CourierNewPS-BoldItalicMT"],
86
+ ["couriernewpsbolditalicmt", "CourierNewPS-BoldItalicMT"]
87
+ ]);
88
+ const ZERO_CODE = 48;
89
+ const NINE_CODE = 57;
90
+ const FULL_STOP_CODE = 46;
91
+ function isDigitAt(name, index) {
92
+ const code = name.charCodeAt(index);
93
+ return code >= ZERO_CODE && code <= NINE_CODE;
94
+ }
95
+ function digitRunStart(name, end) {
96
+ let start = end;
97
+ while (start > 0 && isDigitAt(name, start - 1)) start -= 1;
98
+ return start;
99
+ }
100
+ /**
101
+ * `name` without a trailing size or version number a producer appends to a font name ("Arial.Bold0133", "Arial086.75"): digits, optionally a fraction. Found by one scan back from the end, so the work is the length of the number and not of the name: the end-anchored pattern this replaces (a digit run followed by an optional fraction, then the end) is retried from every offset of a long digit run that a letter follows, which takes time quadratic in its length.
102
+ * @param name - A `/BaseFont` name.
103
+ * @returns The name with its trailing number cut off, or the name itself when it does not end in one.
104
+ */
105
+ function withoutTrailingNumber(name) {
106
+ const fractionStart = digitRunStart(name, name.length);
107
+ if (fractionStart === name.length) return name;
108
+ const point = fractionStart - 1;
109
+ if (point > 0 && name.charCodeAt(point) === FULL_STOP_CODE) {
110
+ const wholeStart = digitRunStart(name, point);
111
+ if (wholeStart < point) return name.slice(0, wholeStart);
112
+ }
113
+ return name.slice(0, fractionStart);
114
+ }
115
+ /**
116
+ * The PostScript name of the core face a font name spells, or `undefined` when it spells none: `TimesNewRoman` and `Times.New.Roman.Bold092` are `TimesNewRomanPSMT` and `TimesNewRomanPS-BoldMT`. A spelling is only a candidate: the glyph order is accepted per glyph, against the font dictionary's own widths (`coreFaceTextOfNumberedGlyph`).
117
+ * @param fontName - A `/BaseFont` name, with or without its subset tag.
118
+ * @returns The face's PostScript name, or `undefined`.
119
+ */
120
+ function coreFaceNameOfFontName(fontName) {
121
+ const spelled = withoutTrailingNumber(fontName.replace(/^[A-Z]{6}\+/u, "")).replace(/[^A-Za-z]/gu, "").toLowerCase();
122
+ return CORE_FACE_BY_SPELLING.get(spelled);
123
+ }
124
+ const NUMBERED_GLYPH_NAME = /^G(\d+)$/u;
125
+ const WIDTH_AGREEMENT_TOLERANCE = 25;
126
+ const THOUSANDTHS_PER_EM = 1e3;
127
+ const MIN_AGREEING_SHARE = .9;
128
+ const MIN_NUMBERED_GLYPHS = 5;
129
+ /**
130
+ * What a core face draws at the glyphs of a CFF subset that names each `G<id>`, accepted for the whole font only when the font dictionary's widths agree with the face's advances over at least `MIN_NUMBERED_GLYPHS` glyphs and `MIN_AGREEING_SHARE` of them, so a subset of any other face, or of another release of this one, is read as before.
131
+ * @param fontName - The font's `/BaseFont`.
132
+ * @param glyphs - Each glyph of the font a `/Differences` array names, with the width, in thousandths of an em, the dictionary gives its code.
133
+ * @returns The character each numbered glyph name draws, or `undefined` when the font is not taken for a core face.
134
+ */
135
+ function coreFaceTextsOfNumberedGlyphs(fontName, glyphs) {
136
+ const faceName = coreFaceNameOfFontName(fontName);
137
+ const face = faceName === void 0 ? void 0 : readFaces().get(faceName);
138
+ if (face === void 0) return;
139
+ const texts = /* @__PURE__ */ new Map();
140
+ let numbered = 0;
141
+ let agreeing = 0;
142
+ for (const { name, width } of glyphs) {
143
+ const id = NUMBERED_GLYPH_NAME.exec(name)?.[1];
144
+ const advance = id === void 0 ? void 0 : face.advances[Number.parseInt(id, 10)];
145
+ if (id === void 0 || advance === void 0) continue;
146
+ numbered++;
147
+ const text = face.texts[Number.parseInt(id, 10)];
148
+ if (Math.abs(advance * THOUSANDTHS_PER_EM / face.unitsPerEm - width) <= WIDTH_AGREEMENT_TOLERANCE) {
149
+ agreeing++;
150
+ if (text !== void 0) texts.set(name, text);
151
+ }
152
+ }
153
+ return numbered >= MIN_NUMBERED_GLYPHS && agreeing >= numbered * MIN_AGREEING_SHARE ? texts : void 0;
154
+ }
61
155
  //#endregion
62
156
  exports.coreFaceGlyphOrder = coreFaceGlyphOrder;
157
+ exports.coreFaceNameOfFontName = coreFaceNameOfFontName;
158
+ exports.coreFaceTextsOfNumberedGlyphs = coreFaceTextsOfNumberedGlyphs;
159
+ exports.withoutTrailingNumber = withoutTrailingNumber;
@@ -6,5 +6,27 @@
6
6
  * @returns a lookup from a glyph id and the program's own advance width for that glyph to the character the face draws there, answering undefined wherever the width disagrees with the face's or the face draws no character; or undefined when the name is not a core face or the grid is not the face's
7
7
  */
8
8
  declare function coreFaceGlyphOrder(postScriptName: string, unitsPerEm: number): ((glyphId: number, advanceWidth: number) => string | undefined) | undefined;
9
+ /**
10
+ * `name` without a trailing size or version number a producer appends to a font name ("Arial.Bold0133", "Arial086.75"): digits, optionally a fraction. Found by one scan back from the end, so the work is the length of the number and not of the name: the end-anchored pattern this replaces (a digit run followed by an optional fraction, then the end) is retried from every offset of a long digit run that a letter follows, which takes time quadratic in its length.
11
+ * @param name - A `/BaseFont` name.
12
+ * @returns The name with its trailing number cut off, or the name itself when it does not end in one.
13
+ */
14
+ declare function withoutTrailingNumber(name: string): string;
15
+ /**
16
+ * The PostScript name of the core face a font name spells, or `undefined` when it spells none: `TimesNewRoman` and `Times.New.Roman.Bold092` are `TimesNewRomanPSMT` and `TimesNewRomanPS-BoldMT`. A spelling is only a candidate: the glyph order is accepted per glyph, against the font dictionary's own widths (`coreFaceTextOfNumberedGlyph`).
17
+ * @param fontName - A `/BaseFont` name, with or without its subset tag.
18
+ * @returns The face's PostScript name, or `undefined`.
19
+ */
20
+ declare function coreFaceNameOfFontName(fontName: string): string | undefined;
21
+ /**
22
+ * What a core face draws at the glyphs of a CFF subset that names each `G<id>`, accepted for the whole font only when the font dictionary's widths agree with the face's advances over at least `MIN_NUMBERED_GLYPHS` glyphs and `MIN_AGREEING_SHARE` of them, so a subset of any other face, or of another release of this one, is read as before.
23
+ * @param fontName - The font's `/BaseFont`.
24
+ * @param glyphs - Each glyph of the font a `/Differences` array names, with the width, in thousandths of an em, the dictionary gives its code.
25
+ * @returns The character each numbered glyph name draws, or `undefined` when the font is not taken for a core face.
26
+ */
27
+ declare function coreFaceTextsOfNumberedGlyphs(fontName: string, glyphs: readonly {
28
+ readonly name: string;
29
+ readonly width: number;
30
+ }[]): ReadonlyMap<string, string> | undefined;
9
31
  //#endregion
10
- export { coreFaceGlyphOrder };
32
+ export { coreFaceGlyphOrder, coreFaceNameOfFontName, coreFaceTextsOfNumberedGlyphs, withoutTrailingNumber };
@@ -6,5 +6,27 @@
6
6
  * @returns a lookup from a glyph id and the program's own advance width for that glyph to the character the face draws there, answering undefined wherever the width disagrees with the face's or the face draws no character; or undefined when the name is not a core face or the grid is not the face's
7
7
  */
8
8
  declare function coreFaceGlyphOrder(postScriptName: string, unitsPerEm: number): ((glyphId: number, advanceWidth: number) => string | undefined) | undefined;
9
+ /**
10
+ * `name` without a trailing size or version number a producer appends to a font name ("Arial.Bold0133", "Arial086.75"): digits, optionally a fraction. Found by one scan back from the end, so the work is the length of the number and not of the name: the end-anchored pattern this replaces (a digit run followed by an optional fraction, then the end) is retried from every offset of a long digit run that a letter follows, which takes time quadratic in its length.
11
+ * @param name - A `/BaseFont` name.
12
+ * @returns The name with its trailing number cut off, or the name itself when it does not end in one.
13
+ */
14
+ declare function withoutTrailingNumber(name: string): string;
15
+ /**
16
+ * The PostScript name of the core face a font name spells, or `undefined` when it spells none: `TimesNewRoman` and `Times.New.Roman.Bold092` are `TimesNewRomanPSMT` and `TimesNewRomanPS-BoldMT`. A spelling is only a candidate: the glyph order is accepted per glyph, against the font dictionary's own widths (`coreFaceTextOfNumberedGlyph`).
17
+ * @param fontName - A `/BaseFont` name, with or without its subset tag.
18
+ * @returns The face's PostScript name, or `undefined`.
19
+ */
20
+ declare function coreFaceNameOfFontName(fontName: string): string | undefined;
21
+ /**
22
+ * What a core face draws at the glyphs of a CFF subset that names each `G<id>`, accepted for the whole font only when the font dictionary's widths agree with the face's advances over at least `MIN_NUMBERED_GLYPHS` glyphs and `MIN_AGREEING_SHARE` of them, so a subset of any other face, or of another release of this one, is read as before.
23
+ * @param fontName - The font's `/BaseFont`.
24
+ * @param glyphs - Each glyph of the font a `/Differences` array names, with the width, in thousandths of an em, the dictionary gives its code.
25
+ * @returns The character each numbered glyph name draws, or `undefined` when the font is not taken for a core face.
26
+ */
27
+ declare function coreFaceTextsOfNumberedGlyphs(fontName: string, glyphs: readonly {
28
+ readonly name: string;
29
+ readonly width: number;
30
+ }[]): ReadonlyMap<string, string> | undefined;
9
31
  //#endregion
10
- export { coreFaceGlyphOrder };
32
+ export { coreFaceGlyphOrder, coreFaceNameOfFontName, coreFaceTextsOfNumberedGlyphs, withoutTrailingNumber };
@@ -57,5 +57,99 @@ function coreFaceGlyphOrder(postScriptName, unitsPerEm) {
57
57
  if (face?.unitsPerEm !== unitsPerEm) return;
58
58
  return (glyphId, advanceWidth) => face.advances[glyphId] === advanceWidth ? face.texts[glyphId] : void 0;
59
59
  }
60
+ /** The PostScript names of the core faces, by the family and style a font name spells with its punctuation, subset tag and Distiller size suffix taken off. */
61
+ const CORE_FACE_BY_SPELLING = /* @__PURE__ */ new Map([
62
+ ["arial", "ArialMT"],
63
+ ["arialmt", "ArialMT"],
64
+ ["arialbold", "Arial-BoldMT"],
65
+ ["arialboldmt", "Arial-BoldMT"],
66
+ ["arialitalic", "Arial-ItalicMT"],
67
+ ["arialitalicmt", "Arial-ItalicMT"],
68
+ ["arialbolditalic", "Arial-BoldItalicMT"],
69
+ ["arialbolditalicmt", "Arial-BoldItalicMT"],
70
+ ["timesnewroman", "TimesNewRomanPSMT"],
71
+ ["timesnewromanpsmt", "TimesNewRomanPSMT"],
72
+ ["timesnewromanbold", "TimesNewRomanPS-BoldMT"],
73
+ ["timesnewromanpsboldmt", "TimesNewRomanPS-BoldMT"],
74
+ ["timesnewromanitalic", "TimesNewRomanPS-ItalicMT"],
75
+ ["timesnewromanpsitalicmt", "TimesNewRomanPS-ItalicMT"],
76
+ ["timesnewromanbolditalic", "TimesNewRomanPS-BoldItalicMT"],
77
+ ["timesnewromanpsbolditalicmt", "TimesNewRomanPS-BoldItalicMT"],
78
+ ["couriernew", "CourierNewPSMT"],
79
+ ["couriernewpsmt", "CourierNewPSMT"],
80
+ ["couriernewbold", "CourierNewPS-BoldMT"],
81
+ ["couriernewpsboldmt", "CourierNewPS-BoldMT"],
82
+ ["couriernewitalic", "CourierNewPS-ItalicMT"],
83
+ ["couriernewpsitalicmt", "CourierNewPS-ItalicMT"],
84
+ ["couriernewbolditalic", "CourierNewPS-BoldItalicMT"],
85
+ ["couriernewpsbolditalicmt", "CourierNewPS-BoldItalicMT"]
86
+ ]);
87
+ const ZERO_CODE = 48;
88
+ const NINE_CODE = 57;
89
+ const FULL_STOP_CODE = 46;
90
+ function isDigitAt(name, index) {
91
+ const code = name.charCodeAt(index);
92
+ return code >= ZERO_CODE && code <= NINE_CODE;
93
+ }
94
+ function digitRunStart(name, end) {
95
+ let start = end;
96
+ while (start > 0 && isDigitAt(name, start - 1)) start -= 1;
97
+ return start;
98
+ }
99
+ /**
100
+ * `name` without a trailing size or version number a producer appends to a font name ("Arial.Bold0133", "Arial086.75"): digits, optionally a fraction. Found by one scan back from the end, so the work is the length of the number and not of the name: the end-anchored pattern this replaces (a digit run followed by an optional fraction, then the end) is retried from every offset of a long digit run that a letter follows, which takes time quadratic in its length.
101
+ * @param name - A `/BaseFont` name.
102
+ * @returns The name with its trailing number cut off, or the name itself when it does not end in one.
103
+ */
104
+ function withoutTrailingNumber(name) {
105
+ const fractionStart = digitRunStart(name, name.length);
106
+ if (fractionStart === name.length) return name;
107
+ const point = fractionStart - 1;
108
+ if (point > 0 && name.charCodeAt(point) === FULL_STOP_CODE) {
109
+ const wholeStart = digitRunStart(name, point);
110
+ if (wholeStart < point) return name.slice(0, wholeStart);
111
+ }
112
+ return name.slice(0, fractionStart);
113
+ }
114
+ /**
115
+ * The PostScript name of the core face a font name spells, or `undefined` when it spells none: `TimesNewRoman` and `Times.New.Roman.Bold092` are `TimesNewRomanPSMT` and `TimesNewRomanPS-BoldMT`. A spelling is only a candidate: the glyph order is accepted per glyph, against the font dictionary's own widths (`coreFaceTextOfNumberedGlyph`).
116
+ * @param fontName - A `/BaseFont` name, with or without its subset tag.
117
+ * @returns The face's PostScript name, or `undefined`.
118
+ */
119
+ function coreFaceNameOfFontName(fontName) {
120
+ const spelled = withoutTrailingNumber(fontName.replace(/^[A-Z]{6}\+/u, "")).replace(/[^A-Za-z]/gu, "").toLowerCase();
121
+ return CORE_FACE_BY_SPELLING.get(spelled);
122
+ }
123
+ const NUMBERED_GLYPH_NAME = /^G(\d+)$/u;
124
+ const WIDTH_AGREEMENT_TOLERANCE = 25;
125
+ const THOUSANDTHS_PER_EM = 1e3;
126
+ const MIN_AGREEING_SHARE = .9;
127
+ const MIN_NUMBERED_GLYPHS = 5;
128
+ /**
129
+ * What a core face draws at the glyphs of a CFF subset that names each `G<id>`, accepted for the whole font only when the font dictionary's widths agree with the face's advances over at least `MIN_NUMBERED_GLYPHS` glyphs and `MIN_AGREEING_SHARE` of them, so a subset of any other face, or of another release of this one, is read as before.
130
+ * @param fontName - The font's `/BaseFont`.
131
+ * @param glyphs - Each glyph of the font a `/Differences` array names, with the width, in thousandths of an em, the dictionary gives its code.
132
+ * @returns The character each numbered glyph name draws, or `undefined` when the font is not taken for a core face.
133
+ */
134
+ function coreFaceTextsOfNumberedGlyphs(fontName, glyphs) {
135
+ const faceName = coreFaceNameOfFontName(fontName);
136
+ const face = faceName === void 0 ? void 0 : readFaces().get(faceName);
137
+ if (face === void 0) return;
138
+ const texts = /* @__PURE__ */ new Map();
139
+ let numbered = 0;
140
+ let agreeing = 0;
141
+ for (const { name, width } of glyphs) {
142
+ const id = NUMBERED_GLYPH_NAME.exec(name)?.[1];
143
+ const advance = id === void 0 ? void 0 : face.advances[Number.parseInt(id, 10)];
144
+ if (id === void 0 || advance === void 0) continue;
145
+ numbered++;
146
+ const text = face.texts[Number.parseInt(id, 10)];
147
+ if (Math.abs(advance * THOUSANDTHS_PER_EM / face.unitsPerEm - width) <= WIDTH_AGREEMENT_TOLERANCE) {
148
+ agreeing++;
149
+ if (text !== void 0) texts.set(name, text);
150
+ }
151
+ }
152
+ return numbered >= MIN_NUMBERED_GLYPHS && agreeing >= numbered * MIN_AGREEING_SHARE ? texts : void 0;
153
+ }
60
154
  //#endregion
61
- export { coreFaceGlyphOrder };
155
+ export { coreFaceGlyphOrder, coreFaceNameOfFontName, coreFaceTextsOfNumberedGlyphs, withoutTrailingNumber };
@@ -3,6 +3,7 @@ const require_encoding = require("./encoding.cjs");
3
3
  const require_afm_widths = require("./afm-widths.cjs");
4
4
  const require_objects = require("./objects.cjs");
5
5
  const require_filters = require("./filters.cjs");
6
+ const require_core_font_glyph_orders = require("./core-font-glyph-orders.cjs");
6
7
  const require_font_style = require("./font-style.cjs");
7
8
  const require_glyph_names = require("./glyph-names.cjs");
8
9
  const require_builtin_encoding = require("./builtin-encoding.cjs");
@@ -10,6 +11,7 @@ const require_cmap = require("./cmap.cjs");
10
11
  const require_encoding_symbol_tables = require("./encoding-symbol-tables.cjs");
11
12
  const require_fonts = require("./fonts.cjs");
12
13
  const require_symbol_font_private_use = require("./symbol-font-private-use.cjs");
14
+ const require_verified_glyph_tables = require("./verified-glyph-tables.cjs");
13
15
  //#region src/font-read.ts
14
16
  const BITS_PER_BYTE = 8;
15
17
  const SYMBOLIC_FLAG_BIT = 4;
@@ -50,6 +52,33 @@ function lazyFontProgram(descriptor, baseFont, context) {
50
52
  return read.encoding;
51
53
  };
52
54
  }
55
+ function lazyVerifiedGlyphTable(descriptor, family, context) {
56
+ let read;
57
+ return () => {
58
+ if (descriptor === void 0 || !require_verified_glyph_tables.VERIFIED_GLYPH_FAMILIES.has(family)) return;
59
+ read ??= { table: verifiedGlyphTableOfProgram(descriptor, family, context) };
60
+ return read.table;
61
+ };
62
+ }
63
+ const PROGRAM_DIGESTS = /* @__PURE__ */ new WeakMap();
64
+ function digestOfFontStream(stream, context) {
65
+ let digest = PROGRAM_DIGESTS.get(stream);
66
+ if (digest === void 0) {
67
+ digest = require_verified_glyph_tables.programDigest(require_filters.decodeStream(stream.raw, stream.dict, context.sink).bytes);
68
+ PROGRAM_DIGESTS.set(stream, digest);
69
+ }
70
+ return digest;
71
+ }
72
+ function verifiedGlyphTableOfProgram(descriptor, family, context) {
73
+ for (const key of [
74
+ "FontFile3",
75
+ "FontFile2",
76
+ "FontFile"
77
+ ]) {
78
+ const stream = context.resolver.resolve(require_objects.dictGet(descriptor, key));
79
+ if (stream?.kind === "stream") return require_verified_glyph_tables.verifiedGlyphTableOfDigest(family, digestOfFontStream(stream, context), context.verifiedGlyphTables);
80
+ }
81
+ }
53
82
  function readDifferencesMap(differences) {
54
83
  const map = /* @__PURE__ */ new Map();
55
84
  if (differences === void 0) return map;
@@ -86,6 +115,7 @@ function buildSimpleFont(fontDict, context) {
86
115
  const symbolic = styleFlags.symbolic ?? false;
87
116
  const builtinGlyphName = builtinSymbolGlyphNameLookup(baseFamily);
88
117
  const symbolFont = require_symbol_font_private_use.symbolFontOf(baseFamily);
118
+ const verifiedGlyphs = lazyVerifiedGlyphTable(descriptor, baseFamily, context);
89
119
  const widthsArr = resolvedArray(require_objects.dictGet(fontDict, "Widths"), context);
90
120
  const firstChar = require_objects.asNumber(require_objects.dictGet(fontDict, "FirstChar")) ?? 0;
91
121
  const missingWidth = (descriptor !== void 0 ? require_objects.asNumber(require_objects.dictGet(descriptor, "MissingWidth")) : void 0) ?? 0;
@@ -125,9 +155,19 @@ function buildSimpleFont(fontDict, context) {
125
155
  });
126
156
  const differencesMap = readDifferencesMap(encodingDict !== void 0 ? resolvedArray(require_objects.dictGet(encodingDict, "Differences"), context) : void 0);
127
157
  const programEncoding = lazyFontProgram(descriptor, baseFont, context);
158
+ let numberedTexts;
159
+ const numberedCoreFaceTexts = () => {
160
+ numberedTexts ??= require_core_font_glyph_orders.coreFaceTextsOfNumberedGlyphs(baseFont, [...differencesMap].map(([code, name]) => ({
161
+ name,
162
+ width: widthOf(code)
163
+ }))) ?? /* @__PURE__ */ new Map();
164
+ return numberedTexts;
165
+ };
128
166
  const unicodeForDifferencesName = (name) => {
129
167
  const named = require_glyph_names.glyphNameToUnicode(name);
130
168
  if (named !== void 0) return named;
169
+ const ofCoreFace = numberedCoreFaceTexts().get(name);
170
+ if (ofCoreFace !== void 0) return ofCoreFace;
131
171
  const program = programEncoding();
132
172
  const glyphId = program?.glyphIdOfName(name);
133
173
  return glyphId === void 0 ? void 0 : program?.glyphIdToUnicode(glyphId);
@@ -160,6 +200,12 @@ function buildSimpleFont(fontDict, context) {
160
200
  let out = "";
161
201
  let unmapped = 0;
162
202
  for (const code of codes) {
203
+ const glyphName = differencesMap.get(code) ?? namedBaseGlyphName?.(code);
204
+ const verified = glyphName === void 0 ? void 0 : verifiedGlyphs()?.glyphs.get(glyphName);
205
+ if (verified !== void 0) {
206
+ out += verified;
207
+ continue;
208
+ }
163
209
  const viaToUnicode = toUnicode?.lookup(code);
164
210
  if (viaToUnicode !== void 0) {
165
211
  out += require_symbol_font_private_use.withSymbolFontCharacters(symbolFont, viaToUnicode);
@@ -1,6 +1,7 @@
1
1
  import { i as PdfDiagnosticSink } from "./diagnostics-BkG_Ej2x.cjs";
2
2
  import { n as PdfDict } from "./objects-e2RS3-yT.cjs";
3
3
  import { FontMetricsPort, PdfObjectResolver } from "./interpret-types.cjs";
4
+ import { i as VerifiedGlyphTableSource } from "./verified-glyph-tables-B_ZoMhyT.cjs";
4
5
  //#region src/font-read.d.ts
5
6
  interface VerticalGlyphMetrics {
6
7
  readonly displacementY: number;
@@ -19,6 +20,8 @@ interface PdfFont {
19
20
  interface FontReadContext {
20
21
  readonly resolver: PdfObjectResolver;
21
22
  readonly sink: PdfDiagnosticSink;
23
+ /** The verified font subsets to read a font through, by the SHA-256 of its decoded program; the subsets `verified-glyph-tables.ts` holds when absent. Supplied by a test that builds its own. */
24
+ readonly verifiedGlyphTables?: ReadonlyMap<string, VerifiedGlyphTableSource>;
22
25
  }
23
26
  interface FontResolverService {
24
27
  readonly metrics: FontMetricsPort;
@@ -1,6 +1,7 @@
1
1
  import { i as PdfDiagnosticSink } from "./diagnostics-BkG_Ej2x.js";
2
2
  import { n as PdfDict } from "./objects-e2RS3-yT.js";
3
3
  import { FontMetricsPort, PdfObjectResolver } from "./interpret-types.js";
4
+ import { i as VerifiedGlyphTableSource } from "./verified-glyph-tables-B_ZoMhyT.js";
4
5
  //#region src/font-read.d.ts
5
6
  interface VerticalGlyphMetrics {
6
7
  readonly displacementY: number;
@@ -19,6 +20,8 @@ interface PdfFont {
19
20
  interface FontReadContext {
20
21
  readonly resolver: PdfObjectResolver;
21
22
  readonly sink: PdfDiagnosticSink;
23
+ /** The verified font subsets to read a font through, by the SHA-256 of its decoded program; the subsets `verified-glyph-tables.ts` holds when absent. Supplied by a test that builds its own. */
24
+ readonly verifiedGlyphTables?: ReadonlyMap<string, VerifiedGlyphTableSource>;
22
25
  }
23
26
  interface FontResolverService {
24
27
  readonly metrics: FontMetricsPort;
package/dist/font-read.js CHANGED
@@ -2,6 +2,7 @@ import { namedEncodingGlyphName, winAnsiGlyphName } from "./encoding.js";
2
2
  import { afmWidthOfCode } from "./afm-widths.js";
3
3
  import { asArray, asName, asNumber, dictGet, isName } from "./objects.js";
4
4
  import { decodeStream } from "./filters.js";
5
+ import { coreFaceTextsOfNumberedGlyphs } from "./core-font-glyph-orders.js";
5
6
  import { styleFromBaseFontName } from "./font-style.js";
6
7
  import { glyphNameToUnicode } from "./glyph-names.js";
7
8
  import { readFontProgramEncoding } from "./builtin-encoding.js";
@@ -9,6 +10,7 @@ import { parseToUnicodeCMap } from "./cmap.js";
9
10
  import { symbolGlyphName, zapfDingbatsGlyphName } from "./encoding-symbol-tables.js";
10
11
  import { resolveStandardFont } from "./fonts.js";
11
12
  import { symbolFontOf, withSymbolFontCharacters } from "./symbol-font-private-use.js";
13
+ import { VERIFIED_GLYPH_FAMILIES, programDigest, verifiedGlyphTableOfDigest } from "./verified-glyph-tables.js";
12
14
  //#region src/font-read.ts
13
15
  const BITS_PER_BYTE = 8;
14
16
  const SYMBOLIC_FLAG_BIT = 4;
@@ -49,6 +51,33 @@ function lazyFontProgram(descriptor, baseFont, context) {
49
51
  return read.encoding;
50
52
  };
51
53
  }
54
+ function lazyVerifiedGlyphTable(descriptor, family, context) {
55
+ let read;
56
+ return () => {
57
+ if (descriptor === void 0 || !VERIFIED_GLYPH_FAMILIES.has(family)) return;
58
+ read ??= { table: verifiedGlyphTableOfProgram(descriptor, family, context) };
59
+ return read.table;
60
+ };
61
+ }
62
+ const PROGRAM_DIGESTS = /* @__PURE__ */ new WeakMap();
63
+ function digestOfFontStream(stream, context) {
64
+ let digest = PROGRAM_DIGESTS.get(stream);
65
+ if (digest === void 0) {
66
+ digest = programDigest(decodeStream(stream.raw, stream.dict, context.sink).bytes);
67
+ PROGRAM_DIGESTS.set(stream, digest);
68
+ }
69
+ return digest;
70
+ }
71
+ function verifiedGlyphTableOfProgram(descriptor, family, context) {
72
+ for (const key of [
73
+ "FontFile3",
74
+ "FontFile2",
75
+ "FontFile"
76
+ ]) {
77
+ const stream = context.resolver.resolve(dictGet(descriptor, key));
78
+ if (stream?.kind === "stream") return verifiedGlyphTableOfDigest(family, digestOfFontStream(stream, context), context.verifiedGlyphTables);
79
+ }
80
+ }
52
81
  function readDifferencesMap(differences) {
53
82
  const map = /* @__PURE__ */ new Map();
54
83
  if (differences === void 0) return map;
@@ -85,6 +114,7 @@ function buildSimpleFont(fontDict, context) {
85
114
  const symbolic = styleFlags.symbolic ?? false;
86
115
  const builtinGlyphName = builtinSymbolGlyphNameLookup(baseFamily);
87
116
  const symbolFont = symbolFontOf(baseFamily);
117
+ const verifiedGlyphs = lazyVerifiedGlyphTable(descriptor, baseFamily, context);
88
118
  const widthsArr = resolvedArray(dictGet(fontDict, "Widths"), context);
89
119
  const firstChar = asNumber(dictGet(fontDict, "FirstChar")) ?? 0;
90
120
  const missingWidth = (descriptor !== void 0 ? asNumber(dictGet(descriptor, "MissingWidth")) : void 0) ?? 0;
@@ -124,9 +154,19 @@ function buildSimpleFont(fontDict, context) {
124
154
  });
125
155
  const differencesMap = readDifferencesMap(encodingDict !== void 0 ? resolvedArray(dictGet(encodingDict, "Differences"), context) : void 0);
126
156
  const programEncoding = lazyFontProgram(descriptor, baseFont, context);
157
+ let numberedTexts;
158
+ const numberedCoreFaceTexts = () => {
159
+ numberedTexts ??= coreFaceTextsOfNumberedGlyphs(baseFont, [...differencesMap].map(([code, name]) => ({
160
+ name,
161
+ width: widthOf(code)
162
+ }))) ?? /* @__PURE__ */ new Map();
163
+ return numberedTexts;
164
+ };
127
165
  const unicodeForDifferencesName = (name) => {
128
166
  const named = glyphNameToUnicode(name);
129
167
  if (named !== void 0) return named;
168
+ const ofCoreFace = numberedCoreFaceTexts().get(name);
169
+ if (ofCoreFace !== void 0) return ofCoreFace;
130
170
  const program = programEncoding();
131
171
  const glyphId = program?.glyphIdOfName(name);
132
172
  return glyphId === void 0 ? void 0 : program?.glyphIdToUnicode(glyphId);
@@ -159,6 +199,12 @@ function buildSimpleFont(fontDict, context) {
159
199
  let out = "";
160
200
  let unmapped = 0;
161
201
  for (const code of codes) {
202
+ const glyphName = differencesMap.get(code) ?? namedBaseGlyphName?.(code);
203
+ const verified = glyphName === void 0 ? void 0 : verifiedGlyphs()?.glyphs.get(glyphName);
204
+ if (verified !== void 0) {
205
+ out += verified;
206
+ continue;
207
+ }
162
208
  const viaToUnicode = toUnicode?.lookup(code);
163
209
  if (viaToUnicode !== void 0) {
164
210
  out += withSymbolFontCharacters(symbolFont, viaToUnicode);
@@ -0,0 +1,38 @@
1
+ //#region src/verified-glyph-tables.d.ts
2
+ /**
3
+ * The SHA-256 of a decoded font program as lower-case hex, the key the verified subsets are filed under.
4
+ * @param bytes - The decoded bytes of the embedded font program.
5
+ * @returns The digest.
6
+ */
7
+ declare function programDigest(bytes: Uint8Array<ArrayBuffer>): string;
8
+ /** One verified subset: the font family its `/BaseFont` names (the subset tag taken off) and the character each listed glyph draws, by the program's own glyph name. */
9
+ interface VerifiedGlyphTable {
10
+ readonly family: string;
11
+ readonly glyphs: ReadonlyMap<string, string>;
12
+ }
13
+ interface VerifiedGlyphTableSource {
14
+ readonly family: string;
15
+ readonly glyphs: Readonly<Record<string, string>>;
16
+ }
17
+ /** The subsets checked so far, by the SHA-256 of the decoded font program. */
18
+ declare const VERIFIED_SOURCES: ReadonlyMap<string, VerifiedGlyphTableSource>;
19
+ /** The font families some verified subset belongs to: a font of any other family is not hashed. */
20
+ declare const VERIFIED_GLYPH_FAMILIES: ReadonlySet<string>;
21
+ /**
22
+ * The verified table for an embedded font program given its digest, or `undefined` for any program that is not exactly one that was checked.
23
+ * @param family - The font's family as `styleFromBaseFontName` reads it: the table is used only for a font whose family is the one it was checked for.
24
+ * @param digest - The SHA-256 of the decoded font program (`programDigest`).
25
+ * @param tables - The verified subsets to look in, by program SHA-256; the checked ones unless a test supplies its own.
26
+ * @returns The table for that subset, or `undefined`.
27
+ */
28
+ declare function verifiedGlyphTableOfDigest(family: string, digest: string, tables?: ReadonlyMap<string, VerifiedGlyphTableSource>): VerifiedGlyphTable | undefined;
29
+ /**
30
+ * The verified table for an embedded font program, or `undefined` for any program that is not exactly one that was checked.
31
+ * @param family - The font's family as `styleFromBaseFontName` reads it: the table is used only for a font whose family is the one it was checked for.
32
+ * @param program - The decoded bytes of the embedded font program.
33
+ * @param tables - The verified subsets to look in, by program SHA-256; the checked ones unless a test supplies its own.
34
+ * @returns The table for that subset, or `undefined`.
35
+ */
36
+ declare function verifiedGlyphTableFor(family: string, program: Uint8Array<ArrayBuffer>, tables?: ReadonlyMap<string, VerifiedGlyphTableSource>): VerifiedGlyphTable | undefined;
37
+ //#endregion
38
+ export { programDigest as a, VerifiedGlyphTableSource as i, VERIFIED_SOURCES as n, verifiedGlyphTableFor as o, VerifiedGlyphTable as r, verifiedGlyphTableOfDigest as s, VERIFIED_GLYPH_FAMILIES as t };
@@ -0,0 +1,38 @@
1
+ //#region src/verified-glyph-tables.d.ts
2
+ /**
3
+ * The SHA-256 of a decoded font program as lower-case hex, the key the verified subsets are filed under.
4
+ * @param bytes - The decoded bytes of the embedded font program.
5
+ * @returns The digest.
6
+ */
7
+ declare function programDigest(bytes: Uint8Array<ArrayBuffer>): string;
8
+ /** One verified subset: the font family its `/BaseFont` names (the subset tag taken off) and the character each listed glyph draws, by the program's own glyph name. */
9
+ interface VerifiedGlyphTable {
10
+ readonly family: string;
11
+ readonly glyphs: ReadonlyMap<string, string>;
12
+ }
13
+ interface VerifiedGlyphTableSource {
14
+ readonly family: string;
15
+ readonly glyphs: Readonly<Record<string, string>>;
16
+ }
17
+ /** The subsets checked so far, by the SHA-256 of the decoded font program. */
18
+ declare const VERIFIED_SOURCES: ReadonlyMap<string, VerifiedGlyphTableSource>;
19
+ /** The font families some verified subset belongs to: a font of any other family is not hashed. */
20
+ declare const VERIFIED_GLYPH_FAMILIES: ReadonlySet<string>;
21
+ /**
22
+ * The verified table for an embedded font program given its digest, or `undefined` for any program that is not exactly one that was checked.
23
+ * @param family - The font's family as `styleFromBaseFontName` reads it: the table is used only for a font whose family is the one it was checked for.
24
+ * @param digest - The SHA-256 of the decoded font program (`programDigest`).
25
+ * @param tables - The verified subsets to look in, by program SHA-256; the checked ones unless a test supplies its own.
26
+ * @returns The table for that subset, or `undefined`.
27
+ */
28
+ declare function verifiedGlyphTableOfDigest(family: string, digest: string, tables?: ReadonlyMap<string, VerifiedGlyphTableSource>): VerifiedGlyphTable | undefined;
29
+ /**
30
+ * The verified table for an embedded font program, or `undefined` for any program that is not exactly one that was checked.
31
+ * @param family - The font's family as `styleFromBaseFontName` reads it: the table is used only for a font whose family is the one it was checked for.
32
+ * @param program - The decoded bytes of the embedded font program.
33
+ * @param tables - The verified subsets to look in, by program SHA-256; the checked ones unless a test supplies its own.
34
+ * @returns The table for that subset, or `undefined`.
35
+ */
36
+ declare function verifiedGlyphTableFor(family: string, program: Uint8Array<ArrayBuffer>, tables?: ReadonlyMap<string, VerifiedGlyphTableSource>): VerifiedGlyphTable | undefined;
37
+ //#endregion
38
+ export { programDigest as a, VerifiedGlyphTableSource as i, VERIFIED_SOURCES as n, verifiedGlyphTableFor as o, VerifiedGlyphTable as r, verifiedGlyphTableOfDigest as s, VERIFIED_GLYPH_FAMILIES as t };