reamkit 1.31.0 → 1.32.1

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 (98) hide show
  1. package/README.md +10 -0
  2. package/dist/esm/core/bidi/index.d.ts +10 -0
  3. package/dist/esm/core/bidi/index.js +18 -2
  4. package/dist/esm/core/converter/facade.js +10 -5
  5. package/dist/esm/core/converter/project.js +1 -0
  6. package/dist/esm/core/converter/ream.js +17 -6
  7. package/dist/esm/core/converter/sheet-images.d.ts +17 -0
  8. package/dist/esm/core/converter/sheet-images.js +33 -0
  9. package/dist/esm/core/crypto/offcrypto.js +56 -12
  10. package/dist/esm/core/document-model/index.d.ts +1 -1
  11. package/dist/esm/core/document-model/types.d.ts +112 -0
  12. package/dist/esm/core/drawingml/chart-geometry.d.ts +36 -13
  13. package/dist/esm/core/drawingml/chart-geometry.js +372 -146
  14. package/dist/esm/core/drawingml/chart-parser.d.ts +19 -0
  15. package/dist/esm/core/drawingml/chart-parser.js +126 -18
  16. package/dist/esm/core/drawingml/colors.d.ts +6 -0
  17. package/dist/esm/core/drawingml/colors.js +23 -3
  18. package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
  19. package/dist/esm/core/drawingml/shape-render.js +4 -2
  20. package/dist/esm/core/font/measure.d.ts +9 -0
  21. package/dist/esm/core/font/measure.js +9 -1
  22. package/dist/esm/core/fonts/families.js +4 -2
  23. package/dist/esm/core/fonts/scripts.js +4 -2
  24. package/dist/esm/core/ir/flow.d.ts +6 -0
  25. package/dist/esm/core/ir/sheet.d.ts +43 -0
  26. package/dist/esm/core/number-format.d.ts +40 -15
  27. package/dist/esm/core/number-format.js +295 -83
  28. package/dist/esm/core/opc/tag-scan.d.ts +15 -0
  29. package/dist/esm/core/opc/tag-scan.js +41 -0
  30. package/dist/esm/core/opc/xml-entities.js +39 -2
  31. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  32. package/dist/esm/core/spreadsheet-model/types.d.ts +96 -0
  33. package/dist/esm/core/style-cascade/resolver.js +4 -2
  34. package/dist/esm/excel/column-bands.d.ts +39 -6
  35. package/dist/esm/excel/column-bands.js +41 -17
  36. package/dist/esm/excel/comments-parser.js +19 -0
  37. package/dist/esm/excel/conditional-format.d.ts +7 -3
  38. package/dist/esm/excel/conditional-format.js +77 -39
  39. package/dist/esm/excel/formula/context.d.ts +18 -0
  40. package/dist/esm/excel/formula/eval.js +51 -0
  41. package/dist/esm/excel/formula/functions.js +37 -14
  42. package/dist/esm/excel/formula/index.d.ts +2 -2
  43. package/dist/esm/excel/formula/lexer.d.ts +1 -1
  44. package/dist/esm/excel/formula/lexer.js +41 -0
  45. package/dist/esm/excel/formula/parser.d.ts +25 -0
  46. package/dist/esm/excel/formula/parser.js +95 -0
  47. package/dist/esm/excel/print-model.d.ts +38 -1
  48. package/dist/esm/excel/print-model.js +536 -83
  49. package/dist/esm/excel/shared-strings-parser.d.ts +9 -0
  50. package/dist/esm/excel/shared-strings-parser.js +11 -1
  51. package/dist/esm/excel/sheet-drawing.d.ts +38 -2
  52. package/dist/esm/excel/sheet-drawing.js +47 -13
  53. package/dist/esm/excel/sheet-shape-parser.d.ts +6 -2
  54. package/dist/esm/excel/sheet-shape-parser.js +18 -6
  55. package/dist/esm/excel/sheet-to-flow.d.ts +17 -0
  56. package/dist/esm/excel/sheet-to-flow.js +434 -47
  57. package/dist/esm/excel/styles-parser.d.ts +9 -0
  58. package/dist/esm/excel/styles-parser.js +80 -26
  59. package/dist/esm/excel/table-parser.js +5 -0
  60. package/dist/esm/excel/table-style-presets.d.ts +26 -0
  61. package/dist/esm/excel/table-style-presets.js +1555 -0
  62. package/dist/esm/excel/table-style.d.ts +13 -0
  63. package/dist/esm/excel/table-style.js +75 -0
  64. package/dist/esm/excel/tint.d.ts +10 -0
  65. package/dist/esm/excel/tint.js +75 -0
  66. package/dist/esm/excel/vml-drawing.d.ts +41 -3
  67. package/dist/esm/excel/vml-drawing.js +144 -11
  68. package/dist/esm/excel/worksheet-parser.js +89 -15
  69. package/dist/esm/excel/xls/biff-reader.js +10 -2
  70. package/dist/esm/excel/xlsx-reader.js +234 -19
  71. package/dist/esm/excel/xlsx-writer.js +8 -7
  72. package/dist/esm/html/html-writer.js +202 -20
  73. package/dist/esm/layout/fit-pages.d.ts +11 -0
  74. package/dist/esm/layout/fit-pages.js +86 -0
  75. package/dist/esm/layout/line-paint.d.ts +58 -0
  76. package/dist/esm/layout/line-paint.js +107 -0
  77. package/dist/esm/layout/page-doc.d.ts +51 -0
  78. package/dist/esm/layout/page-doc.js +141 -1
  79. package/dist/esm/layout/styled-layout.d.ts +7 -0
  80. package/dist/esm/layout/styled-layout.js +275 -52
  81. package/dist/esm/layout/turned-section.js +14 -2
  82. package/dist/esm/markdown/markdown-writer.js +75 -13
  83. package/dist/esm/pdf/styled-page-emitter.js +49 -71
  84. package/dist/esm/pdf/styled-page-renderer.js +1 -1
  85. package/dist/esm/pdf-reader/annot-draw.js +2 -2
  86. package/dist/esm/pdf-reader/annots.js +3 -1
  87. package/dist/esm/pdf-reader/font.js +4 -4
  88. package/dist/esm/pdf-reader/layout.js +1 -1
  89. package/dist/esm/svg/svg-text.d.ts +58 -0
  90. package/dist/esm/svg/svg-text.js +322 -0
  91. package/dist/esm/svg/svg-writer.js +135 -71
  92. package/dist/esm/word/document-parser.js +1 -1
  93. package/dist/esm/word/docx-reader.js +13 -8
  94. package/dist/esm/word/docx-to-pdf.js +3 -3
  95. package/dist/esm/word/docx-writer.js +11 -2
  96. package/dist/esm/word/font-table.js +89 -10
  97. package/dist/esm/word/numbering-parser.js +1 -1
  98. package/package.json +7 -3
@@ -32,6 +32,7 @@ function writeMarkdown(flow, options = {}) {
32
32
  anchors: referencedAnchors(flow.body),
33
33
  notes: noteNumbers(flow),
34
34
  mediaNames: /* @__PURE__ */ new Map(),
35
+ drawn: /* @__PURE__ */ new Set(),
35
36
  ...flow.numbering ? { numbering: flow.numbering } : {}
36
37
  };
37
38
  if (flow.headersFooters && flow.headersFooters.size > 0) lose(ctx, "dropped", FEATURES.headersFooters, "markdown has no pages to band headers onto");
@@ -81,9 +82,29 @@ function lose(ctx, severity, feature, detail) {
81
82
  */
82
83
  function joinBlocks(blocks) {
83
84
  const body = blocks.map(trimHardBreaks).filter((b) => b.length > 0).join("\n\n");
84
- return body.length > 0 ? `${body.replace(/[ \t]+$/gm, "")}\n` : "";
85
+ return body.length > 0 ? `${trimLineEnds(body)}\n` : "";
85
86
  }
86
87
  /**
88
+ * The text without the spaces and tabs that end its lines — what
89
+ * `/[ \t]+$/gm` leaves, a line ending wherever `$` does under `m`: at LF, CR,
90
+ * U+2028, U+2029 and the end of the text. The expression retries every blank
91
+ * of a long run that something else follows, and is quadratic on it; this is
92
+ * one pass.
93
+ */
94
+ function trimLineEnds(text) {
95
+ const out = [];
96
+ let start = 0;
97
+ for (let i = 0; i <= text.length; i++) {
98
+ if (i < text.length && !isLineEnd(text.charCodeAt(i))) continue;
99
+ let end = i;
100
+ while (end > start && (text[end - 1] === " " || text[end - 1] === " ")) end--;
101
+ out.push(text.slice(start, end), text.slice(i, i + 1));
102
+ start = i + 1;
103
+ }
104
+ return out.join("");
105
+ }
106
+ var isLineEnd = (c) => c === 10 || c === 13 || c === 8232 || c === 8233;
107
+ /**
87
108
  * Drop a hard break sitting at either end of a block: at the end there is no
88
109
  * next line to break to, at the start no previous one, and either way the
89
110
  * backslash is left standing in the text as itself. Only a break is taken —
@@ -98,8 +119,14 @@ function emitBlock(out, el, ctx) {
98
119
  return;
99
120
  }
100
121
  ctx.list.length = 0;
101
- if (el.kind === "table") emitTable(out, el.table, ctx);
102
- else if (el.kind === "image") {
122
+ if (el.kind === "table") {
123
+ emitTable(out, el.table, ctx);
124
+ for (const drawing of el.table.overlay?.drawings ?? []) {
125
+ if (ctx.drawn.has(drawing)) continue;
126
+ ctx.drawn.add(drawing);
127
+ emitBlock(out, drawing, ctx);
128
+ }
129
+ } else if (el.kind === "image") {
103
130
  const img = pictureMarkdown(el.image.resource, el.image.altText, ctx);
104
131
  if (img.length > 0) out.push(img);
105
132
  } else if (el.kind === "chart") lose(ctx, "dropped", FEATURES.charts, "markdown cannot draw a chart");
@@ -211,7 +238,7 @@ function emitListItem(out, resolved, marker, inline, ctx) {
211
238
  top.markerWidth = bullet.length + 1;
212
239
  const pad = " ".repeat(top.indent);
213
240
  const cont = " ".repeat(top.indent + top.markerWidth);
214
- const line = `${pad}${bullet} ${inline.replaceAll("\\\n", `\\\n${cont}`)}`.replace(/\s+$/, "");
241
+ const line = `${pad}${bullet} ${inline.replaceAll("\\\n", `\\\n${cont}`)}`.trimEnd();
215
242
  if (wasOpen && out.length > 0) out[out.length - 1] += `\n${line}`;
216
243
  else out.push(line);
217
244
  }
@@ -225,9 +252,24 @@ function listBullet(marker, numId, ilvl, counter, ctx) {
225
252
  const level = numberingLevel(numId, ilvl, ctx.numbering);
226
253
  if (!(level ? level.format !== "bullet" && level.format !== "none" : /\d/.test(marker))) return "-";
227
254
  if (level && level.format !== "decimal" && level.format !== "decimalZero") lose(ctx, "degraded", FEATURES.lists, `${level.format} list markers render as decimal`);
228
- const digits = /(\d+)\D*$/.exec(marker);
229
- if (marker.replace(/\D+/g, "").length > (digits?.[1]?.length ?? 0)) lose(ctx, "degraded", FEATURES.lists, "multi-level list marker flattened to one number");
230
- return `${digits ? Number(digits[1]) : counter}.`;
255
+ const digits = lastNumber(marker);
256
+ if (marker.replace(/\D+/g, "").length > (digits?.length ?? 0)) lose(ctx, "degraded", FEATURES.lists, "multi-level list marker flattened to one number");
257
+ return `${digits !== void 0 ? Number(digits) : counter}.`;
258
+ }
259
+ /**
260
+ * The last run of digits in a marker — what `/(\d+)\D*$/` matched, read back
261
+ * from the end: that expression tried every digit of a long run as its start.
262
+ * The template the marker comes from is the file's, so the run can be any
263
+ * length, and every item of the list reads it.
264
+ */
265
+ function lastNumber(marker) {
266
+ const isDigit = (i) => /\d/.test(marker[i] ?? "");
267
+ let end = marker.length;
268
+ while (end > 0 && !isDigit(end - 1)) end--;
269
+ if (end === 0) return void 0;
270
+ let start = end - 1;
271
+ while (start > 0 && isDigit(start - 1)) start--;
272
+ return marker.slice(start, end);
231
273
  }
232
274
  function numberingLevel(numId, ilvl, numbering) {
233
275
  if (!numbering) return void 0;
@@ -275,8 +317,10 @@ function noteNumbers(flow) {
275
317
  add(r.endnoteRef, endnotes, flow.endnotes);
276
318
  add(r.commentRef, comments, flow.comments);
277
319
  }
278
- else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
279
- else if (el.kind === "shape") visitShape(el.shape);
320
+ else if (el.kind === "table") {
321
+ for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
322
+ visit(el.table.overlay?.drawings ?? []);
323
+ } else if (el.kind === "shape") visitShape(el.shape);
280
324
  };
281
325
  visit(flow.body);
282
326
  return {
@@ -357,7 +401,7 @@ function destination(url) {
357
401
  * paragraph it belongs to — the same shape GFM's own heading anchors take.
358
402
  */
359
403
  function bookmarkSlug(name) {
360
- const slug = name.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "");
404
+ const slug = name.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((word) => word.length > 0).join("-");
361
405
  return slug.length > 0 ? slug : "bookmark";
362
406
  }
363
407
  /** The bookmark names some run actually links to — the only ones worth an anchor. */
@@ -370,8 +414,10 @@ function referencedAnchors(body) {
370
414
  const visit = (els) => {
371
415
  for (const el of els) if (el.kind === "paragraph") {
372
416
  for (const r of el.paragraph.runs) if (r.anchor !== void 0) out.add(r.anchor);
373
- } else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
374
- else if (el.kind === "shape") visitShape(el.shape);
417
+ } else if (el.kind === "table") {
418
+ for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
419
+ visit(el.table.overlay?.drawings ?? []);
420
+ } else if (el.kind === "shape") visitShape(el.shape);
375
421
  };
376
422
  visit(body);
377
423
  return out;
@@ -673,7 +719,7 @@ var SPAN_EDGE = String.raw`(?:\\\n|\s)`;
673
719
  function applyMarks(text, marks, ctx, before, after) {
674
720
  const escaped = escapeInline(text);
675
721
  const lead = new RegExp(`^${SPAN_EDGE}*`).exec(escaped)?.[0] ?? "";
676
- const trail = new RegExp(`${SPAN_EDGE}*$`).exec(escaped)?.[0] ?? "";
722
+ const trail = trailingEdge(escaped);
677
723
  const core = escaped.slice(lead.length, escaped.length - trail.length);
678
724
  if (core.length === 0) return escaped;
679
725
  const outerBefore = lead.length > 0 ? lead[lead.length - 1] : before;
@@ -707,6 +753,22 @@ function applyMarks(text, marks, ctx, before, after) {
707
753
  }
708
754
  return `${lead}${linkify(out, marks, ctx)}${trail}`;
709
755
  }
756
+ /**
757
+ * The whitespace and hard breaks the escaped text ends in — what
758
+ * `${SPAN_EDGE}*$` matches, read back from the end instead. Tried from the
759
+ * front, that expression rescans a long run of whitespace from each of its
760
+ * characters whenever something other than the end follows the run, and is
761
+ * quadratic on it.
762
+ */
763
+ function trailingEdge(escaped) {
764
+ let from = escaped.length;
765
+ while (from > 0) {
766
+ const c = escaped[from - 1];
767
+ if (c === "\\" ? escaped[from] !== "\n" : !/\s/u.test(c)) break;
768
+ from--;
769
+ }
770
+ return escaped.slice(from);
771
+ }
710
772
  var isSpace = (c) => c === "" || /\s/u.test(c);
711
773
  var isPunct = (c) => /[\p{P}\p{S}]/u.test(c);
712
774
  /** §6.2 — the delimiter run is left-flanking, so it can open emphasis. */
@@ -1,10 +1,10 @@
1
1
  import { dict, name, ref, stream, unicodeString } from "./objects.js";
2
2
  import { embedTtfFont } from "./cid-font.js";
3
3
  import { preparePdfEncryption } from "./encryption.js";
4
- import { reorderVisual } from "../core/bidi/algorithm.js";
4
+ import { pageLayers, paintPlan } from "../layout/page-doc.js";
5
5
  import { reverseByCodePoint } from "../core/bidi/index.js";
6
- import { GLUE_SHRINK_RATIO } from "../layout/styled-layout.js";
7
- import { paintPlan } from "../layout/page-doc.js";
6
+ import "../layout/styled-layout.js";
7
+ import { BORDER_DASHES, FAUX_ITALIC_SHEAR, computeJustifyExtra, lineVisualOrder, settableInOneMatrix, washVeil } from "../layout/line-paint.js";
8
8
  import { emitClipPath, emitVectorShape, shadowBlurLayers } from "./vector-graphics.js";
9
9
  import { buildGradientAlphaMask, buildGradientPattern, shapeBbox } from "./shading.js";
10
10
  import { warpGlyphMatrix } from "../core/drawingml/text-warp.js";
@@ -16,16 +16,6 @@ import { addSignaturePlaceholder } from "./signature.js";
16
16
  import { buildXmpPacket } from "./xmp.js";
17
17
  //#region src/pdf/styled-page-emitter.ts
18
18
  var encoder = new TextEncoder();
19
- function washVeil(wash) {
20
- if (!wash) return void 0;
21
- const alpha = 1 - wash.gain;
22
- if (!(alpha > .004)) return void 0;
23
- const offset = .5 * (1 - wash.gain) + wash.black;
24
- return {
25
- alpha: Math.min(1, alpha),
26
- grey: Math.max(0, Math.min(1, offset / alpha))
27
- };
28
- }
29
19
  /**
30
20
  * The veil operators for a washed picture, to follow its own `/Im Do`: the
31
21
  * placement matrix maps the unit square onto the picture as drawn, so the wash
@@ -268,7 +258,7 @@ function assembleStyledPdf(laid, options, doc) {
268
258
  tagFor: (structId) => builder.node(structId).type
269
259
  };
270
260
  }
271
- const { content: contentBytes, links } = emitPageContent(page, pageTagging, gradientNames, alphaStateNames, wantDuotone, gradientMaskNames);
261
+ const { content: contentBytes, links } = emitPageLayers(page, pageTagging, gradientNames, alphaStateNames, wantDuotone, gradientMaskNames);
272
262
  const contentsRef = doc.add(stream({}, contentBytes));
273
263
  const pageEntries = {
274
264
  Type: name("Page"),
@@ -611,31 +601,56 @@ function emitTaggedRuns(out, cmds, tagging, body) {
611
601
  * 24. Scaling them by the width instead drew page-borders-export-case-2.docx's
612
602
  * half-point frame as a row of specks a quarter the length it should be.
613
603
  */
614
- var BORDER_DASHES = new Map([
615
- ["dashed", [8, 2.5]],
616
- ["dashSmallGap", [3, 1]],
617
- ["dotted", [.5, 1]],
618
- ["dashDot", [
619
- 8,
620
- 2.5,
621
- 2.5,
622
- 2.5
623
- ]],
624
- ["dashDotDot", [
625
- 8,
626
- 2.5,
627
- 2.5,
628
- 2.5,
629
- 2.5,
630
- 2.5
631
- ]]
632
- ]);
633
- var FAUX_ITALIC_SHEAR = .2126;
634
604
  /** The PDF dash array for a border style; an empty string means a solid rule. */
635
605
  function dashPatternFor(style) {
636
606
  const pattern = style !== void 0 ? BORDER_DASHES.get(style) : void 0;
637
607
  return pattern ? `[${pattern.map(formatNumber).join(" ")}]` : "";
638
608
  }
609
+ /**
610
+ * A page's content stream in its layers (pageLayers): its own items, then —
611
+ * when some of them stand IN FRONT of its text (PageItemBase.over) — those,
612
+ * painted again pass by pass over what the first layer left, their own text
613
+ * last; and each group seen through a window of its own (PageItemBase.window)
614
+ * inside a clip to it. Each layer is a balanced stream of its own, so they
615
+ * simply follow one another.
616
+ */
617
+ function emitPageLayers(page, ...rest) {
618
+ const layers = pageLayers(page.commands);
619
+ if (layers.length === 1 && !layers[0].window) return emitPageContent(page, ...rest);
620
+ const parts = [];
621
+ const links = [];
622
+ for (const layer of layers) {
623
+ const emitted = emitPageContent({
624
+ ...page,
625
+ commands: layer.items
626
+ }, ...rest);
627
+ links.push(...emitted.links);
628
+ const w = layer.window;
629
+ if (!w) {
630
+ parts.push(emitted.content);
631
+ continue;
632
+ }
633
+ if (emitted.content.length === 0) continue;
634
+ const rect = [
635
+ w.x,
636
+ page.height - w.y - w.height,
637
+ w.width,
638
+ w.height
639
+ ].map(formatNumber);
640
+ parts.push(encoder.encode(`q ${rect.join(" ")} re W n`), emitted.content, encoder.encode("Q"));
641
+ }
642
+ const content = new Uint8Array(parts.reduce((sum, p) => sum + p.length, 0) + Math.max(0, parts.length - 1));
643
+ let at = 0;
644
+ parts.forEach((p, i) => {
645
+ if (i > 0) content[at++] = 10;
646
+ content.set(p, at);
647
+ at += p.length;
648
+ });
649
+ return {
650
+ content,
651
+ links
652
+ };
653
+ }
639
654
  function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneStateFor, gradientMaskNames) {
640
655
  const commands = page.commands;
641
656
  const links = [];
@@ -1414,9 +1429,6 @@ function emitPageContent(page, tagging, gradientNames, alphaStateNames, duotoneS
1414
1429
  links
1415
1430
  };
1416
1431
  }
1417
- function lineVisualOrder(line) {
1418
- return reorderVisual(line.tokens.map((t) => t.bidiLevel));
1419
- }
1420
1432
  /**
1421
1433
  * The operators that put an image XObject in a box — ISO 32000-1 §8.9.5.1: the
1422
1434
  * unit square scaled to the box and moved to `(x, y)`, its bottom-left corner.
@@ -1460,40 +1472,6 @@ function rotateAboutCm(cx, cy, deg) {
1460
1472
  const s = Math.sin(rad);
1461
1473
  return `${formatNumber(c)} ${formatNumber(s)} ${formatNumber(-s)} ${formatNumber(c)} ${formatNumber(cx - cx * c + cy * s)} ${formatNumber(cy - cx * s - cy * c)} cm`;
1462
1474
  }
1463
- /**
1464
- * Whether a line is ONE text matrix and a run of glyphs: nothing in it placed
1465
- * on its own — a tab, a stretched space, a picture, a formula, a right-to-left
1466
- * run, a rise, a faked italic — and nothing drawn beside its glyphs, a run's
1467
- * shading, a highlight or a rule. Only such a line can be turned by its matrix
1468
- * alone; any other is placed token by token in page space.
1469
- *
1470
- * @param line The laid-out line.
1471
- * @returns True when a single `Tm` sets the whole of it.
1472
- */
1473
- function settableInOneMatrix(line) {
1474
- return computeJustifyExtra(line) === 0 && line.tokens.every((t) => t.kind === "text" && t.tab !== true && t.bidiLevel % 2 === 0 && t.risePt === void 0 && t.synthetic?.italic !== true && t.highlight !== true && t.resolvedRun.shadingColorHex === void 0 && t.resolvedRun.underline === "none" && !t.resolvedRun.strike);
1475
- }
1476
- function computeJustifyExtra(line) {
1477
- const alignment = line.resolved.alignment;
1478
- if (alignment !== "both" && alignment !== "distribute") return 0;
1479
- if (line.noJustify === true) return 0;
1480
- if (line.isLastInParagraph && alignment !== "distribute") return 0;
1481
- let last = line.tokens.length - 1;
1482
- while (last >= 0 && line.tokens[last].kind === "text" && line.tokens[last].isSpace) last--;
1483
- let spaces = 0;
1484
- let narrowest = Infinity;
1485
- let contentWidthPt = 0;
1486
- for (let i = 0; i <= last; i++) {
1487
- const tok = line.tokens[i];
1488
- contentWidthPt += tok.widthPt;
1489
- if (!tok.isSpace) continue;
1490
- spaces++;
1491
- if (tok.widthPt < narrowest) narrowest = tok.widthPt;
1492
- }
1493
- if (spaces === 0) return 0;
1494
- const extra = (line.availableWidthPt - contentWidthPt) / spaces;
1495
- return extra < 0 ? Math.max(extra, -narrowest * GLUE_SHRINK_RATIO) : extra;
1496
- }
1497
1475
  function hexToRgb01(hex) {
1498
1476
  return [
1499
1477
  parseInt(hex.slice(0, 2), 16) / 255,
@@ -1,6 +1,6 @@
1
1
  import { PdfDocument } from "./writer.js";
2
- import { layoutStyledDocument } from "../layout/styled-layout.js";
3
2
  import "../layout/page-doc.js";
3
+ import { layoutStyledDocument } from "../layout/styled-layout.js";
4
4
  import { emitStyledPdf, emitStyledPdfEncrypted } from "./styled-page-emitter.js";
5
5
  //#region src/pdf/styled-page-renderer.ts
6
6
  /**
@@ -260,7 +260,7 @@ function freeTextContents(file, annot) {
260
260
  const found = file.get(annot, "Contents");
261
261
  const raw = found instanceof PdfHexString ? String.fromCharCode(...found.bytes) : typeof found === "string" ? found : void 0;
262
262
  if (raw === void 0) return void 0;
263
- const text = textString(raw).replace(/\s+$/u, "");
263
+ const text = textString(raw).trimEnd();
264
264
  return text.length > 0 ? text : void 0;
265
265
  }
266
266
  /**
@@ -331,7 +331,7 @@ function defaultAppearance(file, annot) {
331
331
  const form = acro instanceof Map ? file.get(acro, "DA") : void 0;
332
332
  const da = own ?? (typeof form === "string" ? form : "");
333
333
  const tf = /\/([^\s/]+)\s+([\d.]+)\s+Tf/u.exec(da);
334
- const rgb = /([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+rg/u.exec(da);
334
+ const rgb = /(?<![\d.])([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+rg/u.exec(da);
335
335
  const gray = /(^|\s)([\d.]+)\s+g(\s|$)/u.exec(da);
336
336
  const color = rgb ? `${num(Number(rgb[1]))} ${num(Number(rgb[2]))} ${num(Number(rgb[3]))}` : gray ? `${num(Number(gray[2]))} ${num(Number(gray[2]))} ${num(Number(gray[2]))}` : "0 0 0";
337
337
  return {
@@ -137,7 +137,9 @@ function typedField(file, annot) {
137
137
  * field whose value is "Hello World".
138
138
  */
139
139
  function withoutVariableText(file, stream) {
140
- const stripped = new TextDecoder("latin1").decode(file.streamData(stream)).replace(/\/Tx\s+BMC[\s\S]*?EMC/gu, "");
140
+ const text = new TextDecoder("latin1").decode(file.streamData(stream));
141
+ const end = text.lastIndexOf("EMC") + 3;
142
+ const stripped = text.slice(0, end).replace(/\/Tx\s+BMC[\s\S]*?EMC/gu, "") + text.slice(end);
141
143
  const dict = new Map(stream.dict);
142
144
  dict.delete("Filter");
143
145
  dict.delete("DecodeParms");
@@ -1,14 +1,14 @@
1
1
  import { knowsFamily, resolveFamilyStyle } from "../core/fonts/remote-fonts.js";
2
2
  import { parseTtf } from "../core/font/ttf-parser.js";
3
3
  import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
4
+ import { baseEncodingTable, isStandardLatinFace, macGlyphName, standardEncodingTable, winAnsiLatinName } from "./encodings.js";
5
+ import { cffCidToGid, cffFsType, cffNameToGid, cffOutlineSource, cffSpaceAdvance, openTypeCff } from "./cff-outline.js";
6
+ import { cmapSubtables, outlineSource, postGlyphNames, sfntFsType, sfntSpaceAdvance } from "./glyf-outline.js";
4
7
  import { parseToUnicodeCMap } from "./cmap.js";
5
8
  import { decodePredefined, predefinedCMap, splitPredefined } from "./predefined-cmap.js";
6
9
  import { isZapfDingbats, zapfDingbatsChar } from "./dingbats.js";
7
10
  import { textForGlyphName } from "./glyph-names.js";
8
- import { baseEncodingTable, isStandardLatinFace, macGlyphName, standardEncodingTable, winAnsiLatinName } from "./encodings.js";
9
- import { cffCidToGid, cffFsType, cffNameToGid, cffOutlineSource, cffSpaceAdvance, openTypeCff } from "./cff-outline.js";
10
11
  import { type1Font } from "./type1-outline.js";
11
- import { cmapSubtables, outlineSource, postGlyphNames, sfntFsType, sfntSpaceAdvance } from "./glyf-outline.js";
12
12
  import { standardFace, standardWidth } from "./standard-widths.js";
13
13
  import { eachPageFont, embeddedFontName, hasLiftableProgram, programStyle } from "./embedded-fonts.js";
14
14
  //#region src/pdf-reader/font.ts
@@ -1047,7 +1047,7 @@ function familyOfFace(baseFont) {
1047
1047
  const name = plainFace(baseFont);
1048
1048
  const cut = /^(.+?)[-,]([^-,]+)$/u.exec(name);
1049
1049
  const whole = cut && STYLE_WORDS.test(cut[2]) ? cut[1] : cut ? name.replace(/-/gu, " ") : GLUED_STYLE.exec(name)?.[1] ?? name;
1050
- return (whole.replace(/(?:PSMT|PS|MT)$/u, "") || whole).replace(/([a-z])([A-Z])/gu, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/gu, "$1 $2").replace(/\bDeja Vu\b/u, "DejaVu").replace(/\s+/gu, " ").trim();
1050
+ return (whole.replace(/(?:PSMT|PS|MT)$/u, "") || whole).replace(/([a-z])([A-Z])/gu, "$1 $2").replace(/(?<![A-Z])([A-Z]+)([A-Z][a-z])/gu, "$1 $2").replace(/\bDeja Vu\b/u, "DejaVu").replace(/\s+/gu, " ").trim();
1051
1051
  }
1052
1052
  /**
1053
1053
  * A face name with what producers write around it taken off: the subset tag
@@ -1585,7 +1585,7 @@ function inkSpan(runs) {
1585
1585
  const last = marked[marked.length - 1];
1586
1586
  const space = (r) => r.spaceWidthPt !== void 0 && r.spaceWidthPt > 0 ? r.spaceWidthPt : (r.fontSizePt || 10) * .25;
1587
1587
  const lead = (/^\s*/u.exec(first.text)?.[0].length ?? 0) * space(first);
1588
- const trail = (/\s*$/u.exec(last.text)?.[0].length ?? 0) * space(last);
1588
+ const trail = (last.text.length - last.text.trimEnd().length) * space(last);
1589
1589
  const x = first.x + lead;
1590
1590
  return {
1591
1591
  x,
@@ -0,0 +1,58 @@
1
+ import { FontResource, LaidOutDocument, TextLineItem } from '../layout/page-doc.js';
2
+ import { ImageCrop } from '../core/document-model/index.js';
3
+ import { VectorShape } from '../core/vector.js';
4
+ /** The glyph outlines of a page's faces, each defined once and used by id. */
5
+ export declare class SvgGlyphs {
6
+ private readonly sources;
7
+ private readonly ids;
8
+ private readonly defs;
9
+ private readonly faceIds;
10
+ /**
11
+ * The id of the path that draws glyph `gid` of `font`, defining it on first
12
+ * use: `null` for a glyph with no ink (a space), `undefined` where the face
13
+ * carries no outlines this reads — the caller then sets the token as text.
14
+ *
15
+ * @param font The face.
16
+ * @param gid The glyph.
17
+ * @returns The `<path>` id, `null`, or `undefined`.
18
+ */
19
+ use(font: FontResource, gid: number): string | null | undefined;
20
+ /** The `<defs>` holding every glyph used, or nothing when none was. */
21
+ defsXml(): string;
22
+ private sourceOf;
23
+ }
24
+ /** What drawing a line needs from the writer around it. */
25
+ export interface SvgLineCtx {
26
+ readonly glyphs: SvgGlyphs;
27
+ /** The document-wide counter generated ids are numbered from. */
28
+ readonly ids: {
29
+ n: number;
30
+ };
31
+ readonly laid: LaidOutDocument;
32
+ /** A picture in a box, as the writer draws one. */
33
+ readonly image: (out: Array<string>, placed: PlacedImage) => void;
34
+ /** A vector shape, as the writer draws one. */
35
+ readonly shape: (out: Array<string>, shape: VectorShape) => void;
36
+ }
37
+ /** A picture placed in a box on the page — its top-left corner and size, in points. */
38
+ export interface PlacedImage {
39
+ readonly resourceName: string;
40
+ readonly x: number;
41
+ readonly y: number;
42
+ readonly width: number;
43
+ readonly height: number;
44
+ readonly crop?: ImageCrop;
45
+ readonly rotationDeg?: number;
46
+ readonly flipH?: boolean;
47
+ readonly flipV?: boolean;
48
+ readonly alpha?: number;
49
+ }
50
+ /**
51
+ * Draw one laid-out line: what stands behind its glyphs, then the glyphs, a
52
+ * picture or formula wherever one stands in it.
53
+ *
54
+ * @param out The SVG fragment sink.
55
+ * @param item The line, as the layout placed it.
56
+ * @param ctx The glyph definitions and the writer's own drawing.
57
+ */
58
+ export declare function emitSvgLine(out: Array<string>, item: TextLineItem, ctx: SvgLineCtx): void;