@stll/folio-core 0.47.7 → 0.49.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 (213) hide show
  1. package/dist/ai-edits/apply.js +565 -121
  2. package/dist/ai-edits/minimal-replacement.d.ts +64 -0
  3. package/dist/ai-edits/minimal-replacement.js +282 -0
  4. package/dist/controller/fontReadiness.js +3 -3
  5. package/dist/controller/hyphenationReadiness.d.ts +19 -0
  6. package/dist/controller/hyphenationReadiness.js +47 -0
  7. package/dist/controller/layoutPipeline.d.ts +3 -0
  8. package/dist/controller/layoutPipeline.js +70 -18
  9. package/dist/display-list/build/buildDisplayList.js +39 -12
  10. package/dist/display-list/build/pageFurniture.d.ts +7 -1
  11. package/dist/display-list/build/pageFurniture.js +21 -8
  12. package/dist/display-list/build/paragraphPrimitives.js +68 -11
  13. package/dist/display-list/build/tablePrimitives.js +10 -6
  14. package/dist/display-list/build/textBoxPrimitives.js +1 -1
  15. package/dist/display-list/build/unsupported.d.ts +2 -2
  16. package/dist/display-list/build/unsupported.js +0 -0
  17. package/dist/display-list/dom/renderDisplayListToDom.js +0 -1
  18. package/dist/docx/attributeRemainder.js +10 -2
  19. package/dist/docx/blockContentParser.d.ts +2 -0
  20. package/dist/docx/blockContentParser.js +79 -64
  21. package/dist/docx/bulletMarkers.d.ts +18 -2
  22. package/dist/docx/bulletMarkers.js +27 -2
  23. package/dist/docx/commentParser.d.ts +2 -1
  24. package/dist/docx/commentParser.js +57 -45
  25. package/dist/docx/containerChildren.d.ts +21 -8
  26. package/dist/docx/containerChildren.js +39 -9
  27. package/dist/docx/documentParser.d.ts +5 -1
  28. package/dist/docx/documentParser.js +8 -5
  29. package/dist/docx/documentSectionFacts.d.ts +22 -0
  30. package/dist/docx/documentSectionFacts.js +43 -0
  31. package/dist/docx/drawingGroupChildren.d.ts +60 -0
  32. package/dist/docx/drawingGroupChildren.js +176 -0
  33. package/dist/docx/drawingUtils.js +3 -0
  34. package/dist/docx/fieldParser.d.ts +8 -1
  35. package/dist/docx/fieldParser.js +47 -3
  36. package/dist/docx/fontTableParser.js +34 -27
  37. package/dist/docx/footnoteParser.d.ts +3 -2
  38. package/dist/docx/footnoteParser.js +15 -12
  39. package/dist/docx/groupDrawingParser.d.ts +9 -2
  40. package/dist/docx/groupDrawingParser.js +125 -37
  41. package/dist/docx/headerFooterParser.d.ts +7 -2
  42. package/dist/docx/headerFooterParser.js +15 -5
  43. package/dist/docx/hyperlinkParser.d.ts +48 -36
  44. package/dist/docx/hyperlinkParser.js +97 -82
  45. package/dist/docx/numberingParser.js +108 -84
  46. package/dist/docx/packageParts.d.ts +45 -1
  47. package/dist/docx/packageParts.js +93 -2
  48. package/dist/docx/paraIdAttribute.d.ts +3 -2
  49. package/dist/docx/paragraphParser.d.ts +5 -1
  50. package/dist/docx/paragraphParser.js +468 -365
  51. package/dist/docx/paragraphProperties.js +123 -109
  52. package/dist/docx/paragraphPropertySource.d.ts +6 -1
  53. package/dist/docx/paragraphPropertySource.js +9 -1
  54. package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
  55. package/dist/docx/paragraphTextBoxEnrichment.js +143 -10
  56. package/dist/docx/parser.d.ts +7 -1
  57. package/dist/docx/parser.js +33 -16
  58. package/dist/docx/previewBudget.d.ts +55 -33
  59. package/dist/docx/previewBudget.js +59 -65
  60. package/dist/docx/rezip.js +41 -58
  61. package/dist/docx/runParser.d.ts +4 -1
  62. package/dist/docx/runParser.js +182 -184
  63. package/dist/docx/sdtProperties.js +87 -84
  64. package/dist/docx/sectionParser.js +169 -160
  65. package/dist/docx/selectiveSaveFlags.d.ts +10 -3
  66. package/dist/docx/selectiveSaveFlags.js +1 -1
  67. package/dist/docx/selectiveXmlPatch.d.ts +56 -34
  68. package/dist/docx/selectiveXmlPatch.js +222 -115
  69. package/dist/docx/serializer/groupTextBoxWriteBack.d.ts +12 -0
  70. package/dist/docx/serializer/groupTextBoxWriteBack.js +101 -0
  71. package/dist/docx/serializer/paragraphSerializer.js +47 -42
  72. package/dist/docx/serializer/runSerializer.d.ts +5 -1
  73. package/dist/docx/serializer/runSerializer.js +4 -3
  74. package/dist/docx/serializer/settingsSerializer.js +19 -1
  75. package/dist/docx/settingsParser.js +8 -0
  76. package/dist/docx/shapeAlternateContent.d.ts +25 -0
  77. package/dist/docx/shapeAlternateContent.js +52 -0
  78. package/dist/docx/streamingXmlParser.d.ts +6 -2
  79. package/dist/docx/streamingXmlParser.js +12 -7
  80. package/dist/docx/styleParser.js +34 -8
  81. package/dist/docx/tableParser.d.ts +21 -12
  82. package/dist/docx/tableParser.js +518 -441
  83. package/dist/docx/textBoxParser.d.ts +18 -5
  84. package/dist/docx/textBoxParser.js +12 -6
  85. package/dist/docx/verbatimCapture.d.ts +10 -8
  86. package/dist/docx/verbatimCapture.js +45 -2
  87. package/dist/docx/vmlImageParser.d.ts +15 -13
  88. package/dist/docx/vmlImageParser.js +75 -60
  89. package/dist/docx/vmlPreview.d.ts +3 -3
  90. package/dist/docx/vmlPreview.js +3 -4
  91. package/dist/docx/xmlNamespaceContext.d.ts +9 -0
  92. package/dist/docx/xmlNamespaceContext.js +54 -0
  93. package/dist/docx/xmlParser.d.ts +6 -3
  94. package/dist/docx/xmlParser.js +11 -41
  95. package/dist/docx/xmlResourceLimits.d.ts +2 -1
  96. package/dist/docx/xmlResourceLimits.js +4 -1
  97. package/dist/fonts/headlessMeasure.js +0 -0
  98. package/dist/headless-layout.d.ts +11 -2
  99. package/dist/headless-layout.js +153 -107
  100. package/dist/internal/headlessRevisionResolution.js +50 -13
  101. package/dist/internal/paragraphFormattingSerialization.js +3 -3
  102. package/dist/layout-bridge/convert/endnoteLayout.d.ts +65 -0
  103. package/dist/layout-bridge/convert/endnoteLayout.js +227 -0
  104. package/dist/layout-bridge/convert/footnoteLayout.d.ts +19 -3
  105. package/dist/layout-bridge/convert/footnoteLayout.js +40 -31
  106. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -7
  107. package/dist/layout-bridge/convert/headerFooterLayout.js +5 -20
  108. package/dist/layout-bridge/convert/paragraphMarkFormatting.d.ts +10 -0
  109. package/dist/layout-bridge/convert/paragraphMarkFormatting.js +59 -0
  110. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +29 -1
  111. package/dist/layout-bridge/convert/toFlowBlocks.js +284 -39
  112. package/dist/layout-bridge/engine/hitTest.js +5 -3
  113. package/dist/layout-bridge/engine/selectionRects.js +5 -3
  114. package/dist/layout-engine/anchorLayoutInCellCompatibility.d.ts +10 -0
  115. package/dist/layout-engine/anchorLayoutInCellCompatibility.js +22 -0
  116. package/dist/layout-engine/index.d.ts +2 -2
  117. package/dist/layout-engine/index.js +102 -14
  118. package/dist/layout-engine/justificationCompatibility.d.ts +1 -1
  119. package/dist/layout-engine/justificationCompatibility.js +9 -5
  120. package/dist/layout-engine/keep-together.d.ts +15 -6
  121. package/dist/layout-engine/keep-together.js +43 -16
  122. package/dist/layout-engine/layoutInstrumentation.d.ts +8 -2
  123. package/dist/layout-engine/layoutInstrumentation.js +8 -1
  124. package/dist/layout-engine/measure/advanceComposition.js +50 -7
  125. package/dist/layout-engine/measure/hyphenationDictionaries.d.ts +74 -0
  126. package/dist/layout-engine/measure/hyphenationDictionaries.js +138 -0
  127. package/dist/layout-engine/measure/hyphenationPreload.d.ts +11 -0
  128. package/dist/layout-engine/measure/hyphenationPreload.js +24 -0
  129. package/dist/layout-engine/measure/lineBreakProvider.d.ts +12 -2
  130. package/dist/layout-engine/measure/lineBreakProvider.js +30 -30
  131. package/dist/layout-engine/measure/lineBreaks.d.ts +6 -1
  132. package/dist/layout-engine/measure/lineBreaks.js +10 -3
  133. package/dist/layout-engine/measure/listMarkerWidth.d.ts +9 -6
  134. package/dist/layout-engine/measure/listMarkerWidth.js +20 -22
  135. package/dist/layout-engine/measure/measureBlocks.js +46 -10
  136. package/dist/layout-engine/measure/measureContainer.js +51 -8
  137. package/dist/layout-engine/measure/measureHelpers.d.ts +15 -3
  138. package/dist/layout-engine/measure/measureHelpers.js +34 -8
  139. package/dist/layout-engine/measure/measureParagraph.js +143 -101
  140. package/dist/layout-engine/measure/measureTypes.d.ts +5 -0
  141. package/dist/layout-engine/measure/smallCapsCasing.d.ts +56 -0
  142. package/dist/layout-engine/measure/smallCapsCasing.js +88 -0
  143. package/dist/layout-engine/measure/tabCalculator.d.ts +2 -0
  144. package/dist/layout-engine/measure/tabCalculator.js +2 -1
  145. package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
  146. package/dist/layout-engine/measure/tableCellFloating.js +51 -19
  147. package/dist/layout-engine/measure/tableCellGrid.d.ts +28 -3
  148. package/dist/layout-engine/measure/tableCellGrid.js +45 -5
  149. package/dist/layout-engine/measure/tableFragmentBorderGeometry.js +2 -1
  150. package/dist/layout-engine/noteAreaFlow.d.ts +19 -0
  151. package/dist/layout-engine/noteAreaFlow.js +83 -0
  152. package/dist/layout-engine/paginator.d.ts +12 -1
  153. package/dist/layout-engine/paginator.js +18 -2
  154. package/dist/layout-engine/paragraphSpacing.d.ts +4 -2
  155. package/dist/layout-engine/paragraphSpacing.js +2 -1
  156. package/dist/layout-engine/tableRowBreak.d.ts +8 -1
  157. package/dist/layout-engine/tableRowBreak.js +25 -2
  158. package/dist/layout-engine/types.d.ts +108 -3
  159. package/dist/layout-engine/types.js +96 -1
  160. package/dist/layout-painter/index.d.ts +3 -0
  161. package/dist/layout-painter/renderPage.js +32 -83
  162. package/dist/layout-painter/renderParagraph.d.ts +6 -1
  163. package/dist/layout-painter/renderParagraph.js +75 -26
  164. package/dist/layout-painter/renderTable.js +26 -13
  165. package/dist/layout-painter/renderTextBox.d.ts +12 -2
  166. package/dist/layout-painter/renderTextBox.js +26 -2
  167. package/dist/prosemirror/alternateContentAttrs.d.ts +9 -0
  168. package/dist/prosemirror/alternateContentAttrs.js +35 -0
  169. package/dist/prosemirror/attrs/index.d.ts +9 -9
  170. package/dist/prosemirror/attrs/index.js +133 -11
  171. package/dist/prosemirror/commands/comments.js +51 -41
  172. package/dist/prosemirror/contentControlRevisions.d.ts +43 -0
  173. package/dist/prosemirror/contentControlRevisions.js +84 -0
  174. package/dist/prosemirror/conversion/fromProseDoc.js +54 -23
  175. package/dist/prosemirror/conversion/markInterner.d.ts +21 -0
  176. package/dist/prosemirror/conversion/markInterner.js +90 -0
  177. package/dist/prosemirror/conversion/toProseDoc.js +117 -58
  178. package/dist/prosemirror/extensions/core/DocExtension.js +2 -1
  179. package/dist/prosemirror/extensions/core/ParagraphExtension.js +1 -0
  180. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.d.ts +7 -7
  181. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +7 -7
  182. package/dist/prosemirror/extensions/marks/markUtils.d.ts +6 -2
  183. package/dist/prosemirror/extensions/marks/markUtils.js +39 -51
  184. package/dist/prosemirror/extensions/nodes/FieldExtension.js +6 -3
  185. package/dist/prosemirror/extensions/nodes/SdtExtension.js +9 -2
  186. package/dist/prosemirror/extensions/nodes/ShapeExtension.js +1 -0
  187. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +5 -1
  188. package/dist/prosemirror/listMarker.d.ts +1 -0
  189. package/dist/prosemirror/listMarker.js +4 -2
  190. package/dist/prosemirror/listRenderingAttrs.d.ts +1 -0
  191. package/dist/prosemirror/listRenderingAttrs.js +3 -0
  192. package/dist/prosemirror/paragraphDirection.d.ts +29 -2
  193. package/dist/prosemirror/paragraphDirection.js +21 -2
  194. package/dist/prosemirror/plugins/suggestionMode.js +37 -8
  195. package/dist/prosemirror/rejoinRunCarriers.d.ts +27 -0
  196. package/dist/prosemirror/rejoinRunCarriers.js +66 -0
  197. package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +10 -0
  198. package/dist/prosemirror/runIdentityAcrossRevisions.js +90 -0
  199. package/dist/prosemirror/schema/nodes.d.ts +50 -6
  200. package/dist/prosemirror/textBoxHostParagraph.d.ts +13 -0
  201. package/dist/prosemirror/textBoxHostParagraph.js +21 -0
  202. package/dist/prosemirror/validation.js +91 -80
  203. package/dist/types/content.d.ts +2 -2
  204. package/dist/utils/createDocument.js +10 -1
  205. package/dist/utils/scriptSegments.d.ts +33 -4
  206. package/dist/utils/scriptSegments.js +39 -6
  207. package/dist/utils/textFormattingMerge.d.ts +17 -3
  208. package/dist/utils/textFormattingMerge.js +18 -12
  209. package/dist/utils/trailingText.d.ts +22 -0
  210. package/dist/utils/trailingText.js +32 -0
  211. package/package.json +2 -2
  212. package/dist/layout-engine/justifiedLineFit.d.ts +0 -7
  213. package/dist/layout-engine/justifiedLineFit.js +0 -6
@@ -4,7 +4,8 @@ import { evaluateFieldInstruction } from "../fields/evaluateField.js";
4
4
  import { applyComplexScriptFormatting, hasComplexScriptFormatting, resolveComplexScriptFormatting } from "../layout-engine/measure/complexScriptFormatting.js";
5
5
  import { getHyperlinkInstanceIndex } from "../layout-engine/measure/hyperlinkInstance.js";
6
6
  import { getListMarkerInlineWidth, getListMarkerVisualOffset, resolveListMarkerFont } from "../layout-engine/measure/listMarkerWidth.js";
7
- import { DOCX_SCRIPT_FONT_SCALE } from "../layout-engine/measure/measureHelpers.js";
7
+ import { DEFAULT_FONT_FAMILY, docxScriptFontSize } from "../layout-engine/measure/measureHelpers.js";
8
+ import { SMALL_CAPS_SCALE, smallCapsSegments } from "../layout-engine/measure/smallCapsCasing.js";
8
9
  import { calculateTabWidth } from "../layout-engine/measure/tabCalculator.js";
9
10
  import { FONT_KERNING_MODE, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode, toPaintedText } from "../layout-engine/measure/textMeasurementPolicy.js";
10
11
  import { isFloatingImageRun } from "../layout-engine/types.js";
@@ -17,7 +18,8 @@ import { getHorizontalScaleFactor } from "../utils/horizontalScale.js";
17
18
  import { resolvePhysicalParagraphInlineLayout } from "../utils/paragraphInlineLayout.js";
18
19
  import { inlineImageBoundingBox, parseRotationDegrees, rotatedBoundingBox } from "../utils/rotationBoundingBox.js";
19
20
  import { sanitizeImageSrc } from "../utils/sanitizeImageSrc.js";
20
- import { SCRIPT_CLASS, hasCjk, hasComplexScript, segmentByScript } from "../utils/scriptSegments.js";
21
+ import { SCRIPT_CLASS, eastAsiaHintApplies, hasComplexScript, hasEastAsiaSlotText, segmentByScript } from "../utils/scriptSegments.js";
22
+ import { trailingSpaceStart } from "../utils/trailingText.js";
21
23
  import { anchorTargetAttrs, sanitizeExternalUrl } from "../utils/urlSecurity.js";
22
24
  import { borderStrokeToCss, resolveParagraphBorderHorizontalOutsets } from "./borderStroke.js";
23
25
  import { planCursiveJoiners, withCursiveJoiners } from "./cursiveJoiners.js";
@@ -146,6 +148,14 @@ function getRenderableTextColor(run) {
146
148
  if (!shouldRenderTextColor(textColor, run.highlight, run.textColorSource)) return;
147
149
  return textColor.trim();
148
150
  }
151
+ /**
152
+ * The colour a list marker paints in, or undefined for the canvas text colour.
153
+ * Black and `auto` fall back to the canvas colour, as they do for runs.
154
+ */
155
+ function getRenderableListMarkerColor(color) {
156
+ if (!color || isAutomaticTextColor(color) || isDefaultBlackTextColor(color)) return;
157
+ return color.trim();
158
+ }
149
159
  function getHyperlinkTextColor(run, inheritedColor) {
150
160
  const textColor = run.color?.trim();
151
161
  if (textColor && !isAutomaticTextColor(textColor) && run.textColorSource === "direct") return textColor;
@@ -155,8 +165,7 @@ function fontSizePtToPx(fontSizePt) {
155
165
  return fontSizePt * 96 / 72;
156
166
  }
157
167
  function getRaisedRunFontSize(run) {
158
- if (run.fontSize) return `${fontSizePtToPx(run.fontSize) * DOCX_SCRIPT_FONT_SCALE}px`;
159
- return `${DOCX_SCRIPT_FONT_SCALE}em`;
168
+ return `${fontSizePtToPx(docxScriptFontSize(run.fontSize || 11))}px`;
160
169
  }
161
170
  function applyHorizontalScaleTransform(element, horizontalScale) {
162
171
  const horizontalScaleFactor = getHorizontalScaleFactor(horizontalScale);
@@ -193,7 +202,6 @@ function applyRunStyles(element, run) {
193
202
  }
194
203
  if (run.letterSpacing) element.style.letterSpacing = `${run.letterSpacing}px`;
195
204
  if (run.allCaps) element.style.textTransform = "uppercase";
196
- if (run.smallCaps) element.style.fontVariant = "small-caps";
197
205
  if (run.positionPx) element.style.verticalAlign = `${run.positionPx}px`;
198
206
  applyHorizontalScaleTransform(element, run.horizontalScale);
199
207
  element.style.fontKerning = getRunFontKerningMode(run, 11);
@@ -330,7 +338,8 @@ function applyPmPositions(element, pmStart, pmEnd) {
330
338
  }
331
339
  const NON_COMPRESSIBLE_WHITESPACE_PATTERN = /([^\S ]+)/gu;
332
340
  const HAS_NON_COMPRESSIBLE_WHITESPACE_PATTERN = /[^\S ]/u;
333
- function appendPaintedText({ host, text, doc, contractedWordSpacing }) {
341
+ /** Fill `host` with `text`, splitting it into word-spacing segments as needed. */
342
+ function fillPaintedText({ host, text, doc, contractedWordSpacing }) {
334
343
  if (!contractedWordSpacing) {
335
344
  host.textContent = text;
336
345
  return;
@@ -349,6 +358,39 @@ function appendPaintedText({ host, text, doc, contractedWordSpacing }) {
349
358
  host.append(segmentEl);
350
359
  }
351
360
  }
361
+ function appendPaintedText({ host, text, doc, contractedWordSpacing, smallCapsFontSizePx }) {
362
+ const wordSpacing = contractedWordSpacing === void 0 ? {} : { contractedWordSpacing };
363
+ if (smallCapsFontSizePx === void 0) {
364
+ fillPaintedText({
365
+ host,
366
+ text,
367
+ doc,
368
+ ...wordSpacing
369
+ });
370
+ return;
371
+ }
372
+ const segments = smallCapsSegments(text);
373
+ if (!segments.some((segment) => segment.small)) {
374
+ fillPaintedText({
375
+ host,
376
+ text,
377
+ doc,
378
+ ...wordSpacing
379
+ });
380
+ return;
381
+ }
382
+ for (const segment of segments) {
383
+ const segmentEl = doc.createElement("span");
384
+ if (segment.small) segmentEl.style.fontSize = `${smallCapsFontSizePx}px`;
385
+ fillPaintedText({
386
+ host: segmentEl,
387
+ text: segment.text,
388
+ doc,
389
+ ...wordSpacing
390
+ });
391
+ host.append(segmentEl);
392
+ }
393
+ }
352
394
  function renderTextRun(run, doc, options) {
353
395
  const span = doc.createElement("span");
354
396
  span.className = `${PARAGRAPH_CLASS_NAMES.run} ${PARAGRAPH_CLASS_NAMES.text}`;
@@ -366,6 +408,7 @@ function renderTextRun(run, doc, options) {
366
408
  applyRunStyles(span, run);
367
409
  applyPmPositions(span, run.pmStart, run.pmEnd);
368
410
  const paintedText = toPaintedText(run.text);
411
+ const smallCapsFontSizePx = run.smallCaps && !run.allCaps ? fontSizePtToPx(run.fontSize || 11) * SMALL_CAPS_SCALE : void 0;
369
412
  const visualSpaceContractionPx = options?.visualSpaceContractionPx;
370
413
  const effectiveHorizontalScaleFactor = getHorizontalScaleFactor(run.horizontalScale) * (options?.ancestorHorizontalScaleFactor ?? 1);
371
414
  const contractedWordSpacing = visualSpaceContractionPx === void 0 || effectiveHorizontalScaleFactor === 0 ? void 0 : `${-visualSpaceContractionPx / effectiveHorizontalScaleFactor}px`;
@@ -385,7 +428,8 @@ function renderTextRun(run, doc, options) {
385
428
  host: anchor,
386
429
  text: paintedText,
387
430
  doc,
388
- ...contractedWordSpacing !== void 0 ? { contractedWordSpacing } : {}
431
+ ...contractedWordSpacing !== void 0 ? { contractedWordSpacing } : {},
432
+ ...smallCapsFontSizePx !== void 0 ? { smallCapsFontSizePx } : {}
389
433
  });
390
434
  if (!run.hyperlink.noDefaultStyle) {
391
435
  const hyperlinkColor = getHyperlinkTextColor(run, span.style.color);
@@ -398,7 +442,8 @@ function renderTextRun(run, doc, options) {
398
442
  host: span,
399
443
  text: paintedText,
400
444
  doc,
401
- ...contractedWordSpacing !== void 0 ? { contractedWordSpacing } : {}
445
+ ...contractedWordSpacing !== void 0 ? { contractedWordSpacing } : {},
446
+ ...smallCapsFontSizePx !== void 0 ? { smallCapsFontSizePx } : {}
402
447
  });
403
448
  applyWhitespaceUnderline(span, run);
404
449
  return span;
@@ -717,7 +762,7 @@ function renderFieldRun(run, doc, { context, visualSpaceContractionPx }) {
717
762
  const horizontalScaleFactor = getHorizontalScaleFactor(resolvedRun.horizontalScale);
718
763
  applyHorizontalScaleTransform(wrapper, resolvedRun.horizontalScale);
719
764
  const { pmStart: _pmStart, pmEnd: _pmEnd, horizontalScale: _horizontalScale, ...segmentBase } = resolvedRun;
720
- for (const segment of segmentByScript(text)) {
765
+ for (const segment of segmentByScript(text, eastAsiaHintApplies(resolvedRun))) {
721
766
  const segmentRun = formatTextRunForScript({
722
767
  ...segmentBase,
723
768
  text: segment.text
@@ -864,7 +909,7 @@ const formatTextRunForScript = (run, script) => {
864
909
  */
865
910
  const needsPerScriptSpans = (run) => {
866
911
  if (run.letterSpacing) return false;
867
- if (run.eastAsiaFontFamily !== void 0 && hasCjk(run.text)) return true;
912
+ if (run.eastAsiaFontFamily !== void 0 && hasEastAsiaSlotText(run.text, eastAsiaHintApplies(run))) return true;
868
913
  return hasComplexScriptFormatting(run) && hasComplexScript(run.text);
869
914
  };
870
915
  /**
@@ -888,7 +933,7 @@ function splitTextRunsByEastAsia(runs) {
888
933
  continue;
889
934
  }
890
935
  let offset = 0;
891
- for (const segment of segmentByScript(run.text)) {
936
+ for (const segment of segmentByScript(run.text, eastAsiaHintApplies(run))) {
892
937
  result.push(formatTextRunForScript({
893
938
  ...run,
894
939
  text: segment.text,
@@ -933,13 +978,13 @@ const splitCollapsibleLineEdgeSpaces = (sourceRuns, collapseLeading) => {
933
978
  const run = runs[index];
934
979
  if (!run || !isTextRun(run)) break;
935
980
  if (run.text.length === 0) continue;
936
- const trailingSpaces = / +$/u.exec(run.text);
937
- if (!trailingSpaces || paintsLineEdgeSpaces(run)) break;
938
- if (trailingSpaces.index === 0) {
981
+ const spacesStart = trailingSpaceStart(run.text);
982
+ if (spacesStart === run.text.length || paintsLineEdgeSpaces(run)) break;
983
+ if (spacesStart === 0) {
939
984
  collapsedTrailingRuns.add(run);
940
985
  continue;
941
986
  }
942
- const [leading, trailing] = splitTextRunAt(run, trailingSpaces.index);
987
+ const [leading, trailing] = splitTextRunAt(run, spacesStart);
943
988
  runs.splice(index, 1, leading, trailing);
944
989
  collapsedTrailingRuns.add(trailing);
945
990
  break;
@@ -1005,6 +1050,7 @@ function runMeasureStyle(run) {
1005
1050
  ...run.alternateFontFamily !== void 0 ? { alternateFontFamily: run.alternateFontFamily } : {},
1006
1051
  ...run.eastAsiaFontFamily !== void 0 ? { eastAsiaFontFamily: run.eastAsiaFontFamily } : {},
1007
1052
  ...run.eastAsiaAlternateFontFamily !== void 0 ? { eastAsiaAlternateFontFamily: run.eastAsiaAlternateFontFamily } : {},
1053
+ ...eastAsiaHintApplies(run) ? { eastAsiaHint: true } : {},
1008
1054
  ...run.complexScriptFontFamily !== void 0 ? { complexScriptFontFamily: run.complexScriptFontFamily } : {},
1009
1055
  ...run.complexScriptAlternateFontFamily !== void 0 ? { complexScriptAlternateFontFamily: run.complexScriptAlternateFontFamily } : {},
1010
1056
  ...run.complexScriptFontSize !== void 0 ? { complexScriptFontSize: run.complexScriptFontSize } : {},
@@ -1028,10 +1074,10 @@ function measureFollowingContentWidth(runs, tabRunIndex, measureText, context) {
1028
1074
  if (isTabRun(run) || isLineBreakRun(run)) break;
1029
1075
  if (isTextRun(run)) {
1030
1076
  const text = run.allCaps ? run.text.toLocaleUpperCase() : run.text;
1031
- width += measureText(text, run.fontSize ?? 11, run.fontFamily ?? "Calibri", runMeasureStyle(run)) * getHorizontalScaleFactor(run.horizontalScale);
1077
+ width += measureText(text, run.fontSize ?? 11, run.fontFamily ?? "Times New Roman", runMeasureStyle(run)) * getHorizontalScaleFactor(run.horizontalScale);
1032
1078
  } else if (isFieldRun(run)) {
1033
1079
  const fieldText = resolveFieldText(run, context);
1034
- width += measureText(run.allCaps ? fieldText.toLocaleUpperCase() : fieldText, run.fontSize ?? 11, run.fontFamily ?? "Calibri", runMeasureStyle(run)) * getHorizontalScaleFactor(run.horizontalScale);
1080
+ width += measureText(run.allCaps ? fieldText.toLocaleUpperCase() : fieldText, run.fontSize ?? 11, run.fontFamily ?? "Times New Roman", runMeasureStyle(run)) * getHorizontalScaleFactor(run.horizontalScale);
1035
1081
  } else if (isImageRun(run) && !isFloatingImageRun(run)) width += inlineImageBoundingBox(run).width || 0;
1036
1082
  else if (isMathRun(run)) width += measureText(run.plainText, run.fontSize ?? 11, run.fontFamily ?? "Cambria Math", runMeasureStyle(run)) * getHorizontalScaleFactor(run.horizontalScale);
1037
1083
  }
@@ -1061,7 +1107,7 @@ function measureDecimalPrefixWidth(runs, tabRunIndex, decimalIndex, measureText,
1061
1107
  const take = Math.min(runText.length, decimalIndex - consumed);
1062
1108
  if (take > 0) {
1063
1109
  const horizontalScaleFactor = getHorizontalScaleFactor(run.horizontalScale);
1064
- width += measureText(runText.slice(0, take), run.fontSize ?? 11, run.fontFamily ?? "Calibri", runMeasureStyle(run)) * horizontalScaleFactor;
1110
+ width += measureText(runText.slice(0, take), run.fontSize ?? 11, run.fontFamily ?? "Times New Roman", runMeasureStyle(run)) * horizontalScaleFactor;
1065
1111
  }
1066
1112
  consumed += runText.length;
1067
1113
  }
@@ -1113,7 +1159,7 @@ function applyLetterSpacingToMeasuredWidth(width, text, letterSpacing) {
1113
1159
  }
1114
1160
  function createTextMeasurer(doc) {
1115
1161
  const ctx = doc.createElement("canvas").getContext("2d");
1116
- return (text, fontSize = 11, fontFamily = "Calibri", style = {}) => {
1162
+ return (text, fontSize = 11, fontFamily = DEFAULT_FONT_FAMILY, style = {}) => {
1117
1163
  if (!ctx) return applyLetterSpacingToMeasuredWidth(text.length * 7, text, style.letterSpacing);
1118
1164
  ctx.fontKerning = getFontKerningMode(style);
1119
1165
  const fontString = (family, size, bold, italic, alternateFamily) => {
@@ -1131,9 +1177,9 @@ function createTextMeasurer(doc) {
1131
1177
  ctx.font = complexFont;
1132
1178
  return applyLetterSpacingToMeasuredWidth(ctx.measureText(text).width, text, style.letterSpacing);
1133
1179
  }
1134
- if (!style.letterSpacing && (style.eastAsiaFontFamily && hasCjk(text) || hasComplexScriptFormatting(style) && hasComplexScript(text))) {
1180
+ if (!style.letterSpacing && (style.eastAsiaFontFamily && hasEastAsiaSlotText(text, style.eastAsiaHint) || hasComplexScriptFormatting(style) && hasComplexScript(text))) {
1135
1181
  let segmentedWidth = 0;
1136
- for (const segment of segmentByScript(text)) {
1182
+ for (const segment of segmentByScript(text, style.eastAsiaHint)) {
1137
1183
  if (segment.script === SCRIPT_CLASS.eastAsia && style.eastAsiaFontFamily) ctx.font = fontString(style.eastAsiaFontFamily, fontSize, style.bold, style.italic, style.eastAsiaAlternateFontFamily);
1138
1184
  else if (segment.script === SCRIPT_CLASS.complex) ctx.font = complexFont;
1139
1185
  else ctx.font = baseFont;
@@ -1189,7 +1235,7 @@ function renderLine(block, line, alignment, doc, options) {
1189
1235
  if (collapsedLeadingSpaceRuns.has(run)) runEl.dataset["collapsedLeadingSpaces"] = "true";
1190
1236
  if (collapsedTrailingSpaceRuns.has(run)) runEl.dataset["collapsedTrailingSpaces"] = "true";
1191
1237
  if (isCollapsedLineEdgeSpaceRun(run)) {
1192
- const spaceAdvance = (collapsedSpaceMeasureText?.(" ", run.fontSize || 11, run.fontFamily || "Calibri", runMeasureStyle(run)) ?? 0) * getHorizontalScaleFactor(run.horizontalScale);
1238
+ const spaceAdvance = (collapsedSpaceMeasureText?.(" ", run.fontSize || 11, run.fontFamily || "Times New Roman", runMeasureStyle(run)) ?? 0) * getHorizontalScaleFactor(run.horizontalScale);
1193
1239
  runEl.dataset["collapsedSpaceAdvance"] = String(spaceAdvance);
1194
1240
  runEl.style.fontSize = "0";
1195
1241
  runEl.style.letterSpacing = "0";
@@ -1340,7 +1386,8 @@ function renderLine(block, line, alignment, doc, options) {
1340
1386
  }
1341
1387
  let tabWidth = tabResult.width;
1342
1388
  const landsOnLeftIndent = tabResult.alignment === "start" && tabLeftIndentPx > 0 && Math.abs(currentX + tabWidth - tabLeftIndentPx) <= RIGHT_EDGE_EPSILON_PX;
1343
- if (!preservesAuthoredEndStop && !landsOnLeftIndent && lineRightEdgeX !== void 0 && canClampTabToRightEdge(tabResult.alignment, i > 0, runsForLine.slice(0, i).some(isTabRun), options?.isLastLine === true) && currentX + tabWidth + followingWidthForCheck > lineRightEdgeX) tabWidth = Math.max(1, lineRightEdgeX - currentX - followingWidthForCheck);
1389
+ const followingContentEndsOnLine = options?.isLastLine === true || runsForLine.slice(i + 1).some((next) => isTabRun(next) || isLineBreakRun(next));
1390
+ if (!(preservesAuthoredEndStop || activeContentRightEdge !== void 0 && tabResult.explicit === true && followingContentEndsOnLine && authoredEndpoint <= activeContentRightEdge + RIGHT_EDGE_EPSILON_PX) && !landsOnLeftIndent && lineRightEdgeX !== void 0 && canClampTabToRightEdge(tabResult.alignment, i > 0, runsForLine.slice(0, i).some(isTabRun), options?.isLastLine === true) && currentX + tabWidth + followingWidthForCheck > lineRightEdgeX) tabWidth = Math.max(1, lineRightEdgeX - currentX - followingWidthForCheck);
1344
1391
  const tabEl = renderTabRun(run, doc, tabWidth, tabResult.leader);
1345
1392
  lineEl.append(tabEl);
1346
1393
  currentX += tabWidth;
@@ -1350,7 +1397,7 @@ function renderLine(block, line, alignment, doc, options) {
1350
1397
  if (isCollapsedLineEdgeSpaceRun(run)) continue;
1351
1398
  if (!measureText) continue;
1352
1399
  const fontSize = run.fontSize || 11;
1353
- const fontFamily = run.fontFamily || "Calibri";
1400
+ const fontFamily = run.fontFamily || "Times New Roman";
1354
1401
  const measuredWidth = measureText(run.allCaps ? run.text.toLocaleUpperCase() : run.text, fontSize, fontFamily, runMeasureStyle(run));
1355
1402
  const runContractionPx = runVisualSpaceContraction({
1356
1403
  text: run.text,
@@ -1383,7 +1430,7 @@ function renderLine(block, line, alignment, doc, options) {
1383
1430
  if (!measureText) continue;
1384
1431
  const fieldText = resolveFieldText(run, options.context);
1385
1432
  const fontSize = run.fontSize || 11;
1386
- const fontFamily = run.fontFamily || "Calibri";
1433
+ const fontFamily = run.fontFamily || "Times New Roman";
1387
1434
  const measuredWidth = measureText(run.allCaps ? fieldText.toLocaleUpperCase() : fieldText, fontSize, fontFamily, runMeasureStyle(run));
1388
1435
  const runContractionPx = runVisualSpaceContraction({
1389
1436
  text: fieldText,
@@ -1628,6 +1675,8 @@ function renderListMarker({ marker, inlineWidth, visualOffset, doc, formatting,
1628
1675
  if (formatting.bold !== void 0) span.style.fontWeight = formatting.bold ? "700" : "normal";
1629
1676
  if (formatting.italic !== void 0) span.style.fontStyle = formatting.italic ? "italic" : "normal";
1630
1677
  if (formatting.rtl !== void 0) span.dir = formatting.rtl ? "rtl" : "ltr";
1678
+ const markerColor = revision ? void 0 : getRenderableListMarkerColor(formatting.color);
1679
+ if (markerColor) setAuthoredTextColor(span.style, markerColor);
1631
1680
  span.style.textAlign = physicalStart;
1632
1681
  span.style.textAlignLast = physicalStart;
1633
1682
  span.style.boxSizing = "border-box";
@@ -1673,4 +1722,4 @@ function renderListMarker({ marker, inlineWidth, visualOffset, doc, formatting,
1673
1722
  return span;
1674
1723
  }
1675
1724
  //#endregion
1676
- export { PARAGRAPH_CLASS_NAMES, UNICODE_BIDI_BY_WRAPPER_CONTROL, getLeaderChar, getRenderableTextColor, paragraphHasTrackedChanges, renderLine, renderParagraphFragment, sliceRunsForLine, splitCollapsibleLineEdgeSpaces, splitTextRunsByEastAsia, startsAfterSoftWrap };
1725
+ export { PARAGRAPH_CLASS_NAMES, UNICODE_BIDI_BY_WRAPPER_CONTROL, getLeaderChar, getRenderableListMarkerColor, getRenderableTextColor, paragraphHasTrackedChanges, renderLine, renderParagraphFragment, sliceRunsForLine, splitCollapsibleLineEdgeSpaces, splitTextRunsByEastAsia, startsAfterSoftWrap };
@@ -1,7 +1,7 @@
1
1
  import { measureParagraph } from "../layout-engine/measure/measureParagraph.js";
2
2
  import { buildTableCellFloatingZones, getTableCellContentWidth, getTableCellFloatingImages } from "../layout-engine/measure/tableCellFloating.js";
3
3
  import { createTableCellFlowState, finishTableCellFlow, placeTableCellBlock } from "../layout-engine/measure/tableCellFlow.js";
4
- import { buildTableCellGrid, buildTableCellPlacements, getSourceCellAt } from "../layout-engine/measure/tableCellGrid.js";
4
+ import { buildTableCellGrid, buildTableCellPlacements, getSourceCellAt, getTableCellSpacingInsetsY } from "../layout-engine/measure/tableCellGrid.js";
5
5
  import { tableFragmentBottomBorders } from "../layout-engine/measure/tableFragmentBorderGeometry.js";
6
6
  import { resolveTableInlineOffset, resolveTableInlinePlacement } from "../layout-engine/measure/tableInlinePlacement.js";
7
7
  import { isFloatingImageRun, isFloatingTextBoxBlock, resolveTableCellPadding, tableColumnsArePinned } from "../layout-engine/types.js";
@@ -293,7 +293,8 @@ function renderNestedTable(block, measure, context, doc, frameWidth = context.co
293
293
  const cellPlacements = buildTableCellPlacements({
294
294
  grid: cellGrid,
295
295
  columnWidths: measure.columnWidths,
296
- bidi: block.bidi === true
296
+ bidi: block.bidi === true,
297
+ cellSpacing: block.cellSpacing
297
298
  });
298
299
  let y = 0;
299
300
  for (let rowIndex = 0; rowIndex < block.rows.length; rowIndex++) {
@@ -318,6 +319,7 @@ function renderNestedTable(block, measure, context, doc, frameWidth = context.co
318
319
  columnsPinned,
319
320
  cellGrid,
320
321
  cellPlacements,
322
+ cellSpacing: block.cellSpacing,
321
323
  inlineOffset: resolveTableInlineOffset({
322
324
  table: block,
323
325
  rowJustification: row.justification,
@@ -375,12 +377,12 @@ function renderCellDiagonalBorder({ border, direction, cellWidth, cellHeight, do
375
377
  }
376
378
  return line;
377
379
  }
378
- function renderTableCell({ cell, cellMeasure, x, width, rowHeight, borderFlags, columnsPinned, context, doc, contentClip, pageContentPosition }) {
380
+ function renderTableCell({ cell, cellMeasure, x, y = 0, width, rowHeight, borderFlags, columnsPinned, context, doc, contentClip, pageContentPosition }) {
379
381
  const cellEl = doc.createElement("div");
380
382
  cellEl.className = TABLE_CLASS_NAMES.cell;
381
383
  cellEl.style.position = "absolute";
382
384
  cellEl.style.left = `${x}px`;
383
- cellEl.style.top = "0";
385
+ cellEl.style.top = `${y}px`;
384
386
  cellEl.style.width = `${width}px`;
385
387
  cellEl.style.height = `${rowHeight}px`;
386
388
  cellEl.style.overflow = "hidden";
@@ -499,7 +501,8 @@ const continuationRowBottomBorderOffsets = (fragment, block, measure, cellGrid)
499
501
  snapAfterRow
500
502
  });
501
503
  };
502
- function renderTableRow({ row, rowMeasure, rowIndex, y, columnWidths, totalRows, context, doc, rowYPositions, isFirstRowInFragment, bidi = false, columnsPinned = false, cellGrid, cellPlacements, contentClip, pageContentPosition, inlineOffset = 0, bottomBorderOffset = 0 }) {
504
+ function renderTableRow({ row, rowMeasure, rowIndex, y, columnWidths, totalRows, context, doc, rowYPositions, isFirstRowInFragment, bidi = false, columnsPinned = false, cellGrid, cellPlacements, cellSpacing, contentClip, pageContentPosition, inlineOffset = 0, bottomBorderOffset = 0 }) {
505
+ const bordersSeparated = (cellSpacing ?? 0) > 0;
503
506
  const rowEl = doc.createElement("div");
504
507
  rowEl.className = TABLE_CLASS_NAMES.row;
505
508
  rowEl.style.position = "absolute";
@@ -530,15 +533,22 @@ function renderTableRow({ row, rowMeasure, rowIndex, y, columnWidths, totalRows,
530
533
  const isLastCol = bidi ? atLogicalStart : atLogicalEnd;
531
534
  const aboveCell = getSourceCellAt(cellGrid, rowIndex - 1, columnIndex);
532
535
  const leftCell = getSourceCellAt(cellGrid, rowIndex, bidi ? columnIndex + colSpan : columnIndex - 1);
533
- const drawTop = isFirstRow || !hasVisibleBorder(aboveCell?.borders?.bottom);
534
- const drawLeft = isFirstCol || !hasVisibleBorder(leftCell?.borders?.right);
536
+ const drawTop = bordersSeparated || isFirstRow || !hasVisibleBorder(aboveCell?.borders?.bottom);
537
+ const drawLeft = bordersSeparated || isFirstCol || !hasVisibleBorder(leftCell?.borders?.right);
538
+ const spacing = getTableCellSpacingInsetsY(cellSpacing, rowIndex, rowSpan, totalRows);
539
+ const boxHeight = Math.max(0, cellHeight - spacing.top - spacing.bottom);
540
+ const boxClip = contentClip ? {
541
+ top: contentClip.top - spacing.top,
542
+ bottom: contentClip.bottom - spacing.top
543
+ } : void 0;
535
544
  const paintBottomBorderSeparately = bottomBorderOffset > 0 && hasVisibleBorder(cell.borders?.bottom);
536
545
  const cellEl = renderTableCell({
537
546
  cell,
538
547
  cellMeasure,
539
548
  x: cellLeft,
549
+ y: spacing.top,
540
550
  width,
541
- rowHeight: cellHeight,
551
+ rowHeight: boxHeight,
542
552
  borderFlags: {
543
553
  drawTop,
544
554
  drawBottom: !paintBottomBorderSeparately,
@@ -549,11 +559,11 @@ function renderTableRow({ row, rowMeasure, rowIndex, y, columnWidths, totalRows,
549
559
  columnsPinned,
550
560
  context,
551
561
  doc,
552
- ...contentClip ? { contentClip } : {},
562
+ ...boxClip ? { contentClip: boxClip } : {},
553
563
  ...pageContentPosition ? { pageContentPosition: {
554
564
  geometry: pageContentPosition.geometry,
555
565
  x: pageContentPosition.x + inlineOffset + cellLeft,
556
- y: pageContentPosition.y
566
+ y: pageContentPosition.y + spacing.top
557
567
  } } : {}
558
568
  });
559
569
  cellEl.dataset["cellIndex"] = String(cellIndex);
@@ -565,9 +575,9 @@ function renderTableRow({ row, rowMeasure, rowIndex, y, columnWidths, totalRows,
565
575
  bottomBorderEl.className = CELL_BOTTOM_BORDER_CLASS;
566
576
  bottomBorderEl.style.position = "absolute";
567
577
  bottomBorderEl.style.left = `${cellLeft}px`;
568
- bottomBorderEl.style.top = "0";
578
+ bottomBorderEl.style.top = `${spacing.top}px`;
569
579
  bottomBorderEl.style.width = `${width}px`;
570
- bottomBorderEl.style.height = `${cellHeight + bottomBorderOffset}px`;
580
+ bottomBorderEl.style.height = `${boxHeight + bottomBorderOffset}px`;
571
581
  bottomBorderEl.style.boxSizing = "border-box";
572
582
  bottomBorderEl.style.pointerEvents = "none";
573
583
  bottomBorderEl.style.zIndex = "1";
@@ -637,7 +647,8 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
637
647
  const cellPlacements = buildTableCellPlacements({
638
648
  grid: cellGrid,
639
649
  columnWidths: measure.columnWidths,
640
- bidi: block.bidi === true
650
+ bidi: block.bidi === true,
651
+ cellSpacing: block.cellSpacing
641
652
  });
642
653
  const contentRowBottomBorderOffsets = continuationRowBottomBorderOffsets(fragment, block, measure, cellGrid);
643
654
  const headerRowCount = fragment.headerRowCount ?? 0;
@@ -665,6 +676,7 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
665
676
  columnsPinned,
666
677
  cellGrid,
667
678
  cellPlacements,
679
+ cellSpacing: block.cellSpacing,
668
680
  ...tablePageContentPosition ? { pageContentPosition: {
669
681
  ...tablePageContentPosition,
670
682
  y: tablePageContentPosition.y + y
@@ -718,6 +730,7 @@ function renderTableFragment(fragment, block, measure, context, options = {}) {
718
730
  columnsPinned,
719
731
  cellGrid,
720
732
  cellPlacements,
733
+ cellSpacing: block.cellSpacing,
721
734
  bottomBorderOffset: contentRowBottomBorderOffsets[rowIndex - fragment.fromRow] ?? 0,
722
735
  ...contentClip ? { contentClip } : {},
723
736
  ...tablePageContentPosition ? { pageContentPosition: {
@@ -1,4 +1,4 @@
1
- import { TableBlock, TableMeasure, TextBoxBlock, TextBoxFragment, TextBoxMeasure } from "../layout-engine/types.js";
1
+ import { TableBlock, TableMeasure, TextBoxBlock, TextBoxFragment, TextBoxGradientFill, TextBoxMeasure } from "../layout-engine/types.js";
2
2
  import { RenderContext } from "./renderUtils.js";
3
3
  //#region src/layout-painter/renderTextBox.d.ts
4
4
  /**
@@ -14,9 +14,19 @@ type RenderTextBoxFragmentOptions = {
14
14
  document?: Document;
15
15
  renderTable?: (block: TableBlock, measure: TableMeasure, context: RenderContext, document: Document, frameWidth?: number) => HTMLElement;
16
16
  };
17
+ /**
18
+ * The CSS angle of a DrawingML linear gradient over a `width` x `height` box.
19
+ *
20
+ * DrawingML measures `a:lin@ang` clockwise from the positive x axis; CSS
21
+ * measures clockwise from "to top", a quarter turn earlier. A `scaled` angle is
22
+ * stated in the unit square: stretching that square to the box keeps each
23
+ * isoline through the same corners, so the gradient runs perpendicular to the
24
+ * stretched isoline rather than along the stated angle.
25
+ */
26
+ declare function cssLinearGradientAngle(fill: Pick<TextBoxGradientFill, "angle" | "scaled">, width: number, height: number): number;
17
27
  /**
18
28
  * Render a text box fragment to DOM
19
29
  */
20
30
  declare function renderTextBoxFragment(fragment: TextBoxFragment, block: TextBoxBlock, measure: TextBoxMeasure, context: RenderContext, options?: RenderTextBoxFragmentOptions): HTMLElement;
21
31
  //#endregion
22
- export { RenderTextBoxFragmentOptions, TEXTBOX_CLASS_NAMES, renderTextBoxFragment };
32
+ export { RenderTextBoxFragmentOptions, TEXTBOX_CLASS_NAMES, cssLinearGradientAngle, renderTextBoxFragment };
@@ -10,7 +10,7 @@ import { panic } from "better-result";
10
10
  * Text Box Renderer
11
11
  *
12
12
  * Renders text box fragments to DOM. Handles:
13
- * - Background fill color
13
+ * - Background fill color or linear gradient
14
14
  * - Border/outline
15
15
  * - Internal padding (margins)
16
16
  * - Block content inside the box (using pre-measured data)
@@ -20,6 +20,29 @@ import { panic } from "better-result";
20
20
  */
21
21
  const TEXTBOX_CLASS_NAMES = { textBox: "layout-textbox" };
22
22
  /**
23
+ * The CSS angle of a DrawingML linear gradient over a `width` x `height` box.
24
+ *
25
+ * DrawingML measures `a:lin@ang` clockwise from the positive x axis; CSS
26
+ * measures clockwise from "to top", a quarter turn earlier. A `scaled` angle is
27
+ * stated in the unit square: stretching that square to the box keeps each
28
+ * isoline through the same corners, so the gradient runs perpendicular to the
29
+ * stretched isoline rather than along the stated angle.
30
+ */
31
+ function cssLinearGradientAngle(fill, width, height) {
32
+ const radians = fill.angle * Math.PI / 180;
33
+ return ((90 + (fill.scaled && width > 0 && height > 0 ? Math.atan2(width * Math.sin(radians), height * Math.cos(radians)) : radians) * 180 / Math.PI) % 360 + 360) % 360;
34
+ }
35
+ const formatCssNumber = (value) => String(Math.round(value * 1e3) / 1e3);
36
+ function paintGradientFill(style, fill, width, height) {
37
+ const [first] = fill.stops;
38
+ if (first === void 0) return;
39
+ if (fill.stops.length === 1) {
40
+ setAuthoredBackgroundColor(style, first.color);
41
+ return;
42
+ }
43
+ style.backgroundImage = `linear-gradient(${formatCssNumber(cssLinearGradientAngle(fill, width, height))}deg, ${fill.stops.map((stop) => `${stop.color} ${formatCssNumber(stop.offset * 100)}%`).join(", ")})`;
44
+ }
45
+ /**
23
46
  * Render a text box fragment to DOM
24
47
  */
25
48
  function renderTextBoxFragment(fragment, block, measure, context, options = {}) {
@@ -32,6 +55,7 @@ function renderTextBoxFragment(fragment, block, measure, context, options = {})
32
55
  containerEl.style.overflow = block.textWrap === "none" ? "visible" : "hidden";
33
56
  containerEl.style.boxSizing = "border-box";
34
57
  if (block.fillColor) setAuthoredBackgroundColor(containerEl.style, block.fillColor);
58
+ else if (block.fillGradient) paintGradientFill(containerEl.style, block.fillGradient, fragment.width, fragment.height);
35
59
  const outlineDash = presetDashForOutlineAttr(block.outlineStyle) ?? "solid";
36
60
  if (block.outlineWidth && block.outlineWidth > 0 && outlineDash !== "none") {
37
61
  const color = block.outlineColor || "#000000";
@@ -93,4 +117,4 @@ function renderTextBoxFragment(fragment, block, measure, context, options = {})
93
117
  return containerEl;
94
118
  }
95
119
  //#endregion
96
- export { TEXTBOX_CLASS_NAMES, renderTextBoxFragment };
120
+ export { TEXTBOX_CLASS_NAMES, cssLinearGradientAngle, renderTextBoxFragment };
@@ -0,0 +1,9 @@
1
+ import { AlternateContentAttrs } from "./schema/nodes.js";
2
+ import { Node } from "prosemirror-model";
3
+ //#region src/prosemirror/alternateContentAttrs.d.ts
4
+ /** Attach a captured `mc:AlternateContent` to a freshly projected node. */
5
+ declare const withAlternateContent: (node: Node, xml: string | undefined) => Node;
6
+ /** The captured element, while the node is still the one it was attached to. */
7
+ declare const unchangedAlternateContentXml: (node: Node, captured: AlternateContentAttrs | undefined) => string | undefined;
8
+ //#endregion
9
+ export { unchangedAlternateContentXml, withAlternateContent };
@@ -0,0 +1,35 @@
1
+ import { canonicalJson } from "../utils/canonicalJson.js";
2
+ //#region src/prosemirror/alternateContentAttrs.ts
3
+ const ALTERNATE_CONTENT_ATTR = "_docxAlternateContent";
4
+ /**
5
+ * What the node says about the shape: its attributes and, for a text box, its
6
+ * content. Marks are left out; they carry the run's properties, which sit
7
+ * outside `mc:AlternateContent`.
8
+ *
9
+ * The projection from the model is lossy, so the node is compared with itself
10
+ * as it was projected rather than the rebuilt shape with the parsed one: a
11
+ * round trip that changed nothing would otherwise read as an edit.
12
+ */
13
+ const nodeFingerprint = (node) => {
14
+ const { [ALTERNATE_CONTENT_ATTR]: _captured, ...attrs } = node.attrs;
15
+ return canonicalJson({
16
+ attrs,
17
+ content: node.content.toJSON()
18
+ });
19
+ };
20
+ /** Attach a captured `mc:AlternateContent` to a freshly projected node. */
21
+ const withAlternateContent = (node, xml) => {
22
+ if (xml === void 0) return node;
23
+ const alternateContent = {
24
+ xml,
25
+ fingerprint: nodeFingerprint(node)
26
+ };
27
+ return node.type.create({
28
+ ...node.attrs,
29
+ [ALTERNATE_CONTENT_ATTR]: alternateContent
30
+ }, node.content, node.marks);
31
+ };
32
+ /** The captured element, while the node is still the one it was attached to. */
33
+ const unchangedAlternateContentXml = (node, captured) => captured !== void 0 && captured.fingerprint === nodeFingerprint(node) ? captured.xml : void 0;
34
+ //#endregion
35
+ export { unchangedAlternateContentXml, withAlternateContent };
@@ -4,18 +4,18 @@ import "../schema/index.js";
4
4
  import { Mark, Node } from "prosemirror-model";
5
5
  //#region src/prosemirror/attrs/index.d.ts
6
6
  type ProseMirrorAttrIssue = {
7
- path: string;
8
- message: string;
7
+ readonly path: string;
8
+ readonly message: string;
9
9
  };
10
10
  type ReadProseMirrorAttrsResult<T> = {
11
11
  ok: true;
12
12
  value: T;
13
13
  } | {
14
14
  ok: false;
15
- issues: ProseMirrorAttrIssue[];
15
+ issues: readonly ProseMirrorAttrIssue[];
16
16
  };
17
- declare const readParagraphAttrs: (node: Node) => ReadProseMirrorAttrsResult<ParagraphAttrs>;
18
- declare const expectParagraphAttrs: (node: Node) => ParagraphAttrs;
17
+ declare const readParagraphAttrs: (node: Node) => ReadProseMirrorAttrsResult<Readonly<ParagraphAttrs>>;
18
+ declare const expectParagraphAttrs: (node: Node) => Readonly<ParagraphAttrs>;
19
19
  declare const readHardBreakAttrs: (node: Node) => ReadProseMirrorAttrsResult<HardBreakAttrs>;
20
20
  declare const expectHardBreakAttrs: (node: Node) => HardBreakAttrs;
21
21
  declare const readPageBreakRunAttrs: (node: Node) => ReadProseMirrorAttrsResult<PageBreakRunAttrs>;
@@ -28,10 +28,10 @@ declare const readPreservedXmlAttrs: (node: Node) => ReadProseMirrorAttrsResult<
28
28
  declare const expectPreservedXmlAttrs: (node: Node) => PreservedXmlAttrs;
29
29
  declare const readPreservedBlockAttrs: (node: Node) => ReadProseMirrorAttrsResult<PreservedBlockAttrs>;
30
30
  declare const expectPreservedBlockAttrs: (node: Node) => PreservedBlockAttrs;
31
- declare const readTableAttrs: (node: Node) => ReadProseMirrorAttrsResult<TableAttrs>;
32
- declare const expectTableAttrs: (node: Node) => TableAttrs;
33
- declare const readTableRowAttrs: (node: Node) => ReadProseMirrorAttrsResult<TableRowAttrs>;
34
- declare const expectTableRowAttrs: (node: Node) => TableRowAttrs;
31
+ declare const readTableAttrs: (node: Node) => ReadProseMirrorAttrsResult<Readonly<TableAttrs>>;
32
+ declare const expectTableAttrs: (node: Node) => Readonly<TableAttrs>;
33
+ declare const readTableRowAttrs: (node: Node) => ReadProseMirrorAttrsResult<Readonly<TableRowAttrs>>;
34
+ declare const expectTableRowAttrs: (node: Node) => Readonly<TableRowAttrs>;
35
35
  declare const readTableCellAttrs: (node: Node) => ReadProseMirrorAttrsResult<TableCellAttrs>;
36
36
  declare const expectTableCellAttrs: (node: Node) => TableCellAttrs;
37
37
  declare const readImageAttrs: (node: Node) => ReadProseMirrorAttrsResult<ImageAttrs>;