@stll/folio-core 0.23.1 → 0.25.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 (45) hide show
  1. package/dist/controller/fontReadiness.js +12 -7
  2. package/dist/controller/layoutPipeline.js +3 -4
  3. package/dist/docx/fieldParser.d.ts +1 -1
  4. package/dist/docx/fieldParser.js +2 -9
  5. package/dist/docx/numberingParser.d.ts +2 -1
  6. package/dist/docx/numberingParser.js +24 -80
  7. package/dist/docx/paragraphParser.js +3 -4
  8. package/dist/docx/paragraphTextBoxEnrichment.js +1 -0
  9. package/dist/docx/sectionParser.js +2 -2
  10. package/dist/docx/serializer/runSerializer.js +1 -0
  11. package/dist/docx/textBoxParser.js +4 -0
  12. package/dist/fields/fieldContext.d.ts +2 -1
  13. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +9 -1
  14. package/dist/layout-bridge/convert/headerFooterLayout.js +27 -2
  15. package/dist/layout-bridge/convert/toFlowBlocks.js +12 -16
  16. package/dist/layout-bridge/engine/selectionRects.js +3 -5
  17. package/dist/layout-engine/index.d.ts +2 -2
  18. package/dist/layout-engine/index.js +3 -5
  19. package/dist/layout-engine/measure/cache.js +4 -0
  20. package/dist/layout-engine/measure/listMarkerWidth.d.ts +3 -1
  21. package/dist/layout-engine/measure/listMarkerWidth.js +25 -21
  22. package/dist/layout-engine/measure/measureBlocks.js +27 -11
  23. package/dist/layout-engine/measure/measureParagraph.js +7 -1
  24. package/dist/layout-engine/measure/tableCellFloating.js +2 -3
  25. package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
  26. package/dist/layout-engine/tableRowBreak.js +2 -2
  27. package/dist/layout-engine/types.d.ts +16 -11
  28. package/dist/layout-engine/types.js +15 -1
  29. package/dist/layout-painter/renderParagraph.d.ts +9 -1
  30. package/dist/layout-painter/renderParagraph.js +65 -52
  31. package/dist/layout-painter/renderTable.js +2 -5
  32. package/dist/layout-painter/renderTextBox.js +1 -1
  33. package/dist/layout-painter/renderUtils.d.ts +2 -1
  34. package/dist/prosemirror/attrs/index.js +4 -6
  35. package/dist/prosemirror/conversion/fromProseDoc.js +2 -3
  36. package/dist/prosemirror/conversion/toProseDoc.js +3 -3
  37. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -3
  38. package/dist/prosemirror/extensions/features/ListExtension.js +1 -3
  39. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -0
  40. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
  41. package/dist/prosemirror/schema/nodes.d.ts +5 -7
  42. package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -3
  43. package/dist/utils/paragraphBaseDirection.d.ts +6 -0
  44. package/dist/utils/paragraphBaseDirection.js +12 -0
  45. package/package.json +2 -2
@@ -186,6 +186,10 @@ function hashParagraphBlock(block) {
186
186
  if (attrs.reserveEmptyOutlineHeight) parts.push("outline-empty-reserve");
187
187
  if (attrs.documentGridLinePitch !== void 0) parts.push(`documentGrid:${attrs.documentGridLinePitch}|${attrs.snapToGrid !== false}`);
188
188
  if (attrs.justificationCompatibility) parts.push(`justify-compat:${attrs.justificationCompatibility.type}`);
189
+ if (attrs.listMarker !== void 0) {
190
+ const marker = attrs.listMarkerFormatting;
191
+ parts.push(`marker:${attrs.listMarker}|${attrs.listMarkerHidden}|${attrs.listMarkerAlignment}|${attrs.listMarkerSuffix}|${marker?.fontFamily}|${marker?.eastAsiaFontFamily}|${marker?.complexScriptFontFamily}|${marker?.fontSize}|${marker?.complexScriptFontSize}|${marker?.bold}|${marker?.complexScriptBold}|${marker?.italic}|${marker?.complexScriptItalic}|${marker?.rtl}|${marker?.forceComplexScript}`);
192
+ }
189
193
  parts.push(...lineBreakPolicyCacheParts(attrs));
190
194
  const borders = attrs.borders;
191
195
  if (borders) {
@@ -8,7 +8,7 @@ import { ParagraphBlock } from "../types.js";
8
8
  declare const DEFAULT_TAB_STOP_TWIPS = 720;
9
9
  /**
10
10
  * Marker font resolution per ECMA-376 §17.9.6:
11
- * 1. explicit numbering-level rPr (`attrs.listMarkerFont*`),
11
+ * 1. explicit numbering-level rPr (`attrs.listMarkerFormatting`),
12
12
  * 2. first body text run's font,
13
13
  * 3. paragraph defaults, then document defaults.
14
14
  */
@@ -16,6 +16,8 @@ declare function resolveListMarkerFont(block: ParagraphBlock): {
16
16
  fontFamily: string;
17
17
  fontSize: number;
18
18
  bold?: boolean;
19
+ italic?: boolean;
20
+ rtl?: boolean;
19
21
  };
20
22
  /**
21
23
  * Compute the marker's inline-block width in pixels, or 0 if the paragraph
@@ -1,3 +1,5 @@
1
+ import { hasCjk, hasComplexScript } from "../../utils/scriptSegments.js";
2
+ import { hasComplexScriptFormatting, resolveComplexScriptFormatting } from "./complexScriptFormatting.js";
1
3
  import { ptToPx } from "./measureHelpers.js";
2
4
  import { measureTextWidth } from "./measureProvider.js";
3
5
  //#region src/layout-engine/measure/listMarkerWidth.ts
@@ -12,21 +14,33 @@ const DEFAULT_TAB_STOP_TWIPS = 720;
12
14
  const TWIPS_TO_PX = 96 / 1440;
13
15
  /**
14
16
  * Marker font resolution per ECMA-376 §17.9.6:
15
- * 1. explicit numbering-level rPr (`attrs.listMarkerFont*`),
17
+ * 1. explicit numbering-level rPr (`attrs.listMarkerFormatting`),
16
18
  * 2. first body text run's font,
17
19
  * 3. paragraph defaults, then document defaults.
18
20
  */
19
21
  function resolveListMarkerFont(block) {
20
22
  const attrs = block.attrs;
21
23
  const firstTextRun = block.runs.find((r) => r.kind === "text");
22
- const fontFamily = attrs?.listMarkerFontFamily ?? firstTextRun?.fontFamily ?? attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY;
23
- const fontSize = attrs?.listMarkerFontSize ?? firstTextRun?.fontSize ?? attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE;
24
- const bold = attrs?.listMarkerBold ?? firstTextRun?.bold;
25
- return {
26
- fontFamily,
27
- fontSize,
28
- ...bold !== void 0 ? { bold } : {}
24
+ const markerFormatting = attrs?.listMarkerFormatting;
25
+ const bold = markerFormatting?.bold ?? firstTextRun?.bold;
26
+ const italic = markerFormatting?.italic ?? firstTextRun?.italic;
27
+ const base = {
28
+ fontFamily: markerFormatting?.fontFamily ?? firstTextRun?.fontFamily ?? attrs?.defaultFontFamily ?? DEFAULT_FONT_FAMILY,
29
+ fontSize: markerFormatting?.fontSize ?? firstTextRun?.fontSize ?? attrs?.defaultFontSize ?? DEFAULT_FONT_SIZE,
30
+ ...bold !== void 0 ? { bold } : {},
31
+ ...italic !== void 0 ? { italic } : {},
32
+ ...markerFormatting?.rtl !== void 0 ? { rtl: markerFormatting.rtl } : {}
29
33
  };
34
+ const marker = attrs?.listMarker ?? "";
35
+ if (markerFormatting && hasComplexScriptFormatting(markerFormatting) && (markerFormatting.forceComplexScript || hasComplexScript(marker))) return {
36
+ ...base,
37
+ ...resolveComplexScriptFormatting(markerFormatting)
38
+ };
39
+ if (markerFormatting?.eastAsiaFontFamily && hasCjk(marker)) return {
40
+ ...base,
41
+ fontFamily: markerFormatting.eastAsiaFontFamily
42
+ };
43
+ return base;
30
44
  }
31
45
  /**
32
46
  * Compute the marker's inline-block width in pixels, or 0 if the paragraph
@@ -49,12 +63,7 @@ function resolveListMarkerFont(block) {
49
63
  function getListMarkerInlineWidth(block) {
50
64
  const attrs = block.attrs;
51
65
  if (!attrs?.listMarker || attrs.listMarkerHidden) return 0;
52
- const { fontFamily, fontSize, bold } = resolveListMarkerFont(block);
53
- const style = {
54
- fontFamily,
55
- fontSize,
56
- ...bold !== void 0 ? { bold } : {}
57
- };
66
+ const style = resolveListMarkerFont(block);
58
67
  const naturalWidth = measureTextWidth(attrs.listMarker, style);
59
68
  const markerEndOffset = getMarkerEndOffset(naturalWidth, attrs.listMarkerAlignment);
60
69
  const suffix = attrs.listMarkerSuffix ?? "tab";
@@ -75,19 +84,14 @@ function getListMarkerInlineWidth(block) {
75
84
  let bodyStart;
76
85
  if (firstCustomPast !== void 0 && firstGridPast !== void 0) bodyStart = Math.min(firstCustomPast, firstGridPast);
77
86
  else bodyStart = firstCustomPast ?? firstGridPast;
78
- if (bodyStart === void 0) return naturalWidth + ptToPx(fontSize) * .5;
87
+ if (bodyStart === void 0) return naturalWidth + ptToPx(style.fontSize ?? DEFAULT_FONT_SIZE) * .5;
79
88
  return bodyStart - markerStartPx;
80
89
  }
81
90
  /** Paint-only offset that places the marker around its authored list anchor. */
82
91
  function getListMarkerVisualOffset(block) {
83
92
  const attrs = block.attrs;
84
93
  if (!attrs?.listMarker || attrs.listMarkerHidden) return 0;
85
- const { fontFamily, fontSize, bold } = resolveListMarkerFont(block);
86
- const naturalWidth = measureTextWidth(attrs.listMarker, {
87
- fontFamily,
88
- fontSize,
89
- ...bold !== void 0 ? { bold } : {}
90
- });
94
+ const naturalWidth = measureTextWidth(attrs.listMarker, resolveListMarkerFont(block));
91
95
  if (attrs.listMarkerAlignment === "right") return -naturalWidth;
92
96
  if (attrs.listMarkerAlignment === "center") return -naturalWidth / 2;
93
97
  return 0;
@@ -2,7 +2,7 @@ import { recordMeasureBlock, recordMeasureBlockError } from "../layoutInstrument
2
2
  import { isParagraphFrameTextBox } from "../paragraphFrame.js";
3
3
  import { bandFragmentX, bandTopContentY, isPageFrameRelativeAnchor } from "../textBoxFlow.js";
4
4
  import { getTextBoxGroupId } from "../textBoxGroup.js";
5
- import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText, tableColumnsArePinned } from "../types.js";
5
+ import { DEFAULT_TEXTBOX_MARGINS, floatingTextBoxReservesBand, floatingTextBoxWrapsText, resolveTableCellPadding, tableColumnsArePinned } from "../types.js";
6
6
  import { getCachedParagraphMeasure, setCachedParagraphMeasure } from "./cache.js";
7
7
  import { resolveFloatingTableX } from "./floatingTablePosition.js";
8
8
  import { findClearLineY, measureParagraph } from "./measureParagraph.js";
@@ -63,8 +63,6 @@ function resolveTableWidthPx(width, widthType, contentWidth) {
63
63
  if (widthType === "dxa" || !widthType || widthType === "auto") return Math.round(width / 20 * 1.333);
64
64
  }
65
65
  function measureTableBlock(tableBlock, contentWidth, fieldValues) {
66
- const DEFAULT_CELL_PADDING_X = 7;
67
- const DEFAULT_CELL_PADDING_Y = 0;
68
66
  let columnWidths = tableBlock.columnWidths ?? [];
69
67
  const explicitWidthPx = resolveTableWidthPx(tableBlock.width, tableBlock.widthType, contentWidth);
70
68
  if (columnWidths.length === 0 && tableBlock.rows.length > 0) {
@@ -89,8 +87,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
89
87
  for (let c = 0; c < colSpan && columnIndex + c < columnWidths.length; c++) cellWidth += columnWidths[columnIndex + c] ?? 0;
90
88
  if (cellWidth === 0) cellWidth = cell.width ?? 100;
91
89
  columnIndex = getFirstAvailableColumn(cellGrid, rowIdx, columnIndex + colSpan);
92
- const padLeft = cell.padding?.left ?? DEFAULT_CELL_PADDING_X;
93
- const padRight = cell.padding?.right ?? DEFAULT_CELL_PADDING_X;
90
+ const { left: padLeft, right: padRight } = resolveTableCellPadding(cell);
94
91
  const cellContentWidth = Math.max(1, cellWidth - padLeft - padRight);
95
92
  const measureWidth = cell.noWrap === true && !columnsPinned ? NO_WRAP_MEASURE_WIDTH : cellContentWidth;
96
93
  const cellMeasure = {
@@ -132,8 +129,7 @@ function measureTableBlock(tableBlock, contentWidth, fieldValues) {
132
129
  placeTableCellBlock(flowState, sourceBlock, blockMeasure);
133
130
  }
134
131
  cell.height = finishTableCellFlow(flowState);
135
- const padTop = sourceCell?.padding?.top ?? DEFAULT_CELL_PADDING_Y;
136
- const padBottom = sourceCell?.padding?.bottom ?? DEFAULT_CELL_PADDING_Y;
132
+ const { top: padTop, bottom: padBottom } = resolveTableCellPadding(sourceCell);
137
133
  cell.height += padTop + padBottom;
138
134
  if ((sourceCell?.rowSpan ?? 1) > 1) continue;
139
135
  const borderHeight = getTableCellVerticalBorderHeight(cellGrid, sourceCell, rowIdx);
@@ -444,14 +440,34 @@ function measureTextBoxBlock(tb, fieldValues) {
444
440
  const innerWidth = tb.width - margins.left - margins.right;
445
441
  const innerMeasures = tb.content.map((block) => {
446
442
  if (block.kind === "table") return measureTableBlock(block, innerWidth, fieldValues);
447
- return measureParagraph(block, innerWidth, fieldValues ? { fieldValues } : void 0);
443
+ return measureParagraph(block, tb.textWrap === "none" ? NO_WRAP_MEASURE_WIDTH : innerWidth, fieldValues ? { fieldValues } : void 0);
448
444
  });
445
+ let naturalContentWidth = 0;
446
+ for (let index = 0; index < innerMeasures.length; index++) {
447
+ const measure = innerMeasures[index];
448
+ if (measure.kind === "table") {
449
+ naturalContentWidth = Math.max(naturalContentWidth, measure.totalWidth);
450
+ continue;
451
+ }
452
+ const block = tb.content[index];
453
+ if (!block || block.kind !== "paragraph") continue;
454
+ const indent = block.attrs?.indent;
455
+ const left = indent?.left ?? 0;
456
+ const right = indent?.right ?? 0;
457
+ for (let lineIndex = 0; lineIndex < measure.lines.length; lineIndex++) {
458
+ const line = measure.lines[lineIndex];
459
+ const start = left + (lineIndex === 0 ? (indent?.firstLine ?? 0) - (indent?.hanging ?? 0) : 0);
460
+ const end = start + line.width + right;
461
+ naturalContentWidth = Math.max(naturalContentWidth, Math.max(0, end) - Math.min(0, start));
462
+ }
463
+ }
464
+ const fittedWidth = naturalContentWidth + margins.left + margins.right;
465
+ const totalWidth = tb.autoFit === "shape" && tb.textWrap === "none" ? Math.max(tb.width, fittedWidth) : tb.width;
449
466
  const contentBoxHeight = layoutTextBoxContent(tb.content, innerMeasures).totalHeight + margins.top + margins.bottom;
450
- const totalHeight = tb.autoFit === "shape" ? Math.max(tb.height ?? 0, contentBoxHeight) : tb.height ?? contentBoxHeight;
451
467
  return {
452
468
  kind: "textBox",
453
- width: tb.width,
454
- height: totalHeight,
469
+ width: totalWidth,
470
+ height: tb.autoFit === "shape" ? Math.max(tb.height ?? 0, contentBoxHeight) : tb.height ?? contentBoxHeight,
455
471
  innerMeasures
456
472
  };
457
473
  }
@@ -1,5 +1,6 @@
1
1
  import { calculateTabWidth, pixelsToTwips } from "../../prosemirror/utils/tabCalculator.js";
2
2
  import { CJK_FALLBACK_FONT_FAMILY, isCjkFont } from "../../utils/fontResolver.js";
3
+ import { isRtlParagraph } from "../../utils/paragraphBaseDirection.js";
3
4
  import { inlineImageBoundingBox } from "../../utils/rotationBoundingBox.js";
4
5
  import { hasCjk, hasComplexScript } from "../../utils/scriptSegments.js";
5
6
  import { measuredLineAdvance } from "../lineFlow.js";
@@ -668,6 +669,7 @@ function findClearLineY(startY, lineHeight, zones, contentWidth, minWidth) {
668
669
  function measureParagraph(block, maxWidth, options) {
669
670
  const runs = block.runs;
670
671
  const attrs = block.attrs;
672
+ const isRtl = isRtlParagraph(block);
671
673
  const spacing = attrs?.spacing;
672
674
  const isJustifiedParagraph = attrs?.alignment === "justify";
673
675
  const justificationProfile = {
@@ -936,9 +938,13 @@ function measureParagraph(block, maxWidth, options) {
936
938
  decimalPrefixWidth
937
939
  });
938
940
  let tabWidth = tabResult.width;
941
+ const authoredEndpoint = contentX + tabWidth + followingWidth;
942
+ const activeContentRightEdge = maxWidth - currentLine.rightOffset;
943
+ const preservesLogicalRtlEndStop = isRtl && tabResult.alignment === "end" && authoredEndpoint <= activeContentRightEdge + WIDTH_TOLERANCE;
939
944
  const landsOnLeftIndent = tabResult.alignment === "start" && indentLeft > 0 && Math.abs(contentX + tabWidth - indentLeft) <= WIDTH_TOLERANCE;
940
945
  const lineRightEdgeX = indentLeft + (isFirstLine ? firstLineOffset + markerInlineWidth : 0) + currentLine.availableWidth + currentLine.leftOffset;
941
- if (!landsOnLeftIndent && !hasFollowingTabOnLine(runs, runIndex) && canClampTabToRightEdge(tabResult.alignment, currentLine.width, hasPriorTabOnLine(runs, runIndex), followingWidth, currentLine.availableWidth) && (tabWidth > 0 || followingWidth > 0) && contentX + tabWidth + followingWidth > lineRightEdgeX + WIDTH_TOLERANCE) tabWidth = Math.max(1, lineRightEdgeX - contentX - followingWidth);
946
+ if (!preservesLogicalRtlEndStop && !landsOnLeftIndent && !hasFollowingTabOnLine(runs, runIndex) && canClampTabToRightEdge(tabResult.alignment, currentLine.width, hasPriorTabOnLine(runs, runIndex), followingWidth, currentLine.availableWidth) && (tabWidth > 0 || followingWidth > 0) && contentX + tabWidth + followingWidth > lineRightEdgeX + WIDTH_TOLERANCE) tabWidth = Math.max(1, lineRightEdgeX - contentX - followingWidth);
947
+ if (preservesLogicalRtlEndStop) currentLine.availableWidth = Math.max(currentLine.availableWidth, currentLine.width + tabWidth + followingWidth);
942
948
  if (currentLine.width + tabWidth > currentLine.availableWidth + WIDTH_TOLERANCE) {
943
949
  startNewLine(runIndex, 0);
944
950
  updateMaxFont(style);
@@ -1,5 +1,5 @@
1
1
  import { emuToPixels } from "../../utils/units.js";
2
- import { isFloatingImageRun } from "../types.js";
2
+ import { isFloatingImageRun, resolveTableCellPadding } from "../types.js";
3
3
  import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
4
4
  import { createTableCellFlowState, placeTableCellBlock } from "./tableCellFlow.js";
5
5
  //#region src/layout-engine/measure/tableCellFloating.ts
@@ -34,8 +34,7 @@ const resolveCellScopedPosition = ({ run, paragraphY, contentWidth }) => {
34
34
  };
35
35
  };
36
36
  function getTableCellContentWidth(cell, cellMeasure) {
37
- const padLeft = cell?.padding?.left ?? 7;
38
- const padRight = cell?.padding?.right ?? 7;
37
+ const { left: padLeft, right: padRight } = resolveTableCellPadding(cell);
39
38
  return Math.max(0, cellMeasure.width - padLeft - padRight);
40
39
  }
41
40
  function getTableCellFloatingImages(cell, cellMeasure, contentWidth, resolvePosition) {
@@ -11,7 +11,7 @@ const INITIAL_RENDERED_BREAK_STATE = { type: "noPageAdvance" };
11
11
  */
12
12
  const reconcileBreakBeforeBlock = ({ state, block, previousBlock, page, blocksById, hasExplicitPageBreak, renderedBreakNeedsSnap }) => {
13
13
  if (hasExplicitPageBreak) return {
14
- forcePageBreak: true,
14
+ forcePageBreak: !(state.type === "pageAdvance" && state.reason === "authoredBoundary" && state.boundary === "sectionBreak" && previousBlock?.kind === "sectionBreak" && previousBlock.type !== "continuous"),
15
15
  suppressSpaceBefore: false,
16
16
  state: INITIAL_RENDERED_BREAK_STATE
17
17
  };
@@ -2,6 +2,7 @@ import { measureParagraph } from "./measure/measureParagraph.js";
2
2
  import { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages } from "./measure/tableCellFloating.js";
3
3
  import { createTableCellFlowState, placeTableCellBlock } from "./measure/tableCellFlow.js";
4
4
  import { isEmptyParagraph } from "./paragraphSpacing.js";
5
+ import { resolveTableCellPadding } from "./types.js";
5
6
  //#region src/layout-engine/tableRowBreak.ts
6
7
  /**
7
8
  * Table row-break geometry. Ported from eigenpal/docx-editor#698 (folio subset).
@@ -18,7 +19,6 @@ import { isEmptyParagraph } from "./paragraphSpacing.js";
18
19
  * here yet (a separate follow-up); each row uses its own cells' content.
19
20
  */
20
21
  const BREAK_OFFSET_EPSILON = .01;
21
- const DEFAULT_TABLE_CELL_PADDING_TOP = 1;
22
22
  function isInsideRange(offset, range) {
23
23
  return offset > range.top && offset < range.bottom;
24
24
  }
@@ -49,7 +49,7 @@ function cellBreakGeometry(cell, measure) {
49
49
  const suppressibleLeadingRanges = [];
50
50
  const cellBlocks = cell?.blocks;
51
51
  const blockMeasures = measure.blocks;
52
- const padTop = cell?.padding?.top ?? DEFAULT_TABLE_CELL_PADDING_TOP;
52
+ const { top: padTop } = resolveTableCellPadding(cell);
53
53
  const contentWidth = getTableCellContentWidth(cell, measure);
54
54
  const floatingZones = buildTableCellFloatingZones(cell !== void 0 ? getTableCellFloatingImages(cell, measure, contentWidth) : [], contentWidth);
55
55
  const flowState = createTableCellFlowState();
@@ -1,7 +1,7 @@
1
1
  import { OutlineStyleAttr } from "../types/documentEnumValues.js";
2
- import { ImagePosition, ImageWrap, SdtProperties, SdtType, ShapeTextBody, TableCellFormatting, TableWidthType } from "@stll/docx-core/model";
2
+ import { ImagePosition, ImageWrap, NumberFormat, 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, SectionPageNumbering, 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, resolveTableCellPadding, tableColumnsArePinned };
5
5
  }
6
6
  /**
7
7
  * Unique identifier for a block in the document.
@@ -127,6 +127,7 @@ type RunFormatting = {
127
127
  /** Groups a suggestion's runs for accept/reject and scroll-to. */
128
128
  suggestionId?: string;
129
129
  };
130
+ type ListMarkerFormatting = Pick<RunFormatting, "fontFamily" | "eastAsiaFontFamily" | "complexScriptFontFamily" | "fontSize" | "complexScriptFontSize" | "bold" | "complexScriptBold" | "italic" | "complexScriptItalic" | "rtl" | "forceComplexScript">;
130
131
  /**
131
132
  * Hyperlink information for a run.
132
133
  */
@@ -461,15 +462,14 @@ type ParagraphAttrs = {
461
462
  listMarker?: string;
462
463
  listIsBullet?: boolean;
463
464
  listMarkerHidden?: boolean;
464
- listMarkerFontFamily?: string;
465
- listMarkerFontSize?: number;
465
+ /** Canonical numbering-level marker typography, resolved for layout. */
466
+ listMarkerFormatting?: ListMarkerFormatting;
466
467
  /**
467
468
  * Effective paragraph-mark size used only to measure a visible list
468
469
  * paragraph's final line. Word lets the inherited complex-script size raise
469
470
  * that line box without enlarging the marker or Western text glyphs.
470
471
  */
471
472
  listParagraphMarkFontSize?: number;
472
- listMarkerBold?: boolean;
473
473
  /** Horizontal alignment of the marker around the paragraph's list anchor. */
474
474
  listMarkerAlignment?: "left" | "center" | "right";
475
475
  /**
@@ -578,6 +578,9 @@ type TableCell = {
578
578
  */
579
579
  noWrap?: boolean;
580
580
  };
581
+ type TableCellPadding = NonNullable<TableCell["padding"]>;
582
+ /** Resolve authored cell margins against the TableNormal defaults. */
583
+ declare const resolveTableCellPadding: (cell: Pick<TableCell, "padding"> | undefined) => TableCellPadding;
581
584
  /**
582
585
  * A table row containing cells.
583
586
  */
@@ -705,11 +708,11 @@ type SectionBreakBlock = {
705
708
  /** Normalized section page-number policy. An omitted OOXML start continues numbering. */
706
709
  type SectionPageNumbering = {
707
710
  type: "continue";
708
- format?: string;
711
+ format?: NumberFormat;
709
712
  } | {
710
713
  type: "restart";
711
714
  start: number;
712
- format?: string;
715
+ format?: NumberFormat;
713
716
  };
714
717
  type PageHeaderFooterRefs = {
715
718
  titlePg?: boolean;
@@ -760,6 +763,8 @@ type TextBoxBlock = {
760
763
  height?: number;
761
764
  /** Text fitting behavior */
762
765
  autoFit?: ShapeTextBody["autoFit"];
766
+ /** Horizontal text wrapping inside the box */
767
+ textWrap?: ShapeTextBody["textWrap"];
763
768
  /** Fill/background color */
764
769
  fillColor?: string;
765
770
  /** Border width in pixels */
@@ -1081,7 +1086,7 @@ type Page = {
1081
1086
  /** Authored page number shown by PAGE fields. */
1082
1087
  logicalNumber: number;
1083
1088
  /** OOXML number format for this page's section. */
1084
- logicalNumberFormat?: string;
1089
+ logicalNumberFormat?: NumberFormat;
1085
1090
  /** Fragments positioned on this page. */
1086
1091
  fragments: Fragment[];
1087
1092
  /** Page margins. */
@@ -1235,8 +1240,6 @@ type LayoutOptions = {
1235
1240
  * page.
1236
1241
  */
1237
1242
  footnoteHeightById?: Map<number, number>;
1238
- /** Section break type for the body-level (final) section (for section transition logic). */
1239
- bodyBreakType?: "continuous" | "nextPage" | "evenPage" | "oddPage";
1240
1243
  /** Header/footer references for each document section, by section index. */
1241
1244
  sectionHeaderFooterRefs?: PageHeaderFooterRefs[];
1242
1245
  };
@@ -1308,6 +1311,8 @@ type HeaderFooterContent = {
1308
1311
  */
1309
1312
  marginPushTop?: number;
1310
1313
  marginPushBottom?: number;
1314
+ /** Page-coordinate lower edge of a header wrap band overlapping the body top. */
1315
+ bodyTopClearance?: number;
1311
1316
  /**
1312
1317
  * Relationship id (`rId`) of the source HF part. Emitted as `data-rid`
1313
1318
  * on the painted `.layout-page-header` / `.layout-page-footer` so the
@@ -1380,4 +1385,4 @@ declare function tableColumnsArePinned(table: TableBlock): boolean;
1380
1385
  /** Return the leading visual offset for a row with omitted grid columns. */
1381
1386
  declare const getTableRowLeadingWidth: (row: TableRow, columnWidths: readonly number[]) => number;
1382
1387
  //#endregion
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, 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 };
1388
+ 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, resolveTableCellPadding, tableColumnsArePinned, types_d_exports };
@@ -13,8 +13,22 @@ var types_exports = /* @__PURE__ */ __exportAll({
13
13
  isFloatingImageRun: () => isFloatingImageRun,
14
14
  isFloatingTextBoxBlock: () => isFloatingTextBoxBlock,
15
15
  isTextWrappingFloatingImageRun: () => isTextWrappingFloatingImageRun,
16
+ resolveTableCellPadding: () => resolveTableCellPadding,
16
17
  tableColumnsArePinned: () => tableColumnsArePinned
17
18
  });
19
+ const DEFAULT_TABLE_CELL_PADDING = {
20
+ top: 0,
21
+ right: 7,
22
+ bottom: 0,
23
+ left: 7
24
+ };
25
+ /** Resolve authored cell margins against the TableNormal defaults. */
26
+ const resolveTableCellPadding = (cell) => ({
27
+ top: cell?.padding?.top ?? DEFAULT_TABLE_CELL_PADDING.top,
28
+ right: cell?.padding?.right ?? DEFAULT_TABLE_CELL_PADDING.right,
29
+ bottom: cell?.padding?.bottom ?? DEFAULT_TABLE_CELL_PADDING.bottom,
30
+ left: cell?.padding?.left ?? DEFAULT_TABLE_CELL_PADDING.left
31
+ });
18
32
  /** Default internal margins for text boxes (OOXML defaults in pixels) */
19
33
  const DEFAULT_TEXTBOX_MARGINS = {
20
34
  top: 4,
@@ -128,4 +142,4 @@ const getTableRowLeadingWidth = (row, columnWidths) => {
128
142
  return columnWidths.slice(0, row.gridBefore ?? 0).reduce((sum, columnWidth) => sum + columnWidth, 0);
129
143
  };
130
144
  //#endregion
131
- export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, tableColumnsArePinned, types_exports };
145
+ export { DEFAULT_TEXTBOX_MARGINS, DEFAULT_TEXTBOX_WIDTH, FOOTNOTE_ENTRY_MARGIN_BOTTOM, FOOTNOTE_FALLBACK_LINE_HEIGHT, FOOTNOTE_SEPARATOR_HEIGHT, floatingTextBoxReservesBand, floatingTextBoxWrapsText, getTableRowLeadingWidth, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun, resolveTableCellPadding, tableColumnsArePinned, types_exports };
@@ -1,4 +1,4 @@
1
- import { MeasuredLine, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphMeasure, Run, TabStop } from "../layout-engine/types.js";
1
+ import { HyperlinkInfo, MeasuredLine, ParagraphBlock, ParagraphBorders, ParagraphFragment, ParagraphMeasure, Run, TabStop } from "../layout-engine/types.js";
2
2
  import { RenderContext } from "./renderUtils.js";
3
3
  //#region src/layout-painter/renderParagraph.d.ts
4
4
  /**
@@ -64,8 +64,14 @@ type RenderLineOptions = {
64
64
  context?: RenderContext;
65
65
  /** Left indent in pixels */
66
66
  leftIndentPx?: number;
67
+ /** Authored OOXML left indent used by logical tab-stop calculations. */
68
+ tabLeftIndentPx?: number;
67
69
  /** First line indent in pixels (positive) or hanging indent (negative) */
68
70
  firstLineIndentPx?: number;
71
+ /** Paragraph base direction for logical first-line indentation. */
72
+ isRtl?: boolean;
73
+ /** Full paragraph content-box width before physical indents. */
74
+ contentWidthPx?: number;
69
75
  /** Line-specific floating image margins (calculated per-line based on Y overlap) */
70
76
  floatingMargins?: {
71
77
  leftMargin: number;
@@ -73,6 +79,8 @@ type RenderLineOptions = {
73
79
  };
74
80
  /** Track inline image runs already rendered in this paragraph fragment to prevent duplicates */
75
81
  renderedInlineImageKeys?: Set<string>;
82
+ /** Hyperlink fragments whose complete displayed text is a URL. */
83
+ leftToRightDisplayedUrlHyperlinks?: ReadonlySet<HyperlinkInfo>;
76
84
  /**
77
85
  * Rightmost x where inline content may render, in content-area coords. Used
78
86
  * by the right-tab anchor (TOC pattern); passed in directly rather than