reamkit 1.23.0 → 1.25.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 (102) hide show
  1. package/README.md +22 -11
  2. package/dist/esm/core/bmp.d.ts +33 -0
  3. package/dist/esm/core/bmp.js +276 -0
  4. package/dist/esm/core/converter/facade.d.ts +3 -3
  5. package/dist/esm/core/converter/facade.js +12 -0
  6. package/dist/esm/core/converter/ream.d.ts +48 -4
  7. package/dist/esm/core/converter/ream.js +25 -4
  8. package/dist/esm/core/document-model/index.d.ts +1 -1
  9. package/dist/esm/core/document-model/types.d.ts +54 -0
  10. package/dist/esm/core/drawingml/chart-geometry.js +3 -2
  11. package/dist/esm/core/drawingml/diagram/colors.d.ts +52 -0
  12. package/dist/esm/core/drawingml/diagram/colors.js +125 -0
  13. package/dist/esm/core/drawingml/diagram/data-model.d.ts +47 -0
  14. package/dist/esm/core/drawingml/diagram/data-model.js +119 -0
  15. package/dist/esm/core/drawingml/diagram/layout-engine.d.ts +46 -0
  16. package/dist/esm/core/drawingml/diagram/layout-engine.js +948 -0
  17. package/dist/esm/core/drawingml/diagram/run.d.ts +37 -0
  18. package/dist/esm/core/drawingml/diagram/run.js +50 -0
  19. package/dist/esm/core/drawingml/diagram/to-drawing.d.ts +16 -0
  20. package/dist/esm/core/drawingml/diagram/to-drawing.js +86 -0
  21. package/dist/esm/core/drawingml/preset-geometry.js +116 -6
  22. package/dist/esm/core/drawingml/text-warp.d.ts +58 -0
  23. package/dist/esm/core/drawingml/text-warp.js +355 -0
  24. package/dist/esm/core/drawingml/theme-parser.js +9 -3
  25. package/dist/esm/core/font/measure.d.ts +12 -0
  26. package/dist/esm/core/font/measure.js +36 -0
  27. package/dist/esm/core/images.d.ts +30 -2
  28. package/dist/esm/core/images.js +121 -5
  29. package/dist/esm/core/metafile/emf.js +144 -16
  30. package/dist/esm/core/metafile/picture.d.ts +49 -0
  31. package/dist/esm/core/metafile/picture.js +70 -1
  32. package/dist/esm/core/metafile/wmf.js +53 -4
  33. package/dist/esm/core/ole/escher-blip.js +11 -1
  34. package/dist/esm/core/outline.d.ts +17 -0
  35. package/dist/esm/core/outline.js +30 -0
  36. package/dist/esm/core/style-cascade/resolver.js +1 -0
  37. package/dist/esm/core/style-cascade/types.d.ts +3 -1
  38. package/dist/esm/excel/print-model.js +2 -2
  39. package/dist/esm/excel/sheet-drawing.js +1 -1
  40. package/dist/esm/excel/sheet-to-flow.d.ts +10 -0
  41. package/dist/esm/excel/sheet-to-flow.js +30 -3
  42. package/dist/esm/html/html-writer.js +5 -3
  43. package/dist/esm/index.d.ts +3 -0
  44. package/dist/esm/index.js +2 -1
  45. package/dist/esm/layout/page-doc.d.ts +35 -0
  46. package/dist/esm/layout/page-doc.js +14 -1
  47. package/dist/esm/layout/styled-layout.js +217 -30
  48. package/dist/esm/markdown/markdown-writer.d.ts +41 -0
  49. package/dist/esm/markdown/markdown-writer.js +733 -0
  50. package/dist/esm/pdf/shading.js +8 -6
  51. package/dist/esm/pdf/styled-page-emitter.js +62 -4
  52. package/dist/esm/pdf/vector-graphics.js +1 -1
  53. package/dist/esm/pdf-reader/annots.d.ts +24 -0
  54. package/dist/esm/pdf-reader/annots.js +126 -0
  55. package/dist/esm/pdf-reader/content.d.ts +131 -5
  56. package/dist/esm/pdf-reader/content.js +169 -12
  57. package/dist/esm/pdf-reader/display.d.ts +56 -0
  58. package/dist/esm/pdf-reader/display.js +162 -0
  59. package/dist/esm/pdf-reader/document.d.ts +36 -1
  60. package/dist/esm/pdf-reader/document.js +92 -25
  61. package/dist/esm/pdf-reader/embedded-fonts.d.ts +31 -0
  62. package/dist/esm/pdf-reader/embedded-fonts.js +94 -0
  63. package/dist/esm/pdf-reader/flow-build.d.ts +61 -6
  64. package/dist/esm/pdf-reader/flow-build.js +128 -22
  65. package/dist/esm/pdf-reader/font.js +185 -4
  66. package/dist/esm/pdf-reader/image-decode.js +56 -5
  67. package/dist/esm/pdf-reader/images.d.ts +6 -0
  68. package/dist/esm/pdf-reader/images.js +25 -5
  69. package/dist/esm/pdf-reader/jpeg.d.ts +18 -0
  70. package/dist/esm/pdf-reader/jpeg.js +419 -0
  71. package/dist/esm/pdf-reader/layout.d.ts +1 -1
  72. package/dist/esm/pdf-reader/layout.js +221 -32
  73. package/dist/esm/pdf-reader/pattern-tint.d.ts +17 -0
  74. package/dist/esm/pdf-reader/pattern-tint.js +181 -0
  75. package/dist/esm/pdf-reader/reader.d.ts +9 -1
  76. package/dist/esm/pdf-reader/reader.js +22 -6
  77. package/dist/esm/pdf-reader/shading.d.ts +14 -0
  78. package/dist/esm/pdf-reader/shading.js +27 -1
  79. package/dist/esm/pdf-reader/tagged.js +156 -17
  80. package/dist/esm/pdf-reader/text.d.ts +13 -1
  81. package/dist/esm/pdf-reader/text.js +70 -3
  82. package/dist/esm/pdf-reader/vector.d.ts +25 -1
  83. package/dist/esm/pdf-reader/vector.js +168 -12
  84. package/dist/esm/pptx/ppt/ppt-reader.js +95 -27
  85. package/dist/esm/pptx/ppt/ppt-text.d.ts +41 -0
  86. package/dist/esm/pptx/ppt/ppt-text.js +327 -37
  87. package/dist/esm/pptx/pptx-reader.js +90 -10
  88. package/dist/esm/pptx/slide-parser.d.ts +4 -1
  89. package/dist/esm/pptx/slide-parser.js +104 -13
  90. package/dist/esm/pptx/sp-helpers.d.ts +7 -3
  91. package/dist/esm/pptx/sp-helpers.js +45 -7
  92. package/dist/esm/pptx/table-style.d.ts +7 -0
  93. package/dist/esm/pptx/table-style.js +59 -7
  94. package/dist/esm/svg/svg-writer.js +1 -0
  95. package/dist/esm/word/document-parser.d.ts +4 -1
  96. package/dist/esm/word/document-parser.js +4 -1
  97. package/dist/esm/word/docx-reader.js +26 -13
  98. package/dist/esm/word/docx-writer.js +15 -1
  99. package/dist/esm/word/drawing-parser.d.ts +20 -0
  100. package/dist/esm/word/drawing-parser.js +44 -5
  101. package/dist/esm/word/table-parser.js +3 -0
  102. package/package.json +1 -1
@@ -284,14 +284,16 @@ function normalizeStops(stops) {
284
284
  colorHex: "000000",
285
285
  alpha: 1
286
286
  }];
287
- out[0] = {
288
- ...out[0],
287
+ const first = out[0];
288
+ if (first.offset > 0) out.unshift({
289
+ ...first,
289
290
  offset: 0
290
- };
291
- out[out.length - 1] = {
292
- ...out[out.length - 1],
291
+ });
292
+ const last = out[out.length - 1];
293
+ if (last.offset < 1) out.push({
294
+ ...last,
293
295
  offset: 1
294
- };
296
+ });
295
297
  return out;
296
298
  }
297
299
  function rgb01(hex) {
@@ -7,6 +7,7 @@ import { GLUE_SHRINK_RATIO } from "../layout/styled-layout.js";
7
7
  import { paintPlan } from "../layout/page-doc.js";
8
8
  import { emitClipPath, emitVectorShape, shadowBlurLayers } from "./vector-graphics.js";
9
9
  import { buildGradientAlphaMask, buildGradientPattern, shapeBbox } from "./shading.js";
10
+ import { warpGlyphMatrix } from "../core/drawingml/text-warp.js";
10
11
  import { sanitizeHref } from "../core/links.js";
11
12
  import { embedAssociatedFile } from "./embedded-file.js";
12
13
  import { buildSrgbIccProfile } from "./icc-profile.js";
@@ -111,7 +112,11 @@ function assembleStyledPdf(laid, options, doc) {
111
112
  if (!pdfaProfile) for (const page of renderedPages) {
112
113
  const ph = page.height;
113
114
  const plan = paintPlan(page.commands);
114
- const shapesNeedingPatterns = [...plan.shapes, ...[...plan.behind, ...plan.pictures.flat()].filter((i) => i.type === "shape")];
115
+ const shapesNeedingPatterns = [...plan.shapes, ...[
116
+ ...plan.behind,
117
+ ...plan.pictures.flat(),
118
+ ...plan.ordered.flat()
119
+ ].filter((i) => i.type === "shape")];
115
120
  for (const item of shapesNeedingPatterns) {
116
121
  const gradient = item.shape.fillGradient;
117
122
  if (!gradient) continue;
@@ -211,7 +216,11 @@ function assembleStyledPdf(laid, options, doc) {
211
216
  wantAlpha(img.alpha);
212
217
  wantDuotone(img, page.height);
213
218
  }
214
- for (const item of [...plan.behind, ...plan.pictures.flat()]) {
219
+ for (const item of [
220
+ ...plan.behind,
221
+ ...plan.pictures.flat(),
222
+ ...plan.ordered.flat()
223
+ ]) {
215
224
  if (item.type === "image") {
216
225
  wantAlpha(washVeil(item.wash)?.alpha);
217
226
  wantAlpha(item.alpha);
@@ -644,6 +653,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
644
653
  let lastSize = -1;
645
654
  let lastColor = "";
646
655
  let lastFauxWidth = 0;
656
+ let lastStroke;
647
657
  let lastTz = 100;
648
658
  const switchFontIfNeeded = (tok) => {
649
659
  const fontKey = tok.font.resourceName;
@@ -652,11 +662,20 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
652
662
  lastFont = fontKey;
653
663
  lastSize = tok.fontSizePt;
654
664
  }
655
- const fauxWidth = tok.synthetic?.bold === true ? tok.fontSizePt * .03 : 0;
665
+ const outline = tok.resolvedRun.textOutline;
666
+ const fauxWidth = outline !== void 0 ? outline.widthPt : tok.synthetic?.bold === true ? tok.fontSizePt * .03 : 0;
656
667
  if (fauxWidth !== lastFauxWidth) {
657
668
  out.push(fauxWidth > 0 ? `2 Tr ${formatNumber(fauxWidth)} w` : "0 Tr");
658
669
  lastFauxWidth = fauxWidth;
659
670
  }
671
+ const strokeHex = outline?.colorHex;
672
+ if (strokeHex !== lastStroke) {
673
+ if (strokeHex !== void 0) {
674
+ const [sr, sg, sb] = hexToRgb01(strokeHex);
675
+ out.push(`${formatNumber(sr)} ${formatNumber(sg)} ${formatNumber(sb)} RG`);
676
+ }
677
+ lastStroke = strokeHex;
678
+ }
660
679
  const tz = Math.round((tok.synthetic?.widthScale ?? 1) * 100);
661
680
  if (tz !== lastTz) {
662
681
  out.push(`${formatNumber(tz)} Tz`);
@@ -713,6 +732,8 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
713
732
  }
714
733
  lastFont = "";
715
734
  lastFauxWidth = 0;
735
+ lastStroke = void 0;
736
+ lastStroke = void 0;
716
737
  lastTz = 100;
717
738
  lastSize = -1;
718
739
  lastColor = "";
@@ -761,6 +782,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
761
782
  }
762
783
  lastFont = "";
763
784
  lastFauxWidth = 0;
785
+ lastStroke = void 0;
764
786
  lastTz = 100;
765
787
  lastSize = -1;
766
788
  lastColor = "";
@@ -795,6 +817,8 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
795
817
  out.push("Q");
796
818
  lastFont = "";
797
819
  lastFauxWidth = 0;
820
+ lastStroke = void 0;
821
+ lastStroke = void 0;
798
822
  lastTz = 100;
799
823
  lastSize = -1;
800
824
  lastColor = "";
@@ -822,6 +846,8 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
822
846
  for (const op of emitVectorShape(shape)) out.push(op);
823
847
  lastFont = "";
824
848
  lastFauxWidth = 0;
849
+ lastStroke = void 0;
850
+ lastStroke = void 0;
825
851
  lastTz = 100;
826
852
  lastSize = -1;
827
853
  lastColor = "";
@@ -841,6 +867,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
841
867
  out.push(`${formatNumber(clip.x)} ${formatNumber(H - clip.y - clip.height)} ${formatNumber(clip.width)} ${formatNumber(clip.height)} re W n`);
842
868
  lastFont = "";
843
869
  lastFauxWidth = 0;
870
+ lastStroke = void 0;
844
871
  lastTz = 100;
845
872
  lastSize = -1;
846
873
  lastColor = "";
@@ -855,6 +882,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
855
882
  out.push("Q");
856
883
  lastFont = "";
857
884
  lastFauxWidth = 0;
885
+ lastStroke = void 0;
858
886
  lastTz = 100;
859
887
  lastSize = -1;
860
888
  lastColor = "";
@@ -1009,6 +1037,34 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
1009
1037
  }
1010
1038
  const hasRise = line.tokens.some((t) => t.kind === "text" && t.risePt !== void 0);
1011
1039
  const hasFauxItalic = line.tokens.some((t) => t.kind === "text" && t.synthetic?.italic === true);
1040
+ const warp = cmd.warp;
1041
+ if (warp) {
1042
+ if (!inBT) {
1043
+ out.push("BT");
1044
+ inBT = true;
1045
+ }
1046
+ let cursor = originX;
1047
+ for (const tok of line.tokens) {
1048
+ if (tok.kind !== "text" || tok.isSpace) {
1049
+ cursor += tok.widthPt;
1050
+ continue;
1051
+ }
1052
+ switchFontIfNeeded(tok);
1053
+ const chars = [...tok.text];
1054
+ const raw = chars.map((c) => tok.font.measure.textWidthPt(c, tok.fontSizePt));
1055
+ const total = raw.reduce((a, b) => a + b, 0);
1056
+ const k = total > 0 ? tok.widthPt / total : 1;
1057
+ for (let i = 0; i < chars.length; i++) {
1058
+ const adv = raw[i] * k;
1059
+ const m = warpGlyphMatrix(warp, cursor, adv, cmd.baselineY);
1060
+ cursor += adv;
1061
+ if (!m) continue;
1062
+ out.push(`${formatNumber(m[0])} ${formatNumber(-m[1])} ${formatNumber(m[2])} ${formatNumber(-m[3])} ${formatNumber(m[4])} ${formatNumber(H - m[5])} Tm`);
1063
+ out.push(tok.font.measure.showText(chars[i]));
1064
+ }
1065
+ }
1066
+ return;
1067
+ }
1012
1068
  if (rotationDeg !== 0 && !hasImageToken && !hasMathToken) {
1013
1069
  if (!inBT) {
1014
1070
  out.push("BT");
@@ -1104,6 +1160,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
1104
1160
  if (item.type === "shape") {
1105
1161
  const t = item.shape.transform;
1106
1162
  const fillAlpha = item.shape.fillAlpha;
1163
+ const layer = item.shape.shadow ? shadowBlurLayers(item.shape.shadow) : void 0;
1107
1164
  for (const op of emitVectorShape({
1108
1165
  ...item.shape,
1109
1166
  transform: [
@@ -1114,7 +1171,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
1114
1171
  t[4],
1115
1172
  H - t[5]
1116
1173
  ]
1117
- }, gradientNames?.get(item.shape), void 0, gradientMaskNames?.get(item.shape) ?? (fillAlpha !== void 0 && fillAlpha < 1 ? alphaStateNames?.get(Math.round(fillAlpha * 1e3) / 1e3) : void 0))) out.push(op);
1174
+ }, gradientNames?.get(item.shape), layer && layer.alpha < 1 ? alphaStateNames?.get(Math.round(layer.alpha * 1e3) / 1e3) : void 0, gradientMaskNames?.get(item.shape) ?? (fillAlpha !== void 0 && fillAlpha < 1 ? alphaStateNames?.get(Math.round(fillAlpha * 1e3) / 1e3) : void 0))) out.push(op);
1118
1175
  return;
1119
1176
  }
1120
1177
  if (item.type === "image") {
@@ -1287,6 +1344,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
1287
1344
  for (const item of run) emitInOrder(item);
1288
1345
  if (tagging) out.push("EMC");
1289
1346
  }
1347
+ for (const run of plan.ordered) for (const item of run) emitInOrder(item);
1290
1348
  emitLinesPass();
1291
1349
  return {
1292
1350
  content: encoder.encode(out.join("\n")),
@@ -31,7 +31,7 @@ function shadowBlurLayers(shadow) {
31
31
  const count = Math.min(8, Math.max(2, Math.round(shadow.blurPt / 2)));
32
32
  return {
33
33
  count,
34
- alpha: shadow.alpha / count
34
+ alpha: 1 - Math.pow(1 - shadow.alpha, 1 / count)
35
35
  };
36
36
  }
37
37
  function shadowBbox(shape) {
@@ -0,0 +1,24 @@
1
+ import { Matrix } from './content.js';
2
+ import { PdfDict, PdfStream } from '../pdf/objects.js';
3
+ import { PdfFile, PdfPage } from './document.js';
4
+ /** One annotation's normal appearance, ready to interpret in page space. */
5
+ export interface Appearance {
6
+ readonly stream: PdfStream;
7
+ /** Maps the appearance's own space onto the page (§12.5.5). */
8
+ readonly ctm: Matrix;
9
+ /** The appearance's `/Resources`, when it states its own. */
10
+ readonly resources: PdfDict | undefined;
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
+ export declare function collectPageAppearances(file: PdfFile, page: PdfPage): Array<Appearance>;
@@ -0,0 +1,126 @@
1
+ import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
2
+ import { IDENTITY, multiply } from "./content.js";
3
+ //#region src/pdf-reader/annots.ts
4
+ /** §12.5.3 `/F` — the annotation is not painted at all. */
5
+ var FLAG_HIDDEN = 2;
6
+ var FLAG_NOVIEW = 32;
7
+ /**
8
+ * Every annotation appearance the page shows, in `/Annots` order — which is
9
+ * the order they paint in, over the page's own content.
10
+ *
11
+ * A `Popup` is a note's window and is never part of the page. An annotation
12
+ * 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
+ *
15
+ * @param file The owning file, for resolving references.
16
+ * @param page The page whose annotations are wanted.
17
+ * @returns The appearances, each with the matrix that places it on the page.
18
+ */
19
+ function collectPageAppearances(file, page) {
20
+ const annots = file.get(page.dict, "Annots");
21
+ if (!Array.isArray(annots)) return [];
22
+ const out = [];
23
+ for (const entry of annots) {
24
+ const annot = file.resolve(entry);
25
+ if (!(annot instanceof Map)) continue;
26
+ const subtype = file.get(annot, "Subtype");
27
+ if (subtype instanceof PdfName && subtype.value === "Popup") continue;
28
+ const flags = file.get(annot, "F");
29
+ if (typeof flags === "number" && (flags & FLAG_HIDDEN || flags & FLAG_NOVIEW)) continue;
30
+ const stream = normalAppearance(file, annot);
31
+ if (!stream) continue;
32
+ const rect = rectangle(file.get(annot, "Rect"));
33
+ if (!rect) continue;
34
+ const matrix = matrixOf(file, stream.dict);
35
+ const bbox = rectangle(file.get(stream.dict, "BBox"));
36
+ const resources = file.get(stream.dict, "Resources");
37
+ out.push({
38
+ stream,
39
+ ctm: multiply(matrix, fitToRect(bbox, matrix, rect)),
40
+ resources: resources instanceof Map ? resources : void 0
41
+ });
42
+ }
43
+ return out;
44
+ }
45
+ /** §12.5.5 `/AP` `/N` — the normal appearance, through `/AS` when it is a set. */
46
+ function normalAppearance(file, annot) {
47
+ const ap = file.get(annot, "AP");
48
+ if (!(ap instanceof Map)) return void 0;
49
+ const normal = file.get(ap, "N");
50
+ if (normal instanceof PdfStream) return normal;
51
+ if (!(normal instanceof Map)) return void 0;
52
+ 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;
55
+ const only = [...normal.values()].map((v) => file.resolve(v)).filter((v) => v instanceof PdfStream);
56
+ return only.length === 1 ? only[0] : void 0;
57
+ }
58
+ /**
59
+ * §12.5.5 — the matrix taking the form's `/Matrix`-transformed `/BBox` onto the
60
+ * annotation's `/Rect`: the two are fitted corner to corner, so an appearance
61
+ * authored at any size lands exactly in the rectangle that owns it.
62
+ */
63
+ function fitToRect(bbox, matrix, rect) {
64
+ if (!bbox) return [
65
+ 1,
66
+ 0,
67
+ 0,
68
+ 1,
69
+ rect[0],
70
+ rect[1]
71
+ ];
72
+ const xs = [];
73
+ const ys = [];
74
+ for (const [x, y] of [
75
+ [bbox[0], bbox[1]],
76
+ [bbox[2], bbox[1]],
77
+ [bbox[2], bbox[3]],
78
+ [bbox[0], bbox[3]]
79
+ ]) {
80
+ xs.push(matrix[0] * x + matrix[2] * y + matrix[4]);
81
+ ys.push(matrix[1] * x + matrix[3] * y + matrix[5]);
82
+ }
83
+ const bw = Math.max(...xs) - Math.min(...xs);
84
+ const bh = Math.max(...ys) - Math.min(...ys);
85
+ const sx = bw > 0 ? (rect[2] - rect[0]) / bw : 1;
86
+ const sy = bh > 0 ? (rect[3] - rect[1]) / bh : 1;
87
+ return [
88
+ sx,
89
+ 0,
90
+ 0,
91
+ sy,
92
+ rect[0] - Math.min(...xs) * sx,
93
+ rect[1] - Math.min(...ys) * sy
94
+ ];
95
+ }
96
+ /** §8.10.2 `/Matrix`, or the identity when the form states none. */
97
+ function matrixOf(file, dict) {
98
+ const m = file.resolve(dict.get("Matrix") ?? PDF_NULL);
99
+ if (!Array.isArray(m) || m.length !== 6) return IDENTITY;
100
+ const n = m.map((v) => {
101
+ const r = file.resolve(v);
102
+ return typeof r === "number" ? r : 0;
103
+ });
104
+ return [
105
+ n[0],
106
+ n[1],
107
+ n[2],
108
+ n[3],
109
+ n[4],
110
+ n[5]
111
+ ];
112
+ }
113
+ /** A four-number array as an ordered rectangle, or `undefined`. */
114
+ function rectangle(v) {
115
+ if (!Array.isArray(v) || v.length < 4) return void 0;
116
+ const n = v.slice(0, 4).map((x) => typeof x === "number" ? x : NaN);
117
+ if (n.some((x) => !Number.isFinite(x))) return void 0;
118
+ return [
119
+ Math.min(n[0], n[2]),
120
+ Math.min(n[1], n[3]),
121
+ Math.max(n[0], n[2]),
122
+ Math.max(n[1], n[3])
123
+ ];
124
+ }
125
+ //#endregion
126
+ export { collectPageAppearances };
@@ -1,4 +1,5 @@
1
1
  import { ShapeGradient } from '../core/vector.js';
2
+ import { PdfDict, PdfStream } from '../pdf/objects.js';
2
3
  /**
3
4
  * A page font as the interpreter needs it (built from the font dictionaries in
4
5
  * EP2b): how wide each code is, and how a run of codes decodes to Unicode. An
@@ -12,6 +13,27 @@ export interface ContentFont {
12
13
  decode: (codes: ReadonlyArray<number>) => string;
13
14
  /** Glyph advance for one code, in 1000-unit text space. */
14
15
  width: (code: number) => number;
16
+ /** §9.6.2 — the face's own `/BaseFont` name, for a document that embeds it. */
17
+ readonly name?: string;
18
+ /** §9.8.1 — the face is a bold one (weight, the ForceBold flag, or its name). */
19
+ readonly bold?: boolean;
20
+ /** §9.8.1 — the face is slanted (`/ItalicAngle`, the Italic flag, or its name). */
21
+ readonly italic?: boolean;
22
+ /**
23
+ * §9.6.5 — a Type 3 face, whose glyphs are content streams rather than
24
+ * outlines. What such a font draws is not type at all: it is whatever the
25
+ * procedure paints, in the resources the font states.
26
+ */
27
+ readonly type3?: Type3Face;
28
+ }
29
+ /** §9.6.5 — the parts of a Type 3 font a caller needs to run its glyphs. */
30
+ export interface Type3Face {
31
+ /** `/FontMatrix` — glyph space to text space. */
32
+ readonly matrix: Matrix;
33
+ /** `/Encoding` + `/CharProcs` — the content stream one code draws. */
34
+ readonly proc: (code: number) => PdfStream | undefined;
35
+ /** `/Resources` the procedures draw with, when the font states its own. */
36
+ readonly resources: PdfDict | undefined;
15
37
  }
16
38
  /**
17
39
  * One positioned text run emitted by a show operator: its decoded text, the
@@ -24,8 +46,63 @@ export interface TextRun {
24
46
  readonly x: number;
25
47
  /** Glyph origin y in page space (points). */
26
48
  readonly y: number;
49
+ /**
50
+ * Where the pen stood after the last glyph, in page space (§9.4.4). The
51
+ * interpreter advances the text matrix by the font's own widths, so this is
52
+ * a measurement, not the half-em-per-character guess the reader used to make
53
+ * — and the difference between a word space and a table column is exactly
54
+ * the kind of thing a guess gets wrong.
55
+ */
56
+ readonly endX: number;
57
+ /** The pen's y after the last glyph — with {@link endX}, the whole advance. */
58
+ readonly endY: number;
59
+ /**
60
+ * The baseline's direction in page space, degrees counter-clockwise from
61
+ * left-to-right (§9.4.2 — the text matrix may turn as well as move). Absent
62
+ * for ordinary upright text, which is nearly all of it.
63
+ */
64
+ readonly angleDeg?: number;
27
65
  readonly fontSizePt: number;
28
66
  readonly fontKey: string;
67
+ /**
68
+ * §9.6.2 `/BaseFont` — the face's own name, subset prefix dropped and
69
+ * lowercased, or absent when the font states none. This is what a rebuilt run
70
+ * asks for, so a page whose faces the file EMBEDS is re-set in them rather
71
+ * than in a substitute (see `./embedded-fonts`).
72
+ */
73
+ readonly fontName?: string;
74
+ /** §9.8.1 — the face the glyphs were shown in is a bold one. */
75
+ readonly bold?: boolean;
76
+ /**
77
+ * §8.6.6.2 — the glyphs are filled with a tiling PATTERN, named here for the
78
+ * caller to resolve: a pattern is a content stream, not a colour, and the
79
+ * fill colour still standing from before is not what the page shows.
80
+ */
81
+ readonly fillPatternName?: string;
82
+ /**
83
+ * §9.3.6 — the page painted these glyphs NOWHERE: mode 3 shows nothing and
84
+ * mode 7 only adds to the clip. A scanned page carries its recognised words
85
+ * that way, under the picture of the page — so the run is kept, because it
86
+ * is the only text such a document has, and a reader reproducing the page
87
+ * leaves it to the picture.
88
+ */
89
+ readonly invisible?: boolean;
90
+ /**
91
+ * §9.3.6 — the colour the glyphs are STROKED in, when the rendering mode
92
+ * asks for a stroke, and how wide the pen is.
93
+ */
94
+ readonly outlineHex?: string;
95
+ readonly outlineWidthPt?: number;
96
+ /** §9.8.1 — the face the glyphs were shown in is a slanted one. */
97
+ readonly italic?: boolean;
98
+ /**
99
+ * §9.6.5 — the face is a Type 3 one, so what the page SHOWS here is the
100
+ * glyph procedures, not type. The run is kept for its words; a reader that
101
+ * reproduces the page draws the procedures instead of re-setting it.
102
+ */
103
+ readonly type3?: boolean;
104
+ /** §8.6.8 — the non-stroking colour the glyphs were painted in (6-hex). */
105
+ readonly colorHex: string;
29
106
  /**
30
107
  * The marked-content id of the enclosing `BDC` sequence (§14.6), if any — the
31
108
  * link from this text to the structure element that owns it (E-PDF EP3).
@@ -43,6 +120,8 @@ export interface TextRun {
43
120
  * `mcid` links the paint to its structure element (a `/Figure`, E-PDF EP6).
44
121
  */
45
122
  export interface ImagePlacement {
123
+ /** Where the `Do` fell in the stream's painting order — see {@link VectorPlacement.order}. */
124
+ readonly order: number;
46
125
  /** XObject resource name (no leading slash). */
47
126
  readonly name: string;
48
127
  readonly ctm: Matrix;
@@ -72,17 +151,38 @@ export type PathSeg = {
72
151
  readonly op: 'close';
73
152
  };
74
153
  /**
75
- * A painted path emitted by the path-painting operators (§8.5.3), captured in
76
- * page space (y-up). The optional fields record only what the paint mode set:
77
- * a fill colour/gradient (EP10/EP16c) and/or a stroke colour + width (EP11),
78
- * plus the enclosing structure id.
154
+ * §8.5.4 — the clipping region in force when a path was painted: the path that
155
+ * `W`/`W*` installed, plus its page-space bounding box.
79
156
  */
157
+ export interface ClipRegion {
158
+ readonly segs: ReadonlyArray<PathSeg>;
159
+ readonly minX: number;
160
+ readonly minY: number;
161
+ readonly maxX: number;
162
+ readonly maxY: number;
163
+ }
80
164
  export interface VectorPlacement {
165
+ /**
166
+ * Where this fell in the stream's painting order (§8.5.3): later covers
167
+ * earlier, and a `Do` of a form is numbered here too, so a caller walking
168
+ * into that form knows exactly where its marks belong among these.
169
+ */
170
+ readonly order: number;
81
171
  readonly segs: ReadonlyArray<PathSeg>;
172
+ /** §8.5.4 — the clip in force when it was painted, when there was one. */
173
+ readonly clip?: ClipRegion;
82
174
  /** Fill colour (6-hex), present iff the path is filled (`f` / `F` / `f*` / `B` / `b`). */
83
175
  readonly fillHex?: string;
84
176
  /** Shading pattern, present iff filled with one (EP16c). */
85
177
  readonly gradient?: ShapeGradient;
178
+ /** §11.6.4.4 `/ca` — how opaque the fill is, when the page asked for less. */
179
+ readonly alpha?: number;
180
+ /**
181
+ * §8.7.3 — the TILING pattern resource name the path is filled with. Its
182
+ * content is a stream of its own, so what the fill actually shows is only
183
+ * known by walking into it; the `fillHex` beside this is not the fill.
184
+ */
185
+ readonly patternName?: string;
86
186
  /** Stroke colour (6-hex), present iff the path is stroked (`S` / `s` / `B` / `b`) — EP11. */
87
187
  readonly strokeHex?: string;
88
188
  /** Stroke width in page-space points — EP11. */
@@ -94,6 +194,19 @@ export interface InterpretResult {
94
194
  readonly texts: Array<TextRun>;
95
195
  readonly images: Array<ImagePlacement>;
96
196
  readonly vectors: Array<VectorPlacement>;
197
+ /** §9.6.5 — every Type 3 glyph the stream showed, with where to run it. */
198
+ readonly glyphs: Array<Type3Call>;
199
+ }
200
+ /**
201
+ * §9.6.5 — one showing of a Type 3 glyph: which procedure, and the matrix that
202
+ * puts glyph space on the page.
203
+ */
204
+ export interface Type3Call {
205
+ readonly stream: PdfStream;
206
+ readonly resources: PdfDict | undefined;
207
+ readonly ctm: Matrix;
208
+ /** §8.5.3 — its place in the stream's painting order, as a form call has. */
209
+ readonly order: number;
97
210
  }
98
211
  /**
99
212
  * 2D affine matrix `[a b c d e f]`, row-vector convention (`[x y 1] · M`):
@@ -120,6 +233,19 @@ export declare function multiply(a: Matrix, b: Matrix): Matrix;
120
233
  * an unmapped key falls back to Latin-1 with a half-em advance.
121
234
  * @param initialCtm The starting CTM mapping user space to page space.
122
235
  * @param shadings Shading patterns by name, selected by `scn`/`sc` (EP16c).
236
+ * @param alphas Constant fill alphas by `/ExtGState` name, selected by `gs`.
123
237
  * @returns The extracted text runs, image placements and vector paths.
124
238
  */
125
- export declare function interpretContent(bytes: Uint8Array, fonts: ReadonlyMap<string, ContentFont>, initialCtm?: Matrix, shadings?: ReadonlyMap<string, ShapeGradient>): InterpretResult;
239
+ export declare function interpretContent(bytes: Uint8Array, fonts: ReadonlyMap<string, ContentFont>, initialCtm?: Matrix, shadings?: ReadonlyMap<string, ShapeGradient>, alphas?: ReadonlyMap<string, number>): InterpretResult;
240
+ /**
241
+ * Whether a string is wholly right-to-left: at least one letter of an RTL
242
+ * script and nothing of any other, spaces and joiners aside.
243
+ *
244
+ * Anything mixed — a number inside an Arabic sentence runs left to right —
245
+ * needs the full bidi algorithm, and guessing at it would be worse than
246
+ * leaving it alone.
247
+ *
248
+ * @param text The string to judge.
249
+ * @returns Whether it is one run of right-to-left script.
250
+ */
251
+ export declare function isRightToLeft(text: string): boolean;