reamkit 1.26.0 → 1.28.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 (85) hide show
  1. package/README.md +14 -8
  2. package/dist/esm/core/converter/ream.d.ts +0 -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.d.ts +65 -0
  17. package/dist/esm/pdf-reader/annot-draw.js +487 -0
  18. package/dist/esm/pdf-reader/annots.d.ts +0 -12
  19. package/dist/esm/pdf-reader/annots.js +30 -4
  20. package/dist/esm/pdf-reader/ccitt.d.ts +20 -3
  21. package/dist/esm/pdf-reader/ccitt.js +102 -6
  22. package/dist/esm/pdf-reader/cff-outline.d.ts +36 -0
  23. package/dist/esm/pdf-reader/cff-outline.js +1122 -0
  24. package/dist/esm/pdf-reader/cie-color.d.ts +33 -0
  25. package/dist/esm/pdf-reader/cie-color.js +112 -0
  26. package/dist/esm/pdf-reader/cmap.js +5 -2
  27. package/dist/esm/pdf-reader/content.d.ts +123 -3
  28. package/dist/esm/pdf-reader/content.js +232 -54
  29. package/dist/esm/pdf-reader/dingbats.d.ts +11 -0
  30. package/dist/esm/pdf-reader/dingbats.js +1033 -0
  31. package/dist/esm/pdf-reader/display.d.ts +1 -1
  32. package/dist/esm/pdf-reader/display.js +61 -6
  33. package/dist/esm/pdf-reader/document.d.ts +6 -0
  34. package/dist/esm/pdf-reader/document.js +55 -10
  35. package/dist/esm/pdf-reader/embedded-fonts.d.ts +23 -3
  36. package/dist/esm/pdf-reader/embedded-fonts.js +36 -4
  37. package/dist/esm/pdf-reader/encodings.d.ts +25 -0
  38. package/dist/esm/pdf-reader/encodings.js +110 -0
  39. package/dist/esm/pdf-reader/flow-build.d.ts +19 -5
  40. package/dist/esm/pdf-reader/flow-build.js +111 -11
  41. package/dist/esm/pdf-reader/font.js +403 -21
  42. package/dist/esm/pdf-reader/function.d.ts +16 -0
  43. package/dist/esm/pdf-reader/function.js +414 -0
  44. package/dist/esm/pdf-reader/glyf-outline.d.ts +43 -0
  45. package/dist/esm/pdf-reader/glyf-outline.js +351 -0
  46. package/dist/esm/pdf-reader/glyph-names.js +20 -0
  47. package/dist/esm/pdf-reader/icc.d.ts +10 -0
  48. package/dist/esm/pdf-reader/icc.js +210 -0
  49. package/dist/esm/pdf-reader/image-decode.d.ts +9 -4
  50. package/dist/esm/pdf-reader/image-decode.js +274 -72
  51. package/dist/esm/pdf-reader/images.d.ts +18 -0
  52. package/dist/esm/pdf-reader/images.js +154 -11
  53. package/dist/esm/pdf-reader/jbig2.d.ts +23 -0
  54. package/dist/esm/pdf-reader/jbig2.js +126 -32
  55. package/dist/esm/pdf-reader/layout.d.ts +32 -0
  56. package/dist/esm/pdf-reader/layout.js +1316 -64
  57. package/dist/esm/pdf-reader/lexer.d.ts +2 -0
  58. package/dist/esm/pdf-reader/lexer.js +4 -0
  59. package/dist/esm/pdf-reader/math-rows.d.ts +23 -0
  60. package/dist/esm/pdf-reader/math-rows.js +198 -0
  61. package/dist/esm/pdf-reader/optional-content.d.ts +36 -0
  62. package/dist/esm/pdf-reader/optional-content.js +93 -0
  63. package/dist/esm/pdf-reader/predefined-cmap.d.ts +21 -0
  64. package/dist/esm/pdf-reader/predefined-cmap.js +102 -0
  65. package/dist/esm/pdf-reader/reader.d.ts +6 -6
  66. package/dist/esm/pdf-reader/reader.js +102 -16
  67. package/dist/esm/pdf-reader/shading.d.ts +139 -8
  68. package/dist/esm/pdf-reader/shading.js +309 -39
  69. package/dist/esm/pdf-reader/standard-metrics.d.ts +8 -0
  70. package/dist/esm/pdf-reader/standard-metrics.js +18 -0
  71. package/dist/esm/pdf-reader/standard-widths.d.ts +20 -0
  72. package/dist/esm/pdf-reader/standard-widths.js +62 -0
  73. package/dist/esm/pdf-reader/stream-filters.d.ts +6 -0
  74. package/dist/esm/pdf-reader/stream-filters.js +67 -0
  75. package/dist/esm/pdf-reader/tagged.js +204 -32
  76. package/dist/esm/pdf-reader/text-rules.d.ts +16 -0
  77. package/dist/esm/pdf-reader/text-rules.js +185 -0
  78. package/dist/esm/pdf-reader/text.js +157 -4
  79. package/dist/esm/pdf-reader/type1-outline.d.ts +21 -0
  80. package/dist/esm/pdf-reader/type1-outline.js +576 -0
  81. package/dist/esm/pdf-reader/vector.d.ts +5 -0
  82. package/dist/esm/pdf-reader/vector.js +79 -27
  83. package/dist/esm/word/document-parser.js +4 -1
  84. package/dist/esm/word/docx-writer.js +74 -11
  85. package/package.json +1 -1
@@ -0,0 +1,487 @@
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) ?? checkedBox(file, annot);
23
+ const markup = markupShape(file, annot, subtype.value);
24
+ if (markup) return markup;
25
+ const pen = borderWidth(file, annot);
26
+ const drawing = pathFor(file, annot, subtype.value, pen);
27
+ if (!drawing || drawing.ops.length === 0) return void 0;
28
+ const stroke = colorOf(file.get(annot, "C"));
29
+ const fillColor = colorOf(file.get(annot, "IC"));
30
+ const body = [
31
+ "q",
32
+ `${num(pen)} w`,
33
+ ...stroke !== void 0 ? [`${stroke} RG`] : [],
34
+ ...fillColor !== void 0 ? [`${fillColor} rg`] : [],
35
+ ...drawing.ops,
36
+ drawing.paint,
37
+ "Q"
38
+ ].join("\n");
39
+ return new PdfStream(/* @__PURE__ */ new Map(), new TextEncoder().encode(body));
40
+ }
41
+ /**
42
+ * §12.7.4.2 — a check box or radio button whose appearance was never generated.
43
+ *
44
+ * Its state is `/AS`, or `/V` where the widget states none, and `/Off` means
45
+ * exactly that: nothing is drawn. Anything else is the ON state, and what a
46
+ * viewer draws for it is the caption `/MK /CA` — one character of ZapfDingbats,
47
+ * `4` (a check) by default, `l` (a filled circle) for the radio buttons that
48
+ * ask for it.
49
+ *
50
+ * The border and background come from `/MK`, and where the file gives none
51
+ * neither is drawn: checkbox_no_appearance.pdf states no `/MK` at all, and
52
+ * poppler draws its ticked box as a bare check mark on the paper.
53
+ */
54
+ function checkedBox(file, annot) {
55
+ if (inherited(file, annot, "FT") !== "Btn") return void 0;
56
+ const as = file.get(annot, "AS");
57
+ const state = as instanceof PdfName ? as.value : inherited(file, annot, "V");
58
+ const rect = rectangle(file.get(annot, "Rect"));
59
+ if (!rect) return void 0;
60
+ const [x0, y0, x1, y1] = rect;
61
+ const w = x1 - x0;
62
+ const h = y1 - y0;
63
+ if (!(w > 0) || !(h > 0)) return void 0;
64
+ const mk = file.get(annot, "MK");
65
+ const border = mk instanceof Map ? colorOf(file.get(mk, "BC")) : void 0;
66
+ const ground = mk instanceof Map ? colorOf(file.get(mk, "BG")) : void 0;
67
+ const pen = borderWidth(file, annot);
68
+ const ops = ["q"];
69
+ if (ground !== void 0) ops.push(`${ground} rg`, `${num(x0)} ${num(y0)} ${num(w)} ${num(h)} re`, "f");
70
+ if (border !== void 0 && pen > 0) ops.push(`${border} RG`, `${num(pen)} w`, `${num(x0 + pen / 2)} ${num(y0 + pen / 2)} ${num(w - pen)} ${num(h - pen)} re`, "S");
71
+ if (state !== void 0 && state !== "Off") {
72
+ const stated = mk instanceof Map ? file.get(mk, "CA") : void 0;
73
+ const caption = typeof stated === "string" && stated.length > 0 ? stated[0] : "4";
74
+ const ink = defaultAppearance(file, annot).color;
75
+ const side = Math.min(w, h) * CAPTION_EM;
76
+ const cx = x0 + w / 2;
77
+ const cy = y0 + h / 2;
78
+ if (caption === "l" || caption === "n") {
79
+ ops.push(`${ink} rg`);
80
+ ops.push(...caption === "n" ? [`${num(cx - side / 2)} ${num(cy - side / 2)} ${num(side)} ${num(side)} re`] : circleOps(cx, cy, side / 2), "f");
81
+ } else {
82
+ const pen = Math.max(.6, side * CHECK_PEN_EM);
83
+ ops.push(`${ink} RG`, `${num(pen)} w`, "1 J", "1 j", `${num(cx - side * .4)} ${num(cy + side * .05)} m`, `${num(cx - side * .12)} ${num(cy - side * .32)} l`, `${num(cx + side * .42)} ${num(cy + side * .36)} l`, "S");
84
+ }
85
+ }
86
+ ops.push("Q");
87
+ if (ops.length <= 2) return void 0;
88
+ return new PdfStream(/* @__PURE__ */ new Map(), new TextEncoder().encode(ops.join("\n")));
89
+ }
90
+ /** How much of the box the mark fills, and how thick the check's pen is. */
91
+ var CAPTION_EM = .8;
92
+ var CHECK_PEN_EM = .13;
93
+ /** A circle of `r` about `(cx, cy)`, as the four Bézier arcs that draw one. */
94
+ function circleOps(cx, cy, r) {
95
+ const k = r * .5523;
96
+ return [
97
+ `${num(cx - r)} ${num(cy)} m`,
98
+ `${num(cx - r)} ${num(cy + k)} ${num(cx - k)} ${num(cy + r)} ${num(cx)} ${num(cy + r)} c`,
99
+ `${num(cx + k)} ${num(cy + r)} ${num(cx + r)} ${num(cy + k)} ${num(cx + r)} ${num(cy)} c`,
100
+ `${num(cx + r)} ${num(cy - k)} ${num(cx + k)} ${num(cy - r)} ${num(cx)} ${num(cy - r)} c`,
101
+ `${num(cx - k)} ${num(cy - r)} ${num(cx - r)} ${num(cy - k)} ${num(cx - r)} ${num(cy)} c`
102
+ ];
103
+ }
104
+ /**
105
+ * §12.5.6.10 — a text markup drawn as a MARK on the paper.
106
+ *
107
+ * The usual reading puts it on the runs it covers, which is right for a page
108
+ * with words on it. bug1538111.pdf has none: four markup annotations over an
109
+ * empty page, and read as marks about text that is not there they came back as
110
+ * nothing at all. Drawn, each is what its subtype says over its own quads.
111
+ */
112
+ function markupShape(file, annot, kind) {
113
+ const markup = textMarkupOf(file, annot);
114
+ if (!markup) return void 0;
115
+ const rgb = colorOf(file.get(annot, "C")) ?? "0 0 0";
116
+ const ops = [
117
+ "q",
118
+ `${rgb} rg`,
119
+ `${rgb} RG`
120
+ ];
121
+ for (const q of markup.quads) {
122
+ const left = q.x0;
123
+ const right = q.x1;
124
+ const bottom = q.y0;
125
+ const h = q.h;
126
+ if (!(right > left) || !(h > 0)) continue;
127
+ if (kind === "Highlight") {
128
+ ops.push(`${num(left)} ${num(bottom)} ${num(right - left)} ${num(h)} re`, "f");
129
+ continue;
130
+ }
131
+ const pen = Math.max(.5, h * MARK_PEN_EM);
132
+ if (kind === "Squiggly") {
133
+ const amp = Math.max(.75, h * SQUIGGLE_AMP_EM);
134
+ const step = amp * 2;
135
+ ops.push(`${num(pen)} w`, `${num(left)} ${num(bottom + amp)} m`);
136
+ let up = true;
137
+ for (let x = left + step; x < right; x += step, up = !up) ops.push(`${num(x)} ${num(bottom + (up ? amp * 2 : 0))} l`);
138
+ ops.push("S");
139
+ continue;
140
+ }
141
+ const y = kind === "StrikeOut" ? bottom + h / 2 : bottom + pen;
142
+ ops.push(`${num(pen)} w`, `${num(left)} ${num(y)} m`, `${num(right)} ${num(y)} l`, "S");
143
+ }
144
+ if (ops.length <= 3) return void 0;
145
+ ops.push("Q");
146
+ return new PdfStream(/* @__PURE__ */ new Map(), new TextEncoder().encode(ops.join("\n")));
147
+ }
148
+ /** How thick a drawn underline or strike is, as a share of the line's height. */
149
+ var MARK_PEN_EM = .05;
150
+ /** How far a squiggle rises above its baseline, likewise. */
151
+ var SQUIGGLE_AMP_EM = .07;
152
+ /**
153
+ * §12.7.2 `/DR` — the resources a generated field appearance draws with.
154
+ *
155
+ * The `/DA` string names a font (`/Helv 12 Tf`) and that name means nothing
156
+ * without the form's own default resource dictionary, which is where every
157
+ * field's face lives.
158
+ *
159
+ * @param file The owning file.
160
+ * @param annot The annotation the appearance was generated for.
161
+ * @returns The form's `/DR`, or `undefined` when there is none.
162
+ */
163
+ function drawnResources(file, annot) {
164
+ const subtype = file.get(annot, "Subtype");
165
+ if (!(subtype instanceof PdfName) || subtype.value !== "Widget") return void 0;
166
+ const acro = file.get(file.catalog, "AcroForm");
167
+ if (!(acro instanceof Map)) return void 0;
168
+ const dr = file.get(acro, "DR");
169
+ return dr instanceof Map ? dr : void 0;
170
+ }
171
+ /**
172
+ * §12.7.3.3 — the text a form field holds, set the way its `/DA` asks.
173
+ *
174
+ * A widget normally carries the picture of its value in `/AP`, and a viewer
175
+ * that fills a field regenerates it. One that does not — or a file flagged
176
+ * `NeedAppearances` — leaves only `/V`, and reading the appearances alone gives
177
+ * a form with every box empty. The field's own default appearance string names
178
+ * the face, the size and the colour; the rest is arithmetic on `/Rect`.
179
+ *
180
+ * Only a TEXT field: a button's value is a state, and a choice field's list is
181
+ * drawn from `/Opt` rather than typed into a box.
182
+ */
183
+ function typedValue(file, annot) {
184
+ if (inherited(file, annot, "FT") !== "Tx") return void 0;
185
+ const value = inheritedString(file, annot, "V");
186
+ if (value === void 0 || value.length === 0) return void 0;
187
+ const rect = rectangle(file.get(annot, "Rect"));
188
+ if (!rect) return void 0;
189
+ const da = defaultAppearance(file, annot);
190
+ const multiline = ((inheritedNumber(file, annot, "Ff") ?? 0) & MULTILINE) !== 0;
191
+ const box = inset(rect, borderWidth(file, annot) + 1);
192
+ const height = box[3] - box[1];
193
+ const width = box[2] - box[0];
194
+ if (!(width > 0 && height > 0)) return void 0;
195
+ const size = da.size > 0 ? da.size : multiline ? 12 : Math.min(12, height * .66);
196
+ const lines = multiline ? value.split(/\r\n|[\r\n]/u) : [value.replace(/[\r\n]+/gu, " ")];
197
+ const leading = size * 1.16;
198
+ const first = multiline ? box[3] - size : box[1] + (height - size) / 2 + size * .22;
199
+ const ops = [
200
+ "q",
201
+ "BT",
202
+ `${da.font} ${num(size)} Tf`,
203
+ `${da.color} rg`
204
+ ];
205
+ lines.forEach((line, i) => {
206
+ const y = first - i * leading;
207
+ if (y < box[1] - size) return;
208
+ ops.push(`1 0 0 1 ${num(box[0] + indent(line, size, width, quadding(file, annot)))} ${num(y)} Tm`);
209
+ ops.push(`(${escapeText(line)}) Tj`);
210
+ });
211
+ ops.push("ET", "Q");
212
+ return new PdfStream(/* @__PURE__ */ new Map(), new TextEncoder().encode(ops.join("\n")));
213
+ }
214
+ /** §12.7.4.3 — the field is a multi-line text box (bit 13 of `/Ff`). */
215
+ var MULTILINE = 4096;
216
+ /**
217
+ * §12.7.3.1 `/Q` — 0 left, 1 centred, 2 right. Where it is not left the line
218
+ * has to be measured, and the face is not in hand: half the size per character
219
+ * is what Helvetica averages, and a field's value is short enough that the
220
+ * error stays under a character.
221
+ */
222
+ function indent(line, size, width, q) {
223
+ if (q !== 1 && q !== 2) return 0;
224
+ const guess = Math.min(width, line.length * size * .5);
225
+ return q === 1 ? (width - guess) / 2 : width - guess;
226
+ }
227
+ /** §12.7.3.1 `/Q`, inherited from the field's parent. */
228
+ function quadding(file, annot) {
229
+ return inheritedNumber(file, annot, "Q") ?? 0;
230
+ }
231
+ /**
232
+ * §12.7.3.3 `/DA` — the default appearance string, from the field or the form.
233
+ * Only the `Tf` and the colour are read; the rest is a content stream fragment
234
+ * this reader does not need to run.
235
+ */
236
+ function defaultAppearance(file, annot) {
237
+ const acro = file.get(file.catalog, "AcroForm");
238
+ const own = inheritedString(file, annot, "DA");
239
+ const form = acro instanceof Map ? file.get(acro, "DA") : void 0;
240
+ const da = own ?? (typeof form === "string" ? form : "");
241
+ const tf = /\/([^\s/]+)\s+([\d.]+)\s+Tf/u.exec(da);
242
+ const rgb = /([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+rg/u.exec(da);
243
+ const gray = /(^|\s)([\d.]+)\s+g(\s|$)/u.exec(da);
244
+ 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";
245
+ return {
246
+ font: `/${tf?.[1] ?? "Helv"}`,
247
+ size: Number(tf?.[2] ?? 0),
248
+ color
249
+ };
250
+ }
251
+ /** §7.3.4.2 — the characters a literal string may not carry unescaped. */
252
+ function escapeText(s) {
253
+ return s.replace(/[\\()]/gu, (c) => `\\${c}`).replace(/[\r\n]/gu, " ");
254
+ }
255
+ /** A field entry, from the widget or the field tree above it (§12.7.3.1). */
256
+ function inherited(file, annot, key) {
257
+ const found = climb(file, annot, key);
258
+ return found instanceof PdfName ? found.value : void 0;
259
+ }
260
+ /** The same, for an entry stated as a string. */
261
+ function inheritedString(file, annot, key) {
262
+ const found = climb(file, annot, key);
263
+ return typeof found === "string" ? found : void 0;
264
+ }
265
+ /** The same, for a number. */
266
+ function inheritedNumber(file, annot, key) {
267
+ const found = climb(file, annot, key);
268
+ return typeof found === "number" ? found : void 0;
269
+ }
270
+ /** Walk `/Parent` until the entry is found, or the tree ends. */
271
+ function climb(file, annot, key) {
272
+ let at = annot;
273
+ for (let depth = 0; at && depth < 32; depth++) {
274
+ const here = file.get(at, key);
275
+ if (here !== PDF_NULL) return here;
276
+ const up = file.get(at, "Parent");
277
+ at = up instanceof Map ? up : void 0;
278
+ }
279
+ }
280
+ /**
281
+ * §12.5.6.10 — what a TEXT-MARKUP annotation says about the words it covers.
282
+ *
283
+ * A highlight, an underline, a strikeout and a squiggle are not drawings: each
284
+ * names a run of text and a way of marking it. Lifted as artwork they are a
285
+ * band and a rule anchored to the page, which is right until the text re-sets
286
+ * and is then a band sitting between two paragraphs it does not mark. Carried
287
+ * on the RUNS they survive the reflow, and a .docx gets what the annotation
288
+ * meant — `w:shd`, `w:u`, `w:strike`.
289
+ *
290
+ * @param file The owning file.
291
+ * @param annot The annotation dictionary.
292
+ * @returns How it marks and what it marks, or `undefined` for another subtype.
293
+ */
294
+ function textMarkupOf(file, annot) {
295
+ const subtype = file.get(annot, "Subtype");
296
+ if (!(subtype instanceof PdfName)) return void 0;
297
+ const kind = subtype.value;
298
+ if (kind !== "Highlight" && kind !== "Underline" && kind !== "StrikeOut" && kind !== "Squiggly") return;
299
+ const quads = eachQuad(file.get(annot, "QuadPoints"));
300
+ if (quads.length === 0) return void 0;
301
+ const rgb = colorOf(file.get(annot, "C"));
302
+ const opacity = file.get(annot, "CA");
303
+ const hex = rgb === void 0 ? void 0 : hexOf(rgb, typeof opacity === "number" && opacity > 0 && opacity < 1 ? opacity : 1);
304
+ return {
305
+ mark: kind === "Highlight" ? { highlightHex: hex ?? "FFFF00" } : kind === "StrikeOut" ? { strike: true } : {
306
+ underline: kind === "Squiggly" ? "wave" : "single",
307
+ ...hex !== void 0 ? { underlineHex: hex } : {}
308
+ },
309
+ quads
310
+ };
311
+ }
312
+ /** The `rg` operands as a 6-hex colour, mixed with white by `alpha`. */
313
+ function hexOf(operands, alpha = 1) {
314
+ const n = operands.split(" ").map(Number);
315
+ if (n.length !== 3 || n.some((v) => !Number.isFinite(v))) return void 0;
316
+ return n.map((v) => {
317
+ const over = Math.min(1, Math.max(0, v)) * alpha + (1 - alpha);
318
+ return Math.round(over * 255).toString(16).padStart(2, "0");
319
+ }).join("").toUpperCase();
320
+ }
321
+ /** What each markup subtype draws, from the geometry it states. */
322
+ function pathFor(file, annot, subtype, pen) {
323
+ switch (subtype) {
324
+ case "Ink": return {
325
+ ops: polylines(listOfLists(file.get(annot, "InkList")), false),
326
+ paint: "S"
327
+ };
328
+ case "Line": return {
329
+ ops: polylines([numbers(file.get(annot, "L"))], false),
330
+ paint: "S"
331
+ };
332
+ case "Polygon":
333
+ case "PolyLine": return {
334
+ ops: polylines([numbers(file.get(annot, "Vertices"))], subtype === "Polygon"),
335
+ paint: filledOrStroked(file, annot)
336
+ };
337
+ case "Square":
338
+ case "Circle": {
339
+ const rect = rectangle(file.get(annot, "Rect"));
340
+ if (!rect) return void 0;
341
+ return {
342
+ ops: boxOps(inset(rect, pen / 2), subtype === "Circle"),
343
+ paint: filledOrStroked(file, annot)
344
+ };
345
+ }
346
+ default: return;
347
+ }
348
+ }
349
+ /** `/IC` fills a square, a circle or a polygon; `/C` strokes its border. */
350
+ function filledOrStroked(file, annot) {
351
+ if (colorOf(file.get(annot, "IC")) === void 0) return "S";
352
+ return colorOf(file.get(annot, "C")) !== void 0 ? "B" : "f";
353
+ }
354
+ /** Each run of x y pairs as one subpath, closed or open. */
355
+ function polylines(runs, close) {
356
+ const ops = [];
357
+ for (const pts of runs) {
358
+ if (pts.length < 4) continue;
359
+ ops.push(`${num(pts[0])} ${num(pts[1])} m`);
360
+ for (let i = 2; i + 1 < pts.length; i += 2) ops.push(`${num(pts[i])} ${num(pts[i + 1])} l`);
361
+ if (close) ops.push("h");
362
+ }
363
+ return ops;
364
+ }
365
+ /** §12.5.6.8 — a square is its rectangle; a circle is the ellipse inside it. */
366
+ function boxOps(r, ellipse) {
367
+ const [x0, y0, x1, y1] = r;
368
+ if (!(x1 > x0 && y1 > y0)) return [];
369
+ if (!ellipse) return [`${num(x0)} ${num(y0)} ${num(x1 - x0)} ${num(y1 - y0)} re`];
370
+ const cx = (x0 + x1) / 2;
371
+ const cy = (y0 + y1) / 2;
372
+ const rx = (x1 - x0) / 2;
373
+ const ry = (y1 - y0) / 2;
374
+ const hx = rx * KAPPA;
375
+ const hy = ry * KAPPA;
376
+ const c = (ax, ay, bx, by, x, y) => `${num(ax)} ${num(ay)} ${num(bx)} ${num(by)} ${num(x)} ${num(y)} c`;
377
+ return [
378
+ `${num(cx + rx)} ${num(cy)} m`,
379
+ c(cx + rx, cy + hy, cx + hx, cy + ry, cx, cy + ry),
380
+ c(cx - hx, cy + ry, cx - rx, cy + hy, cx - rx, cy),
381
+ c(cx - rx, cy - hy, cx - hx, cy - ry, cx, cy - ry),
382
+ c(cx + hx, cy - ry, cx + rx, cy - hy, cx + rx, cy),
383
+ "h"
384
+ ];
385
+ }
386
+ /**
387
+ * §12.5.6.10 `/QuadPoints` — the run of text a text-markup annotation marks,
388
+ * eight numbers per quad. Their stated order is notoriously not the order
389
+ * producers write them in, so each quad is taken as the box its four corners
390
+ * bound, which comes to the same box either way for text that is not turned.
391
+ */
392
+ function eachQuad(quads) {
393
+ const n = numbers(quads);
394
+ const out = [];
395
+ for (let q = 0; q + 7 < n.length; q += 8) {
396
+ const xs = [
397
+ n[q],
398
+ n[q + 2],
399
+ n[q + 4],
400
+ n[q + 6]
401
+ ];
402
+ const ys = [
403
+ n[q + 1],
404
+ n[q + 3],
405
+ n[q + 5],
406
+ n[q + 7]
407
+ ];
408
+ const x0 = Math.min(...xs);
409
+ const x1 = Math.max(...xs);
410
+ const y0 = Math.min(...ys);
411
+ const h = Math.max(...ys) - y0;
412
+ if (x1 > x0 && h > 0) out.push({
413
+ x0,
414
+ x1,
415
+ y0,
416
+ h
417
+ });
418
+ }
419
+ return out;
420
+ }
421
+ /** §12.5.4 `/BS` `/W`, else §12.5.2 `/Border`'s third number, else one point. */
422
+ function borderWidth(file, annot) {
423
+ const bs = file.get(annot, "BS");
424
+ if (bs instanceof Map) {
425
+ const w = file.get(bs, "W");
426
+ if (typeof w === "number" && w >= 0) return w;
427
+ }
428
+ const border = numbers(file.get(annot, "Border"));
429
+ if (border.length >= 3 && border[2] >= 0) return border[2];
430
+ return DEFAULT_BORDER_PT;
431
+ }
432
+ /**
433
+ * §12.5.6.2 — an annotation colour is 1, 3 or 4 numbers (grey, RGB, CMYK), and
434
+ * an EMPTY array means no colour at all. Returned as the three operands `rg`
435
+ * and `RG` both take.
436
+ */
437
+ function colorOf(value) {
438
+ const n = numbers(value);
439
+ if (n.length === 1) return `${num(n[0])} ${num(n[0])} ${num(n[0])}`;
440
+ if (n.length === 3) return n.map((v) => num(v)).join(" ");
441
+ if (n.length === 4) {
442
+ const [c, m, y, k] = n;
443
+ return [
444
+ num((1 - c) * (1 - k)),
445
+ num((1 - m) * (1 - k)),
446
+ num((1 - y) * (1 - k))
447
+ ].join(" ");
448
+ }
449
+ }
450
+ /** The arrays inside an array — `/InkList` is one run of points per stroke. */
451
+ function listOfLists(value) {
452
+ if (!Array.isArray(value)) return [];
453
+ return value.map((v) => numbers(v));
454
+ }
455
+ /** The numeric members of an array value, or an empty list. */
456
+ function numbers(value) {
457
+ if (!Array.isArray(value)) return [];
458
+ return value.filter((v) => typeof v === "number" && Number.isFinite(v));
459
+ }
460
+ /** A four-number array as an ordered rectangle, or `undefined`. */
461
+ function rectangle(v) {
462
+ const n = numbers(v);
463
+ if (n.length < 4) return void 0;
464
+ return [
465
+ Math.min(n[0], n[2]),
466
+ Math.min(n[1], n[3]),
467
+ Math.max(n[0], n[2]),
468
+ Math.max(n[1], n[3])
469
+ ];
470
+ }
471
+ /** The rectangle pulled in on every side, never past its own middle. */
472
+ function inset(r, by) {
473
+ const dx = Math.min(by, (r[2] - r[0]) / 2);
474
+ const dy = Math.min(by, (r[3] - r[1]) / 2);
475
+ return [
476
+ r[0] + dx,
477
+ r[1] + dy,
478
+ r[2] - dx,
479
+ r[3] - dy
480
+ ];
481
+ }
482
+ /** Three decimals is finer than any pen, and keeps the stream readable. */
483
+ function num(v) {
484
+ return String(Math.round(v * 1e3) / 1e3);
485
+ }
486
+ //#endregion
487
+ export { drawnAppearance, drawnResources, textMarkupOf };
@@ -9,16 +9,4 @@ export interface Appearance {
9
9
  /** The appearance's `/Resources`, when it states its own. */
10
10
  readonly resources: PdfDict | undefined;
11
11
  }
12
- /**
13
- * Every annotation appearance the page shows, in `/Annots` order — which is
14
- * the order they paint in, over the page's own content.
15
- *
16
- * A `Popup` is a note's window and is never part of the page. An annotation
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.
19
- *
20
- * @param file The owning file, for resolving references.
21
- * @param page The page whose annotations are wanted.
22
- * @returns The appearances, each with the matrix that places it on the page.
23
- */
24
12
  export declare function collectPageAppearances(file: PdfFile, page: PdfPage): Array<Appearance>;
@@ -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,12 +11,26 @@ 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.
17
20
  * @returns The appearances, each with the matrix that places it on the page.
18
21
  */
22
+ /** Whether the page's own content stream shows any glyphs at all. */
23
+ function pageHasText(file, page) {
24
+ const had = textful.get(page.dict);
25
+ if (had !== void 0) return had;
26
+ const content = file.pageContent(page);
27
+ let found = false;
28
+ for (let i = 0; i + 1 < content.length && !found; i++) found = content[i] === 66 && content[i + 1] === 84;
29
+ textful.set(page.dict, found);
30
+ return found;
31
+ }
32
+ /** One answer per page: the collector is called from three passes. */
33
+ var textful = /* @__PURE__ */ new WeakMap();
19
34
  function collectPageAppearances(file, page) {
20
35
  const annots = file.get(page.dict, "Annots");
21
36
  if (!Array.isArray(annots)) return [];
@@ -25,10 +40,19 @@ function collectPageAppearances(file, page) {
25
40
  if (!(annot instanceof Map)) continue;
26
41
  const subtype = file.get(annot, "Subtype");
27
42
  if (subtype instanceof PdfName && subtype.value === "Popup") continue;
43
+ if (textMarkupOf(file, annot) && pageHasText(file, page)) continue;
28
44
  const flags = file.get(annot, "F");
29
45
  if (typeof flags === "number" && (flags & FLAG_HIDDEN || flags & FLAG_NOVIEW)) continue;
30
46
  const stream = normalAppearance(file, annot);
31
- if (!stream) continue;
47
+ if (!stream) {
48
+ const drawn = drawnAppearance(file, annot);
49
+ if (drawn) out.push({
50
+ stream: drawn,
51
+ ctm: IDENTITY,
52
+ resources: drawnResources(file, annot)
53
+ });
54
+ continue;
55
+ }
32
56
  const rect = rectangle(file.get(annot, "Rect"));
33
57
  if (!rect) continue;
34
58
  const matrix = matrixOf(file, stream.dict);
@@ -50,8 +74,10 @@ function normalAppearance(file, annot) {
50
74
  if (normal instanceof PdfStream) return normal;
51
75
  if (!(normal instanceof Map)) return void 0;
52
76
  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;
77
+ if (state instanceof PdfName) {
78
+ const picked = file.resolve(normal.get(state.value) ?? PDF_NULL);
79
+ return picked instanceof PdfStream ? picked : void 0;
80
+ }
55
81
  const only = [...normal.values()].map((v) => file.resolve(v)).filter((v) => v instanceof PdfStream);
56
82
  return only.length === 1 ? only[0] : void 0;
57
83
  }
@@ -3,7 +3,7 @@
3
3
  * §7.4.6), pulled from the filter's `/DecodeParms`.
4
4
  */
5
5
  export interface CcittParams {
6
- /** `/K` — `<0` Group 4, `0` Group 3 1-D, `>0` Group 3 2-D (unsupported). */
6
+ /** `/K` — `<0` Group 4, `0` Group 3 1-D, `>0` Group 3 mixed 1-D/2-D. */
7
7
  readonly k: number;
8
8
  /** `/Columns` (pixels per row). */
9
9
  readonly columns: number;
@@ -19,10 +19,27 @@ export interface CcittParams {
19
19
  * @param data The raw fax codestream (any wrapping filters already stripped).
20
20
  * @param params The `/CCITTFaxDecode` parameters.
21
21
  * @returns The packed bitmap (`rowBytes × rows`, bit 1 = black, MSB first), or
22
- * `undefined` when the stream cannot be decoded (e.g. Group 3 2-D, or nothing
23
- * decoded at all).
22
+ * `undefined` when nothing could be decoded at all.
24
23
  */
25
24
  export declare function decodeCcitt(data: Uint8Array, params: CcittParams): Uint8Array | undefined;
25
+ /**
26
+ * ISO/IEC 14492 §C.5 — the bitplanes of a grey-scale image, all MMR-coded into
27
+ * ONE stream, one after another with an EOFB between them.
28
+ *
29
+ * They cannot be decoded a plane at a time from the head of the data, because
30
+ * nothing says how many BYTES a plane took: the reader has to carry its place
31
+ * across them. Advancing instead by the size of what came OUT left every plane
32
+ * after the first reading from the middle of the one before it, and
33
+ * bitmap-halftone-10bpp-mmr.pdf came back as three specks.
34
+ *
35
+ * @param data The stream holding all the planes.
36
+ * @param columns Each plane's width, in pixels.
37
+ * @param rows Each plane's height.
38
+ * @param count How many planes to read.
39
+ * @returns One packed bitmap per plane, or `undefined` if the first will not
40
+ * decode at all.
41
+ */
42
+ export declare function decodeCcittPlanes(data: Uint8Array, columns: number, rows: number, count: number): Array<Uint8Array> | undefined;
26
43
  /** A decoded T.6 two-dimensional mode code: pass, horizontal, or vertical V(d). */
27
44
  export interface Mode {
28
45
  readonly kind: 'pass' | 'horizontal' | 'vertical';