reamkit 1.25.1 → 1.27.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 (52) hide show
  1. package/README.md +12 -5
  2. package/dist/esm/pdf-reader/annot-draw.d.ts +65 -0
  3. package/dist/esm/pdf-reader/annot-draw.js +374 -0
  4. package/dist/esm/pdf-reader/annots.d.ts +3 -1
  5. package/dist/esm/pdf-reader/annots.js +18 -4
  6. package/dist/esm/pdf-reader/ccitt.d.ts +18 -0
  7. package/dist/esm/pdf-reader/ccitt.js +70 -2
  8. package/dist/esm/pdf-reader/cie-color.d.ts +33 -0
  9. package/dist/esm/pdf-reader/cie-color.js +112 -0
  10. package/dist/esm/pdf-reader/content.d.ts +47 -17
  11. package/dist/esm/pdf-reader/content.js +175 -14
  12. package/dist/esm/pdf-reader/display.d.ts +1 -1
  13. package/dist/esm/pdf-reader/display.js +21 -6
  14. package/dist/esm/pdf-reader/document.d.ts +6 -0
  15. package/dist/esm/pdf-reader/document.js +29 -1
  16. package/dist/esm/pdf-reader/embedded-fonts.d.ts +12 -3
  17. package/dist/esm/pdf-reader/embedded-fonts.js +22 -3
  18. package/dist/esm/pdf-reader/flow-build.d.ts +12 -4
  19. package/dist/esm/pdf-reader/flow-build.js +102 -8
  20. package/dist/esm/pdf-reader/font.js +97 -16
  21. package/dist/esm/pdf-reader/function.d.ts +16 -0
  22. package/dist/esm/pdf-reader/function.js +414 -0
  23. package/dist/esm/pdf-reader/glyph-names.d.ts +6 -0
  24. package/dist/esm/pdf-reader/glyph-names.js +408 -0
  25. package/dist/esm/pdf-reader/image-decode.d.ts +8 -4
  26. package/dist/esm/pdf-reader/image-decode.js +224 -12
  27. package/dist/esm/pdf-reader/images.d.ts +12 -0
  28. package/dist/esm/pdf-reader/images.js +109 -9
  29. package/dist/esm/pdf-reader/jbig2.d.ts +109 -0
  30. package/dist/esm/pdf-reader/jbig2.js +2606 -0
  31. package/dist/esm/pdf-reader/layout.d.ts +32 -0
  32. package/dist/esm/pdf-reader/layout.js +189 -40
  33. package/dist/esm/pdf-reader/lexer.d.ts +2 -0
  34. package/dist/esm/pdf-reader/lexer.js +4 -0
  35. package/dist/esm/pdf-reader/optional-content.d.ts +36 -0
  36. package/dist/esm/pdf-reader/optional-content.js +93 -0
  37. package/dist/esm/pdf-reader/reader.d.ts +5 -2
  38. package/dist/esm/pdf-reader/reader.js +80 -7
  39. package/dist/esm/pdf-reader/shading.d.ts +71 -6
  40. package/dist/esm/pdf-reader/shading.js +185 -15
  41. package/dist/esm/pdf-reader/standard-metrics.d.ts +8 -0
  42. package/dist/esm/pdf-reader/standard-metrics.js +18 -0
  43. package/dist/esm/pdf-reader/standard-widths.d.ts +20 -0
  44. package/dist/esm/pdf-reader/standard-widths.js +62 -0
  45. package/dist/esm/pdf-reader/tagged.js +204 -32
  46. package/dist/esm/pdf-reader/text-rules.d.ts +16 -0
  47. package/dist/esm/pdf-reader/text-rules.js +112 -0
  48. package/dist/esm/pdf-reader/text.js +78 -4
  49. package/dist/esm/pdf-reader/vector.d.ts +5 -0
  50. package/dist/esm/pdf-reader/vector.js +39 -25
  51. package/dist/esm/word/docx-writer.js +213 -23
  52. package/package.json +1 -1
package/README.md CHANGED
@@ -191,12 +191,19 @@ preview, flowed HTML export, and **docx + xlsx output** (write WordprocessingML
191
191
  tagged PDF from its structure tree (headings, tables, lists, reading order), an
192
192
  untagged one heuristically from glyph positions (lines, paragraphs, headings,
193
193
  and a clean two-column split). It lifts back the text (via each font's
194
- `/ToUnicode`, or the embedded program's own `cmap` where there is none), the
194
+ `/ToUnicode`, or the embedded program's own `cmap` where there is none, or the
195
+ glyph names its `/Encoding` states — which is all a PDF from TeX gives), the
195
196
  font programs themselves, raster images (JPEG verbatim; PNG/Flate/LZW/CCITT-fax
196
- decoded and re-encoded), `/Link` hyperlinks, form-XObject content, annotation
197
- appearances, and the page's artwork: filled / stroked / gradient shapes,
198
- clipping paths, tiling patterns, constant alpha, and the Type 3 glyphs that are
199
- drawings rather than letters. It reads modern compressed files (cross-reference
197
+ and **JBIG2** decoded and re-encoded), `/Link` hyperlinks, form-XObject content,
198
+ annotation appearances (drawing one itself where the file supplies none),
199
+ colour set through a named space — device, CIE (`CalGray`, `Lab`) or a
200
+ `Separation`/`DeviceN` run through its own tint transform — and the page's
201
+ artwork: filled / stroked / gradient shapes, clipping paths, tiling patterns,
202
+ stencil image masks, constant alpha, and the Type 3 glyphs that are drawings
203
+ rather than letters. It honours the layers a file turns off (§8.11 optional
204
+ content) and the box it says to show (`/CropBox`), and it decides for itself
205
+ whether a file is a document to re-flow or a page to keep — a paper is mostly
206
+ lines, a form is mostly marks — which `pdfLayout` overrides. It reads modern compressed files (cross-reference
200
207
  + object streams) and encrypted ones (RC4 / AES — the user password is passed to
201
208
  `Ream.parse(bytes, { password })`, defaulting to the permissions-only case); a
202
209
  filter it does not carry can be handed to it through
@@ -0,0 +1,65 @@
1
+ import { PdfDict, PdfStream } from '../pdf/objects.js';
2
+ import { PdfFile } from './document.js';
3
+ /**
4
+ * A generated normal appearance for an annotation that carries none, or
5
+ * `undefined` when its subtype or its geometry says nothing to draw.
6
+ *
7
+ * The stream is in PAGE space, so the caller places it with the identity: it is
8
+ * not fitted to `/Rect` the way an authored appearance is, because what it is
9
+ * drawn from is already stated in page coordinates.
10
+ *
11
+ * @param file The owning file, for resolving references.
12
+ * @param annot The annotation dictionary.
13
+ * @returns The synthesized appearance stream, or `undefined`.
14
+ */
15
+ export declare function drawnAppearance(file: PdfFile, annot: PdfDict): PdfStream | undefined;
16
+ /**
17
+ * §12.7.2 `/DR` — the resources a generated field appearance draws with.
18
+ *
19
+ * The `/DA` string names a font (`/Helv 12 Tf`) and that name means nothing
20
+ * without the form's own default resource dictionary, which is where every
21
+ * field's face lives.
22
+ *
23
+ * @param file The owning file.
24
+ * @param annot The annotation the appearance was generated for.
25
+ * @returns The form's `/DR`, or `undefined` when there is none.
26
+ */
27
+ export declare function drawnResources(file: PdfFile, annot: PdfDict): PdfDict | undefined;
28
+ /**
29
+ * §12.5.6.10 — what a TEXT-MARKUP annotation says about the words it covers.
30
+ *
31
+ * A highlight, an underline, a strikeout and a squiggle are not drawings: each
32
+ * names a run of text and a way of marking it. Lifted as artwork they are a
33
+ * band and a rule anchored to the page, which is right until the text re-sets
34
+ * and is then a band sitting between two paragraphs it does not mark. Carried
35
+ * on the RUNS they survive the reflow, and a .docx gets what the annotation
36
+ * meant — `w:shd`, `w:u`, `w:strike`.
37
+ *
38
+ * @param file The owning file.
39
+ * @param annot The annotation dictionary.
40
+ * @returns How it marks and what it marks, or `undefined` for another subtype.
41
+ */
42
+ export declare function textMarkupOf(file: PdfFile, annot: PdfDict): TextMarkupAnnot | undefined;
43
+ /** A text-markup annotation: how it marks, and the boxes it marks. */
44
+ export interface TextMarkupAnnot {
45
+ readonly mark: TextMarkup;
46
+ readonly quads: ReadonlyArray<Quad>;
47
+ }
48
+ /** §12.5.6.10 — how a text-markup annotation marks the words it covers. */
49
+ export interface TextMarkup {
50
+ /** `/Highlight` — the wash painted behind them (6-hex). */
51
+ readonly highlightHex?: string;
52
+ /** `/Underline`, `/Squiggly` — a rule under them, straight or wavy. */
53
+ readonly underline?: 'single' | 'wave';
54
+ /** The rule's own colour (6-hex), when the annotation states one. */
55
+ readonly underlineHex?: string;
56
+ /** `/StrikeOut` — a rule through them. */
57
+ readonly strike?: boolean;
58
+ }
59
+ /** One marked box in page space: its span of x, its foot and its height. */
60
+ export interface Quad {
61
+ readonly x0: number;
62
+ readonly x1: number;
63
+ readonly y0: number;
64
+ readonly h: number;
65
+ }
@@ -0,0 +1,374 @@
1
+ import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
2
+ //#region src/pdf-reader/annot-draw.ts
3
+ /** The circle constant: how far a Bézier handle reaches to round a quarter. */
4
+ var KAPPA = .5523;
5
+ /** §12.5.2 `/Border` and §12.5.4 `/BS` `/W` both default the pen to one point. */
6
+ var DEFAULT_BORDER_PT = 1;
7
+ /**
8
+ * A generated normal appearance for an annotation that carries none, or
9
+ * `undefined` when its subtype or its geometry says nothing to draw.
10
+ *
11
+ * The stream is in PAGE space, so the caller places it with the identity: it is
12
+ * not fitted to `/Rect` the way an authored appearance is, because what it is
13
+ * drawn from is already stated in page coordinates.
14
+ *
15
+ * @param file The owning file, for resolving references.
16
+ * @param annot The annotation dictionary.
17
+ * @returns The synthesized appearance stream, or `undefined`.
18
+ */
19
+ function drawnAppearance(file, annot) {
20
+ const subtype = file.get(annot, "Subtype");
21
+ if (!(subtype instanceof PdfName)) return void 0;
22
+ if (subtype.value === "Widget") return typedValue(file, annot);
23
+ const pen = borderWidth(file, annot);
24
+ const drawing = pathFor(file, annot, subtype.value, pen);
25
+ if (!drawing || drawing.ops.length === 0) return void 0;
26
+ const stroke = colorOf(file.get(annot, "C"));
27
+ const fillColor = colorOf(file.get(annot, "IC"));
28
+ const body = [
29
+ "q",
30
+ `${num(pen)} w`,
31
+ ...stroke !== void 0 ? [`${stroke} RG`] : [],
32
+ ...fillColor !== void 0 ? [`${fillColor} rg`] : [],
33
+ ...drawing.ops,
34
+ drawing.paint,
35
+ "Q"
36
+ ].join("\n");
37
+ return new PdfStream(/* @__PURE__ */ new Map(), new TextEncoder().encode(body));
38
+ }
39
+ /**
40
+ * §12.7.2 `/DR` — the resources a generated field appearance draws with.
41
+ *
42
+ * The `/DA` string names a font (`/Helv 12 Tf`) and that name means nothing
43
+ * without the form's own default resource dictionary, which is where every
44
+ * field's face lives.
45
+ *
46
+ * @param file The owning file.
47
+ * @param annot The annotation the appearance was generated for.
48
+ * @returns The form's `/DR`, or `undefined` when there is none.
49
+ */
50
+ function drawnResources(file, annot) {
51
+ const subtype = file.get(annot, "Subtype");
52
+ if (!(subtype instanceof PdfName) || subtype.value !== "Widget") return void 0;
53
+ const acro = file.get(file.catalog, "AcroForm");
54
+ if (!(acro instanceof Map)) return void 0;
55
+ const dr = file.get(acro, "DR");
56
+ return dr instanceof Map ? dr : void 0;
57
+ }
58
+ /**
59
+ * §12.7.3.3 — the text a form field holds, set the way its `/DA` asks.
60
+ *
61
+ * A widget normally carries the picture of its value in `/AP`, and a viewer
62
+ * that fills a field regenerates it. One that does not — or a file flagged
63
+ * `NeedAppearances` — leaves only `/V`, and reading the appearances alone gives
64
+ * a form with every box empty. The field's own default appearance string names
65
+ * the face, the size and the colour; the rest is arithmetic on `/Rect`.
66
+ *
67
+ * Only a TEXT field: a button's value is a state, and a choice field's list is
68
+ * drawn from `/Opt` rather than typed into a box.
69
+ */
70
+ function typedValue(file, annot) {
71
+ if (inherited(file, annot, "FT") !== "Tx") return void 0;
72
+ const value = inheritedString(file, annot, "V");
73
+ if (value === void 0 || value.length === 0) return void 0;
74
+ const rect = rectangle(file.get(annot, "Rect"));
75
+ if (!rect) return void 0;
76
+ const da = defaultAppearance(file, annot);
77
+ const multiline = ((inheritedNumber(file, annot, "Ff") ?? 0) & MULTILINE) !== 0;
78
+ const box = inset(rect, borderWidth(file, annot) + 1);
79
+ const height = box[3] - box[1];
80
+ const width = box[2] - box[0];
81
+ if (!(width > 0 && height > 0)) return void 0;
82
+ const size = da.size > 0 ? da.size : multiline ? 12 : Math.min(12, height * .66);
83
+ const lines = multiline ? value.split(/\r\n|[\r\n]/u) : [value.replace(/[\r\n]+/gu, " ")];
84
+ const leading = size * 1.16;
85
+ const first = multiline ? box[3] - size : box[1] + (height - size) / 2 + size * .22;
86
+ const ops = [
87
+ "q",
88
+ "BT",
89
+ `${da.font} ${num(size)} Tf`,
90
+ `${da.color} rg`
91
+ ];
92
+ lines.forEach((line, i) => {
93
+ const y = first - i * leading;
94
+ if (y < box[1] - size) return;
95
+ ops.push(`1 0 0 1 ${num(box[0] + indent(line, size, width, quadding(file, annot)))} ${num(y)} Tm`);
96
+ ops.push(`(${escapeText(line)}) Tj`);
97
+ });
98
+ ops.push("ET", "Q");
99
+ return new PdfStream(/* @__PURE__ */ new Map(), new TextEncoder().encode(ops.join("\n")));
100
+ }
101
+ /** §12.7.4.3 — the field is a multi-line text box (bit 13 of `/Ff`). */
102
+ var MULTILINE = 4096;
103
+ /**
104
+ * §12.7.3.1 `/Q` — 0 left, 1 centred, 2 right. Where it is not left the line
105
+ * has to be measured, and the face is not in hand: half the size per character
106
+ * is what Helvetica averages, and a field's value is short enough that the
107
+ * error stays under a character.
108
+ */
109
+ function indent(line, size, width, q) {
110
+ if (q !== 1 && q !== 2) return 0;
111
+ const guess = Math.min(width, line.length * size * .5);
112
+ return q === 1 ? (width - guess) / 2 : width - guess;
113
+ }
114
+ /** §12.7.3.1 `/Q`, inherited from the field's parent. */
115
+ function quadding(file, annot) {
116
+ return inheritedNumber(file, annot, "Q") ?? 0;
117
+ }
118
+ /**
119
+ * §12.7.3.3 `/DA` — the default appearance string, from the field or the form.
120
+ * Only the `Tf` and the colour are read; the rest is a content stream fragment
121
+ * this reader does not need to run.
122
+ */
123
+ function defaultAppearance(file, annot) {
124
+ const acro = file.get(file.catalog, "AcroForm");
125
+ const own = inheritedString(file, annot, "DA");
126
+ const form = acro instanceof Map ? file.get(acro, "DA") : void 0;
127
+ const da = own ?? (typeof form === "string" ? form : "");
128
+ const tf = /\/([^\s/]+)\s+([\d.]+)\s+Tf/u.exec(da);
129
+ const rgb = /([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+rg/u.exec(da);
130
+ const gray = /(^|\s)([\d.]+)\s+g(\s|$)/u.exec(da);
131
+ const color = rgb ? `${num(Number(rgb[1]))} ${num(Number(rgb[2]))} ${num(Number(rgb[3]))}` : gray ? `${num(Number(gray[2]))} ${num(Number(gray[2]))} ${num(Number(gray[2]))}` : "0 0 0";
132
+ return {
133
+ font: `/${tf?.[1] ?? "Helv"}`,
134
+ size: Number(tf?.[2] ?? 0),
135
+ color
136
+ };
137
+ }
138
+ /** §7.3.4.2 — the characters a literal string may not carry unescaped. */
139
+ function escapeText(s) {
140
+ return s.replace(/[\\()]/gu, (c) => `\\${c}`).replace(/[\r\n]/gu, " ");
141
+ }
142
+ /** A field entry, from the widget or the field tree above it (§12.7.3.1). */
143
+ function inherited(file, annot, key) {
144
+ const found = climb(file, annot, key);
145
+ return found instanceof PdfName ? found.value : void 0;
146
+ }
147
+ /** The same, for an entry stated as a string. */
148
+ function inheritedString(file, annot, key) {
149
+ const found = climb(file, annot, key);
150
+ return typeof found === "string" ? found : void 0;
151
+ }
152
+ /** The same, for a number. */
153
+ function inheritedNumber(file, annot, key) {
154
+ const found = climb(file, annot, key);
155
+ return typeof found === "number" ? found : void 0;
156
+ }
157
+ /** Walk `/Parent` until the entry is found, or the tree ends. */
158
+ function climb(file, annot, key) {
159
+ let at = annot;
160
+ for (let depth = 0; at && depth < 32; depth++) {
161
+ const here = file.get(at, key);
162
+ if (here !== PDF_NULL) return here;
163
+ const up = file.get(at, "Parent");
164
+ at = up instanceof Map ? up : void 0;
165
+ }
166
+ }
167
+ /**
168
+ * §12.5.6.10 — what a TEXT-MARKUP annotation says about the words it covers.
169
+ *
170
+ * A highlight, an underline, a strikeout and a squiggle are not drawings: each
171
+ * names a run of text and a way of marking it. Lifted as artwork they are a
172
+ * band and a rule anchored to the page, which is right until the text re-sets
173
+ * and is then a band sitting between two paragraphs it does not mark. Carried
174
+ * on the RUNS they survive the reflow, and a .docx gets what the annotation
175
+ * meant — `w:shd`, `w:u`, `w:strike`.
176
+ *
177
+ * @param file The owning file.
178
+ * @param annot The annotation dictionary.
179
+ * @returns How it marks and what it marks, or `undefined` for another subtype.
180
+ */
181
+ function textMarkupOf(file, annot) {
182
+ const subtype = file.get(annot, "Subtype");
183
+ if (!(subtype instanceof PdfName)) return void 0;
184
+ const kind = subtype.value;
185
+ if (kind !== "Highlight" && kind !== "Underline" && kind !== "StrikeOut" && kind !== "Squiggly") return;
186
+ const quads = eachQuad(file.get(annot, "QuadPoints"));
187
+ if (quads.length === 0) return void 0;
188
+ const rgb = colorOf(file.get(annot, "C"));
189
+ const opacity = file.get(annot, "CA");
190
+ const hex = rgb === void 0 ? void 0 : hexOf(rgb, typeof opacity === "number" && opacity > 0 && opacity < 1 ? opacity : 1);
191
+ return {
192
+ mark: kind === "Highlight" ? { highlightHex: hex ?? "FFFF00" } : kind === "StrikeOut" ? { strike: true } : {
193
+ underline: kind === "Squiggly" ? "wave" : "single",
194
+ ...hex !== void 0 ? { underlineHex: hex } : {}
195
+ },
196
+ quads
197
+ };
198
+ }
199
+ /** The `rg` operands as a 6-hex colour, mixed with white by `alpha`. */
200
+ function hexOf(operands, alpha = 1) {
201
+ const n = operands.split(" ").map(Number);
202
+ if (n.length !== 3 || n.some((v) => !Number.isFinite(v))) return void 0;
203
+ return n.map((v) => {
204
+ const over = Math.min(1, Math.max(0, v)) * alpha + (1 - alpha);
205
+ return Math.round(over * 255).toString(16).padStart(2, "0");
206
+ }).join("").toUpperCase();
207
+ }
208
+ /** What each markup subtype draws, from the geometry it states. */
209
+ function pathFor(file, annot, subtype, pen) {
210
+ switch (subtype) {
211
+ case "Ink": return {
212
+ ops: polylines(listOfLists(file.get(annot, "InkList")), false),
213
+ paint: "S"
214
+ };
215
+ case "Line": return {
216
+ ops: polylines([numbers(file.get(annot, "L"))], false),
217
+ paint: "S"
218
+ };
219
+ case "Polygon":
220
+ case "PolyLine": return {
221
+ ops: polylines([numbers(file.get(annot, "Vertices"))], subtype === "Polygon"),
222
+ paint: filledOrStroked(file, annot)
223
+ };
224
+ case "Square":
225
+ case "Circle": {
226
+ const rect = rectangle(file.get(annot, "Rect"));
227
+ if (!rect) return void 0;
228
+ return {
229
+ ops: boxOps(inset(rect, pen / 2), subtype === "Circle"),
230
+ paint: filledOrStroked(file, annot)
231
+ };
232
+ }
233
+ default: return;
234
+ }
235
+ }
236
+ /** `/IC` fills a square, a circle or a polygon; `/C` strokes its border. */
237
+ function filledOrStroked(file, annot) {
238
+ if (colorOf(file.get(annot, "IC")) === void 0) return "S";
239
+ return colorOf(file.get(annot, "C")) !== void 0 ? "B" : "f";
240
+ }
241
+ /** Each run of x y pairs as one subpath, closed or open. */
242
+ function polylines(runs, close) {
243
+ const ops = [];
244
+ for (const pts of runs) {
245
+ if (pts.length < 4) continue;
246
+ ops.push(`${num(pts[0])} ${num(pts[1])} m`);
247
+ for (let i = 2; i + 1 < pts.length; i += 2) ops.push(`${num(pts[i])} ${num(pts[i + 1])} l`);
248
+ if (close) ops.push("h");
249
+ }
250
+ return ops;
251
+ }
252
+ /** §12.5.6.8 — a square is its rectangle; a circle is the ellipse inside it. */
253
+ function boxOps(r, ellipse) {
254
+ const [x0, y0, x1, y1] = r;
255
+ if (!(x1 > x0 && y1 > y0)) return [];
256
+ if (!ellipse) return [`${num(x0)} ${num(y0)} ${num(x1 - x0)} ${num(y1 - y0)} re`];
257
+ const cx = (x0 + x1) / 2;
258
+ const cy = (y0 + y1) / 2;
259
+ const rx = (x1 - x0) / 2;
260
+ const ry = (y1 - y0) / 2;
261
+ const hx = rx * KAPPA;
262
+ const hy = ry * KAPPA;
263
+ const c = (ax, ay, bx, by, x, y) => `${num(ax)} ${num(ay)} ${num(bx)} ${num(by)} ${num(x)} ${num(y)} c`;
264
+ return [
265
+ `${num(cx + rx)} ${num(cy)} m`,
266
+ c(cx + rx, cy + hy, cx + hx, cy + ry, cx, cy + ry),
267
+ c(cx - hx, cy + ry, cx - rx, cy + hy, cx - rx, cy),
268
+ c(cx - rx, cy - hy, cx - hx, cy - ry, cx, cy - ry),
269
+ c(cx + hx, cy - ry, cx + rx, cy - hy, cx + rx, cy),
270
+ "h"
271
+ ];
272
+ }
273
+ /**
274
+ * §12.5.6.10 `/QuadPoints` — the run of text a text-markup annotation marks,
275
+ * eight numbers per quad. Their stated order is notoriously not the order
276
+ * producers write them in, so each quad is taken as the box its four corners
277
+ * bound, which comes to the same box either way for text that is not turned.
278
+ */
279
+ function eachQuad(quads) {
280
+ const n = numbers(quads);
281
+ const out = [];
282
+ for (let q = 0; q + 7 < n.length; q += 8) {
283
+ const xs = [
284
+ n[q],
285
+ n[q + 2],
286
+ n[q + 4],
287
+ n[q + 6]
288
+ ];
289
+ const ys = [
290
+ n[q + 1],
291
+ n[q + 3],
292
+ n[q + 5],
293
+ n[q + 7]
294
+ ];
295
+ const x0 = Math.min(...xs);
296
+ const x1 = Math.max(...xs);
297
+ const y0 = Math.min(...ys);
298
+ const h = Math.max(...ys) - y0;
299
+ if (x1 > x0 && h > 0) out.push({
300
+ x0,
301
+ x1,
302
+ y0,
303
+ h
304
+ });
305
+ }
306
+ return out;
307
+ }
308
+ /** §12.5.4 `/BS` `/W`, else §12.5.2 `/Border`'s third number, else one point. */
309
+ function borderWidth(file, annot) {
310
+ const bs = file.get(annot, "BS");
311
+ if (bs instanceof Map) {
312
+ const w = file.get(bs, "W");
313
+ if (typeof w === "number" && w >= 0) return w;
314
+ }
315
+ const border = numbers(file.get(annot, "Border"));
316
+ if (border.length >= 3 && border[2] >= 0) return border[2];
317
+ return DEFAULT_BORDER_PT;
318
+ }
319
+ /**
320
+ * §12.5.6.2 — an annotation colour is 1, 3 or 4 numbers (grey, RGB, CMYK), and
321
+ * an EMPTY array means no colour at all. Returned as the three operands `rg`
322
+ * and `RG` both take.
323
+ */
324
+ function colorOf(value) {
325
+ const n = numbers(value);
326
+ if (n.length === 1) return `${num(n[0])} ${num(n[0])} ${num(n[0])}`;
327
+ if (n.length === 3) return n.map((v) => num(v)).join(" ");
328
+ if (n.length === 4) {
329
+ const [c, m, y, k] = n;
330
+ return [
331
+ num((1 - c) * (1 - k)),
332
+ num((1 - m) * (1 - k)),
333
+ num((1 - y) * (1 - k))
334
+ ].join(" ");
335
+ }
336
+ }
337
+ /** The arrays inside an array — `/InkList` is one run of points per stroke. */
338
+ function listOfLists(value) {
339
+ if (!Array.isArray(value)) return [];
340
+ return value.map((v) => numbers(v));
341
+ }
342
+ /** The numeric members of an array value, or an empty list. */
343
+ function numbers(value) {
344
+ if (!Array.isArray(value)) return [];
345
+ return value.filter((v) => typeof v === "number" && Number.isFinite(v));
346
+ }
347
+ /** A four-number array as an ordered rectangle, or `undefined`. */
348
+ function rectangle(v) {
349
+ const n = numbers(v);
350
+ if (n.length < 4) return void 0;
351
+ return [
352
+ Math.min(n[0], n[2]),
353
+ Math.min(n[1], n[3]),
354
+ Math.max(n[0], n[2]),
355
+ Math.max(n[1], n[3])
356
+ ];
357
+ }
358
+ /** The rectangle pulled in on every side, never past its own middle. */
359
+ function inset(r, by) {
360
+ const dx = Math.min(by, (r[2] - r[0]) / 2);
361
+ const dy = Math.min(by, (r[3] - r[1]) / 2);
362
+ return [
363
+ r[0] + dx,
364
+ r[1] + dy,
365
+ r[2] - dx,
366
+ r[3] - dy
367
+ ];
368
+ }
369
+ /** Three decimals is finer than any pen, and keeps the stream readable. */
370
+ function num(v) {
371
+ return String(Math.round(v * 1e3) / 1e3);
372
+ }
373
+ //#endregion
374
+ export { drawnAppearance, drawnResources, textMarkupOf };
@@ -15,7 +15,9 @@ export interface Appearance {
15
15
  *
16
16
  * A `Popup` is a note's window and is never part of the page. An annotation
17
17
  * flagged Hidden or NoView paints nothing. An `/AP` `/N` may be a stream or a
18
- * dictionary of states, in which case `/AS` names the one in force.
18
+ * dictionary of states, in which case `/AS` names the one in force. An
19
+ * annotation with no appearance at all gets one written for it from its own
20
+ * geometry, where its subtype says exactly what that is ({@link drawnAppearance}).
19
21
  *
20
22
  * @param file The owning file, for resolving references.
21
23
  * @param page The page whose annotations are wanted.
@@ -1,5 +1,6 @@
1
1
  import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
2
2
  import { IDENTITY, multiply } from "./content.js";
3
+ import { drawnAppearance, drawnResources, textMarkupOf } from "./annot-draw.js";
3
4
  //#region src/pdf-reader/annots.ts
4
5
  /** §12.5.3 `/F` — the annotation is not painted at all. */
5
6
  var FLAG_HIDDEN = 2;
@@ -10,7 +11,9 @@ var FLAG_NOVIEW = 32;
10
11
  *
11
12
  * A `Popup` is a note's window and is never part of the page. An annotation
12
13
  * flagged Hidden or NoView paints nothing. An `/AP` `/N` may be a stream or a
13
- * dictionary of states, in which case `/AS` names the one in force.
14
+ * dictionary of states, in which case `/AS` names the one in force. An
15
+ * annotation with no appearance at all gets one written for it from its own
16
+ * geometry, where its subtype says exactly what that is ({@link drawnAppearance}).
14
17
  *
15
18
  * @param file The owning file, for resolving references.
16
19
  * @param page The page whose annotations are wanted.
@@ -25,10 +28,19 @@ function collectPageAppearances(file, page) {
25
28
  if (!(annot instanceof Map)) continue;
26
29
  const subtype = file.get(annot, "Subtype");
27
30
  if (subtype instanceof PdfName && subtype.value === "Popup") continue;
31
+ if (textMarkupOf(file, annot)) continue;
28
32
  const flags = file.get(annot, "F");
29
33
  if (typeof flags === "number" && (flags & FLAG_HIDDEN || flags & FLAG_NOVIEW)) continue;
30
34
  const stream = normalAppearance(file, annot);
31
- if (!stream) continue;
35
+ if (!stream) {
36
+ const drawn = drawnAppearance(file, annot);
37
+ if (drawn) out.push({
38
+ stream: drawn,
39
+ ctm: IDENTITY,
40
+ resources: drawnResources(file, annot)
41
+ });
42
+ continue;
43
+ }
32
44
  const rect = rectangle(file.get(annot, "Rect"));
33
45
  if (!rect) continue;
34
46
  const matrix = matrixOf(file, stream.dict);
@@ -50,8 +62,10 @@ function normalAppearance(file, annot) {
50
62
  if (normal instanceof PdfStream) return normal;
51
63
  if (!(normal instanceof Map)) return void 0;
52
64
  const state = file.get(annot, "AS");
53
- const picked = state instanceof PdfName ? file.resolve(normal.get(state.value) ?? PDF_NULL) : PDF_NULL;
54
- if (picked instanceof PdfStream) return picked;
65
+ if (state instanceof PdfName) {
66
+ const picked = file.resolve(normal.get(state.value) ?? PDF_NULL);
67
+ return picked instanceof PdfStream ? picked : void 0;
68
+ }
55
69
  const only = [...normal.values()].map((v) => file.resolve(v)).filter((v) => v instanceof PdfStream);
56
70
  return only.length === 1 ? only[0] : void 0;
57
71
  }
@@ -23,6 +23,24 @@ export interface CcittParams {
23
23
  * decoded at all).
24
24
  */
25
25
  export declare function decodeCcitt(data: Uint8Array, params: CcittParams): Uint8Array | undefined;
26
+ /**
27
+ * ISO/IEC 14492 §C.5 — the bitplanes of a grey-scale image, all MMR-coded into
28
+ * ONE stream, one after another with an EOFB between them.
29
+ *
30
+ * They cannot be decoded a plane at a time from the head of the data, because
31
+ * nothing says how many BYTES a plane took: the reader has to carry its place
32
+ * across them. Advancing instead by the size of what came OUT left every plane
33
+ * after the first reading from the middle of the one before it, and
34
+ * bitmap-halftone-10bpp-mmr.pdf came back as three specks.
35
+ *
36
+ * @param data The stream holding all the planes.
37
+ * @param columns Each plane's width, in pixels.
38
+ * @param rows Each plane's height.
39
+ * @param count How many planes to read.
40
+ * @returns One packed bitmap per plane, or `undefined` if the first will not
41
+ * decode at all.
42
+ */
43
+ export declare function decodeCcittPlanes(data: Uint8Array, columns: number, rows: number, count: number): Array<Uint8Array> | undefined;
26
44
  /** A decoded T.6 two-dimensional mode code: pass, horizontal, or vertical V(d). */
27
45
  export interface Mode {
28
46
  readonly kind: 'pass' | 'horizontal' | 'vertical';
@@ -10,10 +10,46 @@
10
10
  * decoded at all).
11
11
  */
12
12
  function decodeCcitt(data, params) {
13
+ return decodeInto(new BitReader(data), params);
14
+ }
15
+ /**
16
+ * ISO/IEC 14492 §C.5 — the bitplanes of a grey-scale image, all MMR-coded into
17
+ * ONE stream, one after another with an EOFB between them.
18
+ *
19
+ * They cannot be decoded a plane at a time from the head of the data, because
20
+ * nothing says how many BYTES a plane took: the reader has to carry its place
21
+ * across them. Advancing instead by the size of what came OUT left every plane
22
+ * after the first reading from the middle of the one before it, and
23
+ * bitmap-halftone-10bpp-mmr.pdf came back as three specks.
24
+ *
25
+ * @param data The stream holding all the planes.
26
+ * @param columns Each plane's width, in pixels.
27
+ * @param rows Each plane's height.
28
+ * @param count How many planes to read.
29
+ * @returns One packed bitmap per plane, or `undefined` if the first will not
30
+ * decode at all.
31
+ */
32
+ function decodeCcittPlanes(data, columns, rows, count) {
33
+ const reader = new BitReader(data);
34
+ const out = [];
35
+ for (let i = 0; i < count; i++) {
36
+ const plane = decodeInto(reader, {
37
+ k: -1,
38
+ columns,
39
+ rows,
40
+ byteAlign: false
41
+ });
42
+ if (!plane) return out.length > 0 ? out : void 0;
43
+ out.push(plane);
44
+ reader.skipEofb();
45
+ reader.align();
46
+ }
47
+ return out;
48
+ }
49
+ function decodeInto(reader, params) {
13
50
  const { k, columns, rows, byteAlign } = params;
14
51
  if (k > 0) return void 0;
15
52
  if (columns <= 0 || rows <= 0 || columns > 1 << 20) return void 0;
16
- const reader = new BitReader(data);
17
53
  const rowBytes = columns + 7 >> 3;
18
54
  const out = new Uint8Array(rowBytes * rows);
19
55
  let ref = [columns, columns];
@@ -145,6 +181,38 @@ var BitReader = class {
145
181
  this.bytePos++;
146
182
  }
147
183
  }
184
+ /**
185
+ * Step over the end-of-facsimile-block that closes an MMR plane: two EOLs,
186
+ * each eleven zeroes and a one. Anything else is left where it stands.
187
+ */
188
+ skipEofb() {
189
+ const at = {
190
+ byte: this.bytePos,
191
+ bit: this.bitPos
192
+ };
193
+ for (let eol = 0; eol < 2; eol++) {
194
+ let zeros = 0;
195
+ for (;;) {
196
+ const b = this.readBit();
197
+ if (b < 0) return;
198
+ if (b === 0) {
199
+ zeros++;
200
+ if (zeros > 64) {
201
+ this.bytePos = at.byte;
202
+ this.bitPos = at.bit;
203
+ return;
204
+ }
205
+ continue;
206
+ }
207
+ if (zeros < 11) {
208
+ this.bytePos = at.byte;
209
+ this.bitPos = at.bit;
210
+ return;
211
+ }
212
+ break;
213
+ }
214
+ }
215
+ }
148
216
  };
149
217
  /**
150
218
  * T.4 white-run modified-Huffman codes as `[run, bit-string]` (terminating runs
@@ -406,4 +474,4 @@ var WHITE = buildRunTable(WHITE_CODES, SHARED_MAKEUP);
406
474
  var BLACK = buildRunTable(BLACK_CODES, SHARED_MAKEUP);
407
475
  var MODES = new Map(MODE_CODES.map(([bits, m]) => [bits.length << 8 | parseInt(bits, 2), m]));
408
476
  //#endregion
409
- export { decodeCcitt };
477
+ export { decodeCcitt, decodeCcittPlanes };
@@ -0,0 +1,33 @@
1
+ /** §8.6.5.6/§8.6.5.7 — what a CIE-based space states about itself. */
2
+ export interface CieSpace {
3
+ /** `/WhitePoint` — the colour of the illuminant, in XYZ. */
4
+ readonly white: readonly [number, number, number];
5
+ /** `/Gamma` — one per component. */
6
+ readonly gamma: ReadonlyArray<number>;
7
+ /** §8.6.5.7 `/Matrix` — components to XYZ, column-major as the PDF states it. */
8
+ readonly matrix?: ReadonlyArray<number>;
9
+ }
10
+ /**
11
+ * One colour in a CIE-based space, as sRGB in 0..1.
12
+ *
13
+ * @param space The space's own parameters.
14
+ * @param abc Its components: one for a grey space, three for an RGB one.
15
+ * @returns The sRGB triple a viewer shows for it.
16
+ */
17
+ export declare function cieToSrgb(space: CieSpace, abc: ReadonlyArray<number>): [number, number, number];
18
+ /**
19
+ * §8.6.5.8 — one colour in a `Lab` space, as sRGB in 0..1.
20
+ *
21
+ * Lab is the one CIE space a file states in absolute terms: `L*` from 0 to 100
22
+ * is lightness and `a*`/`b*` are the two opponent axes, and unlike `CalRGB`
23
+ * (see the note above) there is no argument about the way out — every renderer
24
+ * takes it through XYZ against the stated white and adapts to the screen's.
25
+ * issue10339_reduced.pdf paints two grids of blue swatches through an `Indexed`
26
+ * palette whose base is one, and read as anything else the page came back
27
+ * blank.
28
+ *
29
+ * @param white The space's `/WhitePoint`, in XYZ.
30
+ * @param lab `L*`, `a*`, `b*`.
31
+ * @returns The sRGB triple a viewer shows for it.
32
+ */
33
+ export declare function labToSrgb(white: readonly [number, number, number], lab: readonly [number, number, number]): [number, number, number];