reamkit 1.16.0 → 1.17.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 (65) hide show
  1. package/dist/esm/core/bidi/segments.js +1 -2
  2. package/dist/esm/core/converter/project.js +5 -1
  3. package/dist/esm/core/document-model/index.d.ts +1 -1
  4. package/dist/esm/core/document-model/types.d.ts +473 -7
  5. package/dist/esm/core/drawingml/chart-geometry.d.ts +8 -12
  6. package/dist/esm/core/drawingml/chart-geometry.js +58 -9
  7. package/dist/esm/core/drawingml/chart-parser.js +44 -5
  8. package/dist/esm/core/drawingml/colors.d.ts +1 -1
  9. package/dist/esm/core/drawingml/colors.js +27 -1
  10. package/dist/esm/core/drawingml/preset-geometry.js +233 -0
  11. package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
  12. package/dist/esm/core/drawingml/shape-render.js +116 -3
  13. package/dist/esm/core/drawingml/theme-parser.d.ts +10 -0
  14. package/dist/esm/core/drawingml/theme-parser.js +19 -1
  15. package/dist/esm/core/font/measure.d.ts +5 -0
  16. package/dist/esm/core/font/measure.js +21 -1
  17. package/dist/esm/core/images.d.ts +2 -2
  18. package/dist/esm/core/images.js +181 -0
  19. package/dist/esm/core/ir/flow.d.ts +23 -1
  20. package/dist/esm/core/numbering/apply.d.ts +5 -3
  21. package/dist/esm/core/numbering/apply.js +42 -11
  22. package/dist/esm/core/numbering/index.js +2 -0
  23. package/dist/esm/core/numbering/state.d.ts +13 -1
  24. package/dist/esm/core/numbering/state.js +256 -15
  25. package/dist/esm/core/po-helpers.js +16 -1
  26. package/dist/esm/core/style-cascade/resolver.js +66 -11
  27. package/dist/esm/core/style-cascade/table.js +35 -11
  28. package/dist/esm/core/style-cascade/types.d.ts +42 -1
  29. package/dist/esm/core/style-cascade/types.js +4 -0
  30. package/dist/esm/core/vector.d.ts +17 -0
  31. package/dist/esm/excel/print-model.js +1 -1
  32. package/dist/esm/excel/sheet-to-flow.js +10 -8
  33. package/dist/esm/excel/worksheet-parser.js +1 -1
  34. package/dist/esm/html/html-writer.js +18 -4
  35. package/dist/esm/layout/page-doc.d.ts +62 -2
  36. package/dist/esm/layout/styled-layout.d.ts +44 -2
  37. package/dist/esm/layout/styled-layout.js +1870 -228
  38. package/dist/esm/pdf/shading.js +76 -6
  39. package/dist/esm/pdf/styled-page-emitter.js +217 -43
  40. package/dist/esm/pdf/vector-graphics.d.ts +28 -1
  41. package/dist/esm/pdf/vector-graphics.js +94 -11
  42. package/dist/esm/pptx/pptx-reader.js +1 -0
  43. package/dist/esm/pptx/slide-parser.d.ts +21 -4
  44. package/dist/esm/pptx/slide-parser.js +86 -23
  45. package/dist/esm/svg/svg-writer.js +1 -1
  46. package/dist/esm/word/document-parser.d.ts +96 -7
  47. package/dist/esm/word/document-parser.js +537 -116
  48. package/dist/esm/word/docx-reader.js +135 -43
  49. package/dist/esm/word/docx-writer.js +6 -1
  50. package/dist/esm/word/drawing-parser.d.ts +53 -5
  51. package/dist/esm/word/drawing-parser.js +1540 -54
  52. package/dist/esm/word/index.d.ts +4 -4
  53. package/dist/esm/word/numbering-parser.d.ts +2 -1
  54. package/dist/esm/word/numbering-parser.js +107 -21
  55. package/dist/esm/word/paragraph-properties.d.ts +11 -1
  56. package/dist/esm/word/paragraph-properties.js +219 -10
  57. package/dist/esm/word/run-properties.js +45 -1
  58. package/dist/esm/word/settings-parser.d.ts +24 -0
  59. package/dist/esm/word/settings-parser.js +31 -5
  60. package/dist/esm/word/shading.d.ts +11 -0
  61. package/dist/esm/word/shading.js +64 -0
  62. package/dist/esm/word/styles-parser.js +8 -2
  63. package/dist/esm/word/table-parser.d.ts +9 -1
  64. package/dist/esm/word/table-parser.js +182 -30
  65. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { dict, name, ref } from "./objects.js";
1
+ import { dict, name, ref, stream } from "./objects.js";
2
2
  //#region src/pdf/shading.ts
3
3
  /**
4
4
  * The local-space bounding box of a shape's paths (y-up).
@@ -41,8 +41,7 @@ function shapeBbox(shape) {
41
41
  * @returns A reference to the `/Pattern` object, for a `/Pattern cs /Pn scn` fill.
42
42
  */
43
43
  function buildGradientPattern(doc, gradient, bbox, ctm) {
44
- const fnRef = doc.add(buildRamp(gradient.stops));
45
- const shading = gradient.kind === "radial" ? radialShading(bbox, fnRef) : axialShading(gradient.angle ?? 0, bbox, fnRef);
44
+ const shading = gradient.kind === "radial" && gradient.sweep === "rect" ? rectShading(doc, bbox, gradient.stops, gradient.center) : gradient.kind === "radial" ? radialShading(bbox, doc.add(buildRamp(gradient.stops)), gradient.center) : axialShading(gradient.angle ?? 0, bbox, doc.add(buildRamp(gradient.stops)));
46
45
  return doc.add(dict({
47
46
  Type: name("Pattern"),
48
47
  PatternType: 2,
@@ -82,9 +81,9 @@ function axialShading(angleDeg, b, fnRef) {
82
81
  Extend: [true, true]
83
82
  });
84
83
  }
85
- function radialShading(b, fnRef) {
86
- const cx = (b.minX + b.maxX) / 2;
87
- const cy = (b.minY + b.maxY) / 2;
84
+ function radialShading(b, fnRef, center) {
85
+ const cx = center ? b.minX + center.x * (b.maxX - b.minX) : (b.minX + b.maxX) / 2;
86
+ const cy = center ? b.maxY - center.y * (b.maxY - b.minY) : (b.minY + b.maxY) / 2;
88
87
  const r = Math.hypot(b.maxX - b.minX, b.maxY - b.minY) / 2;
89
88
  return dict({
90
89
  ShadingType: 3,
@@ -101,6 +100,77 @@ function radialShading(b, fnRef) {
101
100
  Extend: [true, true]
102
101
  });
103
102
  }
103
+ function rectShading(doc, b, stopsIn, center) {
104
+ const cx = center ? b.minX + center.x * (b.maxX - b.minX) : (b.minX + b.maxX) / 2;
105
+ const cy = center ? b.maxY - center.y * (b.maxY - b.minY) : (b.minY + b.maxY) / 2;
106
+ const halfW = Math.max((b.maxX - b.minX) / 2, 1e-6);
107
+ const halfH = Math.max((b.maxY - b.minY) / 2, 1e-6);
108
+ const domain = [
109
+ b.minX,
110
+ b.maxX,
111
+ b.minY,
112
+ b.maxY
113
+ ];
114
+ const fn = doc.add(stream({
115
+ FunctionType: 4,
116
+ Domain: domain,
117
+ Range: [
118
+ 0,
119
+ 1,
120
+ 0,
121
+ 1,
122
+ 0,
123
+ 1
124
+ ]
125
+ }, new TextEncoder().encode(rectSweepProgram(cx, cy, 1 / halfW, 1 / halfH, stopsIn))));
126
+ return dict({
127
+ ShadingType: 1,
128
+ ColorSpace: name("DeviceRGB"),
129
+ Domain: domain,
130
+ Function: ref(fn.id)
131
+ });
132
+ }
133
+ function rectSweepProgram(cx, cy, sx, sy, stopsIn) {
134
+ const stops = normalizeStops(stopsIn);
135
+ return [
136
+ "{",
137
+ `${ps(cy)} sub abs ${ps(sy)} mul`,
138
+ `exch ${ps(cx)} sub abs ${ps(sx)} mul`,
139
+ "2 copy lt { exch } if pop",
140
+ "dup 1 gt { pop 1 } if",
141
+ rampProgram(stops, 0),
142
+ "}"
143
+ ].join("\n");
144
+ }
145
+ function rampProgram(stops, i) {
146
+ const last = i >= stops.length - 2;
147
+ const a = stops[i];
148
+ const b = stops[i + 1];
149
+ const seg = segmentProgram(a, b);
150
+ if (last) return seg;
151
+ return `dup ${ps(b.offset)} le { ${seg} } { ${rampProgram(stops, i + 1)} } ifelse`;
152
+ }
153
+ function segmentProgram(a, b) {
154
+ const c0 = rgb01(a.colorHex);
155
+ const c1 = rgb01(b.colorHex);
156
+ const span = b.offset - a.offset;
157
+ if (span <= 1e-9) return `pop ${c1.map(ps).join(" ")}`;
158
+ return [
159
+ `${ps(a.offset)} sub ${ps(1 / span)} mul`,
160
+ "dup 0 lt { pop 0 } if dup 1 gt { pop 1 } if",
161
+ "dup dup",
162
+ `${ps(c1[0] - c0[0])} mul ${ps(c0[0])} add`,
163
+ "3 1 roll",
164
+ `${ps(c1[1] - c0[1])} mul ${ps(c0[1])} add`,
165
+ "exch",
166
+ `${ps(c1[2] - c0[2])} mul ${ps(c0[2])} add`
167
+ ].join(" ");
168
+ }
169
+ function ps(n) {
170
+ if (!Number.isFinite(n)) return "0";
171
+ const s = n.toFixed(6).replace(/0+$/u, "").replace(/\.$/u, "");
172
+ return s === "" || s === "-0" ? "0" : s;
173
+ }
104
174
  function buildRamp(stopsIn) {
105
175
  const stops = normalizeStops(stopsIn);
106
176
  if (stops.length <= 2) return dict({
@@ -3,9 +3,9 @@ import { embedTtfFont } from "./cid-font.js";
3
3
  import { preparePdfEncryption } from "./encryption.js";
4
4
  import { reorderVisual } from "../core/bidi/algorithm.js";
5
5
  import { reverseByCodePoint } from "../core/bidi/index.js";
6
- import "../layout/styled-layout.js";
6
+ import { GLUE_SHRINK_RATIO } from "../layout/styled-layout.js";
7
7
  import { paintPlan } from "../layout/page-doc.js";
8
- import { emitVectorShape } from "./vector-graphics.js";
8
+ import { emitClipPath, emitVectorShape, shadowBlurLayers } from "./vector-graphics.js";
9
9
  import { buildGradientPattern, shapeBbox } from "./shading.js";
10
10
  import { sanitizeHref } from "../core/links.js";
11
11
  import { embedAssociatedFile } from "./embedded-file.js";
@@ -97,17 +97,24 @@ function assembleStyledPdf(laid, options, doc) {
97
97
  }
98
98
  const alphaStateNames = /* @__PURE__ */ new Map();
99
99
  const extGStateEntries = {};
100
- for (const page of renderedPages) for (const item of paintPlan(page.commands).shapes) {
101
- const alpha = item.shape.shadow?.alpha;
102
- if (alpha === void 0 || alpha >= 1) continue;
100
+ const wantAlpha = (alpha) => {
101
+ if (alpha === void 0 || alpha >= 1) return;
103
102
  const key = Math.round(alpha * 1e3) / 1e3;
104
- if (alphaStateNames.has(key)) continue;
103
+ if (alphaStateNames.has(key)) return;
105
104
  const nm = `GSa${alphaStateNames.size}`;
106
105
  alphaStateNames.set(key, nm);
107
106
  extGStateEntries[nm] = dict({
108
107
  ca: key,
109
108
  CA: key
110
109
  });
110
+ };
111
+ for (const page of renderedPages) {
112
+ const plan = paintPlan(page.commands);
113
+ for (const item of plan.shapes) {
114
+ const s = item.shape.shadow;
115
+ if (s) wantAlpha(shadowBlurLayers(s).alpha);
116
+ }
117
+ for (const line of plan.lines) for (const tok of line.line.tokens) if (tok.kind === "image" && tok.shadow) wantAlpha(shadowBlurLayers(tok.shadow).alpha);
111
118
  }
112
119
  const resourcesDict = dict({
113
120
  Font: fontResourceDict,
@@ -378,7 +385,8 @@ function defaultPageCtx() {
378
385
  even: { commands: [] }
379
386
  },
380
387
  titlePg: false,
381
- evenAndOddHeaders: false
388
+ evenAndOddHeaders: false,
389
+ continuous: false
382
390
  };
383
391
  }
384
392
  function embedImageResources(doc, laid) {
@@ -445,21 +453,37 @@ function emitTaggedRuns(out, cmds, tagging, body) {
445
453
  close();
446
454
  }
447
455
  /**
448
- * The PDF dash array for a §18.18.3 border style, in points.
456
+ * The dash array for a §18.18.3 / §17.18.2 border style, in points.
449
457
  *
450
- * Excel's dashes are proportional to the rule's weight; LibreOffice draws a
451
- * `thin` dashed edge as 3pt on / 1pt off, which is 4× / 1.33× its 0.75pt width.
452
- * An empty string means a solid rule.
458
+ * A pattern is a pattern, not a weight: Word and Excel both draw the same
459
+ * dashes on a hairline and on a 3pt rule, and both references agree on the
460
+ * lengths — a `dashed` page border strokes `8 2.5` whether its `w:sz` is 4 or
461
+ * 24. Scaling them by the width instead drew page-borders-export-case-2.docx's
462
+ * half-point frame as a row of specks a quarter the length it should be.
453
463
  */
454
- function dashPatternFor(style, widthPt) {
455
- const u = Math.max(.25, widthPt);
456
- switch (style) {
457
- case "dashed": return `[${formatNumber(u * 4)} ${formatNumber(u * 1.33)}]`;
458
- case "dashDot": return `[${formatNumber(u * 4)} ${formatNumber(u * 1.33)} ${formatNumber(u)} ${formatNumber(u * 1.33)}]`;
459
- case "dashDotDot": return `[${formatNumber(u * 4)} ${formatNumber(u * 1.33)} ${formatNumber(u)} ${formatNumber(u * 1.33)} ${formatNumber(u)} ${formatNumber(u * 1.33)}]`;
460
- case "dotted": return `[${formatNumber(u)} ${formatNumber(u * 1.33)}]`;
461
- default: return "";
462
- }
464
+ var BORDER_DASHES = new Map([
465
+ ["dashed", [8, 2.5]],
466
+ ["dashSmallGap", [3, 1]],
467
+ ["dotted", [.5, 1]],
468
+ ["dashDot", [
469
+ 8,
470
+ 2.5,
471
+ 2.5,
472
+ 2.5
473
+ ]],
474
+ ["dashDotDot", [
475
+ 8,
476
+ 2.5,
477
+ 2.5,
478
+ 2.5,
479
+ 2.5,
480
+ 2.5
481
+ ]]
482
+ ]);
483
+ /** The PDF dash array for a border style; an empty string means a solid rule. */
484
+ function dashPatternFor(style) {
485
+ const pattern = style !== void 0 ? BORDER_DASHES.get(style) : void 0;
486
+ return pattern ? `[${pattern.map(formatNumber).join(" ")}]` : "";
463
487
  }
464
488
  function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
465
489
  const commands = page.commands;
@@ -491,7 +515,18 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
491
515
  }
492
516
  emitTaggedRuns(out, plan.images.filter((c) => c.imageResourceName !== ""), tagging, (img) => {
493
517
  out.push("q");
494
- out.push(`${formatNumber(img.width)} 0 0 ${formatNumber(img.height)} ${formatNumber(img.x)} ${formatNumber(H - img.y - img.height)} cm`);
518
+ if (img.clip) {
519
+ const t = img.clip.transform;
520
+ out.push(...emitClipPath(img.clip.paths, [
521
+ t[0],
522
+ -t[1],
523
+ t[2],
524
+ -t[3],
525
+ t[4],
526
+ H - t[5]
527
+ ]));
528
+ }
529
+ out.push(...placeImage(img.x, H - img.y - img.height, img.width, img.height, img.crop, img.rotationDeg, img.flipH, img.flipV));
495
530
  out.push(`/${img.imageResourceName} Do`);
496
531
  out.push("Q");
497
532
  });
@@ -506,7 +541,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
506
541
  for (const b of borders) {
507
542
  const width = b.borderSizePt;
508
543
  const color = b.borderColorHex;
509
- const dash = dashPatternFor(b.borderStyle, width);
544
+ const dash = dashPatternFor(b.borderStyle);
510
545
  if (dash !== lastDash) {
511
546
  out.push(dash === "" ? "[] 0 d 2 J" : `${dash} 0 d 0 J`);
512
547
  lastDash = dash;
@@ -578,14 +613,15 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
578
613
  H - t[5]
579
614
  ]
580
615
  };
581
- const alpha = sh.shape.shadow?.alpha;
582
- const alphaState = alpha !== void 0 && alpha < 1 ? alphaStateNames?.get(Math.round(alpha * 1e3) / 1e3) : void 0;
616
+ const shadowLayer = sh.shape.shadow ? shadowBlurLayers(sh.shape.shadow) : void 0;
617
+ const alphaState = shadowLayer && shadowLayer.alpha < 1 ? alphaStateNames?.get(Math.round(shadowLayer.alpha * 1e3) / 1e3) : void 0;
583
618
  for (const op of emitVectorShape(shape, gradientNames?.get(sh.shape), alphaState)) out.push(op);
584
619
  });
585
620
  const lines = plan.lines;
586
621
  if (lines.length > 0) {
587
622
  let inBT = false;
588
623
  let lastFont = "";
624
+ let lastTc = 0;
589
625
  let lastSize = -1;
590
626
  let lastColor = "";
591
627
  const switchFontIfNeeded = (tok) => {
@@ -595,6 +631,11 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
595
631
  lastFont = fontKey;
596
632
  lastSize = tok.fontSizePt;
597
633
  }
634
+ const tc = tok.resolvedRun.letterSpacingPt ?? 0;
635
+ if (tc !== lastTc) {
636
+ out.push(`${formatNumber(tc)} Tc`);
637
+ lastTc = tc;
638
+ }
598
639
  if (tok.resolvedRun.colorHex !== lastColor) {
599
640
  const [r, g, b] = hexToRgb01(tok.resolvedRun.colorHex);
600
641
  out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} rg`);
@@ -607,13 +648,45 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
607
648
  out.push("ET");
608
649
  inBT = false;
609
650
  }
651
+ const shdw = tok.shadow;
652
+ if (shdw) {
653
+ const bx = tok.drawBox;
654
+ const [shr, shg, shb] = hexToRgb01(shdw.colorHex);
655
+ const layers = shadowBlurLayers(shdw);
656
+ const state = layers.alpha < 1 ? alphaStateNames?.get(Math.round(layers.alpha * 1e3) / 1e3) : void 0;
657
+ const sx = x + (bx?.dxPt ?? 0) + shdw.dxPt;
658
+ const sy = baselineY + (bx?.dyPt ?? 0) - shdw.dyPt;
659
+ const sw = bx?.widthPt ?? tok.widthPt;
660
+ const sh = bx?.heightPt ?? tok.heightPt;
661
+ out.push("q");
662
+ if (state) out.push(`/${state} gs`);
663
+ out.push(`${formatNumber(shr)} ${formatNumber(shg)} ${formatNumber(shb)} rg`);
664
+ for (let i = 0; i < layers.count; i++) {
665
+ const grow = layers.count > 1 ? -shdw.blurPt / 2 + (i + .5) * shdw.blurPt / layers.count : 0;
666
+ out.push(`${formatNumber(sx - grow)} ${formatNumber(sy - grow)} ${formatNumber(Math.max(sw + 2 * grow, 0))} ${formatNumber(Math.max(sh + 2 * grow, 0))} re`);
667
+ out.push("f");
668
+ }
669
+ out.push("Q");
670
+ }
610
671
  out.push("q");
611
- out.push(`${formatNumber(tok.widthPt)} 0 0 ${formatNumber(tok.heightPt)} ${formatNumber(x)} ${formatNumber(baselineY)} cm`);
672
+ const b = tok.drawBox;
673
+ out.push(...b ? placeImage(x + b.dxPt, baselineY + b.dyPt, b.widthPt, b.heightPt, tok.crop, tok.rotationDeg, tok.flipH, tok.flipV) : placeImage(x, baselineY, tok.widthPt, tok.heightPt, tok.crop, tok.rotationDeg, tok.flipH, tok.flipV));
612
674
  out.push(`/${tok.imageResourceName} Do`);
613
675
  out.push("Q");
676
+ const frame = tok.outline;
677
+ if (frame) {
678
+ const [fr, fg, fb] = hexToRgb01(frame.colorHex);
679
+ out.push("q");
680
+ out.push(`${formatNumber(frame.widthPt)} w`);
681
+ out.push(`${formatNumber(fr)} ${formatNumber(fg)} ${formatNumber(fb)} RG`);
682
+ out.push(`${formatNumber(x + (b?.dxPt ?? 0))} ${formatNumber(baselineY + (b?.dyPt ?? 0))} ${formatNumber(b?.widthPt ?? tok.widthPt)} ${formatNumber(b?.heightPt ?? tok.heightPt)} re`);
683
+ out.push("S");
684
+ out.push("Q");
685
+ }
614
686
  lastFont = "";
615
687
  lastSize = -1;
616
688
  lastColor = "";
689
+ lastTc = 0;
617
690
  };
618
691
  const emitMathToken = (tok, originX, baselineY) => {
619
692
  for (const it of tok.items) if (it.kind === "glyph") {
@@ -631,7 +704,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
631
704
  lastColor = "000000";
632
705
  }
633
706
  out.push(`1 0 0 1 ${formatNumber(originX + it.x)} ${formatNumber(baselineY + it.y)} Tm`);
634
- out.push(`<${it.font.measure.encodeTextAsCidHex(it.text)}> Tj`);
707
+ out.push(it.font.measure.showText(it.text));
635
708
  } else if (it.kind === "rule") {
636
709
  if (inBT) {
637
710
  out.push("ET");
@@ -687,6 +760,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
687
760
  lastFont = "";
688
761
  lastSize = -1;
689
762
  lastColor = "";
763
+ lastTc = 0;
690
764
  out.push("BT");
691
765
  inBT = true;
692
766
  body(cmd);
@@ -698,6 +772,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
698
772
  lastFont = "";
699
773
  lastSize = -1;
700
774
  lastColor = "";
775
+ lastTc = 0;
701
776
  };
702
777
  const emitOneLine = (cmd) => {
703
778
  const line = cmd.line;
@@ -749,12 +824,40 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
749
824
  const hasImageToken = line.tokens.some((t) => t.kind === "image");
750
825
  const hasMathToken = line.tokens.some((t) => t.kind === "math");
751
826
  const hasRtl = line.tokens.some((t) => t.bidiLevel % 2 === 1);
827
+ const hasTab = line.tokens.some((t) => t.kind === "text" && t.tab === true);
752
828
  /** A text token carrying underline or strikethrough. */
753
829
  const isDecorated = (tok) => tok.kind === "text" && (tok.resolvedRun.underline !== "none" || tok.resolvedRun.strike);
754
- const encodeToken = (tok) => {
830
+ const showToken = (tok) => {
755
831
  const text = tok.bidiLevel % 2 === 1 ? reverseByCodePoint(tok.text) : tok.text;
756
- return tok.font.measure.encodeTextAsCidHex(text);
832
+ return tok.font.measure.showText(text);
757
833
  };
834
+ if (line.tokens.some((t) => t.kind === "text" && t.resolvedRun.shadingColorHex)) {
835
+ if (inBT) {
836
+ out.push("ET");
837
+ inBT = false;
838
+ }
839
+ const shAscent = Math.max(lineFs, line.mathAscentPt ?? 0);
840
+ const shDescent = Math.max(lineFs * .2, line.mathDescentPt ?? 0);
841
+ out.push("q");
842
+ let sx = originX;
843
+ let painted = "";
844
+ for (const tok of line.tokens) {
845
+ const w = tok.widthPt + (tok.kind === "text" && tok.isSpace ? extraPerSpace : 0);
846
+ const hex = tok.kind === "text" ? tok.resolvedRun.shadingColorHex : void 0;
847
+ if (hex !== void 0) {
848
+ if (hex !== painted) {
849
+ if (painted !== "") out.push("f");
850
+ const [sr, sg, sb] = hexToRgb01(hex);
851
+ out.push(`${formatNumber(sr)} ${formatNumber(sg)} ${formatNumber(sb)} rg`);
852
+ painted = hex;
853
+ }
854
+ out.push(`${formatNumber(sx)} ${formatNumber(baselineY - shDescent)} ${formatNumber(w)} ${formatNumber(shAscent + shDescent)} re`);
855
+ }
856
+ sx += w;
857
+ }
858
+ if (painted !== "") out.push("f");
859
+ out.push("Q");
860
+ }
758
861
  if (line.tokens.some((t) => t.kind === "text" && t.highlight === true)) {
759
862
  if (inBT) {
760
863
  out.push("ET");
@@ -784,12 +887,30 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
784
887
  for (const tok of line.tokens) {
785
888
  const w = tok.widthPt + (tok.kind === "text" && tok.isSpace ? extraPerSpace : 0);
786
889
  if (isDecorated(tok)) {
787
- const [r, g, b] = hexToRgb01(tok.resolvedRun.colorHex);
788
890
  const thickness = Math.max(.4, tok.fontSizePt * .06);
789
- out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} rg`);
891
+ const paint = (hex) => {
892
+ const [r, g, b] = hexToRgb01(hex);
893
+ out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} rg`);
894
+ };
895
+ paint(tok.resolvedRun.colorHex);
790
896
  if (tok.resolvedRun.underline !== "none") {
791
- const y = baselineY - tok.fontSizePt * .12 - thickness;
792
- out.push(`${formatNumber(dx)} ${formatNumber(y)} ${formatNumber(w)} ${formatNumber(thickness)} re f`);
897
+ if (tok.resolvedRun.underlineColorHex) paint(tok.resolvedRun.underlineColorHex);
898
+ const style = tok.resolvedRun.underline;
899
+ const t = style === "thick" || style === "dottedHeavy" || style === "dashHeavy" ? thickness * 2 : thickness;
900
+ const y = baselineY - tok.fontSizePt * .12 - t;
901
+ const dashed = style === "dotted" || style === "dottedHeavy" ? [t, t * 2] : style === "dash" || style === "dashHeavy" ? [t * 4, t * 3] : void 0;
902
+ if (dashed) {
903
+ out.push(`${formatNumber(t)} w`);
904
+ out.push(`[${dashed.map((n) => formatNumber(n)).join(" ")}] 0 d`);
905
+ const [r, g, b] = hexToRgb01(tok.resolvedRun.underlineColorHex ?? tok.resolvedRun.colorHex);
906
+ out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} RG`);
907
+ out.push(`${formatNumber(dx)} ${formatNumber(y + t / 2)} m ${formatNumber(dx + w)} ${formatNumber(y + t / 2)} l S`);
908
+ out.push("[] 0 d");
909
+ } else {
910
+ out.push(`${formatNumber(dx)} ${formatNumber(y)} ${formatNumber(w)} ${formatNumber(t)} re f`);
911
+ if (style === "double") out.push(`${formatNumber(dx)} ${formatNumber(y - t * 2)} ${formatNumber(w)} ${formatNumber(t)} re f`);
912
+ }
913
+ if (tok.resolvedRun.underlineColorHex) paint(tok.resolvedRun.colorHex);
793
914
  }
794
915
  if (tok.resolvedRun.strike) {
795
916
  const y = baselineY + tok.fontSizePt * .26;
@@ -813,9 +934,9 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
813
934
  for (const tok of line.tokens) {
814
935
  if (tok.kind !== "text") continue;
815
936
  switchFontIfNeeded(tok);
816
- out.push(`<${tok.font.measure.encodeTextAsCidHex(tok.text)}> Tj`);
937
+ out.push(tok.font.measure.showText(tok.text));
817
938
  }
818
- } else if (extraPerSpace > 0 || hasImageToken || hasMathToken || hasRtl || hasRise) {
939
+ } else if (extraPerSpace !== 0 || hasImageToken || hasMathToken || hasRtl || hasRise || hasTab) {
819
940
  const order = hasRtl ? lineVisualOrder(line) : line.tokens.map((_, i) => i);
820
941
  let x = originX;
821
942
  for (const ti of order) {
@@ -838,7 +959,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
838
959
  }
839
960
  switchFontIfNeeded(tok);
840
961
  out.push(`1 0 0 1 ${formatNumber(x)} ${formatNumber(baselineY + (tok.risePt ?? 0))} Tm`);
841
- out.push(`<${encodeToken(tok)}> Tj`);
962
+ out.push(showToken(tok));
842
963
  const tokenX0 = x;
843
964
  x += tok.widthPt;
844
965
  if (tok.isSpace) x += extraPerSpace;
@@ -855,8 +976,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
855
976
  for (const tok of line.tokens) {
856
977
  if (tok.kind !== "text") continue;
857
978
  switchFontIfNeeded(tok);
858
- const hex = tok.font.measure.encodeTextAsCidHex(tok.text);
859
- out.push(`<${hex}> Tj`);
979
+ out.push(tok.font.measure.showText(tok.text));
860
980
  trackLink(tok.href, tok.anchor, tok.text, x, x + tok.widthPt);
861
981
  x += tok.widthPt;
862
982
  }
@@ -898,15 +1018,69 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
898
1018
  function lineVisualOrder(line) {
899
1019
  return reorderVisual(line.tokens.map((t) => t.bidiLevel));
900
1020
  }
1021
+ /**
1022
+ * The operators that put an image XObject in a box — ISO 32000-1 §8.9.5.1: the
1023
+ * unit square scaled to the box and moved to `(x, y)`, its bottom-left corner.
1024
+ *
1025
+ * A `a:srcRect` crop (§20.1.8.55) cuts edges off the SOURCE and fits what is
1026
+ * left to the same box, so the picture is drawn larger than the box and the box
1027
+ * is made a clipping path. Ignored, ImageCrop.docx drew the whole mountain
1028
+ * where LibreOffice shows the peak.
1029
+ *
1030
+ * @returns The content-stream lines, to be pushed between `q` and the `Do`.
1031
+ */
1032
+ function placeImage(x, y, width, height, crop, rotationDeg, flipH, flipV) {
1033
+ const cm = (w, h, ox, oy) => `${formatNumber(w)} 0 0 ${formatNumber(h)} ${formatNumber(ox)} ${formatNumber(oy)} cm`;
1034
+ const spin = rotationDeg ? [rotateAboutCm(x + width / 2, y + height / 2, -rotationDeg)] : [];
1035
+ if (flipH === true || flipV === true) {
1036
+ const sx = flipH === true ? -1 : 1;
1037
+ const sy = flipV === true ? -1 : 1;
1038
+ spin.push(`${formatNumber(sx)} 0 0 ${formatNumber(sy)} ${formatNumber(sx === -1 ? 2 * x + width : 0)} ${formatNumber(sy === -1 ? 2 * y + height : 0)} cm`);
1039
+ }
1040
+ if (!crop) return [...spin, cm(width, height, x, y)];
1041
+ const kept = {
1042
+ w: 1 - crop.left - crop.right,
1043
+ h: 1 - crop.top - crop.bottom
1044
+ };
1045
+ const full = {
1046
+ w: width / kept.w,
1047
+ h: height / kept.h
1048
+ };
1049
+ return [
1050
+ ...spin,
1051
+ `${formatNumber(x)} ${formatNumber(y)} ${formatNumber(width)} ${formatNumber(height)} re`,
1052
+ "W",
1053
+ "n",
1054
+ cm(full.w, full.h, x - crop.left * full.w, y - crop.bottom * full.h)
1055
+ ];
1056
+ }
1057
+ /** A `cm` that turns the frame `deg` counter-clockwise about `(cx, cy)`. */
1058
+ function rotateAboutCm(cx, cy, deg) {
1059
+ const rad = deg * Math.PI / 180;
1060
+ const c = Math.cos(rad);
1061
+ const s = Math.sin(rad);
1062
+ return `${formatNumber(c)} ${formatNumber(s)} ${formatNumber(-s)} ${formatNumber(c)} ${formatNumber(cx - cx * c + cy * s)} ${formatNumber(cy - cx * s - cy * c)} cm`;
1063
+ }
901
1064
  function computeJustifyExtra(line) {
902
- if (line.resolved.alignment !== "both") return 0;
903
- if (line.isLastInParagraph) return 0;
1065
+ const alignment = line.resolved.alignment;
1066
+ if (alignment !== "both" && alignment !== "distribute") return 0;
1067
+ if (line.noJustify === true) return 0;
1068
+ if (line.isLastInParagraph && alignment !== "distribute") return 0;
1069
+ let last = line.tokens.length - 1;
1070
+ while (last >= 0 && line.tokens[last].kind === "text" && line.tokens[last].isSpace) last--;
904
1071
  let spaces = 0;
905
- for (const tok of line.tokens) if (tok.isSpace) spaces++;
1072
+ let narrowest = Infinity;
1073
+ let contentWidthPt = 0;
1074
+ for (let i = 0; i <= last; i++) {
1075
+ const tok = line.tokens[i];
1076
+ contentWidthPt += tok.widthPt;
1077
+ if (!tok.isSpace) continue;
1078
+ spaces++;
1079
+ if (tok.widthPt < narrowest) narrowest = tok.widthPt;
1080
+ }
906
1081
  if (spaces === 0) return 0;
907
- const extra = line.availableWidthPt - line.contentWidthPt;
908
- if (extra <= 0) return 0;
909
- return extra / spaces;
1082
+ const extra = (line.availableWidthPt - contentWidthPt) / spaces;
1083
+ return extra < 0 ? Math.max(extra, -narrowest * GLUE_SHRINK_RATIO) : extra;
910
1084
  }
911
1085
  function hexToRgb01(hex) {
912
1086
  return [
@@ -1,4 +1,4 @@
1
- import { VectorShape } from '../core/vector.js';
1
+ import { VectorPath, VectorShape } from '../core/vector.js';
2
2
  export type { PathSegment, StrokeStyle, VectorPath, VectorShape } from '../core/vector.js';
3
3
  export { PathBuilder } from '../core/vector.js';
4
4
  /**
@@ -15,4 +15,31 @@ export { PathBuilder } from '../core/vector.js';
15
15
  * named `/ExtGState` (its constant alpha) from the page's resources.
16
16
  * @returns The content-stream operator lines.
17
17
  */
18
+ /**
19
+ * §20.1.8.40 `blurRad` — how a blurred shadow is drawn: as `count` copies of
20
+ * the silhouette, each at `alpha` transparency, growing through the blur's
21
+ * width. Shared with the emitter that registers the /ExtGState for it, so the
22
+ * alpha it registers is the one the layers are drawn at.
23
+ *
24
+ * @param shadow The shadow (its `blurPt` and `alpha` are read).
25
+ * @returns The number of layers and the transparency of each.
26
+ */
27
+ export declare function shadowBlurLayers(shadow: {
28
+ readonly blurPt: number;
29
+ readonly alpha: number;
30
+ }): {
31
+ count: number;
32
+ alpha: number;
33
+ };
18
34
  export declare function emitVectorShape(shape: VectorShape, patternName?: string, alphaStateName?: string): Array<string>;
35
+ /**
36
+ * The operators that make `paths` the current CLIP, under `transform` — the
37
+ * same local→y-up-page matrix {@link emitVectorShape} takes. Emitted inside the
38
+ * caller's own `q`…`Q`, and followed by the inverse matrix so what comes after
39
+ * is back in page coordinates.
40
+ *
41
+ * @param paths The clip outline, in the shape's local frame.
42
+ * @param transform The local→page matrix.
43
+ * @returns The operators: `cm`, the path, `W n`, and the inverse `cm`.
44
+ */
45
+ export declare function emitClipPath(paths: ReadonlyArray<VectorPath>, transform: readonly [number, number, number, number, number, number]): Array<string>;