reamkit 1.16.0 → 1.18.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 (76) 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 +526 -8
  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 +219 -0
  19. package/dist/esm/core/ir/flow.d.ts +23 -1
  20. package/dist/esm/core/lzw.d.ts +17 -0
  21. package/dist/esm/core/lzw.js +83 -0
  22. package/dist/esm/core/metafile/emf.d.ts +16 -0
  23. package/dist/esm/core/metafile/emf.js +635 -0
  24. package/dist/esm/core/metafile/picture.d.ts +106 -0
  25. package/dist/esm/core/metafile/picture.js +55 -0
  26. package/dist/esm/core/metafile/wmf.d.ts +9 -0
  27. package/dist/esm/core/metafile/wmf.js +371 -0
  28. package/dist/esm/core/numbering/apply.d.ts +5 -3
  29. package/dist/esm/core/numbering/apply.js +42 -11
  30. package/dist/esm/core/numbering/index.js +2 -0
  31. package/dist/esm/core/numbering/state.d.ts +13 -1
  32. package/dist/esm/core/numbering/state.js +256 -15
  33. package/dist/esm/core/po-helpers.js +16 -1
  34. package/dist/esm/core/style-cascade/resolver.js +66 -11
  35. package/dist/esm/core/style-cascade/table.js +35 -11
  36. package/dist/esm/core/style-cascade/types.d.ts +42 -1
  37. package/dist/esm/core/style-cascade/types.js +4 -0
  38. package/dist/esm/core/tiff.d.ts +20 -0
  39. package/dist/esm/core/tiff.js +339 -0
  40. package/dist/esm/core/vector.d.ts +22 -0
  41. package/dist/esm/excel/print-model.js +4 -2
  42. package/dist/esm/excel/sheet-to-flow.js +10 -8
  43. package/dist/esm/excel/worksheet-parser.js +1 -1
  44. package/dist/esm/html/html-writer.js +20 -5
  45. package/dist/esm/layout/page-doc.d.ts +107 -4
  46. package/dist/esm/layout/styled-layout.d.ts +44 -2
  47. package/dist/esm/layout/styled-layout.js +2330 -283
  48. package/dist/esm/pdf/shading.js +76 -6
  49. package/dist/esm/pdf/styled-page-emitter.js +294 -45
  50. package/dist/esm/pdf/vector-graphics.d.ts +29 -2
  51. package/dist/esm/pdf/vector-graphics.js +96 -12
  52. package/dist/esm/pdf-reader/image-decode.js +7 -75
  53. package/dist/esm/pptx/pptx-reader.js +1 -0
  54. package/dist/esm/pptx/slide-parser.d.ts +21 -4
  55. package/dist/esm/pptx/slide-parser.js +86 -23
  56. package/dist/esm/svg/svg-writer.js +2 -2
  57. package/dist/esm/word/document-parser.d.ts +96 -7
  58. package/dist/esm/word/document-parser.js +540 -116
  59. package/dist/esm/word/docx-reader.js +135 -43
  60. package/dist/esm/word/docx-writer.js +6 -1
  61. package/dist/esm/word/drawing-parser.d.ts +58 -5
  62. package/dist/esm/word/drawing-parser.js +1632 -63
  63. package/dist/esm/word/index.d.ts +4 -4
  64. package/dist/esm/word/numbering-parser.d.ts +2 -1
  65. package/dist/esm/word/numbering-parser.js +107 -21
  66. package/dist/esm/word/paragraph-properties.d.ts +11 -1
  67. package/dist/esm/word/paragraph-properties.js +219 -10
  68. package/dist/esm/word/run-properties.js +45 -1
  69. package/dist/esm/word/settings-parser.d.ts +24 -0
  70. package/dist/esm/word/settings-parser.js +31 -5
  71. package/dist/esm/word/shading.d.ts +11 -0
  72. package/dist/esm/word/shading.js +64 -0
  73. package/dist/esm/word/styles-parser.js +8 -2
  74. package/dist/esm/word/table-parser.d.ts +9 -1
  75. package/dist/esm/word/table-parser.js +182 -30
  76. 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";
@@ -15,6 +15,33 @@ import { addSignaturePlaceholder } from "./signature.js";
15
15
  import { buildXmpPacket } from "./xmp.js";
16
16
  //#region src/pdf/styled-page-emitter.ts
17
17
  var encoder = new TextEncoder();
18
+ function washVeil(wash) {
19
+ if (!wash) return void 0;
20
+ const alpha = 1 - wash.gain;
21
+ if (!(alpha > .004)) return void 0;
22
+ const offset = .5 * (1 - wash.gain) + wash.black;
23
+ return {
24
+ alpha: Math.min(1, alpha),
25
+ grey: Math.max(0, Math.min(1, offset / alpha))
26
+ };
27
+ }
28
+ /**
29
+ * The veil operators for a washed picture, to follow its own `/Im Do`: the
30
+ * placement matrix maps the unit square onto the picture as drawn, so the wash
31
+ * covers exactly it — cropped, rotated and flipped along with it.
32
+ */
33
+ function washItems(wash, alphaStateNames) {
34
+ const veil = washVeil(wash);
35
+ if (!veil) return [];
36
+ const g = formatNumber(veil.grey);
37
+ const state = veil.alpha < 1 ? alphaStateNames?.get(Math.round(veil.alpha * 1e3) / 1e3) : void 0;
38
+ return [
39
+ ...state === void 0 ? [] : [`/${state} gs`],
40
+ `${g} ${g} ${g} rg`,
41
+ "0 0 1 1 re",
42
+ "f"
43
+ ];
44
+ }
18
45
  /**
19
46
  * The emit phase: turn a laid-out document into PDF objects (ISO 32000) and
20
47
  * serialize it. The counterpart of `layoutStyledDocument` (the seam of
@@ -97,17 +124,29 @@ function assembleStyledPdf(laid, options, doc) {
97
124
  }
98
125
  const alphaStateNames = /* @__PURE__ */ new Map();
99
126
  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;
127
+ const wantAlpha = (alpha) => {
128
+ if (alpha === void 0 || alpha >= 1) return;
103
129
  const key = Math.round(alpha * 1e3) / 1e3;
104
- if (alphaStateNames.has(key)) continue;
130
+ if (alphaStateNames.has(key)) return;
105
131
  const nm = `GSa${alphaStateNames.size}`;
106
132
  alphaStateNames.set(key, nm);
107
133
  extGStateEntries[nm] = dict({
108
134
  ca: key,
109
135
  CA: key
110
136
  });
137
+ };
138
+ for (const page of renderedPages) {
139
+ const plan = paintPlan(page.commands);
140
+ for (const item of plan.shapes) {
141
+ const s = item.shape.shadow;
142
+ if (s) wantAlpha(shadowBlurLayers(s).alpha);
143
+ wantAlpha(item.shape.fillAlpha);
144
+ }
145
+ for (const img of plan.images) wantAlpha(washVeil(img.wash)?.alpha);
146
+ for (const line of plan.lines) for (const tok of line.line.tokens) {
147
+ if (tok.kind === "image" && tok.shadow) wantAlpha(shadowBlurLayers(tok.shadow).alpha);
148
+ if (tok.kind === "image") wantAlpha(washVeil(tok.wash)?.alpha);
149
+ }
111
150
  }
112
151
  const resourcesDict = dict({
113
152
  Font: fontResourceDict,
@@ -378,12 +417,16 @@ function defaultPageCtx() {
378
417
  even: { commands: [] }
379
418
  },
380
419
  titlePg: false,
381
- evenAndOddHeaders: false
420
+ evenAndOddHeaders: false,
421
+ continuous: false
382
422
  };
383
423
  }
384
424
  function embedImageResources(doc, laid) {
385
425
  const out = /* @__PURE__ */ new Map();
386
- for (const [resourceId, res] of laid.imageResources) out.set(resourceId, addImage(doc, res.prepared).ref);
426
+ for (const [resourceId, res] of laid.imageResources) {
427
+ if (!res.prepared) continue;
428
+ out.set(resourceId, addImage(doc, res.prepared).ref);
429
+ }
387
430
  return out;
388
431
  }
389
432
  function embedFontResources(doc, laid, pdfaProfile) {
@@ -445,21 +488,37 @@ function emitTaggedRuns(out, cmds, tagging, body) {
445
488
  close();
446
489
  }
447
490
  /**
448
- * The PDF dash array for a §18.18.3 border style, in points.
491
+ * The dash array for a §18.18.3 / §17.18.2 border style, in points.
449
492
  *
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.
493
+ * A pattern is a pattern, not a weight: Word and Excel both draw the same
494
+ * dashes on a hairline and on a 3pt rule, and both references agree on the
495
+ * lengths — a `dashed` page border strokes `8 2.5` whether its `w:sz` is 4 or
496
+ * 24. Scaling them by the width instead drew page-borders-export-case-2.docx's
497
+ * half-point frame as a row of specks a quarter the length it should be.
453
498
  */
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
- }
499
+ var BORDER_DASHES = new Map([
500
+ ["dashed", [8, 2.5]],
501
+ ["dashSmallGap", [3, 1]],
502
+ ["dotted", [.5, 1]],
503
+ ["dashDot", [
504
+ 8,
505
+ 2.5,
506
+ 2.5,
507
+ 2.5
508
+ ]],
509
+ ["dashDotDot", [
510
+ 8,
511
+ 2.5,
512
+ 2.5,
513
+ 2.5,
514
+ 2.5,
515
+ 2.5
516
+ ]]
517
+ ]);
518
+ /** The PDF dash array for a border style; an empty string means a solid rule. */
519
+ function dashPatternFor(style) {
520
+ const pattern = style !== void 0 ? BORDER_DASHES.get(style) : void 0;
521
+ return pattern ? `[${pattern.map(formatNumber).join(" ")}]` : "";
463
522
  }
464
523
  function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
465
524
  const commands = page.commands;
@@ -491,8 +550,20 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
491
550
  }
492
551
  emitTaggedRuns(out, plan.images.filter((c) => c.imageResourceName !== ""), tagging, (img) => {
493
552
  out.push("q");
494
- out.push(`${formatNumber(img.width)} 0 0 ${formatNumber(img.height)} ${formatNumber(img.x)} ${formatNumber(H - img.y - img.height)} cm`);
553
+ if (img.clip) {
554
+ const t = img.clip.transform;
555
+ out.push(...emitClipPath(img.clip.paths, [
556
+ t[0],
557
+ -t[1],
558
+ t[2],
559
+ -t[3],
560
+ t[4],
561
+ H - t[5]
562
+ ]));
563
+ }
564
+ out.push(...placeImage(img.x, H - img.y - img.height, img.width, img.height, img.crop, img.rotationDeg, img.flipH, img.flipV));
495
565
  out.push(`/${img.imageResourceName} Do`);
566
+ out.push(...washItems(img.wash, alphaStateNames));
496
567
  out.push("Q");
497
568
  });
498
569
  const borders = plan.borders;
@@ -506,7 +577,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
506
577
  for (const b of borders) {
507
578
  const width = b.borderSizePt;
508
579
  const color = b.borderColorHex;
509
- const dash = dashPatternFor(b.borderStyle, width);
580
+ const dash = dashPatternFor(b.borderStyle);
510
581
  if (dash !== lastDash) {
511
582
  out.push(dash === "" ? "[] 0 d 2 J" : `${dash} 0 d 0 J`);
512
583
  lastDash = dash;
@@ -578,14 +649,17 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
578
649
  H - t[5]
579
650
  ]
580
651
  };
581
- const alpha = sh.shape.shadow?.alpha;
582
- const alphaState = alpha !== void 0 && alpha < 1 ? alphaStateNames?.get(Math.round(alpha * 1e3) / 1e3) : void 0;
583
- for (const op of emitVectorShape(shape, gradientNames?.get(sh.shape), alphaState)) out.push(op);
652
+ const shadowLayer = sh.shape.shadow ? shadowBlurLayers(sh.shape.shadow) : void 0;
653
+ const alphaState = shadowLayer && shadowLayer.alpha < 1 ? alphaStateNames?.get(Math.round(shadowLayer.alpha * 1e3) / 1e3) : void 0;
654
+ const fillAlpha = sh.shape.fillAlpha;
655
+ const fillAlphaState = fillAlpha !== void 0 && fillAlpha < 1 ? alphaStateNames?.get(Math.round(fillAlpha * 1e3) / 1e3) : void 0;
656
+ for (const op of emitVectorShape(shape, gradientNames?.get(sh.shape), alphaState, fillAlphaState)) out.push(op);
584
657
  });
585
658
  const lines = plan.lines;
586
659
  if (lines.length > 0) {
587
660
  let inBT = false;
588
661
  let lastFont = "";
662
+ let lastTc = 0;
589
663
  let lastSize = -1;
590
664
  let lastColor = "";
591
665
  const switchFontIfNeeded = (tok) => {
@@ -595,6 +669,11 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
595
669
  lastFont = fontKey;
596
670
  lastSize = tok.fontSizePt;
597
671
  }
672
+ const tc = tok.resolvedRun.letterSpacingPt ?? 0;
673
+ if (tc !== lastTc) {
674
+ out.push(`${formatNumber(tc)} Tc`);
675
+ lastTc = tc;
676
+ }
598
677
  if (tok.resolvedRun.colorHex !== lastColor) {
599
678
  const [r, g, b] = hexToRgb01(tok.resolvedRun.colorHex);
600
679
  out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} rg`);
@@ -602,18 +681,87 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
602
681
  }
603
682
  };
604
683
  const emitImageToken = (tok, x, baselineY) => {
684
+ const meta = tok.metafile;
685
+ if (meta) {
686
+ if (inBT) {
687
+ out.push("ET");
688
+ inBT = false;
689
+ }
690
+ const b = tok.drawBox;
691
+ const ox = x + (b?.dxPt ?? 0);
692
+ const oy = baselineY + (b?.dyPt ?? 0);
693
+ for (const sh of meta.shapes) for (const op of emitVectorShape({
694
+ ...sh,
695
+ transform: [
696
+ 1,
697
+ 0,
698
+ 0,
699
+ 1,
700
+ ox,
701
+ oy
702
+ ]
703
+ })) out.push(op);
704
+ for (const t of meta.texts) {
705
+ const first = t.line.tokens.find((k) => k.kind === "text");
706
+ if (!first) continue;
707
+ const [r, g, bl] = hexToRgb01(first.resolvedRun.colorHex);
708
+ out.push("BT");
709
+ out.push(`/${first.font.resourceName} ${formatNumber(first.fontSizePt)} Tf`);
710
+ out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(bl)} rg`);
711
+ out.push(`1 0 0 1 ${formatNumber(ox + t.x)} ${formatNumber(oy + t.y)} Tm`);
712
+ out.push(first.font.measure.showText(first.text));
713
+ out.push("ET");
714
+ }
715
+ lastFont = "";
716
+ lastSize = -1;
717
+ lastColor = "";
718
+ return;
719
+ }
605
720
  if (!tok.imageResourceName) return;
606
721
  if (inBT) {
607
722
  out.push("ET");
608
723
  inBT = false;
609
724
  }
725
+ const shdw = tok.shadow;
726
+ if (shdw) {
727
+ const bx = tok.drawBox;
728
+ const [shr, shg, shb] = hexToRgb01(shdw.colorHex);
729
+ const layers = shadowBlurLayers(shdw);
730
+ const state = layers.alpha < 1 ? alphaStateNames?.get(Math.round(layers.alpha * 1e3) / 1e3) : void 0;
731
+ const sx = x + (bx?.dxPt ?? 0) + shdw.dxPt;
732
+ const sy = baselineY + (bx?.dyPt ?? 0) - shdw.dyPt;
733
+ const sw = bx?.widthPt ?? tok.widthPt;
734
+ const sh = bx?.heightPt ?? tok.heightPt;
735
+ out.push("q");
736
+ if (state) out.push(`/${state} gs`);
737
+ out.push(`${formatNumber(shr)} ${formatNumber(shg)} ${formatNumber(shb)} rg`);
738
+ for (let i = 0; i < layers.count; i++) {
739
+ const grow = layers.count > 1 ? -shdw.blurPt / 2 + (i + .5) * shdw.blurPt / layers.count : 0;
740
+ out.push(`${formatNumber(sx - grow)} ${formatNumber(sy - grow)} ${formatNumber(Math.max(sw + 2 * grow, 0))} ${formatNumber(Math.max(sh + 2 * grow, 0))} re`);
741
+ out.push("f");
742
+ }
743
+ out.push("Q");
744
+ }
610
745
  out.push("q");
611
- out.push(`${formatNumber(tok.widthPt)} 0 0 ${formatNumber(tok.heightPt)} ${formatNumber(x)} ${formatNumber(baselineY)} cm`);
746
+ const b = tok.drawBox;
747
+ 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
748
  out.push(`/${tok.imageResourceName} Do`);
749
+ out.push(...washItems(tok.wash, alphaStateNames));
613
750
  out.push("Q");
751
+ const frame = tok.outline;
752
+ if (frame) {
753
+ const [fr, fg, fb] = hexToRgb01(frame.colorHex);
754
+ out.push("q");
755
+ out.push(`${formatNumber(frame.widthPt)} w`);
756
+ out.push(`${formatNumber(fr)} ${formatNumber(fg)} ${formatNumber(fb)} RG`);
757
+ out.push(`${formatNumber(x + (b?.dxPt ?? 0))} ${formatNumber(baselineY + (b?.dyPt ?? 0))} ${formatNumber(b?.widthPt ?? tok.widthPt)} ${formatNumber(b?.heightPt ?? tok.heightPt)} re`);
758
+ out.push("S");
759
+ out.push("Q");
760
+ }
614
761
  lastFont = "";
615
762
  lastSize = -1;
616
763
  lastColor = "";
764
+ lastTc = 0;
617
765
  };
618
766
  const emitMathToken = (tok, originX, baselineY) => {
619
767
  for (const it of tok.items) if (it.kind === "glyph") {
@@ -631,7 +779,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
631
779
  lastColor = "000000";
632
780
  }
633
781
  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`);
782
+ out.push(it.font.measure.showText(it.text));
635
783
  } else if (it.kind === "rule") {
636
784
  if (inBT) {
637
785
  out.push("ET");
@@ -687,6 +835,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
687
835
  lastFont = "";
688
836
  lastSize = -1;
689
837
  lastColor = "";
838
+ lastTc = 0;
690
839
  out.push("BT");
691
840
  inBT = true;
692
841
  body(cmd);
@@ -698,6 +847,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
698
847
  lastFont = "";
699
848
  lastSize = -1;
700
849
  lastColor = "";
850
+ lastTc = 0;
701
851
  };
702
852
  const emitOneLine = (cmd) => {
703
853
  const line = cmd.line;
@@ -749,12 +899,40 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
749
899
  const hasImageToken = line.tokens.some((t) => t.kind === "image");
750
900
  const hasMathToken = line.tokens.some((t) => t.kind === "math");
751
901
  const hasRtl = line.tokens.some((t) => t.bidiLevel % 2 === 1);
902
+ const hasTab = line.tokens.some((t) => t.kind === "text" && t.tab === true);
752
903
  /** A text token carrying underline or strikethrough. */
753
904
  const isDecorated = (tok) => tok.kind === "text" && (tok.resolvedRun.underline !== "none" || tok.resolvedRun.strike);
754
- const encodeToken = (tok) => {
905
+ const showToken = (tok) => {
755
906
  const text = tok.bidiLevel % 2 === 1 ? reverseByCodePoint(tok.text) : tok.text;
756
- return tok.font.measure.encodeTextAsCidHex(text);
907
+ return tok.font.measure.showText(text);
757
908
  };
909
+ if (line.tokens.some((t) => t.kind === "text" && t.resolvedRun.shadingColorHex)) {
910
+ if (inBT) {
911
+ out.push("ET");
912
+ inBT = false;
913
+ }
914
+ const shAscent = Math.max(lineFs, line.mathAscentPt ?? 0);
915
+ const shDescent = Math.max(lineFs * .2, line.mathDescentPt ?? 0);
916
+ out.push("q");
917
+ let sx = originX;
918
+ let painted = "";
919
+ for (const tok of line.tokens) {
920
+ const w = tok.widthPt + (tok.kind === "text" && tok.isSpace ? extraPerSpace : 0);
921
+ const hex = tok.kind === "text" ? tok.resolvedRun.shadingColorHex : void 0;
922
+ if (hex !== void 0) {
923
+ if (hex !== painted) {
924
+ if (painted !== "") out.push("f");
925
+ const [sr, sg, sb] = hexToRgb01(hex);
926
+ out.push(`${formatNumber(sr)} ${formatNumber(sg)} ${formatNumber(sb)} rg`);
927
+ painted = hex;
928
+ }
929
+ out.push(`${formatNumber(sx)} ${formatNumber(baselineY - shDescent)} ${formatNumber(w)} ${formatNumber(shAscent + shDescent)} re`);
930
+ }
931
+ sx += w;
932
+ }
933
+ if (painted !== "") out.push("f");
934
+ out.push("Q");
935
+ }
758
936
  if (line.tokens.some((t) => t.kind === "text" && t.highlight === true)) {
759
937
  if (inBT) {
760
938
  out.push("ET");
@@ -784,12 +962,30 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
784
962
  for (const tok of line.tokens) {
785
963
  const w = tok.widthPt + (tok.kind === "text" && tok.isSpace ? extraPerSpace : 0);
786
964
  if (isDecorated(tok)) {
787
- const [r, g, b] = hexToRgb01(tok.resolvedRun.colorHex);
788
965
  const thickness = Math.max(.4, tok.fontSizePt * .06);
789
- out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} rg`);
966
+ const paint = (hex) => {
967
+ const [r, g, b] = hexToRgb01(hex);
968
+ out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} rg`);
969
+ };
970
+ paint(tok.resolvedRun.colorHex);
790
971
  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`);
972
+ if (tok.resolvedRun.underlineColorHex) paint(tok.resolvedRun.underlineColorHex);
973
+ const style = tok.resolvedRun.underline;
974
+ const t = style === "thick" || style === "dottedHeavy" || style === "dashHeavy" ? thickness * 2 : thickness;
975
+ const y = baselineY - tok.fontSizePt * .12 - t;
976
+ const dashed = style === "dotted" || style === "dottedHeavy" ? [t, t * 2] : style === "dash" || style === "dashHeavy" ? [t * 4, t * 3] : void 0;
977
+ if (dashed) {
978
+ out.push(`${formatNumber(t)} w`);
979
+ out.push(`[${dashed.map((n) => formatNumber(n)).join(" ")}] 0 d`);
980
+ const [r, g, b] = hexToRgb01(tok.resolvedRun.underlineColorHex ?? tok.resolvedRun.colorHex);
981
+ out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} RG`);
982
+ out.push(`${formatNumber(dx)} ${formatNumber(y + t / 2)} m ${formatNumber(dx + w)} ${formatNumber(y + t / 2)} l S`);
983
+ out.push("[] 0 d");
984
+ } else {
985
+ out.push(`${formatNumber(dx)} ${formatNumber(y)} ${formatNumber(w)} ${formatNumber(t)} re f`);
986
+ if (style === "double") out.push(`${formatNumber(dx)} ${formatNumber(y - t * 2)} ${formatNumber(w)} ${formatNumber(t)} re f`);
987
+ }
988
+ if (tok.resolvedRun.underlineColorHex) paint(tok.resolvedRun.colorHex);
793
989
  }
794
990
  if (tok.resolvedRun.strike) {
795
991
  const y = baselineY + tok.fontSizePt * .26;
@@ -813,9 +1009,9 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
813
1009
  for (const tok of line.tokens) {
814
1010
  if (tok.kind !== "text") continue;
815
1011
  switchFontIfNeeded(tok);
816
- out.push(`<${tok.font.measure.encodeTextAsCidHex(tok.text)}> Tj`);
1012
+ out.push(tok.font.measure.showText(tok.text));
817
1013
  }
818
- } else if (extraPerSpace > 0 || hasImageToken || hasMathToken || hasRtl || hasRise) {
1014
+ } else if (extraPerSpace !== 0 || hasImageToken || hasMathToken || hasRtl || hasRise || hasTab) {
819
1015
  const order = hasRtl ? lineVisualOrder(line) : line.tokens.map((_, i) => i);
820
1016
  let x = originX;
821
1017
  for (const ti of order) {
@@ -838,7 +1034,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
838
1034
  }
839
1035
  switchFontIfNeeded(tok);
840
1036
  out.push(`1 0 0 1 ${formatNumber(x)} ${formatNumber(baselineY + (tok.risePt ?? 0))} Tm`);
841
- out.push(`<${encodeToken(tok)}> Tj`);
1037
+ out.push(showToken(tok));
842
1038
  const tokenX0 = x;
843
1039
  x += tok.widthPt;
844
1040
  if (tok.isSpace) x += extraPerSpace;
@@ -855,8 +1051,7 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
855
1051
  for (const tok of line.tokens) {
856
1052
  if (tok.kind !== "text") continue;
857
1053
  switchFontIfNeeded(tok);
858
- const hex = tok.font.measure.encodeTextAsCidHex(tok.text);
859
- out.push(`<${hex}> Tj`);
1054
+ out.push(tok.font.measure.showText(tok.text));
860
1055
  trackLink(tok.href, tok.anchor, tok.text, x, x + tok.widthPt);
861
1056
  x += tok.widthPt;
862
1057
  }
@@ -898,15 +1093,69 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
898
1093
  function lineVisualOrder(line) {
899
1094
  return reorderVisual(line.tokens.map((t) => t.bidiLevel));
900
1095
  }
1096
+ /**
1097
+ * The operators that put an image XObject in a box — ISO 32000-1 §8.9.5.1: the
1098
+ * unit square scaled to the box and moved to `(x, y)`, its bottom-left corner.
1099
+ *
1100
+ * A `a:srcRect` crop (§20.1.8.55) cuts edges off the SOURCE and fits what is
1101
+ * left to the same box, so the picture is drawn larger than the box and the box
1102
+ * is made a clipping path. Ignored, ImageCrop.docx drew the whole mountain
1103
+ * where LibreOffice shows the peak.
1104
+ *
1105
+ * @returns The content-stream lines, to be pushed between `q` and the `Do`.
1106
+ */
1107
+ function placeImage(x, y, width, height, crop, rotationDeg, flipH, flipV) {
1108
+ const cm = (w, h, ox, oy) => `${formatNumber(w)} 0 0 ${formatNumber(h)} ${formatNumber(ox)} ${formatNumber(oy)} cm`;
1109
+ const spin = rotationDeg ? [rotateAboutCm(x + width / 2, y + height / 2, -rotationDeg)] : [];
1110
+ if (flipH === true || flipV === true) {
1111
+ const sx = flipH === true ? -1 : 1;
1112
+ const sy = flipV === true ? -1 : 1;
1113
+ spin.push(`${formatNumber(sx)} 0 0 ${formatNumber(sy)} ${formatNumber(sx === -1 ? 2 * x + width : 0)} ${formatNumber(sy === -1 ? 2 * y + height : 0)} cm`);
1114
+ }
1115
+ if (!crop) return [...spin, cm(width, height, x, y)];
1116
+ const kept = {
1117
+ w: 1 - crop.left - crop.right,
1118
+ h: 1 - crop.top - crop.bottom
1119
+ };
1120
+ const full = {
1121
+ w: width / kept.w,
1122
+ h: height / kept.h
1123
+ };
1124
+ return [
1125
+ ...spin,
1126
+ `${formatNumber(x)} ${formatNumber(y)} ${formatNumber(width)} ${formatNumber(height)} re`,
1127
+ "W",
1128
+ "n",
1129
+ cm(full.w, full.h, x - crop.left * full.w, y - crop.bottom * full.h)
1130
+ ];
1131
+ }
1132
+ /** A `cm` that turns the frame `deg` counter-clockwise about `(cx, cy)`. */
1133
+ function rotateAboutCm(cx, cy, deg) {
1134
+ const rad = deg * Math.PI / 180;
1135
+ const c = Math.cos(rad);
1136
+ const s = Math.sin(rad);
1137
+ return `${formatNumber(c)} ${formatNumber(s)} ${formatNumber(-s)} ${formatNumber(c)} ${formatNumber(cx - cx * c + cy * s)} ${formatNumber(cy - cx * s - cy * c)} cm`;
1138
+ }
901
1139
  function computeJustifyExtra(line) {
902
- if (line.resolved.alignment !== "both") return 0;
903
- if (line.isLastInParagraph) return 0;
1140
+ const alignment = line.resolved.alignment;
1141
+ if (alignment !== "both" && alignment !== "distribute") return 0;
1142
+ if (line.noJustify === true) return 0;
1143
+ if (line.isLastInParagraph && alignment !== "distribute") return 0;
1144
+ let last = line.tokens.length - 1;
1145
+ while (last >= 0 && line.tokens[last].kind === "text" && line.tokens[last].isSpace) last--;
904
1146
  let spaces = 0;
905
- for (const tok of line.tokens) if (tok.isSpace) spaces++;
1147
+ let narrowest = Infinity;
1148
+ let contentWidthPt = 0;
1149
+ for (let i = 0; i <= last; i++) {
1150
+ const tok = line.tokens[i];
1151
+ contentWidthPt += tok.widthPt;
1152
+ if (!tok.isSpace) continue;
1153
+ spaces++;
1154
+ if (tok.widthPt < narrowest) narrowest = tok.widthPt;
1155
+ }
906
1156
  if (spaces === 0) return 0;
907
- const extra = line.availableWidthPt - line.contentWidthPt;
908
- if (extra <= 0) return 0;
909
- return extra / spaces;
1157
+ const extra = (line.availableWidthPt - contentWidthPt) / spaces;
1158
+ return extra < 0 ? Math.max(extra, -narrowest * GLUE_SHRINK_RATIO) : extra;
910
1159
  }
911
1160
  function hexToRgb01(hex) {
912
1161
  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
- export declare function emitVectorShape(shape: VectorShape, patternName?: string, alphaStateName?: string): Array<string>;
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
+ };
34
+ export declare function emitVectorShape(shape: VectorShape, patternName?: string, alphaStateName?: string, fillAlphaStateName?: 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>;