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
@@ -21,10 +21,32 @@ var STANDARD = new Map([
21
21
  ...table(96, "quoteleft"),
22
22
  ...table(160, ". exclamdown cent sterling fraction yen florin section currency quotesingle quotedblleft guillemotleft guilsinglleft guilsinglright fi fl", ". endash dagger daggerdbl periodcentered . paragraph bullet quotesinglbase quotedblbase quotedblright guillemotright ellipsis perthousand . questiondown", ". grave acute circumflex tilde macron breve dotaccent dieresis . ring cedilla . hungarumlaut ogonek caron", "emdash . . . . . . . . . . . . . . .", ". AE . ordfeminine . . . . Lslash Oslash OE ordmasculine . . . .", ". ae . . . dotlessi . . lslash oslash oe germandbls . . . .")
23
23
  ]);
24
+ /** Annex D.2 WIN — CP-1252. Only its punctuation block departs from Latin-1. */
25
+ var WIN_ANSI = new Map([...ASCII, ...table(128, "Euro . quotesinglbase florin quotedblbase ellipsis dagger daggerdbl circumflex perthousand Scaron guilsinglleft OE . Zcaron .", ". quoteleft quoteright quotedblleft quotedblright bullet endash emdash tilde trademark scaron guilsinglright oe . zcaron Ydieresis")]);
26
+ /** Annex D.2 MAC — Mac OS Roman, as the PDF variant states it. */
27
+ var MAC_ROMAN = new Map([...ASCII, ...table(128, "Adieresis Aring Ccedilla Eacute Ntilde Odieresis Udieresis aacute agrave acircumflex adieresis atilde aring ccedilla eacute egrave", "ecircumflex edieresis iacute igrave icircumflex idieresis ntilde oacute ograve ocircumflex odieresis otilde uacute ugrave ucircumflex udieresis", "dagger degree cent sterling section bullet paragraph germandbls registered copyright trademark acute dieresis notequal AE Oslash", "infinity plusminus lessequal greaterequal yen mu partialdiff summation product pi integral ordfeminine ordmasculine Omega ae oslash", "questiondown exclamdown logicalnot radical florin approxequal Delta guillemotleft guillemotright ellipsis space Agrave Atilde Otilde OE oe", "endash emdash quotedblleft quotedblright quoteleft quoteright divide lozenge ydieresis Ydieresis fraction currency guilsinglleft guilsinglright fi fl", "daggerdbl periodcentered quotesinglbase quotedblbase perthousand Acircumflex Ecircumflex Aacute Edieresis Egrave Iacute Icircumflex Idieresis Igrave Oacute Ocircumflex", ". Ograve Uacute Ucircumflex Ugrave dotlessi circumflex tilde macron breve dotaccent ring cedilla hungarumlaut ogonek caron")]);
28
+ /**
29
+ * Annex D.2 WIN — the glyph NAMES of CP-1252's upper half, which the table
30
+ * above leaves to Latin-1 for the TEXT. A program addressed by name still has
31
+ * to be told which glyph 0xE9 is: `eacute`, looked up by name or through the
32
+ * character a Unicode `cmap` keys it by. (0xA0 is `space` and 0xAD `hyphen`
33
+ * here, as Annex D names the glyphs they draw.)
34
+ */
35
+ var WIN_ANSI_LATIN = table(160, "space exclamdown cent sterling currency yen brokenbar section dieresis copyright ordfeminine guillemotleft logicalnot hyphen registered macron", "degree plusminus twosuperior threesuperior acute mu paragraph periodcentered cedilla onesuperior ordmasculine guillemotright onequarter onehalf threequarters questiondown", "Agrave Aacute Acircumflex Atilde Adieresis Aring AE Ccedilla Egrave Eacute Ecircumflex Edieresis Igrave Iacute Icircumflex Idieresis", "Eth Ntilde Ograve Oacute Ocircumflex Otilde Odieresis multiply Oslash Ugrave Uacute Ucircumflex Udieresis Yacute Thorn germandbls", "agrave aacute acircumflex atilde adieresis aring ae ccedilla egrave eacute ecircumflex edieresis igrave iacute icircumflex idieresis", "eth ntilde ograve oacute ocircumflex otilde odieresis divide oslash ugrave uacute ucircumflex udieresis yacute thorn ydieresis");
36
+ /**
37
+ * The glyph a code of WinAnsiEncoding's Latin-1 half selects, by name (see
38
+ * `WIN_ANSI_LATIN`).
39
+ *
40
+ * @param code A one-byte code.
41
+ * @returns The Annex D.2 glyph name, or `undefined` outside 0xA0–0xFF.
42
+ */
43
+ function winAnsiLatinName(code) {
44
+ return WIN_ANSI_LATIN.get(code);
45
+ }
24
46
  var TABLES = {
25
47
  StandardEncoding: STANDARD,
26
- WinAnsiEncoding: new Map([...ASCII, ...table(128, "Euro . quotesinglbase florin quotedblbase ellipsis dagger daggerdbl circumflex perthousand Scaron guilsinglleft OE . Zcaron .", ". quoteleft quoteright quotedblleft quotedblright bullet endash emdash tilde trademark scaron guilsinglright oe . zcaron Ydieresis")]),
27
- MacRomanEncoding: new Map([...ASCII, ...table(128, "Adieresis Aring Ccedilla Eacute Ntilde Odieresis Udieresis aacute agrave acircumflex adieresis atilde aring ccedilla eacute egrave", "ecircumflex edieresis iacute igrave icircumflex idieresis ntilde oacute ograve ocircumflex odieresis otilde uacute ugrave ucircumflex udieresis", "dagger degree cent sterling section bullet paragraph germandbls registered copyright trademark acute dieresis notequal AE Oslash", "infinity plusminus lessequal greaterequal yen mu partialdiff summation product pi integral ordfeminine ordmasculine Omega ae oslash", "questiondown exclamdown logicalnot radical florin approxequal Delta guillemotleft guillemotright ellipsis space Agrave Atilde Otilde OE oe", "endash emdash quotedblleft quotedblright quoteleft quoteright divide lozenge ydieresis Ydieresis fraction currency guilsinglleft guilsinglright fi fl", "daggerdbl periodcentered quotesinglbase quotedblbase perthousand Acircumflex Ecircumflex Aacute Edieresis Egrave Iacute Icircumflex Idieresis Igrave Oacute Ocircumflex", ". Ograve Uacute Ucircumflex Ugrave dotlessi circumflex tilde macron breve dotaccent ring cedilla hungarumlaut ogonek caron")])
48
+ WinAnsiEncoding: WIN_ANSI,
49
+ MacRomanEncoding: MAC_ROMAN
28
50
  };
29
51
  /**
30
52
  * The glyph names of a base encoding, or `undefined` for a name this has no
@@ -107,4 +129,4 @@ function isStandardLatinFace(baseFont) {
107
129
  return STANDARD_LATIN_FACES.has(baseFont.trim().replace(/^[A-Za-z]{6}\+/u, "").toLowerCase());
108
130
  }
109
131
  //#endregion
110
- export { baseEncodingTable, isStandardLatinFace, macGlyphName, standardEncodingTable };
132
+ export { baseEncodingTable, isStandardLatinFace, macGlyphName, standardEncodingTable, winAnsiLatinName };
@@ -0,0 +1,78 @@
1
+ import { ContentFont, TextRun } from './content.js';
2
+ import { FaceOutlines } from '../core/ir/flow.js';
3
+ /** The codes each font showed where the pages paint them, gathered across pages. */
4
+ export type ShownCodes = Map<ContentFont, Set<number>>;
5
+ /**
6
+ * Fold one content stream's shown codes into the document's.
7
+ *
8
+ * @param into The document's codes, added to.
9
+ * @param from One stream's (`InterpretResult.shown`).
10
+ */
11
+ export declare function addShown(into: ShownCodes, from: ReadonlyMap<ContentFont, ReadonlySet<number>>): void;
12
+ /**
13
+ * The outlines of every face the pages painted characters in, keyed by the name
14
+ * a run in the face carries.
15
+ *
16
+ * A face is left out where its program cannot be trusted to say what the page
17
+ * showed: where more than a fifth of its characters have no glyph the reader
18
+ * can find, the way to its glyphs is not the one the page took, and a face
19
+ * embedded that way would show the wrong letters in place of a substitute's
20
+ * right ones. A character whose glyph cannot be found is left out alone — a
21
+ * reader draws it in another face.
22
+ *
23
+ * So is a face that shows a script which is SHAPED: an Arabic letter takes one
24
+ * of four forms by its neighbours, and a face rebuilt here carries one glyph
25
+ * per character and none of the rules that choose between them. Nor can it
26
+ * carry the part of such a face it could: ArabicCIDTrueType.pdf maps half its
27
+ * glyphs to presentation forms, which are shaped already, and half to plain
28
+ * letters — embedded, each line came out in two faces, a bold word with light
29
+ * letters in it.
30
+ *
31
+ * @param shown The codes the pages painted, per font.
32
+ * @param spacing How the pages space each face's text apart where they move the
33
+ * pen rather than draw a glyph (see {@link pageSpacing}).
34
+ * @returns Run font name → the face's outlines.
35
+ */
36
+ export declare function faceOutlinesOf(shown: ShownCodes, spacing?: PageSpacing): Map<string, FaceOutlines>;
37
+ /**
38
+ * How a page spaces a face's text apart where it moves the pen rather than
39
+ * draws a glyph — between words, and between two letters of one word.
40
+ */
41
+ export interface PageSpacing {
42
+ /** Face → the white it leaves between words, in thousandths of an em. */
43
+ readonly wordGaps: ReadonlyMap<string, number>;
44
+ /**
45
+ * Face → the pairs of letters it is KERNED by: the two characters → how far
46
+ * the second stands from where the first's advance leaves it, in thousandths
47
+ * of an em (negative tightens).
48
+ */
49
+ readonly kerning: ReadonlyMap<string, ReadonlyMap<string, number>>;
50
+ }
51
+ /**
52
+ * §9.4.3 — what the gaps between a face's runs say about how it is spaced.
53
+ *
54
+ * The interpreter emits a run for every string of a `TJ` array, so where the
55
+ * page nudges the pen inside a word — which is how a producer KERNS it — two
56
+ * runs meet with the nudge between them, and the letters on either side are
57
+ * the pair. bigboundingbox.pdf kerns its Calibri by eight pairs in one short
58
+ * line, and set without them the line came out 0.67pt longer than the page's
59
+ * and no longer fit the cell it was measured into. Each pair is the median of
60
+ * every nudge the page gives it.
61
+ *
62
+ * A wider gap is the white between two words, where the page moves the pen
63
+ * instead of showing a space; the face's space is the lower quartile of them,
64
+ * which a justified line's stretched gaps do not reach.
65
+ *
66
+ * @param pages The runs each page set, in the order they were painted.
67
+ * @returns Per face, its word gap and its kerning pairs.
68
+ */
69
+ export declare function pageSpacing(pages: ReadonlyArray<ReadonlyArray<TextRun>>): PageSpacing;
70
+ /**
71
+ * The faces a page KERNED: those with kerning pairs (see {@link pageSpacing},
72
+ * which keeps a face's pairs only where there are enough of them to say it was
73
+ * kerning and not an accident of how its strings were cut).
74
+ *
75
+ * @param spacing How the pages space their faces (see {@link pageSpacing}).
76
+ * @returns The run font names of the kerned faces.
77
+ */
78
+ export declare function kernedFaces(spacing: PageSpacing): Set<string>;
@@ -0,0 +1,362 @@
1
+ import { editableEmbedding } from "../core/font/ttf-build.js";
2
+ import { isRightToLeft } from "./content.js";
3
+ import { ASCENDER, NATURAL_LINE_EM } from "./flow-build.js";
4
+ //#region src/pdf-reader/face-outlines.ts
5
+ /**
6
+ * Fold one content stream's shown codes into the document's.
7
+ *
8
+ * @param into The document's codes, added to.
9
+ * @param from One stream's (`InterpretResult.shown`).
10
+ */
11
+ function addShown(into, from) {
12
+ for (const [font, codes] of from) {
13
+ let set = into.get(font);
14
+ if (!set) into.set(font, set = /* @__PURE__ */ new Set());
15
+ for (const code of codes) set.add(code);
16
+ }
17
+ }
18
+ /**
19
+ * The outlines of every face the pages painted characters in, keyed by the name
20
+ * a run in the face carries.
21
+ *
22
+ * A face is left out where its program cannot be trusted to say what the page
23
+ * showed: where more than a fifth of its characters have no glyph the reader
24
+ * can find, the way to its glyphs is not the one the page took, and a face
25
+ * embedded that way would show the wrong letters in place of a substitute's
26
+ * right ones. A character whose glyph cannot be found is left out alone — a
27
+ * reader draws it in another face.
28
+ *
29
+ * So is a face that shows a script which is SHAPED: an Arabic letter takes one
30
+ * of four forms by its neighbours, and a face rebuilt here carries one glyph
31
+ * per character and none of the rules that choose between them. Nor can it
32
+ * carry the part of such a face it could: ArabicCIDTrueType.pdf maps half its
33
+ * glyphs to presentation forms, which are shaped already, and half to plain
34
+ * letters — embedded, each line came out in two faces, a bold word with light
35
+ * letters in it.
36
+ *
37
+ * @param shown The codes the pages painted, per font.
38
+ * @param spacing How the pages space each face's text apart where they move the
39
+ * pen rather than draw a glyph (see {@link pageSpacing}).
40
+ * @returns Run font name → the face's outlines.
41
+ */
42
+ function faceOutlinesOf(shown, spacing = NO_SPACING) {
43
+ const faces = /* @__PURE__ */ new Map();
44
+ for (const [font, codes] of shown) {
45
+ const program = font.program;
46
+ if (!program || font.name === void 0) continue;
47
+ let face = faces.get(font.name);
48
+ if (!face) {
49
+ face = {
50
+ font,
51
+ program,
52
+ glyphs: /* @__PURE__ */ new Map(),
53
+ ligatures: /* @__PURE__ */ new Map(),
54
+ sought: 0,
55
+ missed: 0,
56
+ shaped: false
57
+ };
58
+ faces.set(font.name, face);
59
+ }
60
+ if (program.fsType !== void 0 && (face.fsType === void 0 || editableEmbedding(face.fsType))) face.fsType = program.fsType;
61
+ for (const code of [...codes].sort((a, b) => a - b)) gather(face, font, program, code);
62
+ }
63
+ const out = /* @__PURE__ */ new Map();
64
+ for (const [name, face] of faces) {
65
+ const outlines = outlinesOf(face, spacing.wordGaps.get(name), spacing.kerning.get(name));
66
+ if (outlines) out.set(name, outlines);
67
+ }
68
+ return out;
69
+ }
70
+ var NO_SPACING = {
71
+ wordGaps: /* @__PURE__ */ new Map(),
72
+ kerning: /* @__PURE__ */ new Map()
73
+ };
74
+ /**
75
+ * §9.4.3 — what the gaps between a face's runs say about how it is spaced.
76
+ *
77
+ * The interpreter emits a run for every string of a `TJ` array, so where the
78
+ * page nudges the pen inside a word — which is how a producer KERNS it — two
79
+ * runs meet with the nudge between them, and the letters on either side are
80
+ * the pair. bigboundingbox.pdf kerns its Calibri by eight pairs in one short
81
+ * line, and set without them the line came out 0.67pt longer than the page's
82
+ * and no longer fit the cell it was measured into. Each pair is the median of
83
+ * every nudge the page gives it.
84
+ *
85
+ * A wider gap is the white between two words, where the page moves the pen
86
+ * instead of showing a space; the face's space is the lower quartile of them,
87
+ * which a justified line's stretched gaps do not reach.
88
+ *
89
+ * @param pages The runs each page set, in the order they were painted.
90
+ * @returns Per face, its word gap and its kerning pairs.
91
+ */
92
+ function pageSpacing(pages) {
93
+ const gaps = /* @__PURE__ */ new Map();
94
+ const nudges = /* @__PURE__ */ new Map();
95
+ const beside = /* @__PURE__ */ new Map();
96
+ for (const runs of pages) for (let i = 1; i < runs.length; i++) {
97
+ const a = runs[i - 1];
98
+ const b = runs[i];
99
+ const name = a.fontName;
100
+ if (name === void 0 || b.fontName !== name || a.angleDeg !== void 0) continue;
101
+ if (b.angleDeg !== void 0 || a.fontSizePt !== b.fontSizePt || !(a.fontSizePt > 0)) continue;
102
+ if (Math.abs(a.y - b.y) > a.fontSizePt * SAME_LINE_EM) continue;
103
+ if (isRightToLeft(a.text) || isRightToLeft(b.text)) continue;
104
+ const left = [...a.text].at(-1);
105
+ const right = [...b.text][0];
106
+ if (left === void 0 || right === void 0) continue;
107
+ const gap = (b.x - a.endX) / a.fontSizePt * 1e3;
108
+ if (/\s/u.test(left) || /\s/u.test(right)) {
109
+ if (gap > MIN_KERN && gap < MAX_KERN) {
110
+ let list = beside.get(name);
111
+ if (!list) beside.set(name, list = []);
112
+ list.push(b.x - a.endX);
113
+ }
114
+ continue;
115
+ }
116
+ if (gap >= MIN_WORD_GAP && gap <= MAX_WORD_GAP) {
117
+ let list = gaps.get(name);
118
+ if (!list) gaps.set(name, list = []);
119
+ list.push(gap);
120
+ } else if (gap > MIN_KERN && gap < MAX_KERN) {
121
+ let pairs = nudges.get(name);
122
+ if (!pairs) nudges.set(name, pairs = /* @__PURE__ */ new Map());
123
+ const pair = `${left}${right}`;
124
+ let list = pairs.get(pair);
125
+ if (!list) pairs.set(pair, list = []);
126
+ list.push([gap, b.x - a.endX]);
127
+ }
128
+ }
129
+ const wordGaps = /* @__PURE__ */ new Map();
130
+ for (const [name, list] of gaps) {
131
+ if (list.length < MIN_GAP_SAMPLES) continue;
132
+ list.sort((p, q) => p - q);
133
+ wordGaps.set(name, list[Math.floor(list.length / 4)]);
134
+ }
135
+ const kerning = /* @__PURE__ */ new Map();
136
+ for (const [name, pairs] of nudges) {
137
+ if (tracked([...pairs.values()].flat().map(([, points]) => points), beside.get(name) ?? [])) continue;
138
+ const kept = /* @__PURE__ */ new Map();
139
+ for (const [pair, list] of pairs) {
140
+ const inEm = median(list.map(([em]) => em));
141
+ const inPt = median(list.map(([, points]) => points));
142
+ if (Math.abs(inEm) < MIN_KERN_VALUE || Math.abs(inPt) < MIN_KERN_PT) continue;
143
+ if (list.length >= 2 && list.every(([, points]) => Math.abs(points - inPt) <= TWIP_PT) || Math.abs(inPt) >= SURE_KERN_PT) kept.set(pair, inEm);
144
+ }
145
+ if (kept.size >= MIN_KERNED_PAIRS) kerning.set(name, kept);
146
+ }
147
+ return {
148
+ wordGaps,
149
+ kerning
150
+ };
151
+ }
152
+ /**
153
+ * Whether a face's nudges are TRACKING rather than kerning: one amount between
154
+ * nearly every two letters, and beside its spaces too, where no face is kerned.
155
+ * bug1157493.pdf sets a line of Courier half a tenth of an em tight, every
156
+ * letter and every space alike; read as kerning pairs, its letters closed up
157
+ * and its spaces did not.
158
+ *
159
+ * @param nudges The face's nudges between letters, in points.
160
+ * @param beside Its nudges beside a space, in points.
161
+ */
162
+ function tracked(nudges, beside) {
163
+ if (beside.length < MIN_TRACKED_SPACES || nudges.length === 0) return false;
164
+ const typical = median([...nudges, ...beside]);
165
+ if (Math.abs(typical) < MIN_KERN_PT) return false;
166
+ const near = (points) => Math.abs(points - typical) <= TWIP_PT;
167
+ const all = [...nudges, ...beside];
168
+ return beside.filter(near).length >= MIN_TRACKED_SPACES && all.filter(near).length >= all.length * TRACKED_SHARE;
169
+ }
170
+ /** How many nudges beside spaces, and what share of all, make a face tracked. */
171
+ var MIN_TRACKED_SPACES = 2;
172
+ var TRACKED_SHARE = .8;
173
+ /**
174
+ * The faces a page KERNED: those with kerning pairs (see {@link pageSpacing},
175
+ * which keeps a face's pairs only where there are enough of them to say it was
176
+ * kerning and not an accident of how its strings were cut).
177
+ *
178
+ * @param spacing How the pages space their faces (see {@link pageSpacing}).
179
+ * @returns The run font names of the kerned faces.
180
+ */
181
+ function kernedFaces(spacing) {
182
+ return new Set(spacing.kerning.keys());
183
+ }
184
+ /** The middle of some numbers: the mean of the middle two where they are even. */
185
+ function median(values) {
186
+ const sorted = [...values].sort((p, q) => p - q);
187
+ const mid = sorted.length / 2;
188
+ return sorted.length % 2 === 1 ? sorted[Math.floor(mid)] : (sorted[mid - 1] + sorted[mid]) / 2;
189
+ }
190
+ /** Two runs within this much of an em of each other's baseline share a line. */
191
+ var SAME_LINE_EM = .1;
192
+ /** A gap between words: wider than a kern, narrower than a column's white. */
193
+ var MIN_WORD_GAP = 100;
194
+ var MAX_WORD_GAP = 600;
195
+ /**
196
+ * A nudge inside a word, in thousandths of an em: kerning tightens a pair by as
197
+ * much as a quarter of an em and opens one by far less — a gap wider than this
198
+ * is a space the page did not draw.
199
+ */
200
+ var MIN_KERN = -250;
201
+ var MAX_KERN = 60;
202
+ /** A nudge smaller than this is the rounding of a producer's positions, not a kern. */
203
+ var MIN_KERN_VALUE = 2;
204
+ /**
205
+ * …and so is one smaller than a twip and a half, in points: Word lays a line
206
+ * out in twips and writes the difference from the face's own widths as a
207
+ * nudge. annotation-highlight.pdf's Calibri is nudged three to six thousandths
208
+ * of an em between letters no kerning table pairs, and taken for kerns they
209
+ * moved its words a fraction of a pixel off the page's.
210
+ */
211
+ var MIN_KERN_PT = .075;
212
+ /** A twip, in points: the grid a Word page's positions are rounded to. */
213
+ var TWIP_PT = .05;
214
+ /**
215
+ * A nudge this wide is a kern seen once: rounding moves a letter a twip or
216
+ * two, and bug793632.pdf's Calibri is nudged two twips either way — "st" in
217
+ * and "on" out — once each, by no kerning at all.
218
+ */
219
+ var SURE_KERN_PT = .15;
220
+ /** How many pairs make a face one the page kerned. */
221
+ var MIN_KERNED_PAIRS = 2;
222
+ /** How many gaps it takes to say how wide a face's space is. */
223
+ var MIN_GAP_SAMPLES = 3;
224
+ /** Read one code's glyph into its face, where it stands for one character or a ligature. */
225
+ function gather(face, font, program, code) {
226
+ const chars = [...font.decode([code])];
227
+ if (chars.length > 1) {
228
+ gatherLigature(face, font, program, code, chars);
229
+ return;
230
+ }
231
+ if (chars.length !== 1) return;
232
+ const char = chars[0];
233
+ if (face.glyphs.has(char) || !embeddable(char)) return;
234
+ if (shaped(char)) {
235
+ face.shaped = true;
236
+ return;
237
+ }
238
+ const blank = WHITE.test(char);
239
+ if (!blank) face.sought++;
240
+ const outline = program.glyph(code);
241
+ if (outline === void 0 || outline.length === 0 && !blank) {
242
+ if (!blank) face.missed++;
243
+ return;
244
+ }
245
+ face.glyphs.set(char, {
246
+ outline,
247
+ advance: font.width(code)
248
+ });
249
+ }
250
+ /**
251
+ * §9.10.2 — a code that stands for a run of letters is a LIGATURE: one glyph
252
+ * the page drew for "fi", "ffl" or Calibri's "tt". The text carries the letters
253
+ * and the rebuilt face the glyph, so a reader that joins them draws what the
254
+ * page drew — set apart, attachment.pdf's "attachment" came out with two
255
+ * crossbars where the page has one.
256
+ *
257
+ * Only letters, and no more than {@link MAX_LIGATURE} of them: a producer that
258
+ * maps a glyph to a longer string — a logo to a company's name — has not drawn
259
+ * a ligature.
260
+ */
261
+ function gatherLigature(face, font, program, code, chars) {
262
+ const key = chars.join("");
263
+ if (chars.length > MAX_LIGATURE || face.ligatures.has(key)) return;
264
+ if (!chars.every((c) => /^\p{L}$/u.test(c) && !shaped(c))) return;
265
+ const outline = program.glyph(code);
266
+ if (outline === void 0 || outline.length === 0) return;
267
+ face.ligatures.set(key, {
268
+ outline,
269
+ advance: font.width(code)
270
+ });
271
+ }
272
+ /** The most letters one ligature joins: ffi, ffl. */
273
+ var MAX_LIGATURE = 3;
274
+ /** A face's gathered glyphs as {@link FaceOutlines}, where it is one to embed. */
275
+ function outlinesOf(face, gap, kerning) {
276
+ if (face.shaped || face.sought === 0 || face.missed > face.sought * MAX_MISSED_SHARE) return;
277
+ const glyphs = new Map(face.glyphs);
278
+ const program = face.program;
279
+ if (!glyphs.has(" ")) glyphs.set(" ", {
280
+ outline: [],
281
+ advance: spaceAdvance(face, gap)
282
+ });
283
+ if (!glyphs.has("\xA0")) glyphs.set("\xA0", glyphs.get(" "));
284
+ const { yMin, yMax } = extent(glyphs);
285
+ const pairs = new Map([...kerning ?? []].filter(([pair]) => [...pair].every((c) => glyphs.has(c))));
286
+ const ligatures = new Map([...face.ligatures].filter(([letters]) => [...letters].every((c) => glyphs.has(c))));
287
+ return {
288
+ glyphs,
289
+ ...pairs.size > 0 ? { kerning: pairs } : {},
290
+ ...ligatures.size > 0 ? { ligatures } : {},
291
+ ...face.fsType !== void 0 ? { fsType: face.fsType } : {},
292
+ bold: face.font.bold === true,
293
+ italic: face.font.italic === true,
294
+ postScriptName: program.postScriptName,
295
+ ascent: Math.max(ASCENDER * 1e3, yMax),
296
+ descent: Math.min((ASCENDER - NATURAL_LINE_EM) * 1e3, yMin),
297
+ ...program.capHeight !== void 0 ? { capHeight: program.capHeight } : {},
298
+ ...program.xHeight !== void 0 ? { xHeight: program.xHeight } : {},
299
+ italicAngle: program.italicAngle,
300
+ fixedPitch: program.fixedPitch
301
+ };
302
+ }
303
+ /** A face may miss no more than this share of the glyphs looked for. */
304
+ var MAX_MISSED_SHARE = .2;
305
+ /**
306
+ * The advance of a space the page never showed: the width the file states for
307
+ * the face's space (see `ContentFont.spaceWidth`), where it states one; the
308
+ * width the program gives its space glyph — which a subset keeps though it
309
+ * drops the outline;
310
+ * the white the page itself leaves between the face's words; the one advance
311
+ * every glyph has, in a fixed-pitch face; a quarter of an em otherwise.
312
+ * bigboundingbox.pdf sets its Calibri in a subset that keeps no space and no
313
+ * way to one, and spaces the words by hand: with a quarter of an em for
314
+ * Calibri's 226 thousandths, "Enter the amount you are paying above" no longer
315
+ * fit its cell.
316
+ */
317
+ function spaceAdvance(face, gap) {
318
+ const width = face.font.spaceWidth;
319
+ if (width !== void 0 && width > 0 && width < 1e3) return width;
320
+ if (face.program.spaceAdvance !== void 0) return face.program.spaceAdvance;
321
+ if (gap !== void 0) return gap;
322
+ if (face.program.fixedPitch) {
323
+ const first = [...face.glyphs.values()].find((g) => g.advance > 0);
324
+ if (first) return first.advance;
325
+ }
326
+ return 250;
327
+ }
328
+ /** How high and how low the glyphs' contours reach, in thousandths of an em. */
329
+ function extent(glyphs) {
330
+ let yMin = 0;
331
+ let yMax = 0;
332
+ for (const glyph of glyphs.values()) for (const seg of glyph.outline) {
333
+ if (seg.op === "close") continue;
334
+ yMin = Math.min(yMin, seg.y * 1e3);
335
+ yMax = Math.max(yMax, seg.y * 1e3);
336
+ }
337
+ return {
338
+ yMin,
339
+ yMax
340
+ };
341
+ }
342
+ /** Whitespace: a glyph that rightly draws nothing. */
343
+ var WHITE = /^\s$/u;
344
+ /** Whether a character is one a face carries at all: not a control or a format character. */
345
+ function embeddable(char) {
346
+ return !/^[\p{Cc}\p{Cf}\p{Cs}\uFFFD]$/u.test(char);
347
+ }
348
+ /**
349
+ * Whether a character is a letter of a script whose glyphs a face's shaping
350
+ * rules choose — which a rebuilt face does not have.
351
+ */
352
+ function shaped(char) {
353
+ const cp = char.codePointAt(0);
354
+ if (cp >= 64336 && cp <= 65023 || cp >= 65136 && cp <= 65279) return false;
355
+ if (cp >= 4352 && cp <= 4607 || cp >= 43360 && cp <= 43391) return true;
356
+ if (cp >= 55216 && cp <= 55295) return true;
357
+ return SHAPED.test(char);
358
+ }
359
+ /** The scripts whose glyphs a face's shaping rules choose. */
360
+ var SHAPED = /\p{Script=Arabic}|\p{Script=Syriac}|\p{Script=Thaana}|\p{Script=Nko}|\p{Script=Mandaic}|\p{Script=Devanagari}|\p{Script=Bengali}|\p{Script=Gurmukhi}|\p{Script=Gujarati}|\p{Script=Oriya}|\p{Script=Tamil}|\p{Script=Telugu}|\p{Script=Kannada}|\p{Script=Malayalam}|\p{Script=Sinhala}|\p{Script=Thai}|\p{Script=Lao}|\p{Script=Tibetan}|\p{Script=Myanmar}|\p{Script=Khmer}|\p{Script=Mongolian}|\p{Script=Javanese}|\p{Script=Balinese}/u;
361
+ //#endregion
362
+ export { addShown, faceOutlinesOf, kernedFaces, pageSpacing };
@@ -0,0 +1,52 @@
1
+ import { TextRun } from './content.js';
2
+ import { PdfImage } from './images.js';
3
+ import { PdfVector } from './vector.js';
4
+ /** One drawing on a page, with the words set on it. Page space, y-up. */
5
+ export interface PageFigure {
6
+ readonly minX: number;
7
+ readonly minY: number;
8
+ readonly maxX: number;
9
+ readonly maxY: number;
10
+ /** The paths that draw it, in no particular order. */
11
+ readonly vectors: ReadonlyArray<PdfVector>;
12
+ /** The pictures it holds. */
13
+ readonly images: ReadonlyArray<PdfImage>;
14
+ /** The words set on it. */
15
+ readonly labels: ReadonlyArray<TextRun>;
16
+ }
17
+ /**
18
+ * The figures a page draws.
19
+ *
20
+ * A figure is paths that stand together — each within half an em of the next —
21
+ * and DRAW something: a curve or a slanted line among them. Straight lines
22
+ * and boxes alone are a ruling, which is a table's or a form's, and a page
23
+ * reads those as text. It has words set on it, or it is only artwork, which
24
+ * the page anchors as it always has; and none of its words run across it
25
+ * line after line, which is prose set on a ground, not a label.
26
+ *
27
+ * @param vectors The page's paths, where the page shows them.
28
+ * @param images The page's pictures, likewise.
29
+ * @param runs The page's text, likewise.
30
+ * @param sheet The page's size.
31
+ * @returns The figures, top of the page first.
32
+ */
33
+ export declare function pageFigures(vectors: ReadonlyArray<PdfVector>, images: ReadonlyArray<PdfImage>, runs: ReadonlyArray<TextRun>, sheet: {
34
+ readonly width: number;
35
+ readonly height: number;
36
+ }): Array<PageFigure>;
37
+ /**
38
+ * Whether a run is lettering the file states no character for: glyphs traced
39
+ * as paths (see `PdfVector.glyph`), and a label only by where they stand.
40
+ *
41
+ * @param run A run of the page's text.
42
+ */
43
+ export declare function tracedRun(run: TextRun): boolean;
44
+ /**
45
+ * A figure's paths with the traced letters painted one after another joined
46
+ * into one: the letters of a label are one shape, where a shape a letter made
47
+ * a chart of six hundred.
48
+ *
49
+ * @param paths The figure's paths, in the order the page painted them.
50
+ * @returns The paths to draw, in that order.
51
+ */
52
+ export declare function joinLetters(paths: ReadonlyArray<PdfVector>): Array<PdfVector>;