reamkit 1.29.0 → 1.30.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 (50) hide show
  1. package/dist/esm/core/document-model/types.d.ts +2 -0
  2. package/dist/esm/core/drawingml/shape-render.js +13 -1
  3. package/dist/esm/core/fonts/index.d.ts +1 -1
  4. package/dist/esm/core/fonts/remote-fonts.d.ts +8 -0
  5. package/dist/esm/core/fonts/remote-fonts.js +100 -17
  6. package/dist/esm/core/ir/flow.d.ts +17 -0
  7. package/dist/esm/index.d.ts +1 -1
  8. package/dist/esm/pdf-reader/annot-draw.js +93 -1
  9. package/dist/esm/pdf-reader/annots.d.ts +18 -0
  10. package/dist/esm/pdf-reader/annots.js +86 -9
  11. package/dist/esm/pdf-reader/cmap.js +5 -2
  12. package/dist/esm/pdf-reader/content.d.ts +17 -4
  13. package/dist/esm/pdf-reader/content.js +163 -11
  14. package/dist/esm/pdf-reader/display.js +16 -0
  15. package/dist/esm/pdf-reader/embedded-fonts.d.ts +24 -0
  16. package/dist/esm/pdf-reader/embedded-fonts.js +48 -9
  17. package/dist/esm/pdf-reader/flow-build.d.ts +96 -5
  18. package/dist/esm/pdf-reader/flow-build.js +210 -23
  19. package/dist/esm/pdf-reader/font.d.ts +27 -1
  20. package/dist/esm/pdf-reader/font.js +313 -29
  21. package/dist/esm/pdf-reader/glyf-outline.js +13 -2
  22. package/dist/esm/pdf-reader/glyph-shapes.d.ts +18 -0
  23. package/dist/esm/pdf-reader/glyph-shapes.js +57 -0
  24. package/dist/esm/pdf-reader/image-decode.js +70 -4
  25. package/dist/esm/pdf-reader/images.d.ts +5 -0
  26. package/dist/esm/pdf-reader/images.js +4 -2
  27. package/dist/esm/pdf-reader/jbig2.d.ts +40 -1
  28. package/dist/esm/pdf-reader/jbig2.js +78 -16
  29. package/dist/esm/pdf-reader/jpeg.d.ts +6 -3
  30. package/dist/esm/pdf-reader/jpeg.js +21 -1
  31. package/dist/esm/pdf-reader/layout.d.ts +26 -2
  32. package/dist/esm/pdf-reader/layout.js +638 -92
  33. package/dist/esm/pdf-reader/lexer.d.ts +10 -0
  34. package/dist/esm/pdf-reader/lexer.js +17 -0
  35. package/dist/esm/pdf-reader/pattern-tint.d.ts +11 -1
  36. package/dist/esm/pdf-reader/pattern-tint.js +21 -3
  37. package/dist/esm/pdf-reader/regions.d.ts +25 -0
  38. package/dist/esm/pdf-reader/regions.js +167 -0
  39. package/dist/esm/pdf-reader/shading.d.ts +58 -2
  40. package/dist/esm/pdf-reader/shading.js +181 -11
  41. package/dist/esm/pdf-reader/struct-tree.js +112 -8
  42. package/dist/esm/pdf-reader/tagged.js +27 -7
  43. package/dist/esm/pdf-reader/text-rules.js +1 -1
  44. package/dist/esm/pdf-reader/text.js +9 -14
  45. package/dist/esm/pdf-reader/vector.d.ts +8 -2
  46. package/dist/esm/pdf-reader/vector.js +34 -5
  47. package/dist/esm/word/docx-writer.js +137 -36
  48. package/dist/esm/word/drawing-parser.js +7 -2
  49. package/dist/esm/word/paragraph-properties.js +2 -0
  50. package/package.json +5 -3
@@ -2,6 +2,7 @@ import { pt } from "../core/ir/units.js";
2
2
  import { ResourceStore } from "../core/ir/resources.js";
3
3
  import { EMPTY_STYLE_SHEET, resolveBodyStyles } from "../core/style-cascade/resolver.js";
4
4
  import "../core/style-cascade/index.js";
5
+ import { gradientOverBox } from "./shading.js";
5
6
  import { displayOf } from "./display.js";
6
7
  //#region src/pdf-reader/flow-build.ts
7
8
  /**
@@ -21,6 +22,27 @@ function paragraphBlock(text, outlineLevel) {
21
22
  }
22
23
  };
23
24
  }
25
+ /** One piece of reconstructed text, carrying any hyperlink (E-PDF EP8). */
26
+ /**
27
+ * The space set between a span and what follows it, in the span's own size and
28
+ * face: a word space is as wide as the type it stands in.
29
+ *
30
+ * Written bare, the space took the document's default size, whatever the words
31
+ * around it were set at: issue10665_reduced.pdf sets "78" and "110" twenty
32
+ * points apart in 60-point type, and the eleven-point space between them closed
33
+ * them up to "78110"; in 7-point footnotes the same space is half as wide
34
+ * again as the page's, and pushes their lines over.
35
+ *
36
+ * @param before The span the space follows, where there is one.
37
+ * @returns The space.
38
+ */
39
+ function spaceAfter(before) {
40
+ return {
41
+ text: " ",
42
+ ...before?.sizePt !== void 0 ? { sizePt: before.sizePt } : {},
43
+ ...before?.fontName !== void 0 ? { fontName: before.fontName } : {}
44
+ };
45
+ }
24
46
  /**
25
47
  * Build a paragraph {@link BodyElement} from positioned {@link TextSpan}s,
26
48
  * coalescing consecutive spans that share an `href` into one run (so a link
@@ -65,7 +87,10 @@ function paragraphFromRuns(spans, outlineLevel, placement) {
65
87
  properties: {
66
88
  ...r.sizePt !== void 0 ? { fontSizePt: pt(r.sizePt) } : {},
67
89
  ...r.colorHex !== void 0 ? { colorHex: r.colorHex } : {},
68
- ...r.fontName !== void 0 ? { fontFamily: { ascii: r.fontName } } : {},
90
+ ...r.fontName !== void 0 ? { fontFamily: {
91
+ ascii: r.fontName,
92
+ hAnsi: r.fontName
93
+ } } : {},
69
94
  ...r.outline !== void 0 ? { textOutline: r.outline } : {},
70
95
  ...r.bold ? { bold: true } : {},
71
96
  ...r.italic ? { italic: true } : {},
@@ -85,6 +110,23 @@ function sameMarkup(a, b) {
85
110
  return a?.highlightHex === b?.highlightHex && a?.underline === b?.underline && a?.underlineHex === b?.underlineHex && a?.strike === b?.strike;
86
111
  }
87
112
  /**
113
+ * The height of a line that carries nothing to read: one twip, the least
114
+ * §17.3.1.33 can state. Zero is not a height a writer states at all — it is
115
+ * read as "no line spacing given", and the reader's single spacing comes back.
116
+ */
117
+ var CARRIER_LINE_PT = pt(.05);
118
+ /**
119
+ * §17.3.1.33 — the paragraph a floating mark is anchored in, which takes no
120
+ * room: the mark stands where the page drew it and the line carrying it is on
121
+ * no page. Left at a reader's single spacing, every rule and fill an invoice
122
+ * draws was a blank line in the flow, and the text under it moved down a line
123
+ * for each.
124
+ */
125
+ var FLOAT_CARRIER = {
126
+ spacingLine: CARRIER_LINE_PT,
127
+ spacingLineRule: "exact"
128
+ };
129
+ /**
88
130
  * Store a {@link PdfImage}'s bytes (content-addressed dedup) and build the image
89
131
  * {@link BodyElement} that references them, sized in points from the placement
90
132
  * CTM. `alt` becomes the block's alt text when given.
@@ -112,9 +154,10 @@ function imageBlock(image, resources, alt, frame, zOrder, behind = false) {
112
154
  width: pt(image.widthPt),
113
155
  height: pt(image.heightPt),
114
156
  ...image.rotationDeg !== void 0 ? { rotation60k: Math.round(-image.rotationDeg * 6e4) } : {},
157
+ ...image.flipV === true ? { flipV: true } : {},
115
158
  ...image.crop ? { crop: image.crop } : {},
116
159
  ...image.alpha !== void 0 ? { alpha: image.alpha } : {},
117
- paragraphProperties: {},
160
+ paragraphProperties: float ? FLOAT_CARRIER : {},
118
161
  ...alt ? { altText: alt } : {}
119
162
  }
120
163
  };
@@ -138,7 +181,10 @@ function imageBlock(image, resources, alt, frame, zOrder, behind = false) {
138
181
  * @returns A shape carrying the text, anchored where the glyphs were.
139
182
  */
140
183
  function positionedText(spans, box, frame, zOrder, rotation60k) {
141
- const paragraph = paragraphFromRuns(spans);
184
+ const paragraph = paragraphFromRuns(spans, void 0, {
185
+ spacingLine: pt(Math.max(1, box.height)),
186
+ spacingLineRule: "exact"
187
+ });
142
188
  return {
143
189
  kind: "shape",
144
190
  shape: {
@@ -169,7 +215,7 @@ function positionedText(spans, box, frame, zOrder, rotation60k) {
169
215
  insetRight: pt(0),
170
216
  insetBottom: pt(0)
171
217
  },
172
- paragraphProperties: {}
218
+ paragraphProperties: FLOAT_CARRIER
173
219
  }
174
220
  };
175
221
  }
@@ -226,17 +272,21 @@ function shapeBlock(v, frame, zOrder, behind = false) {
226
272
  const alpha = v.alpha !== void 0 ? { alpha: v.alpha } : {};
227
273
  const fill = v.gradient !== void 0 ? {
228
274
  kind: "gradient",
229
- gradient: v.gradient,
275
+ gradient: gradientOverBox(v.gradient, v),
230
276
  ...alpha
231
277
  } : v.fillHex !== void 0 ? {
232
278
  kind: "solid",
233
279
  colorHex: v.fillHex,
234
280
  ...alpha
235
281
  } : { kind: "none" };
282
+ const dash = v.strokeHex !== void 0 && v.dash !== void 0 ? penDash(v.dash, pen) : [];
236
283
  const line = v.strokeHex !== void 0 ? {
237
284
  width: pt(pen),
238
285
  colorHex: v.strokeHex,
239
- fill: "solid"
286
+ fill: "solid",
287
+ ...dash.length > 0 ? { customDash: dash } : {},
288
+ cap: v.cap ?? "flat",
289
+ ...v.strokeAlpha !== void 0 ? { alpha: v.strokeAlpha } : {}
240
290
  } : void 0;
241
291
  const float = frame !== void 0 ? {
242
292
  wrap: "none",
@@ -267,7 +317,7 @@ function shapeBlock(v, frame, zOrder, behind = false) {
267
317
  },
268
318
  fill,
269
319
  ...line ? { line } : {},
270
- paragraphProperties: {}
320
+ paragraphProperties: float ? FLOAT_CARRIER : {}
271
321
  }
272
322
  };
273
323
  }
@@ -306,6 +356,131 @@ function sectionFromPdfPages(pages) {
306
356
  };
307
357
  }
308
358
  /**
359
+ * §8.4.3.6 — a page's dash pattern as DrawingML states one (§20.1.8.21
360
+ * `a:custDash`): dash and gap in turn, as multiples of the pen's width.
361
+ *
362
+ * An odd count of lengths runs twice before it repeats, so the dashes and the
363
+ * gaps trade places the second time round; written out once more, the pairs
364
+ * come out even. A dash of no length is a DOT, drawn by the pen's cap alone —
365
+ * which no .docx line carries — so it is set a pen's width long and the gap
366
+ * after it gives that back, keeping the pattern's period.
367
+ *
368
+ * @param dash The lengths in page-space points.
369
+ * @param pen The pen's width in points.
370
+ * @returns The pattern in pen widths, or an empty array for a solid line.
371
+ */
372
+ function penDash(dash, pen) {
373
+ if (dash.length === 0 || !(pen > 0)) return [];
374
+ const out = (dash.length % 2 === 1 ? [...dash, ...dash] : [...dash]).map((n) => n / pen);
375
+ for (let i = 0; i < out.length; i += 2) {
376
+ if (out[i] > 0) continue;
377
+ out[i] = 1;
378
+ out[i + 1] = Math.max(out[i + 1] - 1, MIN_DASH_GAP);
379
+ }
380
+ return out;
381
+ }
382
+ /** The least gap a dash pattern keeps, in pen widths, so a dotted line stays dotted. */
383
+ var MIN_DASH_GAP = .5;
384
+ /** What the measure gives back, so the widest line still fits when re-set. */
385
+ var SLACK = .01;
386
+ /** How much of the sheet a margin down the page may take. */
387
+ var DEEPEST_MARGIN = .5;
388
+ /**
389
+ * How far in, as a share of the sheet, a right margin the lines do not show
390
+ * may come: what a sheet of a line or two is re-set across is at least the
391
+ * rest of it.
392
+ */
393
+ var GUESSED_MARGIN = 1 / 3;
394
+ /** The narrowest measure a sheet's own lines may leave, as a share of the sheet. */
395
+ var NARROWEST_MEASURE = .2;
396
+ /**
397
+ * The lines a sheet's runs stand on — baselines further apart than half the
398
+ * size of the type on them — and how many of them END at the sheet's right
399
+ * edge, within an em of the farthest: the lines a measure broke, or a margin
400
+ * justified, rather than the ones their author stopped.
401
+ */
402
+ function linesOf(runs) {
403
+ const ink = runs.filter((r) => r.text.trim() !== "").map((r) => ({
404
+ y: r.y,
405
+ end: r.endX,
406
+ size: r.fontSizePt || 10
407
+ })).sort((a, b) => b.y - a.y);
408
+ const lines = [];
409
+ for (const at of ink) {
410
+ const last = lines[lines.length - 1];
411
+ if (last !== void 0 && last.y - at.y <= Math.max(last.size, at.size) / 2) {
412
+ last.end = Math.max(last.end, at.end);
413
+ last.size = Math.max(last.size, at.size);
414
+ } else lines.push({ ...at });
415
+ }
416
+ const edge = Math.max(...lines.map((l) => l.end));
417
+ return {
418
+ count: lines.length,
419
+ atEdge: lines.filter((l) => edge - l.end <= l.size).length
420
+ };
421
+ }
422
+ /**
423
+ * The white, in ems of the text, a running foot keeps above it — which is
424
+ * what the reader asked of it before it took the line for a foot at all.
425
+ */
426
+ var FOOT_CLEAR_EM = 2;
427
+ /**
428
+ * §17.3.1.33 — where the baseline of an EXACT line stands in its box, from the
429
+ * top. LibreOffice puts it at four fifths of the height; Word at the height
430
+ * less the face's descent, which for the faces documents use is within a tenth
431
+ * of a line of the same place.
432
+ */
433
+ var BASELINE_AT = .8;
434
+ /** A line's box where no pitch was measured: the ordinary single spacing, in ems. */
435
+ var NATURAL_LINE_EM = 1.2;
436
+ /**
437
+ * How far above its baseline the first line's BOX reaches, as a fraction of the
438
+ * size — which is what the top margin has to leave room for. The lines are set
439
+ * in exact boxes (see `groupIntoParagraphs`), so this is where the box's top
440
+ * stands, not where a face's ascender happens to.
441
+ */
442
+ var ASCENDER = BASELINE_AT * NATURAL_LINE_EM;
443
+ /**
444
+ * And how far below its baseline the last line's box reaches. A box set half
445
+ * as deep again as its size reaches three tenths of it down; measured to a
446
+ * face's descender instead, bug1337429.pdf's last line did not fit the sheet
447
+ * it was drawn on and went to a second one.
448
+ */
449
+ var DESCENDER = (1 - BASELINE_AT) * 1.5;
450
+ /**
451
+ * How small, against the document's own text, type may be set and still be
452
+ * read as text rather than as a mark the producer left on the sheet.
453
+ */
454
+ var LEGIBLE_SHARE = .25;
455
+ /**
456
+ * The size a document's text is set in: the middle of every size its runs
457
+ * carry.
458
+ *
459
+ * @param pageRuns Each page's runs.
460
+ * @returns The size, in points; 0 where no run states one.
461
+ */
462
+ function textSizeOf(pageRuns) {
463
+ const sizes = pageRuns.flat().map((r) => r.fontSizePt).filter((s) => s > 0).sort((a, b) => a - b);
464
+ return sizes[Math.floor(sizes.length / 2)] ?? 0;
465
+ }
466
+ /**
467
+ * Whether a run is set too small to be read, beside the text of its document —
468
+ * a mark the producer leaves on the sheet, not a line of the page.
469
+ *
470
+ * TCPDF signs the last page of everything it makes "Powered by TCPDF
471
+ * (www.tcpdf.org)" in type one point high, three points from the corner of the
472
+ * paper. Taken for text it was the leftmost and the lowest thing on the page:
473
+ * the margins came in at the edge of the sheet and every line of basicapi.pdf
474
+ * and alphatrans.pdf was set against it.
475
+ *
476
+ * @param run The run.
477
+ * @param textSize The size the document's text is set in (see {@link textSizeOf}).
478
+ * @returns `true` where the run is a mark rather than text.
479
+ */
480
+ function tooSmallToRead(run, textSize) {
481
+ return run.fontSizePt > 0 && run.fontSizePt < textSize * LEGIBLE_SHARE;
482
+ }
483
+ /**
309
484
  * The margins the SOURCE used, measured off where its words actually sit.
310
485
  *
311
486
  * A PDF states none — text is placed anywhere on the MediaBox — so the reader
@@ -322,18 +497,13 @@ function sectionFromPdfPages(pages) {
322
497
  * @param section The section the page box gave, or `undefined`.
323
498
  * @param shown Each page as it is shown, for its own width and height.
324
499
  * @param pageRuns Each page's runs, already placed on the shown page.
500
+ * @param pageMarks Each page's pictures, which the measure has to hold.
501
+ * @param foot The running foot lifted off the pages, as the first page
502
+ * showed it — the band the text block stands above.
325
503
  * @returns The section with measured margins, or `section` when nothing is
326
504
  * measurable.
327
505
  */
328
- /** What the measure gives back, so the widest line still fits when re-set. */
329
- var SLACK = .01;
330
- /** How much of the sheet a margin down the page may take. */
331
- var DEEPEST_MARGIN = .5;
332
- /** How far a face's ascender stands above its baseline, as a fraction of the size. */
333
- var ASCENDER = .8;
334
- /** And its descender below — the two together are a little over one em. */
335
- var DESCENDER = .22;
336
- function withMeasuredMargins(section, shown, pageRuns, pageMarks = []) {
506
+ function withMeasuredMargins(section, shown, pageRuns, pageMarks = [], foot = []) {
337
507
  if (!section?.pageSize) return section;
338
508
  const width = section.pageSize.width;
339
509
  const height = section.pageSize.height;
@@ -341,8 +511,10 @@ function withMeasuredMargins(section, shown, pageRuns, pageMarks = []) {
341
511
  const rights = [];
342
512
  const tops = [];
343
513
  const bottoms = [];
344
- pageRuns.forEach((runs, i) => {
514
+ const textSize = textSizeOf(pageRuns);
515
+ pageRuns.forEach((all, i) => {
345
516
  const page = shown[i];
517
+ const runs = all.filter((r) => r.annotation !== true && !tooSmallToRead(r, textSize));
346
518
  if (!page || runs.length === 0) return;
347
519
  let minX = Infinity;
348
520
  let maxX = -Infinity;
@@ -365,6 +537,7 @@ function withMeasuredMargins(section, shown, pageRuns, pageMarks = []) {
365
537
  topSize = r.fontSizePt;
366
538
  }
367
539
  }
540
+ const textEnd = maxX;
368
541
  for (const mark of pageMarks[i] ?? []) {
369
542
  if (!Number.isFinite(mark.x) || !Number.isFinite(mark.y)) continue;
370
543
  maxX = Math.max(maxX, mark.x + mark.widthPt);
@@ -372,7 +545,12 @@ function withMeasuredMargins(section, shown, pageRuns, pageMarks = []) {
372
545
  }
373
546
  if (!Number.isFinite(minX) || !Number.isFinite(minY)) return;
374
547
  lefts.push(minX);
375
- rights.push(page.width - maxX);
548
+ const lines = linesOf(runs);
549
+ rights.push({
550
+ value: page.width - maxX,
551
+ full: lines.count >= 3 || maxX > textEnd,
552
+ edged: lines.atEdge >= 2 && maxX <= textEnd
553
+ });
376
554
  tops.push(page.height - maxY - topSize * ASCENDER);
377
555
  bottoms.push(minY - bottomSize * DESCENDER);
378
556
  });
@@ -382,13 +560,21 @@ function withMeasuredMargins(section, shown, pageRuns, pageMarks = []) {
382
560
  return s[Math.floor(s.length / 2)] ?? 0;
383
561
  };
384
562
  const clamp = (v, span, most = 1 / 3) => pt(Math.max(0, Math.min(v, span * most)));
563
+ const inked = foot.filter((r) => r.text.trim() !== "");
564
+ const footTop = Math.max(...inked.map((r) => r.y + r.fontSizePt * ASCENDER));
565
+ const footBottom = Math.min(...inked.map((r) => r.y - r.fontSizePt * DESCENDER));
566
+ const floor = inked.length > 0 ? footTop + textSize * FOOT_CLEAR_EM : Infinity;
567
+ const left = clamp(Math.min(...lefts), width);
568
+ const voters = rights.filter((r) => r.full);
569
+ const farthest = voters.length > 0 && voters.every((r) => r.edged) ? Math.max(width * GUESSED_MARGIN, width * (1 - NARROWEST_MEASURE) - left) : width * GUESSED_MARGIN;
385
570
  return {
386
571
  ...section,
387
572
  margins: {
388
- left: clamp(Math.min(...lefts), width),
389
- right: clamp(median(rights) - width * SLACK, width),
573
+ left,
574
+ right: pt(Math.max(0, Math.min(median((voters.length > 0 ? voters : rights).map((r) => r.value)) - width * SLACK, farthest))),
390
575
  top: clamp(Math.min(...tops), height, DEEPEST_MARGIN),
391
- bottom: clamp(Math.min(...bottoms) - height * SLACK, height, DEEPEST_MARGIN)
576
+ bottom: clamp(Math.min(...bottoms, floor) - height * SLACK, height, DEEPEST_MARGIN),
577
+ ...inked.length > 0 ? { footer: clamp(footBottom, height, DEEPEST_MARGIN) } : {}
392
578
  }
393
579
  };
394
580
  }
@@ -399,7 +585,7 @@ function withMeasuredMargins(section, shown, pageRuns, pageMarks = []) {
399
585
  * both reconstruction paths (the tagged fast-path EP3 and the heuristic layout
400
586
  * path EP4).
401
587
  */
402
- function buildFlowDoc(body, resources = new ResourceStore(), section, embeddedFonts, sections = [], headersFooters) {
588
+ function buildFlowDoc(body, resources = new ResourceStore(), section, embeddedFonts, sections = [], headersFooters, faceFamilies) {
403
589
  return {
404
590
  kind: "flow",
405
591
  body: resolveBodyStyles([...body], EMPTY_STYLE_SHEET),
@@ -407,9 +593,10 @@ function buildFlowDoc(body, resources = new ResourceStore(), section, embeddedFo
407
593
  ...headersFooters && headersFooters.size > 0 ? { headersFooters } : {},
408
594
  ...section ? { section } : {},
409
595
  ...embeddedFonts && embeddedFonts.size > 0 ? { embeddedFonts } : {},
596
+ ...faceFamilies && faceFamilies.size > 0 ? { faceFamilies } : {},
410
597
  styles: EMPTY_STYLE_SHEET,
411
598
  resources
412
599
  };
413
600
  }
414
601
  //#endregion
415
- export { buildFlowDoc, dedupeLosses, imageBlock, paragraphBlock, paragraphFromRuns, positionedText, sectionFromPdfPages, shapeBlock, withMeasuredMargins };
602
+ export { BASELINE_AT, CARRIER_LINE_PT, GUESSED_MARGIN, NATURAL_LINE_EM, buildFlowDoc, dedupeLosses, imageBlock, paragraphBlock, paragraphFromRuns, positionedText, sectionFromPdfPages, shapeBlock, spaceAfter, textSizeOf, tooSmallToRead, withMeasuredMargins };
@@ -1,6 +1,7 @@
1
1
  import { PdfDict } from '../pdf/objects.js';
2
2
  import { ContentFont } from './content.js';
3
- import { PdfFile } from './document.js';
3
+ import { PdfFile, PdfPage } from './document.js';
4
+ import { FaceFamily } from '../core/ir/flow.js';
4
5
  /**
5
6
  * Build a {@link ContentFont} (the interpreter's decode + advance hooks) from a
6
7
  * `/Font` dictionary (E-PDF EP2). Unicode comes from the `/ToUnicode` CMap;
@@ -14,3 +15,28 @@ import { PdfFile } from './document.js';
14
15
  * @returns The decode/advance hooks plus the code width (1 or 2 bytes per code).
15
16
  */
16
17
  export declare function buildContentFont(file: PdfFile, fontDict: PdfDict): ContentFont;
18
+ /**
19
+ * For every face a run may name, the family a word processor knows it by —
20
+ * keyed by the name the run carries (see {@link FaceFamily}).
21
+ *
22
+ * A PDF names the FACE and a .docx names a family: written as the face,
23
+ * `inter-semibold` was a font no reader has, and LibreOffice set the whole of
24
+ * an invoice drawn in Inter in its default serif. The family is the one the
25
+ * descriptor states (§9.8.1 `/FontFamily`) or, where it states none, the one
26
+ * the PostScript name is made of.
27
+ *
28
+ * @param file The document.
29
+ * @param pages The pages whose faces are wanted.
30
+ * @returns Run font name → its family.
31
+ */
32
+ export declare function collectFaceFamilies(file: PdfFile, pages: ReadonlyArray<PdfPage>): Map<string, FaceFamily>;
33
+ /**
34
+ * The family a PostScript face name is made of: `Inter-SemiBold` → `Inter`,
35
+ * `ArialMT` → `Arial`, `TimesNewRomanPS-BoldMT` → `Times New Roman`.
36
+ *
37
+ * §9.6.2.1 names a face `Family-Style` (Word writes `Family,Style`), with the
38
+ * family's words run together. What follows the separator is a style only if
39
+ * it is made of style words — `MS-Mincho` is a family of its own — and the
40
+ * words come apart where the capitals say they do.
41
+ */
42
+ export declare function familyOfFace(baseFont: string): string;