reamkit 1.15.4 → 1.16.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 (94) hide show
  1. package/dist/esm/core/converter/facade.d.ts +5 -0
  2. package/dist/esm/core/converter/ream.d.ts +6 -0
  3. package/dist/esm/core/converter/ream.js +34 -8
  4. package/dist/esm/core/document-model/index.d.ts +1 -1
  5. package/dist/esm/core/document-model/types.d.ts +187 -1
  6. package/dist/esm/core/drawingml/chart-geometry.d.ts +28 -0
  7. package/dist/esm/core/drawingml/chart-geometry.js +371 -88
  8. package/dist/esm/core/drawingml/chart-parser.js +262 -11
  9. package/dist/esm/core/drawingml/colors.d.ts +6 -6
  10. package/dist/esm/core/drawingml/colors.js +58 -18
  11. package/dist/esm/core/drawingml/preset-geometry.js +7 -6
  12. package/dist/esm/core/drawingml/sparkline-geometry.js +6 -4
  13. package/dist/esm/core/drawingml/theme-parser.d.ts +41 -0
  14. package/dist/esm/core/drawingml/theme-parser.js +74 -1
  15. package/dist/esm/core/font/ttf-subset.js +26 -1
  16. package/dist/esm/core/images.js +172 -43
  17. package/dist/esm/core/indexed-colors.d.ts +11 -0
  18. package/dist/esm/core/indexed-colors.js +81 -0
  19. package/dist/esm/core/ir/sheet.d.ts +61 -0
  20. package/dist/esm/{excel → core}/number-format.d.ts +37 -0
  21. package/dist/esm/core/number-format.js +800 -0
  22. package/dist/esm/core/opc/alternate-content.d.ts +13 -0
  23. package/dist/esm/core/opc/alternate-content.js +72 -0
  24. package/dist/esm/core/opc/core-properties.js +1 -0
  25. package/dist/esm/core/opc/package.d.ts +14 -1
  26. package/dist/esm/core/opc/package.js +43 -4
  27. package/dist/esm/core/opc/relationships.js +1 -0
  28. package/dist/esm/core/opc/xml-entities.d.ts +12 -0
  29. package/dist/esm/core/opc/xml-entities.js +91 -0
  30. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  31. package/dist/esm/core/spreadsheet-model/types.d.ts +137 -0
  32. package/dist/esm/core/vector.d.ts +13 -0
  33. package/dist/esm/excel/activex-parser.js +1 -0
  34. package/dist/esm/excel/column-bands.d.ts +26 -1
  35. package/dist/esm/excel/column-bands.js +156 -20
  36. package/dist/esm/excel/comments-parser.js +5 -3
  37. package/dist/esm/excel/conditional-format.d.ts +25 -2
  38. package/dist/esm/excel/conditional-format.js +63 -19
  39. package/dist/esm/excel/escaped-text.d.ts +9 -0
  40. package/dist/esm/excel/escaped-text.js +20 -0
  41. package/dist/esm/excel/form-control-parser.js +1 -0
  42. package/dist/esm/excel/formula/dates.js +1 -1
  43. package/dist/esm/excel/header-footer.d.ts +9 -1
  44. package/dist/esm/excel/header-footer.js +162 -14
  45. package/dist/esm/excel/index.d.ts +1 -1
  46. package/dist/esm/excel/index.js +2 -2
  47. package/dist/esm/excel/pivot-table-parser.js +1 -0
  48. package/dist/esm/excel/print-model.d.ts +75 -0
  49. package/dist/esm/excel/print-model.js +898 -76
  50. package/dist/esm/excel/printer-settings.d.ts +16 -0
  51. package/dist/esm/excel/printer-settings.js +36 -0
  52. package/dist/esm/excel/rich-value.d.ts +13 -0
  53. package/dist/esm/excel/rich-value.js +120 -0
  54. package/dist/esm/excel/shared-strings-parser.js +11 -7
  55. package/dist/esm/excel/sheet-drawing.d.ts +11 -0
  56. package/dist/esm/excel/sheet-drawing.js +26 -3
  57. package/dist/esm/excel/sheet-shape-parser.d.ts +53 -1
  58. package/dist/esm/excel/sheet-shape-parser.js +461 -32
  59. package/dist/esm/excel/sheet-to-flow.d.ts +23 -2
  60. package/dist/esm/excel/sheet-to-flow.js +551 -36
  61. package/dist/esm/excel/slicer-parser.js +1 -0
  62. package/dist/esm/excel/styles-parser.d.ts +30 -2
  63. package/dist/esm/excel/styles-parser.js +177 -40
  64. package/dist/esm/excel/table-parser.js +1 -0
  65. package/dist/esm/excel/vml-drawing.d.ts +68 -0
  66. package/dist/esm/excel/vml-drawing.js +158 -0
  67. package/dist/esm/excel/workbook-parser.d.ts +5 -0
  68. package/dist/esm/excel/workbook-parser.js +5 -1
  69. package/dist/esm/excel/worksheet-parser.d.ts +2 -1
  70. package/dist/esm/excel/worksheet-parser.js +150 -61
  71. package/dist/esm/excel/xlsx-reader.js +242 -11
  72. package/dist/esm/excel/xlsx-to-pdf.js +12 -1
  73. package/dist/esm/html/html-writer.js +11 -2
  74. package/dist/esm/layout/page-doc.d.ts +26 -0
  75. package/dist/esm/layout/styled-layout.d.ts +2 -0
  76. package/dist/esm/layout/styled-layout.js +493 -64
  77. package/dist/esm/pdf/cid-font.js +20 -9
  78. package/dist/esm/pdf/objects.d.ts +13 -0
  79. package/dist/esm/pdf/objects.js +18 -1
  80. package/dist/esm/pdf/styled-page-emitter.js +164 -32
  81. package/dist/esm/pdf/text-page-renderer.js +1 -0
  82. package/dist/esm/pdf/vector-graphics.d.ts +3 -1
  83. package/dist/esm/pdf/vector-graphics.js +19 -2
  84. package/dist/esm/pptx/pptx-reader.js +1 -0
  85. package/dist/esm/pptx/slide-parser.js +12 -1
  86. package/dist/esm/svg/svg-writer.js +23 -4
  87. package/dist/esm/word/document-parser.js +1 -0
  88. package/dist/esm/word/drawing-parser.d.ts +34 -1
  89. package/dist/esm/word/drawing-parser.js +52 -2
  90. package/dist/esm/word/numbering-parser.js +1 -0
  91. package/dist/esm/word/settings-parser.js +1 -0
  92. package/dist/esm/word/styles-parser.js +1 -0
  93. package/package.json +7 -3
  94. package/dist/esm/excel/number-format.js +0 -476
@@ -1,6 +1,6 @@
1
1
  import { glyphClosure, subsetTtf } from "../core/font/ttf-subset.js";
2
2
  import { createFontMeasure } from "../core/font/measure.js";
3
- import { dict, name, ref, stream } from "./objects.js";
3
+ import { deflatedStream, dict, name, ref } from "./objects.js";
4
4
  //#region src/pdf/cid-font.ts
5
5
  var encoder = new TextEncoder();
6
6
  /**
@@ -17,9 +17,10 @@ function embedTtfFont(doc, parsed, options = {}) {
17
17
  const scale = 1e3 / parsed.unitsPerEm;
18
18
  const toPdfUnits = (v) => Math.round(v * scale);
19
19
  const usedGidsArr = options.usedGids ? [...options.usedGids] : void 0;
20
+ const subsetGids = usedGidsArr ? [...glyphClosure(parsed, usedGidsArr)].sort((a, b) => a - b) : void 0;
20
21
  const fontFileBytes = usedGidsArr ? subsetTtf(parsed, usedGidsArr) : parsed.raw;
21
- const fontFileRef = doc.add(stream({ Length1: fontFileBytes.byteLength }, fontFileBytes));
22
- const cidSetRef = usedGidsArr !== void 0 && options.cidSet ? doc.add(stream({}, buildCidSet(glyphClosure(parsed, usedGidsArr)))) : void 0;
22
+ const fontFileRef = doc.add(deflatedStream({ Length1: fontFileBytes.byteLength }, fontFileBytes));
23
+ const cidSetRef = usedGidsArr !== void 0 && options.cidSet ? doc.add(deflatedStream({}, buildCidSet(glyphClosure(parsed, usedGidsArr)))) : void 0;
23
24
  const baseFontName = usedGidsArr ? `${subsetTag(parsed.postScriptName, usedGidsArr)}+${parsed.postScriptName}` : parsed.postScriptName;
24
25
  const descriptorRef = doc.add(dict({
25
26
  Type: name("FontDescriptor"),
@@ -40,8 +41,7 @@ function embedTtfFont(doc, parsed, options = {}) {
40
41
  FontFile2: ref(fontFileRef.id),
41
42
  ...cidSetRef ? { CIDSet: ref(cidSetRef.id) } : {}
42
43
  }));
43
- const widths = new Array(parsed.numGlyphs);
44
- for (let i = 0; i < parsed.numGlyphs; i++) widths[i] = toPdfUnits(parsed.advanceWidths[i] ?? 0);
44
+ const widthRuns = consecutiveRuns(subsetGids ?? Array.from({ length: parsed.numGlyphs }, (_, i) => i)).map((run) => [run[0], run.map((g) => toPdfUnits(parsed.advanceWidths[g] ?? 0))]);
45
45
  const cidFontRef = doc.add(dict({
46
46
  Type: name("Font"),
47
47
  Subtype: name("CIDFontType2"),
@@ -53,10 +53,10 @@ function embedTtfFont(doc, parsed, options = {}) {
53
53
  }),
54
54
  FontDescriptor: ref(descriptorRef.id),
55
55
  DW: 1e3,
56
- W: [0, widths],
56
+ W: widthRuns.flatMap(([first, ws]) => [first, ws]),
57
57
  CIDToGIDMap: name("Identity")
58
58
  }));
59
- const toUnicodeRef = doc.add(stream({}, buildToUnicodeCMap(parsed)));
59
+ const toUnicodeRef = doc.add(deflatedStream({}, buildToUnicodeCMap(parsed, subsetGids)));
60
60
  const type0Ref = doc.add(dict({
61
61
  Type: name("Font"),
62
62
  Subtype: name("Type0"),
@@ -99,7 +99,18 @@ function subsetTag(postScriptName, gids) {
99
99
  }
100
100
  return tag;
101
101
  }
102
- function buildToUnicodeCMap(parsed) {
102
+ /** Split a sorted id list into runs of consecutive ids. */
103
+ function consecutiveRuns(ids) {
104
+ const runs = [];
105
+ for (const id of ids) {
106
+ const last = runs[runs.length - 1];
107
+ if (last && id === last[last.length - 1] + 1) last.push(id);
108
+ else runs.push([id]);
109
+ }
110
+ return runs;
111
+ }
112
+ function buildToUnicodeCMap(parsed, subsetGids) {
113
+ const inSubset = subsetGids ? new Set(subsetGids) : void 0;
103
114
  const gidToCps = /* @__PURE__ */ new Map();
104
115
  const order = [];
105
116
  for (let cp = 9; cp <= 65535; cp++) {
@@ -131,7 +142,7 @@ function buildToUnicodeCMap(parsed) {
131
142
  }
132
143
  }
133
144
  }
134
- const pairs = order.map((g) => [g, gidToCps.get(g)]).sort((a, b) => a[0] - b[0]);
145
+ const pairs = order.filter((g) => inSubset === void 0 || inSubset.has(g)).map((g) => [g, gidToCps.get(g)]).sort((a, b) => a[0] - b[0]);
135
146
  const utf16beHex = (cps) => {
136
147
  let hex = "";
137
148
  for (const cp of cps) if (cp <= 65535) hex += cp.toString(16).padStart(4, "0").toUpperCase();
@@ -62,6 +62,19 @@ export declare const ref: (id: number, gen?: number) => PdfRef;
62
62
  export declare const dict: (entries: Record<string, PdfValue>) => PdfDict;
63
63
  /** Construct a {@link PdfStream} from a dictionary literal plus its byte payload. */
64
64
  export declare const stream: (entries: Record<string, PdfValue>, data: Uint8Array) => PdfStream;
65
+ /**
66
+ * A {@link PdfStream} whose payload is deflated (ISO 32000-1 §7.4.4).
67
+ *
68
+ * Nothing we wrote was compressed at all — `grep /Filter` over one of our own
69
+ * files returned the empty set — so a three-page workbook came out at 192 KB
70
+ * against LibreOffice's 21 KB, most of it an uncompressed font program. Any
71
+ * stream that is not already in a compressed image format belongs here.
72
+ *
73
+ * @param entries The stream's dictionary, minus `/Filter` and `/Length`.
74
+ * @param data The raw payload.
75
+ * @returns The stream, deflated, with `/Filter /FlateDecode`.
76
+ */
77
+ export declare const deflatedStream: (entries: Record<string, PdfValue>, data: Uint8Array) => PdfStream;
65
78
  /**
66
79
  * Build a PDF Unicode string (UTF-16BE with BOM) suitable for `/Info` or
67
80
  * document-level text metadata. Accepts arbitrary Unicode input.
@@ -1,3 +1,4 @@
1
+ import { zlibSync } from "fflate";
1
2
  //#region src/pdf/objects.ts
2
3
  /** The PDF `null` object (§7.3.9), modelled as a unique symbol. */
3
4
  var PDF_NULL = Symbol("PDF_NULL");
@@ -61,6 +62,22 @@ var dict = (entries) => new Map(Object.entries(entries));
61
62
  /** Construct a {@link PdfStream} from a dictionary literal plus its byte payload. */
62
63
  var stream = (entries, data) => new PdfStream(dict(entries), data);
63
64
  /**
65
+ * A {@link PdfStream} whose payload is deflated (ISO 32000-1 §7.4.4).
66
+ *
67
+ * Nothing we wrote was compressed at all — `grep /Filter` over one of our own
68
+ * files returned the empty set — so a three-page workbook came out at 192 KB
69
+ * against LibreOffice's 21 KB, most of it an uncompressed font program. Any
70
+ * stream that is not already in a compressed image format belongs here.
71
+ *
72
+ * @param entries The stream's dictionary, minus `/Filter` and `/Length`.
73
+ * @param data The raw payload.
74
+ * @returns The stream, deflated, with `/Filter /FlateDecode`.
75
+ */
76
+ var deflatedStream = (entries, data) => new PdfStream(dict({
77
+ ...entries,
78
+ Filter: name("FlateDecode")
79
+ }), zlibSync(data));
80
+ /**
64
81
  * Build a PDF Unicode string (UTF-16BE with BOM) suitable for `/Info` or
65
82
  * document-level text metadata. Accepts arbitrary Unicode input.
66
83
  *
@@ -87,4 +104,4 @@ var unicodeString = (s) => {
87
104
  return new PdfHexString(out);
88
105
  };
89
106
  //#endregion
90
- export { PDF_NULL, PdfHexString, PdfName, PdfRawToken, PdfRef, PdfStream, dict, name, ref, stream, unicodeString };
107
+ export { PDF_NULL, PdfHexString, PdfName, PdfRawToken, PdfRef, PdfStream, deflatedStream, dict, name, ref, stream, unicodeString };
@@ -95,10 +95,25 @@ 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
+ 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;
103
+ const key = Math.round(alpha * 1e3) / 1e3;
104
+ if (alphaStateNames.has(key)) continue;
105
+ const nm = `GSa${alphaStateNames.size}`;
106
+ alphaStateNames.set(key, nm);
107
+ extGStateEntries[nm] = dict({
108
+ ca: key,
109
+ CA: key
110
+ });
111
+ }
98
112
  const resourcesDict = dict({
99
113
  Font: fontResourceDict,
100
114
  ...xobjectResourceDict ? { XObject: xobjectResourceDict } : {},
101
- ...Object.keys(patternEntries).length > 0 ? { Pattern: dict(patternEntries) } : {}
115
+ ...Object.keys(patternEntries).length > 0 ? { Pattern: dict(patternEntries) } : {},
116
+ ...Object.keys(extGStateEntries).length > 0 ? { ExtGState: dict(extGStateEntries) } : {}
102
117
  });
103
118
  let pdfaIccRef;
104
119
  let transparencyGroup;
@@ -125,7 +140,7 @@ function assembleStyledPdf(laid, options, doc) {
125
140
  tagFor: (structId) => builder.node(structId).type
126
141
  };
127
142
  }
128
- const { content: contentBytes, links } = emitPageContent(page, pageTagging, gradientNames);
143
+ const { content: contentBytes, links } = emitPageContent(page, pageTagging, gradientNames, alphaStateNames);
129
144
  const contentsRef = doc.add(stream({}, contentBytes));
130
145
  const pageEntries = {
131
146
  Type: name("Page"),
@@ -374,10 +389,13 @@ function embedImageResources(doc, laid) {
374
389
  function embedFontResources(doc, laid, pdfaProfile) {
375
390
  const cidSet = pdfaProfile?.part === 1;
376
391
  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
- }));
392
+ for (const [variant, res] of laid.fontResources) {
393
+ if (res.gids.size === 0) continue;
394
+ out.set(variant, embedTtfFont(doc, res.parsed, {
395
+ usedGids: res.gids,
396
+ cidSet
397
+ }));
398
+ }
381
399
  return out;
382
400
  }
383
401
  function buildFontResourceDict(resources, embedded) {
@@ -426,7 +444,24 @@ function emitTaggedRuns(out, cmds, tagging, body) {
426
444
  }
427
445
  close();
428
446
  }
429
- function emitPageContent(page, tagging, gradientNames) {
447
+ /**
448
+ * The PDF dash array for a §18.18.3 border style, in points.
449
+ *
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.
453
+ */
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
+ }
463
+ }
464
+ function emitPageContent(page, tagging, gradientNames, alphaStateNames) {
430
465
  const commands = page.commands;
431
466
  const links = [];
432
467
  if (commands.length === 0) return {
@@ -464,14 +499,22 @@ function emitPageContent(page, tagging, gradientNames) {
464
499
  if (borders.length > 0) {
465
500
  if (tagging) out.push("/Artifact BMC");
466
501
  out.push("q");
502
+ out.push("2 J");
467
503
  let lastWidth = -1;
468
504
  let lastColor = "";
505
+ let lastDash = "";
469
506
  for (const b of borders) {
470
507
  const width = b.borderSizePt;
471
508
  const color = b.borderColorHex;
472
- if (width !== lastWidth) {
473
- out.push(`${formatNumber(width)} w`);
474
- lastWidth = width;
509
+ const dash = dashPatternFor(b.borderStyle, width);
510
+ if (dash !== lastDash) {
511
+ out.push(dash === "" ? "[] 0 d 2 J" : `${dash} 0 d 0 J`);
512
+ lastDash = dash;
513
+ }
514
+ const strokeW = b.borderStyle === "double" ? Math.max(.25, width / 3) : width;
515
+ if (strokeW !== lastWidth) {
516
+ out.push(`${formatNumber(strokeW)} w`);
517
+ lastWidth = strokeW;
475
518
  }
476
519
  if (color !== lastColor) {
477
520
  const [r, g, bl] = hexToRgb01(color);
@@ -482,23 +525,39 @@ function emitPageContent(page, tagging, gradientNames) {
482
525
  const y = H - b.y - b.height;
483
526
  const w = b.width;
484
527
  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;
528
+ const [x1, y1, x2, y2, nx, ny] = b.side === "top" ? [
529
+ x,
530
+ y + h,
531
+ x + w,
532
+ y + h,
533
+ 0,
534
+ 1
535
+ ] : b.side === "bottom" ? [
536
+ x,
537
+ y,
538
+ x + w,
539
+ y,
540
+ 0,
541
+ 1
542
+ ] : b.side === "left" ? [
543
+ x,
544
+ y,
545
+ x,
546
+ y + h,
547
+ 1,
548
+ 0
549
+ ] : [
550
+ x + w,
551
+ y,
552
+ x + w,
553
+ y + h,
554
+ 1,
555
+ 0
556
+ ];
557
+ const offsets = b.borderStyle === "double" ? [-width / 3, width / 3] : [0];
558
+ for (const d of offsets) {
559
+ out.push(`${formatNumber(x1 + nx * d)} ${formatNumber(y1 + ny * d)} m`);
560
+ out.push(`${formatNumber(x2 + nx * d)} ${formatNumber(y2 + ny * d)} l`);
502
561
  }
503
562
  out.push("S");
504
563
  }
@@ -519,7 +578,9 @@ function emitPageContent(page, tagging, gradientNames) {
519
578
  H - t[5]
520
579
  ]
521
580
  };
522
- for (const op of emitVectorShape(shape, gradientNames?.get(sh.shape))) out.push(op);
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);
523
584
  });
524
585
  const lines = plan.lines;
525
586
  if (lines.length > 0) {
@@ -611,10 +672,38 @@ function emitPageContent(page, tagging, gradientNames) {
611
672
  lastColor = "";
612
673
  }
613
674
  };
675
+ const emitClipped = (cmd, body) => {
676
+ const clip = cmd.clip;
677
+ if (!clip) {
678
+ body(cmd);
679
+ return;
680
+ }
681
+ if (inBT) {
682
+ out.push("ET");
683
+ inBT = false;
684
+ }
685
+ out.push("q");
686
+ out.push(`${formatNumber(clip.x)} ${formatNumber(H - clip.y - clip.height)} ${formatNumber(clip.width)} ${formatNumber(clip.height)} re W n`);
687
+ lastFont = "";
688
+ lastSize = -1;
689
+ lastColor = "";
690
+ out.push("BT");
691
+ inBT = true;
692
+ body(cmd);
693
+ if (inBT) {
694
+ out.push("ET");
695
+ inBT = false;
696
+ }
697
+ out.push("Q");
698
+ lastFont = "";
699
+ lastSize = -1;
700
+ lastColor = "";
701
+ };
614
702
  const emitOneLine = (cmd) => {
615
703
  const line = cmd.line;
616
704
  const originX = cmd.originX;
617
705
  const baselineY = H - cmd.baselineY;
706
+ const rotationDeg = cmd.rotationDeg ?? 0;
618
707
  const lineFs = line.maxFontSizePt || 12;
619
708
  const linkAscent = Math.max(lineFs, line.mathAscentPt ?? 0);
620
709
  const linkDescent = Math.max(lineFs * .2, line.mathDescentPt ?? 0);
@@ -660,6 +749,8 @@ function emitPageContent(page, tagging, gradientNames) {
660
749
  const hasImageToken = line.tokens.some((t) => t.kind === "image");
661
750
  const hasMathToken = line.tokens.some((t) => t.kind === "math");
662
751
  const hasRtl = line.tokens.some((t) => t.bidiLevel % 2 === 1);
752
+ /** A text token carrying underline or strikethrough. */
753
+ const isDecorated = (tok) => tok.kind === "text" && (tok.resolvedRun.underline !== "none" || tok.resolvedRun.strike);
663
754
  const encodeToken = (tok) => {
664
755
  const text = tok.bidiLevel % 2 === 1 ? reverseByCodePoint(tok.text) : tok.text;
665
756
  return tok.font.measure.encodeTextAsCidHex(text);
@@ -683,7 +774,48 @@ function emitPageContent(page, tagging, gradientNames) {
683
774
  out.push("f");
684
775
  out.push("Q");
685
776
  }
686
- if (extraPerSpace > 0 || hasImageToken || hasMathToken || hasRtl) {
777
+ if (line.tokens.some(isDecorated)) {
778
+ if (inBT) {
779
+ out.push("ET");
780
+ inBT = false;
781
+ }
782
+ out.push("q");
783
+ let dx = originX;
784
+ for (const tok of line.tokens) {
785
+ const w = tok.widthPt + (tok.kind === "text" && tok.isSpace ? extraPerSpace : 0);
786
+ if (isDecorated(tok)) {
787
+ const [r, g, b] = hexToRgb01(tok.resolvedRun.colorHex);
788
+ const thickness = Math.max(.4, tok.fontSizePt * .06);
789
+ out.push(`${formatNumber(r)} ${formatNumber(g)} ${formatNumber(b)} rg`);
790
+ 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`);
793
+ }
794
+ if (tok.resolvedRun.strike) {
795
+ const y = baselineY + tok.fontSizePt * .26;
796
+ out.push(`${formatNumber(dx)} ${formatNumber(y)} ${formatNumber(w)} ${formatNumber(thickness)} re f`);
797
+ }
798
+ }
799
+ dx += w;
800
+ }
801
+ out.push("Q");
802
+ }
803
+ const hasRise = line.tokens.some((t) => t.kind === "text" && t.risePt !== void 0);
804
+ if (rotationDeg !== 0 && !hasImageToken && !hasMathToken) {
805
+ if (!inBT) {
806
+ out.push("BT");
807
+ inBT = true;
808
+ }
809
+ const rad = rotationDeg * Math.PI / 180;
810
+ const cos = formatNumber(Math.cos(rad));
811
+ const sin = formatNumber(Math.sin(rad));
812
+ out.push(`${cos} ${sin} ${formatNumber(-Math.sin(rad))} ${cos} ${formatNumber(originX)} ${formatNumber(baselineY)} Tm`);
813
+ for (const tok of line.tokens) {
814
+ if (tok.kind !== "text") continue;
815
+ switchFontIfNeeded(tok);
816
+ out.push(`<${tok.font.measure.encodeTextAsCidHex(tok.text)}> Tj`);
817
+ }
818
+ } else if (extraPerSpace > 0 || hasImageToken || hasMathToken || hasRtl || hasRise) {
687
819
  const order = hasRtl ? lineVisualOrder(line) : line.tokens.map((_, i) => i);
688
820
  let x = originX;
689
821
  for (const ti of order) {
@@ -705,7 +837,7 @@ function emitPageContent(page, tagging, gradientNames) {
705
837
  inBT = true;
706
838
  }
707
839
  switchFontIfNeeded(tok);
708
- out.push(`1 0 0 1 ${formatNumber(x)} ${formatNumber(baselineY)} Tm`);
840
+ out.push(`1 0 0 1 ${formatNumber(x)} ${formatNumber(baselineY + (tok.risePt ?? 0))} Tm`);
709
841
  out.push(`<${encodeToken(tok)}> Tj`);
710
842
  const tokenX0 = x;
711
843
  x += tok.widthPt;
@@ -744,7 +876,7 @@ function emitPageContent(page, tagging, gradientNames) {
744
876
  out.push(`/${tagging.tagFor(sid)} <</MCID ${mcid}>> BDC`);
745
877
  } else if (cmd.artifact === "pagination") out.push("/Artifact <</Type /Pagination>> BDC");
746
878
  else out.push("/Artifact BMC");
747
- emitOneLine(cmd);
879
+ emitClipped(cmd, emitOneLine);
748
880
  if (inBT) {
749
881
  out.push("ET");
750
882
  inBT = false;
@@ -754,7 +886,7 @@ function emitPageContent(page, tagging, gradientNames) {
754
886
  else {
755
887
  out.push("BT");
756
888
  inBT = true;
757
- for (const cmd of lines) emitOneLine(cmd);
889
+ for (const cmd of lines) emitClipped(cmd, emitOneLine);
758
890
  if (inBT) out.push("ET");
759
891
  }
760
892
  }
@@ -1,3 +1,4 @@
1
+ import "./objects.js";
1
2
  import "./cid-font.js";
2
3
  import "./writer.js";
3
4
  new TextEncoder();
@@ -11,6 +11,8 @@ 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
+ export declare function emitVectorShape(shape: VectorShape, patternName?: string, alphaStateName?: string): Array<string>;
@@ -10,12 +10,29 @@ import "../core/vector.js";
10
10
  * @param patternName When set, the fill is this named shading pattern from the
11
11
  * page's `/Pattern` resources (a gradient, EP16b) rather than a solid colour;
12
12
  * only used if the shape actually carries a `fillGradient`.
13
+ * @param alphaStateName When set, the shape's shadow is drawn first, under this
14
+ * named `/ExtGState` (its constant alpha) from the page's resources.
13
15
  * @returns The content-stream operator lines.
14
16
  */
15
- function emitVectorShape(shape, patternName) {
17
+ function emitVectorShape(shape, patternName, alphaStateName) {
16
18
  const out = [];
17
- out.push("q");
18
19
  const [a, b, c, d, e, f] = shape.transform;
20
+ const shadow = shape.shadow;
21
+ if (shadow) {
22
+ out.push("q");
23
+ if (alphaStateName !== void 0) out.push(`/${alphaStateName} gs`);
24
+ out.push(`${num(a)} ${num(b)} ${num(c)} ${num(d)} ${num(e + shadow.dxPt)} ${num(f - shadow.dyPt)} cm`);
25
+ const [sr, sg, sb] = hexToRgb01(shadow.colorHex);
26
+ out.push(`${num(sr)} ${num(sg)} ${num(sb)} rg`);
27
+ let shadowEvenodd = false;
28
+ for (const path of shape.paths) {
29
+ if (path.fillRule === "evenodd") shadowEvenodd = true;
30
+ for (const seg of path.segments) out.push(emitSegment(seg));
31
+ }
32
+ out.push(shadowEvenodd ? "f*" : "f");
33
+ out.push("Q");
34
+ }
35
+ out.push("q");
19
36
  out.push(`${num(a)} ${num(b)} ${num(c)} ${num(d)} ${num(e)} ${num(f)} cm`);
20
37
  const stroke = shape.stroke;
21
38
  if (stroke) {
@@ -19,6 +19,7 @@ var DEFAULT_CX = 9144e3;
19
19
  var DEFAULT_CY = 6858e3;
20
20
  var decoder = new TextDecoder();
21
21
  var parser = new XMLParser({
22
+ htmlEntities: true,
22
23
  ignoreAttributes: false,
23
24
  attributeNamePrefix: "@_",
24
25
  preserveOrder: true,
@@ -1,4 +1,4 @@
1
- import { emuToPt } from "../core/ir/units.js";
1
+ import { emuToPt, pt } from "../core/ir/units.js";
2
2
  import { FEATURES } from "../core/ir/features.js";
3
3
  import { poAttr, poChildren, poFindDescendant, poIntAttr, poIs, poText } from "../core/po-helpers.js";
4
4
  import { defaultColorResolver, resolveColorNode } from "../core/drawingml/colors.js";
@@ -400,6 +400,17 @@ function parseSlideParagraph(aP, ph, cascade, colors, resolveLink, counters) {
400
400
  const run = parseSlideRun(child, defaults, colors, resolveLink);
401
401
  if (run) runs.push(run);
402
402
  }
403
+ if (runs.length === 0) {
404
+ const endRPr = poChildren(aP).find((c) => poIs(c, "a:endParaRPr"));
405
+ const sz = endRPr ? poIntAttr(endRPr, "sz") : void 0;
406
+ if (sz !== void 0 && sz > 0) runs.push({
407
+ text: "",
408
+ properties: {
409
+ ...defaults,
410
+ fontSizePt: pt(sz / 100)
411
+ }
412
+ });
413
+ }
403
414
  const marL = pPr ? poIntAttr(pPr, "marL") : void 0;
404
415
  const indent = pPr ? poIntAttr(pPr, "indent") : void 0;
405
416
  const indentLeft = marL !== void 0 ? emuToPt(marL) : level > 0 ? emuToPt(level * 457200) : void 0;
@@ -87,14 +87,20 @@ function emitPage(out, page, laid, losses, idc) {
87
87
  x2,
88
88
  yBottom
89
89
  ];
90
- out.push(`<line x1="${fmt(ax)}" y1="${fmt(ay)}" x2="${fmt(bx)}" y2="${fmt(by)}" stroke="#${b.borderColorHex}" stroke-width="${fmt(b.borderSizePt)}"/>`);
90
+ out.push(`<line x1="${fmt(ax)}" y1="${fmt(ay)}" x2="${fmt(bx)}" y2="${fmt(by)}" stroke="#${b.borderColorHex}" stroke-width="${fmt(b.borderSizePt)}"${b.borderStyle === "dashed" ? ` stroke-dasharray="${fmt(b.borderSizePt * 4)} ${fmt(b.borderSizePt * 1.33)}"` : b.borderStyle === "dotted" ? ` stroke-dasharray="${fmt(b.borderSizePt)} ${fmt(b.borderSizePt * 1.33)}"` : ""}/>`);
91
91
  }
92
92
  for (const sh of plan.shapes) emitShape(out, sh.shape, idc);
93
- for (const t of plan.lines) emitTextLine(out, t, losses);
93
+ for (const t of plan.lines) emitTextLine(out, t, losses, idc);
94
94
  }
95
- function emitTextLine(out, item, losses) {
95
+ function emitTextLine(out, item, losses, idc) {
96
+ const clip = item.clip;
97
+ if (clip) {
98
+ const id = `clip${idc.n++}`;
99
+ out.push(`<clipPath id="${id}"><rect x="${fmt(clip.x)}" y="${fmt(clip.y)}" width="${fmt(clip.width)}" height="${fmt(clip.height)}"/></clipPath>`, `<g clip-path="url(#${id})">`);
100
+ }
96
101
  const y = item.baselineY;
97
102
  let x = item.originX;
103
+ const rot = item.rotationDeg ? ` transform="rotate(${fmt(-item.rotationDeg)} ${fmt(item.originX)} ${fmt(item.baselineY)})"` : "";
98
104
  for (const tok of item.line.tokens) {
99
105
  if (tok.kind === "image") {
100
106
  x += tok.widthPt;
@@ -109,9 +115,10 @@ function emitTextLine(out, item, losses) {
109
115
  x += tok.widthPt;
110
116
  continue;
111
117
  }
112
- if (tok.text.trim().length > 0) out.push(`<text x="${fmt(x)}" y="${fmt(y)}" font-family="sans-serif" font-size="${fmt(tok.fontSizePt)}" fill="#${tok.resolvedRun.colorHex}">${escapeXml(tok.text)}</text>`);
118
+ if (tok.text.trim().length > 0) out.push(`<text x="${fmt(x)}" y="${fmt(y)}"${rot} font-family="sans-serif" font-size="${fmt(tok.fontSizePt)}" fill="#${tok.resolvedRun.colorHex}">${escapeXml(tok.text)}</text>`);
113
119
  x += tok.widthPt;
114
120
  }
121
+ if (clip) out.push("</g>");
115
122
  }
116
123
  function emitShape(out, shape, idc) {
117
124
  const [a, b, c, d, e, f] = shape.transform;
@@ -123,6 +130,18 @@ function emitShape(out, shape, idc) {
123
130
  fill = `url(#${id})`;
124
131
  } else fill = shape.fillColorHex ? `#${shape.fillColorHex}` : "none";
125
132
  const stroke = shape.stroke ? ` stroke="#${shape.stroke.colorHex}" stroke-width="${fmt(shape.stroke.widthPt)}"` : "";
133
+ const shadow = shape.shadow;
134
+ if (shadow) {
135
+ const id = `shadow${idc.n++}`;
136
+ if (shadow.blurPt > 0) out.push(`<filter id="${id}" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="${fmt(shadow.blurPt / 2)}"/></filter>`);
137
+ const shTransform = `matrix(${fmt(a)} ${fmt(b)} ${fmt(c)} ${fmt(d)} ${fmt(e + shadow.dxPt)} ${fmt(f + shadow.dyPt)})`;
138
+ out.push(`<g opacity="${fmt(shadow.alpha)}"${shadow.blurPt > 0 ? ` filter="url(#${id})"` : ""}>`);
139
+ for (const path of shape.paths) {
140
+ const rule = path.fillRule === "evenodd" ? " fill-rule=\"evenodd\"" : "";
141
+ out.push(`<path d="${pathData(path.segments)}" fill="#${shadow.colorHex}"${rule} transform="${shTransform}"/>`);
142
+ }
143
+ out.push("</g>");
144
+ }
126
145
  for (const path of shape.paths) {
127
146
  const d2 = pathData(path.segments);
128
147
  const rule = path.fillRule === "evenodd" ? " fill-rule=\"evenodd\"" : "";
@@ -12,6 +12,7 @@ import { XMLParser } from "fast-xml-parser";
12
12
  //#region src/word/document-parser.ts
13
13
  var decoder = new TextDecoder("utf-8");
14
14
  var parser = new XMLParser({
15
+ htmlEntities: true,
15
16
  ignoreAttributes: false,
16
17
  attributeNamePrefix: "@_",
17
18
  preserveOrder: true,
@@ -1,4 +1,4 @@
1
- import { BodyElement, FloatAnchor, ShapeFill, ShapeGeometry, ShapeLine, ShapeTextBody, ShapeTransform } from '../core/document-model/index.js';
1
+ import { BodyElement, FloatAnchor, ShapeFill, ShapeGeometry, ShapeLine, ShapeShadow, ShapeTextBody, ShapeTransform } from '../core/document-model/index.js';
2
2
  import { ColorResolver } from '../core/drawingml/colors.js';
3
3
  import { PoNode } from '../core/po-helpers.js';
4
4
  import { Pt } from '../core/ir/index.js';
@@ -104,6 +104,17 @@ export declare function parseDrawing(drawing: PoNode, resolveColor: ColorResolve
104
104
  * @returns The picture, or `null` when there is no embedded `v:imagedata` or no usable size.
105
105
  */
106
106
  export declare function parseVmlPicture(node: PoNode): DrawingContent | null;
107
+ /**
108
+ * §20.1.7.6 `a:xfrm` — how a shape sits in its box: rotated about its centre by
109
+ * `@rot` (sixtieth-thousandths of a degree) and mirrored by `@flipH`/`@flipV`.
110
+ * The box itself comes from elsewhere — the anchor on a sheet, the extent in a
111
+ * document — and holds the shape UNROTATED, which is what makes this a separate
112
+ * transform rather than a different rectangle.
113
+ *
114
+ * @param xfrm The `a:xfrm` element.
115
+ * @returns The transform; empty when it states neither rotation nor a flip.
116
+ */
117
+ export declare function parseXfrm(xfrm: PoNode): ShapeTransform;
107
118
  /**
108
119
  * Parse an `a:prstGeom` (§20.1.9.18) into a preset {@link ShapeGeometry}: the
109
120
  * `@prst` preset name plus the `a:avLst` adjust values (each `a:gd`'s `val …`
@@ -126,6 +137,28 @@ export declare function parseCustGeom(cust: PoNode): ShapeGeometry;
126
137
  * @param resolveColor Resolver for theme/scheme colours.
127
138
  */
128
139
  export declare function parseFill(spPr: PoNode, resolveColor: ColorResolver): ShapeFill;
140
+ /**
141
+ * Parse a shape's drop shadow from its `a:spPr` (§20.1.8.40 `a:effectLst` →
142
+ * `a:outerShdw`).
143
+ *
144
+ * The spec states the displacement in polar form: `dist` in EMU and `dir` in
145
+ * 60 000ths of a degree, measured clockwise from due east in a frame whose y
146
+ * grows DOWNWARD — so the standard 2 700 000 (45°) puts the shadow down and to
147
+ * the right, which is where every reader draws it.
148
+ *
149
+ * @param spPr The shape's `spPr` node.
150
+ * @param resolveColor Resolver for theme/scheme colours.
151
+ * @returns The shadow, or undefined when the shape declares none.
152
+ */
153
+ export declare function parseShadow(spPr: PoNode, resolveColor: ColorResolver): ShapeShadow | undefined;
154
+ /**
155
+ * Build a {@link ShapeShadow} from an `a:outerShdw` node.
156
+ *
157
+ * @param shdw The `a:outerShdw` node.
158
+ * @param resolveColor Resolver for theme/scheme colours.
159
+ * @returns The shadow, or undefined when its colour will not resolve.
160
+ */
161
+ export declare function shadowFromOuterShdw(shdw: PoNode, resolveColor: ColorResolver): ShapeShadow | undefined;
129
162
  /**
130
163
  * Parse a shape's outline (`a:ln`) from its `a:spPr` into a {@link ShapeLine}:
131
164
  * width, cap, solid colour, dash pattern, and an explicit `a:noFill` (an unstroked