@stll/folio-core 0.23.0 → 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 (43) hide show
  1. package/dist/controller/layoutPipeline.js +7 -3
  2. package/dist/docx/blockContentParser.js +2 -47
  3. package/dist/docx/fieldParser.d.ts +1 -1
  4. package/dist/docx/fieldParser.js +12 -4
  5. package/dist/docx/numberingParser.d.ts +2 -11
  6. package/dist/docx/numberingParser.js +3 -79
  7. package/dist/docx/ooxmlCounterFormatter.d.ts +8 -0
  8. package/dist/docx/ooxmlCounterFormatter.js +134 -0
  9. package/dist/docx/serializer/tableSerializer.js +1 -1
  10. package/dist/docx/tableParser.js +2 -1
  11. package/dist/fields/bookmarkPages.js +1 -1
  12. package/dist/fields/evaluateField.js +1 -1
  13. package/dist/fields/fieldContext.d.ts +2 -0
  14. package/dist/fields/resolveFieldValues.js +5 -2
  15. package/dist/layout-bridge/convert/headerFooterLayout.js +6 -0
  16. package/dist/layout-bridge/convert/toFlowBlocks.js +16 -48
  17. package/dist/layout-engine/index.d.ts +3 -2
  18. package/dist/layout-engine/index.js +40 -16
  19. package/dist/layout-engine/measure/cache.js +1 -1
  20. package/dist/layout-engine/measure/complexScriptFormatting.d.ts +18 -0
  21. package/dist/layout-engine/measure/complexScriptFormatting.js +11 -0
  22. package/dist/layout-engine/measure/measureContainer.js +31 -16
  23. package/dist/layout-engine/measure/measureHelpers.js +4 -0
  24. package/dist/layout-engine/measure/measureParagraph.js +13 -3
  25. package/dist/layout-engine/measure/measureTypes.d.ts +4 -0
  26. package/dist/layout-engine/paginator.d.ts +5 -3
  27. package/dist/layout-engine/paginator.js +34 -13
  28. package/dist/layout-engine/types.d.ts +31 -4
  29. package/dist/layout-painter/index.js +2 -1
  30. package/dist/layout-painter/renderPage.js +9 -3
  31. package/dist/layout-painter/renderParagraph.js +42 -25
  32. package/dist/layout-painter/renderUtils.d.ts +2 -0
  33. package/dist/paged-layout/sectionGeometry.d.ts +3 -2
  34. package/dist/paged-layout/sectionGeometry.js +17 -1
  35. package/dist/prosemirror/attrs/index.js +5 -0
  36. package/dist/prosemirror/conversion/fromProseDoc.js +4 -16
  37. package/dist/prosemirror/conversion/toProseDoc.js +4 -1
  38. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.d.ts +3 -2
  39. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +31 -17
  40. package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -0
  41. package/dist/prosemirror/schema/marks.d.ts +8 -0
  42. package/dist/prosemirror/schema/nodes.d.ts +2 -0
  43. package/package.json +1 -1
@@ -18,7 +18,7 @@ import "../layout-engine/types.js";
18
18
  import { renderPages } from "../layout-painter/renderPage.js";
19
19
  import { tryBuildIncrementalMeasures } from "../paged-layout/incrementalMeasure.js";
20
20
  import { computePerBlockMeasureInputs } from "../paged-layout/sectionBlockWidths.js";
21
- import { getMargins, getPageSize, twipsToPixels } from "../paged-layout/sectionGeometry.js";
21
+ import { getMargins, getPageNumbering, getPageSize, twipsToPixels } from "../paged-layout/sectionGeometry.js";
22
22
  import { templatePreviewValuesKey } from "../prosemirror/plugins/templatePreviewValues.js";
23
23
  import { getDocumentWatermark } from "../watermark/index.js";
24
24
  //#region src/controller/layoutPipeline.ts
@@ -195,7 +195,8 @@ function runLayoutPipeline(deps, state, options = {}) {
195
195
  phaseStartedAt = performance.now();
196
196
  const bodyLayoutConfig = {
197
197
  pageSize,
198
- margins: initialBodyMargins
198
+ margins: initialBodyMargins,
199
+ pageNumbering: getPageNumbering(sectionProperties)
199
200
  };
200
201
  if (columns !== void 0) bodyLayoutConfig.columns = columns;
201
202
  const finalSectionProperties = document?.package.document.sections?.at(-1)?.properties;
@@ -213,7 +214,8 @@ function runLayoutPipeline(deps, state, options = {}) {
213
214
  authoredMargins: getMargins(finalSectionProperties),
214
215
  preparedHeader: finalHeaderForLayout,
215
216
  preparedFooter: finalFooterForLayout
216
- })
217
+ }),
218
+ pageNumbering: getPageNumbering(finalSectionProperties)
217
219
  } : bodyLayoutConfig;
218
220
  const finalColumns = getColumns(finalSectionProperties);
219
221
  if (finalColumns !== void 0) finalLayoutConfig.columns = finalColumns;
@@ -258,6 +260,7 @@ function runLayoutPipeline(deps, state, options = {}) {
258
260
  const nextLayoutOpts = {
259
261
  pageSize,
260
262
  margins: initialBodyMargins,
263
+ pageNumbering: bodyLayoutConfig.pageNumbering,
261
264
  pageGap,
262
265
  mirrorMargins
263
266
  };
@@ -273,6 +276,7 @@ function runLayoutPipeline(deps, state, options = {}) {
273
276
  count: 1,
274
277
  gap: 0
275
278
  };
279
+ nextLayoutOpts.finalPageNumbering = finalLayoutConfig.pageNumbering;
276
280
  }
277
281
  if (columns !== void 0) nextLayoutOpts.columns = columns;
278
282
  if (bodyBreakType !== void 0) nextLayoutOpts.bodyBreakType = bodyBreakType;
@@ -1,58 +1,13 @@
1
1
  import { parseBookmarkEnd, parseBookmarkStart } from "./bookmarkParser.js";
2
2
  import { appendBookmarkMarkerToLastParagraphInBlocks, prependBookmarkMarkersToFirstParagraphInBlocks } from "./bookmarkPlacement.js";
3
3
  import { convertBulletToUnicode } from "./bulletMarkers.js";
4
- import { padDecimal } from "./numberingParser.js";
4
+ import { formatOoxmlCounter } from "./ooxmlCounterFormatter.js";
5
5
  import { parseParagraph } from "./paragraphParser.js";
6
6
  import { enrichParagraphTextBoxes } from "./paragraphTextBoxEnrichment.js";
7
7
  import { parseSdtProperties } from "./sdtProperties.js";
8
8
  import { parseTable } from "./tableParser.js";
9
9
  import { elementToXml, findChild, getChildElements, getLocalName, mergeXmlnsDeclarations } from "./xmlParser.js";
10
10
  //#region src/docx/blockContentParser.ts
11
- const toRoman = (numParam) => {
12
- let num = numParam;
13
- const romanNumerals = [
14
- [1e3, "M"],
15
- [900, "CM"],
16
- [500, "D"],
17
- [400, "CD"],
18
- [100, "C"],
19
- [90, "XC"],
20
- [50, "L"],
21
- [40, "XL"],
22
- [10, "X"],
23
- [9, "IX"],
24
- [5, "V"],
25
- [4, "IV"],
26
- [1, "I"]
27
- ];
28
- let result = "";
29
- for (const [value, symbol] of romanNumerals) while (num >= value) {
30
- result += symbol;
31
- num -= value;
32
- }
33
- return result;
34
- };
35
- const toRepeatedLetter = (value, baseCodePoint) => {
36
- if (value <= 0) return "0";
37
- const zeroBased = value - 1;
38
- return String.fromCodePoint(baseCodePoint + zeroBased % 26).repeat(Math.floor(zeroBased / 26) + 1);
39
- };
40
- const formatNumber = (value, numFmt) => {
41
- switch (numFmt) {
42
- case "decimal": return String(value);
43
- case "decimalZero": return padDecimal(value, 2);
44
- case "decimalZero3": return padDecimal(value, 3);
45
- case "decimalZero4": return padDecimal(value, 4);
46
- case "decimalZero5": return padDecimal(value, 5);
47
- case "lowerLetter": return toRepeatedLetter(value, 97);
48
- case "upperLetter": return toRepeatedLetter(value, 65);
49
- case "lowerRoman": return toRoman(value).toLowerCase();
50
- case "upperRoman": return toRoman(value);
51
- case "bullet": return "•";
52
- case "none": return "";
53
- default: return String(value);
54
- }
55
- };
56
11
  const computeListMarker = (paragraph, { numbering, listCounters, abstractCounters, restartedNumIds, previousList, siblingNumIdsByAbstractNumId }) => {
57
12
  const listRendering = paragraph.listRendering;
58
13
  if (!listRendering || !numbering) {
@@ -137,7 +92,7 @@ const computeListMarker = (paragraph, { numbering, listCounters, abstractCounter
137
92
  if (computedMarker.includes(placeholder)) {
138
93
  const value = counters[lvl] ?? 0;
139
94
  const levelInfo = numbering.getLevel(numId, lvl);
140
- const formatted = formatNumber(value, useLegalNumbering ? "decimal" : levelInfo?.numFmt || "decimal");
95
+ const formatted = formatOoxmlCounter(value, useLegalNumbering ? "decimal" : levelInfo?.numFmt || "decimal");
141
96
  computedMarker = computedMarker.replaceAll(placeholder, formatted);
142
97
  }
143
98
  }
@@ -188,7 +188,7 @@ declare function isTocField(field: document_d_exports.Field): boolean;
188
188
  * @param instruction - Parsed instruction for format switches
189
189
  * @returns Formatted page number string
190
190
  */
191
- declare function computePageNumber(pageNumber: number, instruction?: ParsedFieldInstruction): string;
191
+ declare function computePageNumber(pageNumber: number, instruction?: ParsedFieldInstruction, sectionFormat?: string): string;
192
192
  /**
193
193
  * Format a date according to a format string
194
194
  *
@@ -345,16 +345,24 @@ function isTocField(field) {
345
345
  * @param instruction - Parsed instruction for format switches
346
346
  * @returns Formatted page number string
347
347
  */
348
- function computePageNumber(pageNumber, instruction) {
349
- if (!instruction) return String(pageNumber);
350
- const format = getFormatSwitch(instruction);
351
- if (!format) return String(pageNumber);
348
+ function computePageNumber(pageNumber, instruction, sectionFormat) {
349
+ const format = instruction ? getFormatSwitch(instruction) : void 0;
350
+ if (!format || format.toUpperCase() === "MERGEFORMAT") return formatSectionPageNumber(pageNumber, sectionFormat);
352
351
  switch (format.toUpperCase()) {
353
352
  case "ROMAN": return toRoman(pageNumber);
354
353
  case "ALPHABETIC": return toLetter(pageNumber);
355
354
  default: return String(pageNumber);
356
355
  }
357
356
  }
357
+ function formatSectionPageNumber(pageNumber, format) {
358
+ switch (format) {
359
+ case "upperRoman": return toRoman(pageNumber);
360
+ case "lowerRoman": return toRoman(pageNumber).toLowerCase();
361
+ case "upperLetter": return toLetter(pageNumber);
362
+ case "lowerLetter": return toLetter(pageNumber).toLowerCase();
363
+ default: return String(pageNumber);
364
+ }
365
+ }
358
366
  /**
359
367
  * Convert number to uppercase Roman numerals
360
368
  */
@@ -1,4 +1,5 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
+ import { formatOoxmlCounter, padDecimal } from "./ooxmlCounterFormatter.js";
2
3
  //#region src/docx/numberingParser.d.ts
3
4
  /**
4
5
  * Map of rId to numbering definitions
@@ -46,16 +47,6 @@ declare function computeListRendering(numPr: {
46
47
  }, numbering: NumberingMap): (document_d_exports.ListRendering & {
47
48
  levelStarts: number[];
48
49
  }) | null;
49
- /**
50
- * Format a number according to the specified format
51
- *
52
- * @param num - The number to format
53
- * @param format - The number format
54
- * @returns Formatted string
55
- */
56
- declare function formatNumber(num: number, format: document_d_exports.NumberFormat): string;
57
- /** Zero-pad a counter to `width` digits ("decimalZero" family, §17.18.59). */
58
- declare function padDecimal(num: number, width: number): string;
59
50
  /**
60
51
  * Compare two `numPr` references by value — `numId` plus `ilvl` (a missing
61
52
  * `ilvl` is level 0). Used for the style-sourced-numbering provenance check;
@@ -89,4 +80,4 @@ declare function getBulletCharacter(level: document_d_exports.ListLevel): string
89
80
  */
90
81
  declare function isBulletLevel(level: document_d_exports.ListLevel): boolean;
91
82
  //#endregion
92
- export { NumberingMap, computeListRendering, createNumberingMap, formatNumber, getBulletCharacter, getCachedNumberingMap, isBulletLevel, numPrEqual, padDecimal, parseNumbering, renderListMarker };
83
+ export { NumberingMap, computeListRendering, createNumberingMap, formatOoxmlCounter as formatNumber, getBulletCharacter, getCachedNumberingMap, isBulletLevel, numPrEqual, padDecimal, parseNumbering, renderListMarker };
@@ -1,3 +1,4 @@
1
+ import { formatOoxmlCounter, padDecimal } from "./ooxmlCounterFormatter.js";
1
2
  import { FontHintSchema, LevelSuffixSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
2
3
  import { findChild, findChildren, getAttribute, parseBooleanElement, parseNumericAttribute, parseXmlDocument } from "./xmlParser.js";
3
4
  //#region src/docx/numberingParser.ts
@@ -614,37 +615,6 @@ function computeListRendering(numPr, numbering) {
614
615
  return rendering;
615
616
  }
616
617
  /**
617
- * Format a number according to the specified format
618
- *
619
- * @param num - The number to format
620
- * @param format - The number format
621
- * @returns Formatted string
622
- */
623
- function formatNumber(num, format) {
624
- switch (format) {
625
- case "decimal": return num.toString();
626
- case "decimalZero": return padDecimal(num, 2);
627
- case "decimalZero3": return padDecimal(num, 3);
628
- case "decimalZero4": return padDecimal(num, 4);
629
- case "decimalZero5": return padDecimal(num, 5);
630
- case "upperRoman": return toRoman(num).toUpperCase();
631
- case "lowerRoman": return toRoman(num).toLowerCase();
632
- case "upperLetter": return toLetter(num).toUpperCase();
633
- case "lowerLetter": return toLetter(num).toLowerCase();
634
- case "ordinal": return toOrdinal(num);
635
- case "bullet": return "•";
636
- case "none": return "";
637
- case "decimalEnclosedParen": return `(${num})`;
638
- case "numberInDash": return `-${num}-`;
639
- default: return num.toString();
640
- }
641
- }
642
- /** Zero-pad a counter to `width` digits ("decimalZero" family, §17.18.59). */
643
- function padDecimal(num, width) {
644
- if (num < 0) return num.toString();
645
- return num.toString().padStart(width, "0");
646
- }
647
- /**
648
618
  * Compare two `numPr` references by value — `numId` plus `ilvl` (a missing
649
619
  * `ilvl` is level 0). Used for the style-sourced-numbering provenance check;
650
620
  * avoids JSON.stringify equality, which is sensitive to key order.
@@ -654,52 +624,6 @@ function numPrEqual(a, b) {
654
624
  return a.numId === b.numId && (a.ilvl ?? 0) === (b.ilvl ?? 0);
655
625
  }
656
626
  /**
657
- * Convert number to Roman numerals
658
- */
659
- function toRoman(num) {
660
- if (num <= 0 || num > 3999) return num.toString();
661
- const romanNumerals = [
662
- [1e3, "m"],
663
- [900, "cm"],
664
- [500, "d"],
665
- [400, "cd"],
666
- [100, "c"],
667
- [90, "xc"],
668
- [50, "l"],
669
- [40, "xl"],
670
- [10, "x"],
671
- [9, "ix"],
672
- [5, "v"],
673
- [4, "iv"],
674
- [1, "i"]
675
- ];
676
- let result = "";
677
- let remaining = num;
678
- for (const [value, numeral] of romanNumerals) while (remaining >= value) {
679
- result += numeral;
680
- remaining -= value;
681
- }
682
- return result;
683
- }
684
- function toLetter(num) {
685
- if (num <= 0) return "";
686
- const zeroBased = num - 1;
687
- return String.fromCodePoint(97 + zeroBased % 26).repeat(Math.floor(zeroBased / 26) + 1);
688
- }
689
- /**
690
- * Convert number to ordinal (1st, 2nd, 3rd, ...)
691
- */
692
- function toOrdinal(num) {
693
- const suffix = [
694
- "th",
695
- "st",
696
- "nd",
697
- "rd"
698
- ];
699
- const v = num % 100;
700
- return num + (suffix[(v - 20) % 10] ?? suffix[v] ?? suffix[0] ?? "th");
701
- }
702
- /**
703
627
  * Render list marker text by replacing placeholders with formatted numbers
704
628
  *
705
629
  * @param lvlText - The level text pattern (e.g., "%1.", "%1.%2")
@@ -713,7 +637,7 @@ function renderListMarker(lvlText, counters, formats) {
713
637
  const placeholder = `%${i}`;
714
638
  if (result.includes(placeholder)) {
715
639
  const counterIndex = i - 1;
716
- const formatted = formatNumber(counters[counterIndex] ?? 1, formats[counterIndex] ?? "decimal");
640
+ const formatted = formatOoxmlCounter(counters[counterIndex] ?? 1, formats[counterIndex] ?? "decimal");
717
641
  result = result.replaceAll(placeholder, formatted);
718
642
  }
719
643
  }
@@ -742,4 +666,4 @@ function isBulletLevel(level) {
742
666
  return level.numFmt === "bullet" || level.numFmt === "none";
743
667
  }
744
668
  //#endregion
745
- export { computeListRendering, createNumberingMap, formatNumber, getBulletCharacter, getCachedNumberingMap, isBulletLevel, numPrEqual, padDecimal, parseNumbering, renderListMarker };
669
+ export { computeListRendering, createNumberingMap, formatOoxmlCounter as formatNumber, getBulletCharacter, getCachedNumberingMap, isBulletLevel, numPrEqual, padDecimal, parseNumbering, renderListMarker };
@@ -0,0 +1,8 @@
1
+ import { document_d_exports } from "../types/document.js";
2
+ //#region src/docx/ooxmlCounterFormatter.d.ts
3
+ /** Zero-pad a counter to `width` digits (the `decimalZero` family). */
4
+ declare const padDecimal: (value: number, width: number) => string;
5
+ /** Format one OOXML numbering counter using Microsoft Word display semantics. */
6
+ declare const formatOoxmlCounter: (value: number, format: document_d_exports.NumberFormat | undefined) => string;
7
+ //#endregion
8
+ export { formatOoxmlCounter, padDecimal };
@@ -0,0 +1,134 @@
1
+ //#region src/docx/ooxmlCounterFormatter.ts
2
+ const ROMAN_PAIRS = [
3
+ [1e3, "M"],
4
+ [900, "CM"],
5
+ [500, "D"],
6
+ [400, "CD"],
7
+ [100, "C"],
8
+ [90, "XC"],
9
+ [50, "L"],
10
+ [40, "XL"],
11
+ [10, "X"],
12
+ [9, "IX"],
13
+ [5, "V"],
14
+ [4, "IV"],
15
+ [1, "I"]
16
+ ];
17
+ const LATIN_LOWER_SEQUENCE = [..."abcdefghijklmnopqrstuvwxyz"];
18
+ const ARABIC_ALPHA_SEQUENCE = [..."أبتثجحخدذرزسشصضطظعغفقكلمنهوي"];
19
+ const ARABIC_ABJAD_SEQUENCE = [..."أبجدهوزحطيكلمنسعفصقرشتثخذضظغ"];
20
+ const ZERO_WIDTH_NON_JOINER = "‌";
21
+ const MAX_REPEATED_COUNTER_LENGTH = 1024;
22
+ const toRoman = (value) => {
23
+ if (value <= 0 || value > 3999) return String(value);
24
+ let remaining = value;
25
+ let result = "";
26
+ for (const [number, numeral] of ROMAN_PAIRS) while (remaining >= number) {
27
+ result += numeral;
28
+ remaining -= number;
29
+ }
30
+ return result;
31
+ };
32
+ const toRepeatedSequence = (value, sequence) => {
33
+ if (!Number.isSafeInteger(value) || value <= 0) return "";
34
+ const zeroBased = value - 1;
35
+ const character = sequence.at(zeroBased % sequence.length);
36
+ const repetitions = Math.floor(zeroBased / sequence.length) + 1;
37
+ if (repetitions > MAX_REPEATED_COUNTER_LENGTH) return;
38
+ return character?.repeat(repetitions) ?? "";
39
+ };
40
+ const toOrdinal = (value) => {
41
+ const suffixes = [
42
+ "th",
43
+ "st",
44
+ "nd",
45
+ "rd"
46
+ ];
47
+ const remainder = value % 100;
48
+ return `${value}${suffixes[(remainder - 20) % 10] ?? suffixes[remainder] ?? "th"}`;
49
+ };
50
+ /** Zero-pad a counter to `width` digits (the `decimalZero` family). */
51
+ const padDecimal = (value, width) => {
52
+ if (value < 0) return String(value);
53
+ return String(value).padStart(width, "0");
54
+ };
55
+ /** Format one OOXML numbering counter using Microsoft Word display semantics. */
56
+ const formatOoxmlCounter = (value, format) => {
57
+ if (!Number.isFinite(value)) return "";
58
+ switch (format ?? "decimal") {
59
+ case "decimal":
60
+ case "cardinalText":
61
+ case "ordinalText":
62
+ case "hex":
63
+ case "chicago":
64
+ case "ideographDigital":
65
+ case "japaneseCounting":
66
+ case "aiueo":
67
+ case "iroha":
68
+ case "decimalFullWidth":
69
+ case "decimalHalfWidth":
70
+ case "japaneseLegal":
71
+ case "japaneseDigitalTenThousand":
72
+ case "decimalEnclosedCircle":
73
+ case "decimalFullWidth2":
74
+ case "aiueoFullWidth":
75
+ case "irohaFullWidth":
76
+ case "ganada":
77
+ case "chosung":
78
+ case "decimalEnclosedFullstop":
79
+ case "decimalEnclosedCircleChinese":
80
+ case "ideographEnclosedCircle":
81
+ case "ideographTraditional":
82
+ case "ideographZodiac":
83
+ case "ideographZodiacTraditional":
84
+ case "taiwaneseCounting":
85
+ case "ideographLegalTraditional":
86
+ case "taiwaneseCountingThousand":
87
+ case "taiwaneseDigital":
88
+ case "chineseCounting":
89
+ case "chineseLegalSimplified":
90
+ case "chineseCountingThousand":
91
+ case "koreanDigital":
92
+ case "koreanCounting":
93
+ case "koreanLegal":
94
+ case "koreanDigital2":
95
+ case "vietnameseCounting":
96
+ case "russianLower":
97
+ case "russianUpper":
98
+ case "hebrew1":
99
+ case "hebrew2":
100
+ case "hindiVowels":
101
+ case "hindiConsonants":
102
+ case "hindiNumbers":
103
+ case "hindiCounting":
104
+ case "thaiLetters":
105
+ case "thaiNumbers":
106
+ case "thaiCounting": return String(value);
107
+ case "decimalZero": return padDecimal(value, 2);
108
+ case "decimalZero3": return padDecimal(value, 3);
109
+ case "decimalZero4": return padDecimal(value, 4);
110
+ case "decimalZero5": return padDecimal(value, 5);
111
+ case "upperRoman": return toRoman(value);
112
+ case "lowerRoman": return toRoman(value).toLowerCase();
113
+ case "upperLetter": return (toRepeatedSequence(value, LATIN_LOWER_SEQUENCE) ?? String(value)).toUpperCase();
114
+ case "lowerLetter": return toRepeatedSequence(value, LATIN_LOWER_SEQUENCE) ?? String(value);
115
+ case "ordinal": return toOrdinal(value);
116
+ case "bullet": return "•";
117
+ case "none": return "";
118
+ case "decimalEnclosedParen": return `(${value})`;
119
+ case "numberInDash": return `-${value}-`;
120
+ case "arabicAlpha": {
121
+ const counter = toRepeatedSequence(value, ARABIC_ALPHA_SEQUENCE);
122
+ if (counter === void 0) return String(value);
123
+ return counter ? `${counter}${ZERO_WIDTH_NON_JOINER}` : "";
124
+ }
125
+ case "arabicAbjad": {
126
+ const counter = toRepeatedSequence(value, ARABIC_ABJAD_SEQUENCE);
127
+ if (counter === void 0) return String(value);
128
+ return counter ? `${ZERO_WIDTH_NON_JOINER}${counter}` : "";
129
+ }
130
+ default: return String(value);
131
+ }
132
+ };
133
+ //#endregion
134
+ export { formatOoxmlCounter, padDecimal };
@@ -134,7 +134,7 @@ function serializeTableFormatting(formatting, propertyChanges) {
134
134
  if (formatting.styleId) parts.push(`<w:tblStyle w:val="${escapeXml(formatting.styleId)}"/>`);
135
135
  const floatingXml = serializeFloatingTableProperties(formatting.floating);
136
136
  if (floatingXml) parts.push(floatingXml);
137
- if (formatting.bidi) parts.push("<w:bidiVisual/>");
137
+ if (formatting.bidi !== void 0) parts.push(formatting.bidi ? "<w:bidiVisual/>" : "<w:bidiVisual w:val=\"0\"/>");
138
138
  const widthXml = serializeMeasurement(formatting.width, "tblW");
139
139
  if (widthXml) parts.push(widthXml);
140
140
  if (formatting.justification) parts.push(`<w:jc w:val="${formatting.justification}"/>`);
@@ -284,7 +284,8 @@ function parseTableProperties(tblPrElement) {
284
284
  }
285
285
  const floating = parseFloatingTableProperties(findChild(tblPrElement, "w", "tblpPr"));
286
286
  if (floating) formatting.floating = floating;
287
- if (parseBooleanElement(findChild(tblPrElement, "w", "bidiVisual"))) formatting.bidi = true;
287
+ const bidiVisual = findChild(tblPrElement, "w", "bidiVisual");
288
+ if (bidiVisual) formatting.bidi = parseBooleanElement(bidiVisual);
288
289
  if (Object.keys(formatting).length === 0) return;
289
290
  return formatting;
290
291
  }
@@ -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
  }
@@ -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,6 +357,10 @@ 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
365
  function paragraphRunDefaults(pmAttrs, theme) {
404
366
  const defaultTextFormatting = pmAttrs.defaultTextFormatting;
@@ -412,8 +374,12 @@ function paragraphRunDefaults(pmAttrs, theme) {
412
374
  if (complexScriptFontFamily) result.complexScriptFontFamily = complexScriptFontFamily;
413
375
  if (defaultTextFormatting.language) result.language = { ...defaultTextFormatting.language };
414
376
  if (defaultTextFormatting.fontSize !== void 0) result.fontSize = defaultTextFormatting.fontSize / 2;
377
+ if (defaultTextFormatting.fontSizeCs !== void 0) result.complexScriptFontSize = defaultTextFormatting.fontSizeCs / 2;
415
378
  if (defaultTextFormatting.bold !== void 0) result.bold = defaultTextFormatting.bold;
379
+ if (defaultTextFormatting.boldCs !== void 0) result.complexScriptBold = defaultTextFormatting.boldCs;
416
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;
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);
@@ -821,7 +787,8 @@ function convertParagraphAttrs(pmAttrs, theme, listCounters, listAbstractCounter
821
787
  if (pmAttrs.widowControl === false) attrs.widowControl = false;
822
788
  if (pmAttrs.contextualSpacing) attrs.contextualSpacing = true;
823
789
  if (pmAttrs.runInWithNext) attrs.runInWithNext = true;
824
- if (directionIsRtl(pmAttrs.direction)) attrs.bidi = true;
790
+ const bidi = directionToBidi(pmAttrs.direction);
791
+ if (bidi !== void 0) attrs.bidi = bidi;
825
792
  if (pmAttrs.styleId) attrs.styleId = pmAttrs.styleId;
826
793
  const propertyChanges = pmAttrs._propertyChanges ?? [];
827
794
  let changedNumberingChange;
@@ -1187,7 +1154,7 @@ function convertTable(node, startPos, options) {
1187
1154
  if (justification) tableBlock.justification = justification;
1188
1155
  if (indentPx !== void 0) tableBlock.indent = indentPx;
1189
1156
  if (floatingPx) tableBlock.floating = floatingPx;
1190
- if (originalFormatting?.bidi) tableBlock.bidi = true;
1157
+ if (attrs._resolvedBidi ?? originalFormatting?.bidi) tableBlock.bidi = true;
1191
1158
  return tableBlock;
1192
1159
  }
1193
1160
  /**
@@ -1462,6 +1429,7 @@ function toFlowBlocks(doc, options = {}) {
1462
1429
  const breakType = secProps?.sectionStart ?? pmAttrs.sectionBreakType;
1463
1430
  if (breakType) sectionBreak.type = breakType;
1464
1431
  if (secProps) {
1432
+ sectionBreak.pageNumbering = getPageNumbering(secProps);
1465
1433
  const documentGridLinePitchTwips = resolveDocumentGridLinePitch(secProps.docGrid);
1466
1434
  if (documentGridLinePitchTwips !== void 0) sectionBreak.documentGridLinePitchTwips = documentGridLinePitchTwips;
1467
1435
  if (secProps.pageWidth || secProps.pageHeight) sectionBreak.pageSize = {