@stll/folio-core 0.32.2 → 0.33.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 (227) hide show
  1. package/dist/ai-edits/__fixtures__/paragraphs.d.ts +17 -0
  2. package/dist/ai-edits/__fixtures__/paragraphs.js +33 -0
  3. package/dist/ai-edits/apply.d.ts +49 -3
  4. package/dist/ai-edits/apply.js +652 -86
  5. package/dist/ai-edits/blockRange.d.ts +7 -3
  6. package/dist/ai-edits/blockRange.js +14 -6
  7. package/dist/ai-edits/headless.d.ts +39 -5
  8. package/dist/ai-edits/headless.js +76 -17
  9. package/dist/ai-edits/index.d.ts +6 -6
  10. package/dist/ai-edits/index.js +3 -3
  11. package/dist/ai-edits/read.d.ts +4 -0
  12. package/dist/ai-edits/read.js +30 -6
  13. package/dist/ai-edits/scoped-reading.js +4 -3
  14. package/dist/ai-edits/snapshot.d.ts +42 -2
  15. package/dist/ai-edits/snapshot.js +127 -62
  16. package/dist/ai-edits/table-row-column-mutations.d.ts +24 -1
  17. package/dist/ai-edits/table-row-column-mutations.js +98 -6
  18. package/dist/ai-edits/table-targets.d.ts +12 -3
  19. package/dist/ai-edits/table-targets.js +15 -5
  20. package/dist/ai-edits/types.d.ts +219 -15
  21. package/dist/ai-edits/word-diff.d.ts +54 -11
  22. package/dist/ai-edits/word-diff.js +221 -61
  23. package/dist/compare/__fixtures__/body-sequence.d.ts +56 -0
  24. package/dist/compare/__fixtures__/body-sequence.js +136 -0
  25. package/dist/compare/__fixtures__/nested-table.d.ts +16 -0
  26. package/dist/compare/__fixtures__/nested-table.js +50 -0
  27. package/dist/compare/__fixtures__/numbered-list.d.ts +34 -0
  28. package/dist/compare/__fixtures__/numbered-list.js +85 -0
  29. package/dist/compare/column-alignment.d.ts +27 -0
  30. package/dist/compare/column-alignment.js +162 -0
  31. package/dist/compare/compare.d.ts +107 -0
  32. package/dist/compare/compare.js +412 -0
  33. package/dist/compare/formatting.d.ts +29 -0
  34. package/dist/compare/formatting.js +84 -0
  35. package/dist/compare/plan.d.ts +22 -0
  36. package/dist/compare/plan.js +1141 -0
  37. package/dist/compare/reproducible-package.d.ts +21 -0
  38. package/dist/compare/reproducible-package.js +47 -0
  39. package/dist/compare/scenario.d.ts +77 -0
  40. package/dist/compare/scenario.js +263 -0
  41. package/dist/compare/types.d.ts +297 -0
  42. package/dist/compare/types.js +42 -0
  43. package/dist/compare/verification.d.ts +83 -0
  44. package/dist/compare/verification.js +189 -0
  45. package/dist/compat/eigenpal.d.ts +9 -6
  46. package/dist/compat/eigenpal.js +6 -3
  47. package/dist/controller/layoutPipeline.d.ts +13 -0
  48. package/dist/controller/layoutPipeline.js +15 -1
  49. package/dist/display-list/build/buildContext.d.ts +39 -0
  50. package/dist/display-list/build/buildContext.js +31 -0
  51. package/dist/display-list/build/buildDisplayList.d.ts +79 -0
  52. package/dist/display-list/build/buildDisplayList.js +283 -0
  53. package/dist/display-list/build/colors.d.ts +20 -0
  54. package/dist/display-list/build/colors.js +136 -0
  55. package/dist/display-list/build/floatingImages.d.ts +24 -0
  56. package/dist/display-list/build/floatingImages.js +41 -0
  57. package/dist/display-list/build/fontTable.d.ts +31 -0
  58. package/dist/display-list/build/fontTable.js +97 -0
  59. package/dist/display-list/build/furniture.d.ts +86 -0
  60. package/dist/display-list/build/furniture.js +98 -0
  61. package/dist/display-list/build/glyphs.d.ts +55 -0
  62. package/dist/display-list/build/glyphs.js +103 -0
  63. package/dist/display-list/build/headerFooterPrimitives.d.ts +17 -0
  64. package/dist/display-list/build/headerFooterPrimitives.js +40 -0
  65. package/dist/display-list/build/imagePrimitives.d.ts +35 -0
  66. package/dist/display-list/build/imagePrimitives.js +264 -0
  67. package/dist/display-list/build/pageBorderPrimitives.d.ts +23 -0
  68. package/dist/display-list/build/pageBorderPrimitives.js +111 -0
  69. package/dist/display-list/build/pageFurniture.d.ts +35 -0
  70. package/dist/display-list/build/pageFurniture.js +141 -0
  71. package/dist/display-list/build/paragraphPrimitives.d.ts +19 -0
  72. package/dist/display-list/build/paragraphPrimitives.js +819 -0
  73. package/dist/display-list/build/regions.d.ts +46 -0
  74. package/dist/display-list/build/regions.js +56 -0
  75. package/dist/display-list/build/storyPrimitives.d.ts +29 -0
  76. package/dist/display-list/build/storyPrimitives.js +279 -0
  77. package/dist/display-list/build/strokes.d.ts +19 -0
  78. package/dist/display-list/build/strokes.js +78 -0
  79. package/dist/display-list/build/tablePrimitives.d.ts +38 -0
  80. package/dist/display-list/build/tablePrimitives.js +409 -0
  81. package/dist/display-list/build/textBoxPrimitives.d.ts +14 -0
  82. package/dist/display-list/build/textBoxPrimitives.js +129 -0
  83. package/dist/display-list/build/textDecorations.d.ts +29 -0
  84. package/dist/display-list/build/textDecorations.js +29 -0
  85. package/dist/display-list/build/unsupported.d.ts +57 -0
  86. package/dist/display-list/build/unsupported.js +0 -0
  87. package/dist/display-list/build/watermarkPrimitives.d.ts +15 -0
  88. package/dist/display-list/build/watermarkPrimitives.js +128 -0
  89. package/dist/display-list/dom/renderDisplayListToDom.d.ts +26 -0
  90. package/dist/display-list/dom/renderDisplayListToDom.js +620 -0
  91. package/dist/display-list/editor/displayListPagePainter.d.ts +22 -0
  92. package/dist/display-list/editor/displayListPagePainter.js +63 -0
  93. package/dist/display-list/editor/pageRenderer.d.ts +24 -0
  94. package/dist/display-list/editor/pageRenderer.js +36 -0
  95. package/dist/display-list/primitives.d.ts +95 -0
  96. package/dist/display-list/primitives.js +127 -0
  97. package/dist/display-list/types.d.ts +472 -0
  98. package/dist/display-list/types.js +0 -0
  99. package/dist/document-operations.d.ts +39 -8
  100. package/dist/document-operations.js +169 -16
  101. package/dist/document-stories.d.ts +16 -0
  102. package/dist/document-stories.js +48 -4
  103. package/dist/docx/hyperlinkParser.d.ts +9 -1
  104. package/dist/docx/hyperlinkParser.js +19 -13
  105. package/dist/docx/packageParts.d.ts +35 -0
  106. package/dist/docx/packageParts.js +120 -0
  107. package/dist/docx/paraIdRangeNormalization.d.ts +40 -0
  108. package/dist/docx/paraIdRangeNormalization.js +64 -0
  109. package/dist/docx/paragraphParser.js +101 -26
  110. package/dist/docx/paragraphTraversal.js +1 -1
  111. package/dist/docx/renderedPageBreakNormalization.js +3 -1
  112. package/dist/docx/revisionIdNormalization.d.ts +27 -0
  113. package/dist/docx/revisionIdNormalization.js +121 -0
  114. package/dist/docx/rezip.d.ts +6 -0
  115. package/dist/docx/rezip.js +51 -19
  116. package/dist/docx/selectiveSave.js +2 -2
  117. package/dist/docx/serializer/commentSerializer.js +6 -1
  118. package/dist/docx/serializer/paragraphSerializer.js +73 -20
  119. package/dist/docx/serializer/runSerializer.js +11 -10
  120. package/dist/docx/serializer/tableSerializer.js +26 -10
  121. package/dist/docx/server/applyDocxXmlPatchProposal.js +1 -1
  122. package/dist/docx/server/createBilingualDocx.js +3 -2
  123. package/dist/docx/streamingXmlParser.d.ts +13 -1
  124. package/dist/docx/streamingXmlParser.js +49 -11
  125. package/dist/docx/unzip.d.ts +1 -2
  126. package/dist/docx/unzip.js +25 -6
  127. package/dist/docx/xmlParser.d.ts +3 -1
  128. package/dist/docx/xmlParser.js +33 -27
  129. package/dist/export-pdf.d.ts +43 -0
  130. package/dist/export-pdf.js +98 -0
  131. package/dist/fonts/headlessMeasure.d.ts +76 -0
  132. package/dist/fonts/headlessMeasure.js +0 -0
  133. package/dist/fonts/sfnt/parse.d.ts +51 -0
  134. package/dist/fonts/sfnt/parse.js +525 -0
  135. package/dist/fonts/sfnt/subset.d.ts +20 -0
  136. package/dist/fonts/sfnt/subset.js +350 -0
  137. package/dist/fonts/sfnt/tables.d.ts +88 -0
  138. package/dist/fonts/sfnt/tables.js +131 -0
  139. package/dist/fonts/sfnt/woff.d.ts +14 -0
  140. package/dist/fonts/sfnt/woff.js +163 -0
  141. package/dist/generated/text_shaper.js +324 -0
  142. package/dist/generated/text_shaper_bg.wasm +0 -0
  143. package/dist/headless-layout.d.ts +75 -0
  144. package/dist/headless-layout.js +350 -0
  145. package/dist/index.d.ts +9 -6
  146. package/dist/index.js +6 -3
  147. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  148. package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
  149. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  150. package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
  151. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -1
  152. package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
  153. package/dist/layout-engine/measure/advanceComposition.d.ts +62 -0
  154. package/dist/layout-engine/measure/advanceComposition.js +148 -0
  155. package/dist/layout-engine/measure/cache.d.ts +2 -0
  156. package/dist/layout-engine/measure/measureContainer.js +13 -0
  157. package/dist/layout-engine/measure/measureParagraph.js +1 -2
  158. package/dist/layout-engine/measure/measureTypes.d.ts +12 -0
  159. package/dist/layout-engine/measure/tableInlinePlacement.d.ts +3 -2
  160. package/dist/layout-engine/measure/tableInlinePlacement.js +14 -1
  161. package/dist/layout-engine/tableIndentCompatibility.d.ts +5 -0
  162. package/dist/layout-engine/tableIndentCompatibility.js +15 -0
  163. package/dist/layout-engine/types.d.ts +8 -0
  164. package/dist/layout-painter/renderPage.d.ts +29 -1
  165. package/dist/layout-painter/renderPage.js +47 -19
  166. package/dist/layout-painter/renderParagraph.d.ts +21 -2
  167. package/dist/layout-painter/renderParagraph.js +1 -1
  168. package/dist/layout-painter/renderTable.js +37 -4
  169. package/dist/managers/editorShortcuts.d.ts +33 -1
  170. package/dist/managers/editorShortcuts.js +20 -1
  171. package/dist/markdown/renderRuns.js +7 -2
  172. package/dist/markdown/renderTable.js +5 -1
  173. package/dist/model.d.ts +3 -3
  174. package/dist/model.js +2 -2
  175. package/dist/pdf/contentStream.d.ts +73 -0
  176. package/dist/pdf/contentStream.js +143 -0
  177. package/dist/pdf/fonts.d.ts +96 -0
  178. package/dist/pdf/fonts.js +593 -0
  179. package/dist/pdf/images.d.ts +28 -0
  180. package/dist/pdf/images.js +439 -0
  181. package/dist/pdf/objects.d.ts +93 -0
  182. package/dist/pdf/objects.js +237 -0
  183. package/dist/pdf/pageSpace.d.ts +43 -0
  184. package/dist/pdf/pageSpace.js +57 -0
  185. package/dist/pdf/paint.d.ts +32 -0
  186. package/dist/pdf/paint.js +442 -0
  187. package/dist/pdf/writePdf.d.ts +56 -0
  188. package/dist/pdf/writePdf.js +345 -0
  189. package/dist/prosemirror/commands/comments.js +96 -6
  190. package/dist/prosemirror/conversion/fromProseDoc.js +41 -7
  191. package/dist/prosemirror/conversion/toProseDoc.js +41 -4
  192. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +15 -2
  193. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +35 -6
  194. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +2 -2
  195. package/dist/prosemirror/extensions/features/pasteCleanup.js +4 -3
  196. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +3 -1
  197. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +5 -3
  198. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -1
  199. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +9 -3
  200. package/dist/prosemirror/extensions/nodes/TableExtension.js +56 -39
  201. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +3 -1
  202. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +5 -3
  203. package/dist/prosemirror/plugins/suggestionMode.js +1 -0
  204. package/dist/prosemirror/revisionCarriers.js +19 -2
  205. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +2 -2
  206. package/dist/prosemirror/utils/extractTrackedChanges.js +59 -23
  207. package/dist/prosemirror/validation.js +55 -27
  208. package/dist/prosemirror/zeroWidthAnchors.d.ts +10 -0
  209. package/dist/prosemirror/zeroWidthAnchors.js +23 -0
  210. package/dist/redline.js +12 -79
  211. package/dist/server.d.ts +3 -3
  212. package/dist/server.js +2 -2
  213. package/dist/shaping/placeRun.d.ts +51 -0
  214. package/dist/shaping/placeRun.js +84 -0
  215. package/dist/shaping/shaper.d.ts +92 -0
  216. package/dist/shaping/shaper.js +102 -0
  217. package/dist/types/block-id.d.ts +18 -2
  218. package/dist/types/block-id.js +49 -5
  219. package/dist/types/content.d.ts +2 -2
  220. package/dist/utils/clipboard.js +4 -3
  221. package/dist/utils/fontResolver.js +183 -0
  222. package/dist/utils/htmlComments.d.ts +17 -0
  223. package/dist/utils/htmlComments.js +22 -0
  224. package/dist/utils/scriptSegments.d.ts +12 -1
  225. package/dist/utils/scriptSegments.js +17 -1
  226. package/dist/version-comparison.d.ts +1 -1
  227. package/package.json +7 -3
@@ -0,0 +1,136 @@
1
+ import { BLACK, WHITE } from "../primitives.js";
2
+ //#region src/display-list/build/colors.ts
3
+ /**
4
+ * CSS colour → {@link DisplayColor}.
5
+ *
6
+ * The display list's contract is that colours are already resolved: a backend
7
+ * never sees a custom property, a named OOXML highlight, or `currentColor`.
8
+ * Every such value is collapsed here, at the one place that still knows what
9
+ * the editor's stylesheet means.
10
+ */
11
+ /**
12
+ * The editor's document custom properties, materialised. A PDF has no theme
13
+ * and no stylesheet: `--doc-canvas` is the paper, `--doc-canvas-text` the ink,
14
+ * `--suggestion-color` the AI-proposal hue (`renderParagraph.ts`'s
15
+ * `SUGGESTION_COLOR_CSS` fallback).
16
+ */
17
+ const DOC_CANVAS = WHITE;
18
+ const DOC_CANVAS_TEXT = BLACK;
19
+ const SUGGESTION_COLOR = {
20
+ r: 109,
21
+ g: 59,
22
+ b: 214,
23
+ a: 1
24
+ };
25
+ const TRANSPARENT = {
26
+ r: 0,
27
+ g: 0,
28
+ b: 0,
29
+ a: 0
30
+ };
31
+ /** Word's automatic-colour keywords (`w:color w:val="auto"`). */
32
+ const AUTOMATIC_COLOR_VALUES = /* @__PURE__ */ new Set([
33
+ "auto",
34
+ "windowtext",
35
+ "currentcolor",
36
+ "inherit"
37
+ ]);
38
+ /**
39
+ * OOXML's named highlight palette (§17.18.40) plus the CSS basic colours the
40
+ * bridge can pass through. Kept small on purpose: the bridge resolves named
41
+ * highlights to hex before layout, so this is a safety net for the values that
42
+ * still arrive as names.
43
+ *
44
+ * A `Map`, not an object: the key is an authored string, and an object lookup
45
+ * would answer `constructor`, `toString` or `__proto__` from the prototype and
46
+ * hand a function to {@link parseHex}.
47
+ */
48
+ const NAMED_COLORS = /* @__PURE__ */ new Map([
49
+ ["black", "#000000"],
50
+ ["blue", "#0000ff"],
51
+ ["cyan", "#00ffff"],
52
+ ["darkblue", "#000080"],
53
+ ["darkcyan", "#008080"],
54
+ ["darkgray", "#808080"],
55
+ ["darkgrey", "#808080"],
56
+ ["darkgreen", "#008000"],
57
+ ["darkmagenta", "#800080"],
58
+ ["darkred", "#800000"],
59
+ ["darkyellow", "#808000"],
60
+ ["gray", "#808080"],
61
+ ["grey", "#808080"],
62
+ ["green", "#00ff00"],
63
+ ["lightgray", "#c0c0c0"],
64
+ ["lightgrey", "#c0c0c0"],
65
+ ["magenta", "#ff00ff"],
66
+ ["red", "#ff0000"],
67
+ ["silver", "#c0c0c0"],
68
+ ["white", "#ffffff"],
69
+ ["yellow", "#ffff00"]
70
+ ]);
71
+ const DOC_CUSTOM_PROPERTIES = {
72
+ "--doc-canvas": DOC_CANVAS,
73
+ "--doc-canvas-text": DOC_CANVAS_TEXT,
74
+ "--suggestion-color": SUGGESTION_COLOR
75
+ };
76
+ const HEX_PATTERN = /^#?(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/iu;
77
+ const RGB_PATTERN = /^rgba?\(\s*([\d.]+%?)\s*[, ]\s*([\d.]+%?)\s*[, ]\s*([\d.]+%?)\s*(?:[,/]\s*([\d.]+%?)\s*)?\)$/iu;
78
+ const VAR_PATTERN = /^var\(\s*(--[\w-]+)\s*(?:,\s*(.+))?\)$/iu;
79
+ const clampChannel = (value) => Math.min(255, Math.max(0, Math.round(value)));
80
+ const clampAlpha = (value) => Math.min(1, Math.max(0, value));
81
+ const parseHex = (raw) => {
82
+ const hex = raw.replace(/^#/u, "");
83
+ const expand = (part) => Number.parseInt(part.repeat(2), 16);
84
+ if (hex.length === 3 || hex.length === 4) return {
85
+ r: expand(hex[0]),
86
+ g: expand(hex[1]),
87
+ b: expand(hex[2]),
88
+ a: hex.length === 4 ? expand(hex[3]) / 255 : 1
89
+ };
90
+ if (hex.length === 6 || hex.length === 8) return {
91
+ r: Number.parseInt(hex.slice(0, 2), 16),
92
+ g: Number.parseInt(hex.slice(2, 4), 16),
93
+ b: Number.parseInt(hex.slice(4, 6), 16),
94
+ a: hex.length === 8 ? Number.parseInt(hex.slice(6, 8), 16) / 255 : 1
95
+ };
96
+ };
97
+ const parseComponent = (raw, scale) => raw.endsWith("%") ? Number.parseFloat(raw) / 100 * scale : Number.parseFloat(raw);
98
+ const parseRgb = (raw) => {
99
+ const match = RGB_PATTERN.exec(raw);
100
+ if (!match) return;
101
+ const r = clampChannel(parseComponent(match[1], 255));
102
+ const g = clampChannel(parseComponent(match[2], 255));
103
+ const b = clampChannel(parseComponent(match[3], 255));
104
+ const alphaRaw = match[4];
105
+ return {
106
+ r,
107
+ g,
108
+ b,
109
+ a: alphaRaw === void 0 ? 1 : clampAlpha(parseComponent(alphaRaw, 1))
110
+ };
111
+ };
112
+ /**
113
+ * Resolve an authored CSS colour. Returns `undefined` for a value this module
114
+ * cannot turn into concrete channels (`color-mix()`, `oklch()`, an unknown
115
+ * name) so the caller can report it rather than paint a guess.
116
+ */
117
+ const parseDisplayColor = (color) => {
118
+ if (color === void 0) return;
119
+ const trimmed = color.trim();
120
+ if (trimmed.length === 0) return;
121
+ const lower = trimmed.toLowerCase();
122
+ if (lower === "transparent" || lower === "none") return TRANSPARENT;
123
+ if (AUTOMATIC_COLOR_VALUES.has(lower)) return DOC_CANVAS_TEXT;
124
+ const varMatch = VAR_PATTERN.exec(trimmed);
125
+ if (varMatch) {
126
+ const known = DOC_CUSTOM_PROPERTIES[varMatch[1].toLowerCase()];
127
+ if (known) return known;
128
+ return parseDisplayColor(varMatch[2]);
129
+ }
130
+ const named = NAMED_COLORS.get(lower);
131
+ if (named !== void 0) return parseHex(named);
132
+ if (HEX_PATTERN.test(trimmed)) return parseHex(trimmed);
133
+ return parseRgb(lower);
134
+ };
135
+ //#endregion
136
+ export { DOC_CANVAS, DOC_CANVAS_TEXT, SUGGESTION_COLOR, TRANSPARENT, parseDisplayColor };
@@ -0,0 +1,24 @@
1
+ import { ImageRun, Page, ParagraphBlock } from "../../layout-engine/types.js";
2
+ import { PageGeometry } from "../../layout-painter/anchoredImagePosition.js";
3
+ import { DisplayPrimitive } from "../types.js";
4
+ import { BuildContext } from "./buildContext.js";
5
+ //#region src/display-list/build/floatingImages.d.ts
6
+ declare const pageGeometryOf: (page: Page) => PageGeometry;
7
+ type FloatingImagePlacement = {
8
+ readonly run: ImageRun;
9
+ /** `w:wrap behind` paints under the body text; everything else paints over it. */
10
+ readonly behindDoc: boolean;
11
+ readonly primitives: readonly DisplayPrimitive[];
12
+ };
13
+ type CollectFloatingImagesOptions = {
14
+ readonly block: ParagraphBlock;
15
+ /** The paragraph fragment's y, page-absolute. */
16
+ readonly fragmentYPx: number;
17
+ readonly geometry: PageGeometry;
18
+ readonly context: BuildContext;
19
+ /** Runs already placed on this page, so a split paragraph cannot double-paint. */
20
+ readonly placed: Set<ImageRun>;
21
+ };
22
+ declare const collectFloatingImages: ({ block, fragmentYPx, geometry, context, placed }: CollectFloatingImagesOptions) => readonly FloatingImagePlacement[];
23
+ //#endregion
24
+ export { CollectFloatingImagesOptions, FloatingImagePlacement, collectFloatingImages, pageGeometryOf };
@@ -0,0 +1,41 @@
1
+ import { isFloatingImageRun } from "../../layout-engine/types.js";
2
+ import { resolveAnchoredImagePosition } from "../../layout-painter/anchoredImagePosition.js";
3
+ import { paintImage } from "./imagePrimitives.js";
4
+ //#region src/display-list/build/floatingImages.ts
5
+ const pageGeometryOf = (page) => ({
6
+ pageWidth: page.size.w,
7
+ pageHeight: page.size.h,
8
+ marginLeft: page.margins.left,
9
+ marginTop: page.margins.top,
10
+ marginRight: page.margins.right,
11
+ marginBottom: page.margins.bottom,
12
+ ...page.authoredMargins === void 0 ? {} : { authoredMargins: page.authoredMargins },
13
+ contentWidth: page.size.w - page.margins.left - page.margins.right,
14
+ contentHeight: page.size.h - page.margins.top - page.margins.bottom
15
+ });
16
+ const collectFloatingImages = ({ block, fragmentYPx, geometry, context, placed }) => {
17
+ const placements = [];
18
+ for (const run of block.runs) {
19
+ if (run.kind !== "image" || !isFloatingImageRun(run) || placed.has(run)) continue;
20
+ placed.add(run);
21
+ const anchor = resolveAnchoredImagePosition(run, fragmentYPx - geometry.marginTop, geometry);
22
+ placements.push({
23
+ run,
24
+ behindDoc: run.wrapType === "behind",
25
+ primitives: paintImage({
26
+ source: run,
27
+ rect: {
28
+ xPx: geometry.marginLeft + anchor.x,
29
+ yPx: geometry.marginTop + anchor.y,
30
+ widthPx: run.width,
31
+ heightPx: run.height
32
+ },
33
+ context,
34
+ label: "floating image"
35
+ })
36
+ });
37
+ }
38
+ return placements;
39
+ };
40
+ //#endregion
41
+ export { collectFloatingImages, pageGeometryOf };
@@ -0,0 +1,31 @@
1
+ import { EmbeddedFont } from "../../fonts/embeddedFonts.js";
2
+ import { DisplayFontFace, DisplayFontRef } from "../types.js";
3
+ import { FontStyle } from "../../layout-engine/measure/measureTypes.js";
4
+ //#region src/display-list/build/fontTable.d.ts
5
+ type FontFaceRequest = {
6
+ readonly fontFamily: string;
7
+ readonly alternateFontFamily?: string;
8
+ readonly bold?: boolean;
9
+ readonly italic?: boolean;
10
+ /**
11
+ * The style the run measures with. The face's font-box ratios come from the
12
+ * measurer, not from a table of our own, so a backend places a baseline
13
+ * where the engine placed it.
14
+ */
15
+ readonly measureStyle: FontStyle;
16
+ };
17
+ declare class FontTable {
18
+ private readonly faces;
19
+ private readonly indexByKey;
20
+ private readonly embeddedByKey;
21
+ /**
22
+ * `embeddedFonts` are the package's own faces (`fonts/embeddedFonts.ts`).
23
+ * Without them a face the measurer used but no host can supply travels as a
24
+ * family name a backend has no way to resolve.
25
+ */
26
+ constructor(embeddedFonts?: readonly EmbeddedFont[]);
27
+ intern({ fontFamily, alternateFontFamily, bold, italic, measureStyle }: FontFaceRequest): DisplayFontRef;
28
+ snapshot(): readonly DisplayFontFace[];
29
+ }
30
+ //#endregion
31
+ export { FontFaceRequest, FontTable };
@@ -0,0 +1,97 @@
1
+ import { ptToPx } from "../../layout-engine/measure/measureHelpers.js";
2
+ import { getFontMetrics } from "../../layout-engine/measure/measureProvider.js";
3
+ import { resolveFontFamily } from "../../utils/fontResolver.js";
4
+ //#region src/display-list/build/fontTable.ts
5
+ const CSS_NORMAL_WEIGHT = 400;
6
+ const CSS_BOLD_WEIGHT = 700;
7
+ const GENERIC_FAMILIES = {
8
+ serif: "serif",
9
+ "sans-serif": "sans-serif",
10
+ monospace: "monospace",
11
+ cursive: "cursive",
12
+ fantasy: "fantasy"
13
+ };
14
+ const DEFAULT_GENERIC = "sans-serif";
15
+ const splitFallbackStack = (cssFallback) => cssFallback.split(",").map((entry) => entry.trim().replace(/^["']|["']$/gu, ""));
16
+ /**
17
+ * The category `resolveFontFamily` chose. It is the last CSS generic in the
18
+ * stack it built, which is where `detectFontCategory` puts it; reading it back
19
+ * is cheaper and less brittle than re-deriving the category from the name.
20
+ */
21
+ const genericOf = (stack) => {
22
+ for (let index = stack.length - 1; index >= 0; index -= 1) {
23
+ const generic = GENERIC_FAMILIES[stack[index].toLowerCase()];
24
+ if (generic !== void 0) return generic;
25
+ }
26
+ return DEFAULT_GENERIC;
27
+ };
28
+ const faceKey = (family, weight, italic) => `${family.toLowerCase()} ${weight} ${italic ? "i" : "r"}`;
29
+ /**
30
+ * Package faces keyed by every family name a run can resolve to.
31
+ *
32
+ * `resolveFontFamily` substitutes the scoped `folio-embedded-{nonce}-{name}`
33
+ * family only while the document's embedded-family map is installed; a build
34
+ * that never installed it resolves the same run to the authored name. Both
35
+ * names therefore key the same face, and the bytes are interned by id so one
36
+ * face's binary appears once however many names reach it.
37
+ */
38
+ const indexEmbeddedFonts = (faces) => {
39
+ const byId = /* @__PURE__ */ new Map();
40
+ const byKey = /* @__PURE__ */ new Map();
41
+ for (const face of faces) {
42
+ const id = `${face.family}:${face.weight}:${face.style}`;
43
+ const embedded = byId.get(id) ?? {
44
+ id,
45
+ bytes: face.bytes
46
+ };
47
+ byId.set(id, embedded);
48
+ const italic = face.style === "italic";
49
+ byKey.set(faceKey(face.family, face.weight, italic), embedded);
50
+ byKey.set(faceKey(face.originalFamily, face.weight, italic), embedded);
51
+ }
52
+ return byKey;
53
+ };
54
+ var FontTable = class {
55
+ faces = [];
56
+ indexByKey = /* @__PURE__ */ new Map();
57
+ embeddedByKey;
58
+ /**
59
+ * `embeddedFonts` are the package's own faces (`fonts/embeddedFonts.ts`).
60
+ * Without them a face the measurer used but no host can supply travels as a
61
+ * family name a backend has no way to resolve.
62
+ */
63
+ constructor(embeddedFonts = []) {
64
+ this.embeddedByKey = indexEmbeddedFonts(embeddedFonts);
65
+ }
66
+ intern({ fontFamily, alternateFontFamily, bold, italic, measureStyle }) {
67
+ const stack = splitFallbackStack(resolveFontFamily(fontFamily, alternateFontFamily).cssFallback);
68
+ const family = stack.at(0) ?? fontFamily;
69
+ const weight = bold ? CSS_BOLD_WEIGHT : CSS_NORMAL_WEIGHT;
70
+ const isItalic = italic === true;
71
+ const key = faceKey(family, weight, isItalic);
72
+ const existing = this.indexByKey.get(key);
73
+ if (existing !== void 0) return existing;
74
+ const metrics = getFontMetrics(measureStyle);
75
+ const fontSizePx = ptToPx(measureStyle.fontSize ?? metrics.fontSize);
76
+ const scale = fontSizePx > 0 ? 1 / fontSizePx : 0;
77
+ const index = this.faces.length;
78
+ const embedded = this.embeddedByKey.get(key);
79
+ this.faces.push({
80
+ family,
81
+ weight,
82
+ italic: isItalic,
83
+ generic: genericOf(stack),
84
+ fallbacks: stack.slice(1).filter((entry) => GENERIC_FAMILIES[entry.toLowerCase()] === void 0),
85
+ fontBoxAscentRatio: metrics.fontBoxAscent * scale,
86
+ fontBoxDescentRatio: metrics.fontBoxDescent * scale,
87
+ ...embedded === void 0 ? {} : { embedded }
88
+ });
89
+ this.indexByKey.set(key, index);
90
+ return index;
91
+ }
92
+ snapshot() {
93
+ return this.faces;
94
+ }
95
+ };
96
+ //#endregion
97
+ export { FontTable };
@@ -0,0 +1,86 @@
1
+ import { document_d_exports } from "../../types/document.js";
2
+ import { FootnoteContent, HeaderFooterContent, Page } from "../../layout-engine/types.js";
3
+ import { DisplayPrimitive } from "../types.js";
4
+ import { BuildContext } from "./buildContext.js";
5
+ import { PageBorders } from "./pageBorderPrimitives.js";
6
+ import { PageComposer } from "./regions.js";
7
+ //#region src/display-list/build/furniture.d.ts
8
+ /**
9
+ * Everything the builder cannot derive from a `Layout`. Every field is
10
+ * optional, and every one of them means the same thing when absent: the
11
+ * construct is not painted, and it is reported wherever the layout or the
12
+ * caller's `documentFeatures` shows the document had one.
13
+ */
14
+ type PageFurnitureInputs = {
15
+ /**
16
+ * `w:pgBorders` for the section. Absent: no border is painted, and the gap is
17
+ * reported unless `documentFeatures` says the document has none.
18
+ */
19
+ readonly pageBorders?: PageBorders;
20
+ /** Resolves `w:themeColor` on a page border. Absent: authored colours only. */
21
+ readonly theme?: document_d_exports.Theme | null;
22
+ /**
23
+ * The watermark for every page with no more specific match in
24
+ * {@link watermarkByHeaderRId}. Absent with that map also absent: no
25
+ * watermark is painted, and the gap is reported unless `documentFeatures`
26
+ * says the document has none.
27
+ */
28
+ readonly watermark?: document_d_exports.Watermark;
29
+ /**
30
+ * Per-header-rId watermarks, authoritative when present: a page whose active
31
+ * header carries none paints none, rather than inheriting {@link watermark}.
32
+ */
33
+ readonly watermarkByHeaderRId?: ReadonlyMap<string, document_d_exports.Watermark>;
34
+ /**
35
+ * Resolved `data:` source for a picture watermark. Absent: the picture is
36
+ * reported, because the relationship id → asset mapping belongs to the
37
+ * package layer and the builder does no I/O.
38
+ */
39
+ readonly watermarkImageSrc?: string;
40
+ /**
41
+ * The header for every page, or for pages 2+ when `titlePg` is set. Absent
42
+ * with the per-rId map also absent: no header is painted, and a page whose
43
+ * `headerFooterRefs` name one says so through `unsupported`.
44
+ */
45
+ readonly headerContent?: HeaderFooterContent;
46
+ /** The footer counterpart of {@link headerContent}. */
47
+ readonly footerContent?: HeaderFooterContent;
48
+ /** Headers by part relationship id, for section-scoped and even/odd headers. */
49
+ readonly headerContentByRId?: ReadonlyMap<string, HeaderFooterContent>;
50
+ /** Footers by part relationship id. */
51
+ readonly footerContentByRId?: ReadonlyMap<string, HeaderFooterContent>;
52
+ /** The first page's header when `titlePg` is set and the layout carries no section refs. */
53
+ readonly firstPageHeaderContent?: HeaderFooterContent;
54
+ /** The first page's footer when `titlePg` is set and the layout carries no section refs. */
55
+ readonly firstPageFooterContent?: HeaderFooterContent;
56
+ /** `w:titlePg` for the first section. Absent: the first page is not special-cased. */
57
+ readonly titlePg?: boolean;
58
+ /** Overrides `w:pgMar`'s `w:header`. Absent: the page's own margin is used. */
59
+ readonly headerDistancePx?: number;
60
+ /** Overrides `w:pgMar`'s `w:footer`. Absent: the page's own margin is used. */
61
+ readonly footerDistancePx?: number;
62
+ /**
63
+ * Footnote bodies by `w:footnote` id. Absent, or missing an id a page
64
+ * carries: the band still opens with its rule and reserves the paginator's
65
+ * height, and the missing bodies are reported.
66
+ */
67
+ readonly footnoteContentById?: ReadonlyMap<number, FootnoteContent>;
68
+ };
69
+ /**
70
+ * Furniture split by where it sits in the page's paint order: `behind` goes
71
+ * under the body, `above` over it. `renderPage.ts` establishes both.
72
+ */
73
+ type PageFurniturePrimitives = {
74
+ /** Painted under the body: a back page border, the watermark. */
75
+ readonly behind: readonly DisplayPrimitive[];
76
+ /** Painted over it, into the composer, so its regions nest. */
77
+ readonly above: (composer: PageComposer) => void;
78
+ };
79
+ type PaintPageFurnitureOptions = {
80
+ readonly page: Page;
81
+ readonly furniture: PageFurnitureInputs;
82
+ readonly context: BuildContext;
83
+ };
84
+ declare const paintPageFurniture: ({ page, furniture, context }: PaintPageFurnitureOptions) => PageFurniturePrimitives;
85
+ //#endregion
86
+ export { PageFurnitureInputs, PageFurniturePrimitives, PaintPageFurnitureOptions, paintPageFurniture };
@@ -0,0 +1,98 @@
1
+ import { applySectionHeaderFooterOptions, selectSectionHeaderFooterRIds } from "../../layout-painter/renderPage.js";
2
+ import { paintHeaderFooter } from "./headerFooterPrimitives.js";
3
+ import { paintPageBorders } from "./pageBorderPrimitives.js";
4
+ import { paintFootnoteArea } from "./pageFurniture.js";
5
+ import { UNSUPPORTED_CONSTRUCT } from "./unsupported.js";
6
+ import { paintWatermark } from "./watermarkPrimitives.js";
7
+ //#region src/display-list/build/furniture.ts
8
+ const suppliesHeaderFooter = (furniture) => furniture.headerContent !== void 0 || furniture.footerContent !== void 0 || furniture.headerContentByRId !== void 0 || furniture.footerContentByRId !== void 0 || furniture.firstPageHeaderContent !== void 0 || furniture.firstPageFooterContent !== void 0;
9
+ const suppliesWatermark = (furniture) => furniture.watermark !== void 0 || furniture.watermarkByHeaderRId !== void 0;
10
+ /**
11
+ * The header, footer and watermark this page paints, chosen by the painter's
12
+ * own rule (`w:titlePg` first-page swap, `w:evenAndOddHeaders`, section-scoped
13
+ * parts) rather than by a second implementation of it.
14
+ */
15
+ const selectForPage = (page, furniture) => {
16
+ const source = {
17
+ ...furniture.headerContent === void 0 ? {} : { headerContent: furniture.headerContent },
18
+ ...furniture.footerContent === void 0 ? {} : { footerContent: furniture.footerContent },
19
+ ...furniture.headerContentByRId === void 0 ? {} : { headerContentByRId: furniture.headerContentByRId },
20
+ ...furniture.footerContentByRId === void 0 ? {} : { footerContentByRId: furniture.footerContentByRId },
21
+ ...furniture.watermark === void 0 ? {} : { watermark: furniture.watermark },
22
+ ...furniture.watermarkByHeaderRId === void 0 ? {} : { watermarkByHeaderRId: furniture.watermarkByHeaderRId }
23
+ };
24
+ const selected = { ...source };
25
+ if (applySectionHeaderFooterOptions(page, selected, source)) return selected;
26
+ if (furniture.titlePg !== true || page.number !== 1) return selected;
27
+ if (furniture.firstPageHeaderContent === void 0) delete selected.headerContent;
28
+ else selected.headerContent = furniture.firstPageHeaderContent;
29
+ if (furniture.firstPageFooterContent === void 0) delete selected.footerContent;
30
+ else selected.footerContent = furniture.firstPageFooterContent;
31
+ return selected;
32
+ };
33
+ /**
34
+ * Name a header or footer the page asks for and the caller did not supply. A
35
+ * backend cannot tell a page with no header from a page whose header never
36
+ * reached the producer.
37
+ */
38
+ const reportMissingStories = (page, furniture, selected, context) => {
39
+ if (page.headerFooterRefs === void 0) return;
40
+ if (!suppliesHeaderFooter(furniture)) {
41
+ context.unsupported.report(UNSUPPORTED_CONSTRUCT.headerFooterContent, context.pageIndex, "the page names header and footer parts, but no story content was supplied to the builder");
42
+ return;
43
+ }
44
+ const { headerRId, footerRId } = selectSectionHeaderFooterRIds(page);
45
+ const namesHeader = headerRId !== void 0;
46
+ const namesFooter = footerRId !== void 0;
47
+ const headerCarriedAWatermark = headerRId !== void 0 && furniture.watermarkByHeaderRId?.has(headerRId) === true;
48
+ if (namesHeader && !headerCarriedAWatermark && selected.headerContent === void 0) context.unsupported.report(UNSUPPORTED_CONSTRUCT.headerFooterContent, context.pageIndex, "the header part this page selects was not among the supplied stories");
49
+ if (namesFooter && selected.footerContent === void 0) context.unsupported.report(UNSUPPORTED_CONSTRUCT.headerFooterContent, context.pageIndex, "the footer part this page selects was not among the supplied stories");
50
+ };
51
+ const paintPageFurniture = ({ page, furniture, context }) => {
52
+ const selected = selectForPage(page, furniture);
53
+ reportMissingStories(page, furniture, selected, context);
54
+ const borders = furniture.pageBorders === void 0 ? [] : paintPageBorders({
55
+ page,
56
+ borders: furniture.pageBorders,
57
+ ...furniture.theme === void 0 ? {} : { theme: furniture.theme },
58
+ context
59
+ });
60
+ const paintsBorderBehind = furniture.pageBorders?.zOrder === "back";
61
+ const watermark = !suppliesWatermark(furniture) || selected.watermark === void 0 ? [] : paintWatermark({
62
+ page,
63
+ watermark: selected.watermark,
64
+ ...furniture.watermarkImageSrc === void 0 ? {} : { imageSrc: furniture.watermarkImageSrc },
65
+ context
66
+ });
67
+ const above = (composer) => {
68
+ paintFootnoteArea({
69
+ composer,
70
+ page,
71
+ context,
72
+ ...furniture.footnoteContentById === void 0 ? {} : { contentById: furniture.footnoteContentById }
73
+ });
74
+ if (selected.headerContent !== void 0) paintHeaderFooter({
75
+ composer,
76
+ page,
77
+ section: "header",
78
+ content: selected.headerContent,
79
+ ...furniture.headerDistancePx === void 0 ? {} : { distancePx: furniture.headerDistancePx },
80
+ context
81
+ });
82
+ if (selected.footerContent !== void 0) paintHeaderFooter({
83
+ composer,
84
+ page,
85
+ section: "footer",
86
+ content: selected.footerContent,
87
+ ...furniture.footerDistancePx === void 0 ? {} : { distancePx: furniture.footerDistancePx },
88
+ context
89
+ });
90
+ if (!paintsBorderBehind) composer.push(borders);
91
+ };
92
+ return {
93
+ behind: [...paintsBorderBehind ? borders : [], ...watermark],
94
+ above
95
+ };
96
+ };
97
+ //#endregion
98
+ export { paintPageFurniture };
@@ -0,0 +1,55 @@
1
+ import { DisplayRunAdjustments } from "../types.js";
2
+ import { FontStyle } from "../../layout-engine/measure/measureTypes.js";
3
+ //#region src/display-list/build/glyphs.d.ts
4
+ type Glyphs = {
5
+ readonly text: string;
6
+ readonly advancesPx: number[];
7
+ readonly widthPx: number;
8
+ /** Whether these advances were measured with kerning on. */
9
+ readonly kerning: boolean;
10
+ /** Whether they were measured as small capitals. */
11
+ readonly smallCaps: boolean;
12
+ /**
13
+ * What went into the advances beyond the glyphs' own widths. Absent when
14
+ * nothing did, which keeps the common run's primitive as small as it was.
15
+ */
16
+ readonly adjustments?: DisplayRunAdjustments;
17
+ };
18
+ type BuildGlyphsOptions = {
19
+ readonly text: string;
20
+ readonly style: FontStyle;
21
+ readonly allCaps: boolean;
22
+ /** Justification delta applied to every compressible space. */
23
+ readonly spaceDeltaPx: number;
24
+ /**
25
+ * A line-edge space run Word keeps addressable but paints with no advance
26
+ * (the painter zeroes the split-off span's font size instead).
27
+ */
28
+ readonly collapsed: boolean;
29
+ };
30
+ /**
31
+ * `measureRun` returns `charWidths` indexed by UTF-16 unit with the trailing
32
+ * unit of a surrogate pair set to 0, because that is what ProseMirror offsets
33
+ * need. The display list wants code points, so the pair folds into one entry.
34
+ * An uppercase transform that expands a code point (ß → SS) keeps the source
35
+ * advance on the first output code point and gives the rest zero, so the sum
36
+ * still equals what the measurer decided the line on.
37
+ */
38
+ declare const buildGlyphs: (options: BuildGlyphsOptions) => Glyphs;
39
+ /**
40
+ * Everything a measured run contributes to a `glyphRun`.
41
+ *
42
+ * One helper rather than three fields at each emission site: what went into the
43
+ * advances has to travel with them, and a call site that carried the advances
44
+ * and forgot the adjustments would silently paint a run the line was not fitted
45
+ * with.
46
+ */
47
+ declare const glyphRunText: (glyphs: Glyphs) => {
48
+ adjustments?: DisplayRunAdjustments;
49
+ text: string;
50
+ advancesPx: number[];
51
+ kerning: boolean;
52
+ smallCaps: boolean;
53
+ };
54
+ //#endregion
55
+ export { BuildGlyphsOptions, Glyphs, buildGlyphs, glyphRunText };
@@ -0,0 +1,103 @@
1
+ import { measureRun, measureTextWidth } from "../../layout-engine/measure/measureProvider.js";
2
+ import { FONT_KERNING_MODE, getFontKerningMode } from "../../layout-engine/measure/textMeasurementPolicy.js";
3
+ import { getHorizontalScaleFactor } from "../../utils/horizontalScale.js";
4
+ //#region src/display-list/build/glyphs.ts
5
+ /**
6
+ * Text → one advance per code point, taken from the measure seam.
7
+ *
8
+ * Shared by everything that paints glyphs (body lines, list markers, tab
9
+ * leaders, watermark text) so no caller invents a width of its own: the numbers
10
+ * here are the numbers line breaking and pagination were decided on.
11
+ */
12
+ /**
13
+ * `measureRun` returns `charWidths` indexed by UTF-16 unit with the trailing
14
+ * unit of a surrogate pair set to 0, because that is what ProseMirror offsets
15
+ * need. The display list wants code points, so the pair folds into one entry.
16
+ * An uppercase transform that expands a code point (ß → SS) keeps the source
17
+ * advance on the first output code point and gives the rest zero, so the sum
18
+ * still equals what the measurer decided the line on.
19
+ */
20
+ const buildGlyphs = (options) => {
21
+ const { text, style, allCaps, spaceDeltaPx, collapsed } = options;
22
+ const adjustments = adjustmentsOf(options);
23
+ const { charWidths } = measureRun(text, style);
24
+ const advancesPx = [];
25
+ let painted = "";
26
+ let widthPx = 0;
27
+ let unitOffset = 0;
28
+ const shapedScale = shapedScaleOf(text, style, charWidths);
29
+ for (const char of text) {
30
+ const measured = (charWidths[unitOffset] ?? 0) * shapedScale;
31
+ unitOffset += char.length;
32
+ const advance = collapsed ? 0 : measured + (char === " " ? spaceDeltaPx : 0);
33
+ widthPx += advance;
34
+ let isFirst = true;
35
+ for (const outputChar of allCaps ? char.toLocaleUpperCase() : char) {
36
+ painted += outputChar;
37
+ advancesPx.push(isFirst ? advance : 0);
38
+ isFirst = false;
39
+ }
40
+ }
41
+ return {
42
+ text: painted,
43
+ advancesPx,
44
+ widthPx,
45
+ kerning: getFontKerningMode(style) === FONT_KERNING_MODE.enabled,
46
+ smallCaps: style.fontVariant === "small-caps",
47
+ ...adjustments === void 0 ? {} : { adjustments }
48
+ };
49
+ };
50
+ /**
51
+ * What every per-character width has to be multiplied by for the run to occupy
52
+ * the width the line was fitted at.
53
+ *
54
+ * The two numbers come from the same measurer and differ by what shaping does
55
+ * across a boundary: a pair that kerns or ligates is narrower in the string
56
+ * than the two characters are apart. Line breaking used the string's width, so
57
+ * that is the run's width, and advances that summed to anything else would
58
+ * declare an extent no backend paints.
59
+ *
60
+ * Spread over every character rather than dropped on the last, because the
61
+ * difference is not one boundary's: concentrating it would move the run's final
62
+ * glyph by the whole of it.
63
+ */
64
+ const shapedScaleOf = (text, style, charWidths) => {
65
+ const perCharacterPx = charWidths.reduce((total, width) => total + width, 0);
66
+ if (perCharacterPx <= 0) return 1;
67
+ return measureTextWidth(text, style) / perCharacterPx;
68
+ };
69
+ /**
70
+ * The three adjustments named, or nothing when none applies.
71
+ *
72
+ * A collapsed run carries no advances at all, so it has nothing to reapply
73
+ * either: naming a spacing on a run painted at zero width would have a backend
74
+ * widen what the line was fitted without.
75
+ */
76
+ const adjustmentsOf = ({ style, spaceDeltaPx, collapsed }) => {
77
+ const horizontalScale = getHorizontalScaleFactor(style.horizontalScale);
78
+ const letterSpacingPx = collapsed ? 0 : (style.letterSpacing ?? 0) * horizontalScale;
79
+ const wordSpacingPx = collapsed ? 0 : spaceDeltaPx;
80
+ if (letterSpacingPx === 0 && wordSpacingPx === 0 && horizontalScale === 1) return;
81
+ return {
82
+ letterSpacingPx,
83
+ horizontalScale,
84
+ wordSpacingPx
85
+ };
86
+ };
87
+ /**
88
+ * Everything a measured run contributes to a `glyphRun`.
89
+ *
90
+ * One helper rather than three fields at each emission site: what went into the
91
+ * advances has to travel with them, and a call site that carried the advances
92
+ * and forgot the adjustments would silently paint a run the line was not fitted
93
+ * with.
94
+ */
95
+ const glyphRunText = (glyphs) => ({
96
+ text: glyphs.text,
97
+ advancesPx: glyphs.advancesPx,
98
+ kerning: glyphs.kerning,
99
+ smallCaps: glyphs.smallCaps,
100
+ ...glyphs.adjustments === void 0 ? {} : { adjustments: glyphs.adjustments }
101
+ });
102
+ //#endregion
103
+ export { buildGlyphs, glyphRunText };