@stll/folio-core 0.44.0 → 0.45.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 (90) hide show
  1. package/dist/ai-edits/__fixtures__/paragraphs.js +2 -2
  2. package/dist/ai-edits/headless.js +1 -0
  3. package/dist/compare/content-alignment.js +78 -53
  4. package/dist/compare/inline-atoms.js +34 -20
  5. package/dist/content-controls/mutateContentControls.js +4 -2
  6. package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
  7. package/dist/document-operations.js +14 -3
  8. package/dist/docx/borderParser.js +5 -5
  9. package/dist/docx/commentParser.js +47 -36
  10. package/dist/docx/commentThreadKey.d.ts +18 -0
  11. package/dist/docx/commentThreadKey.js +22 -0
  12. package/dist/docx/diagramPreview.js +87 -27
  13. package/dist/docx/groupDrawingParser.js +3 -3
  14. package/dist/docx/hyperlinkParser.js +2 -2
  15. package/dist/docx/imageParser.d.ts +9 -1
  16. package/dist/docx/imageParser.js +58 -12
  17. package/dist/docx/imageRawXml.d.ts +14 -1
  18. package/dist/docx/imageRawXml.js +30 -6
  19. package/dist/docx/mathToMathml.js +12 -14
  20. package/dist/docx/nonVisualDrawingProps.d.ts +34 -0
  21. package/dist/docx/nonVisualDrawingProps.js +46 -0
  22. package/dist/docx/paragraphTextBoxEnrichment.js +3 -0
  23. package/dist/docx/parser.js +2 -2
  24. package/dist/docx/previewBudget.d.ts +64 -0
  25. package/dist/docx/previewBudget.js +88 -0
  26. package/dist/docx/revisionIdNormalization.js +17 -5
  27. package/dist/docx/rezip.js +17 -18
  28. package/dist/docx/sdtPropertiesPatch.js +24 -18
  29. package/dist/docx/sectionReferenceHistory.js +2 -2
  30. package/dist/docx/selectiveSave.js +6 -6
  31. package/dist/docx/serializer/blockSdtSerializer.js +38 -26
  32. package/dist/docx/serializer/borderSerializer.d.ts +1 -1
  33. package/dist/docx/serializer/borderSerializer.js +13 -12
  34. package/dist/docx/serializer/commentSerializer.d.ts +41 -16
  35. package/dist/docx/serializer/commentSerializer.js +82 -85
  36. package/dist/docx/serializer/fontTableSerializer.js +6 -6
  37. package/dist/docx/serializer/headerFooterSerializer.js +5 -5
  38. package/dist/docx/serializer/markupRangeAttributes.js +2 -2
  39. package/dist/docx/serializer/numberingSerializer.js +7 -6
  40. package/dist/docx/serializer/paragraphSerializer.js +19 -18
  41. package/dist/docx/serializer/partNamespaces.js +2 -2
  42. package/dist/docx/serializer/runSerializer.js +48 -28
  43. package/dist/docx/serializer/sectionPropertiesSerializer.js +11 -10
  44. package/dist/docx/serializer/settingsSerializer.js +4 -3
  45. package/dist/docx/serializer/stylesSerializer.js +6 -6
  46. package/dist/docx/serializer/tableSerializer.js +10 -9
  47. package/dist/docx/serializer/textFormattingSerializer.js +29 -28
  48. package/dist/docx/serializer/themeSerializer.js +6 -6
  49. package/dist/docx/serializer/trackedChangeAttributes.js +2 -2
  50. package/dist/docx/serializer/xmlUtils.d.ts +1 -2
  51. package/dist/docx/serializer/xmlUtils.js +1 -13
  52. package/dist/docx/server/boundedArchive.d.ts +12 -0
  53. package/dist/docx/server/boundedArchive.js +20 -1
  54. package/dist/docx/server/validateDocxConformance.js +22 -1
  55. package/dist/docx/shapeParser.js +7 -5
  56. package/dist/docx/textBoxParser.js +7 -2
  57. package/dist/docx/unzip.d.ts +23 -0
  58. package/dist/docx/unzip.js +32 -22
  59. package/dist/docx/verbatimCapture.js +1 -1
  60. package/dist/docx/vmlImageParser.js +3 -2
  61. package/dist/docx/vmlPreview.d.ts +1 -3
  62. package/dist/docx/vmlPreview.js +2 -30
  63. package/dist/docx/xmlParser.d.ts +16 -1
  64. package/dist/docx/xmlParser.js +56 -26
  65. package/dist/docx/xmlResourceLimits.d.ts +89 -9
  66. package/dist/docx/xmlResourceLimits.js +105 -24
  67. package/dist/internal/paragraphFormattingSerialization.js +3 -2
  68. package/dist/layout-painter/renderImage.js +4 -3
  69. package/dist/layout-painter/renderParagraph.js +4 -3
  70. package/dist/managers/autoSaveCodec.js +2 -8
  71. package/dist/markdown/images.js +1 -4
  72. package/dist/prosemirror/attrs/index.js +69 -0
  73. package/dist/prosemirror/commands/image.js +1 -0
  74. package/dist/prosemirror/conversion/fromProseDoc.js +69 -29
  75. package/dist/prosemirror/conversion/toProseDoc.js +83 -19
  76. package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +2 -3
  77. package/dist/prosemirror/extensions/nodes/ImageExtension.js +4 -0
  78. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +7 -2
  79. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +8 -4
  80. package/dist/prosemirror/paragraphFormattingProvenance.d.ts +6 -3
  81. package/dist/prosemirror/paragraphFormattingProvenance.js +12 -3
  82. package/dist/prosemirror/schema/nodes.d.ts +50 -1
  83. package/dist/utils/base64.d.ts +36 -0
  84. package/dist/utils/base64.js +40 -0
  85. package/dist/utils/clipboard.js +2 -1
  86. package/dist/utils/units.d.ts +10 -1
  87. package/dist/utils/units.js +12 -1
  88. package/dist/utils/urlSecurity.d.ts +8 -2
  89. package/dist/utils/urlSecurity.js +21 -3
  90. package/package.json +2 -2
@@ -10,7 +10,7 @@ import { OUTLINE_STYLE_CSS_ALIASES, normalizeShapeTextAnchor } from "../../types
10
10
  import { canonicalJson } from "../../utils/canonicalJson.js";
11
11
  import { normalizeHorizontalScalePercent } from "../../utils/horizontalScale.js";
12
12
  import { mergeTextFormatting } from "../../utils/textFormattingMerge.js";
13
- import { pixelsToEmu } from "../../utils/units.js";
13
+ import { emuToPixels, emuToStrokePixels, pixelsToEmu } from "../../utils/units.js";
14
14
  import { expectBlockSdtAttrs, expectCharacterSpacingMarkAttrs, expectCharacterStyleMarkAttrs, expectCommentMarkAttrs, expectEmphasisMarkAttrs, expectFieldAttrs, expectFontFamilyMarkAttrs, expectFontSizeMarkAttrs, expectFootnoteRefMarkAttrs, expectHardBreakAttrs, expectHighlightMarkAttrs, expectHyperlinkMarkAttrs, expectImageAttrs, expectLanguageMarkAttrs, expectMathAttrs, expectPageBreakRunAttrs, expectPageBreakRunOwnerMarkAttrs, expectParagraphAttrs, expectRunFormattingOverrideMarkAttrs, expectRunPropertyChangeMarkAttrs, expectRunShadingMarkAttrs, expectSdtAttrs, expectShapeAttrs, expectStrikeMarkAttrs, expectSymbolAttrs, expectTabAttrs, expectTableAttrs, expectTableCellAttrs, expectTableRowAttrs, expectTextBoxAttrs, expectTextColorMarkAttrs, expectTextEffectMarkAttrs, expectTrackedChangeMarkAttrs, expectUnderlineMarkAttrs } from "../attrs/index.js";
15
15
  import { autospacingMatchesBase, hasAutospacingBaseSide } from "../autospacingBase.js";
16
16
  import { bookmarkBoundaryDisplacement, expectBookmarkBoundaryAttrs } from "../bookmarkBoundaryAttrs.js";
@@ -79,14 +79,35 @@ function imagePositionFromAttrs(attrs) {
79
79
  vertical
80
80
  };
81
81
  }
82
+ /**
83
+ * The EMUs behind one pixel attribute of a drawing.
84
+ *
85
+ * `toProseDoc` carries the authored EMU beside every pixel value it derived,
86
+ * because EMU → px → EMU does not land back on the same number. While the
87
+ * pixel attribute still projects from the authored EMU nothing has moved it,
88
+ * so the document gets its own value back; once an editor command has, the
89
+ * pixel attribute is the truth and converts.
90
+ */
91
+ const emuFromPixels = (px, key, authored, project) => {
92
+ const emu = authored?.[key];
93
+ return emu !== void 0 && project(emu) === px ? emu : pixelsToEmu(px);
94
+ };
95
+ /**
96
+ * Copy the wrap insets onto a wrap. Shared by the image, shape and text-box
97
+ * paths so one rule decides when an inset keeps its authored EMU.
98
+ */
99
+ const assignWrapDistances = (wrap, attrs) => {
100
+ const authored = attrs._docxAuthoredEmu;
101
+ if (attrs.distTop !== void 0) wrap.distT = emuFromPixels(attrs.distTop, "distTop", authored, emuToPixels);
102
+ if (attrs.distBottom !== void 0) wrap.distB = emuFromPixels(attrs.distBottom, "distBottom", authored, emuToPixels);
103
+ if (attrs.distLeft !== void 0) wrap.distL = emuFromPixels(attrs.distLeft, "distLeft", authored, emuToPixels);
104
+ if (attrs.distRight !== void 0) wrap.distR = emuFromPixels(attrs.distRight, "distRight", authored, emuToPixels);
105
+ };
82
106
  function textBoxWrapFromAttrs(attrs) {
83
107
  if (!(attrs.wrapType !== void 0 && attrs.wrapType !== "inline" || attrs.wrapText !== void 0 || attrs.distTop !== void 0 || attrs.distBottom !== void 0 || attrs.distLeft !== void 0 || attrs.distRight !== void 0)) return;
84
108
  const wrap = { type: attrs.wrapType ?? "inline" };
85
109
  if (attrs.wrapText !== void 0) wrap.wrapText = attrs.wrapText;
86
- if (attrs.distTop !== void 0) wrap.distT = pixelsToEmu(attrs.distTop);
87
- if (attrs.distBottom !== void 0) wrap.distB = pixelsToEmu(attrs.distBottom);
88
- if (attrs.distLeft !== void 0) wrap.distL = pixelsToEmu(attrs.distLeft);
89
- if (attrs.distRight !== void 0) wrap.distR = pixelsToEmu(attrs.distRight);
110
+ assignWrapDistances(wrap, attrs);
90
111
  return wrap;
91
112
  }
92
113
  const assignUniqueParagraph = (paragraphs, paraId, paragraph) => {
@@ -906,6 +927,9 @@ function paragraphAttrsToFormatting(attrs) {
906
927
  else delete result.styleId;
907
928
  assignBooleanToggle(result, attrs, orig, "pageBreakBefore");
908
929
  assignBooleanToggle(result, attrs, orig, "widowControl");
930
+ assignBooleanToggle(result, attrs, orig, "keepNext");
931
+ assignBooleanToggle(result, attrs, orig, "keepLines");
932
+ assignBooleanToggle(result, attrs, orig, "runInWithNext");
909
933
  assignBooleanToggle(result, attrs, orig, "snapToGrid");
910
934
  assignBooleanToggle(result, attrs, orig, "kinsoku");
911
935
  assignBooleanToggle(result, attrs, orig, "overflowPunctuation");
@@ -931,10 +955,13 @@ function paragraphAttrsToFormatting(attrs) {
931
955
  const contextualSpacing = authored("contextualSpacing", attrs.contextualSpacing);
932
956
  const pageBreakBefore = authored("pageBreakBefore", attrs.pageBreakBefore);
933
957
  const widowControl = authored("widowControl", attrs.widowControl);
958
+ const keepNext = authored("keepNext", attrs.keepNext);
959
+ const keepLines = authored("keepLines", attrs.keepLines);
960
+ const runInWithNext = authored("runInWithNext", attrs.runInWithNext);
934
961
  const kinsoku = authored("kinsoku", attrs.kinsoku);
935
962
  const overflowPunctuation = authored("overflowPunctuation", attrs.overflowPunctuation);
936
963
  const suppressAutoHyphens = authored("suppressAutoHyphens", attrs.suppressAutoHyphens);
937
- if (!(directAlignment !== void 0 || shouldSerializeSpaceBefore || shouldSerializeSpaceAfter || beforeAutospacingEdited || afterAutospacingEdited || hasDirectLineSpacing || hasDirectLineSpacingRule || snapToGrid != null || indentLeft || indentRight || indentFirstLine || attrs.numPr || attrs.styleId || borders || shading || tabs || typeof outlineLevel === "number" || contextualSpacing || attrs.spacingExplicit || bidi != null || pageBreakBefore != null || widowControl != null || kinsoku != null || overflowPunctuation != null || suppressAutoHyphens != null)) return;
964
+ if (!(directAlignment !== void 0 || shouldSerializeSpaceBefore || shouldSerializeSpaceAfter || beforeAutospacingEdited || afterAutospacingEdited || hasDirectLineSpacing || hasDirectLineSpacingRule || snapToGrid != null || indentLeft || indentRight || indentFirstLine || attrs.numPr || attrs.styleId || borders || shading || tabs || typeof outlineLevel === "number" || contextualSpacing || attrs.spacingExplicit || bidi != null || pageBreakBefore != null || widowControl != null || keepNext != null || keepLines != null || runInWithNext != null || kinsoku != null || overflowPunctuation != null || suppressAutoHyphens != null)) return;
938
965
  const f = {};
939
966
  if (directAlignment !== void 0) f.alignment = directAlignment;
940
967
  if (shouldSerializeSpaceBefore) f.spaceBefore = spaceBefore;
@@ -959,6 +986,9 @@ function paragraphAttrsToFormatting(attrs) {
959
986
  if (bidi != null) f.bidi = bidi;
960
987
  if (pageBreakBefore != null) f.pageBreakBefore = pageBreakBefore;
961
988
  if (widowControl != null) f.widowControl = widowControl;
989
+ if (keepNext != null) f.keepNext = keepNext;
990
+ if (keepLines != null) f.keepLines = keepLines;
991
+ if (runInWithNext != null) f.runInWithNext = runInWithNext;
962
992
  if (kinsoku != null) f.kinsoku = kinsoku;
963
993
  if (overflowPunctuation != null) f.overflowPunctuation = overflowPunctuation;
964
994
  if (suppressAutoHyphens != null) f.suppressAutoHyphens = suppressAutoHyphens;
@@ -1896,18 +1926,16 @@ const carriedRunFormatting = (formatting) => formatting && Object.keys(formattin
1896
1926
  function createImageRun(node) {
1897
1927
  const attrs = expectImageAttrs(node);
1898
1928
  const wrap = { type: attrs.wrapType || "inline" };
1899
- if (attrs.distTop !== void 0) wrap.distT = pixelsToEmu(attrs.distTop);
1900
- if (attrs.distBottom !== void 0) wrap.distB = pixelsToEmu(attrs.distBottom);
1901
- if (attrs.distLeft !== void 0) wrap.distL = pixelsToEmu(attrs.distLeft);
1902
- if (attrs.distRight !== void 0) wrap.distR = pixelsToEmu(attrs.distRight);
1929
+ assignWrapDistances(wrap, attrs);
1903
1930
  if (attrs.wrapText) wrap.wrapText = attrs.wrapText;
1931
+ const authoredEmu = attrs._docxAuthoredEmu;
1904
1932
  const image = {
1905
1933
  type: "image",
1906
1934
  rId: attrs.rId || "",
1907
1935
  src: attrs.src,
1908
1936
  size: {
1909
- width: pixelsToEmu(attrs.width || 0),
1910
- height: pixelsToEmu(attrs.height || 0)
1937
+ width: emuFromPixels(attrs.width || 0, "width", authoredEmu, emuToPixels),
1938
+ height: emuFromPixels(attrs.height || 0, "height", authoredEmu, emuToPixels)
1911
1939
  },
1912
1940
  wrap
1913
1941
  };
@@ -1920,10 +1948,13 @@ function createImageRun(node) {
1920
1948
  const imagePosition = imagePositionFromAttrs(attrs.position);
1921
1949
  if (imagePosition) image.position = imagePosition;
1922
1950
  if (attrs.layoutInCell !== void 0) image.layoutInCell = attrs.layoutInCell;
1951
+ if (attrs.decorative !== void 0) image.decorative = attrs.decorative;
1952
+ if (attrs.hidden !== void 0) image.hidden = attrs.hidden;
1953
+ if (attrs.docPrExtensions !== void 0) image.docPrExtensions = [...attrs.docPrExtensions];
1923
1954
  if (attrs.frameLocks !== void 0) image.frameLocks = { ...attrs.frameLocks };
1924
1955
  if (attrs.borderWidth && attrs.borderWidth > 0) {
1925
1956
  const outline = {
1926
- width: pixelsToEmu(attrs.borderWidth),
1957
+ width: emuFromPixels(attrs.borderWidth, "borderWidth", authoredEmu, emuToStrokePixels),
1927
1958
  style: attrs.borderStyle ? {
1928
1959
  solid: "solid",
1929
1960
  dotted: "dot",
@@ -2003,24 +2034,25 @@ const drawingFromImageAttrs = (image, attrs) => {
2003
2034
  */
2004
2035
  function createShapeRun(node) {
2005
2036
  const attrs = expectShapeAttrs(node);
2037
+ const authoredEmu = attrs._docxAuthoredEmu;
2006
2038
  const shape = {
2007
2039
  type: "shape",
2008
2040
  shapeType: attrs.shapeType || "rect",
2009
2041
  size: {
2010
- width: attrs.width ? pixelsToEmu(attrs.width) : 0,
2011
- height: attrs.height ? pixelsToEmu(attrs.height) : 0
2042
+ width: attrs.width ? emuFromPixels(attrs.width, "width", authoredEmu, emuToPixels) : 0,
2043
+ height: attrs.height ? emuFromPixels(attrs.height, "height", authoredEmu, emuToPixels) : 0
2012
2044
  }
2013
2045
  };
2014
2046
  if (attrs.shapeId) shape.id = attrs.shapeId;
2047
+ if (attrs.shapeName !== void 0) shape.name = attrs.shapeName;
2048
+ if (attrs.alt !== void 0) shape.alt = attrs.alt;
2049
+ if (attrs.title !== void 0) shape.title = attrs.title;
2015
2050
  const geometryAdjustments = parseShapeGeometryAdjustments(attrs.geometryAdjustments);
2016
2051
  if (geometryAdjustments !== void 0) shape.geometryAdjustments = geometryAdjustments;
2017
2052
  const shapeTransform = parseTransformAttr(attrs.transform);
2018
2053
  if (shapeTransform) shape.transform = shapeTransform;
2019
2054
  const wrap = { type: attrs.wrapType || "inline" };
2020
- if (attrs.distTop !== void 0) wrap.distT = pixelsToEmu(attrs.distTop);
2021
- if (attrs.distBottom !== void 0) wrap.distB = pixelsToEmu(attrs.distBottom);
2022
- if (attrs.distLeft !== void 0) wrap.distL = pixelsToEmu(attrs.distLeft);
2023
- if (attrs.distRight !== void 0) wrap.distR = pixelsToEmu(attrs.distRight);
2055
+ assignWrapDistances(wrap, attrs);
2024
2056
  if (attrs.wrapText) wrap.wrapText = attrs.wrapText;
2025
2057
  shape.wrap = wrap;
2026
2058
  const shapePosition = imagePositionFromAttrs(attrs.position);
@@ -2050,11 +2082,15 @@ function createShapeRun(node) {
2050
2082
  color: attrs.fillColorValue ?? { rgb: attrs.fillColor.replace("#", "") }
2051
2083
  };
2052
2084
  else if (attrs.fillType === "none") shape.fill = { type: "none" };
2053
- if (attrs.outlineStyle !== "none" && (attrs.outlineWidth !== void 0 && attrs.outlineWidth > 0 || attrs.outlineColor || attrs.outlineStyle || attrs.outlineCap || attrs.outlineHeadEnd || attrs.outlineTailEnd)) {
2085
+ if (attrs.outlineStyle !== "none" && (attrs.outlineWidth !== void 0 && attrs.outlineWidth > 0 || attrs.outlineColor || attrs.outlineStyle || attrs.outlineCap || attrs.outlineJoin || attrs.outlineHeadEnd || attrs.outlineTailEnd)) {
2054
2086
  const shapeOutline = {};
2055
- if (attrs.outlineWidth !== void 0 && attrs.outlineWidth > 0) shapeOutline.width = pixelsToEmu(attrs.outlineWidth);
2056
- if (attrs.outlineStyle) shapeOutline.style = normalizeShapeOutlineStyle(attrs.outlineStyle) ?? "solid";
2087
+ if (attrs.outlineWidth !== void 0 && attrs.outlineWidth > 0) shapeOutline.width = emuFromPixels(attrs.outlineWidth, "outlineWidth", authoredEmu, emuToStrokePixels);
2088
+ if (attrs.outlineStyle) {
2089
+ const style = normalizeShapeOutlineStyle(attrs.outlineStyle);
2090
+ if (style !== void 0) shapeOutline.style = style;
2091
+ }
2057
2092
  if (attrs.outlineCap) shapeOutline.cap = attrs.outlineCap;
2093
+ if (attrs.outlineJoin) shapeOutline.join = attrs.outlineJoin;
2058
2094
  if (attrs.outlineHeadEnd) shapeOutline.headEnd = attrs.outlineHeadEnd;
2059
2095
  if (attrs.outlineTailEnd) shapeOutline.tailEnd = attrs.outlineTailEnd;
2060
2096
  if (attrs.outlineColor) shapeOutline.color = attrs.outlineColorValue ?? { rgb: attrs.outlineColor.replace("#", "") };
@@ -3015,6 +3051,7 @@ const projectTextBoxBodyContent = (state, blocks) => {
3015
3051
  */
3016
3052
  function convertPMTextBox(node, styleResolver = null) {
3017
3053
  const attrs = expectTextBoxAttrs(node);
3054
+ const authoredEmu = attrs._docxAuthoredEmu;
3018
3055
  const verticalAlign = normalizeShapeTextAnchor(attrs.verticalAlign);
3019
3056
  const childBlocks = [];
3020
3057
  node.forEach((child) => {
@@ -3026,8 +3063,8 @@ function convertPMTextBox(node, styleResolver = null) {
3026
3063
  type: "shape",
3027
3064
  shapeType: "textBox",
3028
3065
  size: {
3029
- width: attrs.width ? pixelsToEmu(attrs.width) : 0,
3030
- height: attrs.height ? pixelsToEmu(attrs.height) : 0
3066
+ width: attrs.width ? emuFromPixels(attrs.width, "width", authoredEmu, emuToPixels) : 0,
3067
+ height: attrs.height ? emuFromPixels(attrs.height, "height", authoredEmu, emuToPixels) : 0
3031
3068
  },
3032
3069
  textBody: {
3033
3070
  content: textBodyContent.type === "source-empty" ? [] : textBodyContent.content,
@@ -3037,15 +3074,18 @@ function convertPMTextBox(node, styleResolver = null) {
3037
3074
  ...verticalAlign !== void 0 ? { anchor: verticalAlign } : {},
3038
3075
  margins: (() => {
3039
3076
  const m = {};
3040
- if (typeof attrs.marginTop === "number") m.top = pixelsToEmu(attrs.marginTop);
3041
- if (typeof attrs.marginBottom === "number") m.bottom = pixelsToEmu(attrs.marginBottom);
3042
- if (typeof attrs.marginLeft === "number") m.left = pixelsToEmu(attrs.marginLeft);
3043
- if (typeof attrs.marginRight === "number") m.right = pixelsToEmu(attrs.marginRight);
3077
+ if (typeof attrs.marginTop === "number") m.top = emuFromPixels(attrs.marginTop, "marginTop", authoredEmu, emuToPixels);
3078
+ if (typeof attrs.marginBottom === "number") m.bottom = emuFromPixels(attrs.marginBottom, "marginBottom", authoredEmu, emuToPixels);
3079
+ if (typeof attrs.marginLeft === "number") m.left = emuFromPixels(attrs.marginLeft, "marginLeft", authoredEmu, emuToPixels);
3080
+ if (typeof attrs.marginRight === "number") m.right = emuFromPixels(attrs.marginRight, "marginRight", authoredEmu, emuToPixels);
3044
3081
  return m;
3045
3082
  })()
3046
3083
  }
3047
3084
  };
3048
3085
  if (attrs.textBoxId) shape.id = attrs.textBoxId;
3086
+ if (attrs.textBoxName !== void 0) shape.name = attrs.textBoxName;
3087
+ if (attrs.alt !== void 0) shape.alt = attrs.alt;
3088
+ if (attrs.title !== void 0) shape.title = attrs.title;
3049
3089
  const transform = parseTransformAttr(attrs.transform);
3050
3090
  if (transform) shape.transform = transform;
3051
3091
  if (attrs.fillColor) shape.fill = {
@@ -3054,7 +3094,7 @@ function convertPMTextBox(node, styleResolver = null) {
3054
3094
  };
3055
3095
  if (attrs.outlineStyle !== "none" && attrs.outlineWidth && attrs.outlineWidth > 0) {
3056
3096
  const tbOutline = {
3057
- width: pixelsToEmu(attrs.outlineWidth),
3097
+ width: emuFromPixels(attrs.outlineWidth, "outlineWidth", authoredEmu, emuToStrokePixels),
3058
3098
  style: normalizeShapeOutlineStyle(attrs.outlineStyle) ?? "solid"
3059
3099
  };
3060
3100
  if (attrs.outlineColor) tbOutline.color = { rgb: attrs.outlineColor.replace("#", "") };
@@ -10,7 +10,7 @@ import { normalizeHorizontalScalePercent } from "../../utils/horizontalScale.js"
10
10
  import { mergeParagraphFormatting, mergeParagraphTabStops } from "../../utils/paragraphFormattingMerge.js";
11
11
  import { tableOfContentsStyleLevel } from "../../utils/tableOfContentsStyle.js";
12
12
  import { mergeTextFormatting } from "../../utils/textFormattingMerge.js";
13
- import { emuToPixels } from "../../utils/units.js";
13
+ import { emuToPixels, emuToStrokePixels } from "../../utils/units.js";
14
14
  import { setAutospacingBaseValue } from "../autospacingBase.js";
15
15
  import { textFormattingToMarks } from "../extensions/marks/markUtils.js";
16
16
  import { listRenderingAttrPatch } from "../listRenderingAttrs.js";
@@ -784,6 +784,7 @@ function calculateRowSpans(table) {
784
784
  const { colIndex: cellColIndex, vMerge, isMergeContinuation, startRow, hasMeaningfulContent } = cellInfo;
785
785
  const key = `${rowIndex}-${cellColIndex}`;
786
786
  if (vMerge === "restart") {
787
+ closeVerticalMerge(activeMerges, result, cellColIndex);
787
788
  activeMerges.set(cellColIndex, rowIndex);
788
789
  result.set(key, {
789
790
  rowSpan: 1,
@@ -814,7 +815,7 @@ function calculateRowSpans(table) {
814
815
  skip: true
815
816
  });
816
817
  } else {
817
- activeMerges.delete(cellColIndex);
818
+ closeVerticalMerge(activeMerges, result, cellColIndex);
818
819
  result.set(key, {
819
820
  rowSpan: 1,
820
821
  skip: false
@@ -822,14 +823,28 @@ function calculateRowSpans(table) {
822
823
  }
823
824
  }
824
825
  }
826
+ clearActiveVerticalMerges(activeMerges, result);
825
827
  return result;
826
828
  }
829
+ /**
830
+ * End the vertical merge active in one column.
831
+ *
832
+ * A `w:vMerge="restart"` is carried through the editor by its cell's rowspan,
833
+ * which only exists once a continuation joins it. A merge that closes with a
834
+ * rowspan of 1 — a restart with no continuation, one interrupted by a plain
835
+ * cell, one whose continuation is in a row a revision removed — has nothing
836
+ * but this flag to say the cell was a merge origin, and dropping it changes
837
+ * the table's visible structure.
838
+ */
839
+ function closeVerticalMerge(activeMerges, result, colIndex) {
840
+ const startRow = activeMerges.get(colIndex);
841
+ if (startRow === void 0) return;
842
+ const restartCell = result.get(`${startRow}-${colIndex}`);
843
+ if (restartCell && restartCell.rowSpan === 1) restartCell.preserveVMergeRestart = true;
844
+ activeMerges.delete(colIndex);
845
+ }
827
846
  function clearActiveVerticalMerges(activeMerges, result) {
828
- for (const [colIndex, startRow] of activeMerges) {
829
- const restartCell = result.get(`${startRow}-${colIndex}`);
830
- if (restartCell) restartCell.preserveVMergeRestart = true;
831
- }
832
- activeMerges.clear();
847
+ for (const colIndex of [...activeMerges.keys()]) closeVerticalMerge(activeMerges, result, colIndex);
833
848
  }
834
849
  function tableCellHasMeaningfulContent(cell) {
835
850
  return cell.content.some(blockHasMeaningfulContent);
@@ -1758,6 +1773,18 @@ function withRunBoundaryMarks(node, marks) {
1758
1773
  if (!marks.some(({ type }) => type.name === "hyperlink" || type.name === "pageBreakRunOwner")) return node;
1759
1774
  return node.mark(marks);
1760
1775
  }
1776
+ /**
1777
+ * The authored-EMU carrier for a drawing, or nothing when the source authored
1778
+ * none of these values and there is accordingly nothing to carry.
1779
+ */
1780
+ const authoredEmuAttrs = (values) => Object.values(values).some((value) => value !== void 0) ? values : void 0;
1781
+ /** The wrap insets of a drawing, keyed by the pixel attribute each becomes. */
1782
+ const wrapDistanceEmu = (wrap) => ({
1783
+ distTop: wrap?.distT,
1784
+ distBottom: wrap?.distB,
1785
+ distLeft: wrap?.distL,
1786
+ distRight: wrap?.distR
1787
+ });
1761
1788
  function convertImage({ image, rawXml, rawXmlMode, rawImageFingerprint, runFormatting }) {
1762
1789
  const imageSize = image.size;
1763
1790
  const widthPx = imageSize?.width ? emuToPixels(imageSize.width) : void 0;
@@ -1813,7 +1840,7 @@ function convertImage({ image, rawXml, rawXmlMode, rawImageFingerprint, runForma
1813
1840
  let borderColor;
1814
1841
  let borderStyle;
1815
1842
  if (image.outline && image.outline.width) {
1816
- borderWidth = Math.round(image.outline.width / 914400 * 96 * 100) / 100;
1843
+ borderWidth = emuToStrokePixels(image.outline.width);
1817
1844
  if (image.outline.color?.rgb) borderColor = `#${image.outline.color.rgb}`;
1818
1845
  borderStyle = image.outline.style ? {
1819
1846
  solid: "solid",
@@ -1856,8 +1883,17 @@ function convertImage({ image, rawXml, rawXmlMode, rawImageFingerprint, runForma
1856
1883
  paddingLeft: image.padding?.left,
1857
1884
  position,
1858
1885
  layoutInCell: image.layoutInCell,
1886
+ decorative: image.decorative,
1887
+ hidden: image.hidden,
1888
+ docPrExtensions: image.docPrExtensions ? [...image.docPrExtensions] : void 0,
1859
1889
  frameLocks: image.frameLocks ? { ...image.frameLocks } : void 0,
1860
1890
  borderWidth,
1891
+ _docxAuthoredEmu: authoredEmuAttrs({
1892
+ width: imageSize?.width,
1893
+ height: imageSize?.height,
1894
+ borderWidth: image.outline?.width,
1895
+ ...wrapDistanceEmu(image.wrap)
1896
+ }),
1861
1897
  borderColor,
1862
1898
  borderStyle,
1863
1899
  wrapText,
@@ -1946,16 +1982,18 @@ function convertShape(shape, runFormatting) {
1946
1982
  let outlineColorValue;
1947
1983
  let outlineStyle = "none";
1948
1984
  let outlineCap;
1985
+ let outlineJoin;
1949
1986
  let outlineHeadEnd;
1950
1987
  let outlineTailEnd;
1951
1988
  if (shape.outline) {
1952
- if (shape.outline.width) outlineWidth = Math.round(shape.outline.width / 914400 * 96 * 100) / 100;
1989
+ if (shape.outline.width) outlineWidth = emuToStrokePixels(shape.outline.width);
1953
1990
  if (shape.outline.color) {
1954
1991
  outlineColorValue = shape.outline.color;
1955
1992
  outlineColor = resolveColorValueToHex(shape.outline.color);
1956
1993
  }
1957
- outlineStyle = shape.outline.style || "solid";
1994
+ outlineStyle = shape.outline.style;
1958
1995
  outlineCap = shape.outline.cap;
1996
+ outlineJoin = shape.outline.join;
1959
1997
  outlineHeadEnd = shape.outline.headEnd;
1960
1998
  outlineTailEnd = shape.outline.tailEnd;
1961
1999
  } else outlineWidth = 0;
@@ -1990,8 +2028,17 @@ function convertShape(shape, runFormatting) {
1990
2028
  shapeType: shapeAttrs.shapeType ?? "rect",
1991
2029
  geometryAdjustments: shape.geometryAdjustments === void 0 ? void 0 : JSON.stringify(shape.geometryAdjustments),
1992
2030
  shapeId: shape.id,
2031
+ shapeName: shape.name,
2032
+ alt: shape.alt,
2033
+ title: shape.title,
1993
2034
  width: widthPx,
1994
2035
  height: heightPx,
2036
+ _docxAuthoredEmu: authoredEmuAttrs({
2037
+ width: shapeSize?.width,
2038
+ height: shapeSize?.height,
2039
+ outlineWidth: shape.outline?.width,
2040
+ ...wrapDistanceEmu(shape.wrap)
2041
+ }),
1995
2042
  fillColor,
1996
2043
  fillColorValue,
1997
2044
  fillType,
@@ -2003,6 +2050,7 @@ function convertShape(shape, runFormatting) {
2003
2050
  outlineColorValue,
2004
2051
  outlineStyle,
2005
2052
  outlineCap,
2053
+ outlineJoin,
2006
2054
  outlineHeadEnd,
2007
2055
  outlineTailEnd,
2008
2056
  transform,
@@ -2158,6 +2206,9 @@ function textBoxFromShape(shape, textBody) {
2158
2206
  content: textBody.content
2159
2207
  };
2160
2208
  if (shape.id) textBox.id = shape.id;
2209
+ if (shape.name !== void 0) textBox.name = shape.name;
2210
+ if (shape.alt !== void 0) textBox.alt = shape.alt;
2211
+ if (shape.title !== void 0) textBox.title = shape.title;
2161
2212
  if (shape.position) textBox.position = shape.position;
2162
2213
  if (shape.wrap) textBox.wrap = shape.wrap;
2163
2214
  if (shape.fill) textBox.fill = shape.fill;
@@ -2184,7 +2235,7 @@ function convertTextBox(textBox, styleResolver, options) {
2184
2235
  let outlineColor;
2185
2236
  let outlineStyle;
2186
2237
  if (textBox.outline && textBox.outline.width) {
2187
- outlineWidth = Math.round(textBox.outline.width / 914400 * 96 * 100) / 100;
2238
+ outlineWidth = emuToStrokePixels(textBox.outline.width);
2188
2239
  if (textBox.outline.color?.rgb) outlineColor = `#${textBox.outline.color.rgb}`;
2189
2240
  outlineStyle = textBox.outline.style || "solid";
2190
2241
  }
@@ -2196,10 +2247,10 @@ function convertTextBox(textBox, styleResolver, options) {
2196
2247
  if (textBox.transform.flipV) transforms.push("scaleY(-1)");
2197
2248
  if (transforms.length > 0) transform = transforms.join(" ");
2198
2249
  }
2199
- const marginTop = textBox.margins?.top !== void 0 ? emuToPixels(textBox.margins.top) : 4;
2200
- const marginBottom = textBox.margins?.bottom !== void 0 ? emuToPixels(textBox.margins.bottom) : 4;
2201
- const marginLeft = textBox.margins?.left !== void 0 ? emuToPixels(textBox.margins.left) : 7;
2202
- const marginRight = textBox.margins?.right !== void 0 ? emuToPixels(textBox.margins.right) : 7;
2250
+ const marginTop = textBox.margins?.top !== void 0 ? emuToPixels(textBox.margins.top) : void 0;
2251
+ const marginBottom = textBox.margins?.bottom !== void 0 ? emuToPixels(textBox.margins.bottom) : void 0;
2252
+ const marginLeft = textBox.margins?.left !== void 0 ? emuToPixels(textBox.margins.left) : void 0;
2253
+ const marginRight = textBox.margins?.right !== void 0 ? emuToPixels(textBox.margins.right) : void 0;
2203
2254
  const contentNodes = [];
2204
2255
  for (const block of textBox.content) {
2205
2256
  if (block.type === "paragraph") {
@@ -2243,18 +2294,31 @@ function convertTextBox(textBox, styleResolver, options) {
2243
2294
  else if (wrapType === "behind" || wrapType === "inFront") displayMode = "float";
2244
2295
  else if (cssFloat !== "none") displayMode = "float";
2245
2296
  else displayMode = "block";
2246
- const distTop = textBox.wrap?.distT ? emuToPixels(textBox.wrap.distT) : void 0;
2247
- const distBottom = textBox.wrap?.distB ? emuToPixels(textBox.wrap.distB) : void 0;
2248
- const distLeft = textBox.wrap?.distL ? emuToPixels(textBox.wrap.distL) : void 0;
2249
- const distRight = textBox.wrap?.distR ? emuToPixels(textBox.wrap.distR) : void 0;
2297
+ const distTop = textBox.wrap?.distT !== void 0 ? emuToPixels(textBox.wrap.distT) : void 0;
2298
+ const distBottom = textBox.wrap?.distB !== void 0 ? emuToPixels(textBox.wrap.distB) : void 0;
2299
+ const distLeft = textBox.wrap?.distL !== void 0 ? emuToPixels(textBox.wrap.distL) : void 0;
2300
+ const distRight = textBox.wrap?.distR !== void 0 ? emuToPixels(textBox.wrap.distR) : void 0;
2250
2301
  return schema.node("textBox", {
2251
2302
  width: widthPx,
2252
2303
  height: heightPx,
2304
+ _docxAuthoredEmu: authoredEmuAttrs({
2305
+ width: textBoxSize?.width,
2306
+ height: textBoxSize?.height,
2307
+ outlineWidth: textBox.outline?.width,
2308
+ marginTop: textBox.margins?.top,
2309
+ marginBottom: textBox.margins?.bottom,
2310
+ marginLeft: textBox.margins?.left,
2311
+ marginRight: textBox.margins?.right,
2312
+ ...wrapDistanceEmu(textBox.wrap)
2313
+ }),
2253
2314
  autoFit: textBox.autoFit,
2254
2315
  wordArt: textBox.wordArt,
2255
2316
  textWrap: textBox.textWrap,
2256
2317
  verticalAlign: textBox.verticalAlign,
2257
2318
  textBoxId: textBox.id,
2319
+ textBoxName: textBox.name,
2320
+ alt: textBox.alt,
2321
+ title: textBox.title,
2258
2322
  fillColor,
2259
2323
  outlineWidth,
2260
2324
  outlineColor,
@@ -1,4 +1,4 @@
1
- import { normalizeUserUrl, sanitizeExternalUrl } from "../../../utils/urlSecurity.js";
1
+ import { anchorTargetAttrs, normalizeUserUrl, sanitizeExternalUrl } from "../../../utils/urlSecurity.js";
2
2
  import { expectHyperlinkMarkAttrs } from "../../attrs/index.js";
3
3
  import { createMarkExtension } from "../create.js";
4
4
  import { isMarkActive } from "./markUtils.js";
@@ -96,8 +96,7 @@ const HyperlinkExtension = createMarkExtension({
96
96
  const { href, tooltip } = expectHyperlinkMarkAttrs(mark);
97
97
  const domAttrs = {
98
98
  href: sanitizeStoredHref(href),
99
- target: "_blank",
100
- rel: "noopener noreferrer"
99
+ ...anchorTargetAttrs(void 0)
101
100
  };
102
101
  if (tooltip) domAttrs["title"] = tooltip;
103
102
  return [
@@ -39,8 +39,12 @@ const ImageExtension = createNodeExtension({
39
39
  paddingLeft: { default: null },
40
40
  position: { default: null },
41
41
  layoutInCell: { default: null },
42
+ decorative: { default: null },
43
+ hidden: { default: null },
44
+ docPrExtensions: { default: null },
42
45
  frameLocks: { default: null },
43
46
  borderWidth: { default: null },
47
+ _docxAuthoredEmu: { default: null },
44
48
  borderColor: { default: null },
45
49
  borderStyle: { default: null },
46
50
  wrapText: { default: null },
@@ -371,19 +371,24 @@ const ShapeExtension = createNodeExtension({
371
371
  geometryAdjustments: { default: null },
372
372
  _docxRunFormatting: { default: null },
373
373
  shapeId: { default: null },
374
+ shapeName: { default: null },
375
+ alt: { default: null },
376
+ title: { default: null },
374
377
  width: { default: 100 },
375
378
  height: { default: 80 },
379
+ _docxAuthoredEmu: { default: null },
376
380
  fillColor: { default: null },
377
381
  fillColorValue: { default: null },
378
382
  fillType: { default: "solid" },
379
383
  gradientType: { default: null },
380
384
  gradientAngle: { default: null },
381
385
  gradientStops: { default: null },
382
- outlineWidth: { default: 1 },
386
+ outlineWidth: { default: null },
383
387
  outlineColor: { default: "var(--doc-shape-outline, #000000)" },
384
388
  outlineColorValue: { default: null },
385
- outlineStyle: { default: "solid" },
389
+ outlineStyle: { default: null },
386
390
  outlineCap: { default: null },
391
+ outlineJoin: { default: null },
387
392
  outlineHeadEnd: { default: null },
388
393
  outlineTailEnd: { default: null },
389
394
  transform: { default: null },
@@ -77,19 +77,23 @@ const TextBoxExtension = createNodeExtension({
77
77
  attrs: {
78
78
  width: { default: 200 },
79
79
  height: { default: null },
80
+ _docxAuthoredEmu: { default: null },
80
81
  autoFit: { default: null },
81
82
  wordArt: { default: null },
82
83
  textWrap: { default: null },
83
84
  textBoxId: { default: null },
85
+ textBoxName: { default: null },
86
+ alt: { default: null },
87
+ title: { default: null },
84
88
  fillColor: { default: null },
85
89
  outlineWidth: { default: null },
86
90
  outlineColor: { default: null },
87
91
  outlineStyle: { default: null },
88
92
  transform: { default: null },
89
- marginTop: { default: 4 },
90
- marginBottom: { default: 4 },
91
- marginLeft: { default: 7 },
92
- marginRight: { default: 7 },
93
+ marginTop: { default: null },
94
+ marginBottom: { default: null },
95
+ marginLeft: { default: null },
96
+ marginRight: { default: null },
93
97
  verticalAlign: { default: null },
94
98
  displayMode: { default: "inline" },
95
99
  cssFloat: { default: null },
@@ -95,10 +95,12 @@ declare const PARAGRAPH_FORMATTING_WRITE_BACK: {
95
95
  readonly attr: "tabs";
96
96
  };
97
97
  readonly keepNext: {
98
- readonly kind: "original-only";
98
+ readonly kind: "style-resolved-attr";
99
+ readonly attr: "keepNext";
99
100
  };
100
101
  readonly keepLines: {
101
- readonly kind: "original-only";
102
+ readonly kind: "style-resolved-attr";
103
+ readonly attr: "keepLines";
102
104
  };
103
105
  readonly widowControl: {
104
106
  readonly kind: "style-resolved-attr";
@@ -142,7 +144,8 @@ declare const PARAGRAPH_FORMATTING_WRITE_BACK: {
142
144
  readonly kind: "original-only";
143
145
  };
144
146
  readonly runInWithNext: {
145
- readonly kind: "original-only";
147
+ readonly kind: "style-resolved-attr";
148
+ readonly attr: "runInWithNext";
146
149
  };
147
150
  };
148
151
  type StyleResolvedParagraphField = { [Field in keyof typeof PARAGRAPH_FORMATTING_WRITE_BACK]: (typeof PARAGRAPH_FORMATTING_WRITE_BACK)[Field] extends {
@@ -53,8 +53,14 @@ const PARAGRAPH_FORMATTING_WRITE_BACK = {
53
53
  kind: "style-resolved-attr",
54
54
  attr: "tabs"
55
55
  },
56
- keepNext: { kind: "original-only" },
57
- keepLines: { kind: "original-only" },
56
+ keepNext: {
57
+ kind: "style-resolved-attr",
58
+ attr: "keepNext"
59
+ },
60
+ keepLines: {
61
+ kind: "style-resolved-attr",
62
+ attr: "keepLines"
63
+ },
58
64
  widowControl: {
59
65
  kind: "style-resolved-attr",
60
66
  attr: "widowControl"
@@ -82,7 +88,10 @@ const PARAGRAPH_FORMATTING_WRITE_BACK = {
82
88
  attr: "suppressAutoHyphens"
83
89
  },
84
90
  runProperties: { kind: "original-only" },
85
- runInWithNext: { kind: "original-only" }
91
+ runInWithNext: {
92
+ kind: "style-resolved-attr",
93
+ attr: "runInWithNext"
94
+ }
86
95
  };
87
96
  const STYLE_RESOLVED_PARAGRAPH_FIELDS = Object.keys(PARAGRAPH_FORMATTING_WRITE_BACK).filter((field) => PARAGRAPH_FORMATTING_WRITE_BACK[field].kind === "style-resolved-attr");
88
97
  /**