reamkit 1.27.0 → 1.29.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 (68) hide show
  1. package/README.md +6 -5
  2. package/dist/esm/core/converter/ream.d.ts +8 -9
  3. package/dist/esm/core/converter/ream.js +0 -1
  4. package/dist/esm/core/document-model/types.d.ts +5 -3
  5. package/dist/esm/core/font/index.d.ts +1 -0
  6. package/dist/esm/core/font/ligatures.d.ts +17 -0
  7. package/dist/esm/core/font/ligatures.js +49 -0
  8. package/dist/esm/core/font/ttf-parser.d.ts +2 -1
  9. package/dist/esm/core/font/ttf-parser.js +11 -2
  10. package/dist/esm/core/fonts/remote-fonts.d.ts +1 -1
  11. package/dist/esm/core/fonts/remote-fonts.js +47 -2
  12. package/dist/esm/core/fonts/scripts.js +10 -5
  13. package/dist/esm/excel/header-footer.js +53 -8
  14. package/dist/esm/layout/styled-layout.js +53 -3
  15. package/dist/esm/pdf/cid-font.js +35 -8
  16. package/dist/esm/pdf-reader/annot-draw.js +114 -1
  17. package/dist/esm/pdf-reader/annots.d.ts +0 -14
  18. package/dist/esm/pdf-reader/annots.js +13 -1
  19. package/dist/esm/pdf-reader/ccitt.d.ts +2 -3
  20. package/dist/esm/pdf-reader/ccitt.js +32 -4
  21. package/dist/esm/pdf-reader/cff-outline.d.ts +36 -0
  22. package/dist/esm/pdf-reader/cff-outline.js +1122 -0
  23. package/dist/esm/pdf-reader/cmap.js +16 -6
  24. package/dist/esm/pdf-reader/content.d.ts +91 -0
  25. package/dist/esm/pdf-reader/content.js +156 -60
  26. package/dist/esm/pdf-reader/dingbats.d.ts +11 -0
  27. package/dist/esm/pdf-reader/dingbats.js +1033 -0
  28. package/dist/esm/pdf-reader/display.js +41 -1
  29. package/dist/esm/pdf-reader/document.d.ts +8 -0
  30. package/dist/esm/pdf-reader/document.js +26 -9
  31. package/dist/esm/pdf-reader/embedded-fonts.d.ts +11 -0
  32. package/dist/esm/pdf-reader/embedded-fonts.js +14 -1
  33. package/dist/esm/pdf-reader/encodings.d.ts +25 -0
  34. package/dist/esm/pdf-reader/encodings.js +110 -0
  35. package/dist/esm/pdf-reader/flow-build.d.ts +15 -4
  36. package/dist/esm/pdf-reader/flow-build.js +24 -11
  37. package/dist/esm/pdf-reader/font.js +329 -8
  38. package/dist/esm/pdf-reader/glyf-outline.d.ts +43 -0
  39. package/dist/esm/pdf-reader/glyf-outline.js +351 -0
  40. package/dist/esm/pdf-reader/glyph-names.js +20 -0
  41. package/dist/esm/pdf-reader/icc.d.ts +10 -0
  42. package/dist/esm/pdf-reader/icc.js +210 -0
  43. package/dist/esm/pdf-reader/image-decode.d.ts +4 -3
  44. package/dist/esm/pdf-reader/image-decode.js +85 -64
  45. package/dist/esm/pdf-reader/images.d.ts +6 -0
  46. package/dist/esm/pdf-reader/images.js +50 -7
  47. package/dist/esm/pdf-reader/jbig2.js +104 -19
  48. package/dist/esm/pdf-reader/layout.js +1531 -55
  49. package/dist/esm/pdf-reader/math-rows.d.ts +23 -0
  50. package/dist/esm/pdf-reader/math-rows.js +198 -0
  51. package/dist/esm/pdf-reader/predefined-cmap.d.ts +21 -0
  52. package/dist/esm/pdf-reader/predefined-cmap.js +102 -0
  53. package/dist/esm/pdf-reader/reader.d.ts +6 -9
  54. package/dist/esm/pdf-reader/reader.js +34 -21
  55. package/dist/esm/pdf-reader/shading.d.ts +81 -2
  56. package/dist/esm/pdf-reader/shading.js +191 -25
  57. package/dist/esm/pdf-reader/stream-filters.d.ts +6 -0
  58. package/dist/esm/pdf-reader/stream-filters.js +67 -0
  59. package/dist/esm/pdf-reader/tagged.js +1 -1
  60. package/dist/esm/pdf-reader/text-rules.js +83 -10
  61. package/dist/esm/pdf-reader/text.js +82 -3
  62. package/dist/esm/pdf-reader/type1-outline.d.ts +21 -0
  63. package/dist/esm/pdf-reader/type1-outline.js +576 -0
  64. package/dist/esm/pdf-reader/vector.d.ts +2 -0
  65. package/dist/esm/pdf-reader/vector.js +44 -4
  66. package/dist/esm/word/document-parser.js +4 -1
  67. package/dist/esm/word/docx-writer.js +28 -11
  68. package/package.json +1 -1
@@ -0,0 +1,23 @@
1
+ import { MathNode } from '../core/document-model/index.js';
2
+ import { TextRun } from './content.js';
3
+ /** A matrix found on the page: what to draw, where, and which runs it used. */
4
+ export interface MathBlock {
5
+ /** The math object — a row of delimiters and whatever stands between them. */
6
+ readonly math: MathNode;
7
+ /** The top baseline it was drawn on, for ordering the page's blocks. */
8
+ readonly top: number;
9
+ /** Where it stands across the page. */
10
+ readonly x: number;
11
+ readonly width: number;
12
+ /** The runs it consumed, which the prose reading must not read again. */
13
+ readonly used: ReadonlySet<TextRun>;
14
+ }
15
+ /**
16
+ * The matrices a page's rows hold (§22.1.2.68 `m:m`).
17
+ *
18
+ * @param runs The column's runs.
19
+ * @param fontSize The text's own size, which says how far apart the lines of
20
+ * PROSE stand — a display's sub-lines stand closer.
21
+ * @returns One entry per matrix found, in page order.
22
+ */
23
+ export declare function matrixBlocks(runs: ReadonlyArray<TextRun>, fontSize: number): Array<MathBlock>;
@@ -0,0 +1,198 @@
1
+ //#region src/pdf-reader/math-rows.ts
2
+ /** The brackets a matrix may be written in, opening → closing. */
3
+ var BRACKETS = new Map([
4
+ ["(", ")"],
5
+ ["[", "]"],
6
+ ["{", "}"],
7
+ ["⟨", "⟩"],
8
+ ["|", "|"]
9
+ ]);
10
+ /**
11
+ * The matrices a page's rows hold (§22.1.2.68 `m:m`).
12
+ *
13
+ * @param runs The column's runs.
14
+ * @param fontSize The text's own size, which says how far apart the lines of
15
+ * PROSE stand — a display's sub-lines stand closer.
16
+ * @returns One entry per matrix found, in page order.
17
+ */
18
+ function matrixBlocks(runs, fontSize) {
19
+ const out = [];
20
+ for (const cluster of clusters(baselines(runs, fontSize), fontSize)) {
21
+ const found = matrixFrom(cluster);
22
+ if (found) out.push(found);
23
+ }
24
+ return out;
25
+ }
26
+ /** The baseline a row of runs stands on. */
27
+ var baselineOf = (row) => Math.max(...row.map((r) => r.y));
28
+ /**
29
+ * The runs grouped by the baseline they were DRAWN on, top to bottom.
30
+ *
31
+ * Tighter than the reading's own rows, which allow a line half an em of drift
32
+ * so that a footnote mark stays with its word: the sub-lines of a display stand
33
+ * six points apart in a ten-point face, and grouped that loosely
34
+ * bug1997343.pdf's brackets joined the row of numbers under them.
35
+ */
36
+ function baselines(runs, fontSize) {
37
+ const tol = Math.max(1, fontSize * SAME_BASELINE);
38
+ const out = [];
39
+ for (const run of [...runs].sort((a, b) => b.y - a.y || a.x - b.x)) {
40
+ const last = out[out.length - 1];
41
+ if (last && Math.abs(baselineOf(last) - run.y) <= tol) last.push(run);
42
+ else out.push([run]);
43
+ }
44
+ return out;
45
+ }
46
+ /** How far off a baseline, in ems, a run may sit and still be ON it. */
47
+ var SAME_BASELINE = .15;
48
+ /**
49
+ * The runs of a page grouped into the SUB-LINES of one display: rows standing
50
+ * closer together than a line of prose does, three or more of them (two rows
51
+ * and the brackets between).
52
+ */
53
+ function clusters(rows, fontSize) {
54
+ const out = [];
55
+ let run = [];
56
+ for (const row of rows) {
57
+ const last = run[run.length - 1];
58
+ if (last && baselineOf(last) - baselineOf(row) >= fontSize * SUBLINE_GAP) {
59
+ if (run.length >= MIN_SUBLINES) out.push(run);
60
+ run = [];
61
+ }
62
+ run.push(row);
63
+ }
64
+ if (run.length >= MIN_SUBLINES) out.push(run);
65
+ return out;
66
+ }
67
+ /** How far apart, in ems, two baselines have to stand to be separate LINES. */
68
+ var SUBLINE_GAP = .9;
69
+ /** A matrix is a row of cells above another, and the brackets between them. */
70
+ var MIN_SUBLINES = 3;
71
+ /** Build the matrix a cluster of sub-lines holds, or nothing where it holds none. */
72
+ function matrixFrom(cluster) {
73
+ const middle = cluster.find((row) => pairsOf(row).length > 0);
74
+ if (!middle) return void 0;
75
+ const pairs = pairsOf(middle);
76
+ const cells = cluster.filter((row) => row !== middle);
77
+ if (cells.length === 0) return void 0;
78
+ const children = [];
79
+ let used = /* @__PURE__ */ new Set();
80
+ let at = -Infinity;
81
+ for (const pair of pairs) {
82
+ for (const run of middle) if (run.x >= at && run.endX <= pair.open.x && run.text.trim().length > 0) {
83
+ children.push({
84
+ type: "run",
85
+ text: spaced(run.text)
86
+ });
87
+ used.add(run);
88
+ }
89
+ const inside = matrixInside(cluster, middle, pair);
90
+ if (!inside) return void 0;
91
+ children.push({
92
+ type: "delimiter",
93
+ begChr: pair.open.text.trim(),
94
+ endChr: pair.close.text.trim(),
95
+ children: [inside.matrix]
96
+ });
97
+ used = new Set([
98
+ ...used,
99
+ ...inside.used,
100
+ pair.open,
101
+ pair.close
102
+ ]);
103
+ at = pair.close.endX;
104
+ }
105
+ for (const run of middle) if (run.x >= at && run.text.trim().length > 0) {
106
+ children.push({
107
+ type: "run",
108
+ text: spaced(run.text)
109
+ });
110
+ used.add(run);
111
+ }
112
+ if (children.length === 0) return void 0;
113
+ for (const row of cluster) for (const run of row) if (!used.has(run)) return void 0;
114
+ const all = [...used];
115
+ return {
116
+ math: {
117
+ type: "row",
118
+ children
119
+ },
120
+ top: baselineOf(cells[0] ?? middle),
121
+ x: Math.min(...all.map((r) => r.x)),
122
+ width: Math.max(...all.map((r) => r.endX)) - Math.min(...all.map((r) => r.x)),
123
+ used
124
+ };
125
+ }
126
+ /**
127
+ * An operator standing between two matrices, with the air the page gave it.
128
+ * The file sets `(1 2 / 3 4)(1 1 / 0 1) = (1 3 / 3 7)` with the equals sign
129
+ * clear of both brackets; set tight against them it reads as one word.
130
+ */
131
+ var spaced = (text) => ` ${text.trim()} `;
132
+ /** The bracket pairs a row holds, left to right and never nested. */
133
+ function pairsOf(row) {
134
+ const out = [];
135
+ let open;
136
+ for (const run of [...row].sort((a, b) => a.x - b.x)) {
137
+ const text = run.text.trim();
138
+ if (open === void 0) {
139
+ if (BRACKETS.has(text)) open = run;
140
+ continue;
141
+ }
142
+ if (text === BRACKETS.get(open.text.trim())) {
143
+ out.push({
144
+ open,
145
+ close: run
146
+ });
147
+ open = void 0;
148
+ }
149
+ }
150
+ return out;
151
+ }
152
+ /**
153
+ * The grid inside one bracket pair: every sub-line's runs that fall between the
154
+ * brackets, clustered into columns by where they stand.
155
+ */
156
+ function matrixInside(cluster, middle, pair) {
157
+ const used = /* @__PURE__ */ new Set();
158
+ const rows = [];
159
+ for (const row of cluster) {
160
+ const inside = row.filter((run) => run !== pair.open && run !== pair.close && run.x >= pair.open.x && run.endX <= pair.close.endX && run.text.trim().length > 0);
161
+ if (inside.length === 0) continue;
162
+ if (row === middle && inside.some((run) => BRACKETS.has(run.text.trim()))) return void 0;
163
+ inside.sort((a, b) => a.x - b.x);
164
+ for (const run of inside) used.add(run);
165
+ rows.push(inside);
166
+ }
167
+ if (rows.length < 2) return void 0;
168
+ const size = Math.max(...rows.flat().map((r) => r.fontSizePt || 10));
169
+ const centres = [];
170
+ const columnOf = (run) => {
171
+ const centre = (run.x + run.endX) / 2;
172
+ const found = centres.findIndex((c) => Math.abs(c - centre) <= size / 2);
173
+ if (found >= 0) return found;
174
+ centres.push(centre);
175
+ return centres.length - 1;
176
+ };
177
+ const grid = rows.map((row) => {
178
+ const cells = [];
179
+ for (const run of row) {
180
+ const at = columnOf(run);
181
+ (cells[at] ??= []).push(run.text.trim());
182
+ }
183
+ return cells;
184
+ });
185
+ const width = Math.max(...grid.map((row) => row.length));
186
+ return {
187
+ matrix: {
188
+ type: "matrix",
189
+ rows: grid.map((row) => Array.from({ length: width }, (_, i) => ({
190
+ type: "run",
191
+ text: (row[i] ?? []).join("")
192
+ })))
193
+ },
194
+ used
195
+ };
196
+ }
197
+ //#endregion
198
+ export { matrixBlocks };
@@ -0,0 +1,21 @@
1
+ /** How a named CMap breaks a string into codes, and what those codes mean. */
2
+ export interface PredefinedCMap {
3
+ /** The `TextDecoder` label the code bytes are in. */
4
+ readonly charset: string;
5
+ /** Whether a byte begins a TWO-byte code; anything else stands alone. */
6
+ readonly leadsPair: (byte: number) => boolean;
7
+ /** §9.7.5.2 — a `…-V` CMap sets its text down the page. */
8
+ readonly vertical: boolean;
9
+ }
10
+ /**
11
+ * What a predefined CMap name comes to.
12
+ *
13
+ * @param name The `/Encoding` name, as the font states it.
14
+ * @returns Its encoding and code structure, or `undefined` for `Identity` and
15
+ * for any name whose encoding this cannot decode.
16
+ */
17
+ export declare function predefinedCMap(name: string): PredefinedCMap | undefined;
18
+ /** Split a shown string the way this CMap's codespace says (§9.7.6.2). */
19
+ export declare function splitPredefined(cmap: PredefinedCMap, bytes: Uint8Array): Array<number>;
20
+ /** The character one code stands for: its own bytes, read in the CMap's encoding. */
21
+ export declare function decodePredefined(cmap: PredefinedCMap, code: number): string;
@@ -0,0 +1,102 @@
1
+ //#region src/pdf-reader/predefined-cmap.ts
2
+ /** Shift-JIS: 81–9F and E0–FC lead a pair; 00–80 and A0–DF stand alone. */
3
+ var shiftJis = (b) => b >= 129 && b <= 159 || b >= 224 && b <= 252;
4
+ /** The EUC family: A1–FE lead a pair, and 8E/8F introduce one as well. */
5
+ var euc = (b) => b >= 161 || b === 142 || b === 143;
6
+ /** Big5, GBK and UHC: everything from 81 up leads a pair. */
7
+ var highLeads = (b) => b >= 129;
8
+ /** Two bytes always, big-endian — the UCS-2 and UTF-16 CMaps. */
9
+ var always = () => true;
10
+ /** The families, longest name first so `90ms-RKSJ` is not read as `RKSJ`. */
11
+ var FAMILIES = [
12
+ {
13
+ match: /RKSJ/u,
14
+ charset: "shift_jis",
15
+ leadsPair: shiftJis
16
+ },
17
+ {
18
+ match: /-EUC(-|$)|^EUC-/u,
19
+ charset: "euc-jp",
20
+ leadsPair: euc
21
+ },
22
+ {
23
+ match: /^(ETen|ETenms|B5pc|HKscs-B5|CNS-EUC)/u,
24
+ charset: "big5",
25
+ leadsPair: highLeads
26
+ },
27
+ {
28
+ match: /^(GBK|GBpc|GBK2K|GB-EUC|GBKp)/u,
29
+ charset: "gbk",
30
+ leadsPair: highLeads
31
+ },
32
+ {
33
+ match: /^(KSC|KSCms|KSCpc)/u,
34
+ charset: "euc-kr",
35
+ leadsPair: highLeads
36
+ },
37
+ {
38
+ match: /UCS2|UTF16/u,
39
+ charset: "utf-16be",
40
+ leadsPair: always
41
+ }
42
+ ];
43
+ /**
44
+ * What a predefined CMap name comes to.
45
+ *
46
+ * @param name The `/Encoding` name, as the font states it.
47
+ * @returns Its encoding and code structure, or `undefined` for `Identity` and
48
+ * for any name whose encoding this cannot decode.
49
+ */
50
+ function predefinedCMap(name) {
51
+ if (name.startsWith("Identity")) return void 0;
52
+ const vertical = name.endsWith("-V");
53
+ const family = FAMILIES.find((f) => f.match.test(name));
54
+ if (!family) return void 0;
55
+ if (!canDecode(family.charset)) return void 0;
56
+ return {
57
+ charset: family.charset,
58
+ leadsPair: family.leadsPair,
59
+ vertical
60
+ };
61
+ }
62
+ /** Split a shown string the way this CMap's codespace says (§9.7.6.2). */
63
+ function splitPredefined(cmap, bytes) {
64
+ const out = [];
65
+ for (let i = 0; i < bytes.length;) {
66
+ const b = bytes[i];
67
+ if (cmap.leadsPair(b) && i + 1 < bytes.length) {
68
+ out.push(b << 8 | bytes[i + 1]);
69
+ i += 2;
70
+ } else {
71
+ out.push(b);
72
+ i++;
73
+ }
74
+ }
75
+ return out;
76
+ }
77
+ /** The character one code stands for: its own bytes, read in the CMap's encoding. */
78
+ function decodePredefined(cmap, code) {
79
+ const bytes = code > 255 ? Uint8Array.from([code >> 8, code & 255]) : Uint8Array.from([code]);
80
+ try {
81
+ return new TextDecoder(cmap.charset, { fatal: false }).decode(bytes);
82
+ } catch {
83
+ return "";
84
+ }
85
+ }
86
+ /** Whether this runtime's `TextDecoder` carries the legacy encoding. */
87
+ function canDecode(charset) {
88
+ const had = supported.get(charset);
89
+ if (had !== void 0) return had;
90
+ let ok = false;
91
+ try {
92
+ new TextDecoder(charset);
93
+ ok = true;
94
+ } catch {
95
+ ok = false;
96
+ }
97
+ supported.set(charset, ok);
98
+ return ok;
99
+ }
100
+ var supported = /* @__PURE__ */ new Map();
101
+ //#endregion
102
+ export { decodePredefined, predefinedCMap, splitPredefined };
@@ -13,17 +13,14 @@ import { FlowDoc } from '../core/ir/flow.js';
13
13
  * opens permissions-only encryption.
14
14
  * @param filters Decoders for `/Filter` names this reader does not implement
15
15
  * (§7.4); see {@link StreamFilters}.
16
- * @param layout `'auto'` (the default) lets the FILE decide: a page that is
17
- * mostly marks is reproduced, one that is mostly lines is re-set,
18
- * and the reader records which it chose. `'flow'` reads a
19
- * re-flowable document out of the page —
20
- * paragraphs and tables in reading order, from the structure
21
- * tree where there is one. `'positional'` keeps the page: every
22
- * line stands where its glyphs do, beside the artwork, which is
23
- * what a form or a drawing needs and what a paragraph cannot be.
24
16
  * @returns The reconstructed FlowDoc and its accumulated {@link Loss} report.
17
+ *
18
+ * Which of the two readings a file gets is the FILE's to decide and no
19
+ * caller's: a page that is mostly marks is reproduced where it stands, one
20
+ * that is mostly lines is re-set as a document, and the reader records which
21
+ * it chose. There is no override — see the note on {@link readingOf}.
25
22
  */
26
- export declare function readPdf(bytes: Uint8Array, password?: string, layout?: 'flow' | 'positional' | 'auto', filters?: StreamFilters): ReadResult<FlowDoc>;
23
+ export declare function readPdf(bytes: Uint8Array, password?: string, filters?: StreamFilters): ReadResult<FlowDoc>;
27
24
  /**
28
25
  * The `pdfReader` adapter: a {@link DocumentReader} that sniffs the `%PDF-`
29
26
  * header and parses the bytes into a {@link FlowDoc} (E-PDF EP5).
@@ -40,17 +40,14 @@ function endsLikePdf(bytes) {
40
40
  * opens permissions-only encryption.
41
41
  * @param filters Decoders for `/Filter` names this reader does not implement
42
42
  * (§7.4); see {@link StreamFilters}.
43
- * @param layout `'auto'` (the default) lets the FILE decide: a page that is
44
- * mostly marks is reproduced, one that is mostly lines is re-set,
45
- * and the reader records which it chose. `'flow'` reads a
46
- * re-flowable document out of the page —
47
- * paragraphs and tables in reading order, from the structure
48
- * tree where there is one. `'positional'` keeps the page: every
49
- * line stands where its glyphs do, beside the artwork, which is
50
- * what a form or a drawing needs and what a paragraph cannot be.
51
43
  * @returns The reconstructed FlowDoc and its accumulated {@link Loss} report.
44
+ *
45
+ * Which of the two readings a file gets is the FILE's to decide and no
46
+ * caller's: a page that is mostly marks is reproduced where it stands, one
47
+ * that is mostly lines is re-set as a document, and the reader records which
48
+ * it chose. There is no override — see the note on {@link readingOf}.
52
49
  */
53
- function readPdf(bytes, password = "", layout = "auto", filters = {}) {
50
+ function readPdf(bytes, password = "", filters = {}) {
54
51
  const file = PdfFile.parse(bytes, password, filters);
55
52
  const losses = [];
56
53
  if (file.encryptionUnsupported) losses.push({
@@ -63,11 +60,11 @@ function readPdf(bytes, password = "", layout = "auto", filters = {}) {
63
60
  feature: FEATURES.text,
64
61
  detail: `PDF stream filter /${name} is not supported; streams using it are unreadable`
65
62
  });
66
- const reading = layout === "auto" ? readingOf(file) : layout;
67
- if (layout === "auto") losses.push({
63
+ const reading = readingOf(file);
64
+ losses.push({
68
65
  severity: "degraded",
69
66
  feature: FEATURES.text,
70
- detail: reading === "positional" ? "PDF read as a PAGE (placed): its artwork outweighs its prose, so every line stands where its glyphs stand — pass pdfLayout: \"flow\" to re-set it as a document instead" : "PDF read as a DOCUMENT (flowing): its prose outweighs its artwork, so the words re-set and the artwork takes its turn in reading order — pass pdfLayout: \"positional\" to reproduce the page instead"
67
+ detail: reading === "positional" ? "PDF read as a PAGE (placed): its artwork outweighs its prose, so every line stands where its glyphs stand" : "PDF read as a DOCUMENT (flowing): its prose outweighs its artwork, so the words re-set and the artwork takes its turn in reading order"
71
68
  });
72
69
  const tagged = reading === "positional" ? void 0 : reconstructTaggedPdf(file);
73
70
  const reconstruction = tagged ?? reconstructByLayout(file, reading);
@@ -77,11 +74,6 @@ function readPdf(bytes, password = "", layout = "auto", filters = {}) {
77
74
  detail: "untagged PDF — text and headings reconstructed heuristically from glyph positions; structure is approximate"
78
75
  });
79
76
  losses.push(...reconstruction.losses);
80
- losses.push({
81
- severity: "dropped",
82
- feature: FEATURES.images,
83
- detail: "PDF bare-shading (sh) vector regions are not reconstructed"
84
- });
85
77
  return {
86
78
  doc: reconstruction.doc,
87
79
  losses
@@ -105,22 +97,43 @@ function readPdf(bytes, password = "", layout = "auto", filters = {}) {
105
97
  * records which reading it took and why, and the caller can name the other.
106
98
  */
107
99
  function readingOf(file) {
108
- /** Below this many marks a page is prose with decoration, whatever the ratio. */
109
- const ENOUGH_MARKS = 20;
100
+ /**
101
+ * Below this many marks a page is prose with decoration, whatever the ratio.
102
+ *
103
+ * It stood at twenty, and calgray.pdf is a five-by-four grid of grey swatches
104
+ * with a label in each: twenty boxes, of which the one painted white is not a
105
+ * mark anybody can see. Nineteen — one short — and all three pages of it were
106
+ * read as prose, the labels of each row run together into a line and the
107
+ * sheet spilling onto a second page. Nineteen boxes in a grid are not a page
108
+ * of prose with a rule under its heading.
109
+ */
110
+ const ENOUGH_MARKS = 12;
110
111
  /** Twice as many marks as lines is a page that is drawn rather than written. */
111
112
  const DRAWN = 2;
113
+ /** Below this many runs, an angle is a stamp or a watermark and not the page. */
114
+ const ENOUGH_TURNED = 8;
112
115
  const ratios = [];
113
116
  for (const page of file.pages()) {
114
117
  let marks = 0;
115
118
  let lines = 0;
119
+ let turned = 0;
120
+ let runs = 0;
116
121
  try {
117
122
  marks = collectPageVectors(file, page, []).vectors.length;
118
123
  const ys = /* @__PURE__ */ new Set();
119
- for (const run of extractPageText(file, page)) ys.add(Math.round(run.y));
124
+ for (const run of extractPageText(file, page)) {
125
+ ys.add(Math.round(run.y));
126
+ runs++;
127
+ if (run.angleDeg !== void 0) turned++;
128
+ }
120
129
  lines = ys.size;
121
130
  } catch {
122
131
  continue;
123
132
  }
133
+ if (runs >= ENOUGH_TURNED && turned > runs * .5) {
134
+ ratios.push(DRAWN);
135
+ continue;
136
+ }
124
137
  if (marks < ENOUGH_MARKS) {
125
138
  ratios.push(0);
126
139
  continue;
@@ -145,7 +158,7 @@ var pdfReader = {
145
158
  FEATURES.images
146
159
  ]),
147
160
  sniff: sniffPdf,
148
- read: (bytes, opts) => readPdf(bytes, typeof opts?.password === "string" ? opts.password : "", opts?.pdfLayout === "positional" ? "positional" : opts?.pdfLayout === "flow" ? "flow" : "auto", isFilters(opts?.filters) ? opts.filters : {})
161
+ read: (bytes, opts) => readPdf(bytes, typeof opts?.password === "string" ? opts.password : "", isFilters(opts?.filters) ? opts.filters : {})
149
162
  };
150
163
  /** A caller's `filters` option, when it is the shape the reader can use. */
151
164
  function isFilters(value) {
@@ -1,4 +1,5 @@
1
1
  import { CieSpace } from './cie-color.js';
2
+ import { IccTransform } from './icc.js';
2
3
  import { PdfFunction } from './function.js';
3
4
  import { ShapeGradient } from '../core/vector.js';
4
5
  import { PdfDict } from '../pdf/objects.js';
@@ -17,6 +18,70 @@ import { PdfFile } from './document.js';
17
18
  * @returns A map from pattern resource name to its {@link ShapeGradient}.
18
19
  */
19
20
  export declare function buildShadingMap(file: PdfFile, resources: PdfDict | undefined): Map<string, ShapeGradient>;
21
+ /**
22
+ * §8.7.4.5.2 — an axial or radial shading as a gradient, for a bare `sh`.
23
+ *
24
+ * A `sh` paints the CLIP rather than a path, so what it needs is not a fill for
25
+ * a shape the page drew but the gradient itself, to fill the region with. It is
26
+ * the same reading `buildShadingMap` does for a pattern.
27
+ *
28
+ * @param file The owning file.
29
+ * @param sh The shading dictionary.
30
+ * @returns The gradient, or `undefined` for a type this does not read.
31
+ */
32
+ export declare function gradientShading(file: PdfFile, sh: PdfDict): ShapeGradient | undefined;
33
+ /**
34
+ * §8.7.4.5 — which kind of shading this is, as the file states it.
35
+ *
36
+ * @param file The owning file.
37
+ * @param sh The shading dictionary.
38
+ * @returns Its `/ShadingType`, or 0 where the file states none.
39
+ */
40
+ export declare function shadingTypeOf(file: PdfFile, sh: PdfDict): number;
41
+ /**
42
+ * §8.7.4.5.3 — a FUNCTION-BASED shading, sampled into a picture.
43
+ *
44
+ * Type 1 is not a ramp between two points: it is a function of two variables
45
+ * over a rectangle, and no gradient can stand for one. Painted by a bare `sh`
46
+ * it fills the clip, and nothing here lifted it at all —
47
+ * function_based_shading.pdf is nine such squares and 43% of the page's ink,
48
+ * and reconstructed to a blank sheet.
49
+ *
50
+ * Sampled it is exactly a picture, which every format downstream can show. The
51
+ * grid is fixed: the function is smooth by construction (§8.7.4.5.3 gives it a
52
+ * `/Domain` and nothing else), so more samples buy nothing a reader can see.
53
+ *
54
+ * @param file The owning file.
55
+ * @param shading The shading dictionary.
56
+ * @returns The picture and the domain it covers, or `undefined` for a shading
57
+ * of another type or one whose function cannot be run.
58
+ */
59
+ export declare function sampledShading(file: PdfFile, shading: PdfDict): {
60
+ rgb: Uint8Array;
61
+ size: number;
62
+ domain: [number, number, number, number];
63
+ } | undefined;
64
+ /**
65
+ * §8.6.8 — the colour a run of `sc` / `scn` components comes to.
66
+ *
67
+ * The space in force decides, and where it was not read the COUNT is the next
68
+ * best witness: three numbers are RGB and four are CMYK on every device space
69
+ * there is. One number is the ambiguous case — grey in a device space, but the
70
+ * strength of a colorant in a Separation, where 1 is the ink at full and reads
71
+ * dark — so a lone component is only taken where the space said what it means.
72
+ *
73
+ * @param nums The components, as the page or a shading's function states them.
74
+ * @param space The space in force, where it was read.
75
+ * @returns The colour as 6 hex digits, or `undefined` where the numbers do not
76
+ * say what colour they are.
77
+ */
78
+ export declare function spaceColor(nums: ReadonlyArray<number>, space: ColorSpaceInfo | undefined): string | undefined;
79
+ /** Three channels, each 0..1, as 6 upper-case hex digits. */
80
+ export declare function rgbHex(r: number, g: number, b: number): string;
81
+ /** One grey level, 0..1, as 6 upper-case hex digits. */
82
+ export declare function grayHex(v: number): string;
83
+ /** §8.6.4.4 — four inks, each 0..1, as 6 upper-case hex digits. */
84
+ export declare function cmykHex(c: number, m: number, y: number, k: number): string;
20
85
  /**
21
86
  * §11.6.4.4 — the constant fill alpha (`/ca`) of every `/ExtGState` the page
22
87
  * names, by name.
@@ -39,10 +104,17 @@ export declare function buildShadingMap(file: PdfFile, resources: PdfDict | unde
39
104
  * state one.
40
105
  */
41
106
  export declare function buildAlphaMap(file: PdfFile, resources: PdfDict | undefined): Map<string, GsPaint>;
42
- /** What a `/ExtGState` says about paint that the reconstruction can carry. */
107
+ /**
108
+ * What a `/ExtGState` says about paint that the reconstruction can carry.
109
+ *
110
+ * Every field is absent when the state does not NAME that parameter, because a
111
+ * `gs` leaves what it does not name alone (§8.4.5).
112
+ */
43
113
  export interface GsPaint {
44
- /** §11.6.4.4 `/ca` — the constant fill alpha, when it is below 1. */
114
+ /** §11.6.4.4 `/ca` — the constant fill alpha, where the state names one. */
45
115
  readonly alpha?: number;
116
+ /** §11.3.5 — whether `/BM` is named at all, since naming `/Normal` ends a blend. */
117
+ readonly statesBlend?: boolean;
46
118
  /** §11.3.5 `/BM` — the paint only darkens, so what it covers shows through. */
47
119
  readonly darkens?: boolean;
48
120
  /**
@@ -53,6 +125,7 @@ export interface GsPaint {
53
125
  /**
54
126
  * §11.6.5 `/SMask` — the paint's opacity varies from place to place, out of
55
127
  * another group's luminosity or alpha. Nothing downstream has a mask like it.
128
+ * `false` where the state names `/None`, which takes a mask off.
56
129
  */
57
130
  readonly masked?: boolean;
58
131
  }
@@ -72,6 +145,12 @@ export interface ColorSpaceInfo {
72
145
  * out of this transform.
73
146
  */
74
147
  readonly cie?: CieSpace;
148
+ /**
149
+ * §8.6.5.5 — the transform an `/ICCBased` profile states, where its form is
150
+ * one this reads. Its numbers mean what the PROFILE makes of them, which for
151
+ * anything but an sRGB-like profile is not what a device space would.
152
+ */
153
+ readonly icc?: IccTransform;
75
154
  /**
76
155
  * §8.6.6.4/§8.6.6.5 — for a `Separation` or `DeviceN`, the way OUT of it: the
77
156
  * tint transform and the space its numbers land in. Absent where the file