reamkit 1.31.0 → 1.32.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 (83) 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/document-model/index.d.ts +1 -1
  10. package/dist/esm/core/document-model/types.d.ts +112 -0
  11. package/dist/esm/core/drawingml/chart-geometry.d.ts +21 -8
  12. package/dist/esm/core/drawingml/chart-geometry.js +198 -108
  13. package/dist/esm/core/drawingml/chart-parser.js +73 -2
  14. package/dist/esm/core/drawingml/colors.d.ts +6 -0
  15. package/dist/esm/core/drawingml/colors.js +23 -3
  16. package/dist/esm/core/drawingml/shape-render.d.ts +3 -1
  17. package/dist/esm/core/drawingml/shape-render.js +4 -2
  18. package/dist/esm/core/font/measure.d.ts +9 -0
  19. package/dist/esm/core/font/measure.js +9 -1
  20. package/dist/esm/core/fonts/families.js +4 -2
  21. package/dist/esm/core/fonts/scripts.js +4 -2
  22. package/dist/esm/core/ir/flow.d.ts +6 -0
  23. package/dist/esm/core/ir/sheet.d.ts +43 -0
  24. package/dist/esm/core/number-format.js +16 -11
  25. package/dist/esm/core/spreadsheet-model/index.d.ts +1 -1
  26. package/dist/esm/core/spreadsheet-model/types.d.ts +96 -0
  27. package/dist/esm/core/style-cascade/resolver.js +4 -2
  28. package/dist/esm/excel/column-bands.d.ts +39 -6
  29. package/dist/esm/excel/column-bands.js +41 -17
  30. package/dist/esm/excel/comments-parser.js +19 -0
  31. package/dist/esm/excel/conditional-format.d.ts +7 -3
  32. package/dist/esm/excel/conditional-format.js +77 -39
  33. package/dist/esm/excel/formula/context.d.ts +18 -0
  34. package/dist/esm/excel/formula/eval.js +51 -0
  35. package/dist/esm/excel/formula/index.d.ts +2 -2
  36. package/dist/esm/excel/formula/lexer.d.ts +1 -1
  37. package/dist/esm/excel/formula/lexer.js +41 -0
  38. package/dist/esm/excel/formula/parser.d.ts +25 -0
  39. package/dist/esm/excel/formula/parser.js +95 -0
  40. package/dist/esm/excel/print-model.d.ts +38 -1
  41. package/dist/esm/excel/print-model.js +522 -80
  42. package/dist/esm/excel/shared-strings-parser.d.ts +9 -0
  43. package/dist/esm/excel/shared-strings-parser.js +11 -1
  44. package/dist/esm/excel/sheet-drawing.d.ts +38 -2
  45. package/dist/esm/excel/sheet-drawing.js +47 -13
  46. package/dist/esm/excel/sheet-shape-parser.d.ts +6 -2
  47. package/dist/esm/excel/sheet-shape-parser.js +18 -6
  48. package/dist/esm/excel/sheet-to-flow.d.ts +17 -0
  49. package/dist/esm/excel/sheet-to-flow.js +434 -47
  50. package/dist/esm/excel/styles-parser.d.ts +9 -0
  51. package/dist/esm/excel/styles-parser.js +80 -26
  52. package/dist/esm/excel/table-parser.js +5 -0
  53. package/dist/esm/excel/table-style-presets.d.ts +26 -0
  54. package/dist/esm/excel/table-style-presets.js +1555 -0
  55. package/dist/esm/excel/table-style.d.ts +13 -0
  56. package/dist/esm/excel/table-style.js +75 -0
  57. package/dist/esm/excel/tint.d.ts +10 -0
  58. package/dist/esm/excel/tint.js +75 -0
  59. package/dist/esm/excel/vml-drawing.d.ts +41 -3
  60. package/dist/esm/excel/vml-drawing.js +143 -10
  61. package/dist/esm/excel/worksheet-parser.js +89 -15
  62. package/dist/esm/excel/xls/biff-reader.js +10 -2
  63. package/dist/esm/excel/xlsx-reader.js +195 -14
  64. package/dist/esm/excel/xlsx-writer.js +8 -7
  65. package/dist/esm/html/html-writer.js +202 -20
  66. package/dist/esm/layout/fit-pages.d.ts +11 -0
  67. package/dist/esm/layout/fit-pages.js +86 -0
  68. package/dist/esm/layout/line-paint.d.ts +58 -0
  69. package/dist/esm/layout/line-paint.js +107 -0
  70. package/dist/esm/layout/page-doc.d.ts +51 -0
  71. package/dist/esm/layout/page-doc.js +141 -1
  72. package/dist/esm/layout/styled-layout.d.ts +7 -0
  73. package/dist/esm/layout/styled-layout.js +274 -51
  74. package/dist/esm/layout/turned-section.js +14 -2
  75. package/dist/esm/markdown/markdown-writer.js +17 -6
  76. package/dist/esm/pdf/styled-page-emitter.js +49 -71
  77. package/dist/esm/pdf/styled-page-renderer.js +1 -1
  78. package/dist/esm/pdf-reader/font.js +3 -3
  79. package/dist/esm/svg/svg-text.d.ts +58 -0
  80. package/dist/esm/svg/svg-text.js +322 -0
  81. package/dist/esm/svg/svg-writer.js +135 -71
  82. package/dist/esm/word/docx-writer.js +11 -2
  83. package/package.json +5 -1
@@ -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
  /**
@@ -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
@@ -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;
@@ -0,0 +1,322 @@
1
+ import { svgPathData } from "../core/vector.js";
2
+ import { reverseByCodePoint } from "../core/bidi/index.js";
3
+ import { FAUX_ITALIC_SHEAR, computeJustifyExtra, lineVisualOrder } from "../layout/line-paint.js";
4
+ import { warpGlyphMatrix } from "../core/drawingml/text-warp.js";
5
+ import { sanitizeHref } from "../core/links.js";
6
+ import { cffOutlineSource, openTypeCff } from "../pdf-reader/cff-outline.js";
7
+ import { outlineSource } from "../pdf-reader/glyf-outline.js";
8
+ //#region src/svg/svg-text.ts
9
+ /** The colour of a comment range's highlight — the PDF emitter's. */
10
+ var HIGHLIGHT_HEX = "fff3a3";
11
+ /** The glyph outlines of a page's faces, each defined once and used by id. */
12
+ var SvgGlyphs = class {
13
+ sources = /* @__PURE__ */ new Map();
14
+ ids = /* @__PURE__ */ new Map();
15
+ defs = [];
16
+ faceIds = /* @__PURE__ */ new Map();
17
+ /**
18
+ * The id of the path that draws glyph `gid` of `font`, defining it on first
19
+ * use: `null` for a glyph with no ink (a space), `undefined` where the face
20
+ * carries no outlines this reads — the caller then sets the token as text.
21
+ *
22
+ * @param font The face.
23
+ * @param gid The glyph.
24
+ * @returns The `<path>` id, `null`, or `undefined`.
25
+ */
26
+ use(font, gid) {
27
+ const source = this.sourceOf(font);
28
+ if (!source) return void 0;
29
+ let face = this.faceIds.get(font);
30
+ if (face === void 0) {
31
+ face = this.faceIds.size;
32
+ this.faceIds.set(font, face);
33
+ }
34
+ const key = `g${String(face)}-${String(gid)}`;
35
+ const known = this.ids.get(key);
36
+ if (known !== void 0) return known;
37
+ const path = source.path(gid);
38
+ if (!path || path.length === 0) {
39
+ this.ids.set(key, null);
40
+ return null;
41
+ }
42
+ this.defs.push(`<path id="${key}" d="${svgPathData(path, fmtEm)}"/>`);
43
+ this.ids.set(key, key);
44
+ return key;
45
+ }
46
+ /** The `<defs>` holding every glyph used, or nothing when none was. */
47
+ defsXml() {
48
+ return this.defs.length > 0 ? `<defs>${this.defs.join("")}</defs>` : "";
49
+ }
50
+ sourceOf(font) {
51
+ let source = this.sources.get(font);
52
+ if (source === void 0) {
53
+ const raw = font.parsed.raw;
54
+ const cff = font.parsed.tables.has("CFF ") ? openTypeCff(raw) : void 0;
55
+ source = (cff ? cffOutlineSource(cff) : outlineSource(raw)) ?? null;
56
+ this.sources.set(font, source);
57
+ }
58
+ return source ?? void 0;
59
+ }
60
+ };
61
+ /**
62
+ * Draw one laid-out line: what stands behind its glyphs, then the glyphs, a
63
+ * picture or formula wherever one stands in it.
64
+ *
65
+ * @param out The SVG fragment sink.
66
+ * @param item The line, as the layout placed it.
67
+ * @param ctx The glyph definitions and the writer's own drawing.
68
+ */
69
+ function emitSvgLine(out, item, ctx) {
70
+ const clip = item.clip;
71
+ if (clip) {
72
+ const id = `clip${String(ctx.ids.n++)}`;
73
+ 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})">`);
74
+ }
75
+ if (item.warp) emitWarped(out, item, ctx);
76
+ else if (item.rotationDeg) {
77
+ out.push(`<g transform="rotate(${fmt(-item.rotationDeg)} ${fmt(item.originX)} ${fmt(item.baselineY)})">`);
78
+ emitFlat(out, item, ctx);
79
+ out.push("</g>");
80
+ } else emitFlat(out, item, ctx);
81
+ if (clip) out.push("</g>");
82
+ }
83
+ /** A line set along its baseline, token by token. */
84
+ function emitFlat(out, item, ctx) {
85
+ const line = item.line;
86
+ const originX = item.originX;
87
+ const baseline = item.baselineY;
88
+ const extraPerSpace = computeJustifyExtra(line);
89
+ const advance = (tok) => tok.widthPt + (tok.kind === "text" && tok.isSpace ? extraPerSpace : 0);
90
+ const lineFs = line.maxFontSizePt || 12;
91
+ const boxAscent = Math.max(lineFs, line.mathAscentPt ?? 0);
92
+ const boxDescent = Math.max(lineFs * .2, line.mathDescentPt ?? 0);
93
+ let x = originX;
94
+ for (const tok of line.tokens) {
95
+ const w = advance(tok);
96
+ if (tok.kind === "text") {
97
+ const hex = tok.resolvedRun.shadingColorHex ?? (tok.highlight === true ? HIGHLIGHT_HEX : void 0);
98
+ if (hex !== void 0) out.push(`<rect x="${fmt(x)}" y="${fmt(baseline - boxAscent)}" width="${fmt(w)}" height="${fmt(boxAscent + boxDescent)}" fill="#${hex}"/>`);
99
+ }
100
+ x += w;
101
+ }
102
+ x = originX;
103
+ for (const tok of line.tokens) {
104
+ const w = advance(tok);
105
+ if (tok.kind === "text" && (tok.resolvedRun.underline !== "none" || tok.resolvedRun.strike)) emitDecoration(out, tok, x, w, baseline);
106
+ x += w;
107
+ }
108
+ const order = line.tokens.some((t) => t.bidiLevel % 2 === 1) ? lineVisualOrder(line) : line.tokens.map((_, i) => i);
109
+ x = originX;
110
+ for (const i of order) {
111
+ const tok = line.tokens[i];
112
+ if (tok.kind === "image") emitImageToken(out, tok, x, baseline, ctx);
113
+ else if (tok.kind === "math") emitMathToken(out, tok, x, baseline, ctx);
114
+ else emitTextToken(out, tok, x, baseline, ctx);
115
+ x += advance(tok);
116
+ }
117
+ const words = line.tokens.map((t) => t.kind === "text" ? t.text : "").join("");
118
+ if (words.trim().length > 0) out.push(`<text x="${fmt(originX)}" y="${fmt(baseline)}" font-size="${fmt(lineFs)}" textLength="${fmt(x - originX)}" lengthAdjust="spacingAndGlyphs" fill-opacity="0" xml:space="preserve">${escapeXml(words)}</text>`);
119
+ }
120
+ /**
121
+ * One run's glyphs from `x` along the baseline: the shaped glyphs of its text
122
+ * (reversed for a right-to-left run, as the PDF shows it), each at the advance
123
+ * the shaper gave it plus the run's letter spacing.
124
+ */
125
+ function emitTextToken(out, tok, x, baseline, ctx) {
126
+ if (tok.text.length === 0) return;
127
+ const size = tok.fontSizePt;
128
+ const scale = (tok.synthetic?.widthScale ?? 1) * (tok.resolvedRun.widthScale ?? 1);
129
+ const slant = tok.synthetic?.italic === true ? FAUX_ITALIC_SHEAR : 0;
130
+ const rise = tok.risePt ?? 0;
131
+ const text = tok.bidiLevel % 2 === 1 ? reverseByCodePoint(tok.text) : tok.text;
132
+ const run = tok.font.measure.glyphRun(text);
133
+ const upem = tok.font.parsed.unitsPerEm;
134
+ const spacingEm = (tok.resolvedRun.letterSpacingPt ?? 0) / (size * scale);
135
+ const uses = [];
136
+ let pen = 0;
137
+ for (let g = 0; g < run.gids.length; g++) {
138
+ const id = ctx.glyphs.use(tok.font, run.gids[g]);
139
+ if (id === void 0) {
140
+ emitAsText(out, tok, x, baseline);
141
+ return;
142
+ }
143
+ if (id !== null) uses.push(`<use href="#${id}"${pen !== 0 ? ` x="${fmtEm(pen)}"` : ""}/>`);
144
+ pen += (run.advances[g] ?? 0) / upem + spacingEm;
145
+ }
146
+ if (uses.length === 0) return;
147
+ const outline = tok.resolvedRun.textOutline;
148
+ const strokeWidth = outline !== void 0 ? outline.widthPt : tok.synthetic?.bold === true ? size * .03 : 0;
149
+ const stroke = strokeWidth > 0 ? ` stroke="#${outline?.colorHex ?? tok.resolvedRun.colorHex}" stroke-width="${fmtEm(strokeWidth / size)}"` : "";
150
+ const group = `<g transform="matrix(${fmt(size * scale)} 0 ${fmt(slant * size)} ${fmt(-size)} ${fmt(x)} ${fmt(baseline - rise)})" fill="#${tok.resolvedRun.colorHex}"${stroke}>${uses.join("")}</g>`;
151
+ const href = tok.href !== void 0 ? sanitizeHref(tok.href) : void 0;
152
+ out.push(href !== void 0 ? `<a href="${escapeXml(href)}">${group}</a>` : group);
153
+ }
154
+ /** A run of a face whose outlines cannot be read: its characters, for the viewer's fonts to set. */
155
+ function emitAsText(out, tok, x, baseline) {
156
+ if (tok.text.trim().length === 0) return;
157
+ out.push(`<text x="${fmt(x)}" y="${fmt(baseline - (tok.risePt ?? 0))}" font-family="sans-serif" font-size="${fmt(tok.fontSizePt)}" fill="#${tok.resolvedRun.colorHex}">${escapeXml(tok.text)}</text>`);
158
+ }
159
+ /** A run's underline and strikethrough, where the PDF emitter draws them. */
160
+ function emitDecoration(out, tok, x, w, baseline) {
161
+ const size = tok.fontSizePt;
162
+ const thickness = Math.max(.4, size * .06);
163
+ const style = tok.resolvedRun.underline;
164
+ if (style !== "none") {
165
+ const hex = tok.resolvedRun.underlineColorHex ?? tok.resolvedRun.colorHex;
166
+ const t = style === "thick" || style === "dottedHeavy" || style === "dashHeavy" ? thickness * 2 : thickness;
167
+ const top = baseline + size * .12;
168
+ const dashed = style === "dotted" || style === "dottedHeavy" ? [t, t * 2] : style === "dash" || style === "dashHeavy" ? [t * 4, t * 3] : void 0;
169
+ if (dashed) out.push(`<line x1="${fmt(x)}" y1="${fmt(top + t / 2)}" x2="${fmt(x + w)}" y2="${fmt(top + t / 2)}" stroke="#${hex}" stroke-width="${fmt(t)}" stroke-dasharray="${dashed.map(fmt).join(" ")}"/>`);
170
+ else {
171
+ out.push(`<rect x="${fmt(x)}" y="${fmt(top)}" width="${fmt(w)}" height="${fmt(t)}" fill="#${hex}"/>`);
172
+ if (style === "double") out.push(`<rect x="${fmt(x)}" y="${fmt(top + t * 2)}" width="${fmt(w)}" height="${fmt(t)}" fill="#${hex}"/>`);
173
+ }
174
+ }
175
+ if (tok.resolvedRun.strike) out.push(`<rect x="${fmt(x)}" y="${fmt(baseline - size * .26 - thickness)}" width="${fmt(w)}" height="${fmt(thickness)}" fill="#${tok.resolvedRun.colorHex}"/>`);
176
+ }
177
+ /**
178
+ * An inline picture standing on the baseline, its bottom edge on it — or a
179
+ * metafile, which draws itself from that corner in a frame of its own (y up).
180
+ */
181
+ function emitImageToken(out, tok, x, baseline, ctx) {
182
+ const b = tok.drawBox;
183
+ const left = x + (b?.dxPt ?? 0);
184
+ const bottom = baseline - (b?.dyPt ?? 0);
185
+ const width = b?.widthPt ?? tok.widthPt;
186
+ const height = b?.heightPt ?? tok.heightPt;
187
+ const meta = tok.metafile;
188
+ if (meta) {
189
+ for (const img of meta.images ?? []) {
190
+ if (img.resourceName === "") continue;
191
+ ctx.image(out, {
192
+ resourceName: img.resourceName,
193
+ x: left + img.x,
194
+ y: bottom - img.y - img.height,
195
+ width: img.width,
196
+ height: img.height,
197
+ ...img.rotationDeg !== void 0 ? { rotationDeg: img.rotationDeg } : {}
198
+ });
199
+ }
200
+ for (const sh of meta.shapes) ctx.shape(out, {
201
+ ...sh,
202
+ transform: [
203
+ 1,
204
+ 0,
205
+ 0,
206
+ -1,
207
+ left,
208
+ bottom
209
+ ]
210
+ });
211
+ for (const t of meta.texts) {
212
+ const first = t.line.tokens.find((k) => k.kind === "text");
213
+ if (first?.kind !== "text") continue;
214
+ emitTextToken(out, first, left + t.x, bottom - t.y, ctx);
215
+ }
216
+ return;
217
+ }
218
+ if (!tok.imageResourceName) return;
219
+ const shadow = tok.shadow;
220
+ if (shadow) {
221
+ const id = `shadow${String(ctx.ids.n++)}`;
222
+ const blur = shadow.blurPt > 0;
223
+ if (blur) out.push(`<filter id="${id}" x="-50%" y="-50%" width="200%" height="200%"><feGaussianBlur stdDeviation="${fmt(shadow.blurPt / 2)}"/></filter>`);
224
+ out.push(`<rect x="${fmt(left + shadow.dxPt)}" y="${fmt(bottom - height + shadow.dyPt)}" width="${fmt(width)}" height="${fmt(height)}" fill="#${shadow.colorHex}" opacity="${fmt(shadow.alpha)}"${blur ? ` filter="url(#${id})"` : ""}/>`);
225
+ }
226
+ ctx.image(out, {
227
+ resourceName: tok.imageResourceName,
228
+ x: left,
229
+ y: bottom - height,
230
+ width,
231
+ height,
232
+ ...tok.crop ? { crop: tok.crop } : {},
233
+ ...tok.rotationDeg !== void 0 ? { rotationDeg: tok.rotationDeg } : {},
234
+ ...tok.flipH === true ? { flipH: true } : {},
235
+ ...tok.flipV === true ? { flipV: true } : {}
236
+ });
237
+ const frame = tok.outline;
238
+ if (frame) out.push(`<rect x="${fmt(left)}" y="${fmt(bottom - height)}" width="${fmt(width)}" height="${fmt(height)}" fill="none" stroke="#${frame.colorHex}" stroke-width="${fmt(frame.widthPt)}"/>`);
239
+ }
240
+ /** An inline formula: its glyphs, rules and paths, placed from its origin on the baseline (y up). */
241
+ function emitMathToken(out, tok, x, baseline, ctx) {
242
+ for (const it of tok.items) if (it.kind === "glyph") {
243
+ const run = it.font.measure.glyphRun(it.text);
244
+ const upem = it.font.parsed.unitsPerEm;
245
+ const uses = [];
246
+ let pen = 0;
247
+ for (let g = 0; g < run.gids.length; g++) {
248
+ const id = ctx.glyphs.use(it.font, run.gids[g]);
249
+ if (id) uses.push(`<use href="#${id}"${pen !== 0 ? ` x="${fmtEm(pen)}"` : ""}/>`);
250
+ pen += (run.advances[g] ?? 0) / upem;
251
+ }
252
+ if (uses.length > 0) out.push(`<g transform="matrix(${fmt(it.sizePt)} 0 0 ${fmt(-it.sizePt)} ${fmt(x + it.x)} ${fmt(baseline - it.y)})" fill="#000000">${uses.join("")}</g>`);
253
+ } else if (it.kind === "rule") out.push(`<rect x="${fmt(x + it.x)}" y="${fmt(baseline - it.y - it.h)}" width="${fmt(it.w)}" height="${fmt(it.h)}" fill="#000000"/>`);
254
+ else ctx.shape(out, {
255
+ paths: [{ segments: it.segments }],
256
+ ...it.fill === true ? { fillColorHex: "000000" } : {},
257
+ ...it.strokeWidthPt !== void 0 ? { stroke: {
258
+ colorHex: "000000",
259
+ widthPt: it.strokeWidthPt
260
+ } } : {},
261
+ transform: [
262
+ 1,
263
+ 0,
264
+ 0,
265
+ -1,
266
+ x,
267
+ baseline
268
+ ]
269
+ });
270
+ }
271
+ /**
272
+ * §20.1.9.10 — WordArt: every glyph at its own point on the warp's curve, each
273
+ * with its own matrix. The token's glyphs are rescaled to the width it was
274
+ * measured at, as the PDF does, so the block still ends where the layout said.
275
+ */
276
+ function emitWarped(out, item, ctx) {
277
+ const warp = item.warp;
278
+ let cursor = item.originX;
279
+ for (const tok of item.line.tokens) {
280
+ if (tok.kind !== "text" || tok.isSpace) {
281
+ cursor += tok.widthPt;
282
+ continue;
283
+ }
284
+ const size = tok.fontSizePt;
285
+ const chars = [...tok.text];
286
+ const raw = chars.map((c) => tok.font.measure.textWidthPt(c, size));
287
+ const total = raw.reduce((a, b) => a + b, 0);
288
+ const k = total > 0 ? tok.widthPt / total : 1;
289
+ for (let i = 0; i < chars.length; i++) {
290
+ const adv = raw[i] * k;
291
+ const m = warpGlyphMatrix(warp, cursor, adv, item.baselineY);
292
+ cursor += adv;
293
+ if (!m) continue;
294
+ const run = tok.font.measure.glyphRun(chars[i]);
295
+ const upem = tok.font.parsed.unitsPerEm;
296
+ const uses = [];
297
+ let pen = 0;
298
+ for (let g = 0; g < run.gids.length; g++) {
299
+ const id = ctx.glyphs.use(tok.font, run.gids[g]);
300
+ if (id) uses.push(`<use href="#${id}"${pen !== 0 ? ` x="${fmtEm(pen)}"` : ""}/>`);
301
+ pen += (run.advances[g] ?? 0) / upem;
302
+ }
303
+ if (uses.length === 0) continue;
304
+ out.push(`<g transform="matrix(${fmt(m[0] * size)} ${fmt(m[1] * size)} ${fmt(m[2] * size)} ${fmt(m[3] * size)} ${fmt(m[4])} ${fmt(m[5])})" fill="#${tok.resolvedRun.colorHex}">${uses.join("")}</g>`);
305
+ }
306
+ }
307
+ }
308
+ function escapeXml(s) {
309
+ return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
310
+ }
311
+ /** A page coordinate, to the hundredth of a point. */
312
+ function fmt(n) {
313
+ const r = Math.round(n * 100) / 100;
314
+ return Object.is(r, -0) ? "0" : String(r);
315
+ }
316
+ /** A coordinate in a one-unit em, to a ten-thousandth of it. */
317
+ function fmtEm(n) {
318
+ const r = Math.round(n * 1e4) / 1e4;
319
+ return Object.is(r, -0) ? "0" : String(r);
320
+ }
321
+ //#endregion
322
+ export { SvgGlyphs, emitSvgLine };