@stll/folio-core 0.22.3 → 0.23.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 (78) hide show
  1. package/dist/ai-edits/apply.d.ts +1 -1
  2. package/dist/controller/layoutPipeline.js +7 -3
  3. package/dist/document-operations.d.ts +4 -4
  4. package/dist/document-operations.js +17 -1
  5. package/dist/docx/blockContentParser.js +2 -47
  6. package/dist/docx/fieldParser.d.ts +1 -1
  7. package/dist/docx/fieldParser.js +12 -4
  8. package/dist/docx/numberingParser.d.ts +2 -11
  9. package/dist/docx/numberingParser.js +3 -79
  10. package/dist/docx/ooxmlCounterFormatter.d.ts +8 -0
  11. package/dist/docx/ooxmlCounterFormatter.js +134 -0
  12. package/dist/docx/rezip.d.ts +1 -1
  13. package/dist/docx/rezip.js +12 -31
  14. package/dist/docx/serializer/tableSerializer.js +1 -1
  15. package/dist/docx/tableParser.js +2 -1
  16. package/dist/fields/bookmarkPages.js +1 -1
  17. package/dist/fields/evaluateField.js +1 -1
  18. package/dist/fields/fieldContext.d.ts +2 -0
  19. package/dist/fields/resolveFieldValues.js +5 -2
  20. package/dist/layout-bridge/convert/headerFooterLayout.js +6 -0
  21. package/dist/layout-bridge/convert/toFlowBlocks.js +51 -109
  22. package/dist/layout-engine/index.d.ts +3 -2
  23. package/dist/layout-engine/index.js +40 -16
  24. package/dist/layout-engine/measure/cache.js +1 -1
  25. package/dist/layout-engine/measure/complexScriptFormatting.d.ts +18 -0
  26. package/dist/layout-engine/measure/complexScriptFormatting.js +11 -0
  27. package/dist/layout-engine/measure/measureContainer.js +31 -16
  28. package/dist/layout-engine/measure/measureHelpers.js +4 -0
  29. package/dist/layout-engine/measure/measureParagraph.js +13 -3
  30. package/dist/layout-engine/measure/measureTypes.d.ts +4 -0
  31. package/dist/layout-engine/paginator.d.ts +5 -3
  32. package/dist/layout-engine/paginator.js +34 -13
  33. package/dist/layout-engine/types.d.ts +31 -4
  34. package/dist/layout-painter/imageLayout.d.ts +1 -1
  35. package/dist/layout-painter/index.js +2 -1
  36. package/dist/layout-painter/renderPage.js +9 -3
  37. package/dist/layout-painter/renderParagraph.js +42 -25
  38. package/dist/layout-painter/renderUtils.d.ts +2 -0
  39. package/dist/managers/AutoSaveManager.js +8 -40
  40. package/dist/managers/TableSelectionManager.d.ts +1 -1
  41. package/dist/managers/autoSaveCodec.d.ts +17 -0
  42. package/dist/managers/autoSaveCodec.js +109 -0
  43. package/dist/paged-layout/sectionGeometry.d.ts +3 -2
  44. package/dist/paged-layout/sectionGeometry.js +17 -1
  45. package/dist/prosemirror/attrs/index.js +278 -25
  46. package/dist/prosemirror/commands/comments.js +3 -3
  47. package/dist/prosemirror/commands/formatting.js +19 -19
  48. package/dist/prosemirror/commands/index.d.ts +2 -2
  49. package/dist/prosemirror/commands/paragraph.js +32 -32
  50. package/dist/prosemirror/commands/propertyChangeScope.d.ts +4 -2
  51. package/dist/prosemirror/commands/propertyChangeScope.js +8 -8
  52. package/dist/prosemirror/commands/table.d.ts +10 -52
  53. package/dist/prosemirror/commands/table.js +34 -34
  54. package/dist/prosemirror/conversion/fromProseDoc.js +24 -33
  55. package/dist/prosemirror/conversion/sdtAttrs.d.ts +2 -1
  56. package/dist/prosemirror/conversion/sdtAttrs.js +9 -4
  57. package/dist/prosemirror/conversion/toProseDoc.js +4 -1
  58. package/dist/prosemirror/extensions/ExtensionManager.d.ts +6 -3
  59. package/dist/prosemirror/extensions/ExtensionManager.js +5 -3
  60. package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +1 -1
  61. package/dist/prosemirror/extensions/features/ListExtension.js +4 -4
  62. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.d.ts +3 -2
  63. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +31 -17
  64. package/dist/prosemirror/extensions/nodes/ShapeExtension.d.ts +1 -1
  65. package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -0
  66. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +1 -1
  67. package/dist/prosemirror/extensions/types.d.ts +134 -1
  68. package/dist/prosemirror/index.d.ts +2 -2
  69. package/dist/prosemirror/plugins/selectionTracker.js +25 -99
  70. package/dist/prosemirror/revisionCarriers.js +3 -1
  71. package/dist/prosemirror/schema/index.d.ts +2 -2
  72. package/dist/prosemirror/schema/index.js +87 -0
  73. package/dist/prosemirror/schema/marks.d.ts +8 -0
  74. package/dist/prosemirror/schema/nodes.d.ts +15 -2
  75. package/dist/prosemirror/selectionState.d.ts +11 -5
  76. package/dist/prosemirror/selectionState.js +94 -57
  77. package/dist/utils/tableOperations.d.ts +7 -6
  78. package/package.json +3 -3
@@ -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,15 +1,15 @@
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
- import { NUMBER_FORMAT_VALUES } from "../../types/documentEnumValues.js";
13
13
  import { resolveColor, resolveHighlightToCss } from "../../utils/colorResolver.js";
14
14
  import { resolveThemeFont } from "../../utils/fontResolver.js";
15
15
  import { resolveShadingFill } from "../../utils/formatToStyle.js";
@@ -75,51 +75,8 @@ function formatNumberedMarker(counters, level) {
75
75
  if (parts.length === 0) return "1.";
76
76
  return `${parts.join(".")}.`;
77
77
  }
78
- const ROMAN_PAIRS = [
79
- [1e3, "M"],
80
- [900, "CM"],
81
- [500, "D"],
82
- [400, "CD"],
83
- [100, "C"],
84
- [90, "XC"],
85
- [50, "L"],
86
- [40, "XL"],
87
- [10, "X"],
88
- [9, "IX"],
89
- [5, "V"],
90
- [4, "IV"],
91
- [1, "I"]
92
- ];
93
- function toRoman(value, upper) {
94
- if (value <= 0) return "";
95
- let remaining = value;
96
- let output = "";
97
- for (const [number, symbol] of ROMAN_PAIRS) while (remaining >= number) {
98
- output += symbol;
99
- remaining -= number;
100
- }
101
- return upper ? output : output.toLowerCase();
102
- }
103
- function toLetter(value, upper) {
104
- if (value <= 0) return "";
105
- const zeroBased = value - 1;
106
- const baseCodePoint = upper ? 65 : 97;
107
- return String.fromCodePoint(baseCodePoint + zeroBased % 26).repeat(Math.floor(zeroBased / 26) + 1);
108
- }
109
78
  function formatCounter(value, format) {
110
- if (!Number.isFinite(value)) return "";
111
- switch (format) {
112
- case "upperRoman": return toRoman(value, true);
113
- case "lowerRoman": return toRoman(value, false);
114
- case "upperLetter": return toLetter(value, true);
115
- case "lowerLetter": return toLetter(value, false);
116
- case "decimalZero": return padDecimal(value, 2);
117
- case "decimalZero3": return padDecimal(value, 3);
118
- case "decimalZero4": return padDecimal(value, 4);
119
- case "decimalZero5": return padDecimal(value, 5);
120
- case "none": return "";
121
- default: return String(value);
122
- }
79
+ return formatOoxmlCounter(value, format);
123
80
  }
124
81
  function resolveListTemplate(template, counters, levelFormats, forceDecimal = false) {
125
82
  return template.replace(/%(?<digit>\d)(?<punct>[.):\]])?/gu, (...args) => {
@@ -364,7 +321,7 @@ const resolveWesternThemeFont = (fontFamily, theme) => {
364
321
  return (themeRef ? resolveThemeFont(themeRef, theme?.fontScheme) : null) ?? fontFamily.ascii ?? fontFamily.hAnsi ?? void 0;
365
322
  };
366
323
  const resolveComplexScriptThemeFont = (fontFamily, theme) => {
367
- return (fontFamily.cstheme ? resolveThemeFont(fontFamily.cstheme, theme?.fontScheme) : null) ?? fontFamily.cs ?? void 0;
324
+ return (fontFamily.csTheme ? resolveThemeFont(fontFamily.csTheme, theme?.fontScheme) : null) ?? fontFamily.cs ?? void 0;
368
325
  };
369
326
  const resolveEastAsiaThemeFont = (fontFamily, theme) => {
370
327
  return (fontFamily.eastAsiaTheme ? resolveThemeFont(fontFamily.eastAsiaTheme, theme?.fontScheme) : null) ?? fontFamily.eastAsia ?? void 0;
@@ -400,6 +357,10 @@ function applyRunFormattingOverrides(formatting, attrs) {
400
357
  if (attrs.shadow === false) formatting.textShadow = false;
401
358
  if (attrs.outline === false) formatting.textOutline = false;
402
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;
403
364
  }
404
365
  function paragraphRunDefaults(pmAttrs, theme) {
405
366
  const defaultTextFormatting = pmAttrs.defaultTextFormatting;
@@ -413,8 +374,12 @@ function paragraphRunDefaults(pmAttrs, theme) {
413
374
  if (complexScriptFontFamily) result.complexScriptFontFamily = complexScriptFontFamily;
414
375
  if (defaultTextFormatting.language) result.language = { ...defaultTextFormatting.language };
415
376
  if (defaultTextFormatting.fontSize !== void 0) result.fontSize = defaultTextFormatting.fontSize / 2;
377
+ if (defaultTextFormatting.fontSizeCs !== void 0) result.complexScriptFontSize = defaultTextFormatting.fontSizeCs / 2;
416
378
  if (defaultTextFormatting.bold !== void 0) result.bold = defaultTextFormatting.bold;
379
+ if (defaultTextFormatting.boldCs !== void 0) result.complexScriptBold = defaultTextFormatting.boldCs;
417
380
  if (defaultTextFormatting.italic !== void 0) result.italic = defaultTextFormatting.italic;
381
+ if (defaultTextFormatting.italicCs !== void 0) result.complexScriptItalic = defaultTextFormatting.italicCs;
382
+ if (defaultTextFormatting.cs !== void 0) result.forceComplexScript = defaultTextFormatting.cs;
418
383
  if (defaultTextFormatting.underline && defaultTextFormatting.underline.style !== "none") {
419
384
  result.underline = { style: defaultTextFormatting.underline.style };
420
385
  if (defaultTextFormatting.underline.color) result.underline.color = resolveColor(defaultTextFormatting.underline.color, theme);
@@ -650,78 +615,54 @@ function toListMarkerRevision(kind, info) {
650
615
  }
651
616
  function isAddedNumberingChange(change) {
652
617
  const previousFormatting = change.previousFormatting;
653
- return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && previousFormatting["numPr"] == null;
618
+ return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && previousFormatting.numPr == null;
654
619
  }
655
620
  function isRemovedNumberingChange(change) {
656
621
  const previousFormatting = change.previousFormatting;
657
- return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && isListNumPr(previousFormatting["numPr"]);
622
+ return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && isListNumPr(previousFormatting.numPr);
658
623
  }
659
624
  function isChangedNumberingChange(currentNumPr, change) {
660
625
  const previousFormatting = change.previousFormatting;
661
- return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && isListNumPr(previousFormatting["numPr"]) && !areListNumPrEqual(previousFormatting["numPr"], currentNumPr);
626
+ return previousFormatting != null && Object.hasOwn(previousFormatting, "numPr") && isListNumPr(previousFormatting.numPr) && !areListNumPrEqual(previousFormatting.numPr, currentNumPr);
662
627
  }
663
628
  function areListNumPrEqual(left, right) {
664
629
  return left.numId === right.numId && left.ilvl === right.ilvl;
665
630
  }
666
- function isRecord(value) {
667
- return typeof value === "object" && value !== null;
668
- }
669
631
  function isListNumPr(value) {
670
- if (!isRecord(value)) return false;
671
- const numId = value["numId"];
672
- const ilvl = value["ilvl"];
673
- return (numId === void 0 || typeof numId === "number") && (ilvl === void 0 || typeof ilvl === "number");
674
- }
675
- function isNumberFormat(value) {
676
- return NUMBER_FORMAT_VALUES.some((format) => format === value);
677
- }
678
- function readNumberFormats(value) {
679
- if (!Array.isArray(value)) return;
680
- const formats = [];
681
- for (const item of value) {
682
- if (!isNumberFormat(item)) return;
683
- formats.push(item);
684
- }
685
- return formats;
686
- }
687
- function readListMarkerSuffix(value) {
688
- if (value === "tab" || value === "space" || value === "nothing") return value;
689
- }
690
- function readListMarkerAlignment(value) {
691
- if (value === "left" || value === "center" || value === "right") return value;
632
+ return value !== void 0 && value !== null;
692
633
  }
693
634
  function toPreviousListAttrs(previousFormatting) {
694
635
  const attrs = {};
695
- const numPr = previousFormatting["numPr"];
636
+ const numPr = previousFormatting.numPr;
696
637
  if (isListNumPr(numPr)) attrs.numPr = numPr;
697
- const listIsBullet = previousFormatting["listIsBullet"];
698
- if (typeof listIsBullet === "boolean") attrs.listIsBullet = listIsBullet;
699
- const listIsLegal = previousFormatting["listIsLegal"];
700
- if (typeof listIsLegal === "boolean") attrs.listIsLegal = listIsLegal;
701
- const listMarker = previousFormatting["listMarker"];
702
- if (typeof listMarker === "string") attrs.listMarker = listMarker;
703
- const listNumFmt = previousFormatting["listNumFmt"];
704
- if (isNumberFormat(listNumFmt)) attrs.listNumFmt = listNumFmt;
705
- const listLevelNumFmts = readNumberFormats(previousFormatting["listLevelNumFmts"]);
706
- if (listLevelNumFmts) attrs.listLevelNumFmts = listLevelNumFmts;
707
- const listLevelStarts = previousFormatting["listLevelStarts"];
708
- if (Array.isArray(listLevelStarts) && listLevelStarts.every((value) => typeof value === "number")) attrs.listLevelStarts = listLevelStarts;
709
- const listAbstractNumId = previousFormatting["listAbstractNumId"];
710
- if (typeof listAbstractNumId === "number") attrs.listAbstractNumId = listAbstractNumId;
711
- const listStartOverride = previousFormatting["listStartOverride"];
712
- if (typeof listStartOverride === "number") attrs.listStartOverride = listStartOverride;
713
- const listMarkerHidden = previousFormatting["listMarkerHidden"];
714
- if (typeof listMarkerHidden === "boolean") attrs.listMarkerHidden = listMarkerHidden;
715
- const listMarkerFontFamily = previousFormatting["listMarkerFontFamily"];
716
- if (typeof listMarkerFontFamily === "string") attrs.listMarkerFontFamily = listMarkerFontFamily;
717
- const listMarkerFontSize = previousFormatting["listMarkerFontSize"];
718
- if (typeof listMarkerFontSize === "number") attrs.listMarkerFontSize = listMarkerFontSize;
719
- const listMarkerBold = previousFormatting["listMarkerBold"];
720
- if (typeof listMarkerBold === "boolean") attrs.listMarkerBold = listMarkerBold;
721
- const listMarkerAlignment = readListMarkerAlignment(previousFormatting["listMarkerAlignment"]);
722
- if (listMarkerAlignment) attrs.listMarkerAlignment = listMarkerAlignment;
723
- const listMarkerSuffix = readListMarkerSuffix(previousFormatting["listMarkerSuffix"]);
724
- if (listMarkerSuffix) attrs.listMarkerSuffix = listMarkerSuffix;
638
+ const listIsBullet = previousFormatting.listIsBullet;
639
+ if (listIsBullet !== void 0) attrs.listIsBullet = listIsBullet;
640
+ const listIsLegal = previousFormatting.listIsLegal;
641
+ if (listIsLegal !== void 0) attrs.listIsLegal = listIsLegal;
642
+ const listMarker = previousFormatting.listMarker;
643
+ if (listMarker !== void 0) attrs.listMarker = listMarker;
644
+ const listNumFmt = previousFormatting.listNumFmt;
645
+ if (listNumFmt !== void 0) attrs.listNumFmt = listNumFmt;
646
+ const listLevelNumFmts = previousFormatting.listLevelNumFmts;
647
+ if (listLevelNumFmts !== void 0) attrs.listLevelNumFmts = listLevelNumFmts;
648
+ const listLevelStarts = previousFormatting.listLevelStarts;
649
+ if (listLevelStarts !== void 0) attrs.listLevelStarts = listLevelStarts;
650
+ const listAbstractNumId = previousFormatting.listAbstractNumId;
651
+ if (listAbstractNumId !== void 0) attrs.listAbstractNumId = listAbstractNumId;
652
+ const listStartOverride = previousFormatting.listStartOverride;
653
+ if (listStartOverride !== void 0) attrs.listStartOverride = listStartOverride;
654
+ const listMarkerHidden = previousFormatting.listMarkerHidden;
655
+ if (listMarkerHidden !== void 0) attrs.listMarkerHidden = listMarkerHidden;
656
+ const listMarkerFontFamily = previousFormatting.listMarkerFontFamily;
657
+ if (listMarkerFontFamily !== void 0) attrs.listMarkerFontFamily = listMarkerFontFamily;
658
+ const listMarkerFontSize = previousFormatting.listMarkerFontSize;
659
+ if (listMarkerFontSize !== void 0) attrs.listMarkerFontSize = listMarkerFontSize;
660
+ const listMarkerBold = previousFormatting.listMarkerBold;
661
+ if (listMarkerBold !== void 0) attrs.listMarkerBold = listMarkerBold;
662
+ const listMarkerAlignment = previousFormatting.listMarkerAlignment;
663
+ if (listMarkerAlignment !== void 0) attrs.listMarkerAlignment = listMarkerAlignment;
664
+ const listMarkerSuffix = previousFormatting.listMarkerSuffix;
665
+ if (listMarkerSuffix !== void 0) attrs.listMarkerSuffix = listMarkerSuffix;
725
666
  return attrs;
726
667
  }
727
668
  function resolveDeletedListMarker(previousListAttrs, listCounters, listAbstractCounters, listSeenNumIds) {
@@ -846,10 +787,10 @@ function convertParagraphAttrs(pmAttrs, theme, listCounters, listAbstractCounter
846
787
  if (pmAttrs.widowControl === false) attrs.widowControl = false;
847
788
  if (pmAttrs.contextualSpacing) attrs.contextualSpacing = true;
848
789
  if (pmAttrs.runInWithNext) attrs.runInWithNext = true;
849
- if (directionIsRtl(pmAttrs.direction)) attrs.bidi = true;
790
+ const bidi = directionToBidi(pmAttrs.direction);
791
+ if (bidi !== void 0) attrs.bidi = bidi;
850
792
  if (pmAttrs.styleId) attrs.styleId = pmAttrs.styleId;
851
- const pPrChange = pmAttrs._propertyChanges;
852
- const propertyChanges = Array.isArray(pPrChange) ? pPrChange : [];
793
+ const propertyChanges = pmAttrs._propertyChanges ?? [];
853
794
  let changedNumberingChange;
854
795
  if (pmAttrs.numPr) {
855
796
  const numPr = {};
@@ -1213,7 +1154,7 @@ function convertTable(node, startPos, options) {
1213
1154
  if (justification) tableBlock.justification = justification;
1214
1155
  if (indentPx !== void 0) tableBlock.indent = indentPx;
1215
1156
  if (floatingPx) tableBlock.floating = floatingPx;
1216
- if (originalFormatting?.bidi) tableBlock.bidi = true;
1157
+ if (attrs._resolvedBidi ?? originalFormatting?.bidi) tableBlock.bidi = true;
1217
1158
  return tableBlock;
1218
1159
  }
1219
1160
  /**
@@ -1488,6 +1429,7 @@ function toFlowBlocks(doc, options = {}) {
1488
1429
  const breakType = secProps?.sectionStart ?? pmAttrs.sectionBreakType;
1489
1430
  if (breakType) sectionBreak.type = breakType;
1490
1431
  if (secProps) {
1432
+ sectionBreak.pageNumbering = getPageNumbering(secProps);
1491
1433
  const documentGridLinePitchTwips = resolveDocumentGridLinePitch(secProps.docGrid);
1492
1434
  if (documentGridLinePitchTwips !== void 0) sectionBreak.documentGridLinePitchTwips = documentGridLinePitchTwips;
1493
1435
  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, 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, 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");
@@ -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 };
@@ -1,5 +1,6 @@
1
1
  import { SCRIPT_CLASS, hasCjk, hasComplexScript, scriptClassOf, segmentByScript } from "../../utils/scriptSegments.js";
2
2
  import { getCachedFontMetrics, getCachedTextWidth, getTextWidthCacheGeneration, setCachedFontMetrics, setCachedTextWidth } from "./cache.js";
3
+ import { hasComplexScriptFormatting, resolveComplexScriptFormatting } from "./complexScriptFormatting.js";
3
4
  import { buildFontString, getResolvedData, ptToPx } from "./measureHelpers.js";
4
5
  import { setMeasureProvider } from "./measureProvider.js";
5
6
  import { canPrefetchMeasurement, prefetchMeasurement } from "./measureWorker.js";
@@ -115,10 +116,14 @@ function canvasGetFontMetrics(style) {
115
116
  * @param style - Font styling properties
116
117
  * @returns Width in pixels
117
118
  */
118
- function canvasMeasureTextWidth(text, style) {
119
+ function canvasMeasureTextWidth(text, sourceStyle) {
119
120
  if (!text) return 0;
121
+ const style = sourceStyle.forceComplexScript ? {
122
+ ...sourceStyle,
123
+ ...resolveComplexScriptFormatting(sourceStyle)
124
+ } : sourceStyle;
120
125
  const measuredText = applyTextTransform(text, style);
121
- if (!style.letterSpacing && needsPerScriptFonts(style, measuredText)) return measureMixedScriptWidth(measuredText, style);
126
+ if (!sourceStyle.forceComplexScript && !style.letterSpacing && needsPerScriptFonts(style, measuredText)) return measureMixedScriptWidth(measuredText, style);
122
127
  const ctx = getCanvasContext();
123
128
  const font = buildFontString(style);
124
129
  const letterSpacing = style.letterSpacing ?? 0;
@@ -156,9 +161,9 @@ function canvasMeasureTextWidth(text, style) {
156
161
  * letter spacing, horizontal scale, transform, and the EA font so the segment
157
162
  * takes the single-font path (no double-counting, no recursion).
158
163
  */
159
- function glyphAdvanceStyle(style, fontFamily) {
164
+ function glyphAdvanceStyle(style) {
160
165
  const result = {};
161
- if (fontFamily !== void 0) result.fontFamily = fontFamily;
166
+ if (style.fontFamily !== void 0) result.fontFamily = style.fontFamily;
162
167
  if (style.fontSize !== void 0) result.fontSize = style.fontSize;
163
168
  if (style.bold !== void 0) result.bold = style.bold;
164
169
  if (style.italic !== void 0) result.italic = style.italic;
@@ -179,19 +184,28 @@ function glyphAdvanceStyle(style, fontFamily) {
179
184
  */
180
185
  function needsPerScriptFonts(style, measuredText) {
181
186
  if (style.eastAsiaFontFamily && hasCjk(measuredText)) return true;
182
- return Boolean(style.complexScriptFontFamily) && hasComplexScript(measuredText);
187
+ return hasComplexScriptFormatting(style) && hasComplexScript(measuredText);
183
188
  }
184
- /** The font slot a script class selects, falling back to the western one. */
185
- function scriptFontFamily(style, script) {
186
- if (script === SCRIPT_CLASS.eastAsia) return style.eastAsiaFontFamily ?? style.fontFamily;
187
- if (script === SCRIPT_CLASS.complex) return style.complexScriptFontFamily ?? style.fontFamily;
188
- return style.fontFamily;
189
+ /** Resolve the independent font/style slot selected by a script segment. */
190
+ function scriptStyle(style, script) {
191
+ if (script === SCRIPT_CLASS.eastAsia) {
192
+ if (style.eastAsiaFontFamily === void 0) return style;
193
+ return {
194
+ ...style,
195
+ fontFamily: style.eastAsiaFontFamily
196
+ };
197
+ }
198
+ if (script === SCRIPT_CLASS.complex) return {
199
+ ...style,
200
+ ...resolveComplexScriptFormatting(style)
201
+ };
202
+ return style;
189
203
  }
190
204
  function measureMixedScriptWidth(measuredText, style) {
191
205
  const letterSpacing = style.letterSpacing ?? 0;
192
206
  const horizontalScale = getHorizontalScaleFactor(style);
193
207
  let glyphWidth = 0;
194
- for (const segment of segmentByScript(measuredText)) glyphWidth += canvasMeasureTextWidth(segment.text, glyphAdvanceStyle(style, scriptFontFamily(style, segment.script)));
208
+ for (const segment of segmentByScript(measuredText)) glyphWidth += canvasMeasureTextWidth(segment.text, glyphAdvanceStyle(scriptStyle(style, segment.script)));
195
209
  let width = glyphWidth;
196
210
  if (letterSpacing) {
197
211
  const codePoints = countCodePoints(measuredText);
@@ -247,7 +261,11 @@ function canvasMeasureText(text, style) {
247
261
  * @param style - Font styling properties
248
262
  * @returns Run measurement with width and per-character widths
249
263
  */
250
- function canvasMeasureRun(text, style) {
264
+ function canvasMeasureRun(text, sourceStyle) {
265
+ const style = sourceStyle.forceComplexScript ? {
266
+ ...sourceStyle,
267
+ ...resolveComplexScriptFormatting(sourceStyle)
268
+ } : sourceStyle;
251
269
  const metrics = canvasGetFontMetrics(style);
252
270
  if (!text) return {
253
271
  width: 0,
@@ -262,10 +280,7 @@ function canvasMeasureRun(text, style) {
262
280
  ...style,
263
281
  fontFamily: style.eastAsiaFontFamily
264
282
  }) : void 0;
265
- const complexScriptFont = style.complexScriptFontFamily !== void 0 && !style.letterSpacing ? buildFontString({
266
- ...style,
267
- fontFamily: style.complexScriptFontFamily
268
- }) : void 0;
283
+ const complexScriptFont = hasComplexScriptFormatting(style) && !style.letterSpacing ? buildFontString(scriptStyle(style, SCRIPT_CLASS.complex)) : void 0;
269
284
  const letterSpacing = style.letterSpacing ?? 0;
270
285
  const scale = getHorizontalScaleFactor(style);
271
286
  const charWidths = [];
@@ -32,6 +32,10 @@ function buildRunFontStyle(run, fallbackFontFamily, fallbackFontSize) {
32
32
  fontFamily: run.fontFamily ?? fallbackFontFamily,
33
33
  ...run.eastAsiaFontFamily !== void 0 ? { eastAsiaFontFamily: run.eastAsiaFontFamily } : {},
34
34
  ...run.complexScriptFontFamily !== void 0 ? { complexScriptFontFamily: run.complexScriptFontFamily } : {},
35
+ ...run.complexScriptFontSize !== void 0 ? { complexScriptFontSize: run.complexScriptFontSize } : {},
36
+ ...run.complexScriptBold !== void 0 ? { complexScriptBold: run.complexScriptBold } : {},
37
+ ...run.complexScriptItalic !== void 0 ? { complexScriptItalic: run.complexScriptItalic } : {},
38
+ ...run.forceComplexScript !== void 0 ? { forceComplexScript: run.forceComplexScript } : {},
35
39
  fontSize,
36
40
  ...run.bold !== void 0 ? { bold: run.bold } : {},
37
41
  ...run.italic !== void 0 ? { italic: run.italic } : {},
@@ -1,10 +1,11 @@
1
1
  import { calculateTabWidth, pixelsToTwips } from "../../prosemirror/utils/tabCalculator.js";
2
2
  import { CJK_FALLBACK_FONT_FAMILY, isCjkFont } from "../../utils/fontResolver.js";
3
3
  import { inlineImageBoundingBox } from "../../utils/rotationBoundingBox.js";
4
- import { hasCjk } from "../../utils/scriptSegments.js";
4
+ import { hasCjk, hasComplexScript } from "../../utils/scriptSegments.js";
5
5
  import { measuredLineAdvance } from "../lineFlow.js";
6
6
  import { isFloatingImageRun } from "../types.js";
7
7
  import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
8
+ import { hasComplexScriptFormatting, resolveComplexScriptFormatting } from "./complexScriptFormatting.js";
8
9
  import { resolveEffectiveLineBreakPolicy } from "./effectiveLineBreakPolicy.js";
9
10
  import { getFloatingAvailableWidth, getFloatingMargins } from "./floatingZones.js";
10
11
  import { defaultLineBreakProvider, getLineBreakProvider } from "./lineBreakProvider.js";
@@ -97,6 +98,15 @@ function cjkLineHeightStyle(run, baseStyle) {
97
98
  fontFamily: CJK_FALLBACK_FONT_FAMILY
98
99
  };
99
100
  }
101
+ /** Pick the dominant typography metrics for a run containing complex script. */
102
+ function complexScriptLineHeightStyle(run, baseStyle) {
103
+ if (!hasComplexScriptFormatting(run) || !run.forceComplexScript && !hasComplexScript(run.text)) return baseStyle;
104
+ const complexStyle = {
105
+ ...baseStyle,
106
+ ...resolveComplexScriptFormatting(run)
107
+ };
108
+ return (complexStyle.fontSize ?? DEFAULT_FONT_SIZE) >= (baseStyle.fontSize ?? DEFAULT_FONT_SIZE) ? complexStyle : baseStyle;
109
+ }
100
110
  /**
101
111
  * Calculate typography metrics from font size and spacing settings
102
112
  *
@@ -1018,7 +1028,7 @@ function measureParagraph(block, maxWidth, options) {
1018
1028
  run: textRun
1019
1029
  });
1020
1030
  const breakPolicy = effectiveLineBreakPolicy.provider;
1021
- const lineHeightStyle = cjkLineHeightStyle(textRun, style);
1031
+ const lineHeightStyle = cjkLineHeightStyle(textRun, complexScriptLineHeightStyle(textRun, style));
1022
1032
  updateMaxFont(lineHeightStyle);
1023
1033
  if (!text || text.length === 0) {
1024
1034
  currentLine.toRun = runIndex;
@@ -1095,7 +1105,7 @@ function measureParagraph(block, maxWidth, options) {
1095
1105
  }
1096
1106
  }
1097
1107
  if (fittingPrefix) {
1098
- for (const segment of fittingPrefix.segments) updateMaxFont(cjkLineHeightStyle(segment.run, segment.style));
1108
+ for (const segment of fittingPrefix.segments) updateMaxFont(cjkLineHeightStyle(segment.run, complexScriptLineHeightStyle(segment.run, segment.style)));
1099
1109
  currentLine.width += fittingPrefix.width + measureTextWidth("-", fittingPrefix.hyphenStyle);
1100
1110
  currentLine.trailingWhitespaceWidth = 0;
1101
1111
  currentLine.toRun = fittingPrefix.endRunIndex;
@@ -26,6 +26,10 @@ type FontStyle = {
26
26
  * points. Same contract as `eastAsiaFontFamily`, over a different slot.
27
27
  */
28
28
  complexScriptFontFamily?: string;
29
+ complexScriptFontSize?: number;
30
+ complexScriptBold?: boolean;
31
+ complexScriptItalic?: boolean;
32
+ forceComplexScript?: boolean;
29
33
  fontSize?: number;
30
34
  bold?: boolean;
31
35
  italic?: boolean;