@stll/folio-core 0.32.2 → 0.33.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 (222) 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 +649 -85
  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 +33 -0
  24. package/dist/compare/__fixtures__/body-sequence.js +67 -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 +406 -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 +1059 -0
  37. package/dist/compare/reproducible-package.d.ts +17 -0
  38. package/dist/compare/reproducible-package.js +30 -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 +282 -0
  42. package/dist/compare/types.js +32 -0
  43. package/dist/compare/verification.d.ts +55 -0
  44. package/dist/compare/verification.js +146 -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/packageParts.d.ts +35 -0
  104. package/dist/docx/packageParts.js +120 -0
  105. package/dist/docx/paragraphParser.js +22 -22
  106. package/dist/docx/paragraphTraversal.js +1 -1
  107. package/dist/docx/renderedPageBreakNormalization.js +3 -1
  108. package/dist/docx/revisionIdNormalization.d.ts +13 -0
  109. package/dist/docx/revisionIdNormalization.js +103 -0
  110. package/dist/docx/rezip.js +26 -17
  111. package/dist/docx/selectiveSave.js +2 -2
  112. package/dist/docx/serializer/commentSerializer.js +6 -1
  113. package/dist/docx/serializer/paragraphSerializer.js +33 -4
  114. package/dist/docx/serializer/runSerializer.js +11 -10
  115. package/dist/docx/serializer/tableSerializer.js +2 -1
  116. package/dist/docx/server/applyDocxXmlPatchProposal.js +1 -1
  117. package/dist/docx/server/createBilingualDocx.js +3 -2
  118. package/dist/docx/streamingXmlParser.d.ts +13 -1
  119. package/dist/docx/streamingXmlParser.js +49 -11
  120. package/dist/docx/unzip.d.ts +1 -2
  121. package/dist/docx/unzip.js +25 -6
  122. package/dist/docx/xmlParser.d.ts +3 -1
  123. package/dist/docx/xmlParser.js +33 -27
  124. package/dist/export-pdf.d.ts +43 -0
  125. package/dist/export-pdf.js +98 -0
  126. package/dist/fonts/headlessMeasure.d.ts +76 -0
  127. package/dist/fonts/headlessMeasure.js +0 -0
  128. package/dist/fonts/sfnt/parse.d.ts +51 -0
  129. package/dist/fonts/sfnt/parse.js +525 -0
  130. package/dist/fonts/sfnt/subset.d.ts +20 -0
  131. package/dist/fonts/sfnt/subset.js +350 -0
  132. package/dist/fonts/sfnt/tables.d.ts +88 -0
  133. package/dist/fonts/sfnt/tables.js +131 -0
  134. package/dist/fonts/sfnt/woff.d.ts +14 -0
  135. package/dist/fonts/sfnt/woff.js +163 -0
  136. package/dist/generated/text_shaper.js +324 -0
  137. package/dist/generated/text_shaper_bg.wasm +0 -0
  138. package/dist/headless-layout.d.ts +75 -0
  139. package/dist/headless-layout.js +350 -0
  140. package/dist/index.d.ts +9 -6
  141. package/dist/index.js +6 -3
  142. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  143. package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
  144. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  145. package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
  146. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -1
  147. package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
  148. package/dist/layout-engine/measure/advanceComposition.d.ts +62 -0
  149. package/dist/layout-engine/measure/advanceComposition.js +148 -0
  150. package/dist/layout-engine/measure/cache.d.ts +2 -0
  151. package/dist/layout-engine/measure/measureContainer.js +13 -0
  152. package/dist/layout-engine/measure/measureParagraph.js +1 -2
  153. package/dist/layout-engine/measure/measureTypes.d.ts +12 -0
  154. package/dist/layout-engine/measure/tableInlinePlacement.d.ts +3 -2
  155. package/dist/layout-engine/measure/tableInlinePlacement.js +14 -1
  156. package/dist/layout-engine/tableIndentCompatibility.d.ts +5 -0
  157. package/dist/layout-engine/tableIndentCompatibility.js +15 -0
  158. package/dist/layout-engine/types.d.ts +8 -0
  159. package/dist/layout-painter/renderPage.d.ts +29 -1
  160. package/dist/layout-painter/renderPage.js +47 -19
  161. package/dist/layout-painter/renderParagraph.d.ts +21 -2
  162. package/dist/layout-painter/renderParagraph.js +1 -1
  163. package/dist/layout-painter/renderTable.js +37 -4
  164. package/dist/managers/editorShortcuts.d.ts +33 -1
  165. package/dist/managers/editorShortcuts.js +20 -1
  166. package/dist/markdown/renderRuns.js +7 -2
  167. package/dist/markdown/renderTable.js +5 -1
  168. package/dist/model.d.ts +3 -3
  169. package/dist/model.js +2 -2
  170. package/dist/pdf/contentStream.d.ts +73 -0
  171. package/dist/pdf/contentStream.js +143 -0
  172. package/dist/pdf/fonts.d.ts +96 -0
  173. package/dist/pdf/fonts.js +593 -0
  174. package/dist/pdf/images.d.ts +28 -0
  175. package/dist/pdf/images.js +439 -0
  176. package/dist/pdf/objects.d.ts +93 -0
  177. package/dist/pdf/objects.js +237 -0
  178. package/dist/pdf/pageSpace.d.ts +43 -0
  179. package/dist/pdf/pageSpace.js +57 -0
  180. package/dist/pdf/paint.d.ts +32 -0
  181. package/dist/pdf/paint.js +442 -0
  182. package/dist/pdf/writePdf.d.ts +56 -0
  183. package/dist/pdf/writePdf.js +345 -0
  184. package/dist/prosemirror/commands/comments.js +91 -6
  185. package/dist/prosemirror/conversion/fromProseDoc.js +41 -7
  186. package/dist/prosemirror/conversion/toProseDoc.js +41 -4
  187. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +15 -2
  188. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +35 -6
  189. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +2 -2
  190. package/dist/prosemirror/extensions/features/pasteCleanup.js +4 -3
  191. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +3 -1
  192. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +5 -3
  193. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -1
  194. package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +9 -3
  195. package/dist/prosemirror/extensions/nodes/TableExtension.js +56 -39
  196. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +3 -1
  197. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +5 -3
  198. package/dist/prosemirror/plugins/suggestionMode.js +1 -0
  199. package/dist/prosemirror/revisionCarriers.js +19 -2
  200. package/dist/prosemirror/utils/extractTrackedChanges.d.ts +2 -2
  201. package/dist/prosemirror/utils/extractTrackedChanges.js +59 -23
  202. package/dist/prosemirror/validation.js +55 -27
  203. package/dist/prosemirror/zeroWidthAnchors.d.ts +10 -0
  204. package/dist/prosemirror/zeroWidthAnchors.js +23 -0
  205. package/dist/redline.js +12 -79
  206. package/dist/server.d.ts +3 -3
  207. package/dist/server.js +2 -2
  208. package/dist/shaping/placeRun.d.ts +51 -0
  209. package/dist/shaping/placeRun.js +84 -0
  210. package/dist/shaping/shaper.d.ts +92 -0
  211. package/dist/shaping/shaper.js +102 -0
  212. package/dist/types/block-id.d.ts +18 -2
  213. package/dist/types/block-id.js +49 -5
  214. package/dist/types/content.d.ts +2 -2
  215. package/dist/utils/clipboard.js +4 -3
  216. package/dist/utils/fontResolver.js +183 -0
  217. package/dist/utils/htmlComments.d.ts +17 -0
  218. package/dist/utils/htmlComments.js +22 -0
  219. package/dist/utils/scriptSegments.d.ts +12 -1
  220. package/dist/utils/scriptSegments.js +17 -1
  221. package/dist/version-comparison.d.ts +1 -1
  222. package/package.json +7 -3
@@ -0,0 +1,264 @@
1
+ import { parseRotationDegrees } from "../../utils/rotationBoundingBox.js";
2
+ import { sanitizeImageSrc } from "../../utils/sanitizeImageSrc.js";
3
+ import { resolveBorderStroke } from "./strokes.js";
4
+ import { UNSUPPORTED_CONSTRUCT } from "./unsupported.js";
5
+ import { Result } from "better-result";
6
+ //#region src/display-list/build/imagePrimitives.ts
7
+ /**
8
+ * Images: the source table, and the primitives an image draws.
9
+ *
10
+ * The display list carries raw bytes plus the format they are already in, so a
11
+ * backend never owns a codec. This module therefore decodes only far enough to
12
+ * fill `pixelWidth`/`pixelHeight` and to know PNG from JPEG. It transcodes
13
+ * nothing: a WMF, EMF, TIFF, SVG, GIF or BMP source is reported through
14
+ * `unsupported` and paints nothing, because a silently missing picture is worse
15
+ * than a named one.
16
+ */
17
+ const DATA_URL_PATTERN = /^data:([^;,]*)(;base64)?,(.*)$/su;
18
+ /**
19
+ * `undefined` for a payload `atob` rejects: a malformed `data:` URL is a
20
+ * document the builder reports, not an exception that aborts the build.
21
+ */
22
+ const decodeBase64 = (encoded) => {
23
+ const decoded = Result.try(() => atob(encoded));
24
+ if (decoded.isErr()) return;
25
+ const binary = decoded.value;
26
+ const bytes = new Uint8Array(binary.length);
27
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
28
+ return bytes;
29
+ };
30
+ const readUint32BE = (bytes, offset) => bytes[offset] << 24 | bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
31
+ const readUint16BE = (bytes, offset) => bytes[offset] << 8 | bytes[offset + 1];
32
+ const startsWith = (bytes, signature) => bytes.length >= signature.length && signature.every((byte, index) => bytes[index] === byte);
33
+ const PNG_SIGNATURE = [
34
+ 137,
35
+ 80,
36
+ 78,
37
+ 71,
38
+ 13,
39
+ 10,
40
+ 26,
41
+ 10
42
+ ];
43
+ const JPEG_SIGNATURE = [
44
+ 255,
45
+ 216,
46
+ 255
47
+ ];
48
+ /** PNG's IHDR is always the first chunk: 8-byte signature, 8-byte header, then w/h. */
49
+ const PNG_WIDTH_OFFSET = 16;
50
+ const decodePng = (bytes) => {
51
+ if (bytes.length < 24) return { reason: "PNG shorter than its IHDR chunk" };
52
+ return {
53
+ format: "png",
54
+ bytes,
55
+ pixelWidth: readUint32BE(bytes, PNG_WIDTH_OFFSET),
56
+ pixelHeight: readUint32BE(bytes, 20)
57
+ };
58
+ };
59
+ /**
60
+ * Frame markers that carry the image dimensions. Every other `SOFn` code in the
61
+ * `0xC0..0xCF` block is a table or restart marker, not a frame header.
62
+ */
63
+ const JPEG_FRAME_MARKERS = /* @__PURE__ */ new Set([
64
+ 192,
65
+ 193,
66
+ 194,
67
+ 195,
68
+ 197,
69
+ 198,
70
+ 199,
71
+ 201,
72
+ 202,
73
+ 203,
74
+ 205,
75
+ 206,
76
+ 207
77
+ ]);
78
+ const decodeJpeg = (bytes) => {
79
+ let offset = 2;
80
+ while (offset + 9 < bytes.length) {
81
+ if (bytes[offset] !== 255) {
82
+ offset += 1;
83
+ continue;
84
+ }
85
+ const marker = bytes[offset + 1];
86
+ if (JPEG_FRAME_MARKERS.has(marker)) return {
87
+ format: "jpeg",
88
+ bytes,
89
+ pixelHeight: readUint16BE(bytes, offset + 5),
90
+ pixelWidth: readUint16BE(bytes, offset + 7)
91
+ };
92
+ const segmentLength = readUint16BE(bytes, offset + 2);
93
+ if (segmentLength < 2) return { reason: "JPEG segment length is degenerate" };
94
+ offset += 2 + segmentLength;
95
+ }
96
+ return { reason: "JPEG carries no frame header" };
97
+ };
98
+ const sniffOtherFormat = (bytes) => {
99
+ if (startsWith(bytes, [
100
+ 71,
101
+ 73,
102
+ 70,
103
+ 56
104
+ ])) return "GIF";
105
+ if (startsWith(bytes, [66, 77])) return "BMP";
106
+ if (startsWith(bytes, [
107
+ 73,
108
+ 73,
109
+ 42,
110
+ 0
111
+ ]) || startsWith(bytes, [
112
+ 77,
113
+ 77,
114
+ 0,
115
+ 42
116
+ ])) return "TIFF";
117
+ if (startsWith(bytes, [
118
+ 215,
119
+ 205,
120
+ 198,
121
+ 154
122
+ ]) || startsWith(bytes, [
123
+ 1,
124
+ 0,
125
+ 0,
126
+ 0
127
+ ])) return "WMF/EMF";
128
+ if (startsWith(bytes, [
129
+ 60,
130
+ 63,
131
+ 120,
132
+ 109
133
+ ]) || startsWith(bytes, [
134
+ 60,
135
+ 115,
136
+ 118,
137
+ 103
138
+ ])) return "SVG";
139
+ };
140
+ const decodeImageBytes = (bytes) => {
141
+ if (startsWith(bytes, PNG_SIGNATURE)) return decodePng(bytes);
142
+ if (startsWith(bytes, JPEG_SIGNATURE)) return decodeJpeg(bytes);
143
+ const other = sniffOtherFormat(bytes);
144
+ return { reason: other === void 0 ? "unrecognised image bytes" : `${other} is not PNG or JPEG` };
145
+ };
146
+ /**
147
+ * Sources interned in first-use order, so two builds of one layout produce the
148
+ * same `DisplayImageRef` for the same picture.
149
+ */
150
+ var ImageTable = class {
151
+ sources = [];
152
+ refBySrc = /* @__PURE__ */ new Map();
153
+ failureBySrc = /* @__PURE__ */ new Map();
154
+ /** `undefined` means "do not paint"; the reason is available via {@link failureFor}. */
155
+ intern(src) {
156
+ const cached = this.refBySrc.get(src);
157
+ if (cached !== void 0) return cached ?? void 0;
158
+ const fail = (reason) => {
159
+ this.refBySrc.set(src, null);
160
+ this.failureBySrc.set(src, reason);
161
+ };
162
+ const safeSrc = sanitizeImageSrc(src);
163
+ if (safeSrc === void 0) return fail("image source is not a data: or blob: URL");
164
+ const match = DATA_URL_PATTERN.exec(safeSrc);
165
+ if (!match) return fail("image bytes are not inline (blob: URLs need a fetch the builder cannot do)");
166
+ if (match[2] === void 0) return fail("only base64 data: URLs carry decodable bytes");
167
+ const bytes = decodeBase64(match[3]);
168
+ if (bytes === void 0) return fail("image data: URL payload is not valid base64");
169
+ if (bytes.length === 0) return fail("image data: URL decoded to no bytes");
170
+ const decoded = decodeImageBytes(bytes);
171
+ if ("reason" in decoded) return fail(decoded.reason);
172
+ const ref = this.sources.length;
173
+ this.sources.push(decoded);
174
+ this.refBySrc.set(src, ref);
175
+ return ref;
176
+ }
177
+ failureFor(src) {
178
+ return this.failureBySrc.get(src);
179
+ }
180
+ snapshot() {
181
+ return this.sources;
182
+ }
183
+ };
184
+ const cropOf = (source) => {
185
+ const l = source.cropLeft ?? 0;
186
+ const t = source.cropTop ?? 0;
187
+ const r = source.cropRight ?? 0;
188
+ const b = source.cropBottom ?? 0;
189
+ if (!(l || t || r || b)) return;
190
+ return {
191
+ l,
192
+ t,
193
+ r,
194
+ b
195
+ };
196
+ };
197
+ /**
198
+ * The primitives one picture draws: the bitmap (rotated and/or washed as the
199
+ * source asks) followed by its border. Empty when the bytes are unusable, with
200
+ * the reason already reported.
201
+ */
202
+ const paintImage = ({ source, rect, context, label }) => {
203
+ const primitives = [];
204
+ const ref = context.images.intern(source.src);
205
+ if (ref === void 0) {
206
+ const reason = context.images.failureFor(source.src) ?? "image could not be decoded";
207
+ const construct = reason.includes("not PNG or JPEG") ? UNSUPPORTED_CONSTRUCT.imageFormat : UNSUPPORTED_CONSTRUCT.imageSource;
208
+ context.unsupported.report(construct, context.pageIndex, `${label}: ${reason}`);
209
+ } else {
210
+ const crop = cropOf(source);
211
+ const image = {
212
+ kind: "image",
213
+ image: ref,
214
+ rect,
215
+ ...crop === void 0 ? {} : { crop },
216
+ opacity: source.opacity == null ? 1 : Math.min(1, Math.max(0, source.opacity))
217
+ };
218
+ const degrees = parseRotationDegrees(source.transform);
219
+ if (degrees === 0) primitives.push(image);
220
+ else primitives.push({
221
+ kind: "rotateGroup",
222
+ degrees,
223
+ originXPx: rect.xPx + rect.widthPx / 2,
224
+ originYPx: rect.yPx + rect.heightPx / 2,
225
+ children: [image]
226
+ });
227
+ }
228
+ if (source.borderWidth != null && source.borderWidth > 0) {
229
+ const { stroke, unresolvedColor } = resolveBorderStroke({
230
+ width: source.borderWidth,
231
+ style: source.borderStyle ?? "solid",
232
+ color: source.borderColor ?? "#000000"
233
+ });
234
+ if (unresolvedColor !== void 0) context.unsupported.report(UNSUPPORTED_CONSTRUCT.unresolvedColor, context.pageIndex, `${label} border colour ${unresolvedColor}`);
235
+ if (stroke) {
236
+ const inset = stroke.thicknessPx / 2;
237
+ primitives.push({
238
+ kind: "rect",
239
+ rect: {
240
+ xPx: rect.xPx + inset,
241
+ yPx: rect.yPx + inset,
242
+ widthPx: Math.max(0, rect.widthPx - stroke.thicknessPx),
243
+ heightPx: Math.max(0, rect.heightPx - stroke.thicknessPx)
244
+ },
245
+ stroke
246
+ });
247
+ }
248
+ }
249
+ return primitives;
250
+ };
251
+ /** An anchored or block image fragment placed by the layout engine. */
252
+ const paintImageFragment = (fragment, block, context) => paintImage({
253
+ source: block,
254
+ rect: {
255
+ xPx: fragment.x,
256
+ yPx: fragment.y,
257
+ widthPx: fragment.width,
258
+ heightPx: fragment.height
259
+ },
260
+ context,
261
+ label: `image block ${String(fragment.blockId)}`
262
+ });
263
+ //#endregion
264
+ export { ImageTable, paintImage, paintImageFragment };
@@ -0,0 +1,23 @@
1
+ import { document_d_exports } from "../../types/document.js";
2
+ import { Page } from "../../layout-engine/types.js";
3
+ import { RenderPageOptions } from "../../layout-painter/renderPage.js";
4
+ import { DisplayPrimitive } from "../types.js";
5
+ import { BuildContext } from "./buildContext.js";
6
+ //#region src/display-list/build/pageBorderPrimitives.d.ts
7
+ /** The section's `w:pgBorders`, as the painter already receives them. */
8
+ type PageBorders = NonNullable<RenderPageOptions["pageBorders"]>;
9
+ type PageBorderPaintOptions = {
10
+ readonly page: Page;
11
+ readonly borders: PageBorders;
12
+ readonly theme?: document_d_exports.Theme | null;
13
+ readonly context: BuildContext;
14
+ };
15
+ /**
16
+ * The four page-border strokes, or nothing when this page does not carry them.
17
+ * `offsetFrom` decides what the box is measured from: `page` insets `w:space`
18
+ * from the paper edge, `text` insets the margin less `w:space` and the stroke
19
+ * itself, so the authored gap between text and border survives a thick rule.
20
+ */
21
+ declare const paintPageBorders: ({ page, borders, theme, context }: PageBorderPaintOptions) => readonly DisplayPrimitive[];
22
+ //#endregion
23
+ export { PageBorderPaintOptions, PageBorders, paintPageBorders };
@@ -0,0 +1,111 @@
1
+ import { resolveColor } from "../../utils/colorResolver.js";
2
+ import { eighthsToPixels, pointsToPixels } from "../../utils/units.js";
3
+ import { resolveBorderStroke } from "./strokes.js";
4
+ import { UNSUPPORTED_CONSTRUCT } from "./unsupported.js";
5
+ //#region src/display-list/build/pageBorderPrimitives.ts
6
+ /** `borderToStyle`: a border with no `w:sz` paints a hairline, not nothing. */
7
+ const HAIRLINE_WIDTH_PX = 1;
8
+ const SIDES = [
9
+ "top",
10
+ "right",
11
+ "bottom",
12
+ "left"
13
+ ];
14
+ /** `pageBorderShouldRender`: `w:display` is about the physical page number. */
15
+ const shouldRender = (pageNumber, display) => {
16
+ switch (display ?? "allPages") {
17
+ case "firstPage": return pageNumber === 1;
18
+ case "notFirstPage": return pageNumber !== 1;
19
+ case "allPages": return true;
20
+ default: return true;
21
+ }
22
+ };
23
+ const spacePx = (border) => border?.space === void 0 ? 0 : pointsToPixels(border.space);
24
+ const strokeOf = (border, theme, context) => {
25
+ if (!border) return;
26
+ const { stroke, unresolvedColor } = resolveBorderStroke({
27
+ width: border.size === void 0 || border.size === 0 ? HAIRLINE_WIDTH_PX : eighthsToPixels(border.size),
28
+ style: border.style,
29
+ color: resolveColor(border.color, theme)
30
+ });
31
+ if (unresolvedColor !== void 0) context.unsupported.report(UNSUPPORTED_CONSTRUCT.unresolvedColor, context.pageIndex, `page border colour ${unresolvedColor}`);
32
+ return stroke;
33
+ };
34
+ /**
35
+ * The four page-border strokes, or nothing when this page does not carry them.
36
+ * `offsetFrom` decides what the box is measured from: `page` insets `w:space`
37
+ * from the paper edge, `text` insets the margin less `w:space` and the stroke
38
+ * itself, so the authored gap between text and border survives a thick rule.
39
+ */
40
+ const paintPageBorders = ({ page, borders, theme, context }) => {
41
+ if (!shouldRender(page.number, borders.display)) return [];
42
+ const strokes = {
43
+ top: strokeOf(borders.top, theme, context),
44
+ right: strokeOf(borders.right, theme, context),
45
+ bottom: strokeOf(borders.bottom, theme, context),
46
+ left: strokeOf(borders.left, theme, context)
47
+ };
48
+ if (SIDES.every((side) => strokes[side] === void 0)) return [];
49
+ const insetPx = (side) => {
50
+ const border = borders[side];
51
+ if ((borders.offsetFrom ?? "text") === "page") return spacePx(border);
52
+ const marginPx = page.margins[side];
53
+ return Math.max(0, marginPx - spacePx(border) - (strokes[side]?.thicknessPx ?? 0));
54
+ };
55
+ const leftPx = insetPx("left");
56
+ const topPx = insetPx("top");
57
+ const rightPx = page.size.w - insetPx("right");
58
+ const bottomPx = page.size.h - insetPx("bottom");
59
+ const primitives = [];
60
+ for (const side of SIDES) {
61
+ const stroke = strokes[side];
62
+ if (stroke === void 0) continue;
63
+ const half = stroke.thicknessPx / 2;
64
+ switch (side) {
65
+ case "top":
66
+ primitives.push({
67
+ kind: "line",
68
+ x1Px: leftPx,
69
+ y1Px: topPx + half,
70
+ x2Px: rightPx,
71
+ y2Px: topPx + half,
72
+ stroke
73
+ });
74
+ break;
75
+ case "bottom":
76
+ primitives.push({
77
+ kind: "line",
78
+ x1Px: leftPx,
79
+ y1Px: bottomPx - half,
80
+ x2Px: rightPx,
81
+ y2Px: bottomPx - half,
82
+ stroke
83
+ });
84
+ break;
85
+ case "left":
86
+ primitives.push({
87
+ kind: "line",
88
+ x1Px: leftPx + half,
89
+ y1Px: topPx,
90
+ x2Px: leftPx + half,
91
+ y2Px: bottomPx,
92
+ stroke
93
+ });
94
+ break;
95
+ case "right":
96
+ primitives.push({
97
+ kind: "line",
98
+ x1Px: rightPx - half,
99
+ y1Px: topPx,
100
+ x2Px: rightPx - half,
101
+ y2Px: bottomPx,
102
+ stroke
103
+ });
104
+ break;
105
+ default:
106
+ }
107
+ }
108
+ return primitives;
109
+ };
110
+ //#endregion
111
+ export { paintPageBorders };
@@ -0,0 +1,35 @@
1
+ import { FootnoteContent, Page } from "../../layout-engine/types.js";
2
+ import { DisplayColor, DisplayPrimitive } from "../types.js";
3
+ import { BuildContext } from "./buildContext.js";
4
+ import { PageComposer } from "./regions.js";
5
+ //#region src/display-list/build/pageFurniture.d.ts
6
+ declare const paintPageBackground: (page: Page, fill: DisplayColor) => DisplayPrimitive;
7
+ /**
8
+ * Vertical rules between newspaper columns. The painter assumes equal columns
9
+ * with a uniform gap (it ignores `columns.widths` / `columns.gaps`), and so
10
+ * does this: diverging here would put the export's rules somewhere the editor
11
+ * never drew them.
12
+ */
13
+ declare const paintColumnSeparators: (page: Page) => readonly DisplayPrimitive[];
14
+ type FootnoteAreaPaintOptions = {
15
+ readonly composer: PageComposer;
16
+ readonly page: Page;
17
+ readonly context: BuildContext;
18
+ /**
19
+ * The bodies, by `w:footnote` id. Absent, or missing an id the page carries,
20
+ * means the caller did not supply that body: the band still opens with its
21
+ * rule and reserves its height, and the missing bodies are named.
22
+ */
23
+ readonly contentById?: ReadonlyMap<number, FootnoteContent>;
24
+ };
25
+ /**
26
+ * The footnote band at the foot of the body column: the separator rule, then
27
+ * one body per note the paginator put on this page.
28
+ *
29
+ * The band's top comes from the reservation the paginator made, clamped up to
30
+ * what the bodies actually need, so a stack that under-reserved by a pixel ends
31
+ * at the page bottom instead of spilling past it.
32
+ */
33
+ declare const paintFootnoteArea: ({ composer, page, context, contentById }: FootnoteAreaPaintOptions) => void;
34
+ //#endregion
35
+ export { FootnoteAreaPaintOptions, paintColumnSeparators, paintFootnoteArea, paintPageBackground };
@@ -0,0 +1,141 @@
1
+ import "../../layout-engine/types.js";
2
+ import { calculateFootnoteAreaRenderHeight } from "../../layout-painter/renderPage.js";
3
+ import { HIT_REGION_KINDS } from "../primitives.js";
4
+ import { DOC_CANVAS_TEXT } from "./colors.js";
5
+ import { paintFootnoteBlocks } from "./storyPrimitives.js";
6
+ import { UNSUPPORTED_CONSTRUCT } from "./unsupported.js";
7
+ //#region src/display-list/build/pageFurniture.ts
8
+ /**
9
+ * Page furniture: everything on a page that is not a body fragment.
10
+ *
11
+ * `renderPage.ts` is the authority on the order and on the geometry, and it
12
+ * takes most of this from `RenderPageOptions` rather than from the `Layout`:
13
+ * page borders, watermarks, footnote bodies and header/footer stories all reach
14
+ * the DOM painter as render options the layout never carried. The builder is
15
+ * handed the same inputs, so a construct the caller supplies is painted and a
16
+ * construct the layout shows evidence of but the caller withheld is reported.
17
+ * The furniture that is derivable from the `Layout` alone — the page
18
+ * background, the column separators and the footnote separator rule — is always
19
+ * painted.
20
+ */
21
+ /** `renderPage.ts:2049`: a hairline in the canvas ink colour. */
22
+ const COLUMN_SEPARATOR_WIDTH_PX = .5;
23
+ /** `renderPage.ts:1354-1360`: a 0.5px rule across a third of the column, centred in its 12px slot. */
24
+ const FOOTNOTE_RULE_THICKNESS_PX = .5;
25
+ const FOOTNOTE_RULE_WIDTH_FRACTION = .33;
26
+ const FOOTNOTE_RULE_MARGIN_PX = (12 - FOOTNOTE_RULE_THICKNESS_PX) / 2;
27
+ const paintPageBackground = (page, fill) => ({
28
+ kind: "rect",
29
+ rect: {
30
+ xPx: 0,
31
+ yPx: 0,
32
+ widthPx: page.size.w,
33
+ heightPx: page.size.h
34
+ },
35
+ fill
36
+ });
37
+ /**
38
+ * Vertical rules between newspaper columns. The painter assumes equal columns
39
+ * with a uniform gap (it ignores `columns.widths` / `columns.gaps`), and so
40
+ * does this: diverging here would put the export's rules somewhere the editor
41
+ * never drew them.
42
+ */
43
+ const paintColumnSeparators = (page) => {
44
+ const columns = page.columns;
45
+ if (!columns?.separator || columns.count <= 1) return [];
46
+ const contentWidthPx = page.size.w - page.margins.left - page.margins.right;
47
+ const contentHeightPx = page.size.h - page.margins.top - page.margins.bottom;
48
+ const columnWidthPx = (contentWidthPx - (columns.count - 1) * columns.gap) / columns.count;
49
+ const primitives = [];
50
+ for (let column = 0; column < columns.count - 1; column += 1) {
51
+ const offsetPx = (column + 1) * columnWidthPx + column * columns.gap + columns.gap / 2;
52
+ primitives.push({
53
+ kind: "rect",
54
+ rect: {
55
+ xPx: page.margins.left + offsetPx,
56
+ yPx: page.margins.top,
57
+ widthPx: COLUMN_SEPARATOR_WIDTH_PX,
58
+ heightPx: contentHeightPx
59
+ },
60
+ fill: DOC_CANVAS_TEXT
61
+ });
62
+ }
63
+ return primitives;
64
+ };
65
+ /**
66
+ * The footnote band at the foot of the body column: the separator rule, then
67
+ * one body per note the paginator put on this page.
68
+ *
69
+ * The band's top comes from the reservation the paginator made, clamped up to
70
+ * what the bodies actually need, so a stack that under-reserved by a pixel ends
71
+ * at the page bottom instead of spilling past it.
72
+ */
73
+ const paintFootnoteArea = ({ composer, page, context, contentById }) => {
74
+ const reservedHeightPx = page.footnoteReservedHeight ?? 0;
75
+ const noteIds = page.footnoteIds ?? [];
76
+ if (reservedHeightPx <= 0 || noteIds.length === 0) return;
77
+ const bodies = noteIds.map((noteId) => ({
78
+ noteId,
79
+ content: contentById?.get(noteId)
80
+ }));
81
+ const missing = bodies.flatMap(({ noteId, content }) => content === void 0 ? [noteId] : []);
82
+ if (missing.length > 0) context.unsupported.report(UNSUPPORTED_CONSTRUCT.footnoteContent, context.pageIndex, `footnote bodies ${missing.join(", ")} were not supplied to the builder, so the band reserves their height and paints nothing`);
83
+ const items = bodies.map(({ noteId, content }) => ({
84
+ noteId,
85
+ displayNumber: String(content?.displayNumber ?? noteId),
86
+ ...content === void 0 ? {} : { content: {
87
+ blocks: content.blocks,
88
+ measures: content.measures,
89
+ height: content.height
90
+ } }
91
+ }));
92
+ const contentHeightPx = page.size.h - page.margins.top - page.margins.bottom;
93
+ const contentWidthPx = page.size.w - page.margins.left - page.margins.right;
94
+ const bandHeightPx = Math.max(reservedHeightPx, calculateFootnoteAreaRenderHeight(items));
95
+ const areaTopPx = page.margins.top + Math.max(-page.margins.top, contentHeightPx - bandHeightPx);
96
+ const primitives = [{
97
+ kind: "rect",
98
+ rect: {
99
+ xPx: page.margins.left,
100
+ yPx: areaTopPx + FOOTNOTE_RULE_MARGIN_PX,
101
+ widthPx: contentWidthPx * FOOTNOTE_RULE_WIDTH_FRACTION,
102
+ heightPx: FOOTNOTE_RULE_THICKNESS_PX
103
+ },
104
+ fill: DOC_CANVAS_TEXT
105
+ }];
106
+ composer.push(primitives);
107
+ for (const [index, { noteId, content }] of bodies.entries()) {
108
+ if (content === void 0) continue;
109
+ const offsetPx = calculateFootnoteAreaRenderHeight(items.slice(0, index));
110
+ const story = {
111
+ kind: "footnote",
112
+ id: noteId
113
+ };
114
+ composer.region({
115
+ kind: HIT_REGION_KINDS.note,
116
+ rect: {
117
+ xPx: page.margins.left,
118
+ yPx: areaTopPx + offsetPx,
119
+ widthPx: contentWidthPx,
120
+ heightPx: content.height
121
+ },
122
+ model: { story }
123
+ }, () => {
124
+ paintFootnoteBlocks({
125
+ composer,
126
+ blocks: content.blocks,
127
+ measures: content.measures,
128
+ xPx: page.margins.left,
129
+ yPx: areaTopPx + offsetPx,
130
+ widthPx: contentWidthPx,
131
+ context: {
132
+ ...context,
133
+ story
134
+ },
135
+ label: `footnote ${content.displayNumber}`
136
+ });
137
+ });
138
+ }
139
+ };
140
+ //#endregion
141
+ export { paintColumnSeparators, paintFootnoteArea, paintPageBackground };
@@ -0,0 +1,19 @@
1
+ import { ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphMeasure } from "../../layout-engine/types.js";
2
+ import { BuildContext } from "./buildContext.js";
3
+ import { PageComposer } from "./regions.js";
4
+ //#region src/display-list/build/paragraphPrimitives.d.ts
5
+ type ParagraphPaintOptions = {
6
+ /** Where the primitives and the regions over them are collected. */
7
+ readonly composer: PageComposer;
8
+ readonly fragment: ParagraphFragment;
9
+ readonly block: ParagraphBlock;
10
+ readonly measure: ParagraphMeasure;
11
+ readonly context: BuildContext;
12
+ /** Borders of the adjacent paragraphs, for ECMA-376 §17.3.1.24 border grouping. */
13
+ readonly prevBorders?: ParagraphBorders;
14
+ readonly nextBorders?: ParagraphBorders;
15
+ };
16
+ /** Every primitive a paragraph fragment paints, back to front. */
17
+ declare const paintParagraphFragment: (options: ParagraphPaintOptions) => void;
18
+ //#endregion
19
+ export { ParagraphPaintOptions, paintParagraphFragment };