@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,283 @@
1
+ import { HIT_REGION_KINDS } from "../primitives.js";
2
+ import { AuthorColorTable } from "./buildContext.js";
3
+ import { DOC_CANVAS } from "./colors.js";
4
+ import { collectFloatingImages, pageGeometryOf } from "./floatingImages.js";
5
+ import { FontTable } from "./fontTable.js";
6
+ import { paintPageFurniture } from "./furniture.js";
7
+ import { ImageTable, paintImageFragment } from "./imagePrimitives.js";
8
+ import { paintColumnSeparators, paintPageBackground } from "./pageFurniture.js";
9
+ import { paintParagraphFragment } from "./paragraphPrimitives.js";
10
+ import { blockRegion, createPageComposer } from "./regions.js";
11
+ import { paintTableFragment } from "./tablePrimitives.js";
12
+ import { paintTextBoxFragment } from "./textBoxPrimitives.js";
13
+ import { UNSUPPORTED_CONSTRUCT, UnsupportedCollector } from "./unsupported.js";
14
+ //#region src/display-list/build/buildDisplayList.ts
15
+ /** Document-wide gaps are attributed to the first page, the only one certain to exist. */
16
+ const FIRST_PAGE_INDEX = 0;
17
+ /**
18
+ * Every feature a source document can have that a `Layout` does not record and
19
+ * that nothing on a page betrays, with the reason it paints nothing when the
20
+ * caller supplies neither the construct nor a flag denying it. The list is the
21
+ * single source of truth: {@link DocumentFeatures} is derived from it, so a
22
+ * feature added here cannot be left without a flag or without a reason.
23
+ *
24
+ * Footnote bodies and header/footer stories are deliberately absent: a page
25
+ * that carries them says so itself (`footnoteIds`, `headerFooterRefs`), so
26
+ * their gaps are reported per page from the layout rather than from a
27
+ * document-wide flag the caller would have to remember to set.
28
+ */
29
+ const DOCUMENT_FEATURE_GAPS = [["pageBorders", "w:pgBorders reach the painter through render options, not through Layout, and none were supplied to the builder"], ["watermark", "watermarks reach the painter through render options, not through Layout, and none was supplied to the builder"]];
30
+ const paragraphTextOf = (block) => block.runs.map((run) => run.kind === "text" ? run.text : "").join("").trim();
31
+ /**
32
+ * Resolve everything that needs the whole document before any page is painted:
33
+ * where each bookmark lands (a `#name` hyperlink cannot become a page target
34
+ * until then) and the heading outline.
35
+ */
36
+ const collectDocumentTargets = (layout, blockLookup) => {
37
+ const bookmarkTargets = /* @__PURE__ */ new Map();
38
+ const outline = [];
39
+ for (const [pageIndex, page] of layout.pages.entries()) for (const fragment of page.fragments) {
40
+ if (fragment.kind !== "paragraph") continue;
41
+ const block = blockLookup.get(String(fragment.blockId))?.block;
42
+ if (block?.kind !== "paragraph") continue;
43
+ for (const name of block.bookmarks ?? []) if (!bookmarkTargets.has(name)) bookmarkTargets.set(name, {
44
+ kind: "page",
45
+ pageIndex,
46
+ yPx: fragment.y
47
+ });
48
+ const level = block.attrs?.outlineLevel;
49
+ if (level !== void 0 && fragment.continuesFromPrev !== true) {
50
+ const title = paragraphTextOf(block);
51
+ if (title.length > 0) outline.push({
52
+ title,
53
+ level,
54
+ pageIndex,
55
+ yPx: fragment.y
56
+ });
57
+ }
58
+ }
59
+ return {
60
+ bookmarkTargets,
61
+ outline
62
+ };
63
+ };
64
+ const bordersOfNeighbour = (entry) => entry?.block.kind === "paragraph" ? entry.block.attrs?.borders : void 0;
65
+ const paintFragment = ({ fragment, entry, context, composer, prevEntry, nextEntry }) => {
66
+ const { block, measure } = entry;
67
+ const mismatch = () => {
68
+ context.unsupported.report(UNSUPPORTED_CONSTRUCT.measureMismatch, context.pageIndex, `${fragment.kind} fragment ${String(fragment.blockId)} has a ${block.kind} block and a ${measure.kind} measure`);
69
+ };
70
+ switch (fragment.kind) {
71
+ case "paragraph": {
72
+ if (block.kind !== "paragraph" || measure.kind !== "paragraph") {
73
+ mismatch();
74
+ return;
75
+ }
76
+ const prevBorders = bordersOfNeighbour(prevEntry);
77
+ const nextBorders = bordersOfNeighbour(nextEntry);
78
+ paintParagraphFragment({
79
+ fragment,
80
+ block,
81
+ measure,
82
+ context,
83
+ composer,
84
+ ...prevBorders === void 0 ? {} : { prevBorders },
85
+ ...nextBorders === void 0 ? {} : { nextBorders }
86
+ });
87
+ return;
88
+ }
89
+ case "table":
90
+ if (block.kind === "table" && measure.kind === "table") {
91
+ paintTableFragment({
92
+ fragment,
93
+ block,
94
+ measure,
95
+ context,
96
+ composer,
97
+ paintTextBox: paintTextBoxFragment
98
+ });
99
+ return;
100
+ }
101
+ mismatch();
102
+ return;
103
+ case "image":
104
+ if (block.kind === "image") {
105
+ composer.push(paintImageFragment(fragment, block, context));
106
+ return;
107
+ }
108
+ mismatch();
109
+ return;
110
+ case "textBox":
111
+ if (block.kind === "textBox" && measure.kind === "textBox") {
112
+ paintTextBoxFragment({
113
+ composer,
114
+ fragment,
115
+ block,
116
+ measure,
117
+ context
118
+ });
119
+ return;
120
+ }
121
+ mismatch();
122
+ return;
123
+ default: {
124
+ const unreachable = fragment;
125
+ context.unsupported.report(UNSUPPORTED_CONSTRUCT.fragmentKind, context.pageIndex, `fragment kind ${JSON.stringify(unreachable)} has no builder`);
126
+ }
127
+ }
128
+ };
129
+ const buildPage = ({ page, pageIndex, totalPages, blockLookup, pageBackground, bookmarkTargets, fonts, images, unsupported, authorColors, furniture }) => {
130
+ const links = [];
131
+ const context = {
132
+ fonts,
133
+ images,
134
+ unsupported,
135
+ authorColors,
136
+ links,
137
+ bookmarkTargets,
138
+ story: { kind: "body" },
139
+ pageIndex,
140
+ pageNumber: page.logicalNumber,
141
+ totalPages
142
+ };
143
+ const entries = page.fragments.map((fragment) => blockLookup.get(String(fragment.blockId)));
144
+ const geometry = pageGeometryOf(page);
145
+ const placedFloats = /* @__PURE__ */ new Set();
146
+ const behindFloats = [];
147
+ const frontFloats = [];
148
+ for (const [index, fragment] of page.fragments.entries()) {
149
+ const block = entries[index]?.block;
150
+ if (fragment.kind !== "paragraph" || block?.kind !== "paragraph") continue;
151
+ for (const placement of collectFloatingImages({
152
+ block,
153
+ fragmentYPx: fragment.y,
154
+ geometry,
155
+ context,
156
+ placed: placedFloats
157
+ })) (placement.behindDoc ? behindFloats : frontFloats).push(...placement.primitives);
158
+ }
159
+ const { behind, above } = paintPageFurniture({
160
+ page,
161
+ furniture,
162
+ context
163
+ });
164
+ const composer = createPageComposer();
165
+ composer.push([
166
+ paintPageBackground(page, pageBackground),
167
+ ...behind,
168
+ ...behindFloats
169
+ ]);
170
+ composer.region({
171
+ kind: HIT_REGION_KINDS.pageContent,
172
+ rect: {
173
+ xPx: page.margins.left,
174
+ yPx: page.margins.top,
175
+ widthPx: page.size.w - page.margins.left - page.margins.right,
176
+ heightPx: page.size.h - page.margins.top - page.margins.bottom
177
+ }
178
+ }, () => {
179
+ for (const [index, fragment] of page.fragments.entries()) {
180
+ const entry = entries[index];
181
+ if (!entry) {
182
+ unsupported.report(UNSUPPORTED_CONSTRUCT.missingBlock, pageIndex, `no block lookup entry for ${fragment.kind} fragment ${String(fragment.blockId)}`);
183
+ continue;
184
+ }
185
+ composer.region(blockRegion({
186
+ fragment,
187
+ kind: FRAGMENT_REGION_KIND[fragment.kind],
188
+ context
189
+ }), () => {
190
+ paintFragment({
191
+ fragment,
192
+ entry,
193
+ context,
194
+ composer,
195
+ prevEntry: entries[index - 1],
196
+ nextEntry: entries[index + 1]
197
+ });
198
+ });
199
+ }
200
+ });
201
+ composer.push(frontFloats);
202
+ composer.push(paintColumnSeparators(page));
203
+ above(composer);
204
+ return {
205
+ pageNumber: page.number,
206
+ widthPx: page.size.w,
207
+ heightPx: page.size.h,
208
+ orientation: page.orientation ?? (page.size.w > page.size.h ? "landscape" : "portrait"),
209
+ primitives: composer.primitives(),
210
+ regions: composer.regions(),
211
+ links
212
+ };
213
+ };
214
+ /** Which kind of region a fragment paints into. */
215
+ const FRAGMENT_REGION_KIND = {
216
+ paragraph: HIT_REGION_KINDS.paragraph,
217
+ table: HIT_REGION_KINDS.table,
218
+ image: HIT_REGION_KINDS.image,
219
+ textBox: HIT_REGION_KINDS.textBox
220
+ };
221
+ /** Whether the caller handed over the construct this document-wide gap names. */
222
+ const SUPPLIED_BY = {
223
+ pageBorders: (furniture) => furniture.pageBorders !== void 0,
224
+ watermark: (furniture) => furniture.watermark !== void 0 || furniture.watermarkByHeaderRId !== void 0
225
+ };
226
+ const createDisplayListBuilder = ({ layout, blockLookup, metadata, pageBackground, documentFeatures, embeddedFonts, ...furniture }) => {
227
+ const fonts = new FontTable(embeddedFonts ?? []);
228
+ const images = new ImageTable();
229
+ const unsupported = new UnsupportedCollector();
230
+ const authorColors = new AuthorColorTable();
231
+ const { bookmarkTargets, outline } = collectDocumentTargets(layout, blockLookup);
232
+ if (layout.pages.length > 0) for (const [feature, detail] of DOCUMENT_FEATURE_GAPS) {
233
+ if (SUPPLIED_BY[feature](furniture)) continue;
234
+ if (documentFeatures !== void 0 && !documentFeatures[feature]) continue;
235
+ unsupported.report(UNSUPPORTED_CONSTRUCT[feature], FIRST_PAGE_INDEX, detail);
236
+ }
237
+ const built = /* @__PURE__ */ new Map();
238
+ const pageAt = (pageIndex) => {
239
+ const existing = built.get(pageIndex);
240
+ if (existing !== void 0) return existing;
241
+ const page = layout.pages[pageIndex];
242
+ if (page === void 0) return;
243
+ const display = buildPage({
244
+ page,
245
+ pageIndex,
246
+ totalPages: layout.pages.length,
247
+ blockLookup,
248
+ pageBackground: pageBackground ?? DOC_CANVAS,
249
+ bookmarkTargets,
250
+ fonts,
251
+ images,
252
+ unsupported,
253
+ authorColors,
254
+ furniture
255
+ });
256
+ built.set(pageIndex, display);
257
+ return display;
258
+ };
259
+ return {
260
+ pageCount: layout.pages.length,
261
+ pageAt,
262
+ snapshot: () => ({
263
+ pages: [...built.entries()].sort(([left], [right]) => left - right).map(([, page]) => page),
264
+ fonts: fonts.snapshot(),
265
+ images: images.snapshot(),
266
+ outline,
267
+ metadata: metadata ?? {},
268
+ unsupported: unsupported.snapshot()
269
+ })
270
+ };
271
+ };
272
+ /**
273
+ * The whole document's display list. Every page is built, so the tables, the
274
+ * outline and the gap report are complete; an editor that paints a window of
275
+ * pages uses {@link createDisplayListBuilder} instead.
276
+ */
277
+ const buildDisplayList = (options) => {
278
+ const builder = createDisplayListBuilder(options);
279
+ for (let pageIndex = 0; pageIndex < builder.pageCount; pageIndex += 1) builder.pageAt(pageIndex);
280
+ return builder.snapshot();
281
+ };
282
+ //#endregion
283
+ export { buildDisplayList, createDisplayListBuilder };
@@ -0,0 +1,20 @@
1
+ import { DisplayColor } from "../types.js";
2
+ //#region src/display-list/build/colors.d.ts
3
+ /**
4
+ * The editor's document custom properties, materialised. A PDF has no theme
5
+ * and no stylesheet: `--doc-canvas` is the paper, `--doc-canvas-text` the ink,
6
+ * `--suggestion-color` the AI-proposal hue (`renderParagraph.ts`'s
7
+ * `SUGGESTION_COLOR_CSS` fallback).
8
+ */
9
+ declare const DOC_CANVAS: DisplayColor;
10
+ declare const DOC_CANVAS_TEXT: DisplayColor;
11
+ declare const SUGGESTION_COLOR: DisplayColor;
12
+ declare const TRANSPARENT: DisplayColor;
13
+ /**
14
+ * Resolve an authored CSS colour. Returns `undefined` for a value this module
15
+ * cannot turn into concrete channels (`color-mix()`, `oklch()`, an unknown
16
+ * name) so the caller can report it rather than paint a guess.
17
+ */
18
+ declare const parseDisplayColor: (color: string | undefined) => DisplayColor | undefined;
19
+ //#endregion
20
+ export { DOC_CANVAS, DOC_CANVAS_TEXT, SUGGESTION_COLOR, TRANSPARENT, parseDisplayColor };
@@ -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 };