@stll/folio-core 0.33.1 → 0.34.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 (121) hide show
  1. package/dist/ai-edits/apply.d.ts +15 -0
  2. package/dist/ai-edits/apply.js +271 -48
  3. package/dist/ai-edits/headless.d.ts +47 -3
  4. package/dist/ai-edits/headless.js +53 -3
  5. package/dist/ai-edits/index.d.ts +1 -1
  6. package/dist/ai-edits/snapshot.d.ts +19 -1
  7. package/dist/ai-edits/snapshot.js +49 -5
  8. package/dist/ai-edits/table-geometry.d.ts +56 -0
  9. package/dist/ai-edits/table-geometry.js +217 -0
  10. package/dist/ai-edits/table-row-column-mutations.d.ts +34 -16
  11. package/dist/ai-edits/table-row-column-mutations.js +12 -4
  12. package/dist/ai-edits/table-template.d.ts +60 -0
  13. package/dist/ai-edits/table-template.js +176 -0
  14. package/dist/ai-edits/types.d.ts +13 -6
  15. package/dist/compare/__fixtures__/body-sequence.d.ts +61 -2
  16. package/dist/compare/__fixtures__/body-sequence.js +94 -6
  17. package/dist/compare/compare.d.ts +14 -7
  18. package/dist/compare/compare.js +136 -38
  19. package/dist/compare/formatting.d.ts +1 -1
  20. package/dist/compare/formatting.js +38 -9
  21. package/dist/compare/plan.d.ts +31 -1
  22. package/dist/compare/plan.js +233 -66
  23. package/dist/compare/types.d.ts +9 -7
  24. package/dist/compare/types.js +7 -5
  25. package/dist/compare/verification.d.ts +31 -12
  26. package/dist/compare/verification.js +106 -18
  27. package/dist/compat/eigenpal.d.ts +4 -4
  28. package/dist/compat/eigenpal.js +2 -2
  29. package/dist/controller/headerFooterEditorManager.js +11 -9
  30. package/dist/controller/layoutPipeline.js +24 -3
  31. package/dist/display-list/build/watermarkPrimitives.js +15 -3
  32. package/dist/display-list/primitives.d.ts +1 -1
  33. package/dist/document-operations.d.ts +10 -1
  34. package/dist/document-operations.js +33 -5
  35. package/dist/docx/appVersionNormalization.d.ts +49 -0
  36. package/dist/docx/appVersionNormalization.js +74 -0
  37. package/dist/docx/blockContentParser.js +3 -2
  38. package/dist/docx/drawingUtils.js +4 -3
  39. package/dist/docx/headerFooterParser.js +8 -14
  40. package/dist/docx/paragraphParser.js +48 -2
  41. package/dist/docx/rezip.d.ts +10 -4
  42. package/dist/docx/rezip.js +54 -18
  43. package/dist/docx/runParser.js +9 -8
  44. package/dist/docx/sdtProperties.js +4 -3
  45. package/dist/docx/selectiveSave.js +7 -6
  46. package/dist/docx/serializer/commentSerializer.d.ts +1 -1
  47. package/dist/docx/serializer/commentSerializer.js +43 -19
  48. package/dist/docx/serializer/documentSerializer.d.ts +3 -1
  49. package/dist/docx/serializer/documentSerializer.js +42 -78
  50. package/dist/docx/serializer/fontTableSerializer.js +8 -5
  51. package/dist/docx/serializer/headerFooterSerializer.d.ts +4 -1
  52. package/dist/docx/serializer/headerFooterSerializer.js +55 -31
  53. package/dist/docx/serializer/noteSerializer.js +34 -30
  54. package/dist/docx/serializer/numberingSerializer.js +8 -4
  55. package/dist/docx/serializer/paragraphSerializer.d.ts +1 -1
  56. package/dist/docx/serializer/paragraphSerializer.js +22 -12
  57. package/dist/docx/serializer/partNamespaces.d.ts +78 -0
  58. package/dist/docx/serializer/partNamespaces.js +324 -0
  59. package/dist/docx/serializer/runSerializer.js +1 -1
  60. package/dist/docx/serializer/settingsSerializer.js +8 -2
  61. package/dist/docx/serializer/stylesSerializer.js +8 -5
  62. package/dist/docx/serializer/tableSerializer.js +83 -18
  63. package/dist/docx/serializer/themeSerializer.js +10 -2
  64. package/dist/docx/server/build.d.ts +1 -1
  65. package/dist/docx/settingsParser.js +3 -0
  66. package/dist/docx/strictValueEncodings.gen.d.ts +15 -0
  67. package/dist/docx/strictValueEncodings.gen.js +275 -0
  68. package/dist/docx/tableParser.d.ts +0 -6
  69. package/dist/docx/tableParser.js +52 -7
  70. package/dist/docx/transitionalSpelling.d.ts +23 -0
  71. package/dist/docx/transitionalSpelling.js +36 -0
  72. package/dist/docx/universalMeasure.d.ts +20 -0
  73. package/dist/docx/universalMeasure.js +32 -0
  74. package/dist/docx/verbatimCapture.d.ts +20 -0
  75. package/dist/docx/verbatimCapture.js +131 -0
  76. package/dist/docx/vmlImageParser.js +4 -3
  77. package/dist/docx/watermarkParser.d.ts +2 -4
  78. package/dist/docx/watermarkParser.js +8 -9
  79. package/dist/docx/xmlParser.d.ts +23 -2
  80. package/dist/docx/xmlParser.js +43 -11
  81. package/dist/headless-layout.js +14 -2
  82. package/dist/index.d.ts +4 -4
  83. package/dist/index.js +2 -2
  84. package/dist/layout-bridge/convert/footnoteLayout.d.ts +2 -2
  85. package/dist/layout-bridge/convert/footnoteLayout.js +23 -10
  86. package/dist/layout-bridge/convert/headerFooterLayout.js +13 -2
  87. package/dist/layout-bridge/convert/toFlowBlocks.js +96 -13
  88. package/dist/layout-engine/index.js +11 -4
  89. package/dist/layout-engine/justifiedLineFit.d.ts +4 -4
  90. package/dist/layout-engine/justifiedLineFit.js +4 -4
  91. package/dist/layout-engine/measure/lineBreakProvider.js +1 -0
  92. package/dist/layout-engine/measure/measureBlocks.js +1 -1
  93. package/dist/layout-engine/measure/measureParagraph.js +29 -29
  94. package/dist/layout-painter/renderPage.js +6 -1
  95. package/dist/layout-painter/renderParagraph.js +18 -8
  96. package/dist/layout-painter/renderWatermark.js +11 -4
  97. package/dist/model.d.ts +3 -3
  98. package/dist/model.js +2 -2
  99. package/dist/prosemirror/attrs/index.js +11 -0
  100. package/dist/prosemirror/commands/comments.js +24 -3
  101. package/dist/prosemirror/containerFinalParagraph.d.ts +47 -0
  102. package/dist/prosemirror/containerFinalParagraph.js +92 -0
  103. package/dist/prosemirror/conversion/fromProseDoc.d.ts +8 -1
  104. package/dist/prosemirror/conversion/fromProseDoc.js +47 -9
  105. package/dist/prosemirror/conversion/toProseDoc.js +61 -14
  106. package/dist/prosemirror/extensions/core/DocExtension.js +7 -1
  107. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
  108. package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +1 -0
  109. package/dist/prosemirror/extensions/nodes/TableExtension.js +5 -0
  110. package/dist/prosemirror/schema/marks.d.ts +3 -9
  111. package/dist/prosemirror/schema/nodes.d.ts +22 -0
  112. package/dist/server.d.ts +1 -1
  113. package/dist/types/block-id.d.ts +12 -1
  114. package/dist/types/block-id.js +17 -1
  115. package/dist/utils/canonicalJson.d.ts +14 -0
  116. package/dist/utils/canonicalJson.js +19 -0
  117. package/dist/utils/fontResolver.js +51 -0
  118. package/dist/utils/formatToStyle.d.ts +1 -1
  119. package/dist/utils/formatToStyle.js +41 -1
  120. package/dist/watermark/index.js +7 -0
  121. package/package.json +2 -2
@@ -1,4 +1,5 @@
1
1
  import { expectParagraphAttrs, expectRunPropertyChangeMarkAttrs } from "../attrs/index.js";
2
+ import { paragraphEndsItsContainer } from "../containerFinalParagraph.js";
2
3
  import { textFormattingToMarks } from "../conversion/toProseDoc.js";
3
4
  import { markStructuralChange } from "../extensions/features/ParagraphChangeTrackerExtension.js";
4
5
  import { getFolioNodeRevisionCarriers } from "../revisionCarriers.js";
@@ -61,6 +62,7 @@ function resolveChange(from, to, mode, revisionIds) {
61
62
  const keepType = mode === "accept" ? insertionType : deletionType;
62
63
  const removeType = mode === "accept" ? deletionType : insertionType;
63
64
  const revisionSet = revisionIds === void 0 ? null : new Set(revisionIds);
65
+ const removeKeptMarksInBulk = revisionSet === null && keepType !== void 0;
64
66
  const matchesRevision = (mark) => revisionSet === null || typeof mark.attrs["revisionId"] === "number" && revisionSet.has(mark.attrs["revisionId"]);
65
67
  if (dispatch) {
66
68
  const tr = state.tr;
@@ -138,10 +140,29 @@ function resolveChange(from, to, mode, revisionIds) {
138
140
  from: rangeFrom,
139
141
  to: rangeTo
140
142
  });
141
- for (const mark of node.marks) if (keepType && mark.type === keepType && matchesRevision(mark)) tr.removeMark(rangeFrom, rangeTo, mark);
143
+ if (!removeKeptMarksInBulk) {
144
+ for (const mark of node.marks) if (keepType && mark.type === keepType && matchesRevision(mark)) tr.removeMark(rangeFrom, rangeTo, mark);
145
+ }
142
146
  return true;
143
147
  });
144
- for (const range of deleteRanges.toReversed()) tr.delete(range.from, range.to);
148
+ if (removeKeptMarksInBulk) tr.removeMark(from, to, keepType);
149
+ let rangesToDelete = deleteRanges;
150
+ if (revisionSet === null) {
151
+ const coalescedDeleteRanges = [];
152
+ for (const range of deleteRanges) {
153
+ const previous = coalescedDeleteRanges.at(-1);
154
+ if (previous && range.from <= previous.to) {
155
+ previous.to = Math.max(previous.to, range.to);
156
+ continue;
157
+ }
158
+ coalescedDeleteRanges.push({
159
+ from: range.from,
160
+ to: range.to
161
+ });
162
+ }
163
+ rangesToDelete = coalescedDeleteRanges;
164
+ }
165
+ for (const range of rangesToDelete.toReversed()) tr.delete(range.from, range.to);
145
166
  pPrMarkOps.sort((a, b) => b.paragraphPos - a.paragraphPos);
146
167
  for (const op of pPrMarkOps) {
147
168
  const mappedPos = tr.mapping.map(op.paragraphPos);
@@ -157,7 +178,7 @@ function resolveChange(from, to, mode, revisionIds) {
157
178
  const resolved = tr.doc.resolve(mappedPos);
158
179
  const previous = resolved.nodeBefore;
159
180
  const sectionCanMoveBack = !carriesSection(paragraph) || previous?.type.name === paragraph.type.name && !carriesSection(previous);
160
- const endsItsContainer = resolved.index() === resolved.parent.childCount - 1;
181
+ const endsItsContainer = paragraphEndsItsContainer(resolved, paragraph.type.name);
161
182
  const canGo = op.markWasAdded || !endsItsContainer;
162
183
  if (sectionCanMoveBack && holdsNoContent(paragraph) && canGo && resolved.parent.childCount > 1) {
163
184
  if (carriesSection(paragraph) && previous) tr.setNodeMarkup(mappedPos - previous.nodeSize, void 0, {
@@ -0,0 +1,47 @@
1
+ import { Node, ResolvedPos } from "prosemirror-model";
2
+ //#region src/prosemirror/containerFinalParagraph.d.ts
3
+ /**
4
+ * Whether the paragraph starting at `at` is the one its container ENDS with,
5
+ * which is the one place a paragraph mark cannot carry a revision.
6
+ *
7
+ * A deleted mark asks a reader to join the paragraph it ends with the one
8
+ * after it, and an inserted mark says that break was added, so rejecting it
9
+ * closes the paragraph back over the next one. A body, a table cell, a header
10
+ * or footer, a note and a text box each end with a paragraph that nothing
11
+ * follows, so neither direction can be resolved there: a consumer is left with
12
+ * a revision it can only ignore, and the redline no longer resolves to the two
13
+ * documents it was written from.
14
+ *
15
+ * A paragraph before a TABLE is not one of those: the table is a following
16
+ * sibling, so the paragraph does not end anything and carries its own mark
17
+ * like any other.
18
+ */
19
+ declare const paragraphEndsItsContainer: (at: ResolvedPos, paragraphTypeName: string) => boolean;
20
+ /**
21
+ * Every paragraph a container ENDS with in `doc`, with its position.
22
+ *
23
+ * One walk for the whole document: asking the question of each paragraph in
24
+ * turn costs a scan of its siblings each time, which is quadratic in a body
25
+ * whose paragraphs a batch has just filled.
26
+ */
27
+ declare const finalParagraphsOf: (doc: Node, paragraphTypeName: string) => {
28
+ position: number;
29
+ node: Node;
30
+ }[];
31
+ /**
32
+ * The paragraph an added break can rotate BACK onto from the one at `at`: the
33
+ * paragraph the run of inserted ones was appended after.
34
+ *
35
+ * The walk crosses the inserted paragraphs — their own marks stay where they
36
+ * are, and only which paragraph is left markless changes — and stops at the
37
+ * first one whose mark is free. `null` when there is none: a table in between,
38
+ * which no mark joins across; a paragraph whose mark says something else,
39
+ * which the rotation must not overwrite; or a container whose every paragraph
40
+ * is new.
41
+ */
42
+ declare const addedBreakCarrierBefore: (at: ResolvedPos, paragraphTypeName: string) => {
43
+ position: number;
44
+ node: Node;
45
+ } | null;
46
+ //#endregion
47
+ export { addedBreakCarrierBefore, finalParagraphsOf, paragraphEndsItsContainer };
@@ -0,0 +1,92 @@
1
+ //#region src/prosemirror/containerFinalParagraph.ts
2
+ /**
3
+ * A node the FILE holds as a child of the container it appears in.
4
+ *
5
+ * The editor keeps a text box as a block-level sibling of the paragraph that
6
+ * anchors it; the file hangs it off that paragraph's own run, so it is not a
7
+ * child of the container at all. A paragraph and a table are children in both.
8
+ */
9
+ const isAContainerChild = (node, paragraphTypeName) => node.type.name === paragraphTypeName || node.type.spec["tableRole"] === "table";
10
+ /** Whether every child from `fromIndex` on is one the file does not hold here. */
11
+ const nothingFollowsIn = (parent, fromIndex, paragraphTypeName) => {
12
+ for (let index = fromIndex; index < parent.childCount; index++) if (isAContainerChild(parent.child(index), paragraphTypeName)) return false;
13
+ return true;
14
+ };
15
+ /**
16
+ * Whether the paragraph starting at `at` is the one its container ENDS with,
17
+ * which is the one place a paragraph mark cannot carry a revision.
18
+ *
19
+ * A deleted mark asks a reader to join the paragraph it ends with the one
20
+ * after it, and an inserted mark says that break was added, so rejecting it
21
+ * closes the paragraph back over the next one. A body, a table cell, a header
22
+ * or footer, a note and a text box each end with a paragraph that nothing
23
+ * follows, so neither direction can be resolved there: a consumer is left with
24
+ * a revision it can only ignore, and the redline no longer resolves to the two
25
+ * documents it was written from.
26
+ *
27
+ * A paragraph before a TABLE is not one of those: the table is a following
28
+ * sibling, so the paragraph does not end anything and carries its own mark
29
+ * like any other.
30
+ */
31
+ const paragraphEndsItsContainer = (at, paragraphTypeName) => nothingFollowsIn(at.parent, at.index() + 1, paragraphTypeName);
32
+ /**
33
+ * Every paragraph a container ENDS with in `doc`, with its position.
34
+ *
35
+ * One walk for the whole document: asking the question of each paragraph in
36
+ * turn costs a scan of its siblings each time, which is quadratic in a body
37
+ * whose paragraphs a batch has just filled.
38
+ */
39
+ const finalParagraphsOf = (doc, paragraphTypeName) => {
40
+ const found = [];
41
+ const collect = (node, contentStart) => {
42
+ let last = null;
43
+ node.forEach((child, offset) => {
44
+ const position = contentStart + offset;
45
+ if (child.type.name === paragraphTypeName) last = {
46
+ position,
47
+ node: child
48
+ };
49
+ else if (isAContainerChild(child, paragraphTypeName)) last = null;
50
+ if (!child.isTextblock && child.childCount > 0) collect(child, position + 1);
51
+ });
52
+ if (last !== null) found.push(last);
53
+ };
54
+ collect(doc, 0);
55
+ return found;
56
+ };
57
+ /** A paragraph's mark, when it says the break was ADDED. */
58
+ const marksAnAddedBreak = (node) => {
59
+ const mark = node.attrs["pPrMark"];
60
+ if (typeof mark !== "object" || mark === null || !("kind" in mark)) return false;
61
+ return mark.kind === "ins" || mark.kind === "moveTo";
62
+ };
63
+ /**
64
+ * The paragraph an added break can rotate BACK onto from the one at `at`: the
65
+ * paragraph the run of inserted ones was appended after.
66
+ *
67
+ * The walk crosses the inserted paragraphs — their own marks stay where they
68
+ * are, and only which paragraph is left markless changes — and stops at the
69
+ * first one whose mark is free. `null` when there is none: a table in between,
70
+ * which no mark joins across; a paragraph whose mark says something else,
71
+ * which the rotation must not overwrite; or a container whose every paragraph
72
+ * is new.
73
+ */
74
+ const addedBreakCarrierBefore = (at, paragraphTypeName) => {
75
+ let position = at.pos;
76
+ for (let index = at.index() - 1; index >= 0; index--) {
77
+ const sibling = at.parent.child(index);
78
+ position -= sibling.nodeSize;
79
+ if (sibling.type.name !== paragraphTypeName) {
80
+ if (isAContainerChild(sibling, paragraphTypeName)) return null;
81
+ continue;
82
+ }
83
+ if (sibling.attrs["pPrMark"] == null) return {
84
+ position,
85
+ node: sibling
86
+ };
87
+ if (!marksAnAddedBreak(sibling)) return null;
88
+ }
89
+ return null;
90
+ };
91
+ //#endregion
92
+ export { addedBreakCarrierBefore, finalParagraphsOf, paragraphEndsItsContainer };
@@ -1,4 +1,5 @@
1
1
  import { document_d_exports } from "../../types/document.js";
2
+ import { TableAttrs, TableCellAttrs, TableRowAttrs } from "../schema/nodes.js";
2
3
  import { Mark, Node } from "prosemirror-model";
3
4
  //#region src/prosemirror/conversion/fromProseDoc.d.ts
4
5
  /**
@@ -12,7 +13,13 @@ type MarksToTextFormattingOptions = {
12
13
  inheritedFormatting: document_d_exports.TextFormatting | undefined;
13
14
  };
14
15
  declare function marksToTextFormatting(marks: readonly Mark[], options?: MarksToTextFormattingOptions): document_d_exports.TextFormatting;
16
+ declare function tableAttrsToFormatting(attrs: TableAttrs): document_d_exports.TableFormatting | undefined;
17
+ /**
18
+ * Convert ProseMirror table row attrs to TableRowFormatting
19
+ */
20
+ declare function tableRowAttrsToFormatting(attrs: TableRowAttrs): document_d_exports.TableRowFormatting | undefined;
15
21
  declare const standaloneTableCellFromProseMirror: (node: Node) => document_d_exports.TableCell;
22
+ declare function tableCellAttrsToFormatting(attrs: TableCellAttrs): document_d_exports.TableCellFormatting | undefined;
16
23
  /**
17
24
  * Update a Document with content from a ProseMirror document
18
25
  * Preserves all non-content parts of the original document
@@ -27,4 +34,4 @@ declare function updateDocumentContent(originalDocument: document_d_exports.Docu
27
34
  */
28
35
  declare function proseDocToBlocks(pmDoc: Node): document_d_exports.BlockContent[];
29
36
  //#endregion
30
- export { fromProseDoc, marksToTextFormatting, proseDocToBlocks, standaloneTableCellFromProseMirror, updateDocumentContent };
37
+ export { fromProseDoc, marksToTextFormatting, proseDocToBlocks, standaloneTableCellFromProseMirror, tableAttrsToFormatting, tableCellAttrsToFormatting, tableRowAttrsToFormatting, updateDocumentContent };
@@ -1,6 +1,7 @@
1
1
  import { numPrEqual } from "../../docx/numberingParser.js";
2
2
  import { ShapeOutlineStyleSchema, narrowEnum } from "../../docx/parserEnums.js";
3
3
  import { OUTLINE_STYLE_CSS_ALIASES, normalizeShapeTextAnchor } from "../../types/documentEnumValues.js";
4
+ import { canonicalJson } from "../../utils/canonicalJson.js";
4
5
  import { normalizeHorizontalScalePercent } from "../../utils/horizontalScale.js";
5
6
  import { pixelsToEmu } from "../../utils/units.js";
6
7
  import { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCharacterStyleMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHardBreakAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectLanguageMarkAttrs, expectMathAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs, expectRunPropertyChangeMarkAttrs, expectRunShadingMarkAttrs, expectSdtAttrs, expectShapeAttrs, expectStrikeMarkAttrs, expectSymbolAttrs, expectTabAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs } from "../attrs/index.js";
@@ -688,9 +689,9 @@ function createTrackedRunWrapper(type, info, child) {
688
689
  content
689
690
  };
690
691
  }
691
- function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, textBoxAnchorMarkers, skipLeadingRenderedPageBreak = false) {
692
+ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, textBoxAnchorMarkers, skipLeadingRenderedPageBreak = false, inheritedFormattingOverride) {
692
693
  const content = [];
693
- const inheritedFormatting = paragraph.type.name === "paragraph" ? expectParagraphAttrs(paragraph).defaultTextFormatting ?? void 0 : void 0;
694
+ const inheritedFormatting = inheritedFormattingOverride ?? (paragraph.type.name === "paragraph" ? expectParagraphAttrs(paragraph).defaultTextFormatting ?? void 0 : void 0);
694
695
  const sortedEmptyHyperlinks = (emptyHyperlinks ?? []).map((attrs, order) => ({
695
696
  attrs,
696
697
  order
@@ -965,7 +966,7 @@ function extractParagraphContent(paragraph, _documentCounts, emptyHyperlinks, te
965
966
  }));
966
967
  } else if (node.type.name === "sdt") {
967
968
  flushCurrentInline();
968
- content.push(createInlineSdtFromNode(node, textBoxAnchorMarkers));
969
+ content.push(createInlineSdtFromNode(node, textBoxAnchorMarkers, inheritedFormatting));
969
970
  } else if (node.type.name === "math") {
970
971
  flushCurrentInline();
971
972
  content.push(createMathFromNode(node));
@@ -1368,11 +1369,11 @@ function createMathFromNode(node) {
1368
1369
  /**
1369
1370
  * Create an InlineSdt from a PM sdt node
1370
1371
  */
1371
- function createInlineSdtFromNode(node, textBoxAnchorMarkers) {
1372
+ function createInlineSdtFromNode(node, textBoxAnchorMarkers, inheritedFormatting) {
1372
1373
  return {
1373
1374
  type: "inlineSdt",
1374
1375
  properties: sdtPropertiesFromAttrs(expectSdtAttrs(node)),
1375
- content: extractParagraphContent(node, void 0, void 0, textBoxAnchorMarkers).filter((c) => c.type === "run" || c.type === "hyperlink" || c.type === "simpleField" || c.type === "complexField" || c.type === "inlineSdt" || c.type === "insertion" || c.type === "deletion" || c.type === "moveFrom" || c.type === "moveTo" || c.type === "mathEquation")
1376
+ content: extractParagraphContent(node, void 0, void 0, textBoxAnchorMarkers, false, inheritedFormatting).filter((c) => c.type === "run" || c.type === "hyperlink" || c.type === "simpleField" || c.type === "complexField" || c.type === "inlineSdt" || c.type === "insertion" || c.type === "deletion" || c.type === "moveFrom" || c.type === "moveTo" || c.type === "mathEquation")
1376
1377
  };
1377
1378
  }
1378
1379
  /**
@@ -1523,6 +1524,7 @@ function createShapeRun(node) {
1523
1524
  function marksToTextFormatting(marks, options) {
1524
1525
  const formatting = {};
1525
1526
  let directOverrideFormatting;
1527
+ let directFontProperties;
1526
1528
  let characterStyleRPr;
1527
1529
  let runFormattingOverrideMark;
1528
1530
  for (const mark of marks) switch (mark.type.name) {
@@ -1650,9 +1652,14 @@ function marksToTextFormatting(marks, options) {
1650
1652
  }
1651
1653
  if (runFormattingOverrideMark) {
1652
1654
  const overrideAttrs = expectRunFormattingOverrideMarkAttrs(runFormattingOverrideMark);
1655
+ directFontProperties = overrideAttrs.directFontProperties;
1653
1656
  applyRunFormattingOverrideAttrs(formatting, overrideAttrs);
1654
1657
  directOverrideFormatting = {};
1655
1658
  applyRunFormattingOverrideAttrs(directOverrideFormatting, overrideAttrs);
1659
+ for (const property of directFontProperties ?? []) {
1660
+ const value = formatting[property];
1661
+ if (value !== void 0) Reflect.set(directOverrideFormatting, property, value);
1662
+ }
1656
1663
  }
1657
1664
  if (characterStyleRPr) return subtractCharacterStyleFormatting({
1658
1665
  directOverrideFormatting,
@@ -1660,8 +1667,29 @@ function marksToTextFormatting(marks, options) {
1660
1667
  inheritedFormatting: options?.inheritedFormatting,
1661
1668
  styleRPr: characterStyleRPr
1662
1669
  });
1670
+ for (const property of [
1671
+ "fontFamily",
1672
+ "fontSize",
1673
+ "color"
1674
+ ]) {
1675
+ if (directFontProperties?.includes(property)) continue;
1676
+ const value = formatting[property];
1677
+ const inheritedValue = options?.inheritedFormatting?.[property];
1678
+ const matchesInherited = property === "fontFamily" ? sameFontFamily(formatting.fontFamily, options?.inheritedFormatting?.fontFamily) : JSON.stringify(value) === JSON.stringify(inheritedValue);
1679
+ if (inheritedValue !== void 0 && matchesInherited) Reflect.deleteProperty(formatting, property);
1680
+ }
1663
1681
  return formatting;
1664
1682
  }
1683
+ const sameFontFamily = (left, right) => [
1684
+ "ascii",
1685
+ "hAnsi",
1686
+ "eastAsia",
1687
+ "hint",
1688
+ "asciiTheme",
1689
+ "hAnsiTheme",
1690
+ "eastAsiaTheme",
1691
+ "csTheme"
1692
+ ].every((property) => left?.[property] === right?.[property]) && (left?.cs ?? left?.ascii) === (right?.cs ?? right?.ascii);
1665
1693
  /**
1666
1694
  * Negatable boolean run-property keys whose serializer emits an explicit
1667
1695
  * `w:val="0"` override when the value is `false` (see `serializeTextFormatting`
@@ -1801,6 +1829,16 @@ function buildCellMarginsFromAttrs(m) {
1801
1829
  /**
1802
1830
  * Convert ProseMirror table attrs to TableFormatting
1803
1831
  */
1832
+ /**
1833
+ * Whether a live attr still holds what the style cascade resolved to.
1834
+ *
1835
+ * `toProseDoc` seeds an attr with the EFFECTIVE value — a border a table
1836
+ * style supplied, a margin a table declared — because that is what the editor
1837
+ * renders with. A save must write only what the node itself states, or the
1838
+ * inherited value becomes a direct override that outranks the style it came
1839
+ * from on reload. The resolved companion is what tells the two apart.
1840
+ */
1841
+ const sameResolvedValue = (live, resolved) => resolved !== void 0 && resolved !== null && canonicalJson(live) === canonicalJson(resolved);
1804
1842
  function tableAttrsToFormatting(attrs) {
1805
1843
  if (attrs._originalFormatting) {
1806
1844
  const orig = attrs._originalFormatting;
@@ -1824,7 +1862,7 @@ function tableAttrsToFormatting(attrs) {
1824
1862
  type: tableWidthType ?? "dxa"
1825
1863
  };
1826
1864
  else delete result.width;
1827
- if (attrs.cellMargins) result.cellMargins = buildCellMarginsFromAttrs(attrs.cellMargins);
1865
+ if (attrs.cellMargins && !sameResolvedValue(attrs.cellMargins, attrs._resolvedCellMargins)) result.cellMargins = buildCellMarginsFromAttrs(attrs.cellMargins);
1828
1866
  return result;
1829
1867
  }
1830
1868
  const tableWidth = attrs.width;
@@ -2022,8 +2060,8 @@ function tableCellAttrsToFormatting(attrs) {
2022
2060
  if (attrs.verticalAlign !== (orig.verticalAlign ?? void 0)) if (attrs.verticalAlign) result.verticalAlign = attrs.verticalAlign;
2023
2061
  else delete result.verticalAlign;
2024
2062
  if (backgroundChanged) result.shading = cellShadingFromAttrs(attrs);
2025
- if (attrs.borders) result.borders = attrs.borders;
2026
- if (attrs.margins) result.margins = buildCellMarginsFromAttrs(attrs.margins);
2063
+ if (attrs.borders && !sameResolvedValue(attrs.borders, attrs._resolvedBorders)) result.borders = attrs.borders;
2064
+ if (attrs.margins && !sameResolvedValue(attrs.margins, attrs._resolvedMargins)) result.margins = buildCellMarginsFromAttrs(attrs.margins);
2027
2065
  if (attrs.textDirection !== (orig.textDirection ?? void 0)) if (attrs.textDirection) result.textDirection = attrs.textDirection;
2028
2066
  else delete result.textDirection;
2029
2067
  return result;
@@ -2154,4 +2192,4 @@ function proseDocToBlocks(pmDoc) {
2154
2192
  return extractBlocks(pmDoc);
2155
2193
  }
2156
2194
  //#endregion
2157
- export { fromProseDoc, marksToTextFormatting, proseDocToBlocks, standaloneTableCellFromProseMirror, updateDocumentContent };
2195
+ export { fromProseDoc, marksToTextFormatting, proseDocToBlocks, standaloneTableCellFromProseMirror, tableAttrsToFormatting, tableCellAttrsToFormatting, tableRowAttrsToFormatting, updateDocumentContent };
@@ -18,6 +18,7 @@ import { shadingToRunShadingAttrs } from "./runShadingMark.js";
18
18
  import { sdtAttrsFromProperties } from "./sdtAttrs.js";
19
19
  import { panic } from "better-result";
20
20
  //#region src/prosemirror/conversion/toProseDoc.ts
21
+ const DETACHED_WATERMARK_HOST = Symbol.for("stll.detachedWatermarkHost");
21
22
  /**
22
23
  * Build a `nextTextBoxGroupId()` generator salted with a random per-load
23
24
  * nonce, so minted text-box anchor ids (`<salt>:<group>:<index>`) are unique
@@ -139,7 +140,12 @@ function toProseDoc(document, options) {
139
140
  };
140
141
  nodes.push(...convertBodyBlocks(paragraphs));
141
142
  if (nodes.length === 0) nodes.push(schema.node("paragraph", {}, []));
142
- const pmDoc = stampNumberedRefFieldBaselines(schema.node("doc", null, nodes));
143
+ const finalSectionStart = document.package.document.sections?.at(-1)?.properties.sectionStart ?? null;
144
+ const adjustLineHeightInTable = document.package.settings?.adjustLineHeightInTable === true;
145
+ const pmDoc = stampNumberedRefFieldBaselines(schema.node("doc", {
146
+ _finalSectionStart: finalSectionStart,
147
+ _adjustLineHeightInTable: adjustLineHeightInTable
148
+ }, nodes));
143
149
  assertValidProseMirrorDocument(pmDoc, "Document conversion produced an invalid ProseMirror document");
144
150
  return pmDoc;
145
151
  }
@@ -210,6 +216,7 @@ function convertParagraph(paragraph, styleResolver, nextHyperlinkInstanceIndex,
210
216
  const paragraphRunFormatting = resolveRunFormattingWithoutDefaults(paragraph.formatting?.runProperties, styleResolver);
211
217
  let inheritableParagraphRunFormatting;
212
218
  if (paragraphRunFormatting && !isTocParagraph) inheritableParagraphRunFormatting = stripParagraphMarkFormattingForBodyRuns(paragraphRunFormatting);
219
+ const ordinaryStyleFormatting = paragraph.formatting?.styleId === void 0 ? mergeTextFormatting(styleRunFormatting, extraRunFormatting) : mergeTextFormatting(extraRunFormatting, styleRunFormatting);
213
220
  const orderedToggleFormatting = cascadeStyleTextFormatting([
214
221
  {
215
222
  formatting: styleResolver?.getDocDefaults()?.rPr,
@@ -223,7 +230,7 @@ function convertParagraph(paragraph, styleResolver, nextHyperlinkInstanceIndex,
223
230
  formatting: paragraphStyleRunFormatting,
224
231
  type: "style"
225
232
  }
226
- ], { ordinaryFormatting: mergeTextFormatting(styleRunFormatting, extraRunFormatting) });
233
+ ], { ordinaryFormatting: ordinaryStyleFormatting });
227
234
  let baseRunFormatting = orderedToggleFormatting.formatting;
228
235
  if (paragraphStyleFontFamily) baseRunFormatting = mergeTextFormatting(baseRunFormatting, { fontFamily: paragraphStyleFontFamily });
229
236
  const paragraphMarkPrecedesStyle = paragraph.formatting?.styleId !== void 0;
@@ -850,7 +857,10 @@ function convertTable(table, styleResolver, context) {
850
857
  if (table.formatting?.justification) attrs.justification = table.formatting.justification;
851
858
  if (columnWidths) attrs.columnWidths = columnWidths;
852
859
  if (table.formatting?.floating) attrs.floating = table.formatting.floating;
853
- if (cellMarginsAttr) attrs.cellMargins = cellMarginsAttr;
860
+ if (cellMarginsAttr) {
861
+ attrs.cellMargins = cellMarginsAttr;
862
+ attrs._resolvedCellMargins = cellMarginsAttr;
863
+ }
854
864
  if (table.formatting?.look) attrs.look = table.formatting.look;
855
865
  if (table.formatting?.borders) attrs.borders = table.formatting.borders;
856
866
  if (resolvedTableIndent) attrs._resolvedIndent = resolvedTableIndent;
@@ -997,10 +1007,10 @@ function convertTableRow(row, styleResolver, context, isHeaderRow, columnWidths,
997
1007
  let cellConditionalStyle = conditionalStyles?.wholeTable;
998
1008
  cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, effectiveRowBandStyle);
999
1009
  cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, vertBandStyle);
1000
- if (cellIsFirstRow && (tableLook?.firstRow || rowCnf?.firstRow || cellCnf?.firstRow)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.firstRow);
1001
- if (cellIsLastRow && (tableLook?.lastRow || rowCnf?.lastRow || cellCnf?.lastRow)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.lastRow);
1002
1010
  if (cellIsFirstCol && (tableLook?.firstColumn || rowCnf?.firstColumn || cellCnf?.firstColumn)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.firstCol);
1003
1011
  if (cellIsLastCol && (tableLook?.lastColumn || rowCnf?.lastColumn || cellCnf?.lastColumn)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.lastCol);
1012
+ if (cellIsFirstRow && (tableLook?.firstRow || rowCnf?.firstRow || cellCnf?.firstRow)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.firstRow);
1013
+ if (cellIsLastRow && (tableLook?.lastRow || rowCnf?.lastRow || cellCnf?.lastRow)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.lastRow);
1004
1014
  if (cellIsFirstRow && cellIsFirstCol && (tableLook?.firstRow || rowCnf?.firstRow || cellCnf?.firstRow) && (tableLook?.firstColumn || rowCnf?.firstColumn || cellCnf?.firstColumn)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.nwCell);
1005
1015
  if (cellIsFirstRow && cellIsLastCol && (tableLook?.firstRow || rowCnf?.firstRow || cellCnf?.firstRow) && (tableLook?.lastColumn || rowCnf?.lastColumn || cellCnf?.lastColumn)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.neCell);
1006
1016
  if (cellIsLastRow && cellIsFirstCol && (tableLook?.lastRow || rowCnf?.lastRow || cellCnf?.lastRow) && (tableLook?.firstColumn || rowCnf?.firstColumn || cellCnf?.firstColumn)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.swCell);
@@ -1059,8 +1069,14 @@ function convertTableCell({ cell, styleResolver, context, isHeader, gridWidthPer
1059
1069
  if (formatting?.noWrap !== void 0) attrs.noWrap = formatting.noWrap;
1060
1070
  const hideMark = formatting?.hideMark ?? conditionalStyle?.tcPr?.hideMark;
1061
1071
  if (hideMark !== void 0) attrs.hideMark = hideMark;
1062
- if (effectiveFormatting.borders) attrs.borders = effectiveFormatting.borders;
1063
- if (effectiveFormatting.margins) attrs.margins = effectiveFormatting.margins;
1072
+ if (effectiveFormatting.borders) {
1073
+ attrs.borders = effectiveFormatting.borders;
1074
+ attrs._resolvedBorders = effectiveFormatting.borders;
1075
+ }
1076
+ if (effectiveFormatting.margins) {
1077
+ attrs.margins = effectiveFormatting.margins;
1078
+ attrs._resolvedMargins = effectiveFormatting.margins;
1079
+ }
1064
1080
  if (formatting) attrs._originalFormatting = formatting;
1065
1081
  if (cell.propertyChanges && cell.propertyChanges.length > 0) attrs.tcPrChange = [...cell.propertyChanges];
1066
1082
  if (cell.structuralChange?.type === "tableCellInsertion") attrs.cellMarker = {
@@ -1256,6 +1272,7 @@ function buildRunMarks(runFormatting, inherited, styleResolver) {
1256
1272
  hasCharacterStyle: styleId !== void 0,
1257
1273
  paragraphMarkOverrides: inherited.paragraphMarkOverrides
1258
1274
  }) });
1275
+ addDirectFontProvenance(marks, runFormatting);
1259
1276
  if (styleId) {
1260
1277
  const styleRPr = characterStyleFormatting ? marksToTextFormatting(textFormattingToMarks(characterStyleFormatting)) : void 0;
1261
1278
  marks.push(schema.mark("characterStyle", {
@@ -1268,6 +1285,24 @@ function buildRunMarks(runFormatting, inherited, styleResolver) {
1268
1285
  mergedFormatting
1269
1286
  };
1270
1287
  }
1288
+ const addDirectFontProvenance = (marks, directFormatting) => {
1289
+ const directFontProperties = [];
1290
+ if (directFormatting?.fontFamily !== void 0) directFontProperties.push("fontFamily");
1291
+ if (directFormatting?.fontSize !== void 0) directFontProperties.push("fontSize");
1292
+ if (directFormatting?.color !== void 0) directFontProperties.push("color");
1293
+ if (!directFormatting || !Object.keys(directFormatting).some((property) => property !== "styleId")) return;
1294
+ const index = marks.findIndex(({ type }) => type.name === "runFormattingOverride");
1295
+ const existing = index >= 0 ? marks.at(index) : void 0;
1296
+ const override = schema.mark("runFormattingOverride", {
1297
+ ...existing?.attrs,
1298
+ ...directFontProperties.length > 0 && { directFontProperties }
1299
+ });
1300
+ if (index >= 0) {
1301
+ marks[index] = override;
1302
+ return;
1303
+ }
1304
+ marks.push(override);
1305
+ };
1271
1306
  const ORDINARY_STYLE_TOGGLE_KEYS = [
1272
1307
  "bold",
1273
1308
  "italic",
@@ -1649,12 +1684,12 @@ function convertShape(shape) {
1649
1684
  position
1650
1685
  });
1651
1686
  }
1652
- function convertParagraphWithTextBoxes(block, styleResolver, { textBoxGroupId, context, extraRunFormatting, tableParagraphOverlay }) {
1687
+ function convertParagraphWithTextBoxes(block, styleResolver, { textBoxGroupId, context, extraRunFormatting, preserveEmptyWrapper, tableParagraphOverlay }) {
1653
1688
  const { textBoxes, textBoxAnchors } = extractTextBoxesFromParagraph(block, textBoxGroupId);
1654
1689
  const pmParagraph = convertParagraph(block, styleResolver, context.nextHyperlinkInstanceIndex, context.pairedBookmarkIds, void 0, extraRunFormatting, tableParagraphOverlay, textBoxAnchors);
1655
1690
  const nodes = [];
1656
1691
  const isEmptyAfterExtraction = textBoxes.length > 0 && !hasContentBesidesTextBoxAnchors(pmParagraph);
1657
- const keepWrapperParagraph = isEmptyAfterExtraction && hasParagraphBoundaryPayload(block, pmParagraph);
1692
+ const keepWrapperParagraph = isEmptyAfterExtraction && (preserveEmptyWrapper === true || hasParagraphBoundaryPayload(block, pmParagraph));
1658
1693
  if (!isEmptyAfterExtraction || keepWrapperParagraph) nodes.push(pmParagraph);
1659
1694
  for (const { textBox, anchorId, trackedChange, inlineSdts } of textBoxes) nodes.push(convertTextBox(textBox, styleResolver, {
1660
1695
  placement: isEmptyAfterExtraction && !keepWrapperParagraph ? "standalone" : "inlineWithPrevious",
@@ -1920,11 +1955,23 @@ function headerFooterToProseDoc(content, options) {
1920
1955
  };
1921
1956
  const convertBlocks = (blocks) => {
1922
1957
  const out = [];
1923
- for (const block of blocks) if (block.type === "paragraph") out.push(...convertParagraphWithTextBoxes(block, styleResolver, {
1924
- textBoxGroupId: nextTextBoxGroupId(),
1925
- context: conversionContext
1926
- }));
1927
- else if (block.type === "table") out.push(convertTable(block, styleResolver, conversionContext));
1958
+ for (const block of blocks) if (block.type === "paragraph") {
1959
+ const isDetachedWatermarkHost = Reflect.get(block, DETACHED_WATERMARK_HOST) === true;
1960
+ const paragraphNodes = convertParagraphWithTextBoxes(block, styleResolver, {
1961
+ textBoxGroupId: nextTextBoxGroupId(),
1962
+ context: conversionContext,
1963
+ preserveEmptyWrapper: isDetachedWatermarkHost
1964
+ });
1965
+ if (isDetachedWatermarkHost) {
1966
+ const paragraphNodeIndex = paragraphNodes.findIndex(({ type }) => type.name === "paragraph");
1967
+ const paragraphNode = paragraphNodes[paragraphNodeIndex];
1968
+ if (paragraphNode) paragraphNodes[paragraphNodeIndex] = paragraphNode.type.create({
1969
+ ...paragraphNode.attrs,
1970
+ _detachedWatermarkHost: true
1971
+ }, paragraphNode.content, paragraphNode.marks);
1972
+ }
1973
+ out.push(...paragraphNodes);
1974
+ } else if (block.type === "table") out.push(convertTable(block, styleResolver, conversionContext));
1928
1975
  else out.push(convertBlockSdt(block, convertBlocks));
1929
1976
  return out;
1930
1977
  };
@@ -6,7 +6,13 @@ import { createNodeExtension } from "../create.js";
6
6
  const DocExtension = createNodeExtension({
7
7
  name: "doc",
8
8
  schemaNodeName: "doc",
9
- nodeSpec: { content: "(paragraph | horizontalRule | pageBreak | table | textBox | blockSdt)+" }
9
+ nodeSpec: {
10
+ attrs: {
11
+ _finalSectionStart: { default: null },
12
+ _adjustLineHeightInTable: { default: false }
13
+ },
14
+ content: "(paragraph | horizontalRule | pageBreak | table | textBox | blockSdt)+"
15
+ }
10
16
  });
11
17
  //#endregion
12
18
  export { DocExtension };
@@ -223,6 +223,7 @@ const paragraphNodeSpec = {
223
223
  pageBreakBefore: { default: null },
224
224
  renderedPageBreakBefore: { default: null },
225
225
  _pageBreakCarrier: { default: null },
226
+ _detachedWatermarkHost: { default: null },
226
227
  _trailingPageBreak: { default: null },
227
228
  keepNext: { default: null },
228
229
  keepLines: { default: null },
@@ -50,6 +50,7 @@ const RunFormattingOverrideExtension = createMarkExtension({
50
50
  schemaMarkName: "runFormattingOverride",
51
51
  markSpec: {
52
52
  attrs: {
53
+ directFontProperties: { default: null },
53
54
  bold: { default: null },
54
55
  italic: { default: null },
55
56
  underline: { default: null },
@@ -151,6 +151,7 @@ const tableSpec = {
151
151
  columnWidths: { default: null },
152
152
  floating: { default: null },
153
153
  cellMargins: { default: null },
154
+ _resolvedCellMargins: { default: null },
154
155
  look: { default: null },
155
156
  borders: { default: null },
156
157
  _resolvedIndent: { default: null },
@@ -319,7 +320,9 @@ const tableCellSpec = {
319
320
  backgroundColor: { default: null },
320
321
  _resolvedBackgroundColor: { default: null },
321
322
  borders: { default: null },
323
+ _resolvedBorders: { default: null },
322
324
  margins: { default: null },
325
+ _resolvedMargins: { default: null },
323
326
  textDirection: { default: null },
324
327
  noWrap: { default: false },
325
328
  hideMark: { default: false },
@@ -375,7 +378,9 @@ const tableHeaderSpec = {
375
378
  backgroundColor: { default: null },
376
379
  _resolvedBackgroundColor: { default: null },
377
380
  borders: { default: null },
381
+ _resolvedBorders: { default: null },
378
382
  margins: { default: null },
383
+ _resolvedMargins: { default: null },
379
384
  textDirection: { default: null },
380
385
  noWrap: { default: false },
381
386
  hideMark: { default: false },
@@ -1,5 +1,5 @@
1
- import { document_d_exports } from "../../types/document.js";
2
1
  import { ShadingProperties } from "../../types/colors.js";
2
+ import { document_d_exports } from "../../types/document.js";
3
3
  //#region src/prosemirror/schema/marks.d.ts
4
4
  /**
5
5
  * Text color mark attributes
@@ -121,17 +121,11 @@ type RunPropertyChangeMarkAttrs = {
121
121
  provenance: TrackedChangeProvenance;
122
122
  suggestionId?: string;
123
123
  };
124
- type RunFormattingOverrideAttrs = { [K in keyof Pick<document_d_exports.TextFormatting, "bold" | "italic" | "strike" | "allCaps" | "smallCaps" | "hidden" | "emboss" | "imprint" | "shadow" | "outline">]?: boolean; } & {
124
+ type RunFormattingOverrideAttrs = Partial<Record<"bold" | "boldCs" | "cs" | "italic" | "italicCs" | "strike" | "allCaps" | "smallCaps" | "hidden" | "emboss" | "imprint" | "shadow" | "outline", boolean>> & {
125
+ directFontProperties?: readonly ("fontFamily" | "fontSize" | "color")[];
125
126
  doubleStrike?: false;
126
127
  rtl?: false;
127
- /** Independent complex-script weight (`w:bCs`). */
128
- boldCs?: boolean;
129
- /** Force complex-script formatting for the full run (`w:cs`). */
130
- cs?: boolean;
131
- /** Independent complex-script size in half-points (`w:szCs`). */
132
128
  fontSizeCs?: number;
133
- /** Independent complex-script slant (`w:iCs`). */
134
- italicCs?: boolean;
135
129
  underline?: "none";
136
130
  };
137
131
  /**
@@ -569,6 +569,13 @@ type TableAttrs = {
569
569
  look?: document_d_exports.TableLook;
570
570
  /** Table-level borders (w:tblBorders) — full BorderSpec per side */
571
571
  borders?: document_d_exports.TableBorders;
572
+ /**
573
+ * Effective default cell margins after the table-style cascade. PM-only;
574
+ * never serialized. What `cellMargins` resolves to when the table declares
575
+ * none of its own, so a save can tell a value a style supplied from one the
576
+ * table states — and stop writing the style's into the table's `w:tblPr`.
577
+ */
578
+ _resolvedCellMargins?: TableAttrs["cellMargins"];
572
579
  /** Effective table indent after style resolution. PM-only; never serialized. */
573
580
  _resolvedIndent?: NonNullable<document_d_exports.TableFormatting["indent"]>;
574
581
  /** Style-derived table justification fallback. PM-only; never serialized. */
@@ -677,6 +684,14 @@ type TableCellAttrs = {
677
684
  hideMark?: boolean;
678
685
  /** Cell borders — full BorderSpec per side (style, color, size) */
679
686
  borders?: document_d_exports.TableCellBorders;
687
+ /**
688
+ * Effective borders after the table and table-style cascade. PM-only; never
689
+ * serialized. The companion to `borders` that `_resolvedBackgroundColor` is
690
+ * to `backgroundColor`: what the cell renders with when it declares none of
691
+ * its own, so a save can tell a border a style supplied from one the cell
692
+ * states.
693
+ */
694
+ _resolvedBorders?: document_d_exports.TableCellBorders;
680
695
  /** Cell margins/padding in twips per side */
681
696
  margins?: {
682
697
  top?: number;
@@ -684,6 +699,13 @@ type TableCellAttrs = {
684
699
  left?: number;
685
700
  right?: number;
686
701
  };
702
+ /** Effective margins after the cascade. PM-only; never serialized. */
703
+ _resolvedMargins?: {
704
+ top?: number;
705
+ bottom?: number;
706
+ left?: number;
707
+ right?: number;
708
+ };
687
709
  /** Original cell formatting from DOCX for lossless round-trip serialization */
688
710
  _originalFormatting?: document_d_exports.TableCellFormatting;
689
711
  /** Tracked cell property changes (w:tcPrChange) for round-trip + accept/reject */