@stll/folio-core 0.9.0 → 0.10.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 (44) hide show
  1. package/dist/ai-edits/headless.d.ts +52 -12
  2. package/dist/ai-edits/headless.js +181 -55
  3. package/dist/ai-edits/index.d.ts +2 -2
  4. package/dist/ai-edits/index.js +2 -2
  5. package/dist/controller/layoutPipeline.js +2 -1
  6. package/dist/document-operations.d.ts +11 -2
  7. package/dist/document-operations.js +7 -1
  8. package/dist/document-stories.d.ts +10 -0
  9. package/dist/document-stories.js +27 -0
  10. package/dist/docx/corePropertiesParser.d.ts +8 -0
  11. package/dist/docx/corePropertiesParser.js +53 -0
  12. package/dist/docx/index.d.ts +2 -1
  13. package/dist/docx/index.js +2 -1
  14. package/dist/docx/metadataPrivacy.d.ts +40 -0
  15. package/dist/docx/metadataPrivacy.js +131 -0
  16. package/dist/docx/parser.js +4 -1
  17. package/dist/docx/rezip.d.ts +5 -4
  18. package/dist/docx/rezip.js +6 -8
  19. package/dist/layout-bridge/convert/toFlowBlocks.js +1 -0
  20. package/dist/layout-engine/measure/cache.js +2 -7
  21. package/dist/layout-engine/measure/effectiveLineBreakPolicy.d.ts +29 -0
  22. package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +60 -0
  23. package/dist/layout-engine/measure/measureParagraph.js +32 -36
  24. package/dist/layout-engine/measure/tableCellFloating.js +39 -33
  25. package/dist/layout-engine/types.d.ts +5 -3
  26. package/dist/prosemirror/attrs/index.js +1 -0
  27. package/dist/prosemirror/conversion/effectiveTableCellFormatting.d.ts +62 -0
  28. package/dist/prosemirror/conversion/effectiveTableCellFormatting.js +131 -0
  29. package/dist/prosemirror/conversion/fromProseDoc.js +1 -0
  30. package/dist/prosemirror/conversion/toProseDoc.js +40 -68
  31. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -1
  32. package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
  33. package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
  34. package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
  35. package/dist/prosemirror/extensions/nodes/ImageExtension.js +1 -0
  36. package/dist/prosemirror/extensions/nodes/TableExtension.js +1 -1
  37. package/dist/prosemirror/schema/nodes.d.ts +2 -1
  38. package/dist/redline.d.ts +26 -11
  39. package/dist/redline.js +95 -62
  40. package/dist/server.d.ts +5 -4
  41. package/dist/server.js +5 -4
  42. package/dist/version-comparison.d.ts +65 -11
  43. package/dist/version-comparison.js +187 -29
  44. package/package.json +1 -1
@@ -3,6 +3,36 @@ import { isFloatingImageRun } from "../types.js";
3
3
  import { clampFloatingWrapMargins } from "./clampFloatingWrapMargins.js";
4
4
  import { createTableCellFlowState, placeTableCellBlock } from "./tableCellFlow.js";
5
5
  //#region src/layout-engine/measure/tableCellFloating.ts
6
+ const resolveCellScopedPosition = ({ run, paragraphY, contentWidth }) => {
7
+ const position = run.position;
8
+ let side = "left";
9
+ let x = 0;
10
+ if (position?.horizontal) {
11
+ const horizontal = position.horizontal;
12
+ if (horizontal.align === "right") {
13
+ side = "right";
14
+ x = contentWidth - run.width;
15
+ } else if (horizontal.align === "center") x = (contentWidth - run.width) / 2;
16
+ else if (horizontal.posOffset !== void 0) {
17
+ x = emuToPixels(horizontal.posOffset);
18
+ side = x > contentWidth / 2 ? "right" : "left";
19
+ }
20
+ } else if (run.cssFloat === "right") {
21
+ side = "right";
22
+ x = contentWidth - run.width;
23
+ }
24
+ let y = paragraphY;
25
+ if (position?.vertical) {
26
+ const vertical = position.vertical;
27
+ if (vertical.posOffset !== void 0) y = paragraphY + emuToPixels(vertical.posOffset);
28
+ else if (vertical.align === "top") y = 0;
29
+ }
30
+ return {
31
+ side,
32
+ x,
33
+ y
34
+ };
35
+ };
6
36
  function getTableCellContentWidth(cell, cellMeasure) {
7
37
  const padLeft = cell?.padding?.left ?? 7;
8
38
  const padRight = cell?.padding?.right ?? 7;
@@ -19,40 +49,16 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth, resolvePosi
19
49
  if (block.kind !== "paragraph") continue;
20
50
  for (const run of block.runs) {
21
51
  if (run.kind !== "image" || !isFloatingImageRun(run)) continue;
22
- const position = run.position;
23
52
  const distTop = run.distTop ?? 0;
24
53
  const distBottom = run.distBottom ?? 0;
25
54
  const distLeft = run.distLeft ?? 12;
26
55
  const distRight = run.distRight ?? 12;
27
- let side;
28
- let x;
29
- let y;
30
- if (resolvePosition) ({side, x, y} = resolvePosition(run, placement.contentTop));
31
- else {
32
- side = "left";
33
- x = 0;
34
- if (position?.horizontal) {
35
- const horizontal = position.horizontal;
36
- if (horizontal.align === "right") {
37
- side = "right";
38
- x = contentWidth - run.width;
39
- } else if (horizontal.align === "center") x = (contentWidth - run.width) / 2;
40
- else if (horizontal.posOffset !== void 0) {
41
- x = emuToPixels(horizontal.posOffset);
42
- side = x > contentWidth / 2 ? "right" : "left";
43
- }
44
- } else if (run.cssFloat === "right") {
45
- side = "right";
46
- x = contentWidth - run.width;
47
- }
48
- y = placement.contentTop;
49
- if (position?.vertical) {
50
- const vertical = position.vertical;
51
- if (vertical.posOffset !== void 0) y = placement.contentTop + emuToPixels(vertical.posOffset);
52
- else if (vertical.align === "top") y = 0;
53
- }
54
- x = Math.max(0, Math.min(x, contentWidth - run.width));
55
- }
56
+ const verticalOriginY = run.position?.vertical?.relativeTo === "paragraph" ? placement.top : placement.contentTop;
57
+ const resolved = run.layoutInCell === false && resolvePosition ? resolvePosition(run, verticalOriginY) : resolveCellScopedPosition({
58
+ run,
59
+ paragraphY: verticalOriginY,
60
+ contentWidth
61
+ });
56
62
  let wrapText = "bothSides";
57
63
  if (run.cssFloat === "left") wrapText = "right";
58
64
  else if (run.cssFloat === "right") wrapText = "left";
@@ -67,9 +73,9 @@ function getTableCellFloatingImages(cell, cellMeasure, contentWidth, resolvePosi
67
73
  ...run.cropRight != null ? { cropRight: run.cropRight } : {},
68
74
  ...run.cropBottom != null ? { cropBottom: run.cropBottom } : {},
69
75
  ...run.cropLeft != null ? { cropLeft: run.cropLeft } : {},
70
- x,
71
- y,
72
- side,
76
+ x: resolved.x,
77
+ y: resolved.y,
78
+ side: resolved.side,
73
79
  distTop,
74
80
  distBottom,
75
81
  distLeft,
@@ -162,7 +162,8 @@ type ImageRun = {
162
162
  * eigenpal #424 (opacity render pipeline).
163
163
  */
164
164
  opacity?: number; /** Position for floating/anchored images */
165
- position?: ImageRunPosition; /** Wrap type from DOCX (inline, square, tight, through, topAndBottom, etc.) */
165
+ position?: ImageRunPosition; /** Whether a table-cell anchor uses the cell as its positioning scope. Undefined defaults true. */
166
+ layoutInCell?: boolean; /** Wrap type from DOCX (inline, square, tight, through, topAndBottom, etc.) */
166
167
  wrapType?: ImageWrap["type"]; /** Display mode for CSS rendering */
167
168
  displayMode?: "inline" | "block" | "float"; /** CSS float direction */
168
169
  cssFloat?: "left" | "right" | "none"; /** Wrap distances in pixels */
@@ -293,13 +294,14 @@ type ListNumPr = {
293
294
  */
294
295
  type ParagraphAttrs = {
295
296
  alignment?: "left" | "center" | "right" | "justify"; /** East Asian first/last-character restrictions (`w:kinsoku`). */
296
- kinsoku?: boolean; /** Hanging punctuation (`w:overflowPunct`), retained for layout policy. */
297
+ kinsoku?: boolean; /** Hanging punctuation (`w:overflowPunct`); defaults to enabled when omitted. */
297
298
  overflowPunctuation?: boolean; /** Exempt this paragraph from document automatic hyphenation. */
298
299
  suppressAutoHyphens?: boolean; /** Document-wide automatic hyphenation controls retained for line layout. */
299
300
  automaticHyphenation?: {
300
301
  enabled: true;
301
302
  doNotHyphenateCaps?: boolean;
302
- consecutiveLineLimit?: number;
303
+ consecutiveLineLimit?: number; /** Maximum tolerated line-end whitespace before hyphenation, in twips. */
304
+ hyphenationZoneTwips?: number;
303
305
  }; /** Document-wide custom line-edge characters and compatibility behavior. */
304
306
  lineBreakRules?: {
305
307
  noLineBreaksBefore?: {
@@ -306,6 +306,7 @@ const readImageAttrs = (node) => {
306
306
  optionalNumber(attrs, "distLeft", "image.attrs.distLeft", issues);
307
307
  optionalNumber(attrs, "distRight", "image.attrs.distRight", issues);
308
308
  optionalImagePosition(attrs, "position", "image.attrs.position", issues);
309
+ optionalBoolean(attrs, "layoutInCell", "image.attrs.layoutInCell", issues);
309
310
  optionalNumber(attrs, "borderWidth", "image.attrs.borderWidth", issues);
310
311
  optionalString(attrs, "borderColor", "image.attrs.borderColor", issues);
311
312
  optionalString(attrs, "borderStyle", "image.attrs.borderStyle", issues);
@@ -0,0 +1,62 @@
1
+ import { document_d_exports } from "../../types/document.js";
2
+
3
+ //#region src/prosemirror/conversion/effectiveTableCellFormatting.d.ts
4
+ type TableCellMarginsAttrs = {
5
+ top?: number;
6
+ bottom?: number;
7
+ left?: number;
8
+ right?: number;
9
+ };
10
+ type EffectiveTableCellWidth = {
11
+ type: "none";
12
+ } | {
13
+ type: "value";
14
+ source: "direct" | "grid";
15
+ value: number;
16
+ widthType: document_d_exports.TableWidthType;
17
+ };
18
+ type EffectiveTableCellBackground = {
19
+ type: "none";
20
+ source: "none" | "direct" | "style";
21
+ } | {
22
+ type: "color";
23
+ source: "direct" | "style";
24
+ rgb: string;
25
+ };
26
+ type EffectiveTableCellFormatting = Readonly<{
27
+ width: EffectiveTableCellWidth;
28
+ background: EffectiveTableCellBackground;
29
+ borders: document_d_exports.TableCellBorders | undefined;
30
+ margins: TableCellMarginsAttrs | undefined;
31
+ }>;
32
+ type TableCellPosition = {
33
+ isFirstRow?: boolean;
34
+ isLastRow?: boolean;
35
+ isFirstColumn?: boolean;
36
+ isLastColumn?: boolean;
37
+ };
38
+ type ResolveEffectiveTableCellFormattingOptions = {
39
+ directFormatting: document_d_exports.TableCellFormatting | undefined;
40
+ styleFormatting: document_d_exports.TableCellFormatting | undefined;
41
+ tableBorders: document_d_exports.TableBorders | undefined;
42
+ position: TableCellPosition;
43
+ gridWidthPercent: number | undefined;
44
+ defaultMargins: TableCellMarginsAttrs | undefined;
45
+ theme: document_d_exports.Theme | null | undefined;
46
+ };
47
+ /**
48
+ * Resolve the display-facing table-cell cascade without mutating authored
49
+ * OOXML formatting. Source discriminators keep inherited values distinct from
50
+ * direct values that the editor may later override.
51
+ */
52
+ declare const resolveEffectiveTableCellFormatting: ({
53
+ directFormatting,
54
+ styleFormatting,
55
+ tableBorders,
56
+ position,
57
+ gridWidthPercent,
58
+ defaultMargins,
59
+ theme
60
+ }: ResolveEffectiveTableCellFormattingOptions) => EffectiveTableCellFormatting;
61
+ //#endregion
62
+ export { TableCellMarginsAttrs, TableCellPosition, resolveEffectiveTableCellFormatting };
@@ -0,0 +1,131 @@
1
+ import { resolveColor } from "../../utils/colorResolver.js";
2
+ import { resolveShadingFill } from "../../utils/formatToStyle.js";
3
+ //#region src/prosemirror/conversion/effectiveTableCellFormatting.ts
4
+ const TABLE_BORDER_SIDES = [
5
+ "top",
6
+ "bottom",
7
+ "left",
8
+ "right",
9
+ "insideH",
10
+ "insideV",
11
+ "topLeftToBottomRight",
12
+ "topRightToBottomLeft"
13
+ ];
14
+ /**
15
+ * Resolve the display-facing table-cell cascade without mutating authored
16
+ * OOXML formatting. Source discriminators keep inherited values distinct from
17
+ * direct values that the editor may later override.
18
+ */
19
+ const resolveEffectiveTableCellFormatting = ({ directFormatting, styleFormatting, tableBorders, position, gridWidthPercent, defaultMargins, theme }) => {
20
+ return {
21
+ width: resolveEffectiveWidth(directFormatting, gridWidthPercent),
22
+ background: resolveEffectiveBackground({
23
+ directFormatting,
24
+ styleFormatting,
25
+ theme
26
+ }),
27
+ borders: resolveEffectiveBorders({
28
+ directFormatting,
29
+ styleFormatting,
30
+ tableBorders,
31
+ position,
32
+ theme
33
+ }),
34
+ margins: resolveEffectiveMargins({
35
+ directFormatting,
36
+ styleFormatting,
37
+ defaultMargins
38
+ })
39
+ };
40
+ };
41
+ const resolveEffectiveWidth = (directFormatting, gridWidthPercent) => {
42
+ const directWidth = directFormatting?.width;
43
+ if (directWidth) return {
44
+ type: "value",
45
+ source: "direct",
46
+ value: directWidth.value,
47
+ widthType: directWidth.type
48
+ };
49
+ if (gridWidthPercent !== void 0) return {
50
+ type: "value",
51
+ source: "grid",
52
+ value: gridWidthPercent,
53
+ widthType: "pct"
54
+ };
55
+ return { type: "none" };
56
+ };
57
+ const resolveEffectiveBackground = ({ directFormatting, styleFormatting, theme }) => {
58
+ const directShading = directFormatting?.shading;
59
+ if (directShading !== void 0) return resolveBackgroundColor({
60
+ shading: directShading,
61
+ source: "direct",
62
+ theme
63
+ });
64
+ const styleShading = styleFormatting?.shading;
65
+ if (styleShading !== void 0) return resolveBackgroundColor({
66
+ shading: styleShading,
67
+ source: "style",
68
+ theme
69
+ });
70
+ return {
71
+ type: "none",
72
+ source: "none"
73
+ };
74
+ };
75
+ const resolveBackgroundColor = ({ shading, source, theme }) => {
76
+ const rgb = resolveShadingFill(shading, theme).replace(/^#/u, "");
77
+ if (!rgb) return {
78
+ type: "none",
79
+ source
80
+ };
81
+ return {
82
+ type: "color",
83
+ source,
84
+ rgb
85
+ };
86
+ };
87
+ const resolveEffectiveBorders = ({ directFormatting, styleFormatting, tableBorders, position, theme }) => {
88
+ const baseBorders = tableBorders ? {
89
+ top: position.isFirstRow ? tableBorders.top : tableBorders.insideH,
90
+ bottom: position.isLastRow ? tableBorders.bottom : tableBorders.insideH,
91
+ left: position.isFirstColumn ? tableBorders.left : tableBorders.insideV,
92
+ right: position.isLastColumn ? tableBorders.right : tableBorders.insideV
93
+ } : void 0;
94
+ const styleBorders = styleFormatting?.borders;
95
+ const directBorders = directFormatting?.borders;
96
+ const borders = baseBorders || styleBorders || directBorders ? {
97
+ ...baseBorders,
98
+ ...styleBorders,
99
+ ...directBorders
100
+ } : void 0;
101
+ return resolveThemedBorderColors(borders, theme);
102
+ };
103
+ const resolveEffectiveMargins = ({ directFormatting, styleFormatting, defaultMargins }) => {
104
+ if (directFormatting?.margins) return cellMarginsToAttrs(directFormatting.margins);
105
+ if (styleFormatting?.margins) return cellMarginsToAttrs(styleFormatting.margins);
106
+ return defaultMargins;
107
+ };
108
+ const cellMarginsToAttrs = (margins) => {
109
+ const result = {};
110
+ if (margins.top?.value !== void 0) result.top = margins.top.value;
111
+ if (margins.bottom?.value !== void 0) result.bottom = margins.bottom.value;
112
+ if (margins.left?.value !== void 0) result.left = margins.left.value;
113
+ if (margins.right?.value !== void 0) result.right = margins.right.value;
114
+ return result;
115
+ };
116
+ const resolveThemedBorderColors = (borders, theme) => {
117
+ if (!borders || !theme?.colorScheme) return borders;
118
+ let resolved;
119
+ for (const side of TABLE_BORDER_SIDES) {
120
+ const border = borders[side];
121
+ if (!border?.color?.themeColor || border.color.auto) continue;
122
+ resolved ??= { ...borders };
123
+ resolved[side] = {
124
+ ...border,
125
+ color: { rgb: resolveColor(border.color, theme).replace(/^#/u, "") }
126
+ };
127
+ }
128
+ return resolved ?? borders;
129
+ };
130
+ //#endregion
131
+ export { resolveEffectiveTableCellFormatting };
@@ -911,6 +911,7 @@ function createImageRun(node) {
911
911
  if (attrs.opacity != null) image.opacity = attrs.opacity;
912
912
  const imagePosition = imagePositionFromAttrs(attrs.position);
913
913
  if (imagePosition) image.position = imagePosition;
914
+ if (attrs.layoutInCell !== void 0) image.layoutInCell = attrs.layoutInCell;
914
915
  if (attrs.borderWidth && attrs.borderWidth > 0) {
915
916
  const outline = {
916
917
  width: pixelsToEmu(attrs.borderWidth),
@@ -7,10 +7,9 @@ import { assertValidProseMirrorDocument } from "../validation.js";
7
7
  import { shadingToRunShadingAttrs } from "./runShadingMark.js";
8
8
  import { marksToTextFormatting } from "./fromProseDoc.js";
9
9
  import { createStyleEngine } from "../../style-engine/styleEngine.js";
10
- import { resolveColor } from "../../utils/colorResolver.js";
11
- import { resolveShadingFill } from "../../utils/formatToStyle.js";
12
10
  import { buildRunFormattingOverrideAttrs } from "../extensions/marks/RunFormattingOverrideExtension.js";
13
11
  import { schema } from "../schema/index.js";
12
+ import { resolveEffectiveTableCellFormatting } from "./effectiveTableCellFormatting.js";
14
13
  //#region src/prosemirror/conversion/toProseDoc.ts
15
14
  const TOC_STYLE_ID = /^TOC\d*$/iu;
16
15
  /**
@@ -725,88 +724,60 @@ function convertTableRow(row, styleResolver, context, isHeaderRow, columnWidths,
725
724
  if (cellIsFirstRow && cellIsLastCol && (tableLook?.firstRow || rowCnf?.firstRow || cellCnf?.firstRow) && (tableLook?.lastColumn || rowCnf?.lastColumn || cellCnf?.lastColumn)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.neCell);
726
725
  if (cellIsLastRow && cellIsFirstCol && (tableLook?.lastRow || rowCnf?.lastRow || cellCnf?.lastRow) && (tableLook?.firstColumn || rowCnf?.firstColumn || cellCnf?.firstColumn)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.swCell);
727
726
  if (cellIsLastRow && cellIsLastCol && (tableLook?.lastRow || rowCnf?.lastRow || cellCnf?.lastRow) && (tableLook?.lastColumn || rowCnf?.lastColumn || cellCnf?.lastColumn)) cellConditionalStyle = mergeConditionalStyles(cellConditionalStyle, conditionalStyles?.seCell);
728
- cells.push(convertTableCell(cell, styleResolver, context, isHeaderRow, gridWidth, cellConditionalStyle, tableBorders, isFirstRow, isLastRow, isFirstCol, isLastCol, calculatedRowSpan, preserveVMergeRestart, rowSpanInfo?.continuationCells, defaultCellMargins));
727
+ cells.push(convertTableCell({
728
+ cell,
729
+ styleResolver,
730
+ context,
731
+ isHeader: isHeaderRow,
732
+ gridWidthPercent: gridWidth,
733
+ conditionalStyle: cellConditionalStyle,
734
+ tableBorders,
735
+ position: {
736
+ isFirstRow,
737
+ isLastRow,
738
+ isFirstColumn: isFirstCol,
739
+ isLastColumn: isLastCol
740
+ },
741
+ calculatedRowSpan,
742
+ preserveVMergeRestart,
743
+ vMergeContinuationCells: rowSpanInfo?.continuationCells,
744
+ defaultCellMargins
745
+ }));
729
746
  }
730
747
  return schema.node("tableRow", attrs, cells);
731
748
  }
732
- const TABLE_BORDER_SIDES = [
733
- "top",
734
- "bottom",
735
- "left",
736
- "right",
737
- "insideH",
738
- "insideV",
739
- "topLeftToBottomRight",
740
- "topRightToBottomLeft"
741
- ];
742
- function resolveThemedBorderColors(borders, theme) {
743
- if (!borders || !theme?.colorScheme) return borders;
744
- let resolved;
745
- for (const side of TABLE_BORDER_SIDES) {
746
- const border = borders[side];
747
- if (!border?.color?.themeColor || border.color.auto) continue;
748
- resolved ??= { ...borders };
749
- resolved[side] = {
750
- ...border,
751
- color: { rgb: resolveColor(border.color, theme).replace(/^#/u, "") }
752
- };
753
- }
754
- return resolved ?? borders;
755
- }
756
749
  /**
757
750
  * Convert a TableCell to a ProseMirror table cell node
758
751
  */
759
- function convertTableCell(cell, styleResolver, context, isHeader, gridWidthPercent, conditionalStyle, tableBorders, isFirstRow, isLastRow, isFirstCol, isLastCol, calculatedRowSpan, preserveVMergeRestart, vMergeContinuationCells, defaultCellMargins) {
760
- const { theme } = context;
752
+ function convertTableCell({ cell, styleResolver, context, isHeader, gridWidthPercent, conditionalStyle, tableBorders, position, calculatedRowSpan, preserveVMergeRestart, vMergeContinuationCells, defaultCellMargins }) {
761
753
  const formatting = cell.formatting;
762
754
  const rowspan = calculatedRowSpan ?? 1;
763
- let width = formatting?.width?.value;
764
- let widthType = formatting?.width?.type;
765
- if (width === void 0 && gridWidthPercent !== void 0) {
766
- width = gridWidthPercent;
767
- widthType = "pct";
768
- }
769
- const backgroundColor = resolveShadingFill(formatting?.shading ?? conditionalStyle?.tcPr?.shading, theme).replace(/^#/u, "");
770
- const baseBorders = (() => {
771
- if (tableBorders) return {
772
- top: isFirstRow ? tableBorders.top : tableBorders.insideH,
773
- bottom: isLastRow ? tableBorders.bottom : tableBorders.insideH,
774
- left: isFirstCol ? tableBorders.left : tableBorders.insideV,
775
- right: isLastCol ? tableBorders.right : tableBorders.insideV
776
- };
777
- })();
778
- const conditionalBorders = conditionalStyle?.tcPr?.borders;
779
- const cellBorders = formatting?.borders;
780
- const borders = resolveThemedBorderColors(baseBorders || conditionalBorders || cellBorders ? {
781
- ...baseBorders,
782
- ...conditionalBorders,
783
- ...cellBorders
784
- } : void 0, theme);
785
- const buildMarginsAttr = (src) => {
786
- const m = {};
787
- if (src.top?.value !== void 0) m.top = src.top.value;
788
- if (src.bottom?.value !== void 0) m.bottom = src.bottom.value;
789
- if (src.left?.value !== void 0) m.left = src.left.value;
790
- if (src.right?.value !== void 0) m.right = src.right.value;
791
- return m;
792
- };
755
+ const effectiveFormatting = resolveEffectiveTableCellFormatting({
756
+ directFormatting: formatting,
757
+ styleFormatting: conditionalStyle?.tcPr,
758
+ tableBorders,
759
+ position,
760
+ gridWidthPercent,
761
+ defaultMargins: defaultCellMargins,
762
+ theme: context.theme
763
+ });
793
764
  const attrs = {
794
765
  colspan: formatting?.gridSpan ?? 1,
795
766
  rowspan
796
767
  };
797
- if (width !== void 0) attrs.width = width;
798
- if (widthType) attrs.widthType = widthType;
768
+ if (effectiveFormatting.width.type === "value") {
769
+ attrs.width = effectiveFormatting.width.value;
770
+ if (effectiveFormatting.width.widthType) attrs.widthType = effectiveFormatting.width.widthType;
771
+ }
799
772
  if (formatting?.verticalAlign) attrs.verticalAlign = formatting.verticalAlign;
800
- if (backgroundColor) {
801
- attrs.backgroundColor = backgroundColor;
802
- attrs._resolvedBackgroundColor = backgroundColor;
773
+ if (effectiveFormatting.background.type === "color") {
774
+ attrs.backgroundColor = effectiveFormatting.background.rgb;
775
+ attrs._resolvedBackgroundColor = effectiveFormatting.background.rgb;
803
776
  }
804
777
  if (formatting?.textDirection) attrs.textDirection = formatting.textDirection;
805
778
  if (formatting?.noWrap !== void 0) attrs.noWrap = formatting.noWrap;
806
- if (borders) attrs.borders = borders;
807
- if (formatting?.margins) attrs.margins = buildMarginsAttr(formatting.margins);
808
- else if (conditionalStyle?.tcPr?.margins) attrs.margins = buildMarginsAttr(conditionalStyle.tcPr.margins);
809
- else if (defaultCellMargins) attrs.margins = defaultCellMargins;
779
+ if (effectiveFormatting.borders) attrs.borders = effectiveFormatting.borders;
780
+ if (effectiveFormatting.margins) attrs.margins = effectiveFormatting.margins;
810
781
  if (formatting) attrs._originalFormatting = formatting;
811
782
  if (cell.propertyChanges && cell.propertyChanges.length > 0) attrs.tcPrChange = [...cell.propertyChanges];
812
783
  if (preserveVMergeRestart) attrs._preserveVMergeRestart = true;
@@ -1072,6 +1043,7 @@ function convertImage(image, rawXml) {
1072
1043
  cropBottom: image.crop?.bottom,
1073
1044
  cropLeft: image.crop?.left,
1074
1045
  position,
1046
+ layoutInCell: image.layoutInCell,
1075
1047
  borderWidth,
1076
1048
  borderColor,
1077
1049
  borderStyle,
@@ -1,8 +1,8 @@
1
1
  import { autospacingMatchesBase } from "../../autospacingBase.js";
2
2
  import { directionIsRtl } from "../../paragraphDirection.js";
3
3
  import { expectParagraphAttrs } from "../../attrs/index.js";
4
- import { paragraphToStyle } from "../../../utils/formatToStyle.js";
5
4
  import { createNodeExtension } from "../create.js";
5
+ import { paragraphToStyle } from "../../../utils/formatToStyle.js";
6
6
  import { collectHeadings } from "../../../utils/headingCollector.js";
7
7
  import { listAttrsFromResolvedStyle, paragraphAttrsFromResolvedStyle } from "../../styles/resolvedStyleAttrs.js";
8
8
  import { Fragment } from "prosemirror-model";
@@ -1,7 +1,7 @@
1
1
  import { HIGHLIGHT_COLOR_VALUES } from "../../../types/documentEnumValues.js";
2
2
  import { expectHighlightMarkAttrs } from "../../attrs/index.js";
3
- import { resolveHighlightToCss } from "../../../utils/colorResolver.js";
4
3
  import { createMarkExtension } from "../create.js";
4
+ import { resolveHighlightToCss } from "../../../utils/colorResolver.js";
5
5
  import { removeMark, setMark } from "./markUtils.js";
6
6
  import { panic } from "better-result";
7
7
  //#region src/prosemirror/extensions/marks/HighlightExtension.ts
@@ -1,6 +1,6 @@
1
1
  import { expectRunShadingMarkAttrs } from "../../attrs/index.js";
2
- import { ensureHexPrefix } from "../../../utils/colorResolver.js";
3
2
  import { createMarkExtension } from "../create.js";
3
+ import { ensureHexPrefix } from "../../../utils/colorResolver.js";
4
4
  import { normalizeCssColorKey, parseDOMHighlightColor } from "./HighlightExtension.js";
5
5
  //#region src/prosemirror/extensions/marks/RunShadingExtension.ts
6
6
  const RunShadingExtension = createMarkExtension({
@@ -1,6 +1,6 @@
1
1
  import { expectTextColorMarkAttrs } from "../../attrs/index.js";
2
- import { textToStyle } from "../../../utils/formatToStyle.js";
3
2
  import { createMarkExtension } from "../create.js";
3
+ import { textToStyle } from "../../../utils/formatToStyle.js";
4
4
  import { removeMark, setMark } from "./markUtils.js";
5
5
  import { panic } from "better-result";
6
6
  //#region src/prosemirror/extensions/marks/TextColorExtension.ts
@@ -33,6 +33,7 @@ const ImageExtension = createNodeExtension({
33
33
  cropBottom: { default: null },
34
34
  cropLeft: { default: null },
35
35
  position: { default: null },
36
+ layoutInCell: { default: null },
36
37
  borderWidth: { default: null },
37
38
  borderColor: { default: null },
38
39
  borderStyle: { default: null },
@@ -1,7 +1,7 @@
1
1
  import { TABLE_CELL_TEXT_DIRECTION_VALUES, TABLE_WIDTH_TYPE_VALUES } from "../../../types/documentEnumValues.js";
2
2
  import { expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, mergeTableAttrs, mergeTableCellAttrs, mergeTableRowAttrs } from "../../attrs/index.js";
3
- import { resolveColor } from "../../../utils/colorResolver.js";
4
3
  import { createExtension, createNodeExtension } from "../create.js";
4
+ import { resolveColor } from "../../../utils/colorResolver.js";
5
5
  import { panic } from "better-result";
6
6
  import { Plugin, PluginKey, Selection, TextSelection } from "prosemirror-state";
7
7
  import { CellSelection, TableMap, columnResizing, mergeCells, removeRow, selectedRect, splitCell, tableEditing } from "prosemirror-tables";
@@ -197,7 +197,8 @@ type ImageAttrs = {
197
197
  cropRight?: number;
198
198
  cropBottom?: number;
199
199
  cropLeft?: number; /** Position for floating images (horizontal and vertical alignment) */
200
- position?: ImagePositionAttrs; /** Border width in pixels */
200
+ position?: ImagePositionAttrs; /** Use the containing table cell as the anchor's positioning scope (the OOXML default). */
201
+ layoutInCell?: boolean; /** Border width in pixels */
201
202
  borderWidth?: number; /** Border color as CSS color string */
202
203
  borderColor?: string; /** Border style (CSS border-style value) */
203
204
  borderStyle?: string; /** Wrap text setting from DOCX (left, right, bothSides, largest) for round-trip */
package/dist/redline.d.ts CHANGED
@@ -1,20 +1,35 @@
1
- import { FolioAIEditApplyResult } from "./ai-edits/types.js";
1
+ import { FolioAIEditAppliedOperation, FolioAIEditSkippedOperation } from "./ai-edits/types.js";
2
+ import { FolioDocumentStoryHandle, FolioResolvedReviewedView } from "./ai-edits/headless.js";
3
+ import { FolioDocumentPrivacyOptions, FolioDocumentPrivacyReport } from "./docx/metadataPrivacy.js";
2
4
 
3
5
  //#region src/redline.d.ts
4
6
  /** Options for {@link generateRedlineDocx}. */
5
7
  type GenerateRedlineDocxOptions = {
6
- /** Author recorded on the generated tracked changes. (default: `"folio compare"`) */author?: string;
8
+ /** Author recorded on the generated tracked changes. (default: `"folio compare"`) */author?: string; /** Resolved base input state. (default: `"final"`) */
9
+ baseView?: FolioResolvedReviewedView; /** Resolved revised input state. (default: `"final"`) */
10
+ revisedView?: FolioResolvedReviewedView; /** Optional output-only package-metadata privacy transforms. */
11
+ privacy?: FolioDocumentPrivacyOptions;
12
+ };
13
+ declare const InvalidGenerateRedlineDocxOptionsError_base: import("better-result").TaggedErrorClass<"InvalidGenerateRedlineDocxOptionsError", {
14
+ message: string;
15
+ option: "baseView" | "revisedView";
16
+ receivedValue: unknown;
17
+ }>;
18
+ declare class InvalidGenerateRedlineDocxOptionsError extends InvalidGenerateRedlineDocxOptionsError_base {}
19
+ type GenerateRedlineUnprocessedStory = {
20
+ baseStory: FolioDocumentStoryHandle | null;
21
+ revisedStory: FolioDocumentStoryHandle | null;
22
+ reason: "missing-base-story" | "missing-revised-story";
7
23
  };
8
24
  /** Result of {@link generateRedlineDocx}. */
9
- type GenerateRedlineDocxResult = FolioAIEditApplyResult & {
10
- /** The redline `.docx`: the base document with base → revised tracked changes. */buffer: ArrayBuffer;
25
+ type GenerateRedlineDocxResult = {
26
+ /** The base package with generated tracked changes. */buffer: ArrayBuffer; /** Operations applied across every matched story. */
27
+ applied: FolioAIEditAppliedOperation[]; /** Block operations that could not be applied. */
28
+ skipped: FolioAIEditSkippedOperation[]; /** Package parts that could not be represented as story-scoped text edits. */
29
+ unprocessedStories: GenerateRedlineUnprocessedStory[]; /** Privacy transforms applied to the generated package. */
30
+ privacyReport: FolioDocumentPrivacyReport;
11
31
  };
12
- /**
13
- * Compare `base` and `revised` and return the base document with every
14
- * difference recorded as a tracked change. See the module doc comment for
15
- * semantics and limitations; `skipped` reports any block the generator could
16
- * not redline (e.g. additions with no anchor in an empty base document).
17
- */
32
+ /** Compare two buffers and return tracked changes for every matched editable story. */
18
33
  declare const generateRedlineDocx: (base: ArrayBuffer, revised: ArrayBuffer, options?: GenerateRedlineDocxOptions) => Promise<GenerateRedlineDocxResult>;
19
34
  //#endregion
20
- export { GenerateRedlineDocxOptions, GenerateRedlineDocxResult, generateRedlineDocx };
35
+ export { GenerateRedlineDocxOptions, GenerateRedlineDocxResult, GenerateRedlineUnprocessedStory, InvalidGenerateRedlineDocxOptionsError, generateRedlineDocx };