@stll/folio-core 0.48.0 → 0.50.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 (170) hide show
  1. package/dist/ai-edits/apply.js +565 -121
  2. package/dist/ai-edits/headless.d.ts +37 -1
  3. package/dist/ai-edits/headless.js +47 -16
  4. package/dist/ai-edits/minimal-replacement.d.ts +64 -0
  5. package/dist/ai-edits/minimal-replacement.js +282 -0
  6. package/dist/controller/layoutPipeline.js +11 -1
  7. package/dist/display-list/build/paragraphPrimitives.js +63 -7
  8. package/dist/display-list/build/textBoxPrimitives.js +1 -1
  9. package/dist/display-list/build/unsupported.d.ts +2 -2
  10. package/dist/display-list/build/unsupported.js +0 -0
  11. package/dist/display-list/dom/renderDisplayListToDom.d.ts +10 -1
  12. package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
  13. package/dist/display-list/html/renderDisplayListToHtml.d.ts +21 -0
  14. package/dist/display-list/html/renderDisplayListToHtml.js +126 -0
  15. package/dist/display-list/selectDisplayPages.d.ts +6 -0
  16. package/dist/display-list/selectDisplayPages.js +44 -0
  17. package/dist/docx/blockContentParser.js +2 -2
  18. package/dist/docx/bulletMarkers.d.ts +18 -2
  19. package/dist/docx/bulletMarkers.js +27 -2
  20. package/dist/docx/drawingGroupChildren.d.ts +60 -0
  21. package/dist/docx/drawingGroupChildren.js +176 -0
  22. package/dist/docx/drawingUtils.js +3 -0
  23. package/dist/docx/groupDrawingParser.d.ts +7 -1
  24. package/dist/docx/groupDrawingParser.js +123 -32
  25. package/dist/docx/packageParts.d.ts +45 -1
  26. package/dist/docx/packageParts.js +93 -2
  27. package/dist/docx/paraIdAttribute.d.ts +3 -2
  28. package/dist/docx/paragraphParser.js +91 -7
  29. package/dist/docx/paragraphTextBoxEnrichment.js +126 -4
  30. package/dist/docx/runParser.js +10 -0
  31. package/dist/docx/selectiveSaveFlags.d.ts +10 -3
  32. package/dist/docx/selectiveSaveFlags.js +1 -1
  33. package/dist/docx/selectiveXmlPatch.d.ts +56 -34
  34. package/dist/docx/selectiveXmlPatch.js +222 -115
  35. package/dist/docx/serializer/groupTextBoxWriteBack.d.ts +12 -0
  36. package/dist/docx/serializer/groupTextBoxWriteBack.js +101 -0
  37. package/dist/docx/serializer/paragraphSerializer.js +5 -4
  38. package/dist/docx/serializer/runSerializer.d.ts +5 -1
  39. package/dist/docx/serializer/runSerializer.js +4 -3
  40. package/dist/docx/shapeAlternateContent.d.ts +25 -0
  41. package/dist/docx/shapeAlternateContent.js +52 -0
  42. package/dist/docx/textBoxParser.d.ts +10 -2
  43. package/dist/docx/textBoxParser.js +9 -3
  44. package/dist/export-pdf.d.ts +35 -2
  45. package/dist/export-pdf.js +51 -20
  46. package/dist/fonts/fontsourceFaces.d.ts +47 -0
  47. package/dist/fonts/fontsourceFaces.js +230 -0
  48. package/dist/internal/headlessRevisionResolution.js +50 -13
  49. package/dist/layout-bridge/convert/flowBlockPostprocessing.d.ts +8 -0
  50. package/dist/layout-bridge/convert/flowBlockPostprocessing.js +106 -0
  51. package/dist/layout-bridge/convert/flowBorders.d.ts +17 -0
  52. package/dist/layout-bridge/convert/flowBorders.js +31 -0
  53. package/dist/layout-bridge/convert/flowConversionShared.d.ts +111 -0
  54. package/dist/layout-bridge/convert/flowConversionShared.js +23 -0
  55. package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -1
  56. package/dist/layout-bridge/convert/footnoteLayout.js +2 -1
  57. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +2 -1
  58. package/dist/layout-bridge/convert/imageConversion.d.ts +33 -0
  59. package/dist/layout-bridge/convert/imageConversion.js +118 -0
  60. package/dist/layout-bridge/convert/listMarkers.d.ts +28 -0
  61. package/dist/layout-bridge/convert/listMarkers.js +98 -0
  62. package/dist/layout-bridge/convert/noteReferences.d.ts +14 -0
  63. package/dist/layout-bridge/convert/noteReferences.js +29 -0
  64. package/dist/layout-bridge/convert/pageBreakSplitting.d.ts +16 -0
  65. package/dist/layout-bridge/convert/pageBreakSplitting.js +140 -0
  66. package/dist/layout-bridge/convert/paragraphAttrs.d.ts +20 -0
  67. package/dist/layout-bridge/convert/paragraphAttrs.js +234 -0
  68. package/dist/layout-bridge/convert/paragraphConversion.d.ts +12 -0
  69. package/dist/layout-bridge/convert/paragraphConversion.js +112 -0
  70. package/dist/layout-bridge/convert/paragraphRuns.d.ts +15 -0
  71. package/dist/layout-bridge/convert/paragraphRuns.js +281 -0
  72. package/dist/layout-bridge/convert/runFormattingMerge.d.ts +19 -0
  73. package/dist/layout-bridge/convert/runFormattingMerge.js +167 -0
  74. package/dist/layout-bridge/convert/runMarkFormatting.d.ts +11 -0
  75. package/dist/layout-bridge/convert/runMarkFormatting.js +291 -0
  76. package/dist/layout-bridge/convert/sectionBoundaries.d.ts +35 -0
  77. package/dist/layout-bridge/convert/sectionBoundaries.js +114 -0
  78. package/dist/layout-bridge/convert/tableConversion.d.ts +12 -0
  79. package/dist/layout-bridge/convert/tableConversion.js +303 -0
  80. package/dist/layout-bridge/convert/textBoxFill.d.ts +20 -0
  81. package/dist/layout-bridge/convert/textBoxFill.js +42 -0
  82. package/dist/layout-bridge/convert/textFormattingConversion.d.ts +22 -0
  83. package/dist/layout-bridge/convert/textFormattingConversion.js +120 -0
  84. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +5 -113
  85. package/dist/layout-bridge/convert/toFlowBlocks.js +11 -2060
  86. package/dist/layout-bridge/engine/hitTest.js +1 -1
  87. package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
  88. package/dist/layout-bridge/engine/measuring/index.js +2 -1
  89. package/dist/layout-bridge/engine/selectionRects.js +1 -1
  90. package/dist/layout-engine/imageLayout.d.ts +9 -0
  91. package/dist/layout-engine/imageLayout.js +48 -0
  92. package/dist/layout-engine/index.d.ts +4 -19
  93. package/dist/layout-engine/index.js +14 -910
  94. package/dist/layout-engine/keep-together.d.ts +5 -2
  95. package/dist/layout-engine/keep-together.js +5 -1
  96. package/dist/layout-engine/layoutFlowShared.d.ts +6 -0
  97. package/dist/layout-engine/layoutFlowShared.js +8 -0
  98. package/dist/layout-engine/measure/advanceComposition.js +49 -3
  99. package/dist/layout-engine/measure/crossRunWords.d.ts +45 -0
  100. package/dist/layout-engine/measure/crossRunWords.js +172 -0
  101. package/dist/layout-engine/measure/floatingLineClearance.d.ts +24 -0
  102. package/dist/layout-engine/measure/floatingLineClearance.js +44 -0
  103. package/dist/layout-engine/measure/inlineObjectMetrics.d.ts +6 -0
  104. package/dist/layout-engine/measure/inlineObjectMetrics.js +52 -0
  105. package/dist/layout-engine/measure/justification.d.ts +32 -0
  106. package/dist/layout-engine/measure/justification.js +83 -0
  107. package/dist/layout-engine/measure/lineFitting.d.ts +48 -0
  108. package/dist/layout-engine/measure/lineFitting.js +145 -0
  109. package/dist/layout-engine/measure/lineTypography.d.ts +44 -0
  110. package/dist/layout-engine/measure/lineTypography.js +124 -0
  111. package/dist/layout-engine/measure/measureBlocks.js +7 -4
  112. package/dist/layout-engine/measure/measureContainer.js +46 -3
  113. package/dist/layout-engine/measure/measureHelpers.js +13 -3
  114. package/dist/layout-engine/measure/measureParagraph.d.ts +1 -20
  115. package/dist/layout-engine/measure/measureParagraph.js +41 -780
  116. package/dist/layout-engine/measure/paragraphMeasureShared.d.ts +109 -0
  117. package/dist/layout-engine/measure/paragraphMeasureShared.js +82 -0
  118. package/dist/layout-engine/measure/paragraphTabs.d.ts +18 -0
  119. package/dist/layout-engine/measure/paragraphTabs.js +78 -0
  120. package/dist/layout-engine/measure/smallCapsCasing.d.ts +56 -0
  121. package/dist/layout-engine/measure/smallCapsCasing.js +88 -0
  122. package/dist/layout-engine/paginator.d.ts +2 -0
  123. package/dist/layout-engine/paginator.js +5 -1
  124. package/dist/layout-engine/paragraphLayout.d.ts +23 -0
  125. package/dist/layout-engine/paragraphLayout.js +204 -0
  126. package/dist/layout-engine/paragraphSpacing.d.ts +4 -2
  127. package/dist/layout-engine/paragraphSpacing.js +2 -1
  128. package/dist/layout-engine/sectionLayout.d.ts +45 -0
  129. package/dist/layout-engine/sectionLayout.js +216 -0
  130. package/dist/layout-engine/tableLayout.d.ts +17 -0
  131. package/dist/layout-engine/tableLayout.js +369 -0
  132. package/dist/layout-engine/tableRowBreak.d.ts +8 -1
  133. package/dist/layout-engine/tableRowBreak.js +22 -1
  134. package/dist/layout-engine/textBoxLayout.d.ts +15 -0
  135. package/dist/layout-engine/textBoxLayout.js +137 -0
  136. package/dist/layout-engine/types.d.ts +21 -2
  137. package/dist/layout-painter/renderPage.js +1 -0
  138. package/dist/layout-painter/renderParagraph.js +41 -4
  139. package/dist/layout-painter/renderTextBox.d.ts +12 -2
  140. package/dist/layout-painter/renderTextBox.js +26 -2
  141. package/dist/paged-layout/sectionBlockWidths.d.ts +2 -1
  142. package/dist/paged-layout/sectionBlockWidths.js +1 -1
  143. package/dist/prosemirror/alternateContentAttrs.d.ts +9 -0
  144. package/dist/prosemirror/alternateContentAttrs.js +35 -0
  145. package/dist/prosemirror/attrs/index.js +90 -0
  146. package/dist/prosemirror/commands/comments.js +51 -41
  147. package/dist/prosemirror/contentControlRevisions.d.ts +43 -0
  148. package/dist/prosemirror/contentControlRevisions.js +84 -0
  149. package/dist/prosemirror/conversion/fromProseDoc.js +46 -22
  150. package/dist/prosemirror/conversion/toProseDoc.js +42 -19
  151. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +7 -7
  152. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -7
  153. package/dist/prosemirror/extensions/nodes/FieldExtension.js +6 -3
  154. package/dist/prosemirror/extensions/nodes/SdtExtension.js +9 -2
  155. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
  156. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +3 -0
  157. package/dist/prosemirror/listMarker.js +2 -2
  158. package/dist/prosemirror/paragraphDirection.d.ts +29 -2
  159. package/dist/prosemirror/paragraphDirection.js +21 -2
  160. package/dist/prosemirror/plugins/suggestionMode.js +37 -8
  161. package/dist/prosemirror/rejoinRunCarriers.d.ts +27 -0
  162. package/dist/prosemirror/rejoinRunCarriers.js +66 -0
  163. package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +10 -0
  164. package/dist/prosemirror/runIdentityAcrossRevisions.js +90 -0
  165. package/dist/prosemirror/schema/nodes.d.ts +37 -5
  166. package/dist/server.d.ts +3 -2
  167. package/dist/types/content.d.ts +2 -2
  168. package/package.json +2 -2
  169. package/dist/layout-engine/justifiedLineFit.d.ts +0 -7
  170. package/dist/layout-engine/justifiedLineFit.js +0 -6
@@ -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,56 @@
1
+ //#region src/layout-engine/measure/smallCapsCasing.d.ts
2
+ /**
3
+ * Casing rules for synthesized `w:smallCaps` glyphs.
4
+ *
5
+ * A face that carries no `smcp`/`c2sc` substitution has no true small-capital
6
+ * glyph, so it is faked: a lowercase letter is drawn as its uppercase form at
7
+ * a smaller point size, on the run's own baseline, at a fixed fraction of the
8
+ * run's declared size — 0.8, an exact ratio observed to repeat across mixed
9
+ * two-size runs at every point size and never round to a half-point `w:sz`
10
+ * step. That is not a canvas or DOM backend's own `font-variant: small-caps`,
11
+ * which synthesizes at its own, uncontrollable, narrower ratio (Blink and
12
+ * WebKit use 0.7).
13
+ *
14
+ * Pure: no canvas, no DOM. Shared by every measurement and paint backend so a
15
+ * run's advances and its glyphs agree on which characters shrink.
16
+ */
17
+ /** Fraction of the run's declared size a synthesized small capital draws at. */
18
+ declare const SMALL_CAPS_SCALE = 0.8;
19
+ /**
20
+ * Whether `char` is a cased letter in its lowercase form — the class
21
+ * `w:smallCaps` draws as a shrunken, uppercased capital. Digits, punctuation,
22
+ * symbols and already-uppercase letters keep the run's full size; combining
23
+ * `char`'s upper and lower forms this way (rather than comparing to a
24
+ * separate "is uppercase" test) is what correctly leaves caseless scripts
25
+ * (CJK, most punctuation) at full size too, since neither form differs from
26
+ * the character itself there.
27
+ */
28
+ declare function isSmallCapsLetter(char: string): boolean;
29
+ /**
30
+ * Per-code-point small-caps classification for `text`, one entry per code
31
+ * point (iterated the same way `[...text]`/`for...of` does, so it lines up
32
+ * with any other code-point walk of the same string).
33
+ *
34
+ * Whitespace carries no case of its own; a run's space draws at the size of
35
+ * the character immediately before it — the trailing space after a
36
+ * synthesized word is itself synthesized — so a whitespace code point here
37
+ * takes the previous code point's class, and only falls back to full size
38
+ * when it opens the string (nothing to inherit from).
39
+ */
40
+ declare function smallCapsMask(text: string): boolean[];
41
+ /** A maximal run of `text` drawn at one size: `small` selects which. */
42
+ type SmallCapsSegment = {
43
+ readonly text: string;
44
+ readonly small: boolean;
45
+ };
46
+ /**
47
+ * `text` split into maximal same-size segments for painting, each already
48
+ * uppercased where `small` — the shrunken capital a `smcp`-less face
49
+ * substitutes for the source lowercase letter. A run with no lowercase letter
50
+ * at all (already-uppercase headings, digits, punctuation) comes back as one
51
+ * `small: false` segment, so a caller can skip the extra painting work its
52
+ * mixed-size runs need.
53
+ */
54
+ declare function smallCapsSegments(text: string): SmallCapsSegment[];
55
+ //#endregion
56
+ export { SMALL_CAPS_SCALE, SmallCapsSegment, isSmallCapsLetter, smallCapsMask, smallCapsSegments };
@@ -0,0 +1,88 @@
1
+ //#region src/layout-engine/measure/smallCapsCasing.ts
2
+ /**
3
+ * Casing rules for synthesized `w:smallCaps` glyphs.
4
+ *
5
+ * A face that carries no `smcp`/`c2sc` substitution has no true small-capital
6
+ * glyph, so it is faked: a lowercase letter is drawn as its uppercase form at
7
+ * a smaller point size, on the run's own baseline, at a fixed fraction of the
8
+ * run's declared size — 0.8, an exact ratio observed to repeat across mixed
9
+ * two-size runs at every point size and never round to a half-point `w:sz`
10
+ * step. That is not a canvas or DOM backend's own `font-variant: small-caps`,
11
+ * which synthesizes at its own, uncontrollable, narrower ratio (Blink and
12
+ * WebKit use 0.7).
13
+ *
14
+ * Pure: no canvas, no DOM. Shared by every measurement and paint backend so a
15
+ * run's advances and its glyphs agree on which characters shrink.
16
+ */
17
+ /** Fraction of the run's declared size a synthesized small capital draws at. */
18
+ const SMALL_CAPS_SCALE = .8;
19
+ /** Whitespace never gets its own small-caps class; see `smallCapsMask`. */
20
+ const WHITESPACE_PATTERN = /\s/u;
21
+ /**
22
+ * Whether `char` is a cased letter in its lowercase form — the class
23
+ * `w:smallCaps` draws as a shrunken, uppercased capital. Digits, punctuation,
24
+ * symbols and already-uppercase letters keep the run's full size; combining
25
+ * `char`'s upper and lower forms this way (rather than comparing to a
26
+ * separate "is uppercase" test) is what correctly leaves caseless scripts
27
+ * (CJK, most punctuation) at full size too, since neither form differs from
28
+ * the character itself there.
29
+ */
30
+ function isSmallCapsLetter(char) {
31
+ return char.toLocaleUpperCase() !== char;
32
+ }
33
+ /**
34
+ * Per-code-point small-caps classification for `text`, one entry per code
35
+ * point (iterated the same way `[...text]`/`for...of` does, so it lines up
36
+ * with any other code-point walk of the same string).
37
+ *
38
+ * Whitespace carries no case of its own; a run's space draws at the size of
39
+ * the character immediately before it — the trailing space after a
40
+ * synthesized word is itself synthesized — so a whitespace code point here
41
+ * takes the previous code point's class, and only falls back to full size
42
+ * when it opens the string (nothing to inherit from).
43
+ */
44
+ function smallCapsMask(text) {
45
+ const mask = [];
46
+ let previous = false;
47
+ for (const char of text) {
48
+ const small = WHITESPACE_PATTERN.test(char) ? previous : isSmallCapsLetter(char);
49
+ mask.push(small);
50
+ previous = small;
51
+ }
52
+ return mask;
53
+ }
54
+ /**
55
+ * `text` split into maximal same-size segments for painting, each already
56
+ * uppercased where `small` — the shrunken capital a `smcp`-less face
57
+ * substitutes for the source lowercase letter. A run with no lowercase letter
58
+ * at all (already-uppercase headings, digits, punctuation) comes back as one
59
+ * `small: false` segment, so a caller can skip the extra painting work its
60
+ * mixed-size runs need.
61
+ */
62
+ function smallCapsSegments(text) {
63
+ const mask = smallCapsMask(text);
64
+ const segments = [];
65
+ let current = "";
66
+ let currentSmall;
67
+ let index = 0;
68
+ for (const char of text) {
69
+ const small = mask[index] ?? false;
70
+ if (currentSmall !== void 0 && small !== currentSmall) {
71
+ segments.push({
72
+ text: current,
73
+ small: currentSmall
74
+ });
75
+ current = "";
76
+ }
77
+ current += small ? char.toLocaleUpperCase() : char;
78
+ currentSmall = small;
79
+ index += 1;
80
+ }
81
+ if (current) segments.push({
82
+ text: current,
83
+ small: currentSmall ?? false
84
+ });
85
+ return segments;
86
+ }
87
+ //#endregion
88
+ export { SMALL_CAPS_SCALE, isSmallCapsLetter, smallCapsMask, smallCapsSegments };
@@ -51,6 +51,8 @@ type PaginatorOptions = {
51
51
  firstPageMargins?: PageMargins;
52
52
  /** Per-section body margins used on even authored page numbers. */
53
53
  sectionEvenPageMargins?: (PageMargins | undefined)[];
54
+ /** Per-section body margins used on the first page of a `w:titlePg` section. */
55
+ sectionFirstPageMargins?: (PageMargins | undefined)[];
54
56
  /** Page-number policy for the initial section. */
55
57
  pageNumbering?: SectionPageNumbering;
56
58
  /** Column configuration (optional). */
@@ -94,7 +94,11 @@ function createPaginator(options) {
94
94
  }
95
95
  function getPageMargins(pageNumber, logicalPageNumber) {
96
96
  const evenMargins = logicalPageNumber % 2 === 0 ? options.sectionEvenPageMargins?.[currentSectionIndex] : void 0;
97
- const pageMargins = pageNumber === 1 && options.firstPageMargins ? { ...options.firstPageMargins } : { ...evenMargins ?? margins };
97
+ const titlePageMargins = currentSectionPageNumber === 1 ? options.sectionFirstPageMargins?.[currentSectionIndex] : void 0;
98
+ let pageMargins;
99
+ if (titlePageMargins) pageMargins = { ...titlePageMargins };
100
+ else if (pageNumber === 1 && options.firstPageMargins) pageMargins = { ...options.firstPageMargins };
101
+ else pageMargins = { ...evenMargins ?? margins };
98
102
  if (options.mirrorMargins === true && pageNumber % 2 === 0) return {
99
103
  ...pageMargins,
100
104
  left: pageMargins.right,
@@ -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 };