@stll/folio-core 0.7.0 → 0.9.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 (193) hide show
  1. package/README.md +39 -0
  2. package/dist/ai-edits/apply.js +18 -4
  3. package/dist/ai-edits/blockRange.d.ts +13 -2
  4. package/dist/ai-edits/blockRange.js +23 -2
  5. package/dist/ai-edits/headless.d.ts +31 -4
  6. package/dist/ai-edits/headless.js +148 -35
  7. package/dist/ai-edits/index.d.ts +6 -4
  8. package/dist/ai-edits/index.js +4 -1
  9. package/dist/ai-edits/scoped-reading.d.ts +9 -0
  10. package/dist/ai-edits/scoped-reading.js +60 -0
  11. package/dist/ai-edits/snapshot.js +53 -7
  12. package/dist/ai-edits/types.d.ts +46 -3
  13. package/dist/compat/eigenpal.d.ts +8 -3
  14. package/dist/compat/eigenpal.js +7 -2
  15. package/dist/controller/fontReadiness.d.ts +29 -0
  16. package/dist/controller/fontReadiness.js +139 -0
  17. package/dist/controller/latestRequestGate.d.ts +13 -0
  18. package/dist/controller/latestRequestGate.js +16 -0
  19. package/dist/controller/layoutPipeline.js +88 -10
  20. package/dist/document-operations.d.ts +10 -3
  21. package/dist/document-operations.js +29 -14
  22. package/dist/docx/blockContentParser.js +53 -10
  23. package/dist/docx/commentParser.js +1 -1
  24. package/dist/docx/compatibility.d.ts +30 -2
  25. package/dist/docx/compatibility.js +118 -39
  26. package/dist/docx/conformance.d.ts +6 -0
  27. package/dist/docx/conformance.js +18 -0
  28. package/dist/docx/encryption/agileDecryption.js +1 -1
  29. package/dist/docx/encryption/encryptionInfo.js +1 -1
  30. package/dist/docx/encryption/openEncryptedDocx.js +1 -1
  31. package/dist/docx/ensureParaIds.d.ts +29 -0
  32. package/dist/docx/ensureParaIds.js +423 -0
  33. package/dist/docx/fontTableParser.d.ts +6 -0
  34. package/dist/docx/fontTableParser.js +72 -0
  35. package/dist/docx/groupDrawingParser.js +7 -2
  36. package/dist/docx/headerFooterParser.js +1 -1
  37. package/dist/docx/normalizeBaseDirection.js +1 -1
  38. package/dist/docx/numberingParser.js +2 -0
  39. package/dist/docx/paragraphParser.js +21 -1
  40. package/dist/docx/paragraphTextBoxEnrichment.js +141 -2
  41. package/dist/docx/parser.d.ts +1 -1
  42. package/dist/docx/parser.js +14 -9
  43. package/dist/docx/rezip.js +70 -5
  44. package/dist/docx/runConsolidator.js +4 -0
  45. package/dist/docx/runParser.js +19 -1
  46. package/dist/docx/selectiveSave.js +3 -3
  47. package/dist/docx/selectiveXmlPatch.d.ts +10 -1
  48. package/dist/docx/selectiveXmlPatch.js +1 -1
  49. package/dist/docx/serializer/fontTableSerializer.d.ts +6 -0
  50. package/dist/docx/serializer/fontTableSerializer.js +26 -0
  51. package/dist/docx/serializer/headerFooterSerializer.js +1 -1
  52. package/dist/docx/serializer/paragraphSerializer.js +8 -0
  53. package/dist/docx/serializer/runSerializer.js +14 -2
  54. package/dist/docx/serializer/settingsSerializer.d.ts +6 -0
  55. package/dist/docx/serializer/settingsSerializer.js +16 -0
  56. package/dist/docx/serializer/stylesSerializer.d.ts +6 -0
  57. package/dist/docx/serializer/stylesSerializer.js +56 -0
  58. package/dist/docx/serializer/tableSerializer.js +29 -26
  59. package/dist/docx/serializer/themeSerializer.d.ts +6 -0
  60. package/dist/docx/serializer/themeSerializer.js +36 -0
  61. package/dist/docx/settingsParser.js +46 -0
  62. package/dist/docx/styleParser.js +26 -1
  63. package/dist/docx/tableParser.js +19 -1
  64. package/dist/docx/textBoxParser.js +6 -1
  65. package/dist/docx/vmlImageParser.js +146 -1
  66. package/dist/i18n/messages/catalogs.gen.d.ts +5916 -5916
  67. package/dist/i18n/messages/catalogs.gen.js +5910 -5910
  68. package/dist/index.d.ts +8 -3
  69. package/dist/index.js +7 -2
  70. package/dist/layout-bridge/convert/footnoteLayout.d.ts +3 -0
  71. package/dist/layout-bridge/convert/footnoteLayout.js +3 -1
  72. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +3 -0
  73. package/dist/layout-bridge/convert/headerFooterLayout.js +22 -3
  74. package/dist/layout-bridge/convert/paragraphFrames.d.ts +7 -0
  75. package/dist/layout-bridge/convert/paragraphFrames.js +136 -0
  76. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +14 -2
  77. package/dist/layout-bridge/convert/toFlowBlocks.js +93 -17
  78. package/dist/layout-bridge/engine/hitTest.js +2 -1
  79. package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
  80. package/dist/layout-bridge/engine/measuring/index.js +2 -1
  81. package/dist/layout-bridge/engine/selectionRects.js +2 -1
  82. package/dist/layout-engine/headerFooterRefs.d.ts +7 -0
  83. package/dist/layout-engine/headerFooterRefs.js +40 -0
  84. package/dist/layout-engine/index.d.ts +3 -2
  85. package/dist/layout-engine/index.js +68 -106
  86. package/dist/layout-engine/measure/cache.js +11 -3
  87. package/dist/layout-engine/measure/font-metrics.worker.js +1 -0
  88. package/dist/layout-engine/measure/index.d.ts +2 -1
  89. package/dist/layout-engine/measure/index.js +2 -1
  90. package/dist/layout-engine/measure/lineBreakProvider.d.ts +33 -0
  91. package/dist/layout-engine/measure/lineBreakProvider.js +252 -0
  92. package/dist/layout-engine/measure/lineBreaks.d.ts +7 -2
  93. package/dist/layout-engine/measure/lineBreaks.js +11 -18
  94. package/dist/layout-engine/measure/listMarkerWidth.d.ts +4 -1
  95. package/dist/layout-engine/measure/listMarkerWidth.js +33 -8
  96. package/dist/layout-engine/measure/measureBlocks.d.ts +4 -2
  97. package/dist/layout-engine/measure/measureBlocks.js +138 -86
  98. package/dist/layout-engine/measure/measureContainer.js +32 -9
  99. package/dist/layout-engine/measure/measureHelpers.js +5 -2
  100. package/dist/layout-engine/measure/measureParagraph.js +346 -74
  101. package/dist/layout-engine/measure/measureTypes.d.ts +2 -1
  102. package/dist/layout-engine/measure/measureWorker.d.ts +2 -2
  103. package/dist/layout-engine/measure/measureWorker.js +8 -19
  104. package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +3 -14
  105. package/dist/layout-engine/measure/tableCellFloating.d.ts +10 -3
  106. package/dist/layout-engine/measure/tableCellFloating.js +37 -29
  107. package/dist/layout-engine/measure/tableCellFlow.d.ts +27 -0
  108. package/dist/layout-engine/measure/tableCellFlow.js +73 -0
  109. package/dist/layout-engine/measure/tableCellGrid.d.ts +15 -0
  110. package/dist/layout-engine/measure/tableCellGrid.js +62 -0
  111. package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +18 -0
  112. package/dist/layout-engine/measure/textBoxParagraphLayout.js +32 -0
  113. package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +18 -0
  114. package/dist/layout-engine/measure/textMeasurementPolicy.js +24 -0
  115. package/dist/layout-engine/paginator.d.ts +2 -1
  116. package/dist/layout-engine/paginator.js +7 -6
  117. package/dist/layout-engine/paragraphFrame.d.ts +22 -0
  118. package/dist/layout-engine/paragraphFrame.js +17 -0
  119. package/dist/layout-engine/paragraphSequence.d.ts +7 -0
  120. package/dist/layout-engine/paragraphSequence.js +25 -0
  121. package/dist/layout-engine/paragraphSpacing.d.ts +17 -0
  122. package/dist/layout-engine/paragraphSpacing.js +30 -0
  123. package/dist/layout-engine/renderedBreakReconciliation.d.ts +63 -0
  124. package/dist/layout-engine/renderedBreakReconciliation.js +86 -0
  125. package/dist/layout-engine/tableRowBreak.d.ts +5 -2
  126. package/dist/layout-engine/tableRowBreak.js +62 -29
  127. package/dist/layout-engine/types.d.ts +54 -12
  128. package/dist/layout-engine/types.js +15 -2
  129. package/dist/layout-painter/anchoredImagePosition.d.ts +23 -0
  130. package/dist/layout-painter/anchoredImagePosition.js +95 -0
  131. package/dist/layout-painter/borderStroke.d.ts +23 -0
  132. package/dist/layout-painter/borderStroke.js +39 -0
  133. package/dist/layout-painter/renderImage.d.ts +5 -4
  134. package/dist/layout-painter/renderImage.js +18 -4
  135. package/dist/layout-painter/renderPage.d.ts +2 -27
  136. package/dist/layout-painter/renderPage.js +15 -101
  137. package/dist/layout-painter/renderParagraph.js +45 -26
  138. package/dist/layout-painter/renderTable.d.ts +2 -0
  139. package/dist/layout-painter/renderTable.js +257 -47
  140. package/dist/layout-painter/renderTextBox.js +17 -7
  141. package/dist/managers/DocumentLoaderManager.d.ts +1 -1
  142. package/dist/managers/DocumentLoaderManager.js +2 -2
  143. package/dist/paged-layout/sectionBlockWidths.d.ts +3 -0
  144. package/dist/paged-layout/sectionBlockWidths.js +9 -0
  145. package/dist/paged-layout/sectionGeometry.js +1 -1
  146. package/dist/prosemirror/attrs/index.d.ts +4 -2
  147. package/dist/prosemirror/attrs/index.js +32 -2
  148. package/dist/prosemirror/commands/propertyChangeScope.js +1 -1
  149. package/dist/prosemirror/conversion/fromProseDoc.js +35 -18
  150. package/dist/prosemirror/conversion/toProseDoc.js +73 -37
  151. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  152. package/dist/prosemirror/extensions/core/ParagraphExtension.js +8 -2
  153. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +1 -1
  154. package/dist/prosemirror/extensions/features/ListExtension.js +2 -0
  155. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +68 -16
  156. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +1 -0
  157. package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
  158. package/dist/prosemirror/extensions/marks/LanguageExtension.d.ts +7 -0
  159. package/dist/prosemirror/extensions/marks/LanguageExtension.js +30 -0
  160. package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
  161. package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
  162. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +1 -1
  163. package/dist/prosemirror/extensions/marks/markUtils.js +12 -0
  164. package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
  165. package/dist/prosemirror/extensions/nodes/TableExtension.js +4 -2
  166. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
  167. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
  168. package/dist/prosemirror/schema/index.d.ts +2 -2
  169. package/dist/prosemirror/schema/marks.d.ts +6 -1
  170. package/dist/prosemirror/schema/nodes.d.ts +16 -12
  171. package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
  172. package/dist/prosemirror/utils/tabCalculator.d.ts +3 -1
  173. package/dist/prosemirror/utils/tabCalculator.js +12 -10
  174. package/dist/prosemirror/validation.js +4 -1
  175. package/dist/redline.js +13 -3
  176. package/dist/server.d.ts +9 -4
  177. package/dist/server.js +7 -2
  178. package/dist/style-sets/extract.d.ts +35 -0
  179. package/dist/style-sets/extract.js +123 -0
  180. package/dist/style-sets/stellaStyle.d.ts +13 -0
  181. package/dist/style-sets/stellaStyle.js +516 -0
  182. package/dist/style-sets/types.d.ts +31 -0
  183. package/dist/style-sets/types.js +5 -0
  184. package/dist/types/index.d.ts +2 -1
  185. package/dist/utils/createDocument.d.ts +12 -2
  186. package/dist/utils/createDocument.js +55 -36
  187. package/dist/utils/fontResolver.js +6 -6
  188. package/dist/utils/formatToStyle.js +1 -1
  189. package/dist/utils/hexId.d.ts +8 -1
  190. package/dist/utils/hexId.js +11 -3
  191. package/dist/utils/paragraphFormattingMerge.d.ts +14 -3
  192. package/dist/utils/paragraphFormattingMerge.js +14 -4
  193. package/package.json +5 -1
@@ -1,4 +1,4 @@
1
- import { MeasureWorkerRequest, MeasureWorkerResponse } from "./measureWorkerProtocol.js";
1
+ import { MeasureRequestEntry, MeasureWorkerRequest, MeasureWorkerResponse } from "./measureWorkerProtocol.js";
2
2
 
3
3
  //#region src/layout-engine/measure/measureWorker.d.ts
4
4
  /**
@@ -32,7 +32,7 @@ declare function __setMeasureWorkerTransport(factory: TransportFactory | null):
32
32
  * `OffscreenCanvas`/`Worker`, or when the proxy has been disabled by
33
33
  * a previous error.
34
34
  */
35
- declare function prefetchMeasurement(text: string, font: string, letterSpacing: number, horizontalScale: number, fontCacheKey: string, fontFingerprintWidth: number): void;
35
+ declare function prefetchMeasurement(request: MeasureRequestEntry): void;
36
36
  declare function canPrefetchMeasurement(): boolean;
37
37
  /**
38
38
  * Test-only: synchronously drain the pending queue. The caller is
@@ -142,16 +142,10 @@ function flush(current) {
142
142
  const entries = [];
143
143
  for (const entry of current.pending.values()) {
144
144
  if (entries.length >= MAX_BATCH_SIZE) break;
145
- current.pending.delete(makeKey(entry.text, entry.fontCacheKey, entry.letterSpacing));
146
- if (entry.cacheGeneration !== cacheGeneration) continue;
147
- entries.push({
148
- text: entry.text,
149
- font: entry.font,
150
- fontCacheKey: entry.fontCacheKey,
151
- fontFingerprintWidth: entry.fontFingerprintWidth,
152
- letterSpacing: entry.letterSpacing,
153
- horizontalScale: entry.horizontalScale
154
- });
145
+ const { cacheGeneration: entryGeneration, ...request } = entry;
146
+ current.pending.delete(makeKey(request.text, request.fontCacheKey, request.letterSpacing));
147
+ if (entryGeneration !== cacheGeneration) continue;
148
+ entries.push(request);
155
149
  }
156
150
  if (entries.length === 0) {
157
151
  if (current.pending.size > 0) scheduleFlush(current);
@@ -196,20 +190,15 @@ function handleResponse(current, message) {
196
190
  * `OffscreenCanvas`/`Worker`, or when the proxy has been disabled by
197
191
  * a previous error.
198
192
  */
199
- function prefetchMeasurement(text, font, letterSpacing, horizontalScale, fontCacheKey, fontFingerprintWidth) {
193
+ function prefetchMeasurement(request) {
200
194
  if (!isWorkerFontMetricsEnabled()) return;
201
- if (!text) return;
195
+ if (!request.text) return;
202
196
  const current = ensureProxy();
203
197
  if (current === null) return;
204
- const key = makeKey(text, fontCacheKey, letterSpacing);
198
+ const key = makeKey(request.text, request.fontCacheKey, request.letterSpacing);
205
199
  if (current.pending.has(key)) return;
206
200
  current.pending.set(key, {
207
- text,
208
- font,
209
- fontCacheKey,
210
- fontFingerprintWidth,
211
- letterSpacing,
212
- horizontalScale,
201
+ ...request,
213
202
  cacheGeneration: getTextWidthCacheGeneration()
214
203
  });
215
204
  scheduleFlush(current);
@@ -1,18 +1,6 @@
1
+ import { FontKerningMode } from "./textMeasurementPolicy.js";
2
+
1
3
  //#region src/layout-engine/measure/measureWorkerProtocol.d.ts
2
- /**
3
- * Wire format shared between the main-thread proxy
4
- * (`measureWorker.ts`) and the worker entry
5
- * (`font-metrics.worker.ts`).
6
- *
7
- * The protocol is intentionally minimal: a batched request carrying an
8
- * array of measurement inputs, and a matching response with widths in
9
- * the same order. The worker is a pure function — no state, no cache.
10
- * The main thread owns the LRU cache and is the sole authority on what
11
- * is worth measuring.
12
- *
13
- * Keep this file tiny and dependency-free so it can be imported from
14
- * both contexts.
15
- */
16
4
  /**
17
5
  * Single measurement request inside a batch. Mirrors the inputs that
18
6
  * `measureTextWidth` already takes on the main thread.
@@ -40,6 +28,7 @@ type MeasureRequestEntry = {
40
28
  fontFingerprintWidth: number;
41
29
  letterSpacing: number;
42
30
  horizontalScale: number;
31
+ fontKerning: FontKerningMode;
43
32
  };
44
33
  declare const WORKER_FONT_FINGERPRINT_TEXT = "HAMBURGEFONTS ivwqy 0123456789";
45
34
  /**
@@ -1,4 +1,4 @@
1
- import { TableCell, TableCellMeasure } from "../types.js";
1
+ import { ImageRun, TableCell, TableCellMeasure } from "../types.js";
2
2
  import { FloatingImageZone } from "./floatingZones.js";
3
3
 
4
4
  //#region src/layout-engine/measure/tableCellFloating.d.ts
@@ -8,6 +8,11 @@ type TableCellFloatingImage = {
8
8
  height: number;
9
9
  alt?: string;
10
10
  transform?: string;
11
+ opacity?: number;
12
+ cropTop?: number;
13
+ cropRight?: number;
14
+ cropBottom?: number;
15
+ cropLeft?: number;
11
16
  x: number;
12
17
  y: number;
13
18
  side: "left" | "right";
@@ -19,8 +24,10 @@ type TableCellFloatingImage = {
19
24
  pmStart?: number;
20
25
  pmEnd?: number;
21
26
  };
27
+ type TableCellFloatingPosition = Pick<TableCellFloatingImage, "x" | "y" | "side">;
28
+ type ResolveTableCellFloatingPosition = (run: ImageRun, paragraphY: number) => TableCellFloatingPosition;
22
29
  declare function getTableCellContentWidth(cell: TableCell | undefined, cellMeasure: TableCellMeasure): number;
23
- declare function getTableCellFloatingImages(cell: TableCell, cellMeasure: TableCellMeasure, contentWidth: number): TableCellFloatingImage[];
30
+ declare function getTableCellFloatingImages(cell: TableCell, cellMeasure: TableCellMeasure, contentWidth: number, resolvePosition?: ResolveTableCellFloatingPosition): TableCellFloatingImage[];
24
31
  declare function buildTableCellFloatingZones(floatingImages: TableCellFloatingImage[], contentWidth: number): FloatingImageZone[];
25
32
  //#endregion
26
- export { TableCellFloatingImage, buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages };
33
+ export { ResolveTableCellFloatingPosition, TableCellFloatingImage, buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages };
@@ -1,22 +1,22 @@
1
1
  import { emuToPixels } from "../../utils/units.js";
2
2
  import { isFloatingImageRun } from "../types.js";
3
3
  import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
4
+ import { createTableCellFlowState, placeTableCellBlock } from "./tableCellFlow.js";
4
5
  //#region src/layout-engine/measure/tableCellFloating.ts
5
6
  function getTableCellContentWidth(cell, cellMeasure) {
6
7
  const padLeft = cell?.padding?.left ?? 7;
7
8
  const padRight = cell?.padding?.right ?? 7;
8
9
  return Math.max(0, cellMeasure.width - padLeft - padRight);
9
10
  }
10
- function getTableCellFloatingImages(cell, cellMeasure, contentWidth) {
11
+ function getTableCellFloatingImages(cell, cellMeasure, contentWidth, resolvePosition) {
11
12
  const result = [];
12
- let paragraphY = 0;
13
+ const flowState = createTableCellFlowState();
13
14
  for (let blockIndex = 0; blockIndex < cell.blocks.length; blockIndex++) {
14
15
  const block = cell.blocks[blockIndex];
15
- if (block?.kind !== "paragraph") {
16
- const blockMeasure = cellMeasure.blocks[blockIndex];
17
- if (blockMeasure?.kind === "table") paragraphY += blockMeasure.totalHeight;
18
- continue;
19
- }
16
+ const blockMeasure = cellMeasure.blocks[blockIndex];
17
+ if (!block || !blockMeasure) continue;
18
+ const placement = placeTableCellBlock(flowState, block, blockMeasure);
19
+ if (block.kind !== "paragraph") continue;
20
20
  for (const run of block.runs) {
21
21
  if (run.kind !== "image" || !isFloatingImageRun(run)) continue;
22
22
  const position = run.position;
@@ -24,30 +24,35 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth) {
24
24
  const distBottom = run.distBottom ?? 0;
25
25
  const distLeft = run.distLeft ?? 12;
26
26
  const distRight = run.distRight ?? 12;
27
- let side = "left";
28
- let x = 0;
29
- if (position?.horizontal) {
30
- const horizontal = position.horizontal;
31
- if (horizontal.align === "right") {
27
+ let side;
28
+ let x;
29
+ let y;
30
+ if (resolvePosition) ({side, x, y} = resolvePosition(run, placement.contentTop));
31
+ else {
32
+ side = "left";
33
+ x = 0;
34
+ if (position?.horizontal) {
35
+ const horizontal = position.horizontal;
36
+ if (horizontal.align === "right") {
37
+ side = "right";
38
+ x = contentWidth - run.width;
39
+ } else if (horizontal.align === "center") x = (contentWidth - run.width) / 2;
40
+ else if (horizontal.posOffset !== void 0) {
41
+ x = emuToPixels(horizontal.posOffset);
42
+ side = x > contentWidth / 2 ? "right" : "left";
43
+ }
44
+ } else if (run.cssFloat === "right") {
32
45
  side = "right";
33
46
  x = contentWidth - run.width;
34
- } else if (horizontal.align === "left") x = 0;
35
- else if (horizontal.align === "center") x = (contentWidth - run.width) / 2;
36
- else if (horizontal.posOffset !== void 0) {
37
- x = emuToPixels(horizontal.posOffset);
38
- side = x > contentWidth / 2 ? "right" : "left";
39
47
  }
40
- } else if (run.cssFloat === "right") {
41
- side = "right";
42
- x = contentWidth - run.width;
43
- }
44
- let y = paragraphY;
45
- if (position?.vertical) {
46
- const vertical = position.vertical;
47
- if (vertical.posOffset !== void 0) y = paragraphY + emuToPixels(vertical.posOffset);
48
- else if (vertical.align === "top") y = 0;
48
+ y = placement.contentTop;
49
+ if (position?.vertical) {
50
+ const vertical = position.vertical;
51
+ if (vertical.posOffset !== void 0) y = placement.contentTop + emuToPixels(vertical.posOffset);
52
+ else if (vertical.align === "top") y = 0;
53
+ }
54
+ x = Math.max(0, Math.min(x, contentWidth - run.width));
49
55
  }
50
- x = Math.max(0, Math.min(x, contentWidth - run.width));
51
56
  let wrapText = "bothSides";
52
57
  if (run.cssFloat === "left") wrapText = "right";
53
58
  else if (run.cssFloat === "right") wrapText = "left";
@@ -57,6 +62,11 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth) {
57
62
  height: run.height,
58
63
  ...run.alt !== void 0 ? { alt: run.alt } : {},
59
64
  ...run.transform !== void 0 ? { transform: run.transform } : {},
65
+ ...run.opacity != null ? { opacity: run.opacity } : {},
66
+ ...run.cropTop != null ? { cropTop: run.cropTop } : {},
67
+ ...run.cropRight != null ? { cropRight: run.cropRight } : {},
68
+ ...run.cropBottom != null ? { cropBottom: run.cropBottom } : {},
69
+ ...run.cropLeft != null ? { cropLeft: run.cropLeft } : {},
60
70
  x,
61
71
  y,
62
72
  side,
@@ -69,8 +79,6 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth) {
69
79
  ...run.pmEnd !== void 0 ? { pmEnd: run.pmEnd } : {}
70
80
  });
71
81
  }
72
- const blockMeasure = cellMeasure.blocks[blockIndex];
73
- if (blockMeasure?.kind === "paragraph") paragraphY += blockMeasure.totalHeight;
74
82
  }
75
83
  return result;
76
84
  }
@@ -0,0 +1,27 @@
1
+ import { FlowBlock, Measure } from "../types.js";
2
+
3
+ //#region src/layout-engine/measure/tableCellFlow.d.ts
4
+ type TableCellBlockPlacement = {
5
+ top: number;
6
+ contentTop: number;
7
+ contentHeight: number;
8
+ leadingSpacing: number;
9
+ };
10
+ type TableCellFlowState = {
11
+ height: number;
12
+ previousParagraphWasEmpty: boolean;
13
+ trailingSpacing: number;
14
+ };
15
+ declare const createTableCellFlowState: () => TableCellFlowState;
16
+ declare const finishTableCellFlow: (state: TableCellFlowState) => number;
17
+ declare const getTableCellBlockContentHeight: (block: FlowBlock, measure: Measure) => number;
18
+ /**
19
+ * Place one block in an unpaginated table-cell story.
20
+ *
21
+ * Adjacent paragraph spacing collapses to the larger side. Consecutive
22
+ * authored empty paragraphs remain independent vertical spacers, including
23
+ * at the end of the cell.
24
+ */
25
+ declare const placeTableCellBlock: (state: TableCellFlowState, block: FlowBlock, measure: Measure) => TableCellBlockPlacement;
26
+ //#endregion
27
+ export { TableCellBlockPlacement, TableCellFlowState, createTableCellFlowState, finishTableCellFlow, getTableCellBlockContentHeight, placeTableCellBlock };
@@ -0,0 +1,73 @@
1
+ import { measuredLineRangeHeight } from "../lineFlow.js";
2
+ import { isFloatingTextBoxBlock } from "../types.js";
3
+ import { getParagraphSpacingAfter, getParagraphSpacingBefore, isEmptyParagraph } from "../paragraphSpacing.js";
4
+ //#region src/layout-engine/measure/tableCellFlow.ts
5
+ const createTableCellFlowState = () => ({
6
+ height: 0,
7
+ previousParagraphWasEmpty: false,
8
+ trailingSpacing: 0
9
+ });
10
+ const finishTableCellFlow = (state) => state.height + state.trailingSpacing;
11
+ const isSuppressedParagraphMeasure = (measure) => measure.totalHeight === 0 && measure.lines.every(({ lineHeight }) => lineHeight === 0);
12
+ const paragraphSpacing = (block, measure) => {
13
+ if (isSuppressedParagraphMeasure(measure)) return {
14
+ before: 0,
15
+ after: 0
16
+ };
17
+ return {
18
+ before: getParagraphSpacingBefore(block),
19
+ after: getParagraphSpacingAfter(block)
20
+ };
21
+ };
22
+ const getTableCellBlockContentHeight = (block, measure) => {
23
+ if (block.kind === "textBox" && isFloatingTextBoxBlock(block)) return 0;
24
+ if (block.kind === "paragraph" && measure.kind === "paragraph") return measuredLineRangeHeight(measure.lines, 0, measure.lines.length);
25
+ if ("totalHeight" in measure) return measure.totalHeight;
26
+ if ("height" in measure) return measure.height;
27
+ return 0;
28
+ };
29
+ /**
30
+ * Place one block in an unpaginated table-cell story.
31
+ *
32
+ * Adjacent paragraph spacing collapses to the larger side. Consecutive
33
+ * authored empty paragraphs remain independent vertical spacers, including
34
+ * at the end of the cell.
35
+ */
36
+ const placeTableCellBlock = (state, block, measure) => {
37
+ const top = state.height;
38
+ const contentHeight = getTableCellBlockContentHeight(block, measure);
39
+ if (block.kind === "paragraph" && measure.kind === "paragraph" && isSuppressedParagraphMeasure(measure)) return {
40
+ top,
41
+ contentTop: top,
42
+ contentHeight: 0,
43
+ leadingSpacing: 0
44
+ };
45
+ if (block.kind !== "paragraph" || measure.kind !== "paragraph") {
46
+ const leadingSpacing = state.trailingSpacing;
47
+ const contentTop = top + leadingSpacing;
48
+ state.height = contentTop + contentHeight;
49
+ state.previousParagraphWasEmpty = false;
50
+ state.trailingSpacing = 0;
51
+ return {
52
+ top,
53
+ contentTop,
54
+ contentHeight,
55
+ leadingSpacing
56
+ };
57
+ }
58
+ const spacing = paragraphSpacing(block, measure);
59
+ const empty = isEmptyParagraph(block);
60
+ const leadingSpacing = empty && state.previousParagraphWasEmpty ? spacing.before + state.trailingSpacing : Math.max(spacing.before, state.trailingSpacing);
61
+ const contentTop = top + leadingSpacing;
62
+ state.height = contentTop + contentHeight;
63
+ state.previousParagraphWasEmpty = empty;
64
+ state.trailingSpacing = spacing.after;
65
+ return {
66
+ top,
67
+ contentTop,
68
+ contentHeight,
69
+ leadingSpacing
70
+ };
71
+ };
72
+ //#endregion
73
+ export { createTableCellFlowState, finishTableCellFlow, getTableCellBlockContentHeight, placeTableCellBlock };
@@ -0,0 +1,15 @@
1
+ import { TableCell, TableRow } from "../types.js";
2
+
3
+ //#region src/layout-engine/measure/tableCellGrid.d.ts
4
+ type TableCellGrid = {
5
+ occupiedColumnsByRow: ReadonlyMap<number, ReadonlySet<number>>;
6
+ sourceCellsByRow: ReadonlyMap<number, ReadonlyMap<number, TableCell>>;
7
+ sourceColumnsByCell: ReadonlyMap<TableCell, number>;
8
+ };
9
+ declare const buildTableCellGrid: (rows: readonly TableRow[], columnCount: number) => TableCellGrid;
10
+ declare const getFirstAvailableColumn: (grid: TableCellGrid, rowIndex: number, startingColumn: number) => number;
11
+ declare const getSourceCellAt: (grid: TableCellGrid, rowIndex: number, columnIndex: number) => TableCell | undefined;
12
+ declare const getSourceCellColumn: (grid: TableCellGrid, cell: TableCell) => number | undefined;
13
+ declare const getTableCellVerticalBorderHeight: (grid: TableCellGrid, cell: TableCell | undefined, rowIndex: number) => number;
14
+ //#endregion
15
+ export { TableCellGrid, buildTableCellGrid, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellVerticalBorderHeight };
@@ -0,0 +1,62 @@
1
+ //#region src/layout-engine/measure/tableCellGrid.ts
2
+ const buildTableCellGrid = (rows, columnCount) => {
3
+ const occupiedColumnsByRow = /* @__PURE__ */ new Map();
4
+ const sourceCellsByRow = /* @__PURE__ */ new Map();
5
+ const sourceColumnsByCell = /* @__PURE__ */ new Map();
6
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
7
+ const row = rows[rowIndex];
8
+ if (!row) continue;
9
+ let columnIndex = firstAvailableColumn(occupiedColumnsByRow.get(rowIndex), row.gridBefore ?? 0);
10
+ for (const cell of row.cells) {
11
+ const columnSpan = cell.colSpan ?? 1;
12
+ const rowSpan = cell.rowSpan ?? 1;
13
+ sourceColumnsByCell.set(cell, columnIndex);
14
+ const rowEnd = Math.min(rows.length, rowIndex + rowSpan);
15
+ const columnEnd = Math.min(columnCount, columnIndex + columnSpan);
16
+ for (let gridRowIndex = rowIndex; gridRowIndex < rowEnd; gridRowIndex++) {
17
+ const cellsByColumn = getOrCreateCellRow(sourceCellsByRow, gridRowIndex);
18
+ for (let gridColumnIndex = columnIndex; gridColumnIndex < columnEnd; gridColumnIndex++) cellsByColumn.set(gridColumnIndex, cell);
19
+ }
20
+ if (rowSpan > 1) for (let spannedRowIndex = rowIndex + 1; spannedRowIndex < rowIndex + rowSpan; spannedRowIndex++) {
21
+ const occupied = getOrCreateOccupiedRow(occupiedColumnsByRow, spannedRowIndex);
22
+ for (let columnOffset = 0; columnOffset < columnSpan; columnOffset++) occupied.add(columnIndex + columnOffset);
23
+ }
24
+ columnIndex = firstAvailableColumn(occupiedColumnsByRow.get(rowIndex), columnIndex + columnSpan);
25
+ }
26
+ }
27
+ return {
28
+ occupiedColumnsByRow,
29
+ sourceCellsByRow,
30
+ sourceColumnsByCell
31
+ };
32
+ };
33
+ const getFirstAvailableColumn = (grid, rowIndex, startingColumn) => firstAvailableColumn(grid.occupiedColumnsByRow.get(rowIndex), startingColumn);
34
+ const getSourceCellAt = (grid, rowIndex, columnIndex) => grid.sourceCellsByRow.get(rowIndex)?.get(columnIndex);
35
+ const getSourceCellColumn = (grid, cell) => grid.sourceColumnsByCell.get(cell);
36
+ const getTableCellVerticalBorderHeight = (grid, cell, rowIndex) => {
37
+ const sourceColumn = cell ? getSourceCellColumn(grid, cell) : void 0;
38
+ const aboveBottom = (sourceColumn === void 0 ? void 0 : getSourceCellAt(grid, rowIndex - 1, sourceColumn))?.borders?.bottom;
39
+ const aboveOwnsEdge = aboveBottom !== void 0 && aboveBottom.style !== "none" && aboveBottom.style !== "nil";
40
+ return (rowIndex === 0 || !aboveOwnsEdge ? cell?.borders?.top?.width ?? 0 : 0) + (cell?.borders?.bottom?.width ?? 0);
41
+ };
42
+ const firstAvailableColumn = (occupiedColumns, startingColumn) => {
43
+ let columnIndex = startingColumn;
44
+ while (occupiedColumns?.has(columnIndex)) columnIndex++;
45
+ return columnIndex;
46
+ };
47
+ const getOrCreateCellRow = (sourceCellsByRow, rowIndex) => {
48
+ const existing = sourceCellsByRow.get(rowIndex);
49
+ if (existing !== void 0) return existing;
50
+ const cellsByColumn = /* @__PURE__ */ new Map();
51
+ sourceCellsByRow.set(rowIndex, cellsByColumn);
52
+ return cellsByColumn;
53
+ };
54
+ const getOrCreateOccupiedRow = (occupiedColumnsByRow, rowIndex) => {
55
+ const existing = occupiedColumnsByRow.get(rowIndex);
56
+ if (existing !== void 0) return existing;
57
+ const occupiedColumns = /* @__PURE__ */ new Set();
58
+ occupiedColumnsByRow.set(rowIndex, occupiedColumns);
59
+ return occupiedColumns;
60
+ };
61
+ //#endregion
62
+ export { buildTableCellGrid, getFirstAvailableColumn, getSourceCellAt, getSourceCellColumn, getTableCellVerticalBorderHeight };
@@ -0,0 +1,18 @@
1
+ import { ParagraphBlock, ParagraphMeasure } from "../types.js";
2
+
3
+ //#region src/layout-engine/measure/textBoxParagraphLayout.d.ts
4
+ type TextBoxParagraphPlacement = {
5
+ leadingSpacing: number;
6
+ contentHeight: number;
7
+ };
8
+ type TextBoxParagraphLayout = {
9
+ placements: TextBoxParagraphPlacement[];
10
+ totalHeight: number;
11
+ };
12
+ /**
13
+ * Lay out a text box's paragraph story as one unpaginated flow. Adjacent
14
+ * before/after spacing collapses to the larger value, matching body flow.
15
+ */
16
+ declare function layoutTextBoxParagraphs(blocks: readonly ParagraphBlock[], measures: readonly ParagraphMeasure[]): TextBoxParagraphLayout;
17
+ //#endregion
18
+ export { TextBoxParagraphLayout, TextBoxParagraphPlacement, layoutTextBoxParagraphs };
@@ -0,0 +1,32 @@
1
+ import { measuredLineRangeHeight } from "../lineFlow.js";
2
+ import { getParagraphSpacingAfter, getParagraphSpacingBefore } from "../paragraphSpacing.js";
3
+ import { panic } from "better-result";
4
+ //#region src/layout-engine/measure/textBoxParagraphLayout.ts
5
+ /**
6
+ * Lay out a text box's paragraph story as one unpaginated flow. Adjacent
7
+ * before/after spacing collapses to the larger value, matching body flow.
8
+ */
9
+ function layoutTextBoxParagraphs(blocks, measures) {
10
+ if (blocks.length !== measures.length) panic("layoutTextBoxParagraphs: block and measure counts must match");
11
+ const placements = [];
12
+ let totalHeight = 0;
13
+ let trailingSpacing = 0;
14
+ for (let index = 0; index < blocks.length; index += 1) {
15
+ const block = blocks[index];
16
+ const measure = measures[index];
17
+ const leadingSpacing = Math.max(getParagraphSpacingBefore(block), trailingSpacing);
18
+ const contentHeight = measuredLineRangeHeight(measure.lines, 0, measure.lines.length);
19
+ placements.push({
20
+ leadingSpacing,
21
+ contentHeight
22
+ });
23
+ totalHeight += leadingSpacing + contentHeight;
24
+ trailingSpacing = getParagraphSpacingAfter(block);
25
+ }
26
+ return {
27
+ placements,
28
+ totalHeight: totalHeight + trailingSpacing
29
+ };
30
+ }
31
+ //#endregion
32
+ export { layoutTextBoxParagraphs };
@@ -0,0 +1,18 @@
1
+ import { RunFormatting } from "../types.js";
2
+ import { FontStyle } from "./measureTypes.js";
3
+
4
+ //#region src/layout-engine/measure/textMeasurementPolicy.d.ts
5
+ declare const FONT_KERNING_MODE: {
6
+ readonly enabled: "normal";
7
+ readonly disabled: "none";
8
+ };
9
+ type FontKerningMode = (typeof FONT_KERNING_MODE)[keyof typeof FONT_KERNING_MODE];
10
+ type RunKerningInput = Pick<RunFormatting, "fontSize" | "kerningMinPt">;
11
+ /** Resolve an authored kerning threshold against the effective run size. */
12
+ declare function getRunFontKerningMode(run: RunKerningInput, fallbackFontSize: number): FontKerningMode;
13
+ /** Convert normalized measurement style into the canvas/CSS kerning mode. */
14
+ declare function getFontKerningMode(style: Pick<FontStyle, "kerning">): FontKerningMode;
15
+ /** Count ordinary ASCII spaces that justification may compress. */
16
+ declare function countCompressibleSpaces(text: string): number;
17
+ //#endregion
18
+ export { FONT_KERNING_MODE, FontKerningMode, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode };
@@ -0,0 +1,24 @@
1
+ //#region src/layout-engine/measure/textMeasurementPolicy.ts
2
+ const FONT_KERNING_MODE = {
3
+ enabled: "normal",
4
+ disabled: "none"
5
+ };
6
+ /** Resolve an authored kerning threshold against the effective run size. */
7
+ function getRunFontKerningMode(run, fallbackFontSize) {
8
+ const threshold = run.kerningMinPt;
9
+ const fontSize = run.fontSize ?? fallbackFontSize;
10
+ if (threshold === void 0 || fontSize < threshold) return FONT_KERNING_MODE.disabled;
11
+ return FONT_KERNING_MODE.enabled;
12
+ }
13
+ /** Convert normalized measurement style into the canvas/CSS kerning mode. */
14
+ function getFontKerningMode(style) {
15
+ return style.kerning ? FONT_KERNING_MODE.enabled : FONT_KERNING_MODE.disabled;
16
+ }
17
+ /** Count ordinary ASCII spaces that justification may compress. */
18
+ function countCompressibleSpaces(text) {
19
+ let count = 0;
20
+ for (const char of text) if (char === " ") count += 1;
21
+ return count;
22
+ }
23
+ //#endregion
24
+ export { FONT_KERNING_MODE, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode };
@@ -35,7 +35,8 @@ type PaginatorOptions = {
35
35
  * its title page (typically extended top margin to clear an overflowing
36
36
  * first-page header) vs. its body pages. Pages 2+ use `margins`.
37
37
  */
38
- firstPageMargins?: PageMargins; /** Column configuration (optional). */
38
+ firstPageMargins?: PageMargins; /** Per-section body margins used on even section pages. */
39
+ sectionEvenPageMargins?: (PageMargins | undefined)[]; /** Column configuration (optional). */
39
40
  columns?: ColumnLayout; /** Per-page footnote reserved heights (pageNumber → height in pixels). */
40
41
  footnoteReservedHeights?: Map<number, number>; /** Header/footer refs by section index. */
41
42
  sectionHeaderFooterRefs?: PageHeaderFooterRefs[]; /** Callback when a new page is created. */
@@ -47,7 +47,7 @@ function createPaginator(options) {
47
47
  }
48
48
  function currentPageUsesActiveLayout(state) {
49
49
  if (pendingPageSize || pendingMargins) return false;
50
- return arePageSizesEqual(state.page.size, pageSize) && areMarginsEqual(state.page.margins, getPageMargins(state.page.number));
50
+ return arePageSizesEqual(state.page.size, pageSize) && areMarginsEqual(state.page.margins, getPageMargins(state.page.number, state.page.sectionPageNumber ?? currentSectionPageNumber));
51
51
  }
52
52
  if (getContentHeight() <= 0) panic("Paginator: page size and margins yield no content area");
53
53
  let columnWidths = calculateColumnWidths(pageSize.w, margins.left, margins.right, columns);
@@ -62,8 +62,9 @@ function createPaginator(options) {
62
62
  if (getContentHeight() <= 0) panic("Paginator: section page size and margins yield no content area");
63
63
  recalculateColumnWidths();
64
64
  }
65
- function getPageMargins(pageNumber) {
66
- const pageMargins = pageNumber === 1 && options.firstPageMargins ? { ...options.firstPageMargins } : { ...margins };
65
+ function getPageMargins(pageNumber, sectionPageNumber) {
66
+ const evenMargins = sectionPageNumber % 2 === 0 ? options.sectionEvenPageMargins?.[currentSectionIndex] : void 0;
67
+ const pageMargins = pageNumber === 1 && options.firstPageMargins ? { ...options.firstPageMargins } : { ...evenMargins ?? margins };
67
68
  if (options.mirrorMargins === true && pageNumber % 2 === 0) return {
68
69
  ...pageMargins,
69
70
  left: pageMargins.right,
@@ -77,7 +78,7 @@ function createPaginator(options) {
77
78
  * Get X position for a given column index.
78
79
  */
79
80
  function getColumnX(columnIndex) {
80
- let x = states.at(-1)?.page.margins.left ?? getPageMargins(1).left;
81
+ let x = states.at(-1)?.page.margins.left ?? getPageMargins(1, 1).left;
81
82
  for (let index = 0; index < columnIndex; index++) x += (columnWidths[index] ?? columnWidths[0] ?? 0) + gapAfterColumn(columns, index);
82
83
  return x;
83
84
  }
@@ -88,7 +89,7 @@ function createPaginator(options) {
88
89
  if (pendingPageSize || pendingMargins) applyPendingLayout();
89
90
  const pageNumber = pages.length + 1;
90
91
  currentSectionPageNumber += 1;
91
- const pageMargins = getPageMargins(pageNumber);
92
+ const pageMargins = getPageMargins(pageNumber, currentSectionPageNumber);
92
93
  const topMargin = pageMargins.top;
93
94
  const contentBottom = pageSize.h - pageMargins.bottom;
94
95
  const footnoteHeight = options.footnoteReservedHeights?.get(pageNumber) ?? 0;
@@ -244,7 +245,7 @@ function createPaginator(options) {
244
245
  const current = states.at(-1);
245
246
  if (!current || current.page.fragments.length > 0 || current.cursorY !== current.topMargin) return false;
246
247
  if (pendingPageSize || pendingMargins) applyPendingLayout();
247
- const pageMargins = getPageMargins(current.page.number);
248
+ const pageMargins = getPageMargins(current.page.number, 1);
248
249
  const topMargin = pageMargins.top;
249
250
  const rawContentBottom = pageSize.h - pageMargins.bottom;
250
251
  const footnoteHeight = options.footnoteReservedHeights?.get(current.page.number) ?? 0;
@@ -0,0 +1,22 @@
1
+ import { ParagraphBlock, TextBoxBlock } from "./types.js";
2
+
3
+ //#region src/layout-engine/paragraphFrame.d.ts
4
+ type ParagraphFrame = {
5
+ width?: number;
6
+ height?: number;
7
+ hSpace?: number;
8
+ vSpace?: number;
9
+ hAnchor?: "text" | "margin" | "page";
10
+ vAnchor?: "text" | "margin" | "page";
11
+ x?: number;
12
+ y?: number;
13
+ xAlign?: "left" | "center" | "right" | "inside" | "outside";
14
+ yAlign?: "top" | "center" | "bottom" | "inside" | "outside" | "inline";
15
+ wrap?: "around" | "auto" | "none" | "notBeside" | "through" | "tight";
16
+ };
17
+ declare function setParagraphFrame(block: ParagraphBlock, frame: ParagraphFrame): void;
18
+ declare function getParagraphFrame(block: ParagraphBlock): ParagraphFrame | undefined;
19
+ declare function markParagraphFrameTextBox(block: TextBoxBlock): void;
20
+ declare function isParagraphFrameTextBox(block: TextBoxBlock): boolean;
21
+ //#endregion
22
+ export { ParagraphFrame, getParagraphFrame, isParagraphFrameTextBox, markParagraphFrameTextBox, setParagraphFrame };
@@ -0,0 +1,17 @@
1
+ //#region src/layout-engine/paragraphFrame.ts
2
+ const paragraphFrames = /* @__PURE__ */ new WeakMap();
3
+ const paragraphFrameTextBoxes = /* @__PURE__ */ new WeakSet();
4
+ function setParagraphFrame(block, frame) {
5
+ paragraphFrames.set(block, frame);
6
+ }
7
+ function getParagraphFrame(block) {
8
+ return paragraphFrames.get(block);
9
+ }
10
+ function markParagraphFrameTextBox(block) {
11
+ paragraphFrameTextBoxes.add(block);
12
+ }
13
+ function isParagraphFrameTextBox(block) {
14
+ return paragraphFrameTextBoxes.has(block);
15
+ }
16
+ //#endregion
17
+ export { getParagraphFrame, isParagraphFrameTextBox, markParagraphFrameTextBox, setParagraphFrame };
@@ -0,0 +1,7 @@
1
+ import { FlowBlock } from "./types.js";
2
+
3
+ //#region src/layout-engine/paragraphSequence.d.ts
4
+ declare const continuesNumberedSequence: (previous: FlowBlock | undefined, current: FlowBlock) => boolean;
5
+ declare const continuesTabbedParagraphSequence: (previous: FlowBlock | undefined, current: FlowBlock) => boolean;
6
+ //#endregion
7
+ export { continuesNumberedSequence, continuesTabbedParagraphSequence };
@@ -0,0 +1,25 @@
1
+ //#region src/layout-engine/paragraphSequence.ts
2
+ const continuesNumberedSequence = (previous, current) => {
3
+ if (previous?.kind !== "paragraph" || current.kind !== "paragraph") return false;
4
+ const previousNumPr = previous.attrs?.numPr;
5
+ const currentNumPr = current.attrs?.numPr;
6
+ if (previousNumPr?.numId === void 0 || currentNumPr?.numId === void 0) return false;
7
+ return previousNumPr.numId === currentNumPr.numId && previousNumPr.ilvl === currentNumPr.ilvl;
8
+ };
9
+ const continuesTabbedParagraphSequence = (previous, current) => {
10
+ if (previous?.kind !== "paragraph" || current.kind !== "paragraph") return false;
11
+ if (!previous.attrs?.styleId || previous.attrs.styleId !== current.attrs?.styleId) return false;
12
+ if (!previous.runs.some((run) => run.kind === "tab") || !current.runs.some((run) => run.kind === "tab")) return false;
13
+ const previousIndent = previous.attrs.indent;
14
+ const currentIndent = current.attrs.indent;
15
+ if (previousIndent?.left !== currentIndent?.left || previousIndent?.right !== currentIndent?.right || previousIndent?.firstLine !== currentIndent?.firstLine || previousIndent?.hanging !== currentIndent?.hanging) return false;
16
+ const previousTabs = previous.attrs.tabs ?? [];
17
+ const currentTabs = current.attrs.tabs ?? [];
18
+ return previousTabs.length === currentTabs.length && previousTabs.every((tab, index) => {
19
+ const currentTab = currentTabs[index];
20
+ if (!currentTab) return false;
21
+ return tab.val === currentTab.val && tab.pos === currentTab.pos && tab.leader === currentTab.leader;
22
+ });
23
+ };
24
+ //#endregion
25
+ export { continuesNumberedSequence, continuesTabbedParagraphSequence };