reamkit 1.22.0 → 1.24.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 (101) hide show
  1. package/dist/esm/core/bmp.d.ts +33 -0
  2. package/dist/esm/core/bmp.js +276 -0
  3. package/dist/esm/core/converter/ream.d.ts +16 -0
  4. package/dist/esm/core/converter/ream.js +59 -9
  5. package/dist/esm/core/document-model/types.d.ts +55 -0
  6. package/dist/esm/core/drawingml/chart-geometry.js +14 -7
  7. package/dist/esm/core/drawingml/colors.d.ts +4 -2
  8. package/dist/esm/core/drawingml/colors.js +15 -0
  9. package/dist/esm/core/drawingml/diagram/colors.d.ts +52 -0
  10. package/dist/esm/core/drawingml/diagram/colors.js +125 -0
  11. package/dist/esm/core/drawingml/diagram/data-model.d.ts +47 -0
  12. package/dist/esm/core/drawingml/diagram/data-model.js +119 -0
  13. package/dist/esm/core/drawingml/diagram/layout-engine.d.ts +46 -0
  14. package/dist/esm/core/drawingml/diagram/layout-engine.js +948 -0
  15. package/dist/esm/core/drawingml/diagram/run.d.ts +37 -0
  16. package/dist/esm/core/drawingml/diagram/run.js +50 -0
  17. package/dist/esm/core/drawingml/diagram/to-drawing.d.ts +16 -0
  18. package/dist/esm/core/drawingml/diagram/to-drawing.js +86 -0
  19. package/dist/esm/core/drawingml/preset-geometry.js +116 -6
  20. package/dist/esm/core/drawingml/text-warp.d.ts +58 -0
  21. package/dist/esm/core/drawingml/text-warp.js +355 -0
  22. package/dist/esm/core/drawingml/theme-parser.d.ts +38 -0
  23. package/dist/esm/core/drawingml/theme-parser.js +73 -4
  24. package/dist/esm/core/font/measure.d.ts +12 -0
  25. package/dist/esm/core/font/measure.js +36 -0
  26. package/dist/esm/core/fonts/families.d.ts +10 -0
  27. package/dist/esm/core/fonts/families.js +44 -0
  28. package/dist/esm/core/fonts/index.d.ts +3 -2
  29. package/dist/esm/core/fonts/remote-fonts.d.ts +50 -0
  30. package/dist/esm/core/fonts/remote-fonts.js +114 -8
  31. package/dist/esm/core/fonts/scripts.d.ts +26 -0
  32. package/dist/esm/core/fonts/scripts.js +177 -0
  33. package/dist/esm/core/images.d.ts +30 -2
  34. package/dist/esm/core/images.js +121 -5
  35. package/dist/esm/core/metafile/blit.d.ts +33 -0
  36. package/dist/esm/core/metafile/blit.js +67 -0
  37. package/dist/esm/core/metafile/dib.d.ts +61 -0
  38. package/dist/esm/core/metafile/dib.js +205 -0
  39. package/dist/esm/core/metafile/emf.js +280 -38
  40. package/dist/esm/core/metafile/picture.d.ts +61 -1
  41. package/dist/esm/core/metafile/picture.js +70 -1
  42. package/dist/esm/core/metafile/wmf.js +202 -13
  43. package/dist/esm/core/ole/escher-blip.d.ts +12 -0
  44. package/dist/esm/core/ole/escher-blip.js +98 -0
  45. package/dist/esm/{pdf-reader → core}/png-encode.js +1 -1
  46. package/dist/esm/excel/print-model.js +5 -3
  47. package/dist/esm/excel/sheet-drawing.js +1 -1
  48. package/dist/esm/excel/sheet-to-flow.js +16 -2
  49. package/dist/esm/excel/styles-parser.js +1 -1
  50. package/dist/esm/excel/xls/biff-reader.js +53 -18
  51. package/dist/esm/excel/xls/biff-styles.d.ts +5 -2
  52. package/dist/esm/excel/xls/biff-styles.js +9 -7
  53. package/dist/esm/excel/xls/escher.js +90 -24
  54. package/dist/esm/html/html-writer.js +2 -1
  55. package/dist/esm/layout/page-doc.d.ts +74 -0
  56. package/dist/esm/layout/page-doc.js +14 -1
  57. package/dist/esm/layout/styled-layout.d.ts +2 -2
  58. package/dist/esm/layout/styled-layout.js +475 -48
  59. package/dist/esm/pdf/shading.js +8 -6
  60. package/dist/esm/pdf/styled-page-emitter.js +77 -5
  61. package/dist/esm/pdf/vector-graphics.js +1 -1
  62. package/dist/esm/pdf-reader/image-decode.js +1 -1
  63. package/dist/esm/pptx/embedded-fonts.d.ts +31 -0
  64. package/dist/esm/pptx/embedded-fonts.js +104 -0
  65. package/dist/esm/pptx/placeholder-cascade.d.ts +73 -6
  66. package/dist/esm/pptx/placeholder-cascade.js +114 -18
  67. package/dist/esm/pptx/ppt/ppt-reader.js +191 -13
  68. package/dist/esm/pptx/ppt/ppt-text.d.ts +107 -1
  69. package/dist/esm/pptx/ppt/ppt-text.js +873 -144
  70. package/dist/esm/pptx/pptx-reader.d.ts +8 -0
  71. package/dist/esm/pptx/pptx-reader.js +117 -15
  72. package/dist/esm/pptx/preset-table-styles.d.ts +9 -0
  73. package/dist/esm/pptx/preset-table-styles.js +27 -0
  74. package/dist/esm/pptx/slide-parser.d.ts +11 -2
  75. package/dist/esm/pptx/slide-parser.js +203 -44
  76. package/dist/esm/pptx/sp-helpers.d.ts +9 -4
  77. package/dist/esm/pptx/sp-helpers.js +48 -9
  78. package/dist/esm/pptx/table-style.d.ts +16 -1
  79. package/dist/esm/pptx/table-style.js +104 -10
  80. package/dist/esm/svg/svg-writer.js +1 -0
  81. package/dist/esm/word/document-parser.d.ts +11 -2
  82. package/dist/esm/word/document-parser.js +10 -7
  83. package/dist/esm/word/docx-reader.js +32 -16
  84. package/dist/esm/word/docx-to-pdf.d.ts +8 -7
  85. package/dist/esm/word/docx-to-pdf.js +25 -7
  86. package/dist/esm/word/docx-writer.js +4 -0
  87. package/dist/esm/word/drawing-parser.d.ts +20 -0
  88. package/dist/esm/word/drawing-parser.js +67 -22
  89. package/dist/esm/word/numbering-parser.d.ts +2 -1
  90. package/dist/esm/word/numbering-parser.js +6 -6
  91. package/dist/esm/word/paragraph-properties.d.ts +2 -1
  92. package/dist/esm/word/paragraph-properties.js +2 -2
  93. package/dist/esm/word/run-properties.d.ts +5 -2
  94. package/dist/esm/word/run-properties.js +13 -8
  95. package/dist/esm/word/styles-parser.d.ts +2 -1
  96. package/dist/esm/word/styles-parser.js +12 -12
  97. package/dist/esm/word/table-parser.js +6 -3
  98. package/dist/esm/word/theme-fonts.d.ts +10 -0
  99. package/dist/esm/word/theme-fonts.js +28 -0
  100. package/package.json +1 -1
  101. /package/dist/esm/{pdf-reader → core}/png-encode.d.ts +0 -0
@@ -1,5 +1,7 @@
1
1
  import { PathBuilder } from "../vector.js";
2
- import { applyTransform, cloneDc, colorRef, newDeviceContext } from "./picture.js";
2
+ import { applyTransform, clippedAway, cloneDc, colorRef, intersectClip, newDeviceContext, primBounds } from "./picture.js";
3
+ import { cropDib, fadedDib, readDib } from "./dib.js";
4
+ import { makeBlitter } from "./blit.js";
3
5
  import { fromSymbolFont } from "./symbol-fonts.js";
4
6
  //#region src/core/metafile/emf.ts
5
7
  /** Whether the bytes open with an EMF header (MS-EMF §2.3.4.2: type 1 + " EMF"). */
@@ -21,11 +23,6 @@ var RECORD_NAMES = new Map([
21
23
  [74, "region paint"],
22
24
  [78, "masked blit"],
23
25
  [79, "parallelogram blit"],
24
- [80, "device bitmap"],
25
- [81, "bitmap"],
26
- [76, "bitmap"],
27
- [77, "bitmap"],
28
- [114, "alpha blit"],
29
26
  [116, "transparent blit"],
30
27
  [118, "gradient fill"]
31
28
  ]);
@@ -74,9 +71,47 @@ function readEmf(bytes) {
74
71
  };
75
72
  let building;
76
73
  const open = () => building ?? [];
74
+ const inFigure = () => {
75
+ const tail = building?.[building.length - 1];
76
+ return tail !== void 0 && tail.op !== "close";
77
+ };
78
+ const emit = (prim) => {
79
+ if (!clippedAway(dc, primBounds(prim))) prims.push(prim);
80
+ };
81
+ const blitter = makeBlitter(emit);
82
+ /**
83
+ * §2.3.1 — one blit record: the source bitmap into the destination
84
+ * rectangle, both stated in the record's own fields. The rectangle is mapped
85
+ * the way every other coordinate here is, so the picture lands in the same
86
+ * device frame as the paths around it.
87
+ */
88
+ const blit = (o) => {
89
+ const dib = o.cbBmi > 0 ? readDib(bytes, off + o.bmiAt, {
90
+ bitsAt: off + o.bitsAt,
91
+ cbBits: o.cbBits,
92
+ ...o.blend?.perPixel === true ? { alpha: true } : {}
93
+ }) : void 0;
94
+ if (!dib) {
95
+ skipped.add("bitmap");
96
+ return;
97
+ }
98
+ const s = o.src;
99
+ const part = s && (s.x !== 0 || s.y !== 0 || s.w !== dib.width || s.h !== dib.height) ? cropDib(dib, s.x, o.inBitmap === true && dib.bottomUp === true ? dib.height - s.y - s.h : s.y, s.w, s.h) : dib;
100
+ const a = px(o.dest.x, o.dest.y);
101
+ const b = px(o.dest.x + o.dest.w, o.dest.y + o.dest.h);
102
+ blitter.blit(o.blend && o.blend.alpha < 255 ? fadedDib(part, o.blend.alpha / 255) : part, {
103
+ x: Math.min(a.x, b.x),
104
+ y: Math.min(a.y, b.y),
105
+ width: Math.abs(b.x - a.x),
106
+ height: Math.abs(b.y - a.y)
107
+ }, o.rop);
108
+ };
77
109
  const strokeOf = () => {
78
110
  if (dc.pen.style === "none") return void 0;
79
- const w = dc.pen.widthLu <= 1 ? 1 : dc.pen.widthLu * Math.abs(sx());
111
+ const m = dc.transform;
112
+ const world = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2])) || 1;
113
+ const lu = dc.pen.widthLu * world;
114
+ const w = lu <= 1 ? 1 : lu * Math.abs(sx());
80
115
  return {
81
116
  colorHex: dc.pen.colorHex,
82
117
  widthPt: w,
@@ -92,13 +127,22 @@ function readEmf(bytes) {
92
127
  const st = stroke ? strokeOf() : void 0;
93
128
  const fillHex = fill && !dc.brush.hollow ? dc.brush.colorHex : void 0;
94
129
  if (fillHex === void 0 && !st) return;
95
- prims.push({
130
+ emit({
96
131
  kind: "path",
97
132
  paths: [path],
98
133
  ...fillHex !== void 0 ? { fillColorHex: fillHex } : {},
99
134
  ...st ? { stroke: st } : {}
100
135
  });
101
136
  };
137
+ /** Two mapped corners → the rectangle they bound, in either order. */
138
+ function rectOf(a, b) {
139
+ return {
140
+ left: Math.min(a.x, b.x),
141
+ top: Math.min(a.y, b.y),
142
+ right: Math.max(a.x, b.x),
143
+ bottom: Math.max(a.y, b.y)
144
+ };
145
+ }
102
146
  /** A closed rectangle, as the geometry records draw one. */
103
147
  const rectSegments = (l, t, r, b) => {
104
148
  const p0 = px(l, t);
@@ -151,6 +195,32 @@ function readEmf(bytes) {
151
195
  };
152
196
  haveView = true;
153
197
  break;
198
+ case 30: {
199
+ const a = px(at(8), at(12));
200
+ const b = px(at(16), at(20));
201
+ dc = {
202
+ ...dc,
203
+ clip: intersectClip(dc, rectOf(a, b))
204
+ };
205
+ break;
206
+ }
207
+ case 75: {
208
+ const cbRgnData = u(8);
209
+ const mode = u(12);
210
+ if (cbRgnData === 0 && mode === 5) {
211
+ const { clip: _drop, ...rest } = dc;
212
+ dc = rest;
213
+ } else if (mode === 5 && cbRgnData >= 32 && u(20) === 1) {
214
+ const base = off + 16 + 32;
215
+ const a = px(v.getInt32(base, true), v.getInt32(base + 4, true));
216
+ const b = px(v.getInt32(base + 8, true), v.getInt32(base + 12, true));
217
+ dc = {
218
+ ...dc,
219
+ clip: rectOf(a, b)
220
+ };
221
+ }
222
+ break;
223
+ }
154
224
  case 33:
155
225
  stack.push(cloneDc(dc));
156
226
  break;
@@ -256,7 +326,7 @@ function readEmf(bytes) {
256
326
  dc.y = at(12);
257
327
  const to = px(dc.x, dc.y);
258
328
  if (building) {
259
- if (building.length === 0) building.push({
329
+ if (!inFigure()) building.push({
260
330
  op: "move",
261
331
  x: from.x,
262
332
  y: from.y
@@ -302,6 +372,11 @@ function readEmf(bytes) {
302
372
  case 44:
303
373
  paint(rectSegments(at(8), at(12), at(16), at(20)), true, true);
304
374
  break;
375
+ case 47:
376
+ case 46:
377
+ case 45:
378
+ paint(arcSegments(px, at(8), at(12), at(16), at(20), at(24), at(28), at(32), at(36), type), type !== 45, true);
379
+ break;
305
380
  case 3:
306
381
  case 4:
307
382
  case 2:
@@ -320,10 +395,13 @@ function readEmf(bytes) {
320
395
  const segs = polySegments(px, pts, {
321
396
  closed,
322
397
  bezier: type === 2 || type === 5 || type === 85 || type === 88,
323
- ...type === 5 || type === 6 || type === 88 || type === 89 ? { from: {
324
- x: dc.x,
325
- y: dc.y
326
- } } : {}
398
+ ...type === 5 || type === 6 || type === 88 || type === 89 ? {
399
+ from: {
400
+ x: dc.x,
401
+ y: dc.y
402
+ },
403
+ joined: inFigure()
404
+ } : {}
327
405
  });
328
406
  if (pts.length > 0) {
329
407
  dc.x = pts[pts.length - 1].x;
@@ -370,7 +448,7 @@ function readEmf(bytes) {
370
448
  if (text.trim() !== "") {
371
449
  const p = px(refX, refY);
372
450
  const font = dc.font;
373
- prims.push({
451
+ emit({
374
452
  kind: "text",
375
453
  text: fromSymbolFont(text, font?.family),
376
454
  x: p.x,
@@ -388,25 +466,115 @@ function readEmf(bytes) {
388
466
  }
389
467
  break;
390
468
  case 76:
391
- {
392
- const cbBmiSrc = u(88);
393
- const rop = u(40);
394
- if (cbBmiSrc === 0) {
395
- const x = at(24);
396
- const y = at(28);
397
- const w = at(32);
398
- const h = at(36);
399
- const hex = rop === 66 ? "000000" : rop === 16711778 ? "FFFFFF" : void 0;
400
- const brush = dc.brush;
401
- if (hex !== void 0) dc.brush = {
402
- kind: "brush",
403
- colorHex: hex,
404
- hollow: false
405
- };
406
- paint(rectSegments(x, y, x + w, y + h), true, false);
407
- dc.brush = brush;
408
- } else skipped.add("bitmap");
469
+ case 77: {
470
+ const cbBmiSrc = u(88);
471
+ const rop = u(40);
472
+ const x = at(24);
473
+ const y = at(28);
474
+ const w = at(32);
475
+ const h = at(36);
476
+ if (cbBmiSrc === 0) {
477
+ const hex = rop === 66 ? "000000" : rop === 16711778 ? "FFFFFF" : void 0;
478
+ if (hex === void 0 && rop !== 15728673) break;
479
+ const brush = dc.brush;
480
+ if (hex !== void 0) dc.brush = {
481
+ kind: "brush",
482
+ colorHex: hex,
483
+ hollow: false
484
+ };
485
+ paint(rectSegments(x, y, x + w, y + h), true, false);
486
+ dc.brush = brush;
487
+ break;
409
488
  }
489
+ blit({
490
+ bmiAt: u(84),
491
+ cbBmi: cbBmiSrc,
492
+ bitsAt: u(92),
493
+ cbBits: u(96),
494
+ dest: {
495
+ x,
496
+ y,
497
+ w,
498
+ h
499
+ },
500
+ src: {
501
+ x: at(44),
502
+ y: at(48),
503
+ w: type === 77 ? at(100) : w,
504
+ h: type === 77 ? at(104) : h
505
+ },
506
+ rop
507
+ });
508
+ break;
509
+ }
510
+ case 80:
511
+ blit({
512
+ bmiAt: u(48),
513
+ cbBmi: u(52),
514
+ bitsAt: u(56),
515
+ cbBits: u(60),
516
+ dest: {
517
+ x: at(24),
518
+ y: at(28),
519
+ w: at(40),
520
+ h: at(44)
521
+ },
522
+ src: {
523
+ x: at(32),
524
+ y: at(36),
525
+ w: at(40),
526
+ h: at(44)
527
+ },
528
+ inBitmap: true,
529
+ rop: 13369376
530
+ });
531
+ break;
532
+ case 81:
533
+ blit({
534
+ bmiAt: u(48),
535
+ cbBmi: u(52),
536
+ bitsAt: u(56),
537
+ cbBits: u(60),
538
+ dest: {
539
+ x: at(24),
540
+ y: at(28),
541
+ w: at(72),
542
+ h: at(76)
543
+ },
544
+ src: {
545
+ x: at(32),
546
+ y: at(36),
547
+ w: at(40),
548
+ h: at(44)
549
+ },
550
+ inBitmap: true,
551
+ rop: u(68)
552
+ });
553
+ break;
554
+ case 114:
555
+ blit({
556
+ bmiAt: u(84),
557
+ cbBmi: u(88),
558
+ bitsAt: u(92),
559
+ cbBits: u(96),
560
+ dest: {
561
+ x: at(24),
562
+ y: at(28),
563
+ w: at(32),
564
+ h: at(36)
565
+ },
566
+ src: {
567
+ x: at(44),
568
+ y: at(48),
569
+ w: at(100),
570
+ h: at(104)
571
+ },
572
+ blend: {
573
+ alpha: u(40) >>> 16 & 255,
574
+ perPixel: u(40) >>> 24 === 1
575
+ },
576
+ rop: 13369376
577
+ });
410
578
  break;
411
579
  default:
412
580
  {
@@ -557,12 +725,14 @@ function polySegments(map, pts, o) {
557
725
  if (pts.length === 0) return segs;
558
726
  let i = 0;
559
727
  if (o.from) {
560
- const s = map(o.from.x, o.from.y);
561
- segs.push({
562
- op: "move",
563
- x: s.x,
564
- y: s.y
565
- });
728
+ if (o.joined !== true) {
729
+ const s = map(o.from.x, o.from.y);
730
+ segs.push({
731
+ op: "move",
732
+ x: s.x,
733
+ y: s.y
734
+ });
735
+ }
566
736
  } else {
567
737
  const s = map(pts[0].x, pts[0].y);
568
738
  segs.push({
@@ -598,6 +768,78 @@ function polySegments(map, pts, o) {
598
768
  return segs;
599
769
  }
600
770
  var KAPPA = .5522847498307936;
771
+ /**
772
+ * §2.3.5.2/.4/.13 — the wedge, chord or bare curve an `EMR_ARC`, `EMR_CHORD` or
773
+ * `EMR_PIE` draws.
774
+ *
775
+ * All three state the same thing: an ellipse inscribed in a box, and two points
776
+ * naming RAYS from its centre. The arc runs from where the first ray crosses
777
+ * the ellipse to where the second does, anticlockwise on the page — GDI's
778
+ * default direction — and the two records differ only in how they close: a pie
779
+ * back through the centre, a chord straight across, an arc not at all.
780
+ *
781
+ * Built in the metafile's own coordinates and mapped point by point, exactly as
782
+ * the ellipse and the polygons are: the mapping is affine, so a Bézier's
783
+ * control points survive it.
784
+ *
785
+ * @param map The metafile's logical → page mapping.
786
+ * @param l,t,r,b The box the ellipse is inscribed in.
787
+ * @param sx,sy The point naming the ray the arc starts at.
788
+ * @param ex,ey The point naming the ray it ends at.
789
+ * @param type The record type, which decides how the figure closes.
790
+ * @returns The path segments, in page coordinates.
791
+ */
792
+ function arcSegments(map, l, t, r, b, sx, sy, ex, ey, type) {
793
+ const cx = (l + r) / 2;
794
+ const cy = (t + b) / 2;
795
+ const rx = (r - l) / 2;
796
+ const ry = (b - t) / 2;
797
+ if (!(rx > 0) || !(ry > 0)) return [];
798
+ const angleOf = (x, y) => Math.atan2((y - cy) / ry, (x - cx) / rx);
799
+ const start = angleOf(sx, sy);
800
+ let sweep = angleOf(ex, ey) - start;
801
+ if (sweep >= 0) sweep -= 2 * Math.PI;
802
+ const point = (a) => map(cx + rx * Math.cos(a), cy + ry * Math.sin(a));
803
+ const segs = [];
804
+ const from = point(start);
805
+ if (type === 47) {
806
+ const c = map(cx, cy);
807
+ segs.push({
808
+ op: "move",
809
+ x: c.x,
810
+ y: c.y
811
+ }, {
812
+ op: "line",
813
+ x: from.x,
814
+ y: from.y
815
+ });
816
+ } else segs.push({
817
+ op: "move",
818
+ x: from.x,
819
+ y: from.y
820
+ });
821
+ const steps = Math.max(1, Math.ceil(Math.abs(sweep) / (Math.PI / 2)));
822
+ const delta = sweep / steps;
823
+ const alpha = 4 / 3 * Math.tan(delta / 4);
824
+ for (let i = 0; i < steps; i++) {
825
+ const a0 = start + delta * i;
826
+ const a1 = a0 + delta;
827
+ const t0 = map(cx + rx * Math.cos(a0) - alpha * rx * Math.sin(a0), cy + ry * Math.sin(a0) + alpha * ry * Math.cos(a0));
828
+ const t1 = map(cx + rx * Math.cos(a1) + alpha * rx * Math.sin(a1), cy + ry * Math.sin(a1) - alpha * ry * Math.cos(a1));
829
+ const to = point(a1);
830
+ segs.push({
831
+ op: "cubic",
832
+ x1: t0.x,
833
+ y1: t0.y,
834
+ x2: t1.x,
835
+ y2: t1.y,
836
+ x: to.x,
837
+ y: to.y
838
+ });
839
+ }
840
+ if (type !== 45) segs.push({ op: "close" });
841
+ return segs;
842
+ }
601
843
  function ellipseSegments(map, l, t, r, b) {
602
844
  const cx = (l + r) / 2;
603
845
  const cy = (t + b) / 2;
@@ -74,7 +74,18 @@ export interface MetaFont {
74
74
  readonly italic?: boolean;
75
75
  readonly escapement?: number;
76
76
  }
77
- export type MetaObject = MetaPen | MetaBrush | MetaFont;
77
+ /**
78
+ * An object the reader does not model — a palette, a region, a pattern brush.
79
+ * It still OCCUPIES a slot in the metafile's object table: handles are indices
80
+ * into that table, so a creation the reader passed over silently shifted every
81
+ * one that followed. 23884's chart selects its coloured pens by handle, and one
82
+ * unrecorded palette put every selection one slot back — on the black pen the
83
+ * pens had displaced.
84
+ */
85
+ export interface MetaOther {
86
+ readonly kind: 'other';
87
+ }
88
+ export type MetaObject = MetaPen | MetaBrush | MetaFont | MetaOther;
78
89
  /** The device context: everything a record draws THROUGH. */
79
90
  export interface DeviceContext {
80
91
  pen: MetaPen;
@@ -91,12 +102,61 @@ export interface DeviceContext {
91
102
  y: number;
92
103
  /** The world→page transform, as `[a, b, c, d, e, f]`. */
93
104
  transform: readonly [number, number, number, number, number, number];
105
+ /**
106
+ * MS-EMF §2.3.2 / MS-WMF §2.3.2 — the region drawing is limited to, in
107
+ * logical units, when the metafile states a RECTANGULAR one. A primitive
108
+ * that falls wholly outside it is not drawn: an embedded workbook's preview
109
+ * clips its sheet to the used range, and unclipped we painted the whole
110
+ * empty grid around it.
111
+ */
112
+ clip?: {
113
+ readonly left: number;
114
+ readonly top: number;
115
+ readonly right: number;
116
+ readonly bottom: number;
117
+ };
94
118
  }
95
119
  export declare const DEFAULT_PEN: MetaPen;
96
120
  export declare const DEFAULT_BRUSH: MetaBrush;
97
121
  /** A device context in its documented initial state (MS-WMF §3.1.5). */
98
122
  export declare function newDeviceContext(): DeviceContext;
99
123
  export declare function cloneDc(dc: DeviceContext): DeviceContext;
124
+ /**
125
+ * Whether a primitive bounded by `box` is wholly outside the device context's
126
+ * clip. Partial overlaps are kept whole — the reader discards what the clip
127
+ * excludes rather than cutting geometry, which is a missing cut, never wrong
128
+ * content.
129
+ *
130
+ * @param dc The device context, for its clip.
131
+ * @param box The primitive's bounds in logical units.
132
+ * @returns `true` when nothing of it would be drawn.
133
+ */
134
+ export declare function clippedAway(dc: DeviceContext, box: {
135
+ left: number;
136
+ top: number;
137
+ right: number;
138
+ bottom: number;
139
+ }): boolean;
140
+ /** The intersection of a device context's clip with another rectangle. */
141
+ export declare function intersectClip(dc: DeviceContext, rect: {
142
+ left: number;
143
+ top: number;
144
+ right: number;
145
+ bottom: number;
146
+ }): NonNullable<DeviceContext['clip']>;
147
+ /**
148
+ * A primitive's bounds in the units it is stored in, for the clip test.
149
+ *
150
+ * @param prim The primitive.
151
+ * @returns Its bounding rectangle; a text run's is its reference point, since
152
+ * the box it fills is not known until it is measured.
153
+ */
154
+ export declare function primBounds(prim: PicturePrim): {
155
+ left: number;
156
+ top: number;
157
+ right: number;
158
+ bottom: number;
159
+ };
100
160
  /** A COLORREF (`0x00bbggrr`) as the 6-hex this codebase's colours use. */
101
161
  export declare function colorRef(v: number): string;
102
162
  /** Apply the world transform to a point. */
@@ -36,6 +36,75 @@ function newDeviceContext() {
36
36
  function cloneDc(dc) {
37
37
  return { ...dc };
38
38
  }
39
+ /**
40
+ * Whether a primitive bounded by `box` is wholly outside the device context's
41
+ * clip. Partial overlaps are kept whole — the reader discards what the clip
42
+ * excludes rather than cutting geometry, which is a missing cut, never wrong
43
+ * content.
44
+ *
45
+ * @param dc The device context, for its clip.
46
+ * @param box The primitive's bounds in logical units.
47
+ * @returns `true` when nothing of it would be drawn.
48
+ */
49
+ function clippedAway(dc, box) {
50
+ const c = dc.clip;
51
+ if (!c) return false;
52
+ return box.right < c.left || box.left > c.right || box.bottom < c.top || box.top > c.bottom;
53
+ }
54
+ /** The intersection of a device context's clip with another rectangle. */
55
+ function intersectClip(dc, rect) {
56
+ const c = dc.clip;
57
+ if (!c) return rect;
58
+ return {
59
+ left: Math.max(c.left, rect.left),
60
+ top: Math.max(c.top, rect.top),
61
+ right: Math.min(c.right, rect.right),
62
+ bottom: Math.min(c.bottom, rect.bottom)
63
+ };
64
+ }
65
+ /**
66
+ * A primitive's bounds in the units it is stored in, for the clip test.
67
+ *
68
+ * @param prim The primitive.
69
+ * @returns Its bounding rectangle; a text run's is its reference point, since
70
+ * the box it fills is not known until it is measured.
71
+ */
72
+ function primBounds(prim) {
73
+ if (prim.kind === "text") return {
74
+ left: prim.x,
75
+ top: prim.y,
76
+ right: prim.x,
77
+ bottom: prim.y
78
+ };
79
+ if (prim.kind === "image") return {
80
+ left: prim.x,
81
+ top: prim.y,
82
+ right: prim.x + prim.width,
83
+ bottom: prim.y + prim.height
84
+ };
85
+ let left = Infinity;
86
+ let top = Infinity;
87
+ let right = -Infinity;
88
+ let bottom = -Infinity;
89
+ for (const path of prim.paths) for (const sg of path.segments) {
90
+ if (!("x" in sg)) continue;
91
+ left = Math.min(left, sg.x);
92
+ right = Math.max(right, sg.x);
93
+ top = Math.min(top, sg.y);
94
+ bottom = Math.max(bottom, sg.y);
95
+ }
96
+ return Number.isFinite(left) ? {
97
+ left,
98
+ top,
99
+ right,
100
+ bottom
101
+ } : {
102
+ left: 0,
103
+ top: 0,
104
+ right: 0,
105
+ bottom: 0
106
+ };
107
+ }
39
108
  /** A COLORREF (`0x00bbggrr`) as the 6-hex this codebase's colours use. */
40
109
  function colorRef(v) {
41
110
  return [
@@ -52,4 +121,4 @@ function applyTransform(t, x, y) {
52
121
  };
53
122
  }
54
123
  //#endregion
55
- export { applyTransform, cloneDc, colorRef, newDeviceContext };
124
+ export { applyTransform, clippedAway, cloneDc, colorRef, intersectClip, newDeviceContext, primBounds };