@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
@@ -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;
@@ -1,4 +1,4 @@
1
- import { ColumnLayout, FOOTNOTE_SEPARATOR_HEIGHT, Fragment, Page, PageHeaderFooterRefs, PageMargins } from "./types.js";
1
+ import { ColumnLayout, FOOTNOTE_SEPARATOR_HEIGHT, Fragment, Page, PageHeaderFooterRefs, PageMargins, SectionPageNumbering } from "./types.js";
2
2
  //#region src/layout-engine/paginator.d.ts
3
3
  /**
4
4
  * Current state of a page being laid out.
@@ -45,8 +45,10 @@ type PaginatorOptions = {
45
45
  * first-page header) vs. its body pages. Pages 2+ use `margins`.
46
46
  */
47
47
  firstPageMargins?: PageMargins;
48
- /** Per-section body margins used on even section pages. */
48
+ /** Per-section body margins used on even authored page numbers. */
49
49
  sectionEvenPageMargins?: (PageMargins | undefined)[];
50
+ /** Page-number policy for the initial section. */
51
+ pageNumbering?: SectionPageNumbering;
50
52
  /** Column configuration (optional). */
51
53
  columns?: ColumnLayout;
52
54
  /** Per-page footnote reserved heights (pageNumber → height in pixels). */
@@ -114,7 +116,7 @@ declare function createPaginator(options: PaginatorOptions): {
114
116
  h: number;
115
117
  }, newMargins?: PageMargins, applyImmediately?: boolean) => void;
116
118
  /** Mark the next created page as the first page of a new section. */
117
- startSection: (sectionIndex: number) => void;
119
+ startSection: (sectionIndex: number, pageNumbering?: SectionPageNumbering) => void;
118
120
  };
119
121
  type Paginator = ReturnType<typeof createPaginator>;
120
122
  //#endregion
@@ -35,6 +35,9 @@ function createPaginator(options) {
35
35
  let pendingMargins;
36
36
  let currentSectionIndex = 0;
37
37
  let currentSectionPageNumber = 0;
38
+ let currentPageNumbering = options.pageNumbering ?? { type: "continue" };
39
+ let nextLogicalPageNumber = currentPageNumbering.type === "restart" ? currentPageNumbering.start : 1;
40
+ let sectionStartPending = true;
38
41
  const pages = [];
39
42
  const states = [];
40
43
  function getContentBottom() {
@@ -48,7 +51,7 @@ function createPaginator(options) {
48
51
  }
49
52
  function currentPageUsesActiveLayout(state) {
50
53
  if (pendingPageSize || pendingMargins) return false;
51
- return arePageSizesEqual(state.page.size, pageSize) && areMarginsEqual(state.page.margins, getPageMargins(state.page.number, state.page.sectionPageNumber ?? currentSectionPageNumber));
54
+ return arePageSizesEqual(state.page.size, pageSize) && areMarginsEqual(state.page.margins, getPageMargins(state.page.number, state.page.logicalNumber));
52
55
  }
53
56
  if (getContentHeight() <= 0) panic("Paginator: page size and margins yield no content area");
54
57
  let columnWidths = calculateColumnWidths(pageSize.w, margins.left, margins.right, columns);
@@ -63,8 +66,8 @@ function createPaginator(options) {
63
66
  if (getContentHeight() <= 0) panic("Paginator: section page size and margins yield no content area");
64
67
  recalculateColumnWidths();
65
68
  }
66
- function getPageMargins(pageNumber, sectionPageNumber) {
67
- const evenMargins = sectionPageNumber % 2 === 0 ? options.sectionEvenPageMargins?.[currentSectionIndex] : void 0;
69
+ function getPageMargins(pageNumber, logicalPageNumber) {
70
+ const evenMargins = logicalPageNumber % 2 === 0 ? options.sectionEvenPageMargins?.[currentSectionIndex] : void 0;
68
71
  const pageMargins = pageNumber === 1 && options.firstPageMargins ? { ...options.firstPageMargins } : { ...evenMargins ?? margins };
69
72
  if (options.mirrorMargins === true && pageNumber % 2 === 0) return {
70
73
  ...pageMargins,
@@ -79,7 +82,7 @@ function createPaginator(options) {
79
82
  * Get X position for a given column index.
80
83
  */
81
84
  function getColumnX(columnIndex) {
82
- let x = states.at(-1)?.page.margins.left ?? getPageMargins(1, 1).left;
85
+ let x = states.at(-1)?.page.margins.left ?? getPageMargins(1, nextLogicalPageNumber).left;
83
86
  for (let index = 0; index < columnIndex; index++) x += (columnWidths[index] ?? columnWidths[0] ?? 0) + gapAfterColumn(columns, index);
84
87
  return x;
85
88
  }
@@ -89,14 +92,19 @@ function createPaginator(options) {
89
92
  function createNewPage() {
90
93
  if (pendingPageSize || pendingMargins) applyPendingLayout();
91
94
  const pageNumber = pages.length + 1;
95
+ const logicalNumber = nextLogicalPageNumber;
96
+ nextLogicalPageNumber += 1;
97
+ sectionStartPending = false;
92
98
  currentSectionPageNumber += 1;
93
- const pageMargins = getPageMargins(pageNumber, currentSectionPageNumber);
99
+ const pageMargins = getPageMargins(pageNumber, logicalNumber);
94
100
  const topMargin = pageMargins.top;
95
101
  const contentBottom = pageSize.h - pageMargins.bottom;
96
102
  const footnoteHeight = options.footnoteReservedHeights?.get(pageNumber) ?? 0;
97
103
  const pageContentBottom = contentBottom - footnoteHeight;
98
104
  const page = {
99
105
  number: pageNumber,
106
+ logicalNumber,
107
+ ...currentPageNumbering.format === void 0 ? {} : { logicalNumberFormat: currentPageNumbering.format },
100
108
  fragments: [],
101
109
  margins: pageMargins,
102
110
  size: { ...pageSize },
@@ -236,12 +244,12 @@ function createPaginator(options) {
236
244
  */
237
245
  function forcePageBreak(breakOptions = {}) {
238
246
  const current = states.at(-1);
239
- if (breakOptions.coalesceBlankPage && current && current.page.fragments.length === 0 && current.cursorY === current.topMargin && currentPageUsesActiveLayout(current)) {
247
+ if (breakOptions.coalesceBlankPage && current && current.page.fragments.length === 0 && current.cursorY === current.topMargin) {
240
248
  if (current.page.sectionIndex !== currentSectionIndex) {
241
- currentSectionPageNumber = 1;
242
- applySectionMetadata(current.page);
249
+ retargetCurrentBlankPage();
250
+ return current;
243
251
  }
244
- return current;
252
+ if (currentPageUsesActiveLayout(current)) return current;
245
253
  }
246
254
  return createNewPage();
247
255
  }
@@ -249,7 +257,8 @@ function createPaginator(options) {
249
257
  const current = states.at(-1);
250
258
  if (!current || current.page.fragments.length > 0 || current.cursorY !== current.topMargin) return false;
251
259
  if (pendingPageSize || pendingMargins) applyPendingLayout();
252
- const pageMargins = getPageMargins(current.page.number, 1);
260
+ retargetSectionMetadata(current.page);
261
+ const pageMargins = getPageMargins(current.page.number, current.page.logicalNumber);
253
262
  const topMargin = pageMargins.top;
254
263
  const rawContentBottom = pageSize.h - pageMargins.bottom;
255
264
  const footnoteHeight = options.footnoteReservedHeights?.get(current.page.number) ?? 0;
@@ -267,8 +276,6 @@ function createPaginator(options) {
267
276
  current.contentBottom = rawContentBottom - footnoteHeight;
268
277
  current.trailingSpacing = 0;
269
278
  columnRegionTop = topMargin;
270
- currentSectionPageNumber = 1;
271
- applySectionMetadata(current.page);
272
279
  return true;
273
280
  }
274
281
  /**
@@ -310,9 +317,23 @@ function createPaginator(options) {
310
317
  pendingPageSize = void 0;
311
318
  pendingMargins = void 0;
312
319
  }
313
- function startSection(sectionIndex) {
320
+ function startSection(sectionIndex, pageNumbering = { type: "continue" }) {
314
321
  currentSectionIndex = sectionIndex;
315
322
  currentSectionPageNumber = 0;
323
+ currentPageNumbering = pageNumbering;
324
+ sectionStartPending = true;
325
+ if (pageNumbering.type === "restart") nextLogicalPageNumber = pageNumbering.start;
326
+ }
327
+ function retargetSectionMetadata(page) {
328
+ currentSectionPageNumber = 1;
329
+ if (sectionStartPending && currentPageNumbering.type === "restart") {
330
+ page.logicalNumber = currentPageNumbering.start;
331
+ nextLogicalPageNumber = currentPageNumbering.start + 1;
332
+ }
333
+ sectionStartPending = false;
334
+ if (currentPageNumbering.format === void 0) delete page.logicalNumberFormat;
335
+ else page.logicalNumberFormat = currentPageNumbering.format;
336
+ applySectionMetadata(page);
316
337
  }
317
338
  function applySectionMetadata(page) {
318
339
  page.sectionIndex = currentSectionIndex;
@@ -1,7 +1,7 @@
1
1
  import { OutlineStyleAttr } from "../types/documentEnumValues.js";
2
2
  import { ImagePosition, ImageWrap, SdtProperties, SdtType, ShapeTextBody, TableCellFormatting, TableWidthType } from "@stll/docx-core/model";
3
3
  declare namespace types_d_exports {
4
- export { 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 };
4
+ export { 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 };
5
5
  }
6
6
  /**
7
7
  * Unique identifier for a block in the document.
@@ -46,6 +46,14 @@ type RunFormatting = {
46
46
  * does NOT do and is why this slot has to reach layout at all.
47
47
  */
48
48
  complexScriptFontFamily?: string;
49
+ /** Independent complex-script size (`w:szCs`), in points. */
50
+ complexScriptFontSize?: number;
51
+ /** Independent complex-script weight (`w:bCs`). */
52
+ complexScriptBold?: boolean;
53
+ /** Independent complex-script slant (`w:iCs`). */
54
+ complexScriptItalic?: boolean;
55
+ /** Force the complex-script formatting slot for every character (`w:cs`). */
56
+ forceComplexScript?: boolean;
49
57
  /** Run language metadata resolved from `w:lang`. */
50
58
  language?: {
51
59
  val?: string;
@@ -119,6 +127,7 @@ type RunFormatting = {
119
127
  /** Groups a suggestion's runs for accept/reject and scroll-to. */
120
128
  suggestionId?: string;
121
129
  };
130
+ type ListMarkerFormatting = Pick<RunFormatting, "fontFamily" | "eastAsiaFontFamily" | "complexScriptFontFamily" | "fontSize" | "complexScriptFontSize" | "bold" | "complexScriptBold" | "italic" | "complexScriptItalic" | "rtl" | "forceComplexScript">;
122
131
  /**
123
132
  * Hyperlink information for a run.
124
133
  */
@@ -453,15 +462,14 @@ type ParagraphAttrs = {
453
462
  listMarker?: string;
454
463
  listIsBullet?: boolean;
455
464
  listMarkerHidden?: boolean;
456
- listMarkerFontFamily?: string;
457
- listMarkerFontSize?: number;
465
+ /** Canonical numbering-level marker typography, resolved for layout. */
466
+ listMarkerFormatting?: ListMarkerFormatting;
458
467
  /**
459
468
  * Effective paragraph-mark size used only to measure a visible list
460
469
  * paragraph's final line. Word lets the inherited complex-script size raise
461
470
  * that line box without enlarging the marker or Western text glyphs.
462
471
  */
463
472
  listParagraphMarkFontSize?: number;
464
- listMarkerBold?: boolean;
465
473
  /** Horizontal alignment of the marker around the paragraph's list anchor. */
466
474
  listMarkerAlignment?: "left" | "center" | "right";
467
475
  /**
@@ -689,9 +697,20 @@ type SectionBreakBlock = {
689
697
  orientation?: "portrait" | "landscape";
690
698
  margins?: PageMargins;
691
699
  columns?: ColumnLayout;
700
+ /** Authored page-number continuation or restart policy for the section ending here. */
701
+ pageNumbering?: SectionPageNumbering;
692
702
  /** Line pitch authored by the section properties that end at this boundary, in twips. */
693
703
  documentGridLinePitchTwips?: number;
694
704
  };
705
+ /** Normalized section page-number policy. An omitted OOXML start continues numbering. */
706
+ type SectionPageNumbering = {
707
+ type: "continue";
708
+ format?: string;
709
+ } | {
710
+ type: "restart";
711
+ start: number;
712
+ format?: string;
713
+ };
695
714
  type PageHeaderFooterRefs = {
696
715
  titlePg?: boolean;
697
716
  evenAndOddHeaders?: boolean;
@@ -1057,8 +1076,12 @@ type PageMargins = {
1057
1076
  * A rendered page containing positioned fragments.
1058
1077
  */
1059
1078
  type Page = {
1060
- /** Page number (1-indexed). */
1079
+ /** Physical page number (1-indexed), used for navigation and page counts. */
1061
1080
  number: number;
1081
+ /** Authored page number shown by PAGE fields. */
1082
+ logicalNumber: number;
1083
+ /** OOXML number format for this page's section. */
1084
+ logicalNumberFormat?: string;
1062
1085
  /** Fragments positioned on this page. */
1063
1086
  fragments: Fragment[];
1064
1087
  /** Page margins. */
@@ -1167,8 +1190,10 @@ type LayoutOptions = {
1167
1190
  * same extension.
1168
1191
  */
1169
1192
  firstPageMargins?: PageMargins;
1170
- /** Per-section body margins used on even section pages. */
1193
+ /** Per-section body margins used on even authored page numbers. */
1171
1194
  sectionEvenPageMargins?: (PageMargins | undefined)[];
1195
+ /** Page-number policy for the initial section. */
1196
+ pageNumbering?: SectionPageNumbering;
1172
1197
  /** Body-level final section page size. */
1173
1198
  finalPageSize?: {
1174
1199
  w: number;
@@ -1178,6 +1203,8 @@ type LayoutOptions = {
1178
1203
  finalMargins?: PageMargins;
1179
1204
  /** Body-level final section column configuration. */
1180
1205
  finalColumns?: ColumnLayout;
1206
+ /** Body-level final section page-number policy. */
1207
+ finalPageNumbering?: SectionPageNumbering;
1181
1208
  /** Column configuration. */
1182
1209
  columns?: ColumnLayout;
1183
1210
  /** Gap between rendered pages (for UI). */
@@ -1353,4 +1380,4 @@ declare function tableColumnsArePinned(table: TableBlock): boolean;
1353
1380
  /** Return the leading visual offset for a row with omitted grid columns. */
1354
1381
  declare const getTableRowLeadingWidth: (row: TableRow, columnWidths: readonly number[]) => number;
1355
1382
  //#endregion
1356
- export { 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, types_d_exports };
1383
+ export { 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, types_d_exports };
@@ -81,7 +81,8 @@ var LayoutPainter = class {
81
81
  for (const i_item of pages) {
82
82
  const page = i_item;
83
83
  const context = {
84
- pageNumber: page.number,
84
+ pageNumber: page.logicalNumber,
85
+ ...page.logicalNumberFormat === void 0 ? {} : { pageNumberFormat: page.logicalNumberFormat },
85
86
  totalPages: this.totalPages,
86
87
  section: "body"
87
88
  };
@@ -601,6 +601,7 @@ function renderFootnoteContent(footnote, contentWidth, doc, context) {
601
601
  wrapper.style.height = `${content.height}px`;
602
602
  const renderContext = {
603
603
  pageNumber: context?.pageNumber ?? 0,
604
+ ...context?.pageNumberFormat === void 0 ? {} : { pageNumberFormat: context.pageNumberFormat },
604
605
  totalPages: context?.totalPages ?? 0,
605
606
  section: context?.section ?? "body",
606
607
  contentWidth
@@ -1133,8 +1134,7 @@ function applySectionHeaderFooterOptions(page, pageOptions, options) {
1133
1134
  if (!refs) return false;
1134
1135
  const isFirstSectionPage = page.sectionPageNumber === 1;
1135
1136
  const useFirst = refs.titlePg === true && isFirstSectionPage;
1136
- const sectionPageNumber = page.sectionPageNumber ?? page.number;
1137
- const useEven = refs.evenAndOddHeaders === true && sectionPageNumber % 2 === 0;
1137
+ const useEven = refs.evenAndOddHeaders === true && page.logicalNumber % 2 === 0;
1138
1138
  const headerRId = (() => {
1139
1139
  if (useFirst) return refs.headerFirst;
1140
1140
  if (useEven) return refs.headerEven;
@@ -1176,7 +1176,8 @@ function sectionPagesContext(page, counts) {
1176
1176
  */
1177
1177
  function buildPageRenderArgs(page, totalPages, options) {
1178
1178
  const context = {
1179
- pageNumber: page.number,
1179
+ pageNumber: page.logicalNumber,
1180
+ ...page.logicalNumberFormat === void 0 ? {} : { pageNumberFormat: page.logicalNumberFormat },
1180
1181
  totalPages,
1181
1182
  section: "body",
1182
1183
  ...options.bookmarkPages ? { bookmarkPages: options.bookmarkPages } : {},
@@ -1224,6 +1225,7 @@ function computePageFingerprintInternal(page, blockLookup, options) {
1224
1225
  parts.push(`m:${pageMarginsFingerprint(page.margins)}`);
1225
1226
  if (page.authoredMargins) parts.push(`am:${pageMarginsFingerprint(page.authoredMargins)}`);
1226
1227
  parts.push(`n:${page.number}`);
1228
+ parts.push(`ln:${page.logicalNumber},${page.logicalNumberFormat ?? ""}`);
1227
1229
  if (page.sectionIndex !== void 0) parts.push(`si:${page.sectionIndex}`);
1228
1230
  if (page.sectionPageNumber !== void 0) parts.push(`sp:${page.sectionPageNumber}`);
1229
1231
  if (page.headerFooterRefs) parts.push(`hf:${JSON.stringify(page.headerFooterRefs)}`);
@@ -1334,6 +1336,10 @@ function runContentKey(run) {
1334
1336
  if (run.highlight) parts.push(`hi:${run.highlight}`);
1335
1337
  if (run.fontFamily) parts.push(`ff:${run.fontFamily}`);
1336
1338
  if (run.complexScriptFontFamily) parts.push(`cs:${run.complexScriptFontFamily}`);
1339
+ if (run.complexScriptFontSize !== void 0) parts.push(`cs-size:${run.complexScriptFontSize}`);
1340
+ if (run.complexScriptBold !== void 0) parts.push(`cs-bold:${run.complexScriptBold}`);
1341
+ if (run.complexScriptItalic !== void 0) parts.push(`cs-italic:${run.complexScriptItalic}`);
1342
+ if (run.forceComplexScript !== void 0) parts.push(`cs-force:${run.forceComplexScript}`);
1337
1343
  if (run.eastAsiaFontFamily) parts.push(`ea:${run.eastAsiaFontFamily}`);
1338
1344
  if (run.fontSize !== void 0) parts.push(`fs:${run.fontSize}`);
1339
1345
  if (run.letterSpacing !== void 0) parts.push(`ls:${run.letterSpacing}`);