reamkit 1.30.0 → 1.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +60 -24
  2. package/dist/esm/core/converter/project.js +3 -1
  3. package/dist/esm/core/crypto/offcrypto.js +1 -1
  4. package/dist/esm/core/document-model/index.d.ts +1 -1
  5. package/dist/esm/core/document-model/types.d.ts +63 -0
  6. package/dist/esm/core/font/index.d.ts +2 -0
  7. package/dist/esm/core/font/ttf-build.d.ts +113 -0
  8. package/dist/esm/core/font/ttf-build.js +1224 -0
  9. package/dist/esm/core/font/ttf-subset.d.ts +19 -0
  10. package/dist/esm/core/font/ttf-subset.js +14 -1
  11. package/dist/esm/core/fonts/provider.d.ts +17 -0
  12. package/dist/esm/core/fonts/provider.js +27 -2
  13. package/dist/esm/core/fonts/remote-fonts.js +8 -1
  14. package/dist/esm/core/ir/flow.d.ts +71 -1
  15. package/dist/esm/core/numbering/index.d.ts +1 -1
  16. package/dist/esm/core/numbering/state.d.ts +11 -1
  17. package/dist/esm/core/numbering/state.js +10 -1
  18. package/dist/esm/core/style-cascade/resolver.js +35 -4
  19. package/dist/esm/core/style-cascade/types.d.ts +19 -1
  20. package/dist/esm/core/style-cascade/types.js +3 -0
  21. package/dist/esm/index.d.ts +2 -2
  22. package/dist/esm/layout/page-doc.d.ts +10 -3
  23. package/dist/esm/layout/styled-layout.d.ts +48 -7
  24. package/dist/esm/layout/styled-layout.js +881 -119
  25. package/dist/esm/layout/turned-section.d.ts +38 -0
  26. package/dist/esm/layout/turned-section.js +193 -0
  27. package/dist/esm/pdf/styled-page-emitter.js +78 -2
  28. package/dist/esm/pdf-reader/cff-outline.d.ts +27 -0
  29. package/dist/esm/pdf-reader/cff-outline.js +169 -21
  30. package/dist/esm/pdf-reader/content.d.ts +53 -0
  31. package/dist/esm/pdf-reader/content.js +9 -1
  32. package/dist/esm/pdf-reader/display.d.ts +36 -0
  33. package/dist/esm/pdf-reader/display.js +66 -1
  34. package/dist/esm/pdf-reader/document.js +5 -1
  35. package/dist/esm/pdf-reader/embedded-fonts.d.ts +2 -1
  36. package/dist/esm/pdf-reader/embedded-fonts.js +32 -2
  37. package/dist/esm/pdf-reader/encodings.d.ts +8 -0
  38. package/dist/esm/pdf-reader/encodings.js +25 -3
  39. package/dist/esm/pdf-reader/face-outlines.d.ts +78 -0
  40. package/dist/esm/pdf-reader/face-outlines.js +362 -0
  41. package/dist/esm/pdf-reader/figures.d.ts +52 -0
  42. package/dist/esm/pdf-reader/figures.js +433 -0
  43. package/dist/esm/pdf-reader/flow-build.d.ts +131 -5
  44. package/dist/esm/pdf-reader/flow-build.js +345 -26
  45. package/dist/esm/pdf-reader/font.js +321 -36
  46. package/dist/esm/pdf-reader/glyf-outline.d.ts +33 -0
  47. package/dist/esm/pdf-reader/glyf-outline.js +135 -1
  48. package/dist/esm/pdf-reader/glyph-names.js +154 -1
  49. package/dist/esm/pdf-reader/layout.d.ts +93 -0
  50. package/dist/esm/pdf-reader/layout.js +1703 -214
  51. package/dist/esm/pdf-reader/page-numbers.d.ts +53 -0
  52. package/dist/esm/pdf-reader/page-numbers.js +167 -0
  53. package/dist/esm/pdf-reader/tagged.js +182 -23
  54. package/dist/esm/pdf-reader/text.d.ts +6 -3
  55. package/dist/esm/pdf-reader/text.js +98 -9
  56. package/dist/esm/pdf-reader/type1-outline.d.ts +11 -0
  57. package/dist/esm/pdf-reader/type1-outline.js +63 -8
  58. package/dist/esm/pdf-reader/vector.js +71 -1
  59. package/dist/esm/word/doc/doc-reader.js +6 -2
  60. package/dist/esm/word/doc/doc-text.d.ts +6 -0
  61. package/dist/esm/word/doc/doc-text.js +19 -1
  62. package/dist/esm/word/document-parser.d.ts +2 -2
  63. package/dist/esm/word/document-parser.js +11 -1
  64. package/dist/esm/word/docx-reader.js +5 -3
  65. package/dist/esm/word/docx-writer.js +207 -21
  66. package/dist/esm/word/drawing-parser.d.ts +5 -3
  67. package/dist/esm/word/drawing-parser.js +49 -8
  68. package/dist/esm/word/font-embed.d.ts +30 -0
  69. package/dist/esm/word/font-embed.js +173 -0
  70. package/dist/esm/word/font-table.d.ts +10 -0
  71. package/dist/esm/word/font-table.js +13 -1
  72. package/dist/esm/word/index.js +1 -1
  73. package/dist/esm/word/numbering-parser.d.ts +3 -1
  74. package/dist/esm/word/numbering-parser.js +2 -1
  75. package/dist/esm/word/paragraph-properties.d.ts +7 -6
  76. package/dist/esm/word/paragraph-properties.js +14 -2
  77. package/dist/esm/word/run-properties.js +26 -0
  78. package/package.json +8 -3
@@ -0,0 +1,433 @@
1
+ //#region src/pdf-reader/figures.ts
2
+ /** No thicker than this, a path is a line. */
3
+ var THIN_PT = 1.5;
4
+ /**
5
+ * A line longer than this many ems RULES the page: the rule over a caption,
6
+ * the line under a heading, a table's border. A drawing's own strokes are
7
+ * shorter, and a long one inside a drawing — a chart's axis — is taken with
8
+ * the drawing it stands in once that is found.
9
+ */
10
+ var RULE_EM = 3;
11
+ /** How close, in ems, two paths stand to be parts of one drawing. */
12
+ var JOIN_EM = .5;
13
+ /** The fewest paths a figure is drawn with: a box and a frame are not one. */
14
+ var MIN_PATHS = 6;
15
+ /** Past this share of the sheet, a drawing is the page's ground or frame. */
16
+ var MOST_OF_PAGE = .5;
17
+ /** How far across a figure a line of its words may run and still be a label. */
18
+ var LABEL_FILL = .6;
19
+ /** Past this many lines that run across it, what a drawing holds is prose. */
20
+ var PROSE_LINES = 2;
21
+ /**
22
+ * How far apart, in ems of the text, two parts of one figure may stand. The
23
+ * parts of comments.pdf's Figure 6 stand twenty-six points apart at 9pt.
24
+ */
25
+ var NEAR_EM = 3;
26
+ /** Type smaller than this share of the text's is a drawing's, not the text's. */
27
+ var SMALL_SHARE = .85;
28
+ /** How much larger than the text a figure's words may be set. */
29
+ var LARGEST_LABEL = 1.2;
30
+ /** How far, in its own ems, small type may stand off a drawing and label it. */
31
+ var CAPTION_EM = 2;
32
+ /** What a glyph mapped to no character reads as (see `UNMAPPED` in ./layout). */
33
+ var UNMAPPED = "�";
34
+ /** Where an annotation's appearance stands in the page's painting order. */
35
+ var ANNOTATION_ORDER = Number.MAX_SAFE_INTEGER;
36
+ /**
37
+ * How many ems of its own type a line of words on a drawing may run before it
38
+ * is a line of prose, however wide the drawing: a label is a word or three.
39
+ */
40
+ var LABEL_EM = 15;
41
+ /** Past this many paths a page is artwork through and through. */
42
+ var MOST_PATHS = 4e3;
43
+ /**
44
+ * The figures a page draws.
45
+ *
46
+ * A figure is paths that stand together — each within half an em of the next —
47
+ * and DRAW something: a curve or a slanted line among them. Straight lines
48
+ * and boxes alone are a ruling, which is a table's or a form's, and a page
49
+ * reads those as text. It has words set on it, or it is only artwork, which
50
+ * the page anchors as it always has; and none of its words run across it
51
+ * line after line, which is prose set on a ground, not a label.
52
+ *
53
+ * @param vectors The page's paths, where the page shows them.
54
+ * @param images The page's pictures, likewise.
55
+ * @param runs The page's text, likewise.
56
+ * @param sheet The page's size.
57
+ * @returns The figures, top of the page first.
58
+ */
59
+ function pageFigures(vectors, images, runs, sheet) {
60
+ const words = runs.filter((r) => r.text.replaceAll(UNMAPPED, "").trim().length > 0);
61
+ const traced = runs.filter((r) => r.type3 !== true && tracedRun(r));
62
+ if (words.length === 0 || vectors.length === 0 || vectors.length > MOST_PATHS) return [];
63
+ const size = median(words.map((r) => r.fontSizePt).filter((s) => s > 0)) || 10;
64
+ const gap = size * JOIN_EM;
65
+ const pageArea = Math.max(1, sheet.width * sheet.height);
66
+ const huge = (b) => (b.maxX - b.minX) * (b.maxY - b.minY) > pageArea * MOST_OF_PAGE;
67
+ const thin = (v) => Math.min(v.maxX - v.minX, v.maxY - v.minY) <= THIN_PT;
68
+ const rule = (v) => thin(v) && Math.max(v.maxX - v.minX, v.maxY - v.minY) > size * RULE_EM;
69
+ const lettering = words.filter((r) => r.type3 === true).map(inkOf);
70
+ const letter = (v) => v.glyph === true || lettering.some((b) => (v.minX + v.maxX) / 2 >= b.minX - 1 && (v.minX + v.maxX) / 2 <= b.maxX + 1 && (v.minY + v.maxY) / 2 >= b.minY - 1 && (v.minY + v.maxY) / 2 <= b.maxY + 1);
71
+ const own = vectors.filter((v) => v.orderKey[0] !== ANNOTATION_ORDER);
72
+ const seeds = own.filter((v) => !rule(v) && !huge(v) && !letter(v));
73
+ const parent = seeds.map((_, k) => k);
74
+ const find = (k) => {
75
+ while (parent[k] !== k) {
76
+ parent[k] = parent[parent[k]];
77
+ k = parent[k];
78
+ }
79
+ return k;
80
+ };
81
+ const order = seeds.map((_, k) => k).sort((a, b) => seeds[a].minX - seeds[b].minX);
82
+ for (let i = 0; i < order.length; i++) {
83
+ const a = seeds[order[i]];
84
+ for (let j = i + 1; j < order.length; j++) {
85
+ const b = seeds[order[j]];
86
+ if (b.minX > a.maxX + gap) break;
87
+ if (b.minY <= a.maxY + gap && b.maxY >= a.minY - gap) parent[find(order[i])] = find(order[j]);
88
+ }
89
+ }
90
+ const clusters = /* @__PURE__ */ new Map();
91
+ seeds.forEach((v, k) => {
92
+ const root = find(k);
93
+ const members = clusters.get(root);
94
+ if (members) members.push(v);
95
+ else clusters.set(root, [v]);
96
+ });
97
+ const found = [];
98
+ const pieces = [];
99
+ for (const members of clusters.values()) {
100
+ const box = boxOf(members);
101
+ if (huge(box)) continue;
102
+ if (members.length < MIN_PATHS || !(members.some(draws) || barChart(members))) pieces.push({
103
+ box,
104
+ vectors: members
105
+ });
106
+ else found.push({
107
+ box,
108
+ vectors: members
109
+ });
110
+ }
111
+ if (found.length === 0) return [];
112
+ const near = size * NEAR_EM;
113
+ const text = words.filter((r) => r.type3 !== true && r.fontSizePt >= size * SMALL_SHARE);
114
+ const textBetween = (a, b) => {
115
+ const both = union(a, b);
116
+ return text.some((r) => {
117
+ const ink = inkOf(r);
118
+ return touches(both, ink, 0) && !touches(a, ink, 0) && !touches(b, ink, 0);
119
+ });
120
+ };
121
+ const rules = own.filter(rule);
122
+ const taken = /* @__PURE__ */ new Set();
123
+ let changed = true;
124
+ while (changed) {
125
+ changed = false;
126
+ for (let i = 0; i < found.length; i++) {
127
+ for (let j = found.length - 1; j > i; j--) {
128
+ const [a, b] = [found[i].box, found[j].box];
129
+ if (!touches(a, b, gap) && !(touches(a, b, near) && !textBetween(a, b))) continue;
130
+ found[i].vectors.push(...found[j].vectors);
131
+ found[i].box = union(a, b);
132
+ found.splice(j, 1);
133
+ changed = true;
134
+ }
135
+ for (let j = pieces.length - 1; j >= 0; j--) {
136
+ const [a, b] = [found[i].box, pieces[j].box];
137
+ if (!touches(a, b, gap) && !(touches(a, b, near) && !textBetween(a, b))) continue;
138
+ found[i].vectors.push(...pieces[j].vectors);
139
+ found[i].box = union(a, b);
140
+ pieces.splice(j, 1);
141
+ changed = true;
142
+ }
143
+ for (const r of rules) {
144
+ if (taken.has(r) || !mostlyWithin(r, found[i].box)) continue;
145
+ taken.add(r);
146
+ found[i].vectors.push(r);
147
+ found[i].box = union(found[i].box, r);
148
+ changed = true;
149
+ }
150
+ }
151
+ }
152
+ const figures = [];
153
+ const labelled = /* @__PURE__ */ new Set();
154
+ const lettered = /* @__PURE__ */ new Set();
155
+ for (const f of found) {
156
+ const free = [...words, ...traced].filter((r) => r.type3 !== true && !labelled.has(r));
157
+ const labels = free.filter((r) => touches(f.box, inkOf(r), 0));
158
+ let box = labels.reduce((b, r) => union(b, inkOf(r)), f.box);
159
+ for (let grown = true; grown;) {
160
+ grown = false;
161
+ for (const r of free) {
162
+ if (labels.includes(r) || r.fontSizePt >= size * SMALL_SHARE) continue;
163
+ const ink = inkOf(r);
164
+ if (!touches(box, ink, r.fontSizePt * CAPTION_EM) || ink.minX < box.minX - r.fontSizePt) continue;
165
+ if (ink.maxX > box.maxX + r.fontSizePt) continue;
166
+ labels.push(r);
167
+ box = union(box, ink);
168
+ grown = true;
169
+ }
170
+ }
171
+ if (labels.length === 0 || runsAcross(labels, box, size)) continue;
172
+ if (labels.some((r) => r.fontSizePt > size * LARGEST_LABEL)) continue;
173
+ const spaces = runs.filter((r) => r.text.trim().length === 0 && r.text.length > 0 && touches(box, inkOf(r), 0));
174
+ for (const r of [...labels, ...spaces]) labelled.add(r);
175
+ const held = images.filter((img) => mostlyInside(img, box));
176
+ const letters = lettersOf(labels.filter(tracedRun), vectors).filter((v) => !lettered.has(v));
177
+ for (const v of letters) lettered.add(v);
178
+ figures.push({
179
+ ...box,
180
+ vectors: [...f.vectors, ...letters],
181
+ images: held,
182
+ labels: [...labels, ...spaces]
183
+ });
184
+ }
185
+ return figures.sort((a, b) => b.maxY - a.maxY);
186
+ }
187
+ /**
188
+ * Whether a run is lettering the file states no character for: glyphs traced
189
+ * as paths (see `PdfVector.glyph`), and a label only by where they stand.
190
+ *
191
+ * @param run A run of the page's text.
192
+ */
193
+ function tracedRun(run) {
194
+ return run.text.includes(UNMAPPED) && run.text.replaceAll(UNMAPPED, "").trim() === "";
195
+ }
196
+ /**
197
+ * The glyphs traced for some runs: the paths traced as glyphs whose middles
198
+ * stand in the runs' ink.
199
+ *
200
+ * @param runs The traced runs.
201
+ * @param paths The page's paths.
202
+ */
203
+ function lettersOf(runs, paths) {
204
+ const inks = runs.map(inkOf);
205
+ return paths.filter((v) => {
206
+ if (v.glyph !== true) return false;
207
+ const x = (v.minX + v.maxX) / 2;
208
+ const y = (v.minY + v.maxY) / 2;
209
+ return inks.some((b) => x >= b.minX - 1 && x <= b.maxX + 1 && y >= b.minY - 1 && y <= b.maxY + 1);
210
+ });
211
+ }
212
+ /**
213
+ * A figure's paths with the traced letters painted one after another joined
214
+ * into one: the letters of a label are one shape, where a shape a letter made
215
+ * a chart of six hundred.
216
+ *
217
+ * @param paths The figure's paths, in the order the page painted them.
218
+ * @returns The paths to draw, in that order.
219
+ */
220
+ function joinLetters(paths) {
221
+ const out = [];
222
+ let letters = [];
223
+ const flush = () => {
224
+ const [first, ...rest] = letters;
225
+ if (first !== void 0) out.push(rest.length === 0 ? first : {
226
+ ...first,
227
+ ...boxOf(letters),
228
+ segs: letters.flatMap((v) => v.segs)
229
+ });
230
+ letters = [];
231
+ };
232
+ for (const v of paths) {
233
+ const last = letters[letters.length - 1];
234
+ if (last !== void 0 && (!v.glyph || last.fillHex !== v.fillHex || last.alpha !== v.alpha)) flush();
235
+ if (v.glyph === true) letters.push(v);
236
+ else out.push(v);
237
+ }
238
+ flush();
239
+ return out;
240
+ }
241
+ /** Whether a path draws: a curve, or a line that is neither level nor upright. */
242
+ function draws(v) {
243
+ let x = 0;
244
+ let y = 0;
245
+ for (const s of v.segs) {
246
+ if (s.op === "cubic") return true;
247
+ if (s.op === "line" && Math.abs(s.x - x) > THIN_PT && Math.abs(s.y - y) > THIN_PT) return true;
248
+ if (s.op !== "close") {
249
+ x = s.x;
250
+ y = s.y;
251
+ }
252
+ }
253
+ return false;
254
+ }
255
+ /**
256
+ * Whether paths draw a BAR CHART: filled rectangles, six and more, standing on
257
+ * one line and as long as each other's figures make them — the one drawing
258
+ * made of nothing but straight lines and boxes that a ruling is not. A
259
+ * table's shading stands its boxes on one line too, but each as long as the
260
+ * last.
261
+ *
262
+ * comments.pdf sets Figures 10 and 12 so, bars of gradient and of flat grey.
263
+ * With no curve among them they were taken for a ruling: their labels were
264
+ * read as lines of the text, Figure 12 and the column beside it came back as
265
+ * one table, and Figure 10's caption, the first line left on its page, set
266
+ * the page's top margin 352 points down.
267
+ *
268
+ * @param paths The paths of one cluster.
269
+ */
270
+ function barChart(paths) {
271
+ const bars = paths.filter((v) => v.fillHex !== void 0 || v.gradient !== void 0).flatMap(rectanglesOf);
272
+ const standing = (base, length) => {
273
+ const lengths = /* @__PURE__ */ new Map();
274
+ for (const b of bars) {
275
+ const at = Math.round(base(b) / BAR_LINE_PT);
276
+ const long = length(b);
277
+ if (long > THIN_PT) lengths.set(at, [...lengths.get(at) ?? [], long]);
278
+ }
279
+ return [...lengths.values()].some((all) => all.length >= MIN_PATHS && Math.max(...all) >= Math.min(...all) * BAR_SPREAD);
280
+ };
281
+ return standing((b) => b.minY, (b) => b.maxY - b.minY) || standing((b) => b.minX, (b) => b.maxX - b.minX);
282
+ }
283
+ /** How near, in points, bars' feet stand to stand on one line. */
284
+ var BAR_LINE_PT = .5;
285
+ /** How much longer the longest bar of a chart is than the shortest, at the least. */
286
+ var BAR_SPREAD = 2;
287
+ /** The upright rectangles a path is drawn with, one per subpath that is one. */
288
+ function rectanglesOf(v) {
289
+ const out = [];
290
+ let corners = [];
291
+ let curved = false;
292
+ const close = () => {
293
+ const xs = new Set(corners.map((c) => Math.round(c.x * 10)));
294
+ const ys = new Set(corners.map((c) => Math.round(c.y * 10)));
295
+ if (!curved && corners.length >= 4 && corners.length <= 5 && xs.size === 2 && ys.size === 2) out.push({
296
+ minX: Math.min(...corners.map((c) => c.x)),
297
+ maxX: Math.max(...corners.map((c) => c.x)),
298
+ minY: Math.min(...corners.map((c) => c.y)),
299
+ maxY: Math.max(...corners.map((c) => c.y))
300
+ });
301
+ corners = [];
302
+ curved = false;
303
+ };
304
+ for (const s of v.segs) if (s.op === "move") {
305
+ close();
306
+ corners.push({
307
+ x: s.x,
308
+ y: s.y
309
+ });
310
+ } else if (s.op === "line") corners.push({
311
+ x: s.x,
312
+ y: s.y
313
+ });
314
+ else if (s.op === "cubic") curved = true;
315
+ close();
316
+ return out;
317
+ }
318
+ /**
319
+ * Whether the words on a drawing are prose: lines that run across most of it,
320
+ * more than a caption's worth. A label is short, a paragraph set on a ground
321
+ * fills it.
322
+ *
323
+ * A line is its words as far as the next gap wider than an em. Two boxes side
324
+ * by side put their labels on one baseline — comments.pdf's "Record" and
325
+ * "Enter" a hundred and twenty points apart — and taken as one line they ran
326
+ * across the figure like a line of its text.
327
+ */
328
+ function runsAcross(labels, box, size) {
329
+ const byLine = [];
330
+ for (const r of labels) {
331
+ const line = byLine.find((l) => Math.abs(l.y - r.y) <= size * .3);
332
+ if (line) {
333
+ line.inks.push(inkOf(r));
334
+ line.em = Math.max(line.em, r.fontSizePt);
335
+ } else byLine.push({
336
+ y: r.y,
337
+ em: r.fontSizePt,
338
+ inks: [inkOf(r)]
339
+ });
340
+ }
341
+ const width = box.maxX - box.minX;
342
+ let across = 0;
343
+ for (const { em, inks } of byLine) {
344
+ inks.sort((a, b) => a.minX - b.minX);
345
+ let from = inks[0].minX;
346
+ let to = inks[0].maxX;
347
+ for (const ink of [...inks.slice(1), void 0]) {
348
+ if (ink !== void 0 && ink.minX - to <= size) {
349
+ to = Math.max(to, ink.maxX);
350
+ continue;
351
+ }
352
+ if (to - from >= Math.min(width * LABEL_FILL, em * LABEL_EM)) across++;
353
+ if (ink !== void 0) {
354
+ from = ink.minX;
355
+ to = ink.maxX;
356
+ }
357
+ }
358
+ }
359
+ return across >= PROSE_LINES;
360
+ }
361
+ /** The box a run's glyphs stand in: a fifth of the size below the baseline, the rest above. */
362
+ function inkOf(r) {
363
+ const s = r.fontSizePt;
364
+ if (Math.abs(r.angleDeg ?? 0) < .5) return {
365
+ minX: Math.min(r.x, r.endX),
366
+ minY: r.y - s * .2,
367
+ maxX: Math.max(r.x, r.endX),
368
+ maxY: r.y + s * .8
369
+ };
370
+ return {
371
+ minX: Math.min(r.x, r.endX) - s * .8,
372
+ minY: Math.min(r.y, r.endY) - s * .8,
373
+ maxX: Math.max(r.x, r.endX) + s * .8,
374
+ maxY: Math.max(r.y, r.endY) + s * .8
375
+ };
376
+ }
377
+ function boxOf(members) {
378
+ return members.reduce(union, { ...members[0] });
379
+ }
380
+ function union(a, b) {
381
+ return {
382
+ minX: Math.min(a.minX, b.minX),
383
+ minY: Math.min(a.minY, b.minY),
384
+ maxX: Math.max(a.maxX, b.maxX),
385
+ maxY: Math.max(a.maxY, b.maxY)
386
+ };
387
+ }
388
+ function touches(a, b, slack) {
389
+ return b.minX <= a.maxX + slack && b.maxX >= a.minX - slack && b.minY <= a.maxY + slack && b.maxY >= a.minY - slack;
390
+ }
391
+ /**
392
+ * Whether a rule lies along a drawing rather than meeting it: across it within
393
+ * a point, and at least half its length inside it. A table's border that ends
394
+ * where a figure begins is not the figure's.
395
+ */
396
+ function mostlyWithin(r, box) {
397
+ const across = r.maxX - r.minX >= r.maxY - r.minY;
398
+ const [from, to, lo, hi] = across ? [
399
+ r.minX,
400
+ r.maxX,
401
+ box.minX,
402
+ box.maxX
403
+ ] : [
404
+ r.minY,
405
+ r.maxY,
406
+ box.minY,
407
+ box.maxY
408
+ ];
409
+ const [side, sideEnd, sideLo, sideHi] = across ? [
410
+ r.minY,
411
+ r.maxY,
412
+ box.minY,
413
+ box.maxY
414
+ ] : [
415
+ r.minX,
416
+ r.maxX,
417
+ box.minX,
418
+ box.maxX
419
+ ];
420
+ if (sideEnd < sideLo - 1 || side > sideHi + 1) return false;
421
+ return Math.min(to, hi) - Math.max(from, lo) >= (to - from) / 2;
422
+ }
423
+ /** Whether most of a picture lies inside a box. */
424
+ function mostlyInside(img, box) {
425
+ return Math.max(0, Math.min(img.x + img.widthPt, box.maxX) - Math.max(img.x, box.minX)) * Math.max(0, Math.min(img.y + img.heightPt, box.maxY) - Math.max(img.y, box.minY)) > img.widthPt * img.heightPt / 2;
426
+ }
427
+ function median(values) {
428
+ if (values.length === 0) return 0;
429
+ const sorted = [...values].sort((a, b) => a - b);
430
+ return sorted[Math.floor(sorted.length / 2)];
431
+ }
432
+ //#endregion
433
+ export { joinLetters, pageFigures, tracedRun };
@@ -1,7 +1,8 @@
1
- import { BodyElement, ParagraphProperties, Section, SectionProperties, TextOutline } from '../core/document-model/index.js';
2
- import { FaceFamily, FlowDoc } from '../core/ir/flow.js';
1
+ import { BodyElement, Ligatures, ParagraphProperties, Section, SectionProperties, ShapeBlock, TextOutline } from '../core/document-model/index.js';
2
+ import { FaceFamily, FaceOutlines, FlowDoc } from '../core/ir/flow.js';
3
3
  import { FontRegistry } from '../core/font/index.js';
4
4
  import { Loss, Pt, ResourceStore } from '../core/ir/index.js';
5
+ import { Display } from './display.js';
5
6
  import { PdfImage } from './images.js';
6
7
  import { PdfPage } from './document.js';
7
8
  import { PdfVector } from './vector.js';
@@ -46,10 +47,18 @@ export declare function paragraphBlock(text: string, outlineLevel?: number): Bod
46
47
  * them up to "78110"; in 7-point footnotes the same space is half as wide
47
48
  * again as the page's, and pushes their lines over.
48
49
  *
50
+ * A space between two words one text markup marks is marked with them: the
51
+ * band a highlighter lays runs across the gap. comments.pdf highlights two
52
+ * lines on its sixth page, and with its spaces left bare the band came back
53
+ * broken at every word.
54
+ *
49
55
  * @param before The span the space follows, where there is one.
56
+ * @param after What follows the space, where anything does.
50
57
  * @returns The space.
51
58
  */
52
- export declare function spaceAfter(before: TextSpan | undefined): TextSpan;
59
+ export declare function spaceAfter(before: TextSpan | undefined, after?: {
60
+ readonly markup?: TextMarkup;
61
+ }): TextSpan;
53
62
  export interface TextSpan {
54
63
  readonly text: string;
55
64
  readonly href?: string;
@@ -73,6 +82,12 @@ export interface TextSpan {
73
82
  readonly script?: 'superscript' | 'subscript';
74
83
  /** §12.5.6.10 — a text-markup annotation marks these words. */
75
84
  readonly markup?: TextMarkup;
85
+ /**
86
+ * §17.3.2.43 — the share of their own width the characters are set at: a
87
+ * word space the page set narrower than the face's own (see `fittedSpace` in
88
+ * ./layout).
89
+ */
90
+ readonly widthScale?: number;
76
91
  }
77
92
  /**
78
93
  * Build a paragraph {@link BodyElement} from positioned {@link TextSpan}s,
@@ -125,6 +140,53 @@ export declare function positionedText(spans: ReadonlyArray<TextSpan>, box: {
125
140
  width: number;
126
141
  height: number;
127
142
  }, frame: PageFrame, zOrder: number, rotation60k?: number): BodyElement;
143
+ /**
144
+ * One part of a figure (see `./figures`): the shape it is drawn as, and where
145
+ * its box stands on the page — its left edge and its top, y-up.
146
+ */
147
+ export interface FigureMember {
148
+ readonly shape: ShapeBlock;
149
+ readonly left: number;
150
+ readonly top: number;
151
+ }
152
+ /** A path of a figure, drawn where the page drew it. */
153
+ export declare function vectorMember(v: PdfVector): FigureMember;
154
+ /**
155
+ * A picture of a figure: a box filled with it (§20.1.8.14), which is what a
156
+ * `pic:pic` inside a group is — the shadows comments.pdf lays under each of
157
+ * its boxes.
158
+ */
159
+ export declare function imageMember(image: PdfImage, resources: ResourceStore): FigureMember;
160
+ /**
161
+ * A label of a figure: a line of its words, in a box as wide as they are.
162
+ * The box never wraps — set in a face a little wider than the page's, one
163
+ * word would break in two.
164
+ */
165
+ export declare function labelMember(spans: ReadonlyArray<TextSpan>, box: {
166
+ x: number;
167
+ y: number;
168
+ width: number;
169
+ height: number;
170
+ }, rotation60k?: number): FigureMember;
171
+ /** The box a figure's parts stand in, on the page (y-up). */
172
+ export declare function membersBox(members: ReadonlyArray<FigureMember>): {
173
+ left: number;
174
+ top: number;
175
+ right: number;
176
+ bottom: number;
177
+ };
178
+ /**
179
+ * §20.5.2.17 — a figure as the one drawing it is: a group holding its paths,
180
+ * its pictures and its labels at their places in it, set in a paragraph of
181
+ * its own. An inline drawing takes the room it is tall (a line holding
182
+ * nothing but a picture is the picture's height in Word), so the text after
183
+ * it follows it, and the figure goes with its paragraph wherever the text
184
+ * puts that.
185
+ *
186
+ * @param members Its parts, in the order they are painted.
187
+ * @param properties The paragraph it stands in.
188
+ */
189
+ export declare function figureBlock(members: ReadonlyArray<FigureMember>, properties: ParagraphProperties): BodyElement;
128
190
  /** Collapse losses sharing a `detail` message (the same colour space dropped on many pages). */
129
191
  export declare function dedupeLosses(losses: ReadonlyArray<Loss>): Array<Loss>;
130
192
  /**
@@ -150,7 +212,36 @@ export declare function shapeBlock(v: PdfVector, frame?: PageFrame, zOrder?: num
150
212
  * this single geometry — a known approximation, still far better than a fixed
151
213
  * `A4`. Returns `undefined` when there is no usable first-page box.
152
214
  */
153
- export declare function sectionFromPdfPages(pages: ReadonlyArray<PdfPage>): SectionProperties | undefined;
215
+ export declare function sectionFromPdfPages(pages: ReadonlyArray<PdfPage>, display?: Display): SectionProperties | undefined;
216
+ /**
217
+ * §17.6.20 — a section read in a page's text frame (see `textFrameOf`), set
218
+ * back on the sheet the page is shown on, its lines running down it (`tbRl`).
219
+ *
220
+ * The frame is the sheet turned back a quarter, so its measurements carry
221
+ * over edge for edge: the frame's left margin is where each line starts, the
222
+ * sheet's top one; its top margin is where the first line stands, the
223
+ * sheet's right one.
224
+ *
225
+ * @param section The section as the frame measured it.
226
+ * @param display The geometry its pages were read in.
227
+ * @returns The section on the sheet, or the section unchanged for a page read
228
+ * on its sheet.
229
+ */
230
+ export declare function sectionOnSheet(section: SectionProperties | undefined, display: Display | undefined): SectionProperties | undefined;
231
+ /**
232
+ * A mark anchored to a page read in its text frame, set on the SHEET the page
233
+ * is shown on: where the page drew it, turned with the page.
234
+ *
235
+ * Word lays a `tbRl` section's text down the sheet but stands a drawing
236
+ * anchored to the page at its offsets on the sheet, upright (§17.6.20) — so a
237
+ * picture the frame placed is carried onto the sheet, and turned a quarter
238
+ * clockwise as the words around it are.
239
+ *
240
+ * @param el A body element, anchored or not.
241
+ * @param sheetWidth The sheet's width, which is the frame's height.
242
+ * @returns The element, anchored on the sheet.
243
+ */
244
+ export declare function floatOntoSheet(el: BodyElement, sheetWidth: number): BodyElement;
154
245
  /** How many lines a sheet must hold before where they end says where its measure does. */
155
246
  export declare const MEASURE_LINES = 3;
156
247
  /**
@@ -168,6 +259,13 @@ export declare const GUESSED_MARGIN: number;
168
259
  export declare const BASELINE_AT = 0.8;
169
260
  /** A line's box where no pitch was measured: the ordinary single spacing, in ems. */
170
261
  export declare const NATURAL_LINE_EM = 1.2;
262
+ /**
263
+ * How far above its baseline the first line's BOX reaches, as a fraction of the
264
+ * size — which is what the top margin has to leave room for. The lines are set
265
+ * in exact boxes (see `groupIntoParagraphs`), so this is where the box's top
266
+ * stands, not where a face's ascender happens to.
267
+ */
268
+ export declare const ASCENDER: number;
171
269
  /**
172
270
  * The size a document's text is set in: the middle of every size its runs
173
271
  * carry.
@@ -223,6 +321,34 @@ export declare function withMeasuredMargins(section: SectionProperties | undefin
223
321
  widthPt: number;
224
322
  heightPt: number;
225
323
  }>>, foot?: ReadonlyArray<TextRun>): SectionProperties | undefined;
324
+ /** What a face's runs ask of a word processor to be set as the page set them. */
325
+ export interface Typesetting {
326
+ /** §17.3.2.19 — kerned from this size up. */
327
+ readonly kerningMinPt?: Pt;
328
+ /** [MS-DOCX] `w14:ligatures` — set with these ligatures. */
329
+ readonly ligatures?: Ligatures;
330
+ }
331
+ /**
332
+ * The runs set in a face the page KERNED or LIGATED, marked so (see
333
+ * `pageSpacing`, `FaceOutlines.ligatures`): a word processor kerns nothing a
334
+ * run does not ask it to, and set unkerned, a line kerned on the page runs
335
+ * longer than it did there; a ligature the embedded face carries is formed
336
+ * where the run asks for its face's ligatures.
337
+ *
338
+ * @param blocks The blocks to mark, tables and text boxes within them too.
339
+ * @param typeset Run font name → what its runs ask for.
340
+ * @returns The blocks, their runs in those faces asking for it.
341
+ */
342
+ export declare function typesetRuns(blocks: ReadonlyArray<BodyElement>, typeset: ReadonlyMap<string, Typesetting>): Array<BodyElement>;
343
+ /**
344
+ * What each face's runs ask for: kerning where the page kerned the face, and
345
+ * its standard ligatures where the page drew one of them (the ligatures the
346
+ * face's embedded font then forms are exactly the ones the page drew).
347
+ *
348
+ * @param kerned The run font names of the faces the page kerned.
349
+ * @param faceOutlines Run font name → the face's outlines, ligatures among them.
350
+ */
351
+ export declare function typesetting(kerned: ReadonlySet<string>, faceOutlines: ReadonlyMap<string, FaceOutlines> | undefined): Map<string, Typesetting>;
226
352
  /**
227
353
  * Assemble the final {@link FlowDoc} for a reconstruction: the body elements
228
354
  * with their styles resolved against the empty style sheet, the lifted-image
@@ -230,4 +356,4 @@ export declare function withMeasuredMargins(section: SectionProperties | undefin
230
356
  * both reconstruction paths (the tagged fast-path EP3 and the heuristic layout
231
357
  * path EP4).
232
358
  */
233
- export declare function buildFlowDoc(body: ReadonlyArray<BodyElement>, resources?: ResourceStore, section?: SectionProperties, embeddedFonts?: ReadonlyMap<string, FontRegistry>, sections?: ReadonlyArray<Section>, headersFooters?: ReadonlyMap<string, ReadonlyArray<BodyElement>>, faceFamilies?: ReadonlyMap<string, FaceFamily>): FlowDoc;
359
+ export declare function buildFlowDoc(body: ReadonlyArray<BodyElement>, resources?: ResourceStore, section?: SectionProperties, embeddedFonts?: ReadonlyMap<string, FontRegistry>, sections?: ReadonlyArray<Section>, headersFooters?: ReadonlyMap<string, ReadonlyArray<BodyElement>>, faceFamilies?: ReadonlyMap<string, FaceFamily>, faceOutlines?: ReadonlyMap<string, FaceOutlines>, kerned?: ReadonlySet<string>): FlowDoc;