@stll/folio-core 0.23.0 → 0.24.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 (50) hide show
  1. package/dist/controller/fontReadiness.js +12 -7
  2. package/dist/controller/layoutPipeline.js +7 -3
  3. package/dist/docx/blockContentParser.js +2 -47
  4. package/dist/docx/fieldParser.d.ts +1 -1
  5. package/dist/docx/fieldParser.js +12 -4
  6. package/dist/docx/numberingParser.d.ts +3 -11
  7. package/dist/docx/numberingParser.js +26 -158
  8. package/dist/docx/ooxmlCounterFormatter.d.ts +8 -0
  9. package/dist/docx/ooxmlCounterFormatter.js +134 -0
  10. package/dist/docx/paragraphParser.js +3 -4
  11. package/dist/docx/serializer/tableSerializer.js +1 -1
  12. package/dist/docx/tableParser.js +2 -1
  13. package/dist/fields/bookmarkPages.js +1 -1
  14. package/dist/fields/evaluateField.js +1 -1
  15. package/dist/fields/fieldContext.d.ts +2 -0
  16. package/dist/fields/resolveFieldValues.js +5 -2
  17. package/dist/layout-bridge/convert/headerFooterLayout.js +7 -1
  18. package/dist/layout-bridge/convert/toFlowBlocks.js +27 -64
  19. package/dist/layout-engine/index.d.ts +3 -2
  20. package/dist/layout-engine/index.js +40 -16
  21. package/dist/layout-engine/measure/cache.js +5 -1
  22. package/dist/layout-engine/measure/complexScriptFormatting.d.ts +18 -0
  23. package/dist/layout-engine/measure/complexScriptFormatting.js +11 -0
  24. package/dist/layout-engine/measure/listMarkerWidth.d.ts +3 -1
  25. package/dist/layout-engine/measure/listMarkerWidth.js +25 -21
  26. package/dist/layout-engine/measure/measureContainer.js +31 -16
  27. package/dist/layout-engine/measure/measureHelpers.js +4 -0
  28. package/dist/layout-engine/measure/measureParagraph.js +13 -3
  29. package/dist/layout-engine/measure/measureTypes.d.ts +4 -0
  30. package/dist/layout-engine/paginator.d.ts +5 -3
  31. package/dist/layout-engine/paginator.js +34 -13
  32. package/dist/layout-engine/types.d.ts +34 -7
  33. package/dist/layout-painter/index.js +2 -1
  34. package/dist/layout-painter/renderPage.js +9 -3
  35. package/dist/layout-painter/renderParagraph.js +50 -42
  36. package/dist/layout-painter/renderUtils.d.ts +2 -0
  37. package/dist/paged-layout/sectionGeometry.d.ts +3 -2
  38. package/dist/paged-layout/sectionGeometry.js +17 -1
  39. package/dist/prosemirror/attrs/index.js +7 -6
  40. package/dist/prosemirror/conversion/fromProseDoc.js +5 -19
  41. package/dist/prosemirror/conversion/toProseDoc.js +5 -4
  42. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -3
  43. package/dist/prosemirror/extensions/features/ListExtension.js +1 -3
  44. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.d.ts +3 -2
  45. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +31 -17
  46. package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -0
  47. package/dist/prosemirror/schema/marks.d.ts +8 -0
  48. package/dist/prosemirror/schema/nodes.d.ts +5 -7
  49. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -3
  50. package/package.json +2 -2
@@ -13,7 +13,7 @@ function buildBookmarkPageMap(pages, blocks) {
13
13
  if (anchors.length === 0) return pageByBookmark;
14
14
  for (const page of pages) for (const fragment of page.fragments) for (const anchor of anchors) {
15
15
  if (!fragmentContainsAnchor(fragment, anchor)) continue;
16
- assignBookmarkPage(pageByBookmark, anchor.names, page.number);
16
+ assignBookmarkPage(pageByBookmark, anchor.names, page.logicalNumber);
17
17
  }
18
18
  return pageByBookmark;
19
19
  }
@@ -38,7 +38,7 @@ function evaluateField(parsed, ctx, options = {}) {
38
38
  const fallback = options.fallback ?? "";
39
39
  if (options.locked) return fallback;
40
40
  switch (parsed.type) {
41
- case "PAGE": return computePageNumber(ctx.pageNumber, parsed);
41
+ case "PAGE": return computePageNumber(ctx.pageNumber, parsed, ctx.pageNumberFormat);
42
42
  case "NUMPAGES": return computePageNumber(ctx.totalPages, parsed);
43
43
  case "SECTIONPAGES": return ctx.sectionPages === void 0 ? fallback : computePageNumber(ctx.sectionPages, parsed);
44
44
  case "TIME": {
@@ -9,6 +9,8 @@
9
9
  type FieldContext = {
10
10
  /** 1-indexed page the field is on (PAGE). */
11
11
  pageNumber: number;
12
+ /** OOXML section-level format used when PAGE has no explicit format switch. */
13
+ pageNumberFormat?: string;
12
14
  /** Total pages in the document (NUMPAGES). */
13
15
  totalPages: number;
14
16
  /** Pages in the field's current section (SECTIONPAGES); omit until the
@@ -22,6 +22,7 @@ function resolveFieldValues(blocks, pages, shared) {
22
22
  const sectionPages = location ? shared.sectionPageCounts.get(location.sectionIndex) : void 0;
23
23
  const context = {
24
24
  pageNumber: location?.page ?? 1,
25
+ ...location?.pageFormat === void 0 ? {} : { pageNumberFormat: location.pageFormat },
25
26
  totalPages: shared.totalPages,
26
27
  bookmarkPages: shared.bookmarkPages,
27
28
  bookmarkText: shared.bookmarkText,
@@ -131,7 +132,8 @@ function fieldValuesEqual(a, b) {
131
132
  function buildBlockPageMap(pages) {
132
133
  const map = /* @__PURE__ */ new Map();
133
134
  for (const page of pages) for (const fragment of page.fragments) if (!map.has(fragment.blockId)) map.set(fragment.blockId, {
134
- page: page.number,
135
+ page: page.logicalNumber,
136
+ ...page.logicalNumberFormat === void 0 ? {} : { pageFormat: page.logicalNumberFormat },
135
137
  sectionIndex: page.sectionIndex ?? 0
136
138
  });
137
139
  return map;
@@ -143,7 +145,8 @@ function buildTableRowPageMap(pages) {
143
145
  for (let rowIndex = fragment.fromRow; rowIndex < fragment.toRow; rowIndex++) {
144
146
  const key = tableRowKey(fragment.blockId, rowIndex);
145
147
  if (!map.has(key)) map.set(key, {
146
- page: page.number,
148
+ page: page.logicalNumber,
149
+ ...page.logicalNumberFormat === void 0 ? {} : { pageFormat: page.logicalNumberFormat },
147
150
  sectionIndex: page.sectionIndex ?? 0
148
151
  });
149
152
  }
@@ -442,7 +442,7 @@ function serializeParagraphAttrs(attrs) {
442
442
  "listMarker",
443
443
  "listIsBullet",
444
444
  "listMarkerHidden",
445
- "listMarkerBold",
445
+ "listMarkerFormatting",
446
446
  "listMarkerAlignment",
447
447
  "listMarkerSuffix",
448
448
  "tabs"
@@ -470,6 +470,12 @@ function serializeRunFmt(run) {
470
470
  "highlightColor",
471
471
  "fontSize",
472
472
  "fontFamily",
473
+ "eastAsiaFontFamily",
474
+ "complexScriptFontFamily",
475
+ "complexScriptFontSize",
476
+ "complexScriptBold",
477
+ "complexScriptItalic",
478
+ "forceComplexScript",
473
479
  "verticalAlign",
474
480
  "letterSpacing",
475
481
  "smallCaps",
@@ -1,13 +1,14 @@
1
1
  import { convertBulletToUnicode } from "../../docx/bulletMarkers.js";
2
2
  import { resolveDocumentGridLinePitch } from "../../docx/documentGrid.js";
3
- import { padDecimal } from "../../docx/numberingParser.js";
3
+ import { formatOoxmlCounter } from "../../docx/ooxmlCounterFormatter.js";
4
4
  import { setParagraphFrame } from "../../layout-engine/paragraphFrame.js";
5
5
  import { setTextBoxGroupId } from "../../layout-engine/textBoxGroup.js";
6
6
  import { DEFAULT_TEXTBOX_MARGINS } from "../../layout-engine/types.js";
7
+ import { getPageNumbering } from "../../paged-layout/sectionGeometry.js";
7
8
  import { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHardBreakAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectLanguageMarkAttrs, expectMathAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs, expectRunShadingMarkAttrs, expectSymbolAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs } from "../../prosemirror/attrs/index.js";
8
9
  import { autospacingMatchesBase } from "../../prosemirror/autospacingBase.js";
9
10
  import { runShadingAttrsToShading } from "../../prosemirror/conversion/runShadingMark.js";
10
- import { directionIsRtl } from "../../prosemirror/paragraphDirection.js";
11
+ import { directionToBidi } from "../../prosemirror/paragraphDirection.js";
11
12
  import { assertValidProseMirrorDocument } from "../../prosemirror/validation.js";
12
13
  import { resolveColor, resolveHighlightToCss } from "../../utils/colorResolver.js";
13
14
  import { resolveThemeFont } from "../../utils/fontResolver.js";
@@ -74,51 +75,8 @@ function formatNumberedMarker(counters, level) {
74
75
  if (parts.length === 0) return "1.";
75
76
  return `${parts.join(".")}.`;
76
77
  }
77
- const ROMAN_PAIRS = [
78
- [1e3, "M"],
79
- [900, "CM"],
80
- [500, "D"],
81
- [400, "CD"],
82
- [100, "C"],
83
- [90, "XC"],
84
- [50, "L"],
85
- [40, "XL"],
86
- [10, "X"],
87
- [9, "IX"],
88
- [5, "V"],
89
- [4, "IV"],
90
- [1, "I"]
91
- ];
92
- function toRoman(value, upper) {
93
- if (value <= 0) return "";
94
- let remaining = value;
95
- let output = "";
96
- for (const [number, symbol] of ROMAN_PAIRS) while (remaining >= number) {
97
- output += symbol;
98
- remaining -= number;
99
- }
100
- return upper ? output : output.toLowerCase();
101
- }
102
- function toLetter(value, upper) {
103
- if (value <= 0) return "";
104
- const zeroBased = value - 1;
105
- const baseCodePoint = upper ? 65 : 97;
106
- return String.fromCodePoint(baseCodePoint + zeroBased % 26).repeat(Math.floor(zeroBased / 26) + 1);
107
- }
108
78
  function formatCounter(value, format) {
109
- if (!Number.isFinite(value)) return "";
110
- switch (format) {
111
- case "upperRoman": return toRoman(value, true);
112
- case "lowerRoman": return toRoman(value, false);
113
- case "upperLetter": return toLetter(value, true);
114
- case "lowerLetter": return toLetter(value, false);
115
- case "decimalZero": return padDecimal(value, 2);
116
- case "decimalZero3": return padDecimal(value, 3);
117
- case "decimalZero4": return padDecimal(value, 4);
118
- case "decimalZero5": return padDecimal(value, 5);
119
- case "none": return "";
120
- default: return String(value);
121
- }
79
+ return formatOoxmlCounter(value, format);
122
80
  }
123
81
  function resolveListTemplate(template, counters, levelFormats, forceDecimal = false) {
124
82
  return template.replace(/%(?<digit>\d)(?<punct>[.):\]])?/gu, (...args) => {
@@ -399,9 +357,12 @@ function applyRunFormattingOverrides(formatting, attrs) {
399
357
  if (attrs.shadow === false) formatting.textShadow = false;
400
358
  if (attrs.outline === false) formatting.textOutline = false;
401
359
  if (attrs.rtl === false) formatting.rtl = false;
360
+ if (attrs.boldCs !== void 0) formatting.complexScriptBold = attrs.boldCs;
361
+ if (attrs.italicCs !== void 0) formatting.complexScriptItalic = attrs.italicCs;
362
+ if (attrs.fontSizeCs !== void 0) formatting.complexScriptFontSize = attrs.fontSizeCs / 2;
363
+ if (attrs.cs !== void 0) formatting.forceComplexScript = attrs.cs;
402
364
  }
403
- function paragraphRunDefaults(pmAttrs, theme) {
404
- const defaultTextFormatting = pmAttrs.defaultTextFormatting;
365
+ function textFormattingToRunFormatting(defaultTextFormatting, theme) {
405
366
  if (!defaultTextFormatting) return {};
406
367
  const result = {};
407
368
  const fontFamily = defaultTextFormatting.fontFamily ? resolveWesternThemeFont(defaultTextFormatting.fontFamily, theme) : void 0;
@@ -412,8 +373,13 @@ function paragraphRunDefaults(pmAttrs, theme) {
412
373
  if (complexScriptFontFamily) result.complexScriptFontFamily = complexScriptFontFamily;
413
374
  if (defaultTextFormatting.language) result.language = { ...defaultTextFormatting.language };
414
375
  if (defaultTextFormatting.fontSize !== void 0) result.fontSize = defaultTextFormatting.fontSize / 2;
376
+ if (defaultTextFormatting.fontSizeCs !== void 0) result.complexScriptFontSize = defaultTextFormatting.fontSizeCs / 2;
415
377
  if (defaultTextFormatting.bold !== void 0) result.bold = defaultTextFormatting.bold;
378
+ if (defaultTextFormatting.boldCs !== void 0) result.complexScriptBold = defaultTextFormatting.boldCs;
416
379
  if (defaultTextFormatting.italic !== void 0) result.italic = defaultTextFormatting.italic;
380
+ if (defaultTextFormatting.italicCs !== void 0) result.complexScriptItalic = defaultTextFormatting.italicCs;
381
+ if (defaultTextFormatting.rtl !== void 0) result.rtl = defaultTextFormatting.rtl;
382
+ if (defaultTextFormatting.cs !== void 0) result.forceComplexScript = defaultTextFormatting.cs;
417
383
  if (defaultTextFormatting.underline && defaultTextFormatting.underline.style !== "none") {
418
384
  result.underline = { style: defaultTextFormatting.underline.style };
419
385
  if (defaultTextFormatting.underline.color) result.underline.color = resolveColor(defaultTextFormatting.underline.color, theme);
@@ -442,6 +408,9 @@ function paragraphRunDefaults(pmAttrs, theme) {
442
408
  if (defaultTextFormatting.emphasisMark && defaultTextFormatting.emphasisMark !== "none") result.emphasisMark = defaultTextFormatting.emphasisMark;
443
409
  return result;
444
410
  }
411
+ function paragraphRunDefaults(pmAttrs, theme) {
412
+ return textFormattingToRunFormatting(pmAttrs.defaultTextFormatting, theme);
413
+ }
445
414
  /**
446
415
  * Build an ImageRun from ProseMirror node attrs, applying conditional property assignment
447
416
  * to satisfy exactOptionalPropertyTypes.
@@ -687,12 +656,8 @@ function toPreviousListAttrs(previousFormatting) {
687
656
  if (listStartOverride !== void 0) attrs.listStartOverride = listStartOverride;
688
657
  const listMarkerHidden = previousFormatting.listMarkerHidden;
689
658
  if (listMarkerHidden !== void 0) attrs.listMarkerHidden = listMarkerHidden;
690
- const listMarkerFontFamily = previousFormatting.listMarkerFontFamily;
691
- if (listMarkerFontFamily !== void 0) attrs.listMarkerFontFamily = listMarkerFontFamily;
692
- const listMarkerFontSize = previousFormatting.listMarkerFontSize;
693
- if (listMarkerFontSize !== void 0) attrs.listMarkerFontSize = listMarkerFontSize;
694
- const listMarkerBold = previousFormatting.listMarkerBold;
695
- if (listMarkerBold !== void 0) attrs.listMarkerBold = listMarkerBold;
659
+ const listMarkerFormatting = previousFormatting.listMarkerFormatting;
660
+ if (listMarkerFormatting !== void 0) attrs.listMarkerFormatting = listMarkerFormatting;
696
661
  const listMarkerAlignment = previousFormatting.listMarkerAlignment;
697
662
  if (listMarkerAlignment !== void 0) attrs.listMarkerAlignment = listMarkerAlignment;
698
663
  const listMarkerSuffix = previousFormatting.listMarkerSuffix;
@@ -708,7 +673,7 @@ function resolveDeletedListMarker(previousListAttrs, listCounters, listAbstractC
708
673
  if (previousListAttrs.listIsBullet) return "•";
709
674
  return null;
710
675
  }
711
- function applyDeletedListMarkerAttrs(attrs, change, listCounters, listAbstractCounters, listSeenNumIds) {
676
+ function applyDeletedListMarkerAttrs(attrs, change, listCounters, listAbstractCounters, listSeenNumIds, theme) {
712
677
  const previousListAttrs = toPreviousListAttrs(change.previousFormatting);
713
678
  const marker = resolveDeletedListMarker(previousListAttrs, listCounters, listAbstractCounters, listSeenNumIds);
714
679
  if (!marker) return;
@@ -716,9 +681,7 @@ function applyDeletedListMarkerAttrs(attrs, change, listCounters, listAbstractCo
716
681
  attrs.listMarkerRevision = toListMarkerRevision("del", change.info);
717
682
  if (previousListAttrs.listIsBullet !== void 0) attrs.listIsBullet = previousListAttrs.listIsBullet;
718
683
  if (previousListAttrs.listMarkerHidden !== void 0) attrs.listMarkerHidden = previousListAttrs.listMarkerHidden;
719
- if (previousListAttrs.listMarkerFontFamily) attrs.listMarkerFontFamily = previousListAttrs.listMarkerFontFamily;
720
- if (previousListAttrs.listMarkerFontSize) attrs.listMarkerFontSize = previousListAttrs.listMarkerFontSize;
721
- if (previousListAttrs.listMarkerBold !== void 0) attrs.listMarkerBold = previousListAttrs.listMarkerBold;
684
+ if (previousListAttrs.listMarkerFormatting) attrs.listMarkerFormatting = textFormattingToRunFormatting(previousListAttrs.listMarkerFormatting, theme);
722
685
  if (previousListAttrs.listMarkerAlignment) attrs.listMarkerAlignment = previousListAttrs.listMarkerAlignment;
723
686
  if (previousListAttrs.listMarkerSuffix) attrs.listMarkerSuffix = previousListAttrs.listMarkerSuffix;
724
687
  }
@@ -821,7 +784,8 @@ function convertParagraphAttrs(pmAttrs, theme, listCounters, listAbstractCounter
821
784
  if (pmAttrs.widowControl === false) attrs.widowControl = false;
822
785
  if (pmAttrs.contextualSpacing) attrs.contextualSpacing = true;
823
786
  if (pmAttrs.runInWithNext) attrs.runInWithNext = true;
824
- if (directionIsRtl(pmAttrs.direction)) attrs.bidi = true;
787
+ const bidi = directionToBidi(pmAttrs.direction);
788
+ if (bidi !== void 0) attrs.bidi = bidi;
825
789
  if (pmAttrs.styleId) attrs.styleId = pmAttrs.styleId;
826
790
  const propertyChanges = pmAttrs._propertyChanges ?? [];
827
791
  let changedNumberingChange;
@@ -853,15 +817,13 @@ function convertParagraphAttrs(pmAttrs, theme, listCounters, listAbstractCounter
853
817
  else if (pmAttrs.listMarker) attrs.listMarker = pmAttrs.listIsBullet ? convertBulletToUnicode(pmAttrs.listMarker) : pmAttrs.listMarker;
854
818
  if (pmAttrs.listIsBullet !== void 0) attrs.listIsBullet = pmAttrs.listIsBullet;
855
819
  if (pmAttrs.listMarkerHidden) attrs.listMarkerHidden = true;
856
- if (pmAttrs.listMarkerFontFamily) attrs.listMarkerFontFamily = pmAttrs.listMarkerFontFamily;
857
- if (pmAttrs.listMarkerFontSize) attrs.listMarkerFontSize = pmAttrs.listMarkerFontSize;
858
- if (pmAttrs.listMarkerBold !== null && pmAttrs.listMarkerBold !== void 0) attrs.listMarkerBold = pmAttrs.listMarkerBold;
820
+ if (pmAttrs.listMarkerFormatting) attrs.listMarkerFormatting = textFormattingToRunFormatting(pmAttrs.listMarkerFormatting, theme);
859
821
  if (pmAttrs.listMarkerAlignment) attrs.listMarkerAlignment = pmAttrs.listMarkerAlignment;
860
822
  if (pmAttrs.listMarkerSuffix) attrs.listMarkerSuffix = pmAttrs.listMarkerSuffix;
861
823
  if (pmAttrs.listMarkerSecondSlotOffsetTwips !== void 0) attrs.listMarkerSecondSlotOffsetTwips = pmAttrs.listMarkerSecondSlotOffsetTwips;
862
824
  if (!pmAttrs.numPr) {
863
825
  const numberingRemovedChange = propertyChanges.find(isRemovedNumberingChange);
864
- if (numberingRemovedChange) applyDeletedListMarkerAttrs(attrs, numberingRemovedChange, originalListCounters, originalListAbstractCounters, originalListSeenNumIds);
826
+ if (numberingRemovedChange) applyDeletedListMarkerAttrs(attrs, numberingRemovedChange, originalListCounters, originalListAbstractCounters, originalListSeenNumIds, theme);
865
827
  }
866
828
  if (defaultTabStopTwips !== void 0) attrs.defaultTabStopTwips = defaultTabStopTwips;
867
829
  const dtf = pmAttrs.defaultTextFormatting;
@@ -1187,7 +1149,7 @@ function convertTable(node, startPos, options) {
1187
1149
  if (justification) tableBlock.justification = justification;
1188
1150
  if (indentPx !== void 0) tableBlock.indent = indentPx;
1189
1151
  if (floatingPx) tableBlock.floating = floatingPx;
1190
- if (originalFormatting?.bidi) tableBlock.bidi = true;
1152
+ if (attrs._resolvedBidi ?? originalFormatting?.bidi) tableBlock.bidi = true;
1191
1153
  return tableBlock;
1192
1154
  }
1193
1155
  /**
@@ -1462,6 +1424,7 @@ function toFlowBlocks(doc, options = {}) {
1462
1424
  const breakType = secProps?.sectionStart ?? pmAttrs.sectionBreakType;
1463
1425
  if (breakType) sectionBreak.type = breakType;
1464
1426
  if (secProps) {
1427
+ sectionBreak.pageNumbering = getPageNumbering(secProps);
1465
1428
  const documentGridLinePitchTwips = resolveDocumentGridLinePitch(secProps.docGrid);
1466
1429
  if (documentGridLinePitchTwips !== void 0) sectionBreak.documentGridLinePitchTwips = documentGridLinePitchTwips;
1467
1430
  if (secProps.pageWidth || secProps.pageHeight) sectionBreak.pageSize = {
@@ -1,4 +1,4 @@
1
- import { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned } from "./types.js";
1
+ import { BlockId, BorderStyle, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionPageNumbering, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned } from "./types.js";
2
2
  import { PageState, Paginator, PaginatorOptions, createPaginator } from "./paginator.js";
3
3
  import { KeepNextChain, calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
4
4
  import { resolveSectionHeaderFooterRefs } from "./headerFooterRefs.js";
@@ -11,6 +11,7 @@ type SectionLayoutConfig = {
11
11
  h: number;
12
12
  };
13
13
  margins: PageMargins;
14
+ pageNumbering: SectionPageNumbering;
14
15
  columns?: ColumnLayout;
15
16
  };
16
17
  declare function collectSectionConfigs(blocks: FlowBlock[], initialConfig: SectionLayoutConfig, finalConfig: SectionLayoutConfig): {
@@ -43,4 +44,4 @@ declare function layoutDocument(blocks: FlowBlock[], measures: Measure[], option
43
44
  */
44
45
  declare function getHeaderRowsHeight(measure: TableMeasure, headerRowCount: number): number;
45
46
  //#endregion
46
- export { BlockId, BorderStyle, type BreakDecision, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, type KeepNextChain, Layout, LayoutOptions, LineBreakRun, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, type PageState, type Paginator, type PaginatorOptions, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionLayoutConfig, type SectionState, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, scheduleSectionBreak, tableColumnsArePinned };
47
+ export { BlockId, BorderStyle, type BreakDecision, CellBorderSpec, CellBorders, ColumnBreakBlock, ColumnBreakMeasure, ColumnLayout, DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, DocumentPosition, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, FieldRun, FloatingTablePosition, FlowBlock, FootnoteContent, Fragment, FragmentBase, HeaderFooterContent, HeaderFooterContentHeights, HeaderFooterLayout, HitTestResult, HyperlinkInfo, ImageBlock, ImageFragment, ImageMeasure, ImageRun, ImageRunPosition, type KeepNextChain, Layout, LayoutOptions, LineBreakRun, ListMarkerFormatting, ListNumPr, MathRun, Measure, MeasuredLine, Page, PageBreakBlock, PageBreakMeasure, PageHeaderFooterRefs, PageMargins, type PageState, type Paginator, type PaginatorOptions, ParagraphAttrs, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphIndent, ParagraphMeasure, ParagraphSpacing, RenderedPageBreakRun, Run, RunFormatting, SdtGroup, SectionBreakBlock, SectionBreakMeasure, SectionLayoutConfig, SectionPageNumbering, type SectionState, TabAlignment, TabRun, TabStop, TableBlock, TableCell, TableCellMeasure, TableFragment, TableMeasure, TableRow, TableRowMeasure, TextBoxBlock, TextBoxFlowAttrs, TextBoxFragment, TextBoxMeasure, TextRun, applyContextualSpacing, applyPendingToActive, assertExhaustiveFlowBlock, calculateChainHeight, collectSectionConfigs, computeKeepNextChains, createInitialSectionState, createPaginator, findPageIndexContainingPmPos, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, getHeaderRowsHeight, getMidChainIndices, getTableRowLeadingWidth, hasKeepLines, hasPageBreakBefore, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, layoutDocument, resolveSectionHeaderFooterRefs, scheduleSectionBreak, tableColumnsArePinned };
@@ -5,7 +5,7 @@ import { measuredLineAdvance } from "./lineFlow.js";
5
5
  import { resolveFloatingTableX } from "./measure/floatingTablePosition.js";
6
6
  import { createPaginator } from "./paginator.js";
7
7
  import { getParagraphFragmentPmRange } from "./paragraphFragmentRange.js";
8
- import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, paragraphsShareStyle, resolveEffectiveParagraphSpacingTree } from "./paragraphSpacing.js";
8
+ import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph, paragraphsShareStyle, resolveEffectiveParagraphSpacingTree } from "./paragraphSpacing.js";
9
9
  import { assertExhaustiveFlowBlock, findPageIndexContainingPmPos } from "./pmPageIndex.js";
10
10
  import { INITIAL_RENDERED_BREAK_STATE, reconcileAfterBlock, reconcileBreakBeforeBlock, recordReflowBoundary } from "./renderedBreakReconciliation.js";
11
11
  import { applyPendingToActive, createInitialSectionState, getEffectiveColumns, getEffectiveMargins, getEffectivePageSize, scheduleSectionBreak } from "./section-breaks.js";
@@ -25,6 +25,7 @@ const DEFAULT_COLUMNS = {
25
25
  gap: 0
26
26
  };
27
27
  const DEFAULT_SECTION_BREAK_TYPE = "nextPage";
28
+ const CONTINUE_PAGE_NUMBERING = { type: "continue" };
28
29
  function collectSectionConfigs(blocks, initialConfig, finalConfig) {
29
30
  const configs = [];
30
31
  const breakIndices = [];
@@ -35,7 +36,8 @@ function collectSectionConfigs(blocks, initialConfig, finalConfig) {
35
36
  const sectionBreak = block;
36
37
  const config = {
37
38
  pageSize: sectionBreak.pageSize ?? previousConfig.pageSize,
38
- margins: sectionBreak.margins ?? previousConfig.margins
39
+ margins: sectionBreak.margins ?? previousConfig.margins,
40
+ pageNumbering: sectionBreak.pageNumbering ?? CONTINUE_PAGE_NUMBERING
39
41
  };
40
42
  if (sectionBreak.columns !== void 0) config.columns = sectionBreak.columns;
41
43
  configs.push(config);
@@ -160,12 +162,14 @@ function layoutDocument(blocks, measures, options) {
160
162
  if (pageSize.w - margins.left - margins.right <= 0) panic("layoutDocument: page size and margins yield no content area");
161
163
  const bodyConfig = {
162
164
  pageSize,
163
- margins
165
+ margins,
166
+ pageNumbering: options.pageNumbering ?? CONTINUE_PAGE_NUMBERING
164
167
  };
165
168
  if (options.columns !== void 0) bodyConfig.columns = options.columns;
166
169
  const finalConfig = {
167
170
  pageSize: finalPageSize,
168
- margins: finalMargins
171
+ margins: finalMargins,
172
+ pageNumbering: options.finalPageNumbering ?? bodyConfig.pageNumbering
169
173
  };
170
174
  const finalColumns = options.finalColumns ?? options.columns;
171
175
  if (finalColumns !== void 0) finalConfig.columns = finalColumns;
@@ -179,6 +183,7 @@ function layoutDocument(blocks, measures, options) {
179
183
  ...options.firstPageMargins !== void 0 ? { firstPageMargins: options.firstPageMargins } : {},
180
184
  ...options.sectionEvenPageMargins !== void 0 ? { sectionEvenPageMargins: options.sectionEvenPageMargins } : {},
181
185
  columns: initialConfig.columns ?? DEFAULT_COLUMNS,
186
+ pageNumbering: initialConfig.pageNumbering,
182
187
  ...options.footnoteReservedHeights !== void 0 ? { footnoteReservedHeights: options.footnoteReservedHeights } : {},
183
188
  ...options.sectionHeaderFooterRefs !== void 0 ? { sectionHeaderFooterRefs: options.sectionHeaderFooterRefs } : {}
184
189
  });
@@ -474,10 +479,27 @@ function getHeaderRowsHeight(measure, headerRowCount) {
474
479
  for (let i = 0; i < headerRowCount && i < measure.rows.length; i++) height += measure.rows[i].height;
475
480
  return height;
476
481
  }
477
- const tableRowStartsWithRenderedPageBreak = (block, rowIndex) => block.rows[rowIndex]?.cells.some((cell) => {
478
- const firstBlock = cell.blocks.at(0);
479
- return firstBlock?.kind === "paragraph" && firstBlock.attrs?.renderedPageBreakBefore === true;
480
- }) ?? false;
482
+ const tableRowStartsWithRenderedPageBreak = (block, rowIndex) => {
483
+ const visibleCells = block.rows[rowIndex]?.cells.filter((cell) => cell.blocks.some((cellBlock) => cellBlock.kind !== "paragraph" || !isEmptyParagraph(cellBlock)));
484
+ if (!visibleCells || visibleCells.length === 0) return false;
485
+ return visibleCells.every((cell) => {
486
+ const firstVisibleBlock = cell.blocks.find((cellBlock) => cellBlock.kind !== "paragraph" || !isEmptyParagraph(cellBlock));
487
+ return firstVisibleBlock?.kind === "paragraph" && firstVisibleBlock.attrs?.renderedPageBreakBefore === true;
488
+ });
489
+ };
490
+ const getVerticallyMergedRows = (block) => {
491
+ const mergedRows = /* @__PURE__ */ new Set();
492
+ for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex += 1) {
493
+ const row = block.rows[rowIndex];
494
+ if (!row) continue;
495
+ for (const cell of row.cells) {
496
+ const rowSpan = cell.rowSpan ?? 1;
497
+ if (rowSpan <= 1) continue;
498
+ for (let mergedRowIndex = rowIndex; mergedRowIndex < Math.min(block.rows.length, rowIndex + rowSpan); mergedRowIndex += 1) mergedRows.add(mergedRowIndex);
499
+ }
500
+ }
501
+ return mergedRows;
502
+ };
481
503
  const flowBlockHasTrackedChanges = (block) => {
482
504
  if (block.kind === "paragraph") return block.runs.some((run) => {
483
505
  if (run.kind === "lineBreak") return false;
@@ -499,7 +521,7 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
499
521
  const headerRowsHeight = getHeaderRowsHeight(measure, headerRowCount);
500
522
  let currentRowIndex = 0;
501
523
  const breakInfo = buildTableRowBreakInfo(block, measure);
502
- const hasVerticalMerges = block.rows.some((row) => row.cells.some((cell) => (cell.rowSpan ?? 1) > 1));
524
+ const verticallyMergedRows = getVerticallyMergedRows(block);
503
525
  const computeTableX = (columnIndex) => {
504
526
  let x = paginator.getColumnX(columnIndex);
505
527
  if (block.justification === "center") x += (paginator.columnWidth - measure.totalWidth) / 2;
@@ -520,7 +542,7 @@ function layoutTable(block, measure, paginator, footnoteHeightById) {
520
542
  const canSplitRow = (rowIndex, state = paginator.getCurrentState()) => {
521
543
  const row = rows[rowIndex];
522
544
  const sourceRow = block.rows[rowIndex];
523
- if (!row || !sourceRow || sourceRow.cantSplit || sourceRow.isHeader || hasVerticalMerges) return false;
545
+ if (!row || !sourceRow || sourceRow.cantSplit || sourceRow.isHeader || verticallyMergedRows.has(rowIndex)) return false;
524
546
  if ((breakInfo.breakOffsets[rowIndex]?.length ?? 0) <= 1) return false;
525
547
  const freshHeaderOverhead = headerRowCount > 0 && rowIndex >= headerRowCount ? headerRowsHeight : 0;
526
548
  const oversized = row.height + freshHeaderOverhead > getCurrentRowCapacity(state);
@@ -881,24 +903,26 @@ function handleSectionBreak(_block, paginator, nextSectionConfig, nextSectionTyp
881
903
  switch (nextSectionType) {
882
904
  case "nextPage":
883
905
  paginator.updatePageLayout(nextSectionConfig.pageSize, nextSectionConfig.margins);
884
- if (nextSectionIndex !== void 0) paginator.startSection(nextSectionIndex);
906
+ if (nextSectionIndex !== void 0) paginator.startSection(nextSectionIndex, nextSectionConfig.pageNumbering);
885
907
  paginator.forcePageBreak({ coalesceBlankPage: true });
886
908
  break;
887
909
  case "evenPage":
888
- paginator.updatePageLayout(nextSectionConfig.pageSize, nextSectionConfig.margins);
889
- if (nextSectionIndex !== void 0) paginator.startSection(nextSectionIndex);
890
910
  if (paginator.forcePageBreak({ coalesceBlankPage: true }).page.number % 2 !== 0) paginator.forcePageBreak();
911
+ paginator.updatePageLayout(nextSectionConfig.pageSize, nextSectionConfig.margins);
912
+ if (nextSectionIndex !== void 0) paginator.startSection(nextSectionIndex, nextSectionConfig.pageNumbering);
913
+ if (!paginator.retargetCurrentBlankPage()) panic("Even-page section target must be blank");
891
914
  break;
892
915
  case "oddPage":
893
- paginator.updatePageLayout(nextSectionConfig.pageSize, nextSectionConfig.margins);
894
- if (nextSectionIndex !== void 0) paginator.startSection(nextSectionIndex);
895
916
  if (paginator.forcePageBreak({ coalesceBlankPage: true }).page.number % 2 === 0) paginator.forcePageBreak();
917
+ paginator.updatePageLayout(nextSectionConfig.pageSize, nextSectionConfig.margins);
918
+ if (nextSectionIndex !== void 0) paginator.startSection(nextSectionIndex, nextSectionConfig.pageNumbering);
919
+ if (!paginator.retargetCurrentBlankPage()) panic("Odd-page section target must be blank");
896
920
  break;
897
921
  case "continuous": {
898
922
  const currentPage = paginator.pages.at(-1);
899
923
  const nextSize = nextSectionConfig.pageSize;
900
924
  const pageSizeChanges = currentPage != null && (Math.round(nextSize.w) !== Math.round(currentPage.size.w) || Math.round(nextSize.h) !== Math.round(currentPage.size.h));
901
- if (nextSectionIndex !== void 0) paginator.startSection(nextSectionIndex);
925
+ if (nextSectionIndex !== void 0) paginator.startSection(nextSectionIndex, nextSectionConfig.pageNumbering);
902
926
  if (pageSizeChanges) {
903
927
  paginator.updatePageLayout(nextSize, nextSectionConfig.margins);
904
928
  if (!paginator.retargetCurrentBlankPage()) paginator.forcePageBreak({ coalesceBlankPage: true });
@@ -170,7 +170,7 @@ const paragraphMeasureCache = /* @__PURE__ */ new Map();
170
170
  */
171
171
  function hashParagraphBlock(block) {
172
172
  const parts = [`lbp:${getLineBreakProviderGeneration()}`];
173
- for (const run of block.runs) if (run.kind === "text") parts.push(`t:${run.text}|${run.fontFamily}|${run.eastAsiaFontFamily}|${run.complexScriptFontFamily}|${run.fontSize}|${run.bold}|${run.italic}|${run.allCaps}|${run.smallCaps}|${run.horizontalScale}|${run.letterSpacing}|${run.language?.val}|${run.language?.eastAsia}|${run.language?.bidi}`);
173
+ for (const run of block.runs) if (run.kind === "text") parts.push(`t:${run.text}|${run.fontFamily}|${run.eastAsiaFontFamily}|${run.complexScriptFontFamily}|${run.fontSize}|${run.complexScriptFontSize}|${run.bold}|${run.complexScriptBold}|${run.italic}|${run.complexScriptItalic}|${run.forceComplexScript}|${run.allCaps}|${run.smallCaps}|${run.horizontalScale}|${run.letterSpacing}|${run.language?.val}|${run.language?.eastAsia}|${run.language?.bidi}`);
174
174
  else if (run.kind === "tab") parts.push(`tab:${run.width}`);
175
175
  else if (run.kind === "image") parts.push(`img:${run.width}x${run.height}:${run.exactLineHeight === true ? "exact" : "text"}`);
176
176
  else if (run.kind === "lineBreak") parts.push("br");
@@ -186,6 +186,10 @@ function hashParagraphBlock(block) {
186
186
  if (attrs.reserveEmptyOutlineHeight) parts.push("outline-empty-reserve");
187
187
  if (attrs.documentGridLinePitch !== void 0) parts.push(`documentGrid:${attrs.documentGridLinePitch}|${attrs.snapToGrid !== false}`);
188
188
  if (attrs.justificationCompatibility) parts.push(`justify-compat:${attrs.justificationCompatibility.type}`);
189
+ if (attrs.listMarker !== void 0) {
190
+ const marker = attrs.listMarkerFormatting;
191
+ parts.push(`marker:${attrs.listMarker}|${attrs.listMarkerHidden}|${attrs.listMarkerAlignment}|${attrs.listMarkerSuffix}|${marker?.fontFamily}|${marker?.eastAsiaFontFamily}|${marker?.complexScriptFontFamily}|${marker?.fontSize}|${marker?.complexScriptFontSize}|${marker?.bold}|${marker?.complexScriptBold}|${marker?.italic}|${marker?.complexScriptItalic}|${marker?.rtl}|${marker?.forceComplexScript}`);
192
+ }
189
193
  parts.push(...lineBreakPolicyCacheParts(attrs));
190
194
  const borders = attrs.borders;
191
195
  if (borders) {
@@ -0,0 +1,18 @@
1
+ //#region src/layout-engine/measure/complexScriptFormatting.d.ts
2
+ type ComplexScriptFormattingSource = {
3
+ complexScriptFontFamily?: string;
4
+ complexScriptFontSize?: number;
5
+ complexScriptBold?: boolean;
6
+ complexScriptItalic?: boolean;
7
+ };
8
+ type ResolvedComplexScriptFormatting = {
9
+ fontFamily?: string;
10
+ fontSize?: number;
11
+ bold?: boolean;
12
+ italic?: boolean;
13
+ };
14
+ /** Resolve Word's independent complex-script slot into ordinary paint fields. */
15
+ declare const resolveComplexScriptFormatting: (source: ComplexScriptFormattingSource) => ResolvedComplexScriptFormatting;
16
+ declare const hasComplexScriptFormatting: (source: ComplexScriptFormattingSource) => boolean;
17
+ //#endregion
18
+ export { ResolvedComplexScriptFormatting, hasComplexScriptFormatting, resolveComplexScriptFormatting };
@@ -0,0 +1,11 @@
1
+ //#region src/layout-engine/measure/complexScriptFormatting.ts
2
+ /** Resolve Word's independent complex-script slot into ordinary paint fields. */
3
+ const resolveComplexScriptFormatting = (source) => ({
4
+ ...source.complexScriptFontFamily !== void 0 ? { fontFamily: source.complexScriptFontFamily } : {},
5
+ ...source.complexScriptFontSize !== void 0 ? { fontSize: source.complexScriptFontSize } : {},
6
+ ...source.complexScriptBold !== void 0 ? { bold: source.complexScriptBold } : {},
7
+ ...source.complexScriptItalic !== void 0 ? { italic: source.complexScriptItalic } : {}
8
+ });
9
+ const hasComplexScriptFormatting = (source) => source.complexScriptFontFamily !== void 0 || source.complexScriptFontSize !== void 0 || source.complexScriptBold !== void 0 || source.complexScriptItalic !== void 0;
10
+ //#endregion
11
+ export { hasComplexScriptFormatting, resolveComplexScriptFormatting };
@@ -8,7 +8,7 @@ import { ParagraphBlock } from "../types.js";
8
8
  declare const DEFAULT_TAB_STOP_TWIPS = 720;
9
9
  /**
10
10
  * Marker font resolution per ECMA-376 §17.9.6:
11
- * 1. explicit numbering-level rPr (`attrs.listMarkerFont*`),
11
+ * 1. explicit numbering-level rPr (`attrs.listMarkerFormatting`),
12
12
  * 2. first body text run's font,
13
13
  * 3. paragraph defaults, then document defaults.
14
14
  */
@@ -16,6 +16,8 @@ declare function resolveListMarkerFont(block: ParagraphBlock): {
16
16
  fontFamily: string;
17
17
  fontSize: number;
18
18
  bold?: boolean;
19
+ italic?: boolean;
20
+ rtl?: boolean;
19
21
  };
20
22
  /**
21
23
  * Compute the marker's inline-block width in pixels, or 0 if the paragraph
@@ -1,3 +1,5 @@
1
+ import { hasCjk, hasComplexScript } from "../../utils/scriptSegments.js";
2
+ import { hasComplexScriptFormatting, resolveComplexScriptFormatting } from "./complexScriptFormatting.js";
1
3
  import { ptToPx } from "./measureHelpers.js";
2
4
  import { measureTextWidth } from "./measureProvider.js";
3
5
  //#region src/layout-engine/measure/listMarkerWidth.ts
@@ -12,21 +14,33 @@ const DEFAULT_TAB_STOP_TWIPS = 720;
12
14
  const TWIPS_TO_PX = 96 / 1440;
13
15
  /**
14
16
  * Marker font resolution per ECMA-376 §17.9.6:
15
- * 1. explicit numbering-level rPr (`attrs.listMarkerFont*`),
17
+ * 1. explicit numbering-level rPr (`attrs.listMarkerFormatting`),
16
18
  * 2. first body text run's font,
17
19
  * 3. paragraph defaults, then document defaults.
18
20
  */
19
21
  function resolveListMarkerFont(block) {
20
22
  const attrs = block.attrs;
21
23
  const firstTextRun = block.runs.find((r) => r.kind === "text");
22
- const fontFamily = attrs?.listMarkerFontFamily ?? firstTextRun?.fontFamily ?? attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY;
23
- const fontSize = attrs?.listMarkerFontSize ?? firstTextRun?.fontSize ?? attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE;
24
- const bold = attrs?.listMarkerBold ?? firstTextRun?.bold;
25
- return {
26
- fontFamily,
27
- fontSize,
28
- ...bold !== void 0 ? { bold } : {}
24
+ const markerFormatting = attrs?.listMarkerFormatting;
25
+ const bold = markerFormatting?.bold ?? firstTextRun?.bold;
26
+ const italic = markerFormatting?.italic ?? firstTextRun?.italic;
27
+ const base = {
28
+ fontFamily: markerFormatting?.fontFamily ?? firstTextRun?.fontFamily ?? attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY,
29
+ fontSize: markerFormatting?.fontSize ?? firstTextRun?.fontSize ?? attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE,
30
+ ...bold !== void 0 ? { bold } : {},
31
+ ...italic !== void 0 ? { italic } : {},
32
+ ...markerFormatting?.rtl !== void 0 ? { rtl: markerFormatting.rtl } : {}
29
33
  };
34
+ const marker = attrs?.listMarker ?? "";
35
+ if (markerFormatting && hasComplexScriptFormatting(markerFormatting) && (markerFormatting.forceComplexScript || hasComplexScript(marker))) return {
36
+ ...base,
37
+ ...resolveComplexScriptFormatting(markerFormatting)
38
+ };
39
+ if (markerFormatting?.eastAsiaFontFamily && hasCjk(marker)) return {
40
+ ...base,
41
+ fontFamily: markerFormatting.eastAsiaFontFamily
42
+ };
43
+ return base;
30
44
  }
31
45
  /**
32
46
  * Compute the marker's inline-block width in pixels, or 0 if the paragraph
@@ -49,12 +63,7 @@ function resolveListMarkerFont(block) {
49
63
  function getListMarkerInlineWidth(block) {
50
64
  const attrs = block.attrs;
51
65
  if (!attrs?.listMarker || attrs.listMarkerHidden) return 0;
52
- const { fontFamily, fontSize, bold } = resolveListMarkerFont(block);
53
- const style = {
54
- fontFamily,
55
- fontSize,
56
- ...bold !== void 0 ? { bold } : {}
57
- };
66
+ const style = resolveListMarkerFont(block);
58
67
  const naturalWidth = measureTextWidth(attrs.listMarker, style);
59
68
  const markerEndOffset = getMarkerEndOffset(naturalWidth, attrs.listMarkerAlignment);
60
69
  const suffix = attrs.listMarkerSuffix ?? "tab";
@@ -75,19 +84,14 @@ function getListMarkerInlineWidth(block) {
75
84
  let bodyStart;
76
85
  if (firstCustomPast !== void 0 && firstGridPast !== void 0) bodyStart = Math.min(firstCustomPast, firstGridPast);
77
86
  else bodyStart = firstCustomPast ?? firstGridPast;
78
- if (bodyStart === void 0) return naturalWidth + ptToPx(fontSize) * .5;
87
+ if (bodyStart === void 0) return naturalWidth + ptToPx(style.fontSize ?? DEFAULT_FONT_SIZE) * .5;
79
88
  return bodyStart - markerStartPx;
80
89
  }
81
90
  /** Paint-only offset that places the marker around its authored list anchor. */
82
91
  function getListMarkerVisualOffset(block) {
83
92
  const attrs = block.attrs;
84
93
  if (!attrs?.listMarker || attrs.listMarkerHidden) return 0;
85
- const { fontFamily, fontSize, bold } = resolveListMarkerFont(block);
86
- const naturalWidth = measureTextWidth(attrs.listMarker, {
87
- fontFamily,
88
- fontSize,
89
- ...bold !== void 0 ? { bold } : {}
90
- });
94
+ const naturalWidth = measureTextWidth(attrs.listMarker, resolveListMarkerFont(block));
91
95
  if (attrs.listMarkerAlignment === "right") return -naturalWidth;
92
96
  if (attrs.listMarkerAlignment === "center") return -naturalWidth / 2;
93
97
  return 0;