@stll/folio-core 0.37.5 → 0.39.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 (144) hide show
  1. package/dist/ai-edits/apply.js +275 -88
  2. package/dist/ai-edits/clean-text.js +12 -1
  3. package/dist/ai-edits/headless.d.ts +110 -0
  4. package/dist/ai-edits/headless.js +569 -40
  5. package/dist/ai-edits/snapshot.d.ts +24 -1
  6. package/dist/ai-edits/snapshot.js +206 -9
  7. package/dist/ai-edits/table-geometry.d.ts +7 -7
  8. package/dist/ai-edits/table-geometry.js +43 -27
  9. package/dist/ai-edits/table-template.d.ts +2 -1
  10. package/dist/ai-edits/table-template.js +9 -3
  11. package/dist/ai-edits/types.d.ts +37 -15
  12. package/dist/compare/__fixtures__/body-sequence.d.ts +4 -0
  13. package/dist/compare/__fixtures__/body-sequence.js +5 -3
  14. package/dist/compare/compare.d.ts +26 -2
  15. package/dist/compare/compare.js +444 -16
  16. package/dist/compare/content-alignment.js +3 -2
  17. package/dist/compare/content-types.d.ts +34 -2
  18. package/dist/compare/content.d.ts +13 -10
  19. package/dist/compare/content.js +69 -23
  20. package/dist/compare/formatting.d.ts +4 -22
  21. package/dist/compare/formatting.js +8 -112
  22. package/dist/compare/inline-atom-resources.d.ts +6 -0
  23. package/dist/compare/inline-atom-resources.js +26 -0
  24. package/dist/compare/inline-atoms.d.ts +35 -0
  25. package/dist/compare/inline-atoms.js +383 -0
  26. package/dist/compare/inline-provenance.d.ts +36 -0
  27. package/dist/compare/inline-provenance.js +334 -0
  28. package/dist/compare/plan.d.ts +4 -1
  29. package/dist/compare/plan.js +32 -4
  30. package/dist/compare/scenario.d.ts +4 -6
  31. package/dist/compare/section-boundary-properties.d.ts +63 -0
  32. package/dist/compare/section-boundary-properties.js +235 -0
  33. package/dist/compare/style-resources.d.ts +30 -0
  34. package/dist/compare/style-resources.js +404 -0
  35. package/dist/compare/types.d.ts +50 -11
  36. package/dist/compare/types.js +3 -1
  37. package/dist/compare/verification.d.ts +3 -5
  38. package/dist/compare/verification.js +8 -39
  39. package/dist/compat/eigenpal.d.ts +4 -4
  40. package/dist/compat/eigenpal.js +3 -3
  41. package/dist/controller/layoutPipeline.js +4 -0
  42. package/dist/display-list/build/buildContext.d.ts +3 -1
  43. package/dist/display-list/build/buildDisplayList.d.ts +4 -1
  44. package/dist/display-list/build/buildDisplayList.js +38 -4
  45. package/dist/display-list/build/commentAnnotations.d.ts +10 -0
  46. package/dist/display-list/build/commentAnnotations.js +17 -0
  47. package/dist/display-list/build/paragraphPrimitives.js +39 -1
  48. package/dist/display-list/primitives.d.ts +1 -1
  49. package/dist/display-list/types.d.ts +16 -1
  50. package/dist/document-operations.d.ts +5 -3
  51. package/dist/document-operations.js +112 -5
  52. package/dist/docx/blockContentParser.js +14 -2
  53. package/dist/docx/diagramPreview.d.ts +7 -0
  54. package/dist/docx/diagramPreview.js +191 -0
  55. package/dist/docx/drawingIdNormalization.js +43 -2
  56. package/dist/docx/drawingRelationships.d.ts +10 -0
  57. package/dist/docx/drawingRelationships.js +28 -0
  58. package/dist/docx/fieldParser.js +1 -1
  59. package/dist/docx/hyperlinkParser.js +3 -5
  60. package/dist/docx/imageRawXml.d.ts +8 -0
  61. package/dist/docx/imageRawXml.js +13 -0
  62. package/dist/docx/newImage.d.ts +2 -2
  63. package/dist/docx/newImage.js +3 -3
  64. package/dist/docx/numberingParser.js +30 -27
  65. package/dist/docx/paragraphParser.js +11 -4
  66. package/dist/docx/paragraphTextBoxEnrichment.js +2 -1
  67. package/dist/docx/parser.js +13 -0
  68. package/dist/docx/removeHeaderFooterParts.d.ts +14 -0
  69. package/dist/docx/removeHeaderFooterParts.js +87 -0
  70. package/dist/docx/rezip.d.ts +3 -1
  71. package/dist/docx/rezip.js +143 -41
  72. package/dist/docx/runParser.js +24 -5
  73. package/dist/docx/sectionParser.js +3 -0
  74. package/dist/docx/sectionReferenceHistory.d.ts +17 -0
  75. package/dist/docx/sectionReferenceHistory.js +59 -0
  76. package/dist/docx/selectiveSave.js +11 -31
  77. package/dist/docx/selectiveXmlPatch.d.ts +8 -1
  78. package/dist/docx/selectiveXmlPatch.js +62 -2
  79. package/dist/docx/serializer/paragraphSerializer.js +2 -0
  80. package/dist/docx/serializer/partNamespaces.d.ts +1 -1
  81. package/dist/docx/serializer/partNamespaces.js +4 -0
  82. package/dist/docx/serializer/runSerializer.js +5 -2
  83. package/dist/docx/serializer/sectionPropertiesSerializer.js +5 -2
  84. package/dist/docx/serializer/textFormattingSerializer.d.ts +1 -1
  85. package/dist/docx/serializer/textFormattingSerializer.js +3 -1
  86. package/dist/docx/tableParser.js +21 -11
  87. package/dist/docx/textBoxParser.js +25 -5
  88. package/dist/docx/xmlParser.d.ts +10 -1
  89. package/dist/docx/xmlParser.js +23 -1
  90. package/dist/export-pdf.js +2 -0
  91. package/dist/headless-layout.js +1 -0
  92. package/dist/index.d.ts +4 -4
  93. package/dist/index.js +3 -3
  94. package/dist/internal/compare/inline-presentation.d.ts +20 -0
  95. package/dist/internal/compare/inline-presentation.js +222 -0
  96. package/dist/internal/paragraphFormattingSerialization.d.ts +3 -1
  97. package/dist/internal/paragraphFormattingSerialization.js +8 -1
  98. package/dist/internal/sectionReferenceResolution.d.ts +21 -0
  99. package/dist/internal/sectionReferenceResolution.js +72 -0
  100. package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
  101. package/dist/layout-engine/index.js +5 -5
  102. package/dist/layout-engine/sectionVerticalAlignment.d.ts +7 -0
  103. package/dist/layout-engine/sectionVerticalAlignment.js +52 -0
  104. package/dist/layout-engine/types.d.ts +3 -1
  105. package/dist/markdown/renderRuns.js +1 -0
  106. package/dist/markdown/renderTable.js +1 -0
  107. package/dist/pdf/writePdf.js +37 -2
  108. package/dist/prosemirror/attrs/index.js +55 -1
  109. package/dist/prosemirror/commands/comments.d.ts +11 -1
  110. package/dist/prosemirror/commands/comments.js +68 -6
  111. package/dist/prosemirror/commands/propertyChangeScope.d.ts +9 -3
  112. package/dist/prosemirror/commands/propertyChangeScope.js +4 -10
  113. package/dist/prosemirror/conversion/fromProseDoc.js +17 -8
  114. package/dist/prosemirror/conversion/toProseDoc.js +18 -57
  115. package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -0
  116. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +3 -0
  117. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
  118. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +39 -1
  119. package/dist/prosemirror/imageCommit.js +7 -9
  120. package/dist/prosemirror/paragraphIndentation.d.ts +21 -0
  121. package/dist/prosemirror/paragraphIndentation.js +72 -0
  122. package/dist/prosemirror/plugins/documentNumbering.d.ts +3 -1
  123. package/dist/prosemirror/plugins/documentNumbering.js +7 -1
  124. package/dist/prosemirror/plugins/documentStyles.d.ts +3 -1
  125. package/dist/prosemirror/plugins/documentStyles.js +7 -1
  126. package/dist/prosemirror/rebaseParagraphRunFormatting.d.ts +3 -1
  127. package/dist/prosemirror/rebaseParagraphRunFormatting.js +2 -1
  128. package/dist/prosemirror/runFormattingInlineCarriers.d.ts +15 -2
  129. package/dist/prosemirror/runFormattingInlineCarriers.js +33 -3
  130. package/dist/prosemirror/runStyleFormatting.d.ts +15 -2
  131. package/dist/prosemirror/runStyleFormatting.js +76 -2
  132. package/dist/prosemirror/schema/marks.d.ts +3 -1
  133. package/dist/prosemirror/schema/marks.js +2 -0
  134. package/dist/prosemirror/schema/nodes.d.ts +5 -0
  135. package/dist/prosemirror/tableCellRevisionVisibility.d.ts +6 -0
  136. package/dist/prosemirror/tableCellRevisionVisibility.js +14 -0
  137. package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
  138. package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
  139. package/dist/redline.js +15 -2
  140. package/dist/server.d.ts +2 -2
  141. package/dist/server.js +2 -2
  142. package/dist/version-comparison.d.ts +2 -0
  143. package/dist/version-comparison.js +7 -2
  144. package/package.json +2 -2
@@ -1,8 +1,12 @@
1
- import { FolioAIBlockParagraphProperties, FolioAIBlockTableLocation, FolioAIEditSkippedOperation, FolioAIInlineFormattingPatch } from "../ai-edits/types.js";
1
+ import { FolioContentFormatRange } from "./content-types.js";
2
+ import { FolioAIBlockParagraphProperties, FolioAIBlockTableLocation, FolioAIEditSkippedOperation } from "../ai-edits/types.js";
2
3
  import { WordDiffGranularity } from "../ai-edits/word-diff.js";
3
4
  import { FolioDocumentStoryHandle, FolioNumberingLevel } from "../ai-edits/headless.js";
4
5
  import { CompareVerification, CompareVerificationCause, CompareVerificationFailure, CompareVerificationInvariant, FinalParagraphMarkRevision } from "./verification.js";
5
6
  //#region src/compare/types.d.ts
7
+ /** Revision encoding requested for the generated package. */
8
+ declare const COMPARE_REVISION_FORMATS: readonly ["word", "folio-exact"];
9
+ type CompareRevisionFormat = (typeof COMPARE_REVISION_FORMATS)[number];
6
10
  /** Everything {@link compareDocx} needs; nothing it reads from the ambient clock. */
7
11
  type CompareDocxOptions = {
8
12
  /** Author recorded on every generated tracked change. */
@@ -34,6 +38,12 @@ type CompareDocxOptions = {
34
38
  * one thing this call promises.
35
39
  */
36
40
  granularity?: WordDiffGranularity;
41
+ /**
42
+ * Revision encoding: `"word"` (default) uses only standard OOXML revision
43
+ * markup. `"folio-exact"` preserves Folio-only review history that must be
44
+ * resolved before the package is handed to Word.
45
+ */
46
+ revisionFormat?: CompareRevisionFormat;
37
47
  };
38
48
  /** Where one change sits in the base or target document. */
39
49
  type CompareChangeLocation = {
@@ -41,13 +51,6 @@ type CompareChangeLocation = {
41
51
  /** Innermost table cell, when the change is inside a table. */
42
52
  cell?: FolioAIBlockTableLocation;
43
53
  };
44
- /** One run of characters whose inline formatting differs, in base-block offsets. */
45
- type CompareFormatRange = {
46
- startOffset: number;
47
- endOffset: number;
48
- /** Differing properties set to the target value; null removes a direct property. */
49
- formatting: FolioAIInlineFormattingPatch;
50
- };
51
54
  /**
52
55
  * One difference between the two documents, in target-document order with
53
56
  * base-only entries slotted where they sat. Every variant is plain JSON, so a
@@ -119,13 +122,32 @@ type CompareChange = {
119
122
  targetBlockId: string;
120
123
  /** Only the properties that differ, set to the target document's value. */
121
124
  properties: FolioAIBlockParagraphProperties;
125
+ } |
126
+ /** The body-level section properties changed. */
127
+ {
128
+ kind: "section-properties";
129
+ location: CompareChangeLocation;
130
+ } |
131
+ /** Full authored run properties changed beyond the neutral inline patch. */
132
+ {
133
+ kind: "run-format";
134
+ location: CompareChangeLocation;
135
+ targetBlockId: string;
136
+ text: string;
137
+ } |
138
+ /** A field, image, or page break changed while the surrounding text stayed the same. */
139
+ {
140
+ kind: "inline-atom";
141
+ location: CompareChangeLocation;
142
+ targetBlockId: string;
143
+ text: string;
122
144
  } | {
123
145
  kind: "format";
124
146
  location: CompareChangeLocation;
125
147
  baseBlockId: string;
126
148
  targetBlockId: string;
127
149
  text: string;
128
- ranges: readonly CompareFormatRange[];
150
+ ranges: readonly FolioContentFormatRange[];
129
151
  } | {
130
152
  kind: "table-row-insert";
131
153
  location: CompareChangeLocation;
@@ -211,6 +233,21 @@ type CompareUnsupportedPart = {
211
233
  baseStory: FolioDocumentStoryHandle | null;
212
234
  targetStory: FolioDocumentStoryHandle | null;
213
235
  };
236
+ /**
237
+ * What a consumer can do with the returned revision package.
238
+ *
239
+ * Word can save standard OOXML revisions. It strips Folio's final-section
240
+ * reference history on an ordinary save, so a caller must resolve that history
241
+ * in Folio before handing a `requires-folio` package to Word when exactness
242
+ * matters.
243
+ */
244
+ type CompareCompatibility = {
245
+ status: "standard-ooxml";
246
+ } | {
247
+ status: "requires-folio";
248
+ reasons: readonly [CompareFolioRequirement, ...CompareFolioRequirement[]];
249
+ };
250
+ type CompareFolioRequirement = "section-reference-history" | "terminal-table-carrier";
214
251
  type CompareResult = {
215
252
  /** The base package carrying the generated tracked changes. */
216
253
  buffer: ArrayBuffer;
@@ -221,12 +258,14 @@ type CompareResult = {
221
258
  * returned at all.
222
259
  */
223
260
  verification: CompareVerification;
261
+ /** The consumer compatibility of the generated revision encoding. */
262
+ compatibility: CompareCompatibility;
224
263
  unsupported: readonly CompareUnsupportedPart[];
225
264
  };
226
265
  declare const InvalidCompareDocxOptionsError_base: import("better-result").TaggedErrorClass<"InvalidCompareDocxOptionsError">;
227
266
  declare class InvalidCompareDocxOptionsError extends InvalidCompareDocxOptionsError_base<{
228
267
  message: string;
229
- option: "timestamp";
268
+ option: "timestamp" | "revisionFormat";
230
269
  receivedValue: unknown;
231
270
  }> {}
232
271
  declare const CompareDocxParseError_base: import("better-result").TaggedErrorClass<"CompareDocxParseError">;
@@ -296,4 +335,4 @@ declare class CompareDocxFinalParagraphMarkError extends CompareDocxFinalParagra
296
335
  }> {}
297
336
  type CompareDocxError = CompareDocxApplyError | CompareDocxFinalParagraphMarkError | CompareDocxOperationLimitError | CompareDocxParseError | CompareDocxRoundTripError | CompareDocxSerializeError | InvalidCompareDocxOptionsError;
298
337
  //#endregion
299
- export { COMPARE_UNSUPPORTED_REASONS, CompareChange, CompareChangeLocation, CompareDocxApplyError, CompareDocxError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxOptions, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, CompareFormatRange, CompareResult, CompareUnsupportedPart, CompareUnsupportedReason, InvalidCompareDocxOptionsError };
338
+ export { COMPARE_REVISION_FORMATS, COMPARE_UNSUPPORTED_REASONS, CompareChange, CompareChangeLocation, CompareCompatibility, CompareDocxApplyError, CompareDocxError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxOptions, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, CompareFolioRequirement, CompareResult, CompareRevisionFormat, CompareUnsupportedPart, CompareUnsupportedReason, InvalidCompareDocxOptionsError };
@@ -4,6 +4,8 @@ import { TaggedError } from "better-result";
4
4
  * Public vocabulary of {@link ./compare.compareDocx}: the change list, the
5
5
  * options that pin its output, and the failures it reports.
6
6
  */
7
+ /** Revision encoding requested for the generated package. */
8
+ const COMPARE_REVISION_FORMATS = ["word", "folio-exact"];
7
9
  /** Why a part of the package is absent from `changes`. */
8
10
  const COMPARE_UNSUPPORTED_REASONS = Object.freeze([
9
11
  "story-missing-in-base",
@@ -41,4 +43,4 @@ var CompareDocxSerializeError = class extends TaggedError("CompareDocxSerializeE
41
43
  */
42
44
  var CompareDocxFinalParagraphMarkError = class extends TaggedError("CompareDocxFinalParagraphMarkError") {};
43
45
  //#endregion
44
- export { COMPARE_UNSUPPORTED_REASONS, CompareDocxApplyError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, InvalidCompareDocxOptionsError };
46
+ export { COMPARE_REVISION_FORMATS, COMPARE_UNSUPPORTED_REASONS, CompareDocxApplyError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, InvalidCompareDocxOptionsError };
@@ -10,7 +10,7 @@ type CompareVerificationInvariant = (typeof COMPARE_VERIFICATION_INVARIANTS)[num
10
10
  * that fits is the one reported, because a block in the wrong container is a
11
11
  * different finding from a block whose words are wrong even when both are true.
12
12
  */
13
- declare const COMPARE_VERIFICATION_CAUSES: readonly ["invisible-structure", "block-count", "container", "inline-structure", "table-geometry", "style", "list-level", "alignment", "spacing", "inline-formatting", "whitespace", "text"];
13
+ declare const COMPARE_VERIFICATION_CAUSES: readonly ["invisible-structure", "block-count", "container", "inline-structure", "table-geometry", "section-properties", "style", "list-level", "alignment", "spacing", "indentation", "inline-formatting", "whitespace", "text"];
14
14
  type CompareVerificationCause = (typeof COMPARE_VERIFICATION_CAUSES)[number];
15
15
  /** One invariant that did not hold, in one story. */
16
16
  type CompareVerificationFailure = {
@@ -33,9 +33,7 @@ type CompareVerification = {
33
33
  status: "unverified";
34
34
  failures: readonly CompareVerificationFailure[];
35
35
  };
36
- /** Effective supported formatting with equivalent adjacent runs normalized. */
37
- declare const projectSupportedInlineFormatting: ({ text, previewRuns }: FolioAIBlock) => string;
38
- type ProjectedBlock = Pick<FolioAIBlock, "text" | "table" | "styleId" | "listLevel" | "directAlignment" | "directSpacing" | "structuralBoundaries">;
36
+ type ProjectedBlock = Pick<FolioAIBlock, "text" | "table" | "styleId" | "listLevel" | "listReference" | "directAlignment" | "directSpacing" | "directIndentation" | "structuralBoundaries">;
39
37
  type ClassifyOptions<T> = {
40
38
  invariant: CompareVerificationInvariant;
41
39
  story: FolioDocumentStoryHandle;
@@ -98,4 +96,4 @@ declare const revisedFinalParagraphMarks: (packageModel: unknown, { since }?: {
98
96
  since?: number;
99
97
  }) => FinalParagraphMarkRevision[];
100
98
  //#endregion
101
- export { COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, CompareVerification, CompareVerificationCause, CompareVerificationFailure, CompareVerificationInvariant, FinalParagraphMarkRevision, classifyGeometryMismatch, classifyProjectionMismatch, projectSupportedInlineFormatting, revisedFinalParagraphMarks };
99
+ export { COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, CompareVerification, CompareVerificationCause, CompareVerificationFailure, CompareVerificationInvariant, FinalParagraphMarkRevision, classifyGeometryMismatch, classifyProjectionMismatch, revisedFinalParagraphMarks };
@@ -1,5 +1,5 @@
1
+ import { paragraphIndentationEqual } from "../prosemirror/paragraphIndentation.js";
1
2
  import { paragraphSpacingEqual } from "../prosemirror/paragraphSpacing.js";
2
- import { resolveColorToHex } from "../utils/colorResolver.js";
3
3
  import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
4
4
  //#region src/compare/verification.ts
5
5
  /**
@@ -8,7 +8,7 @@ import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
8
8
  * The check compares two block projections: what accepting the generated
9
9
  * revisions leaves against the target, and what rejecting them leaves against
10
10
  * the base. A projection carries each block's container, style, list level,
11
- * direct alignment, direct spacing and text, so WHICH field diverged names
11
+ * direct alignment, spacing, indentation and text, so WHICH field diverged names
12
12
  * which part of the pipeline lost the difference — and that is worth reporting
13
13
  * as a typed cause rather than as one opaque "did not reproduce".
14
14
  *
@@ -16,7 +16,6 @@ import { PARAGRAPH_MARK_CHANGE_KINDS } from "@stll/docx-core/model";
16
16
  * Never a phrase of either document, because a caller may log it, put it in a
17
17
  * report, or quote it in a review.
18
18
  */
19
- const normalizeInlineFormattingColor = (color) => resolveColorToHex(color === void 0 ? void 0 : { rgb: color }, null);
20
19
  /** The two directions of the round trip, each an invariant of its own. */
21
20
  const COMPARE_VERIFICATION_INVARIANTS = Object.freeze(["accept-reproduces-target", "reject-reproduces-base"]);
22
21
  /**
@@ -30,48 +29,16 @@ const COMPARE_VERIFICATION_CAUSES = Object.freeze([
30
29
  "container",
31
30
  "inline-structure",
32
31
  "table-geometry",
32
+ "section-properties",
33
33
  "style",
34
34
  "list-level",
35
35
  "alignment",
36
36
  "spacing",
37
+ "indentation",
37
38
  "inline-formatting",
38
39
  "whitespace",
39
40
  "text"
40
41
  ]);
41
- const supportedInlineStyle = ({ bold, italic, underline, strike, fontFamily, fontSizePt, color, directFormatting }) => JSON.stringify([
42
- bold === true,
43
- italic === true,
44
- underline === true,
45
- strike === true,
46
- fontFamily ?? null,
47
- fontSizePt ?? null,
48
- normalizeInlineFormattingColor(color) ?? null,
49
- directFormatting?.bold ?? null,
50
- directFormatting?.italic ?? null,
51
- directFormatting?.underline ?? null,
52
- directFormatting?.strike ?? null,
53
- directFormatting?.fontFamily ?? null,
54
- directFormatting?.fontSizePt ?? null,
55
- normalizeInlineFormattingColor(directFormatting?.color ?? void 0) ?? null
56
- ]);
57
- /** Effective supported formatting with equivalent adjacent runs normalized. */
58
- const projectSupportedInlineFormatting = ({ text, previewRuns }) => {
59
- const projected = [];
60
- for (const run of previewRuns ?? [{ text }]) {
61
- if (run.text.length === 0) continue;
62
- const style = supportedInlineStyle(run);
63
- const previous = projected.at(-1);
64
- if (previous?.style === style) {
65
- previous.length += run.text.length;
66
- continue;
67
- }
68
- projected.push({
69
- length: run.text.length,
70
- style
71
- });
72
- }
73
- return projected.map(({ length, style }) => `${String(length)}:${style}`).join(",");
74
- };
75
42
  const sameContainer = (left, right) => {
76
43
  if (!left || !right) return left === right;
77
44
  return left.outerTableIndex === right.outerTableIndex && left.tableIndex === right.tableIndex && left.rowIndex === right.rowIndex && left.cellIndex === right.cellIndex && left.gridColumnIndex === right.gridColumnIndex && left.columnSpan === right.columnSpan && left.rowSpan === right.rowSpan && left.paragraphIndex === right.paragraphIndex;
@@ -88,7 +55,7 @@ const sameStructuralBoundaries = (left, right) => {
88
55
  }
89
56
  return true;
90
57
  };
91
- const sameProjectedBlock = (left, right) => sameContainer(left.table, right.table) && sameStructuralBoundaries(left.structuralBoundaries, right.structuralBoundaries) && left.styleId === right.styleId && left.listLevel === right.listLevel && left.directAlignment === right.directAlignment && paragraphSpacingEqual(left.directSpacing, right.directSpacing) && left.text === right.text;
58
+ const sameProjectedBlock = (left, right) => sameContainer(left.table, right.table) && sameStructuralBoundaries(left.structuralBoundaries, right.structuralBoundaries) && left.styleId === right.styleId && left.listLevel === right.listLevel && left.listReference?.numId === right.listReference?.numId && left.listReference?.level === right.listReference?.level && left.directAlignment === right.directAlignment && paragraphSpacingEqual(left.directSpacing, right.directSpacing) && paragraphIndentationEqual(left.directIndentation, right.directIndentation) && left.text === right.text;
92
59
  const containerKind = (container) => container ? "cell" : "body";
93
60
  const collapseWhitespace = (text) => text.replace(/\s+/gu, " ").trim();
94
61
  const sameBlockProjection = (left, right) => left.length === right.length && left.every((entry, index) => {
@@ -206,8 +173,10 @@ const classifyProjectionMismatch = ({ invariant, story, actual, expected }) => {
206
173
  if (!sameStructuralBoundaries(left.structuralBoundaries, right.structuralBoundaries)) return failure("inline-structure", `a block's zero-width inline structure does not match ${at} (${counts})`);
207
174
  if (left.text === right.text && left.styleId !== right.styleId) return failure("style", `the paragraph style did not move ${at} (${counts})`);
208
175
  if (left.text === right.text && left.listLevel !== right.listLevel) return failure("list-level", `the list level did not move ${at} (${counts})`);
176
+ if (left.text === right.text && (left.listReference?.numId !== right.listReference?.numId || left.listReference?.level !== right.listReference?.level)) return failure("list-level", `the numbering reference did not move ${at} (${counts})`);
209
177
  if (left.text === right.text && left.directAlignment !== right.directAlignment) return failure("alignment", `the direct paragraph alignment did not move ${at} (${counts})`);
210
178
  if (left.text === right.text && !paragraphSpacingEqual(left.directSpacing, right.directSpacing)) return failure("spacing", `the direct paragraph spacing did not move ${at} (${counts})`);
179
+ if (left.text === right.text && !paragraphIndentationEqual(left.directIndentation, right.directIndentation)) return failure("indentation", `the direct paragraph indentation did not move ${at} (${counts})`);
211
180
  if (collapseWhitespace(left.text) === collapseWhitespace(right.text)) return failure("whitespace", `a block's text differs only in whitespace ${at} (${counts})`);
212
181
  if (actual.length !== expected.length) return failure("block-count", `${counts}, first differing ${at}`);
213
182
  return failure("text", `a ${containerKind(left.table)} block's text does not match ${at}, length ${String(left.text.length)} against ${String(right.text.length)} (${counts})`);
@@ -296,4 +265,4 @@ const revisedFinalParagraphMarks = (packageModel, { since = 0 } = {}) => {
296
265
  return found;
297
266
  };
298
267
  //#endregion
299
- export { COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, classifyGeometryMismatch, classifyProjectionMismatch, projectSupportedInlineFormatting, revisedFinalParagraphMarks };
268
+ export { COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, classifyGeometryMismatch, classifyProjectionMismatch, revisedFinalParagraphMarks };
@@ -1,5 +1,5 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
- import { FolioContentBlock, FolioContentContainerPathEntry, FolioContentIdStability, FolioContentInlineBooleanProperty, FolioContentInlineFormatting, FolioContentInlineFormattingPatch, FolioContentLineSpacingRule, FolioContentParagraphAlignment, FolioContentParagraphKind, FolioContentParagraphSpacing, FolioContentRun, FolioContentSnapshot, FolioContentTableLocation } from "../compare/content-types.js";
2
+ import { FolioContentBlock, FolioContentContainerPathEntry, FolioContentFormatRange, FolioContentIdStability, FolioContentInlineBooleanProperty, FolioContentInlineComparisonResult, FolioContentInlineFormatting, FolioContentInlineFormattingPatch, FolioContentLineSpacingRule, FolioContentParagraphAlignment, FolioContentParagraphKind, FolioContentParagraphSpacing, FolioContentRun, FolioContentSnapshot, FolioContentTableLocation } from "../compare/content-types.js";
3
3
  import { FolioAIBlock, FolioAIBlockAnchor, FolioAIBlockKind, FolioAIBlockPreviewRun, FolioAIBlockStructuralBoundary, FolioAIBlockTableLocation, FolioAIComment, FolioAIEditAppliedOperation, FolioAIEditApplyMode, FolioAIEditApplyResult, FolioAIEditNormalization, FolioAIEditNormalizationCode, FolioAIEditOperation, FolioAIEditPrecondition, FolioAIEditReviewMeta, FolioAIEditSeverity, FolioAIEditSkipReason, FolioAIEditSkippedOperation, FolioAIEditSnapshot, FolioAIInlineBooleanProperty, FolioAIInlineFormatting, FolioAIInlineFormattingPatch, FolioAIParagraphSpacing, FolioAISignatureParty } from "../ai-edits/types.js";
4
4
  import { WORD_DIFF_GRANULARITIES, WordDiffGranularity, WordDiffNormalization, WordDiffOptions, WordDiffSegment, diffWordSegments } from "../ai-edits/word-diff.js";
5
5
  import { FolioAIEditApplyOutcome, FolioRevisionStamp, FolioWordDiffOptions, applyFolioAIEditOperations } from "../ai-edits/apply.js";
@@ -10,9 +10,9 @@ import { AIBarStatus, AIChatMode, AICitation, AICitationSource, AIGenerateInput,
10
10
  import { ApplyResult, applySuggestions } from "../ai-suggestions/apply.js";
11
11
  import { ResolvedAnchor, isSuggestionStale, resolveSuggestionAnchor } from "../ai-suggestions/conflict.js";
12
12
  import { PositionalText, buildPositionalText } from "../ai-suggestions/text-positions.js";
13
- import { CompareContentOptions, FOLIO_CONTENT_COMPARISON_LIMITS, FolioContentBlockProperty, FolioContentComparison, FolioContentComparisonError, FolioContentComparisonEvent, FolioContentComparisonLimit, FolioContentComparisonLimitError, FolioContentFormatRange, FolioContentFormattingChange, FolioContentParagraphFormattingPatch, FolioContentStructuralChange, FolioContentTextSegment, InvalidFolioContentComparisonError, compareContent } from "../compare/content.js";
14
13
  import { COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, CompareVerification, CompareVerificationCause, CompareVerificationFailure, CompareVerificationInvariant, FinalParagraphMarkRevision } from "../compare/verification.js";
15
- import { COMPARE_UNSUPPORTED_REASONS, CompareChange, CompareChangeLocation, CompareDocxApplyError, CompareDocxError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxOptions, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, CompareFormatRange, CompareResult, CompareUnsupportedPart, CompareUnsupportedReason, InvalidCompareDocxOptionsError } from "../compare/types.js";
14
+ import { COMPARE_REVISION_FORMATS, COMPARE_UNSUPPORTED_REASONS, CompareChange, CompareChangeLocation, CompareCompatibility, CompareDocxApplyError, CompareDocxError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxOptions, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, CompareFolioRequirement, CompareResult, CompareRevisionFormat, CompareUnsupportedPart, CompareUnsupportedReason, InvalidCompareDocxOptionsError } from "../compare/types.js";
15
+ import { CompareContentOptions, FOLIO_CONTENT_COMPARISON_LIMITS, FolioContentBlockProperty, FolioContentComparison, FolioContentComparisonError, FolioContentComparisonEvent, FolioContentComparisonLimit, FolioContentComparisonLimitError, FolioContentFormattingChange, FolioContentInlinePresentationProjectionError, FolioContentParagraphFormattingPatch, FolioContentStructuralChange, FolioContentTextSegment, InvalidFolioContentComparisonError, compareContent } from "../compare/content.js";
16
16
  import { MAX_COMPARE_OPERATIONS, compareDocx } from "../compare/compare.js";
17
17
  import { DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DocumentPreset, DocumentStyleSet } from "../style-sets/types.js";
18
18
  import { CreateEmptyDocumentOptions, createEmptyDocument } from "../utils/createDocument.js";
@@ -39,4 +39,4 @@ import { getGoogleFontsEnabled, setEmbeddedFontFamilyMap, setGoogleFontsEnabled
39
39
  import { DOCX_CONFORMANCE_CLASSES } from "../index.js";
40
40
  type Document = document_d_exports.Document;
41
41
  type DocxConformanceClass = document_d_exports.DocxConformanceClass;
42
- export { type AIBarStatus, type AIChatMode, type AICitation, type AICitationRange, type AICitationSource, type AIGenerateInput, type AISuggestion, type AISuggestionApplyMode, type AISuggestionPreset, type AISuggestionSeverity, type AISuggestionStatus, type AcceptAutocompleteResult, type AnonymizationMatch, type AnonymizationTerm, type ApplyFolioDocumentOperationsOptions, type ApplyResult, type AutocompleteSuggestionPluginOptions, type AutocompleteSuggestionState, type AutocompleteSuggestionStatus, type AutocompleteTriggerCheck, type AutocompleteTriggerOptions, type AutocompleteTriggerSkipReason, type BlockRect, COMPARE_UNSUPPORTED_REASONS, COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, type CompareChange, type CompareChangeLocation, type CompareContentOptions, CompareDocxApplyError, type CompareDocxError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, type CompareDocxOptions, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, type CompareFormatRange, type CompareResult, type CompareUnsupportedPart, type CompareUnsupportedReason, type CompareVerification, type CompareVerificationCause, type CompareVerificationFailure, type CompareVerificationInvariant, type CreateEmptyDocumentOptions, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocumentPreset, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, type DocxCompatibility, type DocxCompatibilityContext, type DocxCompatibilityIssue, type DocxCompatibilityLocation, type DocxCompatibilityPart, type DocxConformanceClass, type EmbeddedFont, type EmbeddedFontParts, type ExtractDocumentStyleSetOptions, FOLIO_CONTENT_COMPARISON_LIMITS, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, type FinalParagraphMarkRevision, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIBlockStructuralBoundary, type FolioAIBlockTableLocation, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyOutcome, type FolioAIEditApplyResult, type FolioAIEditNormalization, type FolioAIEditNormalizationCode, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIInlineBooleanProperty, type FolioAIInlineFormatting, type FolioAIInlineFormattingPatch, type FolioAIParagraphSpacing, type FolioAISignatureParty, type FolioBlockId, type FolioContentBlock, type FolioContentBlockProperty, type FolioContentComparison, type FolioContentComparisonError, type FolioContentComparisonEvent, type FolioContentComparisonLimit, FolioContentComparisonLimitError, type FolioContentContainerPathEntry, type FolioContentFormatRange, type FolioContentFormattingChange, type FolioContentIdStability, type FolioContentInlineBooleanProperty, type FolioContentInlineFormatting, type FolioContentInlineFormattingPatch, type FolioContentLineSpacingRule, type FolioContentParagraphAlignment, type FolioContentParagraphFormattingPatch, type FolioContentParagraphKind, type FolioContentParagraphSpacing, type FolioContentRun, type FolioContentSnapshot, type FolioContentStructuralChange, type FolioContentTableLocation, type FolioContentTextSegment, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationResultBase, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocxCompatibilityHost, type FolioDocxCompatibilityProfile, type FolioRevisionStamp, type FolioWordDiffOptions, type ImageMeta, type ImageRef, type InspectDocxCompatibilityOptions, InvalidCompareDocxOptionsError, InvalidFolioContentComparisonError, InvalidFolioDocumentOperationBatchError, MAX_COMPARE_OPERATIONS, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, STELLA_STYLE_SET_NAME, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, UnsupportedFolioDocumentOperationVersionError, WORD_DIFF_GRANULARITIES, type WordDiffGranularity, type WordDiffNormalization, type WordDiffOptions, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildEmbeddedFontFamilyMap, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, compareContent, compareDocx, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, currentFolioBlockId, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioAIContentBlock, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, mergeDocumentContent, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scopeEmbeddedFontFamily, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setEmbeddedFontFamilyMap, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
42
+ export { type AIBarStatus, type AIChatMode, type AICitation, type AICitationRange, type AICitationSource, type AIGenerateInput, type AISuggestion, type AISuggestionApplyMode, type AISuggestionPreset, type AISuggestionSeverity, type AISuggestionStatus, type AcceptAutocompleteResult, type AnonymizationMatch, type AnonymizationTerm, type ApplyFolioDocumentOperationsOptions, type ApplyResult, type AutocompleteSuggestionPluginOptions, type AutocompleteSuggestionState, type AutocompleteSuggestionStatus, type AutocompleteTriggerCheck, type AutocompleteTriggerOptions, type AutocompleteTriggerSkipReason, type BlockRect, COMPARE_REVISION_FORMATS, COMPARE_UNSUPPORTED_REASONS, COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, type CompareChange, type CompareChangeLocation, type CompareCompatibility, type CompareContentOptions, CompareDocxApplyError, type CompareDocxError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, type CompareDocxOptions, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, type CompareFolioRequirement, type CompareResult, type CompareRevisionFormat, type CompareUnsupportedPart, type CompareUnsupportedReason, type CompareVerification, type CompareVerificationCause, type CompareVerificationFailure, type CompareVerificationInvariant, type CreateEmptyDocumentOptions, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, type DeriveBlockIdInput, type DirectiveKind, type DirectiveRange, type Document, type DocumentPreset, type DocumentStyleCatalog, type DocumentStyleCatalogEntry, type DocumentStyleSet, type DocxCompatibility, type DocxCompatibilityContext, type DocxCompatibilityIssue, type DocxCompatibilityLocation, type DocxCompatibilityPart, type DocxConformanceClass, type EmbeddedFont, type EmbeddedFontParts, type ExtractDocumentStyleSetOptions, FOLIO_CONTENT_COMPARISON_LIMITS, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, type FinalParagraphMarkRevision, type FolioAIBlock, type FolioAIBlockAnchor, type FolioAIBlockKind, type FolioAIBlockPreviewRun, type FolioAIBlockStructuralBoundary, type FolioAIBlockTableLocation, type FolioAIComment, type FolioAIEditAppliedOperation, type FolioAIEditApplyMode, type FolioAIEditApplyOutcome, type FolioAIEditApplyResult, type FolioAIEditNormalization, type FolioAIEditNormalizationCode, type FolioAIEditOperation, type FolioAIEditPrecondition, type FolioAIEditReviewMeta, type FolioAIEditSeverity, type FolioAIEditSkipReason, type FolioAIEditSkippedOperation, type FolioAIEditSnapshot, type FolioAIInlineBooleanProperty, type FolioAIInlineFormatting, type FolioAIInlineFormattingPatch, type FolioAIParagraphSpacing, type FolioAISignatureParty, type FolioBlockId, type FolioContentBlock, type FolioContentBlockProperty, type FolioContentComparison, type FolioContentComparisonError, type FolioContentComparisonEvent, type FolioContentComparisonLimit, FolioContentComparisonLimitError, type FolioContentContainerPathEntry, type FolioContentFormatRange, type FolioContentFormattingChange, type FolioContentIdStability, type FolioContentInlineBooleanProperty, type FolioContentInlineComparisonResult, type FolioContentInlineFormatting, type FolioContentInlineFormattingPatch, FolioContentInlinePresentationProjectionError, type FolioContentLineSpacingRule, type FolioContentParagraphAlignment, type FolioContentParagraphFormattingPatch, type FolioContentParagraphKind, type FolioContentParagraphSpacing, type FolioContentRun, type FolioContentSnapshot, type FolioContentStructuralChange, type FolioContentTableLocation, type FolioContentTextSegment, type FolioDocumentOperation, type FolioDocumentOperationAffectedTarget, type FolioDocumentOperationBatch, type FolioDocumentOperationCapabilities, type FolioDocumentOperationIssue, type FolioDocumentOperationMode, type FolioDocumentOperationPrecondition, type FolioDocumentOperationReceipt, type FolioDocumentOperationRecovery, type FolioDocumentOperationResult, type FolioDocumentOperationResultBase, type FolioDocumentOperationStatus, type FolioDocumentOperationStory, type FolioDocumentOperationType, type FolioDocumentOperationUndoFailureReason, type FolioDocumentOperationUndoHandle, type FolioDocumentOperationUndoResult, type FolioDocxCompatibilityHost, type FolioDocxCompatibilityProfile, type FolioRevisionStamp, type FolioWordDiffOptions, type ImageMeta, type ImageRef, type InspectDocxCompatibilityOptions, InvalidCompareDocxOptionsError, InvalidFolioContentComparisonError, InvalidFolioDocumentOperationBatchError, MAX_COMPARE_OPERATIONS, type MarkdownOptions, type MarkdownResult, type PositionalText, type ResolvedAnchor, STELLA_STYLE_SET_NAME, type TemplatePreviewSpan, type TemplatePreviewValue, type TemplatePreviewValues, type TemplateSlashMenuKeyAction, type TemplateSlashMenuState, UnsupportedFolioDocumentOperationVersionError, WORD_DIFF_GRANULARITIES, type WordDiffGranularity, type WordDiffNormalization, type WordDiffOptions, type WordDiffSegment, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildEmbeddedFontFamilyMap, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, compareContent, compareDocx, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, currentFolioBlockId, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioAIContentBlock, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, mergeDocumentContent, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scopeEmbeddedFontFamily, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setEmbeddedFontFamilyMap, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
@@ -6,8 +6,8 @@ import { isSuggestionStale, resolveSuggestionAnchor } from "../ai-suggestions/co
6
6
  import { buildPositionalText } from "../ai-suggestions/text-positions.js";
7
7
  import { DEFAULT_AI_SUGGESTION_PRESETS } from "../ai-suggestions/types.js";
8
8
  import { MAX_COMPARE_OPERATIONS, compareDocx } from "../compare/compare.js";
9
- import { FOLIO_CONTENT_COMPARISON_LIMITS, FolioContentComparisonLimitError, InvalidFolioContentComparisonError, compareContent } from "../compare/content.js";
10
- import { COMPARE_UNSUPPORTED_REASONS, CompareDocxApplyError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, InvalidCompareDocxOptionsError } from "../compare/types.js";
9
+ import { FOLIO_CONTENT_COMPARISON_LIMITS, FolioContentComparisonLimitError, FolioContentInlinePresentationProjectionError, InvalidFolioContentComparisonError, compareContent } from "../compare/content.js";
10
+ import { COMPARE_REVISION_FORMATS, COMPARE_UNSUPPORTED_REASONS, CompareDocxApplyError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, InvalidCompareDocxOptionsError } from "../compare/types.js";
11
11
  import { COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS } from "../compare/verification.js";
12
12
  import { FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, InvalidFolioDocumentOperationBatchError, UnsupportedFolioDocumentOperationVersionError, applyFolioDocumentOperations, assertSupportedFolioDocumentOperationVersion, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, isFolioDocumentOperationModeSupported, isSupportedFolioDocumentOperationVersion, parseFolioDocumentOperationBatch } from "../document-operations.js";
13
13
  import { inspectDocxCompatibility } from "../docx/compatibility.js";
@@ -32,4 +32,4 @@ import { currentFolioBlockId, deriveBlockId, getFolioParaIdFromBlockId, isFolioB
32
32
  import { createEmptyDocument } from "../utils/createDocument.js";
33
33
  import { getGoogleFontsEnabled, setEmbeddedFontFamilyMap, setGoogleFontsEnabled } from "../utils/fontResolver.js";
34
34
  import { mergeDocumentContent } from "../utils/mergeDocumentContent.js";
35
- export { COMPARE_UNSUPPORTED_REASONS, COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, CompareDocxApplyError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, FOLIO_CONTENT_COMPARISON_LIMITS, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FolioContentComparisonLimitError, InvalidCompareDocxOptionsError, InvalidFolioContentComparisonError, InvalidFolioDocumentOperationBatchError, MAX_COMPARE_OPERATIONS, STELLA_STYLE_SET_NAME, UnsupportedFolioDocumentOperationVersionError, WORD_DIFF_GRANULARITIES, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildEmbeddedFontFamilyMap, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, compareContent, compareDocx, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, currentFolioBlockId, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioAIContentBlock, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, mergeDocumentContent, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scopeEmbeddedFontFamily, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setEmbeddedFontFamilyMap, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
35
+ export { COMPARE_REVISION_FORMATS, COMPARE_UNSUPPORTED_REASONS, COMPARE_VERIFICATION_CAUSES, COMPARE_VERIFICATION_INVARIANTS, CompareDocxApplyError, CompareDocxFinalParagraphMarkError, CompareDocxOperationLimitError, CompareDocxParseError, CompareDocxRoundTripError, CompareDocxSerializeError, DEFAULT_AI_SUGGESTION_PRESETS, DEFAULT_AUTOCOMPLETE_DEAD_ZONE_NODES, DOCUMENT_PRESET_VERSION, DOCUMENT_STYLE_SET_VERSION, DOCX_CONFORMANCE_CLASSES, FOLIO_CONTENT_COMPARISON_LIMITS, FOLIO_DOCUMENT_OPERATION_BATCH_MODES, FOLIO_DOCUMENT_OPERATION_CONTRACT_VERSION, FOLIO_DOCUMENT_OPERATION_MODES, FOLIO_DOCUMENT_OPERATION_MODES_BY_TYPE, FOLIO_DOCUMENT_OPERATION_PRECONDITIONS, FOLIO_DOCUMENT_OPERATION_STORIES, FOLIO_DOCUMENT_OPERATION_TYPES, FOLIO_LINE_SPACING_RULE_VALUES, FOLIO_PARAGRAPH_ALIGNMENT_VALUES, FolioContentComparisonLimitError, FolioContentInlinePresentationProjectionError, InvalidCompareDocxOptionsError, InvalidFolioContentComparisonError, InvalidFolioDocumentOperationBatchError, MAX_COMPARE_OPERATIONS, STELLA_STYLE_SET_NAME, UnsupportedFolioDocumentOperationVersionError, WORD_DIFF_GRANULARITIES, acceptAutocompleteSuggestion, acceptAutocompleteWord, anonymizationDecorationsKey, appendAutocompleteToken, applyFolioAIEditOperations, applyFolioDocumentOperations, applySuggestions, assertSupportedFolioDocumentOperationVersion, autocompleteSuggestionKey, autocompleteSuggestionPlugin, buildEmbeddedFontFamilyMap, buildPositionalText, clearAutocompleteSuggestion, clearTemplateSlashMenu, compareContent, compareDocx, consumeTemplateSlashQuery, createAICitationDecorationsPlugin, createDocx, createEmptyDocument, createFolioAIEditSnapshot, createStellaStyleDocumentPreset, createStellaStyleSet, currentFolioBlockId, deriveBlockId, diffWordSegments, extractDocumentStyleSet, extractDocumentStyleSetFromDocx, extractEmbeddedFonts, finishAutocompleteSuggestion, fromMarkdown, getAnonymizationMatches, getAutocompleteSuggestion, getEmbeddedFontFaces, getFolioCaretViewportRect, getFolioDocumentOperationCapabilities, getFolioDocumentOperationIssues, getFolioDocumentOperationReceipts, getFolioParaIdFromBlockId, getFolioSelectionViewportRect, getGoogleFontsEnabled, getTemplateDirectives, getTemplateSlashMenu, hashFolioAIBlockText, inspectDocumentStyles, inspectDocumentStylesFromDocx, inspectDocxCompatibility, isFolioAIContentBlock, isFolioBlockId, isFolioDocumentOperationModeSupported, isSequentialFolioBlockId, isSuggestionStale, isSupportedFolioDocumentOperationVersion, mergeDocumentContent, normalizeFolioAIBlockText, parseFolioDocumentOperationBatch, resetTemplateSlashQuery, resolveSuggestionAnchor, scanDirectives, scopeEmbeddedFontFamily, scrollFolioPositionIntoView, setAICitationsMeta, setAISuggestionsMeta, setActiveCitationMeta, setAnonymizationTermsMeta, setEmbeddedFontFamilyMap, setFocusedSuggestionMeta, setGoogleFontsEnabled, setTemplatePreviewValues, shouldTriggerAutocomplete, startAutocompleteSuggestion, templateSlashMenuKey, toMarkdown, toMarkdownResult };
@@ -1,3 +1,4 @@
1
+ import { displayCommentsFrom } from "../display-list/build/commentAnnotations.js";
1
2
  import { createDisplayListPagePainter } from "../display-list/editor/displayListPagePainter.js";
2
3
  import { PAGE_RENDERER, displayListFurnitureFrom } from "../display-list/editor/pageRenderer.js";
3
4
  import { resolveDocumentGridLinePitch } from "../docx/documentGrid.js";
@@ -307,6 +308,8 @@ function runLayoutPipeline(deps, state, options = {}) {
307
308
  pageGap,
308
309
  mirrorMargins
309
310
  };
311
+ if (document?.package.document.sections !== void 0) nextLayoutOpts.sectionVerticalAlignments = document.package.document.sections.map(({ properties }) => properties.verticalAlign);
312
+ else if (sectionProperties !== null && sectionProperties !== void 0) nextLayoutOpts.sectionVerticalAlignments = [sectionProperties.verticalAlign];
310
313
  if (hasTitlePg) nextLayoutOpts.firstPageMargins = bodyMarginsClearHeaderFooter({
311
314
  authoredMargins: margins,
312
315
  preparedHeader: firstPageHeaderForRender,
@@ -513,6 +516,7 @@ function runLayoutPipeline(deps, state, options = {}) {
513
516
  layout: newLayout,
514
517
  blockLookup,
515
518
  doc: pagesContainer.ownerDocument,
519
+ ...document?.package.document.comments === void 0 ? {} : { comments: displayCommentsFrom(document.package.document.comments) },
516
520
  ...displayListFurnitureFrom(renderOpts),
517
521
  ...footnoteContentMap.size === 0 ? {} : { footnoteContentById: footnoteContentMap }
518
522
  }).paintPage;
@@ -1,4 +1,4 @@
1
- import { DisplayColor, DisplayLink, DisplayLinkTarget, DisplayStoryRef } from "../types.js";
1
+ import { DisplayColor, DisplayLink, DisplayLinkTarget, DisplayRect, DisplayStoryRef } from "../types.js";
2
2
  import { FontTable } from "./fontTable.js";
3
3
  import { ImageTable } from "./imagePrimitives.js";
4
4
  import { UnsupportedCollector } from "./unsupported.js";
@@ -23,6 +23,8 @@ type BuildContext = {
23
23
  readonly authorColors: AuthorColorTable;
24
24
  /** Link sink for the page being built. */
25
25
  readonly links: DisplayLink[];
26
+ /** Exact painted rectangles for each comment range on this page. */
27
+ readonly commentRects: Map<number, DisplayRect[]>;
26
28
  /**
27
29
  * Bookmark name → where it lands, collected in a first pass over the layout.
28
30
  * A `#name` hyperlink can only become a `page` target once every page is
@@ -3,6 +3,7 @@ import { Layout } from "../../layout-engine/types.js";
3
3
  import { BlockLookup } from "../../layout-painter/index.js";
4
4
  import { DisplayColor, DisplayList, DisplayMetadata, DisplayPage } from "../types.js";
5
5
  import { PageFurnitureInputs } from "./furniture.js";
6
+ import { DisplayCommentInput } from "./commentAnnotations.js";
6
7
  //#region src/display-list/build/buildDisplayList.d.ts
7
8
  /**
8
9
  * Every feature a source document can have that a `Layout` does not record and
@@ -42,6 +43,8 @@ type BuildDisplayListOptions = PageFurnitureInputs & {
42
43
  * that cannot find that name on the host paints the document in a substitute.
43
44
  */
44
45
  readonly embeddedFonts?: readonly EmbeddedFont[];
46
+ /** Comment bodies keyed by the `commentIds` already carried on layout runs. */
47
+ readonly comments?: readonly DisplayCommentInput[];
45
48
  };
46
49
  /**
47
50
  * A document's display list, one page at a time.
@@ -68,7 +71,7 @@ type DisplayListBuilder = {
68
71
  */
69
72
  readonly snapshot: () => DisplayList;
70
73
  };
71
- declare const createDisplayListBuilder: ({ layout, blockLookup, metadata, pageBackground, documentFeatures, embeddedFonts, ...furniture }: BuildDisplayListOptions) => DisplayListBuilder;
74
+ declare const createDisplayListBuilder: ({ layout, blockLookup, metadata, pageBackground, documentFeatures, embeddedFonts, comments, ...furniture }: BuildDisplayListOptions) => DisplayListBuilder;
72
75
  /**
73
76
  * The whole document's display list. Every page is built, so the tables, the
74
77
  * outline and the gap report are complete; an editor that paints a window of
@@ -27,6 +27,34 @@ const FIRST_PAGE_INDEX = 0;
27
27
  * document-wide flag the caller would have to remember to set.
28
28
  */
29
29
  const DOCUMENT_FEATURE_GAPS = [["pageBorders", "w:pgBorders reach the painter through render options, not through Layout, and none were supplied to the builder"], ["watermark", "watermarks reach the painter through render options, not through Layout, and none was supplied to the builder"]];
30
+ const rectKey = ({ xPx, yPx, widthPx, heightPx }) => `${String(xPx)}:${String(yPx)}:${String(widthPx)}:${String(heightPx)}`;
31
+ const dedupeRects = (rects) => {
32
+ const seen = /* @__PURE__ */ new Set();
33
+ const unique = [];
34
+ for (const rect of rects) {
35
+ const key = rectKey(rect);
36
+ if (seen.has(key)) continue;
37
+ seen.add(key);
38
+ unique.push(rect);
39
+ }
40
+ return unique;
41
+ };
42
+ const buildCommentAnnotations = (commentsById, commentRects) => {
43
+ const annotations = [];
44
+ for (const [commentId, rects] of commentRects) {
45
+ const comment = commentsById.get(commentId);
46
+ if (comment === void 0) continue;
47
+ const uniqueRects = dedupeRects(rects);
48
+ if (uniqueRects.length === 0) continue;
49
+ annotations.push({
50
+ commentId,
51
+ rects: uniqueRects,
52
+ contents: comment.contents,
53
+ author: comment.author
54
+ });
55
+ }
56
+ return annotations;
57
+ };
30
58
  const paragraphTextOf = (block) => block.runs.map((run) => run.kind === "text" ? run.text : "").join("").trim();
31
59
  /**
32
60
  * Resolve everything that needs the whole document before any page is painted:
@@ -126,14 +154,16 @@ const paintFragment = ({ fragment, entry, context, composer, prevEntry, nextEntr
126
154
  }
127
155
  }
128
156
  };
129
- const buildPage = ({ page, pageIndex, totalPages, blockLookup, pageBackground, bookmarkTargets, fonts, images, unsupported, authorColors, furniture }) => {
157
+ const buildPage = ({ page, pageIndex, totalPages, blockLookup, pageBackground, bookmarkTargets, fonts, images, unsupported, authorColors, furniture, commentsById }) => {
130
158
  const links = [];
159
+ const commentRects = /* @__PURE__ */ new Map();
131
160
  const context = {
132
161
  fonts,
133
162
  images,
134
163
  unsupported,
135
164
  authorColors,
136
165
  links,
166
+ commentRects,
137
167
  bookmarkTargets,
138
168
  story: { kind: "body" },
139
169
  pageIndex,
@@ -208,7 +238,8 @@ const buildPage = ({ page, pageIndex, totalPages, blockLookup, pageBackground, b
208
238
  orientation: page.orientation ?? (page.size.w > page.size.h ? "landscape" : "portrait"),
209
239
  primitives: composer.primitives(),
210
240
  regions: composer.regions(),
211
- links
241
+ links,
242
+ comments: buildCommentAnnotations(commentsById, commentRects)
212
243
  };
213
244
  };
214
245
  /** Which kind of region a fragment paints into. */
@@ -223,12 +254,14 @@ const SUPPLIED_BY = {
223
254
  pageBorders: (furniture) => furniture.pageBorders !== void 0,
224
255
  watermark: (furniture) => furniture.watermark !== void 0 || furniture.watermarkByHeaderRId !== void 0
225
256
  };
226
- const createDisplayListBuilder = ({ layout, blockLookup, metadata, pageBackground, documentFeatures, embeddedFonts, ...furniture }) => {
257
+ const createDisplayListBuilder = ({ layout, blockLookup, metadata, pageBackground, documentFeatures, embeddedFonts, comments, ...furniture }) => {
227
258
  const fonts = new FontTable(embeddedFonts ?? []);
228
259
  const images = new ImageTable();
229
260
  const unsupported = new UnsupportedCollector();
230
261
  const authorColors = new AuthorColorTable();
231
262
  const { bookmarkTargets, outline } = collectDocumentTargets(layout, blockLookup);
263
+ const commentsById = /* @__PURE__ */ new Map();
264
+ for (const comment of comments ?? []) commentsById.set(comment.id, comment);
232
265
  if (layout.pages.length > 0) for (const [feature, detail] of DOCUMENT_FEATURE_GAPS) {
233
266
  if (SUPPLIED_BY[feature](furniture)) continue;
234
267
  if (documentFeatures !== void 0 && !documentFeatures[feature]) continue;
@@ -251,7 +284,8 @@ const createDisplayListBuilder = ({ layout, blockLookup, metadata, pageBackgroun
251
284
  images,
252
285
  unsupported,
253
286
  authorColors,
254
- furniture
287
+ furniture,
288
+ commentsById
255
289
  });
256
290
  built.set(pageIndex, display);
257
291
  return display;
@@ -0,0 +1,10 @@
1
+ import { document_d_exports } from "../../types/document.js";
2
+ //#region src/display-list/build/commentAnnotations.d.ts
3
+ type DisplayCommentInput = {
4
+ readonly id: number;
5
+ readonly author: string;
6
+ readonly contents: string;
7
+ };
8
+ declare const displayCommentsFrom: (comments: readonly document_d_exports.Comment[]) => readonly DisplayCommentInput[];
9
+ //#endregion
10
+ export { DisplayCommentInput, displayCommentsFrom };
@@ -0,0 +1,17 @@
1
+ import { getParagraphText } from "../../docx/paragraphParser.js";
2
+ //#region src/display-list/build/commentAnnotations.ts
3
+ const displayCommentsFrom = (comments) => {
4
+ const inputs = [];
5
+ for (const { id, author, content } of comments) {
6
+ const paragraphs = [];
7
+ for (const paragraph of content) paragraphs.push(getParagraphText(paragraph));
8
+ inputs.push({
9
+ id,
10
+ author,
11
+ contents: paragraphs.join("\n")
12
+ });
13
+ }
14
+ return inputs;
15
+ };
16
+ //#endregion
17
+ export { displayCommentsFrom };
@@ -45,6 +45,20 @@ const HYPERLINK_COLOR = {
45
45
  b: 193,
46
46
  a: 1
47
47
  };
48
+ /** Matches the editable DOM painter's review-range treatment. */
49
+ const COMMENT_BACKGROUND = {
50
+ r: 255,
51
+ g: 212,
52
+ b: 0,
53
+ a: .08
54
+ };
55
+ const COMMENT_BORDER = {
56
+ r: 180,
57
+ g: 130,
58
+ b: 0,
59
+ a: .24
60
+ };
61
+ const COMMENT_BORDER_WIDTH_PX = 1;
48
62
  /** `renderParagraph.ts:603-616`: a raised run paints 0.4em up, a lowered one 0.2em down. */
49
63
  const SUPERSCRIPT_RISE_RATIO = .4;
50
64
  const SUBSCRIPT_DROP_RATIO = .2;
@@ -275,8 +289,32 @@ const emitGlyphRun = ({ sink, context, run, glyphs, style, paintXPx, baselineYPx
275
289
  let runBaselineYPx = baselineYPx - (run.positionPx ?? 0);
276
290
  if (run.superscript) runBaselineYPx -= fontSizePx * SUPERSCRIPT_RISE_RATIO;
277
291
  else if (run.subscript) runBaselineYPx += fontSizePx * SUBSCRIPT_DROP_RATIO;
292
+ const commentIds = run.commentIds ?? [];
278
293
  const background = run.highlight ?? run.shading;
279
- if (background && glyphs.widthPx > 0) {
294
+ if (commentIds.length > 0 && glyphs.widthPx > 0) {
295
+ const metrics = getFontMetrics(style);
296
+ const rect = {
297
+ xPx: paintXPx,
298
+ yPx: runBaselineYPx - metrics.fontBoxAscent,
299
+ widthPx: glyphs.widthPx,
300
+ heightPx: metrics.fontBoxAscent + metrics.fontBoxDescent
301
+ };
302
+ sink.backgrounds.push({
303
+ kind: "rect",
304
+ rect,
305
+ fill: COMMENT_BACKGROUND
306
+ });
307
+ sink.decorations.push(horizontalLine(paintXPx, glyphs.widthPx, rect.yPx + rect.heightPx - .5, {
308
+ color: COMMENT_BORDER,
309
+ thicknessPx: COMMENT_BORDER_WIDTH_PX,
310
+ pattern: "solid"
311
+ }));
312
+ for (const commentId of commentIds) {
313
+ const rects = context.commentRects.get(commentId);
314
+ if (rects === void 0) context.commentRects.set(commentId, [rect]);
315
+ else rects.push(rect);
316
+ }
317
+ } else if (background && glyphs.widthPx > 0) {
280
318
  const fill = parseDisplayColor(background);
281
319
  if (fill) {
282
320
  const metrics = getFontMetrics(style);
@@ -15,7 +15,7 @@ import { hasComplexScript, isComplexScriptCodePoint } from "../utils/scriptSegme
15
15
  * advances rightward from its origin whatever the paragraph does.
16
16
  */
17
17
  declare const glyphCellOffsetsPx: (run: DisplayGlyphRun) => readonly number[];
18
- declare const DISPLAY_PRIMITIVE_KINDS: ("glyphRun" | "rect" | "line" | "image" | "clipGroup" | "rotateGroup" | "opacityGroup")[];
18
+ declare const DISPLAY_PRIMITIVE_KINDS: ("line" | "rect" | "image" | "glyphRun" | "clipGroup" | "rotateGroup" | "opacityGroup")[];
19
19
  /**
20
20
  * Dash geometry of each stroke pattern, as multiples of the stroke thickness.
21
21
  *