@stll/folio-core 0.7.0 → 0.9.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 (193) hide show
  1. package/README.md +39 -0
  2. package/dist/ai-edits/apply.js +18 -4
  3. package/dist/ai-edits/blockRange.d.ts +13 -2
  4. package/dist/ai-edits/blockRange.js +23 -2
  5. package/dist/ai-edits/headless.d.ts +31 -4
  6. package/dist/ai-edits/headless.js +148 -35
  7. package/dist/ai-edits/index.d.ts +6 -4
  8. package/dist/ai-edits/index.js +4 -1
  9. package/dist/ai-edits/scoped-reading.d.ts +9 -0
  10. package/dist/ai-edits/scoped-reading.js +60 -0
  11. package/dist/ai-edits/snapshot.js +53 -7
  12. package/dist/ai-edits/types.d.ts +46 -3
  13. package/dist/compat/eigenpal.d.ts +8 -3
  14. package/dist/compat/eigenpal.js +7 -2
  15. package/dist/controller/fontReadiness.d.ts +29 -0
  16. package/dist/controller/fontReadiness.js +139 -0
  17. package/dist/controller/latestRequestGate.d.ts +13 -0
  18. package/dist/controller/latestRequestGate.js +16 -0
  19. package/dist/controller/layoutPipeline.js +88 -10
  20. package/dist/document-operations.d.ts +10 -3
  21. package/dist/document-operations.js +29 -14
  22. package/dist/docx/blockContentParser.js +53 -10
  23. package/dist/docx/commentParser.js +1 -1
  24. package/dist/docx/compatibility.d.ts +30 -2
  25. package/dist/docx/compatibility.js +118 -39
  26. package/dist/docx/conformance.d.ts +6 -0
  27. package/dist/docx/conformance.js +18 -0
  28. package/dist/docx/encryption/agileDecryption.js +1 -1
  29. package/dist/docx/encryption/encryptionInfo.js +1 -1
  30. package/dist/docx/encryption/openEncryptedDocx.js +1 -1
  31. package/dist/docx/ensureParaIds.d.ts +29 -0
  32. package/dist/docx/ensureParaIds.js +423 -0
  33. package/dist/docx/fontTableParser.d.ts +6 -0
  34. package/dist/docx/fontTableParser.js +72 -0
  35. package/dist/docx/groupDrawingParser.js +7 -2
  36. package/dist/docx/headerFooterParser.js +1 -1
  37. package/dist/docx/normalizeBaseDirection.js +1 -1
  38. package/dist/docx/numberingParser.js +2 -0
  39. package/dist/docx/paragraphParser.js +21 -1
  40. package/dist/docx/paragraphTextBoxEnrichment.js +141 -2
  41. package/dist/docx/parser.d.ts +1 -1
  42. package/dist/docx/parser.js +14 -9
  43. package/dist/docx/rezip.js +70 -5
  44. package/dist/docx/runConsolidator.js +4 -0
  45. package/dist/docx/runParser.js +19 -1
  46. package/dist/docx/selectiveSave.js +3 -3
  47. package/dist/docx/selectiveXmlPatch.d.ts +10 -1
  48. package/dist/docx/selectiveXmlPatch.js +1 -1
  49. package/dist/docx/serializer/fontTableSerializer.d.ts +6 -0
  50. package/dist/docx/serializer/fontTableSerializer.js +26 -0
  51. package/dist/docx/serializer/headerFooterSerializer.js +1 -1
  52. package/dist/docx/serializer/paragraphSerializer.js +8 -0
  53. package/dist/docx/serializer/runSerializer.js +14 -2
  54. package/dist/docx/serializer/settingsSerializer.d.ts +6 -0
  55. package/dist/docx/serializer/settingsSerializer.js +16 -0
  56. package/dist/docx/serializer/stylesSerializer.d.ts +6 -0
  57. package/dist/docx/serializer/stylesSerializer.js +56 -0
  58. package/dist/docx/serializer/tableSerializer.js +29 -26
  59. package/dist/docx/serializer/themeSerializer.d.ts +6 -0
  60. package/dist/docx/serializer/themeSerializer.js +36 -0
  61. package/dist/docx/settingsParser.js +46 -0
  62. package/dist/docx/styleParser.js +26 -1
  63. package/dist/docx/tableParser.js +19 -1
  64. package/dist/docx/textBoxParser.js +6 -1
  65. package/dist/docx/vmlImageParser.js +146 -1
  66. package/dist/i18n/messages/catalogs.gen.d.ts +5916 -5916
  67. package/dist/i18n/messages/catalogs.gen.js +5910 -5910
  68. package/dist/index.d.ts +8 -3
  69. package/dist/index.js +7 -2
  70. package/dist/layout-bridge/convert/footnoteLayout.d.ts +3 -0
  71. package/dist/layout-bridge/convert/footnoteLayout.js +3 -1
  72. package/dist/layout-bridge/convert/headerFooterLayout.d.ts +3 -0
  73. package/dist/layout-bridge/convert/headerFooterLayout.js +22 -3
  74. package/dist/layout-bridge/convert/paragraphFrames.d.ts +7 -0
  75. package/dist/layout-bridge/convert/paragraphFrames.js +136 -0
  76. package/dist/layout-bridge/convert/toFlowBlocks.d.ts +14 -2
  77. package/dist/layout-bridge/convert/toFlowBlocks.js +93 -17
  78. package/dist/layout-bridge/engine/hitTest.js +2 -1
  79. package/dist/layout-bridge/engine/measuring/index.d.ts +2 -1
  80. package/dist/layout-bridge/engine/measuring/index.js +2 -1
  81. package/dist/layout-bridge/engine/selectionRects.js +2 -1
  82. package/dist/layout-engine/headerFooterRefs.d.ts +7 -0
  83. package/dist/layout-engine/headerFooterRefs.js +40 -0
  84. package/dist/layout-engine/index.d.ts +3 -2
  85. package/dist/layout-engine/index.js +68 -106
  86. package/dist/layout-engine/measure/cache.js +11 -3
  87. package/dist/layout-engine/measure/font-metrics.worker.js +1 -0
  88. package/dist/layout-engine/measure/index.d.ts +2 -1
  89. package/dist/layout-engine/measure/index.js +2 -1
  90. package/dist/layout-engine/measure/lineBreakProvider.d.ts +33 -0
  91. package/dist/layout-engine/measure/lineBreakProvider.js +252 -0
  92. package/dist/layout-engine/measure/lineBreaks.d.ts +7 -2
  93. package/dist/layout-engine/measure/lineBreaks.js +11 -18
  94. package/dist/layout-engine/measure/listMarkerWidth.d.ts +4 -1
  95. package/dist/layout-engine/measure/listMarkerWidth.js +33 -8
  96. package/dist/layout-engine/measure/measureBlocks.d.ts +4 -2
  97. package/dist/layout-engine/measure/measureBlocks.js +138 -86
  98. package/dist/layout-engine/measure/measureContainer.js +32 -9
  99. package/dist/layout-engine/measure/measureHelpers.js +5 -2
  100. package/dist/layout-engine/measure/measureParagraph.js +346 -74
  101. package/dist/layout-engine/measure/measureTypes.d.ts +2 -1
  102. package/dist/layout-engine/measure/measureWorker.d.ts +2 -2
  103. package/dist/layout-engine/measure/measureWorker.js +8 -19
  104. package/dist/layout-engine/measure/measureWorkerProtocol.d.ts +3 -14
  105. package/dist/layout-engine/measure/tableCellFloating.d.ts +10 -3
  106. package/dist/layout-engine/measure/tableCellFloating.js +37 -29
  107. package/dist/layout-engine/measure/tableCellFlow.d.ts +27 -0
  108. package/dist/layout-engine/measure/tableCellFlow.js +73 -0
  109. package/dist/layout-engine/measure/tableCellGrid.d.ts +15 -0
  110. package/dist/layout-engine/measure/tableCellGrid.js +62 -0
  111. package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +18 -0
  112. package/dist/layout-engine/measure/textBoxParagraphLayout.js +32 -0
  113. package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +18 -0
  114. package/dist/layout-engine/measure/textMeasurementPolicy.js +24 -0
  115. package/dist/layout-engine/paginator.d.ts +2 -1
  116. package/dist/layout-engine/paginator.js +7 -6
  117. package/dist/layout-engine/paragraphFrame.d.ts +22 -0
  118. package/dist/layout-engine/paragraphFrame.js +17 -0
  119. package/dist/layout-engine/paragraphSequence.d.ts +7 -0
  120. package/dist/layout-engine/paragraphSequence.js +25 -0
  121. package/dist/layout-engine/paragraphSpacing.d.ts +17 -0
  122. package/dist/layout-engine/paragraphSpacing.js +30 -0
  123. package/dist/layout-engine/renderedBreakReconciliation.d.ts +63 -0
  124. package/dist/layout-engine/renderedBreakReconciliation.js +86 -0
  125. package/dist/layout-engine/tableRowBreak.d.ts +5 -2
  126. package/dist/layout-engine/tableRowBreak.js +62 -29
  127. package/dist/layout-engine/types.d.ts +54 -12
  128. package/dist/layout-engine/types.js +15 -2
  129. package/dist/layout-painter/anchoredImagePosition.d.ts +23 -0
  130. package/dist/layout-painter/anchoredImagePosition.js +95 -0
  131. package/dist/layout-painter/borderStroke.d.ts +23 -0
  132. package/dist/layout-painter/borderStroke.js +39 -0
  133. package/dist/layout-painter/renderImage.d.ts +5 -4
  134. package/dist/layout-painter/renderImage.js +18 -4
  135. package/dist/layout-painter/renderPage.d.ts +2 -27
  136. package/dist/layout-painter/renderPage.js +15 -101
  137. package/dist/layout-painter/renderParagraph.js +45 -26
  138. package/dist/layout-painter/renderTable.d.ts +2 -0
  139. package/dist/layout-painter/renderTable.js +257 -47
  140. package/dist/layout-painter/renderTextBox.js +17 -7
  141. package/dist/managers/DocumentLoaderManager.d.ts +1 -1
  142. package/dist/managers/DocumentLoaderManager.js +2 -2
  143. package/dist/paged-layout/sectionBlockWidths.d.ts +3 -0
  144. package/dist/paged-layout/sectionBlockWidths.js +9 -0
  145. package/dist/paged-layout/sectionGeometry.js +1 -1
  146. package/dist/prosemirror/attrs/index.d.ts +4 -2
  147. package/dist/prosemirror/attrs/index.js +32 -2
  148. package/dist/prosemirror/commands/propertyChangeScope.js +1 -1
  149. package/dist/prosemirror/conversion/fromProseDoc.js +35 -18
  150. package/dist/prosemirror/conversion/toProseDoc.js +73 -37
  151. package/dist/prosemirror/extensions/StarterKit.js +2 -0
  152. package/dist/prosemirror/extensions/core/ParagraphExtension.js +8 -2
  153. package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +1 -1
  154. package/dist/prosemirror/extensions/features/ListExtension.js +2 -0
  155. package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +68 -16
  156. package/dist/prosemirror/extensions/marks/FootnoteRefExtension.js +1 -0
  157. package/dist/prosemirror/extensions/marks/HighlightExtension.js +1 -1
  158. package/dist/prosemirror/extensions/marks/LanguageExtension.d.ts +7 -0
  159. package/dist/prosemirror/extensions/marks/LanguageExtension.js +30 -0
  160. package/dist/prosemirror/extensions/marks/RunShadingExtension.js +1 -1
  161. package/dist/prosemirror/extensions/marks/TextColorExtension.js +1 -1
  162. package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +1 -1
  163. package/dist/prosemirror/extensions/marks/markUtils.js +12 -0
  164. package/dist/prosemirror/extensions/nodes/ImageExtension.js +2 -1
  165. package/dist/prosemirror/extensions/nodes/TableExtension.js +4 -2
  166. package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +2 -1
  167. package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +7 -0
  168. package/dist/prosemirror/schema/index.d.ts +2 -2
  169. package/dist/prosemirror/schema/marks.d.ts +6 -1
  170. package/dist/prosemirror/schema/nodes.d.ts +16 -12
  171. package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
  172. package/dist/prosemirror/utils/tabCalculator.d.ts +3 -1
  173. package/dist/prosemirror/utils/tabCalculator.js +12 -10
  174. package/dist/prosemirror/validation.js +4 -1
  175. package/dist/redline.js +13 -3
  176. package/dist/server.d.ts +9 -4
  177. package/dist/server.js +7 -2
  178. package/dist/style-sets/extract.d.ts +35 -0
  179. package/dist/style-sets/extract.js +123 -0
  180. package/dist/style-sets/stellaStyle.d.ts +13 -0
  181. package/dist/style-sets/stellaStyle.js +516 -0
  182. package/dist/style-sets/types.d.ts +31 -0
  183. package/dist/style-sets/types.js +5 -0
  184. package/dist/types/index.d.ts +2 -1
  185. package/dist/utils/createDocument.d.ts +12 -2
  186. package/dist/utils/createDocument.js +55 -36
  187. package/dist/utils/fontResolver.js +6 -6
  188. package/dist/utils/formatToStyle.js +1 -1
  189. package/dist/utils/hexId.d.ts +8 -1
  190. package/dist/utils/hexId.js +11 -3
  191. package/dist/utils/paragraphFormattingMerge.d.ts +14 -3
  192. package/dist/utils/paragraphFormattingMerge.js +14 -4
  193. package/package.json +5 -1
@@ -0,0 +1,95 @@
1
+ import { emuToPixels } from "./renderUtils.js";
2
+ //#region src/layout-painter/anchoredImagePosition.ts
3
+ function resolveHorizontalBand(relativeTo, geometry) {
4
+ switch (relativeTo) {
5
+ case "page": return {
6
+ baseX: -geometry.marginLeft,
7
+ bandWidth: geometry.pageWidth
8
+ };
9
+ case "leftMargin":
10
+ case "insideMargin": return {
11
+ baseX: -geometry.marginLeft,
12
+ bandWidth: geometry.marginLeft
13
+ };
14
+ case "rightMargin":
15
+ case "outsideMargin": return {
16
+ baseX: geometry.contentWidth,
17
+ bandWidth: geometry.marginRight
18
+ };
19
+ case "character": return {
20
+ baseX: 0,
21
+ bandWidth: 0
22
+ };
23
+ default: return {
24
+ baseX: 0,
25
+ bandWidth: geometry.contentWidth
26
+ };
27
+ }
28
+ }
29
+ function resolveVerticalBand(relativeTo, fragmentY, geometry) {
30
+ switch (relativeTo) {
31
+ case "page": return {
32
+ baseY: -geometry.marginTop,
33
+ bandHeight: geometry.pageHeight
34
+ };
35
+ case "topMargin": return {
36
+ baseY: -geometry.marginTop,
37
+ bandHeight: geometry.marginTop
38
+ };
39
+ case "bottomMargin": return {
40
+ baseY: geometry.contentHeight,
41
+ bandHeight: geometry.marginBottom
42
+ };
43
+ case "paragraph":
44
+ case "line": return {
45
+ baseY: fragmentY,
46
+ bandHeight: 0
47
+ };
48
+ default: return {
49
+ baseY: 0,
50
+ bandHeight: geometry.contentHeight
51
+ };
52
+ }
53
+ }
54
+ /** Resolve an anchored image into page-content-relative coordinates. */
55
+ function resolveAnchoredImagePosition(imgRun, fragmentY, geometry) {
56
+ const position = imgRun.position;
57
+ const contentWidth = geometry.contentWidth;
58
+ let side = "left";
59
+ let x = 0;
60
+ if (position?.horizontal) {
61
+ const h = position.horizontal;
62
+ const { baseX, bandWidth } = resolveHorizontalBand(h.relativeTo, geometry);
63
+ const horizontalHasBand = h.relativeTo !== "character";
64
+ if (h.align === "right" || h.align === "outside") {
65
+ side = "right";
66
+ x = horizontalHasBand ? baseX + bandWidth - imgRun.width : 0;
67
+ } else if (h.align === "left" || h.align === "inside") x = baseX;
68
+ else if (h.align === "center") x = horizontalHasBand ? baseX + (bandWidth - imgRun.width) / 2 : 0;
69
+ else if (h.posOffset !== void 0) {
70
+ x = baseX + emuToPixels(h.posOffset);
71
+ side = x > contentWidth / 2 ? "right" : "left";
72
+ } else x = baseX;
73
+ } else if (imgRun.cssFloat === "right") {
74
+ side = "right";
75
+ x = contentWidth - imgRun.width;
76
+ }
77
+ let y;
78
+ if (position?.vertical) {
79
+ const v = position.vertical;
80
+ const { baseY, bandHeight } = resolveVerticalBand(v.relativeTo, fragmentY, geometry);
81
+ const verticalHasBand = v.relativeTo !== "paragraph" && v.relativeTo !== "line";
82
+ if (v.align === "top" || v.align === "inside") y = baseY;
83
+ else if (v.align === "center") y = verticalHasBand ? baseY + (bandHeight - imgRun.height) / 2 : fragmentY;
84
+ else if (v.align === "bottom" || v.align === "outside") y = verticalHasBand ? baseY + bandHeight - imgRun.height : fragmentY;
85
+ else if (v.posOffset !== void 0) y = baseY + emuToPixels(v.posOffset);
86
+ else y = verticalHasBand ? baseY : fragmentY;
87
+ } else y = fragmentY;
88
+ return {
89
+ x,
90
+ y,
91
+ side
92
+ };
93
+ }
94
+ //#endregion
95
+ export { resolveAnchoredImagePosition };
@@ -0,0 +1,23 @@
1
+ import { BorderStyle, ParagraphBorders } from "../layout-engine/types.js";
2
+
3
+ //#region src/layout-painter/borderStroke.d.ts
4
+ type CssBorderStroke = {
5
+ color: string;
6
+ style: string;
7
+ width: number;
8
+ };
9
+ /**
10
+ * Preserve the optical weight of authored subpixel borders. CSS quantizes a
11
+ * fractional border width to one device pixel, so its color carries the
12
+ * fractional coverage while layout keeps the authored width.
13
+ */
14
+ declare const resolveCssBorderStroke: (border: Pick<BorderStyle, "color" | "style" | "width">) => CssBorderStroke;
15
+ declare const borderStrokeToCss: (border: Pick<BorderStyle, "color" | "style" | "width">) => string;
16
+ type ParagraphBorderHorizontalOutsets = {
17
+ left: number;
18
+ right: number;
19
+ };
20
+ /** Resolve the outer horizontal edges of a paragraph border box. */
21
+ declare const resolveParagraphBorderHorizontalOutsets: (borders: ParagraphBorders, hasHorizontalRule: boolean) => ParagraphBorderHorizontalOutsets;
22
+ //#endregion
23
+ export { borderStrokeToCss, resolveCssBorderStroke, resolveParagraphBorderHorizontalOutsets };
@@ -0,0 +1,39 @@
1
+ import { pointsToPixels } from "../utils/units.js";
2
+ //#region src/layout-painter/borderStroke.ts
3
+ const DEFAULT_BORDER_COLOR = "#000000";
4
+ const DEFAULT_BORDER_STYLE = "solid";
5
+ const CSS_HAIRLINE_WIDTH = 1;
6
+ const PARAGRAPH_RULE_ENDPOINT_OUTSET = pointsToPixels(1.5);
7
+ /**
8
+ * Preserve the optical weight of authored subpixel borders. CSS quantizes a
9
+ * fractional border width to one device pixel, so its color carries the
10
+ * fractional coverage while layout keeps the authored width.
11
+ */
12
+ const resolveCssBorderStroke = (border) => {
13
+ const authoredWidth = border.width ?? CSS_HAIRLINE_WIDTH;
14
+ if (authoredWidth <= 0 || authoredWidth >= CSS_HAIRLINE_WIDTH) return {
15
+ color: border.color ?? DEFAULT_BORDER_COLOR,
16
+ style: border.style ?? DEFAULT_BORDER_STYLE,
17
+ width: Math.max(CSS_HAIRLINE_WIDTH, authoredWidth)
18
+ };
19
+ const coveragePercent = Number((authoredWidth * 100).toFixed(4));
20
+ return {
21
+ color: `color-mix(in srgb, ${border.color ?? DEFAULT_BORDER_COLOR} ${coveragePercent}%, transparent)`,
22
+ style: border.style ?? DEFAULT_BORDER_STYLE,
23
+ width: CSS_HAIRLINE_WIDTH
24
+ };
25
+ };
26
+ const borderStrokeToCss = (border) => {
27
+ const stroke = resolveCssBorderStroke(border);
28
+ return `${stroke.width}px ${stroke.style} ${stroke.color}`;
29
+ };
30
+ /** Resolve the outer horizontal edges of a paragraph border box. */
31
+ const resolveParagraphBorderHorizontalOutsets = (borders, hasHorizontalRule) => {
32
+ const ruleOutset = hasHorizontalRule ? PARAGRAPH_RULE_ENDPOINT_OUTSET : 0;
33
+ return {
34
+ left: borders.left ? (borders.left.space ?? 0) + (borders.left.width ?? 0) : ruleOutset,
35
+ right: borders.right ? (borders.right.space ?? 0) + (borders.right.width ?? 0) : ruleOutset
36
+ };
37
+ };
38
+ //#endregion
39
+ export { borderStrokeToCss, resolveCssBorderStroke, resolveParagraphBorderHorizontalOutsets };
@@ -10,7 +10,7 @@ declare const IMAGE_CLASS_NAMES: {
10
10
  imageAnchored: string;
11
11
  };
12
12
  /**
13
- * Structural shape required to apply Word's per-image visual attributes:
13
+ * Structural shape required to apply OOXML per-image visual attributes:
14
14
  * `wp:srcRect` crop fractions and `a:alphaModFix` opacity. `ImageRun` and
15
15
  * `ImageBlock` both satisfy this, so callers don't need an adapter.
16
16
  */
@@ -32,10 +32,11 @@ type ImageVisualAttrs = {
32
32
  * `0`) — that bug otherwise hides every plain image behind `opacity: 0`.
33
33
  */
34
34
  declare function hasImageVisualAttrs(v: ImageVisualAttrs): boolean;
35
+ declare function hasImageCrop(v: ImageVisualAttrs): boolean;
35
36
  /**
36
- * Apply Word's `<a:srcRect>` crop to an `<img>` whose parent already has
37
+ * Apply an OOXML `<a:srcRect>` crop to an `<img>` whose parent already has
37
38
  * `overflow: hidden` and is sized to the *visible* (cropped) dimensions
38
- * (this matches `wp:extent` semantics: in Word the extent is the cropped
39
+ * (this matches `wp:extent` semantics: the extent is the cropped
39
40
  * frame, with `<a:stretch><a:fillRect/>` stretching the cropped source to
40
41
  * fill it).
41
42
  *
@@ -85,4 +86,4 @@ type RenderImageFragmentOptions = {
85
86
  */
86
87
  declare function renderImageFragment(fragment: ImageFragment, block: ImageBlock, _measure: ImageMeasure, _context: RenderContext, options?: RenderImageFragmentOptions): HTMLElement;
87
88
  //#endregion
88
- export { IMAGE_CLASS_NAMES, ImageVisualAttrs, RenderImageFragmentOptions, applyImageVisualAttrs, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
89
+ export { IMAGE_CLASS_NAMES, ImageVisualAttrs, RenderImageFragmentOptions, applyImageVisualAttrs, hasImageCrop, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
@@ -18,12 +18,15 @@ const IMAGE_CLASS_NAMES = {
18
18
  */
19
19
  function hasImageVisualAttrs(v) {
20
20
  if (v.opacity != null && v.opacity < 1) return true;
21
+ return hasImageCrop(v);
22
+ }
23
+ function hasImageCrop(v) {
21
24
  return Boolean(v.cropTop || v.cropRight || v.cropBottom || v.cropLeft);
22
25
  }
23
26
  /**
24
- * Apply Word's `<a:srcRect>` crop to an `<img>` whose parent already has
27
+ * Apply an OOXML `<a:srcRect>` crop to an `<img>` whose parent already has
25
28
  * `overflow: hidden` and is sized to the *visible* (cropped) dimensions
26
- * (this matches `wp:extent` semantics: in Word the extent is the cropped
29
+ * (this matches `wp:extent` semantics: the extent is the cropped
27
30
  * frame, with `<a:stretch><a:fillRect/>` stretching the cropped source to
28
31
  * fill it).
29
32
  *
@@ -54,8 +57,19 @@ function applyImageVisualAttrs(img, v) {
54
57
  const fh = 1 / remainingH;
55
58
  img.style.width = `${fw * 100}%`;
56
59
  img.style.height = `${fh * 100}%`;
60
+ img.style.maxWidth = "none";
61
+ img.style.maxHeight = "none";
57
62
  img.style.marginLeft = `${-left * fw * 100}%`;
58
- img.style.marginTop = `${-top * fh * 100}%`;
63
+ const existingTransform = img.style.transform;
64
+ if (existingTransform) {
65
+ const visibleCenterX = (left + 1 - right) * 50;
66
+ const visibleCenterY = (top + 1 - bottom) * 50;
67
+ img.style.transformOrigin = `${visibleCenterX}% ${visibleCenterY}%`;
68
+ }
69
+ if (top !== 0) {
70
+ const cropTransform = `translateY(${-top * 100}%)`;
71
+ img.style.transform = existingTransform ? `${existingTransform} ${cropTransform}` : cropTransform;
72
+ }
59
73
  img.style.objectFit = "fill";
60
74
  }
61
75
  /**
@@ -127,4 +141,4 @@ function renderImageFragment(fragment, block, _measure, _context, options = {})
127
141
  return containerEl;
128
142
  }
129
143
  //#endregion
130
- export { IMAGE_CLASS_NAMES, applyImageVisualAttrs, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
144
+ export { IMAGE_CLASS_NAMES, applyImageVisualAttrs, hasImageCrop, hasImageVisualAttrs, renderImageFragment, wrapImageWithCrop };
@@ -1,6 +1,7 @@
1
1
  import { document_d_exports } from "../types/document.js";
2
2
  import { FootnoteContent, HeaderFooterContent, ImageRun, Page, isFloatingImageRun } from "../layout-engine/types.js";
3
3
  import { RenderContext, emuToPixels } from "./renderUtils.js";
4
+ import { AnchoredImagePosition, PageGeometry } from "./anchoredImagePosition.js";
4
5
  import { BlockLookup } from "./index.js";
5
6
 
6
7
  //#region src/layout-painter/renderPage.d.ts
@@ -157,32 +158,6 @@ declare function resolveHeaderFooterFloatLeft(width: number, h: {
157
158
  align?: string;
158
159
  alignment?: string;
159
160
  } | undefined, layout: HeaderFooterLayoutInfo): string;
160
- /**
161
- * Page geometry needed to translate OOXML `relativeFrom` anchors into
162
- * painter coordinates. All values are in CSS pixels. Mirrors the upstream
163
- * (eigenpal docx-editor) `PageGeometry` shape introduced in #424 so the
164
- * anchor math stays identical across the two codebases.
165
- */
166
- type PageGeometry = {
167
- pageWidth: number;
168
- pageHeight: number;
169
- marginLeft: number;
170
- marginTop: number;
171
- marginRight: number;
172
- marginBottom: number;
173
- contentWidth: number;
174
- contentHeight: number;
175
- };
176
- /**
177
- * Resolved on-page coordinates for an anchored floating image.
178
- * `x` / `y` are in content-area-relative pixels (content origin = (0, 0)).
179
- * `side` feeds the text-wrap exclusion zone helper.
180
- */
181
- type AnchoredImagePosition = {
182
- x: number;
183
- y: number;
184
- side: "left" | "right";
185
- };
186
161
  /**
187
162
  * Resolve the on-page coordinates of an anchored floating image.
188
163
  *
@@ -290,4 +265,4 @@ declare function findPageShellForPmPos(container: HTMLElement, pmPos: number): {
290
265
  isExact: boolean;
291
266
  } | null;
292
267
  //#endregion
293
- export { AnchoredImagePosition, FootnoteRenderItem, type HeaderFooterContent, HeaderFooterLayoutInfo, PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, PageFloatingImage, PageGeometry, PainterPaintedDetail, type RenderContext, RenderPageOptions, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft };
268
+ export { type AnchoredImagePosition, FootnoteRenderItem, type HeaderFooterContent, HeaderFooterLayoutInfo, PAGE_CLASS_NAMES, PAINTER_PAINTED_EVENT, PageFloatingImage, type PageGeometry, PainterPaintedDetail, type RenderContext, RenderPageOptions, applySectionHeaderFooterOptions, calculateFootnoteAreaRenderHeight, computePageFingerprint, emuToPixels, findPageShellForPmPos, floatingTableReservesBand, getDefaultPageFontFamily, isFloatingImageRun, renderAllPagesNow, renderFloatingImagesLayer, renderFootnoteArea, renderPage, renderPages, resolveAnchoredImagePosition, resolveHeaderFooterFloatLeft };
@@ -1,12 +1,13 @@
1
1
  import { eighthsToPixels, pointsToPixels } from "../utils/units.js";
2
2
  import { resolveFontFamily } from "../utils/fontResolver.js";
3
+ import { borderToStyle } from "../utils/formatToStyle.js";
3
4
  import { floatingTextBoxReservesBand, floatingTextBoxWrapsText, isFloatingImageRun, isFloatingTextBoxBlock, isTextWrappingFloatingImageRun } from "../layout-engine/types.js";
4
5
  import { rectsToFloatingZones } from "../layout-engine/measure/floatingZones.js";
5
6
  import { measureParagraph } from "../layout-engine/measure/measureParagraph.js";
6
- import { borderToStyle } from "../utils/formatToStyle.js";
7
+ import { emuToPixels } from "./renderUtils.js";
8
+ import { resolveAnchoredImagePosition as resolveAnchoredImagePosition$1 } from "./anchoredImagePosition.js";
7
9
  import { renderFragment } from "./renderFragment.js";
8
10
  import { applyImageVisualAttrs, hasImageVisualAttrs, renderImageFragment } from "./renderImage.js";
9
- import { emuToPixels } from "./renderUtils.js";
10
11
  import { renderParagraphFragment } from "./renderParagraph.js";
11
12
  import { renderTextBoxFragment } from "./renderTextBox.js";
12
13
  import { renderTableFragment } from "./renderTable.js";
@@ -33,6 +34,7 @@ const PAGE_CLASS_NAMES = {
33
34
  header: "layout-page-header",
34
35
  footer: "layout-page-footer"
35
36
  };
37
+ const HEADER_FOOTER_RULE_CLIP_MARGIN = pointsToPixels(1.5);
36
38
  const getDefaultPageFontFamily = () => resolveFontFamily("Calibri").cssFallback;
37
39
  /**
38
40
  * Apply page styles to an element
@@ -271,57 +273,6 @@ function applyFragmentStyles(element, fragment, margins) {
271
273
  element.style.width = `${fragment.width}px`;
272
274
  if ("height" in fragment) element.style.height = `${fragment.height}px`;
273
275
  }
274
- function resolveHorizontalBand(relativeTo, geometry) {
275
- switch (relativeTo) {
276
- case "page": return {
277
- baseX: -geometry.marginLeft,
278
- bandWidth: geometry.pageWidth
279
- };
280
- case "leftMargin":
281
- case "insideMargin": return {
282
- baseX: -geometry.marginLeft,
283
- bandWidth: geometry.marginLeft
284
- };
285
- case "rightMargin":
286
- case "outsideMargin": return {
287
- baseX: geometry.contentWidth,
288
- bandWidth: geometry.marginRight
289
- };
290
- case "character": return {
291
- baseX: 0,
292
- bandWidth: 0
293
- };
294
- default: return {
295
- baseX: 0,
296
- bandWidth: geometry.contentWidth
297
- };
298
- }
299
- }
300
- function resolveVerticalBand(relativeTo, fragmentY, geometry) {
301
- switch (relativeTo) {
302
- case "page": return {
303
- baseY: -geometry.marginTop,
304
- bandHeight: geometry.pageHeight
305
- };
306
- case "topMargin": return {
307
- baseY: -geometry.marginTop,
308
- bandHeight: geometry.marginTop
309
- };
310
- case "bottomMargin": return {
311
- baseY: geometry.contentHeight,
312
- bandHeight: geometry.marginBottom
313
- };
314
- case "paragraph":
315
- case "line": return {
316
- baseY: fragmentY,
317
- bandHeight: 0
318
- };
319
- default: return {
320
- baseY: 0,
321
- bandHeight: geometry.contentHeight
322
- };
323
- }
324
- }
325
276
  /**
326
277
  * Resolve the on-page coordinates of an anchored floating image.
327
278
  *
@@ -337,47 +288,7 @@ function resolveVerticalBand(relativeTo, fragmentY, geometry) {
337
288
  * picked by `relativeFrom`.
338
289
  */
339
290
  function resolveAnchoredImagePosition(imgRun, fragmentY, geometry) {
340
- const position = imgRun.position;
341
- const contentWidth = geometry.contentWidth;
342
- let side = "left";
343
- let x = 0;
344
- if (position?.horizontal) {
345
- const h = position.horizontal;
346
- const { baseX, bandWidth } = resolveHorizontalBand(h.relativeTo, geometry);
347
- const horizontalHasBand = h.relativeTo !== "character";
348
- if (h.align === "right" || h.align === "outside") {
349
- side = "right";
350
- x = horizontalHasBand ? baseX + bandWidth - imgRun.width : 0;
351
- } else if (h.align === "left" || h.align === "inside") {
352
- side = "left";
353
- x = baseX;
354
- } else if (h.align === "center") {
355
- side = "left";
356
- x = horizontalHasBand ? baseX + (bandWidth - imgRun.width) / 2 : 0;
357
- } else if (h.posOffset !== void 0) {
358
- x = baseX + emuToPixels(h.posOffset);
359
- side = x > contentWidth / 2 ? "right" : "left";
360
- } else x = baseX;
361
- } else if (imgRun.cssFloat === "right") {
362
- side = "right";
363
- x = contentWidth - imgRun.width;
364
- }
365
- let y;
366
- if (position?.vertical) {
367
- const v = position.vertical;
368
- const { baseY, bandHeight } = resolveVerticalBand(v.relativeTo, fragmentY, geometry);
369
- const verticalHasBand = v.relativeTo !== "paragraph" && v.relativeTo !== "line";
370
- if (v.align === "top" || v.align === "inside") y = baseY;
371
- else if (v.align === "center") y = verticalHasBand ? baseY + (bandHeight - imgRun.height) / 2 : fragmentY;
372
- else if (v.align === "bottom" || v.align === "outside") y = verticalHasBand ? baseY + bandHeight - imgRun.height : fragmentY;
373
- else if (v.posOffset !== void 0) y = baseY + emuToPixels(v.posOffset);
374
- else y = verticalHasBand ? baseY : fragmentY;
375
- } else y = fragmentY;
376
- return {
377
- x,
378
- y,
379
- side
380
- };
291
+ return resolveAnchoredImagePosition$1(imgRun, fragmentY, geometry);
381
292
  }
382
293
  /**
383
294
  * Extract floating images from a paragraph block and determine their page-level positions.
@@ -1011,7 +922,10 @@ function renderPage(page, context, options = {}) {
1011
922
  });
1012
923
  prevParagraphBorders = paragraphBlock.attrs?.borders;
1013
924
  } else if (fragment.kind === "table" && blockData?.block.kind === "table" && blockData.measure.kind === "table") {
1014
- fragmentEl = renderTableFragment(fragment, blockData.block, blockData.measure, fragmentContext, { document: doc });
925
+ fragmentEl = renderTableFragment(fragment, blockData.block, blockData.measure, fragmentContext, {
926
+ document: doc,
927
+ pageGeometry
928
+ });
1015
929
  prevParagraphBorders = void 0;
1016
930
  } else if (fragment.kind === "image" && blockData?.block.kind === "image" && blockData.measure.kind === "image") {
1017
931
  fragmentEl = renderImageFragment(fragment, blockData.block, blockData.measure, fragmentContext, { document: doc });
@@ -1086,7 +1000,6 @@ function renderPage(page, context, options = {}) {
1086
1000
  headerEl.style.width = `${headerContentWidth}px`;
1087
1001
  headerEl.style.height = `${interactiveHeaderHeight}px`;
1088
1002
  headerEl.style.minHeight = `${interactiveHeaderHeight}px`;
1089
- headerEl.style.opacity = "0.62";
1090
1003
  let shouldClipHeader = !headerOverflows && headerContentFitsBox;
1091
1004
  if (options.headerContent && options.headerContent.blocks.length > 0) {
1092
1005
  const headerContentEl = renderHeaderFooterContent(options.headerContent, {
@@ -1107,7 +1020,8 @@ function renderPage(page, context, options = {}) {
1107
1020
  }
1108
1021
  if (shouldClipHeader) {
1109
1022
  headerEl.style.maxHeight = `${availableHeaderHeight}px`;
1110
- headerEl.style.overflow = "hidden";
1023
+ headerEl.style.overflow = "clip";
1024
+ headerEl.style.overflowClipMargin = `${HEADER_FOOTER_RULE_CLIP_MARGIN}px`;
1111
1025
  }
1112
1026
  pageEl.append(headerEl);
1113
1027
  moveBehindHeaderFooterElementsToPage(headerEl, pageEl, headerDistance, page.margins.left);
@@ -1132,7 +1046,6 @@ function renderPage(page, context, options = {}) {
1132
1046
  footerEl.style.width = `${footerContentWidth}px`;
1133
1047
  footerEl.style.height = `${interactiveFooterHeight}px`;
1134
1048
  footerEl.style.minHeight = `${interactiveFooterHeight}px`;
1135
- footerEl.style.opacity = "0.62";
1136
1049
  const footerContentFitsBox = footerVisualBottom - footerVisualTop <= interactiveFooterHeight;
1137
1050
  let shouldClipFooter = !footerOverflows && footerContentFitsBox;
1138
1051
  if (options.footerContent && options.footerContent.blocks.length > 0) {
@@ -1154,7 +1067,8 @@ function renderPage(page, context, options = {}) {
1154
1067
  }
1155
1068
  if (shouldClipFooter) {
1156
1069
  footerEl.style.maxHeight = `${availableFooterHeight}px`;
1157
- footerEl.style.overflow = "hidden";
1070
+ footerEl.style.overflow = "clip";
1071
+ footerEl.style.overflowClipMargin = `${HEADER_FOOTER_RULE_CLIP_MARGIN}px`;
1158
1072
  }
1159
1073
  pageEl.append(footerEl);
1160
1074
  moveBehindHeaderFooterElementsToPage(footerEl, pageEl, footerNaturalTop, page.margins.left);
@@ -1188,12 +1102,12 @@ function applySectionHeaderFooterOptions(page, pageOptions, options) {
1188
1102
  const useEven = refs.evenAndOddHeaders === true && sectionPageNumber % 2 === 0;
1189
1103
  const headerRId = (() => {
1190
1104
  if (useFirst) return refs.headerFirst;
1191
- if (useEven && refs.headerEven) return refs.headerEven;
1105
+ if (useEven) return refs.headerEven;
1192
1106
  return refs.headerDefault;
1193
1107
  })();
1194
1108
  const footerRId = (() => {
1195
1109
  if (useFirst) return refs.footerFirst;
1196
- if (useEven && refs.footerEven) return refs.footerEven;
1110
+ if (useEven) return refs.footerEven;
1197
1111
  return refs.footerDefault;
1198
1112
  })();
1199
1113
  if (headerRId) {
@@ -1,19 +1,22 @@
1
1
  import { parseXmlDocument } from "../docx/xmlParser.js";
2
2
  import { resolveFontFamily } from "../utils/fontResolver.js";
3
+ import { detectBaseDirection } from "../utils/baseDirection.js";
4
+ import { AUTHOR_COLORS, getAuthorColorIdx } from "../utils/authorColors.js";
3
5
  import "../utils/fontWeights.js";
6
+ import { FONT_KERNING_MODE, countCompressibleSpaces, getFontKerningMode, getRunFontKerningMode } from "../layout-engine/measure/textMeasurementPolicy.js";
7
+ import "../layout-engine/measure/measureHelpers.js";
4
8
  import { calculateTabWidth } from "../prosemirror/utils/tabCalculator.js";
5
9
  import { inlineImageBoundingBox, parseRotationDegrees, rotatedBoundingBox } from "../utils/rotationBoundingBox.js";
6
10
  import { hasCjk, segmentByScript } from "../utils/scriptSegments.js";
7
11
  import { isFloatingImageRun } from "../layout-engine/types.js";
8
- import { getListMarkerInlineWidth } from "../layout-engine/measure/listMarkerWidth.js";
12
+ import { getListMarkerInlineWidth, getListMarkerVisualOffset } from "../layout-engine/measure/listMarkerWidth.js";
13
+ import { resolveImageLineAlign } from "./renderUtils.js";
9
14
  import { applySdtDataAttrs } from "./sdtBoundary.js";
10
15
  import { applyImageVisualAttrs, hasImageVisualAttrs, wrapImageWithCrop } from "./renderImage.js";
11
16
  import { ommlToMathml } from "../docx/mathToMathml.js";
12
17
  import { evaluateFieldInstruction } from "../fields/evaluateField.js";
13
- import { AUTHOR_COLORS, getAuthorColorIdx } from "../utils/authorColors.js";
14
- import { detectBaseDirection } from "../utils/baseDirection.js";
18
+ import { borderStrokeToCss, resolveParagraphBorderHorizontalOutsets } from "./borderStroke.js";
15
19
  import { getAutomaticTextColorForBackground } from "./documentColors.js";
16
- import { resolveImageLineAlign } from "./renderUtils.js";
17
20
  //#region src/layout-painter/renderParagraph.ts
18
21
  /**
19
22
  * Paragraph Fragment Renderer
@@ -128,9 +131,7 @@ function applyRunStyles(element, run) {
128
131
  element.style.transform = `scaleX(${run.horizontalScale / 100})`;
129
132
  element.style.transformOrigin = "left center";
130
133
  }
131
- if (run.kerningMinPt && run.kerningMinPt > 0) {
132
- if ((run.fontSize ?? 11) >= run.kerningMinPt) element.style.fontKerning = "normal";
133
- }
134
+ element.style.fontKerning = getRunFontKerningMode(run, 11);
134
135
  if (run.emboss) element.style.textShadow = "1px 1px 1px rgba(255,255,255,0.5), -1px -1px 1px rgba(0,0,0,0.3)";
135
136
  if (run.imprint) element.style.textShadow = "-1px -1px 1px rgba(255,255,255,0.5), 1px 1px 1px rgba(0,0,0,0.3)";
136
137
  if (run.textShadow && !run.emboss && !run.imprint) element.style.textShadow = "1px 1px 2px rgba(0,0,0,0.3)";
@@ -785,19 +786,14 @@ const startsAfterSoftWrap = (block, line) => {
785
786
  * must trigger within this slack.
786
787
  */
787
788
  const RIGHT_EDGE_EPSILON_PX = .5;
788
- function countShrinkableSpaces(runs) {
789
+ function countShrinkableSpaces(runs, context) {
789
790
  let count = 0;
790
- for (const run of runs) if (isTextRun(run)) {
791
- for (const char of run.text ?? "") if (char === " ") count++;
792
- } else if (isFieldRun(run)) {
793
- for (const char of run.fallback ?? "") if (char === " ") count++;
794
- } else if (isMathRun(run)) {
795
- for (const char of run.plainText ?? "") if (char === " ") count++;
796
- }
791
+ for (const run of runs) if (isTextRun(run)) count += countCompressibleSpaces(run.text ?? "");
792
+ else if (isFieldRun(run)) count += countCompressibleSpaces(resolveFieldText(run, context));
797
793
  return count;
798
794
  }
799
795
  /**
800
- * Build a TextMeasureStyle from a TextRun or FieldRun's relevant fields.
796
+ * Build the shared measurement style from a paintable text run.
801
797
  */
802
798
  function runMeasureStyle(run) {
803
799
  return {
@@ -805,7 +801,8 @@ function runMeasureStyle(run) {
805
801
  ...run.italic !== void 0 ? { italic: run.italic } : {},
806
802
  ...run.letterSpacing !== void 0 ? { letterSpacing: run.letterSpacing } : {},
807
803
  ...run.smallCaps !== void 0 ? { smallCaps: run.smallCaps } : {},
808
- ...run.eastAsiaFontFamily !== void 0 ? { eastAsiaFontFamily: run.eastAsiaFontFamily } : {}
804
+ ...run.eastAsiaFontFamily !== void 0 ? { eastAsiaFontFamily: run.eastAsiaFontFamily } : {},
805
+ kerning: getRunFontKerningMode(run, 11) === FONT_KERNING_MODE.enabled
809
806
  };
810
807
  }
811
808
  /**
@@ -910,6 +907,7 @@ function createTextMeasurer(doc) {
910
907
  const ctx = doc.createElement("canvas").getContext("2d");
911
908
  return (text, fontSize = 11, fontFamily = "Calibri", style = {}) => {
912
909
  if (!ctx) return applyLetterSpacingToMeasuredWidth(text.length * 7, text, style.letterSpacing);
910
+ ctx.fontKerning = getFontKerningMode(style);
913
911
  const cssFallback = resolveFontFamily(fontFamily).cssFallback;
914
912
  const fontSizePx = fontSize * 96 / 72;
915
913
  const fontPrefixParts = [];
@@ -996,7 +994,7 @@ function renderLine(block, line, alignment, doc, options) {
996
994
  const firstLineHangingExpansionPx = hasVisibleListMarker ? Math.min(firstLineHangingPx, Math.max(0, options.leftIndentPx ?? 0)) : firstLineHangingPx;
997
995
  const justifyCapacityPx = options.availableWidth + firstLineHangingExpansionPx;
998
996
  const overfullPx = line.width - justifyCapacityPx;
999
- const shrinkableSpaces = countShrinkableSpaces(runsForLine.filter((run) => !isTextRun(run) || !isCollapsedLineEdgeSpaceRun(run)));
997
+ const shrinkableSpaces = countShrinkableSpaces(runsForLine.filter((run) => !isTextRun(run) || !isCollapsedLineEdgeSpaceRun(run)), options.context);
1000
998
  if (overfullPx > RIGHT_EDGE_EPSILON_PX && shrinkableSpaces > 0) {
1001
999
  lineEl.style.textAlign = "left";
1002
1000
  lineEl.style.textAlignLast = "auto";
@@ -1134,6 +1132,20 @@ function renderLine(block, line, alignment, doc, options) {
1134
1132
  lineEl.append(runEl);
1135
1133
  }
1136
1134
  }
1135
+ if (line.discretionaryHyphen) {
1136
+ const sourceRun = block.runs[line.discretionaryHyphen.runIndex];
1137
+ if (sourceRun && isTextRun(sourceRun)) {
1138
+ const hyphenRun = {
1139
+ ...sourceRun,
1140
+ text: "-"
1141
+ };
1142
+ Reflect.deleteProperty(hyphenRun, "pmStart");
1143
+ Reflect.deleteProperty(hyphenRun, "pmEnd");
1144
+ const hyphenEl = renderTextRun(hyphenRun, doc);
1145
+ hyphenEl.dataset["discretionaryHyphen"] = "true";
1146
+ lineEl.append(hyphenEl);
1147
+ }
1148
+ }
1137
1149
  const inFlowRuns = runsForLine.filter((run) => !(isImageRun(run) && isFloatingImageRun(run)));
1138
1150
  const blankBreakRun = inFlowRuns.length > 0 && inFlowRuns.every(isLineBreakRun) ? inFlowRuns.find(isLineBreakRun) : void 0;
1139
1151
  if (blankBreakRun?.pmStart !== void 0) {
@@ -1239,7 +1251,10 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
1239
1251
  }
1240
1252
  };
1241
1253
  fragmentEl.style.boxSizing = "border-box";
1242
- const borderToCss = (b) => `${b.width}px ${borderStyleToCss(b.style)} ${b.color}`;
1254
+ const borderToCss = (border) => borderStrokeToCss({
1255
+ ...border,
1256
+ style: borderStyleToCss(border.style)
1257
+ });
1243
1258
  const groupedWithPrev = bordersFormGroup(options.prevBorders, borders);
1244
1259
  const groupedWithNext = bordersFormGroup(borders, options.nextBorders);
1245
1260
  const renderedTopBorder = groupedWithPrev ? borders.between : borders.top;
@@ -1249,8 +1264,9 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
1249
1264
  borderBox.style.position = "absolute";
1250
1265
  borderBox.style.pointerEvents = "none";
1251
1266
  borderBox.style.boxSizing = "border-box";
1252
- borderBox.style.left = `${indentLeft - (borders.left?.space ?? 0) - (borders.left?.width ?? 0)}px`;
1253
- borderBox.style.right = `${indentRight - (borders.right?.space ?? 0) - (borders.right?.width ?? 0)}px`;
1267
+ const horizontalOutsets = resolveParagraphBorderHorizontalOutsets(borders, renderedTopBorder !== void 0 || renderedBottomBorder !== void 0);
1268
+ borderBox.style.left = `${indentLeft - horizontalOutsets.left}px`;
1269
+ borderBox.style.right = `${indentRight - horizontalOutsets.right}px`;
1254
1270
  borderBox.style.top = `${-(renderedTopBorder?.space ?? 0) - (renderedTopBorder?.width ?? 0)}px`;
1255
1271
  borderBox.style.bottom = `${-(renderedBottomBorder?.space ?? 0) - (renderedBottomBorder?.width ?? 0)}px`;
1256
1272
  if (renderedTopBorder) borderBox.style.borderTop = borderToCss(renderedTopBorder);
@@ -1342,7 +1358,7 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
1342
1358
  lineEl.style.paddingLeft = `${Math.max(0, markerStart)}px`;
1343
1359
  lineEl.style.textIndent = "0";
1344
1360
  let firstTextRun;
1345
- if (!block.attrs.listMarkerFontFamily || !block.attrs.listMarkerFontSize) for (let ri = line.fromRun; ri <= line.toRun; ri++) {
1361
+ if (!block.attrs.listMarkerFontFamily || !block.attrs.listMarkerFontSize || block.attrs.listMarkerBold === void 0) for (let ri = line.fromRun; ri <= line.toRun; ri++) {
1346
1362
  const r = block.runs[ri];
1347
1363
  if (r && r.kind === "text") {
1348
1364
  firstTextRun = r;
@@ -1351,7 +1367,8 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
1351
1367
  }
1352
1368
  const markerFontFamily = block.attrs.listMarkerFontFamily ?? firstTextRun?.fontFamily ?? block.attrs.defaultFontFamily;
1353
1369
  const markerFontSize = block.attrs.listMarkerFontSize ?? firstTextRun?.fontSize ?? block.attrs.defaultFontSize;
1354
- const marker = renderListMarker(block.attrs.listMarker, getListMarkerInlineWidth(block), doc, markerFontFamily, markerFontSize, block.attrs.listMarkerRevision, block.attrs.listMarkerSecondSlotOffsetTwips);
1370
+ const markerBold = block.attrs.listMarkerBold ?? firstTextRun?.bold;
1371
+ const marker = renderListMarker(block.attrs.listMarker, getListMarkerInlineWidth(block), getListMarkerVisualOffset(block), doc, markerFontFamily, markerFontSize, markerBold, block.attrs.listMarkerRevision, block.attrs.listMarkerSecondSlotOffsetTwips);
1355
1372
  const markerMarginLeft = markerStart - Math.min(indentLeft, 0);
1356
1373
  if (markerMarginLeft < 0) marker.style.marginLeft = `${markerMarginLeft}px`;
1357
1374
  lineEl.prepend(marker);
@@ -1362,22 +1379,24 @@ function renderParagraphFragment(fragment, block, measure, context, options = {}
1362
1379
  }
1363
1380
  /**
1364
1381
  * Render a list marker element as an inline-block at the start of the first
1365
- * body line. `minWidth` (from `getListMarkerInlineWidth`) sizes the marker
1382
+ * body line. `inlineWidth` (from `getListMarkerInlineWidth`) sizes the marker
1366
1383
  * so the body text aligns at the next tab stop per ECMA-376 §17.9.25 —
1367
1384
  * this honours `w:suff` (`tab` / `space` / `nothing`) and the document's
1368
1385
  * tab grid. Long markers like "1.1.1." therefore grow to the next stop
1369
1386
  * instead of butting against the body text.
1370
1387
  */
1371
- function renderListMarker(marker, minWidth, doc, fontFamily, fontSize, revision, secondSlotOffsetTwips) {
1388
+ function renderListMarker(marker, inlineWidth, visualOffset, doc, fontFamily, fontSize, bold, revision, secondSlotOffsetTwips) {
1372
1389
  const span = doc.createElement("span");
1373
1390
  span.className = "layout-list-marker";
1374
1391
  span.style.display = "inline-block";
1375
1392
  if (fontFamily) span.style.fontFamily = resolveFontFamily(fontFamily).cssFallback;
1376
1393
  if (fontSize) span.style.fontSize = `${fontSize * 96 / 72}px`;
1394
+ if (bold !== void 0) span.style.fontWeight = bold ? "800" : "normal";
1377
1395
  span.style.textAlign = "left";
1378
1396
  span.style.textAlignLast = "left";
1379
1397
  span.style.boxSizing = "border-box";
1380
- if (minWidth > 0) span.style.minWidth = `${minWidth}px`;
1398
+ span.style.width = `${inlineWidth}px`;
1399
+ if (visualOffset !== 0) span.style.transform = `translateX(${visualOffset}px)`;
1381
1400
  if (revision) {
1382
1401
  const authorIdx = getAuthorColorIdx(revision.author ?? "");
1383
1402
  const authorColor = AUTHOR_COLORS[authorIdx];
@@ -1,5 +1,6 @@
1
1
  import { TableBlock, TableFragment, TableMeasure } from "../layout-engine/types.js";
2
2
  import { RenderContext } from "./renderUtils.js";
3
+ import { PageGeometry } from "./anchoredImagePosition.js";
3
4
 
4
5
  //#region src/layout-painter/renderTable.d.ts
5
6
  /**
@@ -20,6 +21,7 @@ declare const TABLE_CLASS_NAMES: {
20
21
  */
21
22
  type RenderTableFragmentOptions = {
22
23
  document?: Document;
24
+ pageGeometry?: PageGeometry;
23
25
  };
24
26
  /**
25
27
  * Render a table fragment to DOM