@stll/folio-core 0.5.0 → 0.6.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 (37) hide show
  1. package/dist/ai-edits/headless.d.ts +5 -1
  2. package/dist/ai-edits/headless.js +53 -3
  3. package/dist/ai-edits/index.d.ts +2 -2
  4. package/dist/compat/eigenpal.d.ts +2 -2
  5. package/dist/controller/layoutPipeline.js +17 -6
  6. package/dist/document-operations.d.ts +20 -3
  7. package/dist/document-operations.js +7 -4
  8. package/dist/docx/blockContentParser.js +2 -100
  9. package/dist/docx/paragraphTextBoxEnrichment.d.ts +9 -0
  10. package/dist/docx/paragraphTextBoxEnrichment.js +104 -0
  11. package/dist/docx/tableParser.js +2 -0
  12. package/dist/i18n/messages/catalogs.gen.d.ts +1054 -34
  13. package/dist/i18n/messages/catalogs.gen.js +1122 -68
  14. package/dist/i18n/messages/messages.gen.d.ts +62 -2
  15. package/dist/index.d.ts +2 -2
  16. package/dist/layout-bridge/convert/toFlowBlocks.js +15 -13
  17. package/dist/layout-bridge/sectionColumns.js +6 -1
  18. package/dist/layout-engine/index.js +63 -11
  19. package/dist/layout-engine/measure/measureBlocks.js +25 -2
  20. package/dist/layout-engine/paginator.d.ts +2 -0
  21. package/dist/layout-engine/paginator.js +17 -13
  22. package/dist/layout-engine/tableRowBreak.js +3 -0
  23. package/dist/layout-engine/types.d.ts +5 -2
  24. package/dist/layout-painter/index.js +1 -1
  25. package/dist/layout-painter/renderPage.js +7 -2
  26. package/dist/layout-painter/renderParagraph.js +5 -4
  27. package/dist/layout-painter/renderTable.js +88 -10
  28. package/dist/managers/TableSelectionManager.js +1 -1
  29. package/dist/paged-layout/sectionBlockWidths.js +11 -3
  30. package/dist/prosemirror/commands/index.js +1 -1
  31. package/dist/prosemirror/conversion/fromProseDoc.js +11 -2
  32. package/dist/prosemirror/conversion/toProseDoc.js +26 -20
  33. package/dist/prosemirror/extensions/nodes/TableExtension.js +2 -2
  34. package/dist/prosemirror/index.js +1 -1
  35. package/dist/prosemirror/insertOperations.js +1 -1
  36. package/dist/server.d.ts +2 -2
  37. package/package.json +1 -1
@@ -35,6 +35,7 @@ type Messages = {
35
35
  "red": "Red";
36
36
  "yellow": "Yellow";
37
37
  };
38
+ "custom": "Custom";
38
39
  "customColor": "Custom Color";
39
40
  "noColor": "No color";
40
41
  "standardColors": "Standard Colors";
@@ -59,6 +60,7 @@ type Messages = {
59
60
  "closeDialog": "Close";
60
61
  "insert": "Insert";
61
62
  "px": "px";
63
+ "remove": "Remove";
62
64
  "update": "Update";
63
65
  };
64
66
  "contentControlDateAriaLabel": "Pick a date";
@@ -122,13 +124,16 @@ type Messages = {
122
124
  "title": "Footnote & Endnote Properties";
123
125
  };
124
126
  "hyperlink": {
127
+ "addressLabel": "Address";
125
128
  "bookmarkLabel": "Bookmark";
126
129
  "bookmarkPlaceholder": "Select a bookmark";
127
130
  "displayTextHint": "Defaults to the selected text";
128
131
  "displayTextLabel": "Display text";
129
132
  "displayTextPlaceholder": "Text to display";
130
133
  "invalidUrl": "Enter a valid URL";
134
+ "linkTo": "Link to";
131
135
  "removeLink": "Remove link";
136
+ "screenTipLabel": "Screen tip";
132
137
  "tabBookmark": "Bookmark";
133
138
  "tabWebAddress": "Web address";
134
139
  "titleEdit": "Edit hyperlink";
@@ -150,9 +155,11 @@ type Messages = {
150
155
  "top": "Top";
151
156
  };
152
157
  "alignment": "Alignment";
158
+ "distanceFromText": "Distance from text (px)";
153
159
  "horizontal": "Horizontal";
154
160
  "offset": "Offset";
155
161
  "offsetPx": "Offset (px)";
162
+ "position": "Position";
156
163
  "relativeOptions": {
157
164
  "character": "Character";
158
165
  "column": "Column";
@@ -173,12 +180,17 @@ type Messages = {
173
180
  "dashed": "Dashed";
174
181
  "dotted": "Dotted";
175
182
  "double": "Double";
183
+ "groove": "Groove";
184
+ "inset": "Inset";
185
+ "outset": "Outset";
186
+ "ridge": "Ridge";
176
187
  "solid": "Solid";
177
188
  };
178
189
  "color": "Color";
179
190
  "dimensions": "Dimensions";
180
191
  "heightLabel": "Height";
181
192
  "lockAspectRatio": "Lock aspect ratio";
193
+ "preview": "Preview";
182
194
  "style": "Style";
183
195
  "textWrapping": "Text wrapping";
184
196
  "title": "Image Properties";
@@ -193,6 +205,11 @@ type Messages = {
193
205
  "wrapRight": "Wrap right";
194
206
  };
195
207
  };
208
+ "insertImage": {
209
+ "altText": "Alt text";
210
+ "imageFile": "Image file";
211
+ "title": "Insert Image";
212
+ };
196
213
  "insertSymbol": {
197
214
  "categories": {
198
215
  "arrows": "Arrows";
@@ -209,18 +226,19 @@ type Messages = {
209
226
  "title": "Insert symbol";
210
227
  };
211
228
  "insertTable": {
212
- "autofit": "Autofit";
229
+ "autofit": "Autofit to contents";
213
230
  "columnWidthLabel": "Column width";
214
231
  "columnsLabel": "Columns";
215
232
  "fixedWidth": "Fixed width";
216
233
  "hoverToSelect": "Hover to select table size";
217
234
  "insertButton": "Insert";
218
235
  "orSpecifySize": "Or specify size";
236
+ "plainTable": "Plain table";
219
237
  "rowsLabel": "Rows";
220
238
  "sizeSelector": "Table size selector";
221
239
  "style": "Style";
222
240
  "tableSize": "{cols} × {rows} table";
223
- "title": "Insert table";
241
+ "title": "Insert Table";
224
242
  "validationHint": "Rows: {minRows}–{maxRows}, columns: {minCols}–{maxCols}";
225
243
  };
226
244
  "pageSetup": {
@@ -245,6 +263,16 @@ type Messages = {
245
263
  "sizeLabel": "Size";
246
264
  "title": "Page Setup";
247
265
  "top": "Top";
266
+ "unitInches": "in";
267
+ };
268
+ "pasteSpecial": {
269
+ "pasteMode": "Paste mode";
270
+ "title": "Paste Special";
271
+ };
272
+ "splitCell": {
273
+ "mergeBeforeSplit": "Merge selected cells before splitting";
274
+ "splitButton": "Split";
275
+ "title": "Split Cell";
248
276
  };
249
277
  "tableProperties": {
250
278
  "alignOptions": {
@@ -266,6 +294,23 @@ type Messages = {
266
294
  "percentage": "Percentage";
267
295
  };
268
296
  };
297
+ "watermark": {
298
+ "color": "Color";
299
+ "diagonal": "Diagonal";
300
+ "font": "Font";
301
+ "imageRelationshipId": "Header image relationship id";
302
+ "imageTarget": "Image target";
303
+ "noWatermark": "No watermark";
304
+ "opacity": "Opacity";
305
+ "pictureWatermark": "Picture watermark";
306
+ "scale": "Scale";
307
+ "targetIsExternalUrl": "Target is an external URL";
308
+ "text": "Text";
309
+ "textWatermark": "Text watermark";
310
+ "title": "Watermark";
311
+ "type": "Type";
312
+ "washout": "Washout";
313
+ };
269
314
  };
270
315
  "discardChanges": "Discard";
271
316
  "dismiss": "Dismiss";
@@ -338,6 +383,17 @@ type Messages = {
338
383
  "superscriptShortcut": "Superscript (Ctrl+Shift+=)";
339
384
  };
340
385
  "formattingToolbar": "Formatting toolbar";
386
+ "headerFooter": {
387
+ "closeFooterEditing": "Close footer editing";
388
+ "closeHeaderEditing": "Close header editing";
389
+ "footer": "Footer";
390
+ "header": "Header";
391
+ "insertPageCount": "Insert total page count";
392
+ "insertPageNumber": "Insert current page number";
393
+ "options": "Options";
394
+ "removeFooter": "Remove footer";
395
+ "removeHeader": "Remove header";
396
+ };
341
397
  "hideDetails": "Hide details";
342
398
  "historyGroup": "History";
343
399
  "imageOverlay": {
@@ -399,6 +455,7 @@ type Messages = {
399
455
  };
400
456
  "listFormatting": "List formatting";
401
457
  "listsGroup": "Lists";
458
+ "loadDocumentFailedTitle": "Failed to Load Document";
402
459
  "loadingDocument": "Loading document...";
403
460
  "loadingEditor": "Loading editor...";
404
461
  "lockFile": "Lock file";
@@ -411,8 +468,10 @@ type Messages = {
411
468
  "moreFormatting": "More formatting";
412
469
  "networkError": "Network error. Please check your internet connection and try again.";
413
470
  "nextChange": "Next Change";
471
+ "noDocumentLoaded": "No document loaded";
414
472
  "numberedList": "Numbered List";
415
473
  "parseError": "The document could not be parsed. It may be corrupted or in an unsupported format.";
474
+ "parseErrorTitle": "Unable to Parse Document";
416
475
  "paste": "Paste";
417
476
  "pasteUnformatted": "Paste without formatting";
418
477
  "previousChange": "Previous Change";
@@ -572,6 +631,7 @@ type Messages = {
572
631
  "viewGroup": "View";
573
632
  "viewer": {
574
633
  "pageIndicator": "Page {current} of {total}";
634
+ "pageOfTotal": "{current} of {total}";
575
635
  };
576
636
  "zoom": {
577
637
  "zoomIn": "Zoom in";
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockPreviewRun, FolioAIComment, FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditReviewMeta, FolioAIEditSeverity, FolioAIEditSkipReason, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAISignatureParty } from "./ai-edits/types.js";
2
2
  import { applyFolioAIEditOperations } from "./ai-edits/apply.js";
3
3
  import { document_d_exports } from "./types/document.js";
4
- import { ApplyFolioDocumentOperationsOptions, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationStatus, FolioDocumentOperationType, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "./document-operations.js";
4
+ import { ApplyFolioDocumentOperationsOptions, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FolioDocumentOperation, FolioDocumentOperationAffectedTarget, FolioDocumentOperationBatch, FolioDocumentOperationCapabilities, FolioDocumentOperationIssue, FolioDocumentOperationMode, FolioDocumentOperationPrecondition, FolioDocumentOperationReceipt, FolioDocumentOperationRecovery, FolioDocumentOperationResult, FolioDocumentOperationStatus, FolioDocumentOperationType, FolioDocumentOperationUndoFailureReason, FolioDocumentOperationUndoHandle, FolioDocumentOperationUndoResult, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "./document-operations.js";
5
5
  import { createFolioAIEditSnapshot, hashFolioAIBlockText, normalizeFolioAIBlockText } from "./ai-edits/snapshot.js";
6
6
  import { DeriveBlockIdInput, FolioBlockId, deriveBlockId, getFolioParaIdFromBlockId, isFolioBlockId, isSequentialFolioBlockId } from "./types/block-id.js";
7
7
  import { WordDiffSegment, diffWordSegments } from "./ai-edits/word-diff.js";
@@ -27,4 +27,4 @@ import { toMarkdown, toMarkdownResult } from "./markdown/index.js";
27
27
  import { EmbeddedFont, EmbeddedFontParts, extractEmbeddedFonts, getEmbeddedFontFaces } from "./fonts/embeddedFonts.js";
28
28
  import { getGoogleFontsEnabled, setGoogleFontsEnabled } from "./utils/fontResolver.js";
29
29
  type Document = document_d_exports.Document;
30
- export { type AIBarStatus, type AIChatMode, type AICitation, type AICitationRange, type AICitationSource, type AIGenerateInput, type AISuggestion, type AISuggestionApplyMode, type AISuggestionPreset, type AISuggestionSeverity, type AISuggestionStatus, type AcceptAutocompleteResult, type AnonymizationMatch, type AnonymizationTerm, type ApplyFolioDocumentOperationsOptions, type ApplyResult, type AutocompleteSuggestionPluginOptions, type AutocompleteSuggestionState, type AutocompleteSuggestionStatus, type AutocompleteTriggerCheck, type AutocompleteTriggerOptions, type AutocompleteTriggerSkipReason, type CreateEmptyDocumentOptions, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocxCompatibility, type EmbeddedFont, type EmbeddedFontParts, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAISignatureParty, type FolioBlockId, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationType, type ImageMeta, type ImageRef, InvalidFolioDocumentOperationBatchError, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, UnsupportedFolioDocumentOperationVersionError, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, deriveBlockId, diffWordSegments, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
30
+ export { type AIBarStatus, type AIChatMode, type AICitation, type AICitationRange, type AICitationSource, type AIGenerateInput, type AISuggestion, type AISuggestionApplyMode, type AISuggestionPreset, type AISuggestionSeverity, type AISuggestionStatus, type AcceptAutocompleteResult, type AnonymizationMatch, type AnonymizationTerm, type ApplyFolioDocumentOperationsOptions, type ApplyResult, type AutocompleteSuggestionPluginOptions, type AutocompleteSuggestionState, type AutocompleteSuggestionStatus, type AutocompleteTriggerCheck, type AutocompleteTriggerOptions, type AutocompleteTriggerSkipReason, type CreateEmptyDocumentOptions, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocxCompatibility, type EmbeddedFont, type EmbeddedFontParts, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyResult, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAISignatureParty, type FolioBlockId, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationStatus, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type ImageMeta, type ImageRef, InvalidFolioDocumentOperationBatchError, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, UnsupportedFolioDocumentOperationVersionError, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, deriveBlockId, diffWordSegments, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
@@ -6,8 +6,9 @@ import { resolveColor, resolveHighlightToCss } from "../../utils/colorResolver.j
6
6
  import { resolveShadingFill } from "../../utils/formatToStyle.js";
7
7
  import { convertBulletToUnicode } from "../../docx/bulletMarkers.js";
8
8
  import { setTextBoxGroupId } from "../../layout-engine/textBoxGroup.js";
9
+ import { getColumns } from "../sectionColumns.js";
9
10
  import { directionIsRtl } from "../../prosemirror/paragraphDirection.js";
10
- import { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectMathAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs, expectRunShadingMarkAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs } from "../../prosemirror/attrs/index.js";
11
+ import { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHardBreakAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectMathAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs, expectRunShadingMarkAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs } from "../../prosemirror/attrs/index.js";
11
12
  import { autospacingMatchesBase } from "../../prosemirror/autospacingBase.js";
12
13
  import { runShadingAttrsToShading } from "../../prosemirror/conversion/runShadingMark.js";
13
14
  import { assertValidProseMirrorDocument } from "../../prosemirror/validation.js";
@@ -919,6 +920,7 @@ function convertTableCell(node, startPos, options, tableCellMargins) {
919
920
  const block = convertParagraph(child, offset, options);
920
921
  blocks.push(block);
921
922
  } else if (child.type.name === "table") blocks.push(convertTable(child, offset, options));
923
+ else if (child.type.name === "textBox") blocks.push(convertTextBoxNode(child, offset, options));
922
924
  offset += child.nodeSize;
923
925
  });
924
926
  const trailingBlock = blocks.at(-1);
@@ -1213,9 +1215,17 @@ function toFlowBlocks(doc, options = {}) {
1213
1215
  }
1214
1216
  switch (node.type.name) {
1215
1217
  case "paragraph": {
1216
- const block = convertParagraph(node, pos, opts);
1217
1218
  const pmAttrs = expectParagraphAttrs(node);
1218
- trackedPush(block);
1219
+ const onlyChild = node.childCount === 1 ? node.firstChild : null;
1220
+ if (onlyChild?.type.name === "hardBreak" && expectHardBreakAttrs(onlyChild).breakType === "column") {
1221
+ const columnBreak = {
1222
+ kind: "columnBreak",
1223
+ id: nextBlockId(),
1224
+ pmStart: pos,
1225
+ pmEnd: pos + node.nodeSize
1226
+ };
1227
+ trackedPush(columnBreak);
1228
+ } else trackedPush(convertParagraph(node, pos, opts));
1219
1229
  const secProps = pmAttrs._sectionProperties;
1220
1230
  if (secProps || pmAttrs.sectionBreakType) {
1221
1231
  const sectionBreak = {
@@ -1245,16 +1255,8 @@ function toFlowBlocks(doc, options = {}) {
1245
1255
  if (secProps.headerDistance !== void 0) sectionBreak.margins.header = twipsToPixels(secProps.headerDistance);
1246
1256
  if (secProps.footerDistance !== void 0) sectionBreak.margins.footer = twipsToPixels(secProps.footerDistance);
1247
1257
  }
1248
- const colCount = secProps.columnCount ?? 1;
1249
- if (colCount > 1) {
1250
- const cols = {
1251
- count: colCount,
1252
- gap: twipsToPixels(secProps.columnSpace ?? 720),
1253
- equalWidth: secProps.equalWidth ?? true
1254
- };
1255
- if (secProps.separator !== void 0) cols.separator = secProps.separator;
1256
- sectionBreak.columns = cols;
1257
- }
1258
+ const columns = getColumns(secProps);
1259
+ if (columns) sectionBreak.columns = columns;
1258
1260
  }
1259
1261
  trackedPush(sectionBreak);
1260
1262
  }
@@ -7,13 +7,18 @@ const DEFAULT_COLUMN_SPACE_TWIPS = 720;
7
7
  * Returns undefined for single-column (default) to avoid unnecessary paginator overhead.
8
8
  */
9
9
  function getColumns(sectionProps) {
10
- const count = sectionProps?.columnCount ?? 1;
10
+ const authoredColumns = sectionProps?.columns;
11
+ const count = sectionProps?.columnCount ?? authoredColumns?.length ?? 1;
11
12
  if (count <= 1) return void 0;
12
13
  const columns = {
13
14
  count,
14
15
  gap: twipsToPixels(sectionProps?.columnSpace ?? DEFAULT_COLUMN_SPACE_TWIPS),
15
16
  equalWidth: sectionProps?.equalWidth ?? true
16
17
  };
18
+ if (sectionProps?.equalWidth === false && authoredColumns?.length === count && authoredColumns.every(({ width }) => width !== void 0)) {
19
+ columns.widths = authoredColumns.map(({ width }) => twipsToPixels(width ?? 0));
20
+ columns.gaps = authoredColumns.slice(0, -1).map(({ space }) => twipsToPixels(space ?? sectionProps.columnSpace ?? DEFAULT_COLUMN_SPACE_TWIPS));
21
+ }
17
22
  if (sectionProps?.separator !== void 0) columns.separator = sectionProps.separator;
18
23
  return columns;
19
24
  }
@@ -156,7 +156,8 @@ function layoutDocument(blocks, measures, options) {
156
156
  pageSize: finalPageSize,
157
157
  margins: finalMargins
158
158
  };
159
- if (options.columns !== void 0) finalConfig.columns = options.columns;
159
+ const finalColumns = options.finalColumns ?? options.columns;
160
+ if (finalColumns !== void 0) finalConfig.columns = finalColumns;
160
161
  const { configs: sectionConfigs, breakIndices } = collectSectionConfigs(blocks, bodyConfig, finalConfig);
161
162
  const sectionBreakTypes = [...breakIndices.map((index) => blocks[index].type), options.bodyBreakType];
162
163
  const initialConfig = sectionConfigs.at(0) ?? bodyConfig;
@@ -197,11 +198,11 @@ function layoutDocument(blocks, measures, options) {
197
198
  const pageBeforeBlockLayout = paginator.getCurrentState().page.number;
198
199
  switch (block.kind) {
199
200
  case "paragraph":
200
- layoutParagraph(block, measure, paginator, paginator.getContentWidth(), options.footnoteHeightById);
201
+ layoutParagraph(block, measure, paginator, paginator.columnWidth, options.footnoteHeightById);
201
202
  break;
202
203
  case "table":
203
204
  if (block.floating) layoutFloatingTable(block, measure, paginator, paginator.getContentWidth());
204
- else layoutTable(block, measure, paginator);
205
+ else layoutTable(block, measure, paginator, options.footnoteHeightById);
205
206
  break;
206
207
  case "image":
207
208
  layoutImage(block, measure, paginator);
@@ -416,9 +417,10 @@ function getHeaderRowsHeight(measure, headerRowCount) {
416
417
  /**
417
418
  * Layout a table block onto pages.
418
419
  */
419
- function layoutTable(block, measure, paginator) {
420
+ function layoutTable(block, measure, paginator, footnoteHeightById) {
420
421
  const rows = measure.rows;
421
422
  if (rows.length === 0) return;
423
+ const rowFootnoteIds = block.rows.map((row) => collectTableRowFootnoteIds(row, footnoteHeightById));
422
424
  const headerRowCount = countHeaderRows(block);
423
425
  const headerRowsHeight = getHeaderRowsHeight(measure, headerRowCount);
424
426
  let currentRowIndex = 0;
@@ -427,9 +429,9 @@ function layoutTable(block, measure, paginator) {
427
429
  let x = paginator.getColumnX(columnIndex);
428
430
  if (block.justification === "center") x += (paginator.columnWidth - measure.totalWidth) / 2;
429
431
  else if (block.justification === "right") x = x + paginator.columnWidth - measure.totalWidth;
430
- else if (block.indent !== void 0) {
431
- const leadingCellMargin = block.rows[0]?.cells[0]?.padding?.left ?? 0;
432
- x += block.indent - leadingCellMargin;
432
+ else {
433
+ const leadingCellMargin = block.rows.at(0)?.cells.at(0)?.padding?.left ?? 0;
434
+ x += (block.indent ?? 0) - leadingCellMargin;
433
435
  }
434
436
  return x;
435
437
  };
@@ -507,13 +509,42 @@ function layoutTable(block, measure, paginator) {
507
509
  const normalHeaderOverhead = repeatHeaderRowsForNormalFragment ? headerRowsHeight : 0;
508
510
  let rowsHeight = 0;
509
511
  let fittingRows = 0;
512
+ const pageFootnoteIds = new Set(state.page.footnoteIds ?? []);
513
+ const fragmentFootnoteIds = [];
514
+ let fragmentFootnoteHeight = 0;
515
+ let retryOnNextFlowRegion = false;
510
516
  for (let j = currentRowIndex; j < rows.length; j++) {
511
517
  const rowHeight = rows[j].height;
512
- if (rowsHeight + rowHeight + normalHeaderOverhead <= availableHeight || fittingRows === 0) {
518
+ const currentRowFootnoteIds = rowFootnoteIds[j] ?? [];
519
+ const fragmentFootnoteCountBeforeRow = fragmentFootnoteIds.length;
520
+ let rowFootnoteHeight = 0;
521
+ for (const id of currentRowFootnoteIds) {
522
+ if (pageFootnoteIds.has(id) || fragmentFootnoteIds.includes(id)) continue;
523
+ fragmentFootnoteIds.push(id);
524
+ rowFootnoteHeight += footnoteHeightById?.get(id) ?? 0;
525
+ }
526
+ const separatorHeight = pageFootnoteIds.size === 0 && fragmentFootnoteHeight + rowFootnoteHeight > 0 ? 12 : 0;
527
+ if (rowsHeight + rowHeight + normalHeaderOverhead + fragmentFootnoteHeight + rowFootnoteHeight + separatorHeight <= availableHeight) {
513
528
  rowsHeight += rowHeight;
529
+ fragmentFootnoteHeight += rowFootnoteHeight;
514
530
  fittingRows++;
515
- } else break;
531
+ } else if (fittingRows === 0) {
532
+ if (state.cursorY === state.topMargin && state.page.fragments.length === 0) {
533
+ rowsHeight += rowHeight;
534
+ fragmentFootnoteHeight += rowFootnoteHeight;
535
+ fittingRows++;
536
+ break;
537
+ }
538
+ fragmentFootnoteIds.splice(fragmentFootnoteCountBeforeRow);
539
+ paginator.forceColumnBreak();
540
+ retryOnNextFlowRegion = true;
541
+ break;
542
+ } else {
543
+ fragmentFootnoteIds.splice(fragmentFootnoteCountBeforeRow);
544
+ break;
545
+ }
516
546
  }
547
+ if (retryOnNextFlowRegion) continue;
517
548
  const fragmentHeight = rowsHeight + normalHeaderOverhead;
518
549
  const isLastFragment = currentRowIndex + fittingRows >= rows.length;
519
550
  const desiredX = computeTableX(state.columnIndex);
@@ -533,6 +564,7 @@ function layoutTable(block, measure, paginator) {
533
564
  ...repeatHeaderRowsForNormalFragment ? { headerRowCount } : {},
534
565
  ...block.sdtGroups ? { sdtGroups: block.sdtGroups } : {}
535
566
  };
567
+ if (fragmentFootnoteHeight > 0) paginator.addFootnoteHeight(fragmentFootnoteHeight, fragmentFootnoteIds);
536
568
  fragment.y = paginator.addFragment(fragment, fragmentHeight, bandSkip, 0).y;
537
569
  fragment.x = desiredX;
538
570
  currentRowIndex += fittingRows;
@@ -549,6 +581,25 @@ function layoutTable(block, measure, paginator) {
549
581
  }
550
582
  }
551
583
  }
584
+ function collectTableRowFootnoteIds(row, footnoteHeightById) {
585
+ if (!row || !footnoteHeightById) return [];
586
+ const ids = [];
587
+ const walk = (blocks) => {
588
+ for (const block of blocks) {
589
+ if (block.kind === "paragraph") {
590
+ for (const run of block.runs) if (run.kind === "text" && run.footnoteRefId !== void 0 && footnoteHeightById.has(run.footnoteRefId) && !ids.includes(run.footnoteRefId)) ids.push(run.footnoteRefId);
591
+ continue;
592
+ }
593
+ if (block.kind === "table") {
594
+ for (const nestedRow of block.rows) for (const cell of nestedRow.cells) walk(cell.blocks);
595
+ continue;
596
+ }
597
+ if (block.kind === "textBox") walk(block.content);
598
+ }
599
+ };
600
+ for (const cell of row.cells) walk(cell.blocks);
601
+ return ids;
602
+ }
552
603
  /**
553
604
  * Layout a floating table (anchored) without advancing the cursor.
554
605
  */
@@ -587,8 +638,9 @@ function layoutFloatingTable(block, measure, paginator, contentWidth) {
587
638
  else if (spec === "center") y = baseY + (contentHeight - tableHeight) / 2;
588
639
  }
589
640
  if (!usedExplicitY) y = paginator.ensureFits(tableHeight).cursorY;
590
- const minX = margins.left;
591
- const maxX = margins.left + contentWidth - tableWidth;
641
+ const pageAnchored = floating?.horzAnchor === "page";
642
+ const minX = pageAnchored ? 0 : margins.left;
643
+ const maxX = pageAnchored ? page.size.w - tableWidth : margins.left + contentWidth - tableWidth;
592
644
  if (Number.isFinite(maxX)) x = Math.max(minX, Math.min(x, maxX));
593
645
  const fragment = {
594
646
  kind: "table",
@@ -1,4 +1,4 @@
1
- import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, tableColumnsArePinned } from "../types.js";
1
+ import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, isFloatingTextBoxBlock, tableColumnsArePinned } from "../types.js";
2
2
  import { findClearLineY, measureParagraph } from "./measureParagraph.js";
3
3
  import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
4
4
  import { getTextBoxGroupId } from "../textBoxGroup.js";
@@ -56,6 +56,7 @@ function isInlineFlowImageRun(run) {
56
56
  return true;
57
57
  }
58
58
  function measureTableCellBlockVisualHeight(block, blockMeasure) {
59
+ if (block.kind === "textBox" && isFloatingTextBoxBlock(block)) return 0;
59
60
  if (block.kind !== "paragraph" || blockMeasure.kind !== "paragraph") {
60
61
  if ("totalHeight" in blockMeasure) return blockMeasure.totalHeight;
61
62
  if ("height" in blockMeasure) return blockMeasure.height;
@@ -95,7 +96,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
95
96
  columnWidths = Array.from({ length: colCount }, () => equalWidth);
96
97
  } else if (columnWidths.length > 0 && explicitWidthPx) {
97
98
  const totalWidth = columnWidths.reduce((sum, w) => sum + w, 0);
98
- if (totalWidth > 0 && Math.abs(totalWidth - explicitWidthPx) > 1) {
99
+ if (!(tableBlock.layout !== "fixed" && (tableBlock.widthType === void 0 || tableBlock.widthType === "dxa") && totalWidth - explicitWidthPx > 1) && totalWidth > 0 && Math.abs(totalWidth - explicitWidthPx) > 1) {
99
100
  const scale = explicitWidthPx / totalWidth;
100
101
  columnWidths = columnWidths.map((w) => w * scale);
101
102
  }
@@ -176,6 +177,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
176
177
  const padTop = sourceCell?.padding?.top ?? DEFAULT_CELL_PADDING_Y;
177
178
  const padBottom = sourceCell?.padding?.bottom ?? DEFAULT_CELL_PADDING_Y;
178
179
  cell.height += padTop + padBottom;
180
+ if ((sourceCell?.rowSpan ?? 1) > 1) continue;
179
181
  const borderHeight = getTableCellVerticalBorderHeight(sourceCell, rowIdx === 0);
180
182
  maxCellHeightWithBorders = Math.max(maxCellHeightWithBorders, cell.height + borderHeight);
181
183
  maxBorderHeight = Math.max(maxBorderHeight, borderHeight);
@@ -186,6 +188,27 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
186
188
  else if (explicitHeight) row.height = Math.max(maxCellHeightWithBorders, explicitHeight + maxBorderHeight);
187
189
  else row.height = maxCellHeightWithBorders;
188
190
  }
191
+ for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
192
+ const sourceRow = tableBlock.rows[rowIdx];
193
+ const measuredRow = rows[rowIdx];
194
+ if (!sourceRow || !measuredRow) continue;
195
+ for (let cellIdx = 0; cellIdx < sourceRow.cells.length; cellIdx++) {
196
+ const sourceCell = sourceRow.cells[cellIdx];
197
+ const measuredCell = measuredRow.cells[cellIdx];
198
+ const rowSpan = sourceCell?.rowSpan ?? 1;
199
+ if (!sourceCell || !measuredCell || rowSpan <= 1) continue;
200
+ const spanEnd = Math.min(rows.length, rowIdx + rowSpan);
201
+ let combinedHeight = 0;
202
+ for (let spannedRowIdx = rowIdx; spannedRowIdx < spanEnd; spannedRowIdx++) combinedHeight += rows[spannedRowIdx]?.height ?? 0;
203
+ const deficit = measuredCell.height + getTableCellVerticalBorderHeight(sourceCell, rowIdx === 0) - combinedHeight;
204
+ if (deficit <= 0) continue;
205
+ for (let spannedRowIdx = spanEnd - 1; spannedRowIdx >= rowIdx; spannedRowIdx--) {
206
+ if (tableBlock.rows[spannedRowIdx]?.heightRule === "exact") continue;
207
+ rows[spannedRowIdx].height += deficit;
208
+ break;
209
+ }
210
+ }
211
+ }
189
212
  const totalHeight = rows.reduce((h, r) => h + r.height, 0);
190
213
  const totalWidth = columnWidths.reduce((w, cw) => w + cw, 0) || explicitWidthPx || contentWidth;
191
214
  return {
@@ -55,6 +55,8 @@ declare function createPaginator(options: PaginatorOptions): {
55
55
  count: number;
56
56
  gap: number;
57
57
  equalWidth?: boolean;
58
+ widths?: number[];
59
+ gaps?: number[];
58
60
  separator?: boolean;
59
61
  }; /** Get current state. */
60
62
  getCurrentState: () => PageState; /** Get available height in current column. */
@@ -7,12 +7,13 @@ import { panic } from "better-result";
7
7
  * Tracks the current page, cursor position, and available space.
8
8
  * Creates new pages when content doesn't fit.
9
9
  */
10
- /**
11
- * Calculate the width of a single column.
12
- */
13
- function calculateColumnWidth(pageWidth, leftMargin, rightMargin, columns) {
14
- return (pageWidth - leftMargin - rightMargin - (columns.count - 1) * columns.gap) / columns.count;
10
+ /** Calculate active column widths, preferring authored unequal widths. */
11
+ function calculateColumnWidths(pageWidth, leftMargin, rightMargin, columns) {
12
+ if (columns.widths?.length === columns.count && columns.widths.every((width) => Number.isFinite(width) && width > 0)) return [...columns.widths];
13
+ const equalWidth = (pageWidth - leftMargin - rightMargin - (columns.count - 1) * columns.gap) / columns.count;
14
+ return Array.from({ length: columns.count }, () => equalWidth);
15
15
  }
16
+ const gapAfterColumn = (columns, columnIndex) => columns.gaps?.[columnIndex] ?? columns.gap;
16
17
  function arePageSizesEqual(left, right) {
17
18
  return left.w === right.w && left.h === right.h;
18
19
  }
@@ -49,9 +50,9 @@ function createPaginator(options) {
49
50
  return arePageSizesEqual(state.page.size, pageSize) && areMarginsEqual(state.page.margins, getPageMargins(state.page.number));
50
51
  }
51
52
  if (getContentHeight() <= 0) panic("Paginator: page size and margins yield no content area");
52
- let columnWidth = calculateColumnWidth(pageSize.w, margins.left, margins.right, columns);
53
- function recalculateColumnWidth() {
54
- columnWidth = calculateColumnWidth(pageSize.w, margins.left, margins.right, columns);
53
+ let columnWidths = calculateColumnWidths(pageSize.w, margins.left, margins.right, columns);
54
+ function recalculateColumnWidths() {
55
+ columnWidths = calculateColumnWidths(pageSize.w, margins.left, margins.right, columns);
55
56
  }
56
57
  function applyPendingLayout() {
57
58
  if (pendingPageSize) pageSize = pendingPageSize;
@@ -59,7 +60,7 @@ function createPaginator(options) {
59
60
  pendingPageSize = void 0;
60
61
  pendingMargins = void 0;
61
62
  if (getContentHeight() <= 0) panic("Paginator: section page size and margins yield no content area");
62
- recalculateColumnWidth();
63
+ recalculateColumnWidths();
63
64
  }
64
65
  function getPageMargins(pageNumber) {
65
66
  const pageMargins = pageNumber === 1 && options.firstPageMargins ? { ...options.firstPageMargins } : { ...margins };
@@ -75,7 +76,9 @@ function createPaginator(options) {
75
76
  * Get X position for a given column index.
76
77
  */
77
78
  function getColumnX(columnIndex) {
78
- return (states.at(-1)?.page.margins.left ?? getPageMargins(1).left) + columnIndex * (columnWidth + columns.gap);
79
+ let x = states.at(-1)?.page.margins.left ?? getPageMargins(1).left;
80
+ for (let index = 0; index < columnIndex; index++) x += (columnWidths[index] ?? columnWidths[0] ?? 0) + gapAfterColumn(columns, index);
81
+ return x;
79
82
  }
80
83
  /**
81
84
  * Create a new page and add it to the list.
@@ -274,7 +277,7 @@ function createPaginator(options) {
274
277
  */
275
278
  function updateColumns(newColumns) {
276
279
  columns = newColumns;
277
- columnWidth = calculateColumnWidth(pageSize.w, margins.left, margins.right, columns);
280
+ recalculateColumnWidths();
278
281
  const state = getCurrentState();
279
282
  if (columns.count > 1) state.page.columns = { ...columns };
280
283
  else delete state.page.columns;
@@ -290,7 +293,7 @@ function createPaginator(options) {
290
293
  if (newPageSize) pageSize = { ...newPageSize };
291
294
  if (newMargins) margins = { ...newMargins };
292
295
  if (getContentHeight() <= 0) panic("Paginator: section page size and margins yield no content area");
293
- recalculateColumnWidth();
296
+ recalculateColumnWidths();
294
297
  pendingPageSize = void 0;
295
298
  pendingMargins = void 0;
296
299
  }
@@ -312,7 +315,8 @@ function createPaginator(options) {
312
315
  states,
313
316
  /** Column width in pixels (use getColumnWidth() for current value after updates). */
314
317
  get columnWidth() {
315
- return columnWidth;
318
+ const columnIndex = states.at(-1)?.columnIndex ?? 0;
319
+ return columnWidths[columnIndex] ?? columnWidths[0] ?? getContentWidth();
316
320
  },
317
321
  /** Get current column layout (returns copy to prevent external mutation). */
318
322
  get columns() {
@@ -1,3 +1,4 @@
1
+ import { isFloatingTextBoxBlock } from "./types.js";
1
2
  import { measureParagraph } from "./measure/measureParagraph.js";
2
3
  import { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages } from "./measure/tableCellFloating.js";
3
4
  //#region src/layout-engine/tableRowBreak.ts
@@ -74,6 +75,8 @@ function cellBreakGeometry(cell, measure) {
74
75
  y = blockTop + blockMeasure.totalHeight;
75
76
  paragraphY += blockMeasure.totalHeight;
76
77
  } else if (blockMeasure) {
78
+ const block = cellBlocks?.[i];
79
+ if (block?.kind === "textBox" && isFloatingTextBoxBlock(block)) continue;
77
80
  const blockHeight = getAtomicBlockHeight(blockMeasure);
78
81
  if (blockHeight > 0) {
79
82
  const top = y;
@@ -846,7 +846,9 @@ type Page = {
846
846
  type ColumnLayout = {
847
847
  count: number;
848
848
  gap: number;
849
- equalWidth?: boolean; /** Draw vertical separator line between columns (w:sep). */
849
+ equalWidth?: boolean; /** Authored widths for unequal-width section columns. */
850
+ widths?: number[]; /** Authored space after each column except the last. */
851
+ gaps?: number[]; /** Draw vertical separator line between columns (w:sep). */
850
852
  separator?: boolean;
851
853
  };
852
854
  /**
@@ -904,7 +906,8 @@ type LayoutOptions = {
904
906
  w: number;
905
907
  h: number;
906
908
  }; /** Body-level final section margins. */
907
- finalMargins?: PageMargins; /** Column configuration. */
909
+ finalMargins?: PageMargins; /** Body-level final section column configuration. */
910
+ finalColumns?: ColumnLayout; /** Column configuration. */
908
911
  columns?: ColumnLayout; /** Gap between rendered pages (for UI). */
909
912
  pageGap?: number; /** Default line height multiplier. */
910
913
  defaultLineHeight?: number; /** Header content heights by variant. */
@@ -1,8 +1,8 @@
1
1
  import { FRAGMENT_CLASS_NAMES, renderFragment } from "./renderFragment.js";
2
2
  import { IMAGE_CLASS_NAMES, renderImageFragment } from "./renderImage.js";
3
3
  import { renderLine, renderParagraphFragment, sliceRunsForLine } from "./renderParagraph.js";
4
- import { TABLE_CLASS_NAMES, renderTableFragment } from "./renderTable.js";
5
4
  import { TEXTBOX_CLASS_NAMES, renderTextBoxFragment } from "./renderTextBox.js";
5
+ import { TABLE_CLASS_NAMES, renderTableFragment } from "./renderTable.js";
6
6
  import { renderPage, renderPages } from "./renderPage.js";
7
7
  import { prefersReducedMotionBehavior } from "../paged-layout/scrollNavigation.js";
8
8
  import { createFeatureRegistry } from "./registry/registry.js";
@@ -8,8 +8,8 @@ import { renderFragment } from "./renderFragment.js";
8
8
  import { applyImageVisualAttrs, hasImageVisualAttrs, renderImageFragment } from "./renderImage.js";
9
9
  import { emuToPixels } from "./renderUtils.js";
10
10
  import { renderParagraphFragment } from "./renderParagraph.js";
11
- import { renderTableFragment } from "./renderTable.js";
12
11
  import { renderTextBoxFragment } from "./renderTextBox.js";
12
+ import { renderTableFragment } from "./renderTable.js";
13
13
  import { renderWatermarkLayer } from "./renderWatermark.js";
14
14
  import { panic } from "better-result";
15
15
  //#region src/layout-painter/renderPage.ts
@@ -560,7 +560,12 @@ function renderHeaderFooterContent(content, context, options, layout) {
560
560
  ...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
561
561
  ...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {}
562
562
  }, block, measure, hfContext, { document: doc });
563
- fragEl.style.top = `${cursorY}px`;
563
+ const textBoxTop = block.position ? resolveHeaderFooterFloatTop({
564
+ height: measure.height,
565
+ paragraphY: cursorY,
566
+ position: block.position
567
+ }, layout) : cursorY;
568
+ fragEl.style.top = `${textBoxTop}px`;
564
569
  fragEl.style.left = resolveHeaderFooterFloatLeft(measure.width, block.position?.horizontal, layout);
565
570
  if (block.wrapType === "behind") fragEl.style.zIndex = "-1";
566
571
  containerEl.append(fragEl);