@stll/folio-core 0.49.0 → 0.51.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 (107) hide show
  1. package/dist/ai-edits/headless.d.ts +37 -1
  2. package/dist/ai-edits/headless.js +47 -16
  3. package/dist/ai-edits/word-diff.js +134 -1
  4. package/dist/compare/content-alignment.js +220 -5
  5. package/dist/compare/content.d.ts +1 -0
  6. package/dist/compare/content.js +51 -20
  7. package/dist/display-list/dom/renderDisplayListToDom.d.ts +10 -1
  8. package/dist/display-list/dom/renderDisplayListToDom.js +8 -7
  9. package/dist/display-list/html/renderDisplayListToHtml.d.ts +21 -0
  10. package/dist/display-list/html/renderDisplayListToHtml.js +126 -0
  11. package/dist/display-list/selectDisplayPages.d.ts +6 -0
  12. package/dist/display-list/selectDisplayPages.js +44 -0
  13. package/dist/docx/selectiveSave.d.ts +1 -1
  14. package/dist/docx/selectiveSave.js +16 -6
  15. package/dist/docx/structuralXmlPatch.d.ts +14 -0
  16. package/dist/docx/structuralXmlPatch.js +209 -0
  17. package/dist/export-pdf.d.ts +35 -2
  18. package/dist/export-pdf.js +51 -20
  19. package/dist/fonts/fontsourceFaces.d.ts +47 -0
  20. package/dist/fonts/fontsourceFaces.js +230 -0
  21. package/dist/layout-bridge/convert/flowBlockPostprocessing.d.ts +8 -0
  22. package/dist/layout-bridge/convert/flowBlockPostprocessing.js +106 -0
  23. package/dist/layout-bridge/convert/flowBorders.d.ts +17 -0
  24. package/dist/layout-bridge/convert/flowBorders.js +31 -0
  25. package/dist/layout-bridge/convert/flowConversionShared.d.ts +111 -0
  26. package/dist/layout-bridge/convert/flowConversionShared.js +23 -0
  27. package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -1
  28. package/dist/layout-bridge/convert/footnoteLayout.js +2 -1
  29. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +2 -1
  30. package/dist/layout-bridge/convert/imageConversion.d.ts +33 -0
  31. package/dist/layout-bridge/convert/imageConversion.js +118 -0
  32. package/dist/layout-bridge/convert/listMarkers.d.ts +28 -0
  33. package/dist/layout-bridge/convert/listMarkers.js +98 -0
  34. package/dist/layout-bridge/convert/noteReferences.d.ts +14 -0
  35. package/dist/layout-bridge/convert/noteReferences.js +29 -0
  36. package/dist/layout-bridge/convert/pageBreakSplitting.d.ts +16 -0
  37. package/dist/layout-bridge/convert/pageBreakSplitting.js +140 -0
  38. package/dist/layout-bridge/convert/paragraphAttrs.d.ts +20 -0
  39. package/dist/layout-bridge/convert/paragraphAttrs.js +234 -0
  40. package/dist/layout-bridge/convert/paragraphConversion.d.ts +12 -0
  41. package/dist/layout-bridge/convert/paragraphConversion.js +112 -0
  42. package/dist/layout-bridge/convert/paragraphRuns.d.ts +15 -0
  43. package/dist/layout-bridge/convert/paragraphRuns.js +281 -0
  44. package/dist/layout-bridge/convert/runFormattingMerge.d.ts +19 -0
  45. package/dist/layout-bridge/convert/runFormattingMerge.js +167 -0
  46. package/dist/layout-bridge/convert/runMarkFormatting.d.ts +11 -0
  47. package/dist/layout-bridge/convert/runMarkFormatting.js +291 -0
  48. package/dist/layout-bridge/convert/sectionBoundaries.d.ts +35 -0
  49. package/dist/layout-bridge/convert/sectionBoundaries.js +114 -0
  50. package/dist/layout-bridge/convert/tableConversion.d.ts +12 -0
  51. package/dist/layout-bridge/convert/tableConversion.js +303 -0
  52. package/dist/layout-bridge/convert/textBoxFill.d.ts +20 -0
  53. package/dist/layout-bridge/convert/textBoxFill.js +42 -0
  54. package/dist/layout-bridge/convert/textFormattingConversion.d.ts +22 -0
  55. package/dist/layout-bridge/convert/textFormattingConversion.js +120 -0
  56. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +5 -113
  57. package/dist/layout-bridge/convert/toFlowBlocks.js +11 -2082
  58. package/dist/layout-bridge/engine/hitTest.js +1 -1
  59. package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
  60. package/dist/layout-bridge/engine/measuring/index.js +2 -1
  61. package/dist/layout-bridge/engine/selectionRects.js +1 -1
  62. package/dist/layout-engine/imageLayout.d.ts +9 -0
  63. package/dist/layout-engine/imageLayout.js +48 -0
  64. package/dist/layout-engine/index.d.ts +3 -18
  65. package/dist/layout-engine/index.js +12 -937
  66. package/dist/layout-engine/layoutFlowShared.d.ts +6 -0
  67. package/dist/layout-engine/layoutFlowShared.js +8 -0
  68. package/dist/layout-engine/measure/crossRunWords.d.ts +45 -0
  69. package/dist/layout-engine/measure/crossRunWords.js +172 -0
  70. package/dist/layout-engine/measure/floatingLineClearance.d.ts +24 -0
  71. package/dist/layout-engine/measure/floatingLineClearance.js +44 -0
  72. package/dist/layout-engine/measure/inlineObjectMetrics.d.ts +6 -0
  73. package/dist/layout-engine/measure/inlineObjectMetrics.js +52 -0
  74. package/dist/layout-engine/measure/justification.d.ts +32 -0
  75. package/dist/layout-engine/measure/justification.js +83 -0
  76. package/dist/layout-engine/measure/lineFitting.d.ts +48 -0
  77. package/dist/layout-engine/measure/lineFitting.js +145 -0
  78. package/dist/layout-engine/measure/lineTypography.d.ts +44 -0
  79. package/dist/layout-engine/measure/lineTypography.js +124 -0
  80. package/dist/layout-engine/measure/measureBlocks.js +2 -1
  81. package/dist/layout-engine/measure/measureParagraph.d.ts +1 -20
  82. package/dist/layout-engine/measure/measureParagraph.js +32 -734
  83. package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +109 -0
  84. package/dist/layout-engine/measure/paragraphMeasureShared.js +82 -0
  85. package/dist/layout-engine/measure/paragraphTabs.d.ts +18 -0
  86. package/dist/layout-engine/measure/paragraphTabs.js +78 -0
  87. package/dist/layout-engine/paragraphLayout.d.ts +23 -0
  88. package/dist/layout-engine/paragraphLayout.js +204 -0
  89. package/dist/layout-engine/sectionLayout.d.ts +45 -0
  90. package/dist/layout-engine/sectionLayout.js +216 -0
  91. package/dist/layout-engine/tableLayout.d.ts +17 -0
  92. package/dist/layout-engine/tableLayout.js +369 -0
  93. package/dist/layout-engine/textBoxLayout.d.ts +15 -0
  94. package/dist/layout-engine/textBoxLayout.js +137 -0
  95. package/dist/layout-painter/renderPage.js +1 -0
  96. package/dist/managers/editorShortcuts.d.ts +16 -3
  97. package/dist/managers/editorShortcuts.js +6 -4
  98. package/dist/paged-layout/sectionBlockWidths.d.ts +2 -1
  99. package/dist/paged-layout/sectionBlockWidths.js +1 -1
  100. package/dist/prosemirror/extensions/StarterKit.d.ts +3 -0
  101. package/dist/prosemirror/extensions/StarterKit.js +2 -1
  102. package/dist/prosemirror/extensions/core/HistoryExtension.d.ts +9 -1
  103. package/dist/prosemirror/extensions/core/HistoryExtension.js +4 -3
  104. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +12 -5
  105. package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +91 -33
  106. package/dist/server.d.ts +3 -2
  107. package/package.json +2 -2
@@ -0,0 +1,109 @@
1
+ import { FieldRun, ImageRun, LineBreakRun, MathRun, MeasuredLine, Run, TabRun, TextRun } from "../types.js";
2
+ import { FontMetrics, FontStyle } from "./measureTypes.js";
3
+ import { FloatingLineSegmentZone } from "./floatingZones.js";
4
+ //#region src/layout-engine/measure/paragraphMeasureShared.d.ts
5
+ declare const DEFAULT_FONT_SIZE = 11;
6
+ declare const DEFAULT_LINE_HEIGHT_MULTIPLIER = 1;
7
+ declare const WIDTH_TOLERANCE = 0.5;
8
+ /**
9
+ * Typography metrics for a line
10
+ */
11
+ type LineTypography = {
12
+ ascent: number;
13
+ descent: number;
14
+ lineHeight: number;
15
+ };
16
+ /**
17
+ * State tracking for line accumulation
18
+ */
19
+ type LineState = {
20
+ fromRun: number;
21
+ fromChar: number;
22
+ toRun: number;
23
+ toChar: number;
24
+ width: number;
25
+ /** Width of collapsible ASCII whitespace at the current line tail. */
26
+ trailingWhitespaceWidth: number;
27
+ /** Spaces the layout may compress while justifying this line. */
28
+ regularSpaceWidth: number;
29
+ /** Paint plan set only when final-text contraction admits a candidate. */
30
+ justificationPaint?: NonNullable<MeasuredLine["justificationPaint"]>;
31
+ maxFontSize: number;
32
+ maxFontMetrics: FontMetrics | null;
33
+ /**
34
+ * Largest tab font on the line. Tabs are whitespace: their font sizes the
35
+ * line only when no other run on it does (a line of nothing but tabs).
36
+ */
37
+ maxTabFontSize: number;
38
+ maxTabFontMetrics: FontMetrics | null;
39
+ /** Maximum inline image height in pixels (already in px, not points) */
40
+ maxImageHeightPx: number;
41
+ /** Maximum exact-height embedded-object preview on the line. */
42
+ maxExactImageHeightPx: number;
43
+ /** Maximum inline math height in pixels (already in px, not points) */
44
+ maxMathHeightPx: number;
45
+ availableWidth: number;
46
+ /** Left offset from floating images (pixels from content left edge) */
47
+ leftOffset: number;
48
+ /** Right offset from floating images (pixels from content right edge) */
49
+ rightOffset: number;
50
+ /** Optional split segment zones from centered floating exclusions */
51
+ segmentZones?: FloatingLineSegmentZone[];
52
+ discretionaryHyphen?: {
53
+ runIndex: number;
54
+ };
55
+ renderedPageBreakBefore?: boolean;
56
+ };
57
+ /**
58
+ * Extract FontStyle from a run that carries RunFormatting (text, tab, or
59
+ * field). All three share the same formatting shape, so they measure the
60
+ * same way; widening the parameter keeps tab-following measurement
61
+ * (FieldRun page numbers, etc.) consistent with TextRun handling.
62
+ */
63
+ declare function runToFontStyle(run: TextRun | TabRun | FieldRun | MathRun): FontStyle;
64
+ /**
65
+ * Check if a run is a text run
66
+ */
67
+ declare function isTextRun(run: Run): run is TextRun;
68
+ /**
69
+ * Check if a run is a tab run
70
+ */
71
+ declare function isTabRun(run: Run): run is TabRun;
72
+ /**
73
+ * Check if a run is an image run
74
+ */
75
+ declare function isImageRun(run: Run): run is ImageRun;
76
+ /**
77
+ * Check if a run is a line break run
78
+ */
79
+ declare function isLineBreakRun(run: Run): run is LineBreakRun;
80
+ /**
81
+ * Check if a run is a field run
82
+ */
83
+ declare function isFieldRun(run: Run): run is FieldRun;
84
+ /** Text a field run is measured at: its resolved value when known (so the
85
+ * measurer agrees with the painter), else the cached fallback. */
86
+ declare function fieldMeasureText(run: FieldRun, fieldValues: ReadonlyMap<number, string> | undefined): string;
87
+ /**
88
+ * Check if a run is a math equation run
89
+ */
90
+ declare function isMathRun(run: Run): run is MathRun;
91
+ /**
92
+ * Check if text run is empty (only whitespace or no text)
93
+ */
94
+ declare function isEmptyTextRun(run: TextRun): boolean;
95
+ /**
96
+ * Sum the inline pixel widths of runs after a tab, up to (but not including)
97
+ * the next tab or line break. Measured per-run so widths reserved match what
98
+ * the painter draws even when trailing runs use different fonts/sizes.
99
+ *
100
+ * Floating/anchored images are skipped — the painter lifts them out of the
101
+ * paragraph flow (see `isFloatingImageRun`) and `measureFollowingContentWidth`
102
+ * in the painter already excludes them, so counting their width here would
103
+ * desync measurer and painter on tab advance for paragraphs with a tab
104
+ * preceding a floating image.
105
+ */
106
+ declare function isBlockLayoutImageRun(run: ImageRun): boolean;
107
+ declare function isSpaceOrTab(char: string | undefined): boolean;
108
+ //#endregion
109
+ export { DEFAULT_FONT_SIZE, DEFAULT_LINE_HEIGHT_MULTIPLIER, LineState, LineTypography, WIDTH_TOLERANCE, fieldMeasureText, isBlockLayoutImageRun, isEmptyTextRun, isFieldRun, isImageRun, isLineBreakRun, isMathRun, isSpaceOrTab, isTabRun, isTextRun, runToFontStyle };
@@ -0,0 +1,82 @@
1
+ import { isFloatingImageRun } from "../types.js";
2
+ import { DEFAULT_FONT_FAMILY, buildRunFontStyle } from "./measureHelpers.js";
3
+ //#region src/layout-engine/measure/paragraphMeasureShared.ts
4
+ const DEFAULT_FONT_SIZE = 11;
5
+ const DEFAULT_LINE_HEIGHT_MULTIPLIER = 1;
6
+ const WIDTH_TOLERANCE = .5;
7
+ /**
8
+ * Extract FontStyle from a run that carries RunFormatting (text, tab, or
9
+ * field). All three share the same formatting shape, so they measure the
10
+ * same way; widening the parameter keeps tab-following measurement
11
+ * (FieldRun page numbers, etc.) consistent with TextRun handling.
12
+ */
13
+ function runToFontStyle(run) {
14
+ return buildRunFontStyle(run, DEFAULT_FONT_FAMILY, 11);
15
+ }
16
+ /**
17
+ * Check if a run is a text run
18
+ */
19
+ function isTextRun(run) {
20
+ return run.kind === "text";
21
+ }
22
+ /**
23
+ * Check if a run is a tab run
24
+ */
25
+ function isTabRun(run) {
26
+ return run.kind === "tab";
27
+ }
28
+ /**
29
+ * Check if a run is an image run
30
+ */
31
+ function isImageRun(run) {
32
+ return run.kind === "image";
33
+ }
34
+ /**
35
+ * Check if a run is a line break run
36
+ */
37
+ function isLineBreakRun(run) {
38
+ return run.kind === "lineBreak";
39
+ }
40
+ /**
41
+ * Check if a run is a field run
42
+ */
43
+ function isFieldRun(run) {
44
+ return run.kind === "field";
45
+ }
46
+ /** Text a field run is measured at: its resolved value when known (so the
47
+ * measurer agrees with the painter), else the cached fallback. */
48
+ function fieldMeasureText(run, fieldValues) {
49
+ return (run.pmStart === void 0 ? void 0 : fieldValues?.get(run.pmStart)) ?? (run.fallback || "1");
50
+ }
51
+ /**
52
+ * Check if a run is a math equation run
53
+ */
54
+ function isMathRun(run) {
55
+ return run.kind === "math";
56
+ }
57
+ /**
58
+ * Check if text run is empty (only whitespace or no text)
59
+ */
60
+ function isEmptyTextRun(run) {
61
+ return !run.text || run.text.replace(/\u00a0/gu, " ").trim().length === 0;
62
+ }
63
+ /**
64
+ * Sum the inline pixel widths of runs after a tab, up to (but not including)
65
+ * the next tab or line break. Measured per-run so widths reserved match what
66
+ * the painter draws even when trailing runs use different fonts/sizes.
67
+ *
68
+ * Floating/anchored images are skipped — the painter lifts them out of the
69
+ * paragraph flow (see `isFloatingImageRun`) and `measureFollowingContentWidth`
70
+ * in the painter already excludes them, so counting their width here would
71
+ * desync measurer and painter on tab advance for paragraphs with a tab
72
+ * preceding a floating image.
73
+ */
74
+ function isBlockLayoutImageRun(run) {
75
+ if (isFloatingImageRun(run)) return false;
76
+ return run.wrapType === "topAndBottom" || run.displayMode === "block";
77
+ }
78
+ function isSpaceOrTab(char) {
79
+ return char === " " || char === " ";
80
+ }
81
+ //#endregion
82
+ export { DEFAULT_FONT_SIZE, DEFAULT_LINE_HEIGHT_MULTIPLIER, WIDTH_TOLERANCE, fieldMeasureText, isBlockLayoutImageRun, isEmptyTextRun, isFieldRun, isImageRun, isLineBreakRun, isMathRun, isSpaceOrTab, isTabRun, isTextRun, runToFontStyle };
@@ -0,0 +1,18 @@
1
+ import { Run } from "../types.js";
2
+ //#region src/layout-engine/measure/paragraphTabs.d.ts
3
+ declare function measureInlineWidthAfterTab(runs: Run[], tabIndex: number, fieldValues?: ReadonlyMap<number, string>): number;
4
+ declare function hasFollowingTabOnLine(runs: Run[], tabIndex: number): boolean;
5
+ declare function hasPriorTabOnLine(runs: Run[], tabIndex: number): boolean;
6
+ declare function canClampTabToRightEdge(alignment: string, currentLineWidth: number, hasPriorTab: boolean, followingWidth: number, availableWidth: number): boolean;
7
+ /**
8
+ * Width of the inline content preceding the first `.` in the runs that follow
9
+ * a tab, used to anchor `decimal` tab stops. Mirrors `getTextAfterTab` +
10
+ * decimal-prefix measurement in the painter (`renderParagraph.ts`) so the
11
+ * measurer and painter agree on tab advance for decimal stops.
12
+ *
13
+ * Returns 0 when no decimal separator appears before the next tab / line
14
+ * break — `calculateTabWidth` treats that as "no anchor adjustment".
15
+ */
16
+ declare function measureDecimalPrefixWidthAfterTab(runs: Run[], tabIndex: number, fieldValues?: ReadonlyMap<number, string>): number;
17
+ //#endregion
18
+ export { canClampTabToRightEdge, hasFollowingTabOnLine, hasPriorTabOnLine, measureDecimalPrefixWidthAfterTab, measureInlineWidthAfterTab };
@@ -0,0 +1,78 @@
1
+ import { inlineImageBoundingBox } from "../../utils/rotationBoundingBox.js";
2
+ import { isFloatingImageRun } from "../types.js";
3
+ import { measureTextWidth } from "./measureProvider.js";
4
+ import { fieldMeasureText, isBlockLayoutImageRun, isFieldRun, isImageRun, isLineBreakRun, isMathRun, isTabRun, isTextRun, runToFontStyle } from "./paragraphMeasureShared.js";
5
+ //#region src/layout-engine/measure/paragraphTabs.ts
6
+ /**
7
+ * Tab measurement on a line: the width of content following a tab, decimal
8
+ * tab prefixes, neighbouring tabs, and right-edge clamping.
9
+ */
10
+ function measureInlineWidthAfterTab(runs, tabIndex, fieldValues) {
11
+ let width = 0;
12
+ for (let i = tabIndex + 1; i < runs.length; i++) {
13
+ const next = runs[i];
14
+ if (!next || isTabRun(next) || isLineBreakRun(next)) break;
15
+ if (isImageRun(next)) {
16
+ if (isBlockLayoutImageRun(next)) break;
17
+ if (!isFloatingImageRun(next)) width += inlineImageBoundingBox(next).width || 0;
18
+ continue;
19
+ }
20
+ if (isTextRun(next)) width += measureTextWidth(next.text || "", runToFontStyle(next));
21
+ else if (isFieldRun(next)) width += measureTextWidth(fieldMeasureText(next, fieldValues), runToFontStyle(next));
22
+ else if (isMathRun(next)) width += measureTextWidth(next.plainText || "[equation]", runToFontStyle(next));
23
+ }
24
+ return width;
25
+ }
26
+ function hasFollowingTabOnLine(runs, tabIndex) {
27
+ for (let i = tabIndex + 1; i < runs.length; i++) {
28
+ const next = runs[i];
29
+ if (!next || isLineBreakRun(next)) break;
30
+ if (isImageRun(next) && isBlockLayoutImageRun(next)) break;
31
+ if (isTabRun(next)) return true;
32
+ }
33
+ return false;
34
+ }
35
+ function hasPriorTabOnLine(runs, tabIndex) {
36
+ for (let i = tabIndex - 1; i >= 0; i--) {
37
+ const prior = runs[i];
38
+ if (!prior || isLineBreakRun(prior) || isImageRun(prior) && isBlockLayoutImageRun(prior)) break;
39
+ if (isTabRun(prior)) return true;
40
+ }
41
+ return false;
42
+ }
43
+ function canClampTabToRightEdge(alignment, currentLineWidth, hasPriorTab, followingWidth, availableWidth) {
44
+ if (alignment === "start" || alignment === "default") return currentLineWidth > .5 && (hasPriorTab || followingWidth <= availableWidth);
45
+ return true;
46
+ }
47
+ /**
48
+ * Width of the inline content preceding the first `.` in the runs that follow
49
+ * a tab, used to anchor `decimal` tab stops. Mirrors `getTextAfterTab` +
50
+ * decimal-prefix measurement in the painter (`renderParagraph.ts`) so the
51
+ * measurer and painter agree on tab advance for decimal stops.
52
+ *
53
+ * Returns 0 when no decimal separator appears before the next tab / line
54
+ * break — `calculateTabWidth` treats that as "no anchor adjustment".
55
+ */
56
+ function measureDecimalPrefixWidthAfterTab(runs, tabIndex, fieldValues) {
57
+ let text = "";
58
+ let firstRun;
59
+ for (let i = tabIndex + 1; i < runs.length; i++) {
60
+ const next = runs[i];
61
+ if (!next || isTabRun(next) || isLineBreakRun(next)) break;
62
+ if (isTextRun(next)) {
63
+ text += next.text || "";
64
+ firstRun ??= next;
65
+ } else if (isFieldRun(next)) {
66
+ text += fieldMeasureText(next, fieldValues);
67
+ firstRun ??= next;
68
+ } else if (isMathRun(next)) {
69
+ text += next.plainText || "[equation]";
70
+ firstRun ??= next;
71
+ }
72
+ }
73
+ const decimalIndex = text.indexOf(".");
74
+ if (decimalIndex === -1 || !firstRun) return 0;
75
+ return measureTextWidth(text.slice(0, decimalIndex), runToFontStyle(firstRun));
76
+ }
77
+ //#endregion
78
+ export { canClampTabToRightEdge, hasFollowingTabOnLine, hasPriorTabOnLine, measureDecimalPrefixWidthAfterTab, measureInlineWidthAfterTab };
@@ -0,0 +1,23 @@
1
+ import { ParagraphBlock, ParagraphMeasure } from "./types.js";
2
+ import { createPaginator } from "./paginator.js";
3
+ //#region src/layout-engine/paragraphLayout.d.ts
4
+ /**
5
+ * Layout a paragraph block onto pages.
6
+ *
7
+ * When `footnoteHeightById` is provided, each line carrying a footnote
8
+ * ref additionally reserves space on its host page for the fn's
9
+ * content. Pagination decisions include only the reservation growth
10
+ * beyond any retry floor, so a line cannot land on a page that lacks
11
+ * room for both body and note content.
12
+ */
13
+ type LayoutParagraphOptions = {
14
+ block: ParagraphBlock;
15
+ measure: ParagraphMeasure;
16
+ paginator: ReturnType<typeof createPaginator>;
17
+ contentWidth: number;
18
+ footnoteHeightById: Map<number, number> | undefined;
19
+ suppressSpaceBefore: boolean;
20
+ };
21
+ declare function layoutParagraph({ block, measure, paginator, contentWidth, footnoteHeightById, suppressSpaceBefore }: LayoutParagraphOptions): void;
22
+ //#endregion
23
+ export { layoutParagraph };
@@ -0,0 +1,204 @@
1
+ import { hasKeepLines } from "./keep-together.js";
2
+ import { projectedFootnoteReserveGrowth } from "./layoutFlowShared.js";
3
+ import { measuredLineAdvance, measuredLineRangeHeight } from "./lineFlow.js";
4
+ import { getParagraphFragmentPmRange } from "./paragraphFragmentRange.js";
5
+ import { collapseParagraphSpacing, getParagraphSpacingAfter, getParagraphSpacingBefore } from "./paragraphSpacing.js";
6
+ import "./types.js";
7
+ //#region src/layout-engine/paragraphLayout.ts
8
+ /**
9
+ * Places a measured paragraph's lines onto pages: keep rules, widow/orphan
10
+ * control, rendered-break reconciliation, and per-line footnote reservation.
11
+ */
12
+ function hasWidowControl(block) {
13
+ return block.attrs?.widowControl !== false;
14
+ }
15
+ /**
16
+ * Footnote refs whose run sits in line `[fromRun..toRun]`, with their
17
+ * pre-measured content heights. Empty when the engine isn't tracking
18
+ * dynamic fn demand. Used by `layoutParagraph` to (a) reserve fn
19
+ * height per line and (b) record the IDs on the host page so the
20
+ * painter renders the fn on the page where the ref-bearing line
21
+ * actually landed — even when the paragraph splits across pages
22
+ * (fragment pmStart/pmEnd is paragraph-wide and cannot disambiguate
23
+ * between split halves).
24
+ */
25
+ function getLineFootnoteRefs(block, fromRun, toRun, fnHeights) {
26
+ if (!fnHeights) return {
27
+ ids: [],
28
+ height: 0
29
+ };
30
+ const ids = [];
31
+ let height = 0;
32
+ for (let r = fromRun; r <= toRun; r++) {
33
+ const run = block.runs[r];
34
+ if (!run || run.kind !== "text") continue;
35
+ const id = run.footnoteRefId;
36
+ if (id === void 0) continue;
37
+ const h = fnHeights.get(id);
38
+ if (h !== void 0) {
39
+ ids.push(id);
40
+ height += h;
41
+ }
42
+ }
43
+ return {
44
+ ids,
45
+ height
46
+ };
47
+ }
48
+ function layoutParagraph({ block, measure, paginator, contentWidth, footnoteHeightById, suppressSpaceBefore }) {
49
+ const lines = measure.lines;
50
+ if (lines.length === 0) {
51
+ const spaceBefore = suppressSpaceBefore ? 0 : getParagraphSpacingBefore(block);
52
+ const spaceAfter = getParagraphSpacingAfter(block);
53
+ const state = paginator.getCurrentState();
54
+ const fragment = {
55
+ kind: "paragraph",
56
+ ...block.attrs?.suppressEmptyParagraphHeight === true ? { paginationRole: "empty-carrier" } : {},
57
+ blockId: block.id,
58
+ x: paginator.getColumnX(state.columnIndex),
59
+ y: state.cursorY + spaceBefore,
60
+ width: contentWidth,
61
+ height: 0,
62
+ fromLine: 0,
63
+ toLine: 0,
64
+ ...block.pmStart !== void 0 ? { pmStart: block.pmStart } : {},
65
+ ...block.pmEnd !== void 0 ? { pmEnd: block.pmEnd } : {},
66
+ ...block.sdtGroups ? { sdtGroups: block.sdtGroups } : {}
67
+ };
68
+ paginator.addFragment(fragment, 0, spaceBefore, spaceAfter);
69
+ return;
70
+ }
71
+ const spaceBefore = suppressSpaceBefore ? 0 : getParagraphSpacingBefore(block);
72
+ const spaceAfter = getParagraphSpacingAfter(block);
73
+ let keepLinesDemand;
74
+ if (lines.length > 1 && hasKeepLines(block)) {
75
+ let footnoteHeight = 0;
76
+ for (const line of lines) footnoteHeight += getLineFootnoteRefs(block, line.fromRun, line.toRun, footnoteHeightById).height;
77
+ keepLinesDemand = {
78
+ linesHeight: measuredLineRangeHeight(lines, 0, lines.length),
79
+ footnoteHeight
80
+ };
81
+ }
82
+ let keepLinesMoveAttempted = false;
83
+ let currentLineIndex = 0;
84
+ while (currentLineIndex < lines.length) {
85
+ const state = paginator.getCurrentState();
86
+ const availableHeight = paginator.getAvailableHeight();
87
+ const currentLine = lines.at(currentLineIndex);
88
+ const markerAlreadySatisfied = state.columnIndex === 0 && state.cursorY === state.topMargin && state.page.fragments.length === 0;
89
+ if (currentLine?.renderedPageBreakBefore === true && !markerAlreadySatisfied && availableHeight <= measuredLineAdvance(currentLine) * 3) {
90
+ paginator.forcePageBreak();
91
+ continue;
92
+ }
93
+ if (currentLineIndex === 0 && state.trailingSpacing > 0 && state.cursorY !== state.topMargin) {
94
+ const firstLine = lines[0];
95
+ const firstLineRefs = getLineFootnoteRefs(block, firstLine.fromRun, firstLine.toRun, footnoteHeightById);
96
+ const firstLineHeight = measuredLineAdvance(firstLine) + projectedFootnoteReserveGrowth(state, firstLineRefs.height);
97
+ const collapsedLead = collapseParagraphSpacing({
98
+ before: spaceBefore,
99
+ after: state.trailingSpacing
100
+ });
101
+ const columnCapacity = state.contentBottom - state.topMargin;
102
+ if (collapsedLead + firstLineHeight > availableHeight && spaceBefore + firstLineHeight <= columnCapacity) {
103
+ paginator.ensureFits(collapsedLead + firstLineHeight);
104
+ continue;
105
+ }
106
+ }
107
+ if (keepLinesDemand !== void 0 && !keepLinesMoveAttempted && currentLineIndex === 0 && state.cursorY !== state.topMargin) {
108
+ const wholeHeight = keepLinesDemand.linesHeight + projectedFootnoteReserveGrowth(state, keepLinesDemand.footnoteHeight);
109
+ const collapsedLead = collapseParagraphSpacing({
110
+ before: spaceBefore,
111
+ after: state.trailingSpacing
112
+ });
113
+ const columnCapacity = state.contentBottom - state.topMargin;
114
+ if (collapsedLead + wholeHeight > availableHeight && spaceBefore + wholeHeight <= columnCapacity) {
115
+ keepLinesMoveAttempted = true;
116
+ paginator.ensureFits(collapsedLead + wholeHeight);
117
+ continue;
118
+ }
119
+ }
120
+ let linesHeight = 0;
121
+ let linesFnHeight = 0;
122
+ const linesFnIds = [];
123
+ let fittingLines = 0;
124
+ let forcePageBreakAfterFragment = false;
125
+ const firstFragmentSpaceBefore = currentLineIndex === 0 ? collapseParagraphSpacing({
126
+ before: spaceBefore,
127
+ after: state.trailingSpacing
128
+ }) : 0;
129
+ for (let j = currentLineIndex; j < lines.length; j++) {
130
+ const line = lines[j];
131
+ const lineAdvance = measuredLineAdvance(line);
132
+ if (j > currentLineIndex && line.renderedPageBreakBefore === true && availableHeight - (linesHeight + firstFragmentSpaceBefore + linesFnHeight) <= lineAdvance * 3) {
133
+ forcePageBreakAfterFragment = true;
134
+ break;
135
+ }
136
+ const lineRefs = getLineFootnoteRefs(block, line.fromRun, line.toRun, footnoteHeightById);
137
+ const totalWithLine = linesHeight + lineAdvance;
138
+ const footnoteGrowth = projectedFootnoteReserveGrowth(state, linesFnHeight + lineRefs.height);
139
+ if (totalWithLine + firstFragmentSpaceBefore + footnoteGrowth <= availableHeight || fittingLines === 0) {
140
+ linesHeight = totalWithLine;
141
+ linesFnHeight += lineRefs.height;
142
+ for (const id of lineRefs.ids) linesFnIds.push(id);
143
+ fittingLines++;
144
+ } else break;
145
+ }
146
+ let forceBreakAfterFragment = false;
147
+ if (hasWidowControl(block)) {
148
+ const remainingAfter = lines.length - (currentLineIndex + fittingLines);
149
+ if (fittingLines > 1 && remainingAfter === 1) {
150
+ if (currentLineIndex === 0 && fittingLines === 2 && state.cursorY !== state.topMargin) {
151
+ paginator.forceColumnBreak();
152
+ continue;
153
+ }
154
+ fittingLines -= 1;
155
+ forceBreakAfterFragment = true;
156
+ linesHeight = 0;
157
+ linesFnHeight = 0;
158
+ linesFnIds.length = 0;
159
+ for (let j = currentLineIndex; j < currentLineIndex + fittingLines; j++) {
160
+ const line = lines[j];
161
+ linesHeight += measuredLineAdvance(line);
162
+ const lineRefs = getLineFootnoteRefs(block, line.fromRun, line.toRun, footnoteHeightById);
163
+ linesFnHeight += lineRefs.height;
164
+ for (const id of lineRefs.ids) linesFnIds.push(id);
165
+ }
166
+ }
167
+ }
168
+ const isFirstFragment = currentLineIndex === 0;
169
+ const isLastFragment = currentLineIndex + fittingLines >= lines.length;
170
+ const effectiveSpaceBefore = isFirstFragment ? spaceBefore : 0;
171
+ const effectiveSpaceAfter = isLastFragment ? spaceAfter : 0;
172
+ const pmRange = getParagraphFragmentPmRange(block, measure, currentLineIndex, currentLineIndex + fittingLines);
173
+ const fragment = {
174
+ kind: "paragraph",
175
+ ...block.attrs?.suppressEmptyParagraphHeight === true ? { paginationRole: "empty-carrier" } : {},
176
+ blockId: block.id,
177
+ x: paginator.getColumnX(state.columnIndex),
178
+ y: 0,
179
+ width: contentWidth,
180
+ height: linesHeight,
181
+ fromLine: currentLineIndex,
182
+ toLine: currentLineIndex + fittingLines,
183
+ ...pmRange.pmStart !== void 0 ? { pmStart: pmRange.pmStart } : {},
184
+ ...pmRange.pmEnd !== void 0 ? { pmEnd: pmRange.pmEnd } : {},
185
+ ...!isFirstFragment ? { continuesFromPrev: true } : {},
186
+ ...!isLastFragment ? { continuesOnNext: true } : {},
187
+ ...block.sdtGroups ? { sdtGroups: block.sdtGroups } : {}
188
+ };
189
+ if (linesFnHeight > 0) {
190
+ const stateBefore = paginator.getCurrentState();
191
+ paginator.ensureFits(effectiveSpaceBefore + linesHeight + projectedFootnoteReserveGrowth(stateBefore, linesFnHeight));
192
+ const stateAfter = paginator.getCurrentState();
193
+ if (stateAfter.footnoteDemandHeight === 0) paginator.ensureFits(effectiveSpaceBefore + linesHeight + projectedFootnoteReserveGrowth(stateAfter, linesFnHeight + 12));
194
+ }
195
+ fragment.y = paginator.addFragment(fragment, linesHeight, effectiveSpaceBefore, effectiveSpaceAfter).y;
196
+ if (linesFnHeight > 0) paginator.addFootnoteHeight(linesFnHeight, linesFnIds);
197
+ currentLineIndex += fittingLines;
198
+ if (currentLineIndex < lines.length) if (forcePageBreakAfterFragment) paginator.forcePageBreak();
199
+ else if (forceBreakAfterFragment) paginator.forceColumnBreak();
200
+ else paginator.ensureFits(measuredLineAdvance(lines[currentLineIndex]));
201
+ }
202
+ }
203
+ //#endregion
204
+ export { layoutParagraph };
@@ -0,0 +1,45 @@
1
+ import { ColumnLayout, FlowBlock, Measure, PageMargins, SectionBreakBlock, SectionPageNumbering } from "./types.js";
2
+ import { createPaginator } from "./paginator.js";
3
+ import { SectionStart } from "@stll/docx-core/model";
4
+ //#region src/layout-engine/sectionLayout.d.ts
5
+ type SectionLayoutConfig = {
6
+ pageSize: {
7
+ w: number;
8
+ h: number;
9
+ };
10
+ margins: PageMargins;
11
+ pageNumbering: SectionPageNumbering;
12
+ columns?: ColumnLayout;
13
+ };
14
+ declare const DEFAULT_COLUMNS: ColumnLayout;
15
+ declare const CONTINUE_PAGE_NUMBERING: SectionPageNumbering;
16
+ declare function collectSectionConfigs(blocks: FlowBlock[], initialConfig: SectionLayoutConfig, finalConfig: SectionLayoutConfig): {
17
+ configs: SectionLayoutConfig[];
18
+ breakIndices: number[];
19
+ };
20
+ /**
21
+ * Estimate the height of a short paragraph-only multi-column section so its
22
+ * final page can use Word-style balanced columns. Sections containing tables,
23
+ * floating blocks, or authored breaks keep the normal bottom-up pagination;
24
+ * those need structure-aware balancing rather than a height estimate.
25
+ */
26
+ type BalancedParagraphSectionHeightOptions = {
27
+ blocks: FlowBlock[];
28
+ measures: Measure[];
29
+ startIndex: number;
30
+ endIndex: number;
31
+ incomingSpacing: number;
32
+ columnCount: number;
33
+ availableHeight: number;
34
+ };
35
+ declare function balancedParagraphSectionHeight({ blocks, measures, startIndex, endIndex, incomingSpacing, columnCount, availableHeight }: BalancedParagraphSectionHeightOptions): number | undefined;
36
+ /**
37
+ * Handle a section break block.
38
+ * @param block - The section break block (current section's properties)
39
+ * @param paginator - The paginator instance
40
+ * @param nextSectionConfig - Page layout for the NEXT section
41
+ * @param nextSectionType - Break type of the NEXT section (how it starts relative to current)
42
+ */
43
+ declare function handleSectionBreak(_block: SectionBreakBlock, paginator: ReturnType<typeof createPaginator>, nextSectionConfig: SectionLayoutConfig, nextSectionType: SectionStart, nextSectionIndex?: number): void;
44
+ //#endregion
45
+ export { CONTINUE_PAGE_NUMBERING, DEFAULT_COLUMNS, SectionLayoutConfig, balancedParagraphSectionHeight, collectSectionConfigs, handleSectionBreak };