@stll/folio-core 0.28.1 → 0.30.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 (125) hide show
  1. package/dist/controller/folioEditor.d.ts +28 -1
  2. package/dist/controller/folioEditor.js +37 -26
  3. package/dist/controller/fontReadiness.js +28 -24
  4. package/dist/controller/hiddenEditorManager.d.ts +2 -7
  5. package/dist/controller/layoutPipeline.js +33 -3
  6. package/dist/controller/noteEditorManager.d.ts +3 -6
  7. package/dist/docx/metafileSvg.d.ts +12 -0
  8. package/dist/docx/metafileSvg.js +462 -0
  9. package/dist/docx/paragraphTextBoxEnrichment.js +55 -4
  10. package/dist/docx/parser.d.ts +2 -2
  11. package/dist/docx/parser.js +26 -0
  12. package/dist/docx/runParser.js +2 -1
  13. package/dist/docx/serializer/runSerializer.js +14 -2
  14. package/dist/docx/styleParser.js +2 -1
  15. package/dist/docx/textBoxParser.js +14 -0
  16. package/dist/docx/vmlImageParser.js +72 -152
  17. package/dist/docx/vmlPreview.d.ts +31 -0
  18. package/dist/docx/vmlPreview.js +535 -0
  19. package/dist/docx/wrapTypes.d.ts +2 -36
  20. package/dist/docx/wrapTypes.js +1 -39
  21. package/dist/fonts/fontAlternates.d.ts +8 -0
  22. package/dist/fonts/fontAlternates.js +16 -0
  23. package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
  24. package/dist/layout-bridge/convert/footnoteLayout.js +34 -3
  25. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
  26. package/dist/layout-bridge/convert/headerFooterLayout.js +5 -0
  27. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -2
  28. package/dist/layout-bridge/convert/toFlowBlocks.js +136 -120
  29. package/dist/layout-bridge/dom/noteStoryDom.d.ts +1 -1
  30. package/dist/layout-bridge/engine/selectionRects.js +3 -1
  31. package/dist/layout-engine/index.js +65 -32
  32. package/dist/layout-engine/justifiedLineFit.d.ts +7 -0
  33. package/dist/layout-engine/justifiedLineFit.js +6 -0
  34. package/dist/layout-engine/keep-together.js +15 -10
  35. package/dist/layout-engine/measure/complexScriptFormatting.d.ts +6 -1
  36. package/dist/layout-engine/measure/complexScriptFormatting.js +11 -2
  37. package/dist/layout-engine/measure/floatingTablePosition.d.ts +16 -1
  38. package/dist/layout-engine/measure/floatingTablePosition.js +29 -6
  39. package/dist/layout-engine/measure/lineBreakProvider.js +67 -8
  40. package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
  41. package/dist/layout-engine/measure/listMarkerWidth.js +23 -10
  42. package/dist/layout-engine/measure/measureBlocks.d.ts +15 -0
  43. package/dist/layout-engine/measure/measureBlocks.js +197 -30
  44. package/dist/layout-engine/measure/measureContainer.js +20 -24
  45. package/dist/layout-engine/measure/measureHelpers.d.ts +1 -1
  46. package/dist/layout-engine/measure/measureHelpers.js +14 -8
  47. package/dist/layout-engine/measure/measureParagraph.js +111 -37
  48. package/dist/layout-engine/measure/measureTypes.d.ts +3 -0
  49. package/dist/layout-engine/measure/tabCalculator.d.ts +123 -0
  50. package/dist/layout-engine/measure/tabCalculator.js +144 -0
  51. package/dist/layout-engine/measure/tableCellFlow.d.ts +21 -1
  52. package/dist/layout-engine/measure/tableCellFlow.js +45 -8
  53. package/dist/layout-engine/measure/tableInlinePlacement.d.ts +10 -2
  54. package/dist/layout-engine/measure/tableInlinePlacement.js +17 -15
  55. package/dist/layout-engine/paginator.d.ts +26 -2
  56. package/dist/layout-engine/paginator.js +76 -14
  57. package/dist/layout-engine/paragraphSpacing.d.ts +3 -1
  58. package/dist/layout-engine/paragraphSpacing.js +4 -3
  59. package/dist/layout-engine/renderedBreakReconciliation.d.ts +14 -3
  60. package/dist/layout-engine/renderedBreakReconciliation.js +45 -14
  61. package/dist/layout-engine/section-breaks.d.ts +3 -1
  62. package/dist/layout-engine/section-breaks.js +6 -2
  63. package/dist/layout-engine/types.d.ts +18 -1
  64. package/dist/layout-engine/types.js +1 -1
  65. package/dist/layout-painter/imageLayout.d.ts +1 -1
  66. package/dist/layout-painter/renderPage.js +6 -1
  67. package/dist/layout-painter/renderParagraph.js +169 -82
  68. package/dist/layout-painter/renderTable.d.ts +1 -1
  69. package/dist/layout-painter/renderTable.js +18 -7
  70. package/dist/layout-painter/renderTextBox.d.ts +1 -1
  71. package/dist/layout-painter/renderTextBox.js +6 -1
  72. package/dist/markdown/renderParagraph.js +7 -2
  73. package/dist/model.d.ts +3 -3
  74. package/dist/model.js +2 -2
  75. package/dist/paged-layout/sectionBlockWidths.d.ts +14 -0
  76. package/dist/paged-layout/sectionBlockWidths.js +103 -14
  77. package/dist/prosemirror/attrs/index.js +14 -2
  78. package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +8 -0
  79. package/dist/prosemirror/bookmarkBoundaryAttrs.js +66 -0
  80. package/dist/prosemirror/conversion/fromProseDoc.js +176 -54
  81. package/dist/prosemirror/conversion/toProseDoc.js +179 -64
  82. package/dist/prosemirror/extensions/StarterKit.js +11 -3
  83. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +8 -4
  84. package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
  85. package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +9 -5
  86. package/dist/prosemirror/extensions/features/pasteCleanup.d.ts +10 -23
  87. package/dist/prosemirror/extensions/features/pasteCleanup.js +77 -22
  88. package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +9 -5
  89. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +9 -0
  90. package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +67 -0
  91. package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +10 -4
  92. package/dist/prosemirror/extensions/nodes/FieldExtension.js +77 -59
  93. package/dist/prosemirror/extensions/nodes/TableExtension.js +2 -0
  94. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +4 -1
  95. package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +4 -3
  96. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +6 -2
  97. package/dist/prosemirror/listMarker.d.ts +58 -0
  98. package/dist/prosemirror/listMarker.js +185 -0
  99. package/dist/prosemirror/numberedRefFields.d.ts +24 -0
  100. package/dist/prosemirror/numberedRefFields.js +276 -0
  101. package/dist/prosemirror/paraText.js +56 -17
  102. package/dist/prosemirror/plugins/anonymizationDecorations.js +1 -1
  103. package/dist/prosemirror/plugins/pmTextScan.d.ts +3 -1
  104. package/dist/prosemirror/plugins/pmTextScan.js +28 -7
  105. package/dist/prosemirror/plugins/templateDirectives.js +2 -2
  106. package/dist/prosemirror/schema/index.d.ts +2 -2
  107. package/dist/prosemirror/schema/nodes.d.ts +17 -1
  108. package/dist/prosemirror/utils/tabCalculator.d.ts +2 -123
  109. package/dist/prosemirror/utils/tabCalculator.js +1 -140
  110. package/dist/prosemirror/validation.js +93 -0
  111. package/dist/render-dom/RemoteSelectionOverlay.d.ts +4 -4
  112. package/dist/types/documentEnumValues.d.ts +4 -1
  113. package/dist/types/documentEnumValues.js +12 -1
  114. package/dist/types/editor-story.d.ts +8 -0
  115. package/dist/types/editor-story.js +0 -0
  116. package/dist/types/remote-selection.d.ts +11 -0
  117. package/dist/types/remote-selection.js +0 -0
  118. package/dist/types/wrap.d.ts +36 -0
  119. package/dist/types/wrap.js +40 -0
  120. package/dist/utils/fontResolver.d.ts +1 -1
  121. package/dist/utils/fontResolver.js +42 -2
  122. package/dist/utils/formatToStyle.js +4 -2
  123. package/dist/utils/horizontalScale.d.ts +28 -0
  124. package/dist/utils/horizontalScale.js +42 -0
  125. package/package.json +2 -2
@@ -3,12 +3,27 @@ import { FloatingImageZone } from "./floatingZones.js";
3
3
  import "./measureParagraph.js";
4
4
  //#region src/layout-engine/measure/measureBlocks.d.ts
5
5
  declare function measureTableBlock(tableBlock: TableBlock, contentWidth: number, fieldValues?: ReadonlyMap<number, string>): TableMeasure;
6
+ type PhysicalBandPageGeometry = {
7
+ pageWidth: number | number[];
8
+ pageHeight: number | number[];
9
+ marginTop: number | number[];
10
+ marginLeft: number | number[];
11
+ marginRight: number | number[];
12
+ marginBottom: number | number[];
13
+ };
6
14
  type BandPageGeometry = {
7
15
  pageWidth?: number | number[];
8
16
  pageHeight: number | number[];
9
17
  marginLeft?: number | number[];
10
18
  marginRight?: number | number[];
11
19
  marginBottom: number | number[];
20
+ /** Absolute page X of each block's active column origin. */
21
+ contentLeft?: number | number[];
22
+ physicalPage?: PhysicalBandPageGeometry;
23
+ /** Authored flow-column index occupied by each block. */
24
+ columnIndex?: number | number[];
25
+ /** Authored flow-column count for each block's section. */
26
+ columnCount?: number | number[];
12
27
  };
13
28
  /**
14
29
  * Measure a block based on its type.
@@ -1,10 +1,12 @@
1
+ import { hasPageBreakBefore } from "../keep-together.js";
1
2
  import { recordMeasureBlock, recordMeasureBlockError } from "../layoutInstrumentation.js";
2
3
  import { isParagraphFrameTextBox } from "../paragraphFrame.js";
4
+ import { normalizeSectionBreakType } from "../section-breaks.js";
3
5
  import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "../textBoxFlow.js";
4
6
  import { getTextBoxGroupId } from "../textBoxGroup.js";
5
7
  import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText, resolveTableCellPadding, tableColumnsArePinned } from "../types.js";
6
8
  import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
7
- import { resolveFloatingTableX } from "./floatingTablePosition.js";
9
+ import { resolveFloatingTablePageX } from "./floatingTablePosition.js";
8
10
  import { findClearLineY, measureParagraph } from "./measureParagraph.js";
9
11
  import { createTableCellFlowState, finishTableCellFlow, placeTableCellBlock } from "./tableCellFlow.js";
10
12
  import { buildTableCellGrid, getFirstAvailableColumn, getTableCellVerticalBorderHeight } from "./tableCellGrid.js";
@@ -177,6 +179,42 @@ function perBlockNumberValue(value, blockIndex, fallback) {
177
179
  if (Array.isArray(value)) return value[blockIndex] ?? fallback;
178
180
  return value;
179
181
  }
182
+ function resolveFloatingTableVerticalPosition({ floating, tableHeight, pageHeight, marginTop, marginBottom, distTop, distBottom }) {
183
+ let offset = floating.tblpY;
184
+ if (offset === void 0) {
185
+ const contentHeight = Math.max(0, pageHeight - marginTop - marginBottom);
186
+ switch (floating.tblpYSpec) {
187
+ case "top":
188
+ offset = 0;
189
+ break;
190
+ case "center":
191
+ offset = (contentHeight - tableHeight) / 2;
192
+ break;
193
+ case "bottom":
194
+ offset = contentHeight - tableHeight;
195
+ break;
196
+ case "inside":
197
+ case "outside":
198
+ case "inline":
199
+ case void 0: break;
200
+ default: floating.tblpYSpec;
201
+ }
202
+ }
203
+ if (offset === void 0 || floating.vertAnchor === "text") {
204
+ const anchorOffset = offset ?? 0;
205
+ return {
206
+ type: "anchorRelative",
207
+ topOffset: anchorOffset - distTop,
208
+ bottomOffset: anchorOffset + tableHeight + distBottom
209
+ };
210
+ }
211
+ const frameTop = floating.vertAnchor === "page" ? 0 : marginTop;
212
+ return {
213
+ type: "pageRelative",
214
+ pageTop: frameTop + offset - distTop,
215
+ pageBottom: frameTop + offset + tableHeight + distBottom
216
+ };
217
+ }
180
218
  function textBoxWrapSide({ box, contentX, boxWidth, contentWidth }) {
181
219
  if (box.wrapText === "left") return "left";
182
220
  if (box.wrapText === "right") return "right";
@@ -187,6 +225,7 @@ function textBoxWrapSide({ box, contentX, boxWidth, contentWidth }) {
187
225
  }
188
226
  function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry) {
189
227
  const zones = [];
228
+ const tablePageRects = [];
190
229
  const defaultContentWidth = Array.isArray(contentWidth) ? contentWidth[0] ?? 0 : contentWidth;
191
230
  const textBoxGroupAnchors = /* @__PURE__ */ new Map();
192
231
  const paragraphFrameGroupSizes = /* @__PURE__ */ new Map();
@@ -201,11 +240,25 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
201
240
  const marginLeftInput = pageGeometry?.marginLeft ?? 0;
202
241
  const marginRightInput = pageGeometry?.marginRight ?? 0;
203
242
  const marginBottomInput = pageGeometry?.marginBottom ?? 0;
243
+ const contentLeftInput = pageGeometry?.contentLeft ?? marginLeftInput;
244
+ const physicalPageWidthInput = pageGeometry?.physicalPage?.pageWidth ?? pageWidthInput;
245
+ const physicalPageHeightInput = pageGeometry?.physicalPage?.pageHeight ?? pageHeightInput;
246
+ const physicalMarginTopInput = pageGeometry?.physicalPage?.marginTop ?? marginTop;
247
+ const physicalMarginLeftInput = pageGeometry?.physicalPage?.marginLeft ?? marginLeftInput;
248
+ const physicalMarginRightInput = pageGeometry?.physicalPage?.marginRight ?? marginRightInput;
249
+ const physicalMarginBottomInput = pageGeometry?.physicalPage?.marginBottom ?? marginBottomInput;
204
250
  const defaultPageWidth = Array.isArray(pageWidthInput) ? pageWidthInput[0] ?? defaultContentWidth : pageWidthInput;
205
251
  const defaultPageHeight = Array.isArray(pageHeightInput) ? pageHeightInput[0] ?? 0 : pageHeightInput;
206
252
  const defaultMarginLeft = Array.isArray(marginLeftInput) ? marginLeftInput[0] ?? 0 : marginLeftInput;
207
253
  const defaultMarginRight = Array.isArray(marginRightInput) ? marginRightInput[0] ?? 0 : marginRightInput;
208
254
  const defaultMarginBottom = Array.isArray(marginBottomInput) ? marginBottomInput[0] ?? 0 : marginBottomInput;
255
+ const defaultContentLeft = Array.isArray(contentLeftInput) ? contentLeftInput[0] ?? defaultMarginLeft : contentLeftInput;
256
+ const defaultPhysicalPageWidth = Array.isArray(physicalPageWidthInput) ? physicalPageWidthInput[0] ?? defaultPageWidth : physicalPageWidthInput;
257
+ const defaultPhysicalPageHeight = Array.isArray(physicalPageHeightInput) ? physicalPageHeightInput[0] ?? defaultPageHeight : physicalPageHeightInput;
258
+ const defaultPhysicalMarginTop = Array.isArray(physicalMarginTopInput) ? physicalMarginTopInput[0] ?? defaultMarginTop : physicalMarginTopInput;
259
+ const defaultPhysicalMarginLeft = Array.isArray(physicalMarginLeftInput) ? physicalMarginLeftInput[0] ?? defaultMarginLeft : physicalMarginLeftInput;
260
+ const defaultPhysicalMarginRight = Array.isArray(physicalMarginRightInput) ? physicalMarginRightInput[0] ?? defaultMarginRight : physicalMarginRightInput;
261
+ const defaultPhysicalMarginBottom = Array.isArray(physicalMarginBottomInput) ? physicalMarginBottomInput[0] ?? defaultMarginBottom : physicalMarginBottomInput;
209
262
  for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
210
263
  const block = blocks[blockIndex];
211
264
  if (block.kind !== "paragraph") continue;
@@ -281,25 +334,43 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
281
334
  const tableBlock = block;
282
335
  const floating = tableBlock.floating;
283
336
  if (!floating) continue;
284
- const tableMeasure = measureTableBlock(tableBlock, defaultContentWidth);
337
+ const blockPageWidth = perBlockNumberValue(physicalPageWidthInput, blockIndex, defaultPhysicalPageWidth);
338
+ const blockPageHeight = perBlockNumberValue(physicalPageHeightInput, blockIndex, defaultPhysicalPageHeight);
339
+ const blockContentTop = perBlockNumberValue(physicalMarginTopInput, blockIndex, defaultPhysicalMarginTop);
340
+ const blockMarginLeft = perBlockNumberValue(physicalMarginLeftInput, blockIndex, defaultPhysicalMarginLeft);
341
+ const blockMarginRight = perBlockNumberValue(physicalMarginRightInput, blockIndex, defaultPhysicalMarginRight);
342
+ const blockMarginBottom = perBlockNumberValue(physicalMarginBottomInput, blockIndex, defaultPhysicalMarginBottom);
343
+ const blockContentWidth = perBlockNumberValue(contentWidth, blockIndex, defaultContentWidth);
344
+ const blockContentLeft = perBlockNumberValue(contentLeftInput, blockIndex, defaultContentLeft);
345
+ const tableMeasure = measureTableBlock(tableBlock, blockContentWidth);
285
346
  const tableWidth = tableMeasure.totalWidth;
286
347
  const tableHeight = tableMeasure.totalHeight;
287
348
  const distLeft = floating.leftFromText ?? 12;
288
349
  const distRight = floating.rightFromText ?? 12;
289
350
  const distTop = floating.topFromText ?? 0;
290
351
  const distBottom = floating.bottomFromText ?? 0;
291
- let leftMargin = 0;
292
- let rightMargin = 0;
293
- const x = resolveFloatingTableX(floating, tableBlock.justification, tableWidth, defaultContentWidth);
294
- if (x < defaultContentWidth / 2) leftMargin = x + tableWidth + distRight;
295
- else rightMargin = defaultContentWidth - x + distLeft;
296
- const topY = floating.tblpY ?? 0;
297
- const bottomY = topY + tableHeight;
298
- zones.push({
299
- leftMargin,
300
- rightMargin,
301
- topY: topY - distTop,
302
- bottomY: bottomY + distBottom,
352
+ const pageX = resolveFloatingTablePageX({
353
+ anchor: floating,
354
+ justification: tableBlock.justification,
355
+ tableWidth,
356
+ marginWidth: blockPageWidth - blockMarginLeft - blockMarginRight,
357
+ pageWidth: blockPageWidth,
358
+ marginLeft: blockMarginLeft,
359
+ textFrameWidth: blockContentWidth,
360
+ textFrameLeft: blockContentLeft
361
+ });
362
+ tablePageRects.push({
363
+ pageLeft: pageX - distLeft,
364
+ pageRight: pageX + tableWidth + distRight,
365
+ vertical: resolveFloatingTableVerticalPosition({
366
+ floating,
367
+ tableHeight,
368
+ pageHeight: blockPageHeight,
369
+ marginTop: blockContentTop,
370
+ marginBottom: blockMarginBottom,
371
+ distTop,
372
+ distBottom
373
+ }),
303
374
  anchorBlockIndex: blockIndex
304
375
  });
305
376
  }
@@ -393,7 +464,56 @@ function extractFloatingZones(blocks, contentWidth, marginTop = 0, pageGeometry)
393
464
  fullWidthBlock: true
394
465
  });
395
466
  }
396
- return zones;
467
+ return [...zones.map((zone) => ({
468
+ type: "contentZone",
469
+ zone
470
+ })), ...tablePageRects.map((rect) => ({
471
+ type: "tablePageRect",
472
+ rect
473
+ }))];
474
+ }
475
+ function projectFloatingTablePageRect({ rect, contentLeft, contentTop, contentWidth }) {
476
+ const rectLeft = rect.pageLeft - contentLeft;
477
+ const rectRight = rect.pageRight - contentLeft;
478
+ const topY = rect.pageTop - contentTop;
479
+ const bottomY = rect.pageBottom - contentTop;
480
+ if (rectRight <= 0 || rectLeft >= contentWidth) return {
481
+ leftMargin: 0,
482
+ rightMargin: 0,
483
+ topY,
484
+ bottomY
485
+ };
486
+ if (rectLeft < contentWidth / 2) return {
487
+ leftMargin: Math.max(0, Math.min(rectRight, contentWidth)),
488
+ rightMargin: 0,
489
+ topY,
490
+ bottomY
491
+ };
492
+ return {
493
+ leftMargin: 0,
494
+ rightMargin: Math.max(0, Math.min(contentWidth - rectLeft, contentWidth)),
495
+ topY,
496
+ bottomY
497
+ };
498
+ }
499
+ function activateFloatingTablePageRect(template, anchorPageY) {
500
+ switch (template.vertical.type) {
501
+ case "anchorRelative": return {
502
+ pageLeft: template.pageLeft,
503
+ pageRight: template.pageRight,
504
+ pageTop: anchorPageY + template.vertical.topOffset,
505
+ pageBottom: anchorPageY + template.vertical.bottomOffset
506
+ };
507
+ case "pageRelative": return {
508
+ pageLeft: template.pageLeft,
509
+ pageRight: template.pageRight,
510
+ pageTop: template.vertical.pageTop,
511
+ pageBottom: template.vertical.pageBottom
512
+ };
513
+ default:
514
+ template.vertical;
515
+ return template.vertical;
516
+ }
397
517
  }
398
518
  /**
399
519
  * Measure a block based on its type.
@@ -471,16 +591,11 @@ function measureTextBoxBlock(tb, fieldValues) {
471
591
  innerMeasures
472
592
  };
473
593
  }
474
- /**
475
- * `true` for a section break that explicitly starts a new page
476
- * (`nextPage`/`evenPage`/`oddPage`). An absent type follows
477
- * `scheduleSectionBreak` and stays continuous, so measurement must retain any
478
- * active exclusion zones across it. Used by `measureBlocks` to reset the
479
- * running Y for a page-pinned band that lands right after the break.
480
- * eigenpal #694.
481
- */
482
594
  function isNextPageSectionBreak(block) {
483
- return block.kind === "sectionBreak" && block.type !== void 0 && block.type !== "continuous";
595
+ return block.kind === "sectionBreak" && normalizeSectionBreakType(block.type) !== "continuous";
596
+ }
597
+ function isContinuousSectionBreak(block) {
598
+ return block.kind === "sectionBreak" && normalizeSectionBreakType(block.type) === "continuous";
484
599
  }
485
600
  /**
486
601
  * Measure all blocks with floating image support.
@@ -491,7 +606,18 @@ function isNextPageSectionBreak(block) {
491
606
  */
492
607
  function measureBlocks(blocks, contentWidth, marginTop = 0, pageGeometry, fieldValues) {
493
608
  const defaultWidth = Array.isArray(contentWidth) ? contentWidth[0] ?? 0 : contentWidth;
494
- const floatingZonesWithAnchors = extractFloatingZones(blocks, contentWidth, marginTop, pageGeometry);
609
+ const extractedZones = extractFloatingZones(blocks, contentWidth, marginTop, pageGeometry);
610
+ const floatingZonesWithAnchors = [];
611
+ const tablePageRects = [];
612
+ for (const extracted of extractedZones) switch (extracted.type) {
613
+ case "contentZone":
614
+ floatingZonesWithAnchors.push(extracted.zone);
615
+ break;
616
+ case "tablePageRect":
617
+ tablePageRects.push(extracted.rect);
618
+ break;
619
+ default:
620
+ }
495
621
  const marginRelative = floatingZonesWithAnchors.filter((z) => z.isMarginRelative && !z.fullWidthBlock);
496
622
  const ownAnchorZones = floatingZonesWithAnchors.filter((z) => !z.isMarginRelative || z.fullWidthBlock);
497
623
  const marginByTopY = /* @__PURE__ */ new Map();
@@ -515,24 +641,59 @@ function measureBlocks(blocks, contentWidth, marginTop = 0, pageGeometry, fieldV
515
641
  existing.push(zone);
516
642
  zonesByAnchor.set(z.anchorBlockIndex, existing);
517
643
  }
518
- const anchorIndices = new Set(zonesByAnchor.keys());
644
+ const tableRectsByAnchor = /* @__PURE__ */ new Map();
645
+ for (const rect of tablePageRects) {
646
+ const existing = tableRectsByAnchor.get(rect.anchorBlockIndex) ?? [];
647
+ existing.push(rect);
648
+ tableRectsByAnchor.set(rect.anchorBlockIndex, existing);
649
+ }
650
+ const anchorIndices = /* @__PURE__ */ new Set([...zonesByAnchor.keys(), ...tableRectsByAnchor.keys()]);
519
651
  let cumulativeY = 0;
520
652
  let pageRelativeY = 0;
653
+ let columnRegionMaxBottom = 0;
521
654
  let activeZones = [];
655
+ let activeTablePageRects = [];
656
+ const contentLeftInput = pageGeometry?.contentLeft ?? pageGeometry?.marginLeft ?? 0;
657
+ const defaultContentLeft = Array.isArray(contentLeftInput) ? contentLeftInput[0] ?? 0 : contentLeftInput;
658
+ const physicalMarginTopInput = pageGeometry?.physicalPage?.marginTop ?? marginTop;
659
+ const defaultPhysicalMarginTop = Array.isArray(physicalMarginTopInput) ? physicalMarginTopInput[0] ?? 0 : physicalMarginTopInput;
660
+ const columnIndexInput = pageGeometry?.columnIndex ?? 0;
661
+ const columnCountInput = pageGeometry?.columnCount ?? 1;
522
662
  return blocks.map((block, blockIndex) => {
523
663
  recordMeasureBlock(blockIndex, block);
524
- if (block.kind === "pageBreak" || isNextPageSectionBreak(block)) {
664
+ const blockWidth = Array.isArray(contentWidth) ? contentWidth[blockIndex] ?? defaultWidth : contentWidth;
665
+ const blockContentLeft = perBlockNumberValue(contentLeftInput, blockIndex, defaultContentLeft);
666
+ const blockContentTop = perBlockNumberValue(physicalMarginTopInput, blockIndex, defaultPhysicalMarginTop);
667
+ const blockColumnIndex = perBlockNumberValue(columnIndexInput, blockIndex, 0);
668
+ const blockColumnCount = perBlockNumberValue(columnCountInput, blockIndex, 1);
669
+ if (block.kind === "pageBreak" || isNextPageSectionBreak(block) || hasPageBreakBefore(block)) {
525
670
  activeZones = [];
671
+ activeTablePageRects = [];
672
+ cumulativeY = 0;
673
+ pageRelativeY = 0;
674
+ columnRegionMaxBottom = 0;
675
+ } else if (block.kind === "columnBreak") {
676
+ if (blockColumnIndex + 1 >= blockColumnCount) {
677
+ activeZones = [];
678
+ activeTablePageRects = [];
679
+ columnRegionMaxBottom = 0;
680
+ } else columnRegionMaxBottom = Math.max(columnRegionMaxBottom, pageRelativeY);
526
681
  cumulativeY = 0;
527
682
  pageRelativeY = 0;
528
683
  }
529
684
  if (anchorIndices.has(blockIndex)) {
530
685
  activeZones = zonesByAnchor.get(blockIndex) ?? [];
531
- cumulativeY = activeZones.length > 0 && activeZones.every((z) => z.fullWidthBlock) ? pageRelativeY : 0;
686
+ activeTablePageRects = (tableRectsByAnchor.get(blockIndex) ?? []).map((template) => activateFloatingTablePageRect(template, blockContentTop + pageRelativeY));
687
+ cumulativeY = activeTablePageRects.length === 0 && activeZones.length > 0 && activeZones.every((z) => z.fullWidthBlock) || activeTablePageRects.length > 0 ? pageRelativeY : 0;
532
688
  }
533
- const zones = activeZones.length > 0 ? activeZones : void 0;
689
+ const projectedTableZones = activeTablePageRects.map((rect) => projectFloatingTablePageRect({
690
+ rect,
691
+ contentLeft: blockContentLeft,
692
+ contentTop: blockContentTop,
693
+ contentWidth: blockWidth
694
+ }));
695
+ const zones = activeZones.length > 0 || projectedTableZones.length > 0 ? [...activeZones, ...projectedTableZones] : void 0;
534
696
  try {
535
- const blockWidth = Array.isArray(contentWidth) ? contentWidth[blockIndex] ?? defaultWidth : contentWidth;
536
697
  const measure = measureBlock(block, blockWidth, zones, cumulativeY, fieldValues);
537
698
  const clearingZones = measure.kind === "table" ? zones : zones?.filter((zone) => zone.fullWidthBlock);
538
699
  if (clearingZones?.length && (measure.kind === "image" || measure.kind === "table" && !block.floating)) {
@@ -549,6 +710,12 @@ function measureBlocks(blocks, contentWidth, marginTop = 0, pageGeometry, fieldV
549
710
  cumulativeY += measure.totalHeight;
550
711
  pageRelativeY += measure.totalHeight;
551
712
  }
713
+ if (isContinuousSectionBreak(block)) {
714
+ const nextSectionY = Math.max(pageRelativeY, columnRegionMaxBottom);
715
+ cumulativeY = nextSectionY;
716
+ pageRelativeY = nextSectionY;
717
+ columnRegionMaxBottom = 0;
718
+ }
552
719
  return measure;
553
720
  } catch (error) {
554
721
  recordMeasureBlockError(blockIndex, block, error);
@@ -1,6 +1,7 @@
1
+ import { getHorizontalScaleFactor } from "../../utils/horizontalScale.js";
1
2
  import { SCRIPT_CLASS, hasCjk, hasComplexScript, scriptClassOf, segmentByScript } from "../../utils/scriptSegments.js";
2
3
  import { getCachedFontMetrics, getCachedTextWidth, getTextWidthCacheGeneration, setCachedFontMetrics, setCachedTextWidth } from "./cache.js";
3
- import { hasComplexScriptFormatting, resolveComplexScriptFormatting } from "./complexScriptFormatting.js";
4
+ import { applyComplexScriptFormatting, hasComplexScriptFormatting } from "./complexScriptFormatting.js";
4
5
  import { buildFontString, getResolvedData, ptToPx } from "./measureHelpers.js";
5
6
  import { setMeasureProvider } from "./measureProvider.js";
6
7
  import { canPrefetchMeasurement, prefetchMeasurement } from "./measureWorker.js";
@@ -73,7 +74,9 @@ function canvasGetFontMetrics(style) {
73
74
  const bold = style.bold ?? false;
74
75
  const italic = style.italic ?? false;
75
76
  const fontVariant = style.fontVariant;
76
- const cached = getCachedFontMetrics(fontFamily, fontSize, bold, italic, fontVariant);
77
+ const alternateFontFamily = style.alternateFontFamily;
78
+ const fontCacheFamily = alternateFontFamily ? `${fontFamily}\u0000${alternateFontFamily}` : fontFamily;
79
+ const cached = getCachedFontMetrics(fontCacheFamily, fontSize, bold, italic, fontVariant);
77
80
  if (cached !== void 0) return {
78
81
  fontSize,
79
82
  ascent: cached.ascent,
@@ -97,7 +100,7 @@ function canvasGetFontMetrics(style) {
97
100
  }
98
101
  } catch {}
99
102
  lineHeight = Math.max(lineHeight, ascent + descent);
100
- const singleLineRatio = getResolvedData(fontFamily).singleLineRatio;
103
+ const singleLineRatio = getResolvedData(fontFamily, alternateFontFamily).singleLineRatio;
101
104
  const result = {
102
105
  fontSize,
103
106
  ascent,
@@ -106,7 +109,7 @@ function canvasGetFontMetrics(style) {
106
109
  fontFamily,
107
110
  singleLineRatio
108
111
  };
109
- setCachedFontMetrics(fontFamily, fontSize, bold, italic, result, fontVariant);
112
+ setCachedFontMetrics(fontCacheFamily, fontSize, bold, italic, result, fontVariant);
110
113
  return result;
111
114
  }
112
115
  /**
@@ -118,16 +121,13 @@ function canvasGetFontMetrics(style) {
118
121
  */
119
122
  function canvasMeasureTextWidth(text, sourceStyle) {
120
123
  if (!text) return 0;
121
- const style = sourceStyle.forceComplexScript ? {
122
- ...sourceStyle,
123
- ...resolveComplexScriptFormatting(sourceStyle)
124
- } : sourceStyle;
124
+ const style = sourceStyle.forceComplexScript ? applyComplexScriptFormatting(sourceStyle, sourceStyle) : sourceStyle;
125
125
  const measuredText = applyTextTransform(text, style);
126
126
  if (!sourceStyle.forceComplexScript && !style.letterSpacing && needsPerScriptFonts(style, measuredText)) return measureMixedScriptWidth(measuredText, style);
127
127
  const ctx = getCanvasContext();
128
128
  const font = buildFontString(style);
129
129
  const letterSpacing = style.letterSpacing ?? 0;
130
- const horizontalScale = getHorizontalScaleFactor(style);
130
+ const horizontalScale = getHorizontalScaleFactor(style.horizontalScale);
131
131
  const fontKerning = getFontKerningMode(style);
132
132
  const fontCacheKey = style.kerning ? `${font}|kerning:normal|scale:${horizontalScale}` : `${font}|scale:${horizontalScale}`;
133
133
  const cached = getCachedTextWidth(measuredText, fontCacheKey, letterSpacing);
@@ -164,6 +164,7 @@ function canvasMeasureTextWidth(text, sourceStyle) {
164
164
  function glyphAdvanceStyle(style) {
165
165
  const result = {};
166
166
  if (style.fontFamily !== void 0) result.fontFamily = style.fontFamily;
167
+ if (style.alternateFontFamily !== void 0) result.alternateFontFamily = style.alternateFontFamily;
167
168
  if (style.fontSize !== void 0) result.fontSize = style.fontSize;
168
169
  if (style.bold !== void 0) result.bold = style.bold;
169
170
  if (style.italic !== void 0) result.italic = style.italic;
@@ -190,20 +191,20 @@ function needsPerScriptFonts(style, measuredText) {
190
191
  function scriptStyle(style, script) {
191
192
  if (script === SCRIPT_CLASS.eastAsia) {
192
193
  if (style.eastAsiaFontFamily === void 0) return style;
193
- return {
194
+ const result = {
194
195
  ...style,
195
196
  fontFamily: style.eastAsiaFontFamily
196
197
  };
198
+ delete result.alternateFontFamily;
199
+ if (style.eastAsiaAlternateFontFamily !== void 0) result.alternateFontFamily = style.eastAsiaAlternateFontFamily;
200
+ return result;
197
201
  }
198
- if (script === SCRIPT_CLASS.complex) return {
199
- ...style,
200
- ...resolveComplexScriptFormatting(style)
201
- };
202
+ if (script === SCRIPT_CLASS.complex) return applyComplexScriptFormatting(style, style);
202
203
  return style;
203
204
  }
204
205
  function measureMixedScriptWidth(measuredText, style) {
205
206
  const letterSpacing = style.letterSpacing ?? 0;
206
- const horizontalScale = getHorizontalScaleFactor(style);
207
+ const horizontalScale = getHorizontalScaleFactor(style.horizontalScale);
207
208
  let glyphWidth = 0;
208
209
  for (const segment of segmentByScript(measuredText)) glyphWidth += canvasMeasureTextWidth(segment.text, glyphAdvanceStyle(scriptStyle(style, segment.script)));
209
210
  let width = glyphWidth;
@@ -262,10 +263,7 @@ function canvasMeasureText(text, style) {
262
263
  * @returns Run measurement with width and per-character widths
263
264
  */
264
265
  function canvasMeasureRun(text, sourceStyle) {
265
- const style = sourceStyle.forceComplexScript ? {
266
- ...sourceStyle,
267
- ...resolveComplexScriptFormatting(sourceStyle)
268
- } : sourceStyle;
266
+ const style = sourceStyle.forceComplexScript ? applyComplexScriptFormatting(sourceStyle, sourceStyle) : sourceStyle;
269
267
  const metrics = canvasGetFontMetrics(style);
270
268
  if (!text) return {
271
269
  width: 0,
@@ -278,11 +276,12 @@ function canvasMeasureRun(text, sourceStyle) {
278
276
  ctx.fontKerning = style.kerning ? "normal" : "none";
279
277
  const eastAsiaFont = style.eastAsiaFontFamily !== void 0 && !style.letterSpacing ? buildFontString({
280
278
  ...style,
281
- fontFamily: style.eastAsiaFontFamily
279
+ fontFamily: style.eastAsiaFontFamily,
280
+ ...style.eastAsiaAlternateFontFamily !== void 0 ? { alternateFontFamily: style.eastAsiaAlternateFontFamily } : {}
282
281
  }) : void 0;
283
282
  const complexScriptFont = hasComplexScriptFormatting(style) && !style.letterSpacing ? buildFontString(scriptStyle(style, SCRIPT_CLASS.complex)) : void 0;
284
283
  const letterSpacing = style.letterSpacing ?? 0;
285
- const scale = getHorizontalScaleFactor(style);
284
+ const scale = getHorizontalScaleFactor(style.horizontalScale);
286
285
  const charWidths = [];
287
286
  let totalWidth = 0;
288
287
  let offset = 0;
@@ -329,8 +328,5 @@ function applyTextTransform(text, style) {
329
328
  if (style.textTransform === "uppercase") return text.toLocaleUpperCase();
330
329
  return text;
331
330
  }
332
- function getHorizontalScaleFactor(style) {
333
- return (style.horizontalScale ?? 100) / 100;
334
- }
335
331
  //#endregion
336
332
  export { canvasMeasureProvider, getCanvasContext, installCanvasMeasureProvider, resetCanvasContext };
@@ -21,7 +21,7 @@ type ResolvedFontCache = {
21
21
  /**
22
22
  * Get the resolved font data for a font family, with caching.
23
23
  */
24
- declare function getResolvedData(fontFamily: string): ResolvedFontCache;
24
+ declare function getResolvedData(fontFamily: string, alternateFontFamily?: string): ResolvedFontCache;
25
25
  /**
26
26
  * Clear the resolved-font-family cache. `resolveFontFamily`'s output for a
27
27
  * given key can change even when the raw font names don't — e.g. when the
@@ -1,5 +1,6 @@
1
1
  import { resolveFontFamily } from "../../utils/fontResolver.js";
2
2
  import "../../utils/fontWeights.js";
3
+ import { normalizeHorizontalScalePercent } from "../../utils/horizontalScale.js";
3
4
  import { FONT_KERNING_MODE, getRunFontKerningMode } from "./textMeasurementPolicy.js";
4
5
  //#region src/layout-engine/measure/measureHelpers.ts
5
6
  /**
@@ -28,10 +29,14 @@ const DOCX_SCRIPT_FONT_SCALE = .75;
28
29
  function buildRunFontStyle(run, fallbackFontFamily, fallbackFontSize) {
29
30
  const baseFontSize = run.fontSize ?? fallbackFontSize;
30
31
  const fontSize = run.superscript || run.subscript ? baseFontSize * DOCX_SCRIPT_FONT_SCALE : baseFontSize;
32
+ const horizontalScale = normalizeHorizontalScalePercent(run.horizontalScale);
31
33
  return {
32
34
  fontFamily: run.fontFamily ?? fallbackFontFamily,
35
+ ...run.alternateFontFamily !== void 0 ? { alternateFontFamily: run.alternateFontFamily } : {},
33
36
  ...run.eastAsiaFontFamily !== void 0 ? { eastAsiaFontFamily: run.eastAsiaFontFamily } : {},
37
+ ...run.eastAsiaAlternateFontFamily !== void 0 ? { eastAsiaAlternateFontFamily: run.eastAsiaAlternateFontFamily } : {},
34
38
  ...run.complexScriptFontFamily !== void 0 ? { complexScriptFontFamily: run.complexScriptFontFamily } : {},
39
+ ...run.complexScriptAlternateFontFamily !== void 0 ? { complexScriptAlternateFontFamily: run.complexScriptAlternateFontFamily } : {},
35
40
  ...run.complexScriptFontSize !== void 0 ? { complexScriptFontSize: run.complexScriptFontSize } : {},
36
41
  ...run.complexScriptBold !== void 0 ? { complexScriptBold: run.complexScriptBold } : {},
37
42
  ...run.complexScriptItalic !== void 0 ? { complexScriptItalic: run.complexScriptItalic } : {},
@@ -42,7 +47,7 @@ function buildRunFontStyle(run, fallbackFontFamily, fallbackFontSize) {
42
47
  ...run.letterSpacing !== void 0 ? { letterSpacing: run.letterSpacing } : {},
43
48
  ...run.allCaps ? { textTransform: "uppercase" } : {},
44
49
  ...run.smallCaps ? { fontVariant: "small-caps" } : {},
45
- ...run.horizontalScale !== void 0 ? { horizontalScale: run.horizontalScale } : {},
50
+ ...horizontalScale !== void 0 ? { horizontalScale } : {},
46
51
  kerning: getRunFontKerningMode(run, fallbackFontSize) === FONT_KERNING_MODE.enabled
47
52
  };
48
53
  }
@@ -51,23 +56,24 @@ const fontResolvedCache = /* @__PURE__ */ new Map();
51
56
  /**
52
57
  * Get the resolved font data for a font family, with caching.
53
58
  */
54
- function getResolvedData(fontFamily) {
55
- let cached = fontResolvedCache.get(fontFamily);
59
+ function getResolvedData(fontFamily, alternateFontFamily) {
60
+ const cacheKey = `${fontFamily}\u0000${alternateFontFamily ?? ""}`;
61
+ let cached = fontResolvedCache.get(cacheKey);
56
62
  if (cached === void 0) {
57
- const resolved = resolveFontFamily(fontFamily);
63
+ const resolved = resolveFontFamily(fontFamily, alternateFontFamily);
58
64
  cached = {
59
65
  cssFallback: resolved.cssFallback,
60
66
  singleLineRatio: resolved.singleLineRatio
61
67
  };
62
- fontResolvedCache.set(fontFamily, cached);
68
+ fontResolvedCache.set(cacheKey, cached);
63
69
  }
64
70
  return cached;
65
71
  }
66
72
  /**
67
73
  * Get the CSS fallback string for a font family, with caching.
68
74
  */
69
- function getResolvedFallback(fontFamily) {
70
- return getResolvedData(fontFamily).cssFallback;
75
+ function getResolvedFallback(fontFamily, alternateFontFamily) {
76
+ return getResolvedData(fontFamily, alternateFontFamily).cssFallback;
71
77
  }
72
78
  /**
73
79
  * Clear the resolved-font-family cache. `resolveFontFamily`'s output for a
@@ -102,7 +108,7 @@ function buildFontString(style) {
102
108
  const fontSizePx = ptToPx(style.fontSize ?? 11);
103
109
  parts.push(`${fontSizePx}px`);
104
110
  const fontFamily = style.fontFamily ?? "Calibri";
105
- parts.push(getResolvedFallback(fontFamily));
111
+ parts.push(getResolvedFallback(fontFamily, style.alternateFontFamily));
106
112
  return parts.join(" ");
107
113
  }
108
114
  /**