reamkit 1.15.4 → 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 (127) hide show
  1. package/dist/esm/core/bidi/segments.js +1 -2
  2. package/dist/esm/core/converter/facade.d.ts +5 -0
  3. package/dist/esm/core/converter/project.js +5 -1
  4. package/dist/esm/core/converter/ream.d.ts +6 -0
  5. package/dist/esm/core/converter/ream.js +34 -8
  6. package/dist/esm/core/document-model/index.d.ts +1 -1
  7. package/dist/esm/core/document-model/types.d.ts +659 -7
  8. package/dist/esm/core/drawingml/chart-geometry.d.ts +34 -10
  9. package/dist/esm/core/drawingml/chart-geometry.js +424 -92
  10. package/dist/esm/core/drawingml/chart-parser.js +301 -11
  11. package/dist/esm/core/drawingml/colors.d.ts +7 -7
  12. package/dist/esm/core/drawingml/colors.js +84 -18
  13. package/dist/esm/core/drawingml/preset-geometry.js +240 -6
  14. package/dist/esm/core/drawingml/shape-render.d.ts +12 -0
  15. package/dist/esm/core/drawingml/shape-render.js +116 -3
  16. package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
  17. package/dist/esm/core/drawingml/theme-parser.d.ts +51 -0
  18. package/dist/esm/core/drawingml/theme-parser.js +92 -1
  19. package/dist/esm/core/font/measure.d.ts +5 -0
  20. package/dist/esm/core/font/measure.js +21 -1
  21. package/dist/esm/core/font/ttf-subset.js +26 -1
  22. package/dist/esm/core/images.d.ts +2 -2
  23. package/dist/esm/core/images.js +353 -43
  24. package/dist/esm/core/indexed-colors.d.ts +11 -0
  25. package/dist/esm/core/indexed-colors.js +81 -0
  26. package/dist/esm/core/ir/flow.d.ts +23 -1
  27. package/dist/esm/core/ir/sheet.d.ts +61 -0
  28. package/dist/esm/{excel → core}/number-format.d.ts +37 -0
  29. package/dist/esm/core/number-format.js +800 -0
  30. package/dist/esm/core/numbering/apply.d.ts +5 -3
  31. package/dist/esm/core/numbering/apply.js +42 -11
  32. package/dist/esm/core/numbering/index.js +2 -0
  33. package/dist/esm/core/numbering/state.d.ts +13 -1
  34. package/dist/esm/core/numbering/state.js +256 -15
  35. package/dist/esm/core/opc/alternate-content.d.ts +13 -0
  36. package/dist/esm/core/opc/alternate-content.js +72 -0
  37. package/dist/esm/core/opc/core-properties.js +1 -0
  38. package/dist/esm/core/opc/package.d.ts +14 -1
  39. package/dist/esm/core/opc/package.js +43 -4
  40. package/dist/esm/core/opc/relationships.js +1 -0
  41. package/dist/esm/core/opc/xml-entities.d.ts +12 -0
  42. package/dist/esm/core/opc/xml-entities.js +91 -0
  43. package/dist/esm/core/po-helpers.js +16 -1
  44. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  45. package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
  46. package/dist/esm/core/style-cascade/resolver.js +66 -11
  47. package/dist/esm/core/style-cascade/table.js +35 -11
  48. package/dist/esm/core/style-cascade/types.d.ts +42 -1
  49. package/dist/esm/core/style-cascade/types.js +4 -0
  50. package/dist/esm/core/vector.d.ts +30 -0
  51. package/dist/esm/excel/activex-parser.js +1 -0
  52. package/dist/esm/excel/column-bands.d.ts +26 -1
  53. package/dist/esm/excel/column-bands.js +156 -20
  54. package/dist/esm/excel/comments-parser.js +5 -3
  55. package/dist/esm/excel/conditional-format.d.ts +25 -2
  56. package/dist/esm/excel/conditional-format.js +63 -19
  57. package/dist/esm/excel/escaped-text.d.ts +9 -0
  58. package/dist/esm/excel/escaped-text.js +20 -0
  59. package/dist/esm/excel/form-control-parser.js +1 -0
  60. package/dist/esm/excel/formula/dates.js +1 -1
  61. package/dist/esm/excel/header-footer.d.ts +9 -1
  62. package/dist/esm/excel/header-footer.js +162 -14
  63. package/dist/esm/excel/index.d.ts +1 -1
  64. package/dist/esm/excel/index.js +2 -2
  65. package/dist/esm/excel/pivot-table-parser.js +1 -0
  66. package/dist/esm/excel/print-model.d.ts +75 -0
  67. package/dist/esm/excel/print-model.js +899 -77
  68. package/dist/esm/excel/printer-settings.d.ts +16 -0
  69. package/dist/esm/excel/printer-settings.js +36 -0
  70. package/dist/esm/excel/rich-value.d.ts +13 -0
  71. package/dist/esm/excel/rich-value.js +120 -0
  72. package/dist/esm/excel/shared-strings-parser.js +11 -7
  73. package/dist/esm/excel/sheet-drawing.d.ts +11 -0
  74. package/dist/esm/excel/sheet-drawing.js +26 -3
  75. package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
  76. package/dist/esm/excel/sheet-shape-parser.js +461 -32
  77. package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
  78. package/dist/esm/excel/sheet-to-flow.js +553 -36
  79. package/dist/esm/excel/slicer-parser.js +1 -0
  80. package/dist/esm/excel/styles-parser.d.ts +30 -2
  81. package/dist/esm/excel/styles-parser.js +177 -40
  82. package/dist/esm/excel/table-parser.js +1 -0
  83. package/dist/esm/excel/vml-drawing.d.ts +68 -0
  84. package/dist/esm/excel/vml-drawing.js +158 -0
  85. package/dist/esm/excel/workbook-parser.d.ts +5 -0
  86. package/dist/esm/excel/workbook-parser.js +5 -1
  87. package/dist/esm/excel/worksheet-parser.d.ts +2 -1
  88. package/dist/esm/excel/worksheet-parser.js +150 -61
  89. package/dist/esm/excel/xlsx-reader.js +242 -11
  90. package/dist/esm/excel/xlsx-to-pdf.js +12 -1
  91. package/dist/esm/html/html-writer.js +29 -6
  92. package/dist/esm/layout/page-doc.d.ts +87 -1
  93. package/dist/esm/layout/styled-layout.d.ts +46 -2
  94. package/dist/esm/layout/styled-layout.js +2345 -274
  95. package/dist/esm/pdf/cid-font.js +20 -9
  96. package/dist/esm/pdf/objects.d.ts +13 -0
  97. package/dist/esm/pdf/objects.js +18 -1
  98. package/dist/esm/pdf/shading.js +76 -6
  99. package/dist/esm/pdf/styled-page-emitter.js +355 -49
  100. package/dist/esm/pdf/text-page-renderer.js +1 -0
  101. package/dist/esm/pdf/vector-graphics.d.ts +31 -2
  102. package/dist/esm/pdf/vector-graphics.js +103 -3
  103. package/dist/esm/pptx/pptx-reader.js +2 -0
  104. package/dist/esm/pptx/slide-parser.d.ts +21 -4
  105. package/dist/esm/pptx/slide-parser.js +98 -24
  106. package/dist/esm/svg/svg-writer.js +23 -4
  107. package/dist/esm/word/document-parser.d.ts +96 -7
  108. package/dist/esm/word/document-parser.js +538 -116
  109. package/dist/esm/word/docx-reader.js +135 -43
  110. package/dist/esm/word/docx-writer.js +6 -1
  111. package/dist/esm/word/drawing-parser.d.ts +86 -5
  112. package/dist/esm/word/drawing-parser.js +1590 -54
  113. package/dist/esm/word/index.d.ts +4 -4
  114. package/dist/esm/word/numbering-parser.d.ts +2 -1
  115. package/dist/esm/word/numbering-parser.js +108 -21
  116. package/dist/esm/word/paragraph-properties.d.ts +11 -1
  117. package/dist/esm/word/paragraph-properties.js +219 -10
  118. package/dist/esm/word/run-properties.js +45 -1
  119. package/dist/esm/word/settings-parser.d.ts +24 -0
  120. package/dist/esm/word/settings-parser.js +32 -5
  121. package/dist/esm/word/shading.d.ts +11 -0
  122. package/dist/esm/word/shading.js +64 -0
  123. package/dist/esm/word/styles-parser.js +9 -2
  124. package/dist/esm/word/table-parser.d.ts +9 -1
  125. package/dist/esm/word/table-parser.js +182 -30
  126. package/package.json +7 -3
  127. package/dist/esm/excel/number-format.js +0 -476
@@ -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";
@@ -95,10 +95,32 @@ function assembleStyledPdf(laid, options, doc) {
95
95
  gradientNames.set(item.shape, nm);
96
96
  }
97
97
  }
98
+ const alphaStateNames = /* @__PURE__ */ new Map();
99
+ const extGStateEntries = {};
100
+ const wantAlpha = (alpha) => {
101
+ if (alpha === void 0 || alpha >= 1) return;
102
+ const key = Math.round(alpha * 1e3) / 1e3;
103
+ if (alphaStateNames.has(key)) return;
104
+ const nm = `GSa${alphaStateNames.size}`;
105
+ alphaStateNames.set(key, nm);
106
+ extGStateEntries[nm] = dict({
107
+ ca: key,
108
+ CA: key
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);
118
+ }
98
119
  const resourcesDict = dict({
99
120
  Font: fontResourceDict,
100
121
  ...xobjectResourceDict ? { XObject: xobjectResourceDict } : {},
101
- ...Object.keys(patternEntries).length > 0 ? { Pattern: dict(patternEntries) } : {}
122
+ ...Object.keys(patternEntries).length > 0 ? { Pattern: dict(patternEntries) } : {},
123
+ ...Object.keys(extGStateEntries).length > 0 ? { ExtGState: dict(extGStateEntries) } : {}
102
124
  });
103
125
  let pdfaIccRef;
104
126
  let transparencyGroup;
@@ -125,7 +147,7 @@ function assembleStyledPdf(laid, options, doc) {
125
147
  tagFor: (structId) => builder.node(structId).type
126
148
  };
127
149
  }
128
- const { content: contentBytes, links } = emitPageContent(page, pageTagging, gradientNames);
150
+ const { content: contentBytes, links } = emitPageContent(page, pageTagging, gradientNames, alphaStateNames);
129
151
  const contentsRef = doc.add(stream({}, contentBytes));
130
152
  const pageEntries = {
131
153
  Type: name("Page"),
@@ -363,7 +385,8 @@ function defaultPageCtx() {
363
385
  even: { commands: [] }
364
386
  },
365
387
  titlePg: false,
366
- evenAndOddHeaders: false
388
+ evenAndOddHeaders: false,
389
+ continuous: false
367
390
  };
368
391
  }
369
392
  function embedImageResources(doc, laid) {
@@ -374,10 +397,13 @@ function embedImageResources(doc, laid) {
374
397
  function embedFontResources(doc, laid, pdfaProfile) {
375
398
  const cidSet = pdfaProfile?.part === 1;
376
399
  const out = /* @__PURE__ */ new Map();
377
- for (const [variant, res] of laid.fontResources) out.set(variant, embedTtfFont(doc, res.parsed, {
378
- usedGids: res.gids,
379
- cidSet
380
- }));
400
+ for (const [variant, res] of laid.fontResources) {
401
+ if (res.gids.size === 0) continue;
402
+ out.set(variant, embedTtfFont(doc, res.parsed, {
403
+ usedGids: res.gids,
404
+ cidSet
405
+ }));
406
+ }
381
407
  return out;
382
408
  }
383
409
  function buildFontResourceDict(resources, embedded) {
@@ -426,7 +452,40 @@ function emitTaggedRuns(out, cmds, tagging, body) {
426
452
  }
427
453
  close();
428
454
  }
429
- function emitPageContent(page, tagging, gradientNames) {
455
+ /**
456
+ * The dash array for a §18.18.3 / §17.18.2 border style, in points.
457
+ *
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.
463
+ */
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(" ")}]` : "";
487
+ }
488
+ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
430
489
  const commands = page.commands;
431
490
  const links = [];
432
491
  if (commands.length === 0) return {
@@ -456,7 +515,18 @@ function emitPageContent(page, tagging, gradientNames) {
456
515
  }
457
516
  emitTaggedRuns(out, plan.images.filter((c) => c.imageResourceName !== ""), tagging, (img) => {
458
517
  out.push("q");
459
- 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));
460
530
  out.push(`/${img.imageResourceName} Do`);
461
531
  out.push("Q");
462
532
  });
@@ -464,14 +534,22 @@ function emitPageContent(page, tagging, gradientNames) {
464
534
  if (borders.length > 0) {
465
535
  if (tagging) out.push("/Artifact BMC");
466
536
  out.push("q");
537
+ out.push("2 J");
467
538
  let lastWidth = -1;
468
539
  let lastColor = "";
540
+ let lastDash = "";
469
541
  for (const b of borders) {
470
542
  const width = b.borderSizePt;
471
543
  const color = b.borderColorHex;
472
- if (width !== lastWidth) {
473
- out.push(`${formatNumber(width)} w`);
474
- lastWidth = width;
544
+ const dash = dashPatternFor(b.borderStyle);
545
+ if (dash !== lastDash) {
546
+ out.push(dash === "" ? "[] 0 d 2 J" : `${dash} 0 d 0 J`);
547
+ lastDash = dash;
548
+ }
549
+ const strokeW = b.borderStyle === "double" ? Math.max(.25, width / 3) : width;
550
+ if (strokeW !== lastWidth) {
551
+ out.push(`${formatNumber(strokeW)} w`);
552
+ lastWidth = strokeW;
475
553
  }
476
554
  if (color !== lastColor) {
477
555
  const [r, g, bl] = hexToRgb01(color);
@@ -482,23 +560,39 @@ function emitPageContent(page, tagging, gradientNames) {
482
560
  const y = H - b.y - b.height;
483
561
  const w = b.width;
484
562
  const h = b.height;
485
- switch (b.side) {
486
- case "top":
487
- out.push(`${formatNumber(x)} ${formatNumber(y + h)} m`);
488
- out.push(`${formatNumber(x + w)} ${formatNumber(y + h)} l`);
489
- break;
490
- case "bottom":
491
- out.push(`${formatNumber(x)} ${formatNumber(y)} m`);
492
- out.push(`${formatNumber(x + w)} ${formatNumber(y)} l`);
493
- break;
494
- case "left":
495
- out.push(`${formatNumber(x)} ${formatNumber(y)} m`);
496
- out.push(`${formatNumber(x)} ${formatNumber(y + h)} l`);
497
- break;
498
- case "right":
499
- out.push(`${formatNumber(x + w)} ${formatNumber(y)} m`);
500
- out.push(`${formatNumber(x + w)} ${formatNumber(y + h)} l`);
501
- break;
563
+ const [x1, y1, x2, y2, nx, ny] = b.side === "top" ? [
564
+ x,
565
+ y + h,
566
+ x + w,
567
+ y + h,
568
+ 0,
569
+ 1
570
+ ] : b.side === "bottom" ? [
571
+ x,
572
+ y,
573
+ x + w,
574
+ y,
575
+ 0,
576
+ 1
577
+ ] : b.side === "left" ? [
578
+ x,
579
+ y,
580
+ x,
581
+ y + h,
582
+ 1,
583
+ 0
584
+ ] : [
585
+ x + w,
586
+ y,
587
+ x + w,
588
+ y + h,
589
+ 1,
590
+ 0
591
+ ];
592
+ const offsets = b.borderStyle === "double" ? [-width / 3, width / 3] : [0];
593
+ for (const d of offsets) {
594
+ out.push(`${formatNumber(x1 + nx * d)} ${formatNumber(y1 + ny * d)} m`);
595
+ out.push(`${formatNumber(x2 + nx * d)} ${formatNumber(y2 + ny * d)} l`);
502
596
  }
503
597
  out.push("S");
504
598
  }
@@ -519,12 +613,15 @@ function emitPageContent(page, tagging, gradientNames) {
519
613
  H - t[5]
520
614
  ]
521
615
  };
522
- for (const op of emitVectorShape(shape, gradientNames?.get(sh.shape))) out.push(op);
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;
618
+ for (const op of emitVectorShape(shape, gradientNames?.get(sh.shape), alphaState)) out.push(op);
523
619
  });
524
620
  const lines = plan.lines;
525
621
  if (lines.length > 0) {
526
622
  let inBT = false;
527
623
  let lastFont = "";
624
+ let lastTc = 0;
528
625
  let lastSize = -1;
529
626
  let lastColor = "";
530
627
  const switchFontIfNeeded = (tok) => {
@@ -534,6 +631,11 @@ function emitPageContent(page, tagging, gradientNames) {
534
631
  lastFont = fontKey;
535
632
  lastSize = tok.fontSizePt;
536
633
  }
634
+ const tc = tok.resolvedRun.letterSpacingPt ?? 0;
635
+ if (tc !== lastTc) {
636
+ out.push(`${formatNumber(tc)} Tc`);
637
+ lastTc = tc;
638
+ }
537
639
  if (tok.resolvedRun.colorHex !== lastColor) {
538
640
  const [r, g, b] = hexToRgb01(tok.resolvedRun.colorHex);
539
641
  out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} rg`);
@@ -546,13 +648,45 @@ function emitPageContent(page, tagging, gradientNames) {
546
648
  out.push("ET");
547
649
  inBT = false;
548
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
+ }
549
671
  out.push("q");
550
- 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));
551
674
  out.push(`/${tok.imageResourceName} Do`);
552
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
+ }
553
686
  lastFont = "";
554
687
  lastSize = -1;
555
688
  lastColor = "";
689
+ lastTc = 0;
556
690
  };
557
691
  const emitMathToken = (tok, originX, baselineY) => {
558
692
  for (const it of tok.items) if (it.kind === "glyph") {
@@ -570,7 +704,7 @@ function emitPageContent(page, tagging, gradientNames) {
570
704
  lastColor = "000000";
571
705
  }
572
706
  out.push(`1 0 0 1 ${formatNumber(originX + it.x)} ${formatNumber(baselineY + it.y)} Tm`);
573
- out.push(`<${it.font.measure.encodeTextAsCidHex(it.text)}> Tj`);
707
+ out.push(it.font.measure.showText(it.text));
574
708
  } else if (it.kind === "rule") {
575
709
  if (inBT) {
576
710
  out.push("ET");
@@ -611,10 +745,40 @@ function emitPageContent(page, tagging, gradientNames) {
611
745
  lastColor = "";
612
746
  }
613
747
  };
748
+ const emitClipped = (cmd, body) => {
749
+ const clip = cmd.clip;
750
+ if (!clip) {
751
+ body(cmd);
752
+ return;
753
+ }
754
+ if (inBT) {
755
+ out.push("ET");
756
+ inBT = false;
757
+ }
758
+ out.push("q");
759
+ out.push(`${formatNumber(clip.x)} ${formatNumber(H - clip.y - clip.height)} ${formatNumber(clip.width)} ${formatNumber(clip.height)} re W n`);
760
+ lastFont = "";
761
+ lastSize = -1;
762
+ lastColor = "";
763
+ lastTc = 0;
764
+ out.push("BT");
765
+ inBT = true;
766
+ body(cmd);
767
+ if (inBT) {
768
+ out.push("ET");
769
+ inBT = false;
770
+ }
771
+ out.push("Q");
772
+ lastFont = "";
773
+ lastSize = -1;
774
+ lastColor = "";
775
+ lastTc = 0;
776
+ };
614
777
  const emitOneLine = (cmd) => {
615
778
  const line = cmd.line;
616
779
  const originX = cmd.originX;
617
780
  const baselineY = H - cmd.baselineY;
781
+ const rotationDeg = cmd.rotationDeg ?? 0;
618
782
  const lineFs = line.maxFontSizePt || 12;
619
783
  const linkAscent = Math.max(lineFs, line.mathAscentPt ?? 0);
620
784
  const linkDescent = Math.max(lineFs * .2, line.mathDescentPt ?? 0);
@@ -660,10 +824,40 @@ function emitPageContent(page, tagging, gradientNames) {
660
824
  const hasImageToken = line.tokens.some((t) => t.kind === "image");
661
825
  const hasMathToken = line.tokens.some((t) => t.kind === "math");
662
826
  const hasRtl = line.tokens.some((t) => t.bidiLevel % 2 === 1);
663
- const encodeToken = (tok) => {
827
+ const hasTab = line.tokens.some((t) => t.kind === "text" && t.tab === true);
828
+ /** A text token carrying underline or strikethrough. */
829
+ const isDecorated = (tok) => tok.kind === "text" && (tok.resolvedRun.underline !== "none" || tok.resolvedRun.strike);
830
+ const showToken = (tok) => {
664
831
  const text = tok.bidiLevel % 2 === 1 ? reverseByCodePoint(tok.text) : tok.text;
665
- return tok.font.measure.encodeTextAsCidHex(text);
832
+ return tok.font.measure.showText(text);
666
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
+ }
667
861
  if (line.tokens.some((t) => t.kind === "text" && t.highlight === true)) {
668
862
  if (inBT) {
669
863
  out.push("ET");
@@ -683,7 +877,66 @@ function emitPageContent(page, tagging, gradientNames) {
683
877
  out.push("f");
684
878
  out.push("Q");
685
879
  }
686
- if (extraPerSpace > 0 || hasImageToken || hasMathToken || hasRtl) {
880
+ if (line.tokens.some(isDecorated)) {
881
+ if (inBT) {
882
+ out.push("ET");
883
+ inBT = false;
884
+ }
885
+ out.push("q");
886
+ let dx = originX;
887
+ for (const tok of line.tokens) {
888
+ const w = tok.widthPt + (tok.kind === "text" && tok.isSpace ? extraPerSpace : 0);
889
+ if (isDecorated(tok)) {
890
+ const thickness = Math.max(.4, tok.fontSizePt * .06);
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);
896
+ if (tok.resolvedRun.underline !== "none") {
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);
914
+ }
915
+ if (tok.resolvedRun.strike) {
916
+ const y = baselineY + tok.fontSizePt * .26;
917
+ out.push(`${formatNumber(dx)} ${formatNumber(y)} ${formatNumber(w)} ${formatNumber(thickness)} re f`);
918
+ }
919
+ }
920
+ dx += w;
921
+ }
922
+ out.push("Q");
923
+ }
924
+ const hasRise = line.tokens.some((t) => t.kind === "text" && t.risePt !== void 0);
925
+ if (rotationDeg !== 0 && !hasImageToken && !hasMathToken) {
926
+ if (!inBT) {
927
+ out.push("BT");
928
+ inBT = true;
929
+ }
930
+ const rad = rotationDeg * Math.PI / 180;
931
+ const cos = formatNumber(Math.cos(rad));
932
+ const sin = formatNumber(Math.sin(rad));
933
+ out.push(`${cos} ${sin} ${formatNumber(-Math.sin(rad))} ${cos} ${formatNumber(originX)} ${formatNumber(baselineY)} Tm`);
934
+ for (const tok of line.tokens) {
935
+ if (tok.kind !== "text") continue;
936
+ switchFontIfNeeded(tok);
937
+ out.push(tok.font.measure.showText(tok.text));
938
+ }
939
+ } else if (extraPerSpace !== 0 || hasImageToken || hasMathToken || hasRtl || hasRise || hasTab) {
687
940
  const order = hasRtl ? lineVisualOrder(line) : line.tokens.map((_, i) => i);
688
941
  let x = originX;
689
942
  for (const ti of order) {
@@ -705,8 +958,8 @@ function emitPageContent(page, tagging, gradientNames) {
705
958
  inBT = true;
706
959
  }
707
960
  switchFontIfNeeded(tok);
708
- out.push(`1 0 0 1 ${formatNumber(x)} ${formatNumber(baselineY)} Tm`);
709
- out.push(`<${encodeToken(tok)}> Tj`);
961
+ out.push(`1 0 0 1 ${formatNumber(x)} ${formatNumber(baselineY + (tok.risePt ?? 0))} Tm`);
962
+ out.push(showToken(tok));
710
963
  const tokenX0 = x;
711
964
  x += tok.widthPt;
712
965
  if (tok.isSpace) x += extraPerSpace;
@@ -723,8 +976,7 @@ function emitPageContent(page, tagging, gradientNames) {
723
976
  for (const tok of line.tokens) {
724
977
  if (tok.kind !== "text") continue;
725
978
  switchFontIfNeeded(tok);
726
- const hex = tok.font.measure.encodeTextAsCidHex(tok.text);
727
- out.push(`<${hex}> Tj`);
979
+ out.push(tok.font.measure.showText(tok.text));
728
980
  trackLink(tok.href, tok.anchor, tok.text, x, x + tok.widthPt);
729
981
  x += tok.widthPt;
730
982
  }
@@ -744,7 +996,7 @@ function emitPageContent(page, tagging, gradientNames) {
744
996
  out.push(`/${tagging.tagFor(sid)} <</MCID ${mcid}>> BDC`);
745
997
  } else if (cmd.artifact === "pagination") out.push("/Artifact <</Type /Pagination>> BDC");
746
998
  else out.push("/Artifact BMC");
747
- emitOneLine(cmd);
999
+ emitClipped(cmd, emitOneLine);
748
1000
  if (inBT) {
749
1001
  out.push("ET");
750
1002
  inBT = false;
@@ -754,7 +1006,7 @@ function emitPageContent(page, tagging, gradientNames) {
754
1006
  else {
755
1007
  out.push("BT");
756
1008
  inBT = true;
757
- for (const cmd of lines) emitOneLine(cmd);
1009
+ for (const cmd of lines) emitClipped(cmd, emitOneLine);
758
1010
  if (inBT) out.push("ET");
759
1011
  }
760
1012
  }
@@ -766,15 +1018,69 @@ function emitPageContent(page, tagging, gradientNames) {
766
1018
  function lineVisualOrder(line) {
767
1019
  return reorderVisual(line.tokens.map((t) => t.bidiLevel));
768
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
+ }
769
1064
  function computeJustifyExtra(line) {
770
- if (line.resolved.alignment !== "both") return 0;
771
- 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--;
772
1071
  let spaces = 0;
773
- 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
+ }
774
1081
  if (spaces === 0) return 0;
775
- const extra = line.availableWidthPt - line.contentWidthPt;
776
- if (extra <= 0) return 0;
777
- return extra / spaces;
1082
+ const extra = (line.availableWidthPt - contentWidthPt) / spaces;
1083
+ return extra < 0 ? Math.max(extra, -narrowest * GLUE_SHRINK_RATIO) : extra;
778
1084
  }
779
1085
  function hexToRgb01(hex) {
780
1086
  return [
@@ -1,3 +1,4 @@
1
+ import "./objects.js";
1
2
  import "./cid-font.js";
2
3
  import "./writer.js";
3
4
  new TextEncoder();
@@ -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
  /**
@@ -11,6 +11,35 @@ export { PathBuilder } from '../core/vector.js';
11
11
  * @param patternName When set, the fill is this named shading pattern from the
12
12
  * page's `/Pattern` resources (a gradient, EP16b) rather than a solid colour;
13
13
  * only used if the shape actually carries a `fillGradient`.
14
+ * @param alphaStateName When set, the shape's shadow is drawn first, under this
15
+ * named `/ExtGState` (its constant alpha) from the page's resources.
14
16
  * @returns The content-stream operator lines.
15
17
  */
16
- export declare function emitVectorShape(shape: VectorShape, patternName?: 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): 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>;