From d272ec58bfb7b9e7096efd271e9daf50fb15d154 Mon Sep 17 00:00:00 2001 From: Johannes Wilm Date: Mon, 26 Jan 2026 18:06:07 +0100 Subject: [PATCH 1/2] fix: add paragraph border size to paragraph size to prevent overlapping --- .../painters/dom/src/index.test.ts | 60 ++++++++++++++++++ .../painters/dom/src/renderer.ts | 61 +++++++++++++++++++ packages/superdoc/src/types.ts | 2 +- 3 files changed, 122 insertions(+), 1 deletion(-) diff --git a/packages/layout-engine/painters/dom/src/index.test.ts b/packages/layout-engine/painters/dom/src/index.test.ts index b2b2513dc..fe69e1349 100644 --- a/packages/layout-engine/painters/dom/src/index.test.ts +++ b/packages/layout-engine/painters/dom/src/index.test.ts @@ -3810,6 +3810,66 @@ describe('DomPainter', () => { expect(borderLayer.style.borderLeftColor).toBe('rgb(0, 255, 0)'); }); + it('applies padding for border spacing to prevent overlap', () => { + const blockWithBorderSpacing: FlowBlock = { + kind: 'paragraph', + id: 'border-space-block', + attrs: { + borders: { + top: { style: 'solid', width: 2, color: '#ff0000', space: 5 }, + bottom: { style: 'solid', width: 3, color: '#00ff00', space: 10 }, + left: { style: 'dashed', width: 1, color: '#0000ff', space: 4 }, + right: { style: 'dotted', width: 2, color: '#ffff00', space: 6 }, + }, + }, + runs: [{ text: 'Border spacing test', fontFamily: 'Arial', fontSize: 16 }], + }; + + const painter = createDomPainter({ + blocks: [blockWithBorderSpacing], + measures: [measure], + }); + + const borderSpaceLayout: Layout = { + pageSize: layout.pageSize, + pages: [ + { + number: 1, + fragments: [ + { + kind: 'para', + blockId: 'border-space-block', + fromLine: 0, + toLine: 1, + x: 50, + y: 60, + width: 260, + }, + ], + }, + ], + }; + + painter.paint(borderSpaceLayout, mount); + + const fragment = mount.querySelector('[data-block-id="border-space-block"]') as HTMLElement; + const borderLayer = fragment.querySelector('.superdoc-paragraph-border') as HTMLElement; + + // Verify padding is applied to create space for borders + // Top: space(5) + width(2) = 7px + expect(fragment.style.paddingTop).toBe('7px'); + // Bottom: space(10) + width(3) = 13px + expect(fragment.style.paddingBottom).toBe('13px'); + // Left: space(4) + width(1) = 5px + expect(fragment.style.paddingLeft).toBe('5px'); + // Right: space(6) + width(2) = 8px + expect(fragment.style.paddingRight).toBe('8px'); + + // Verify border layer is positioned with space offset + expect(borderLayer.style.top).toBe('5px'); + expect(borderLayer.style.bottom).toBe('10px'); + }); + it('applies paragraph shading fill to fragment backgrounds', () => { const shadedBlock: FlowBlock = { kind: 'paragraph', diff --git a/packages/layout-engine/painters/dom/src/renderer.ts b/packages/layout-engine/painters/dom/src/renderer.ts index 013b44e3a..8471a125c 100644 --- a/packages/layout-engine/painters/dom/src/renderer.ts +++ b/packages/layout-engine/painters/dom/src/renderer.ts @@ -2036,6 +2036,32 @@ export class DomPainter { if (fragmentEl.style.marginRight) fragmentEl.style.removeProperty('margin-right'); if (fragmentEl.style.textIndent) fragmentEl.style.removeProperty('text-indent'); + // Apply border padding to fragment after removing indent padding + // This padding creates space for borders and prevents them from overlapping content + const borders = block.attrs?.borders; + if (borders) { + if (borders.top) { + const topSpace = Math.max(0, borders.top.space ?? 0); + const topWidth = Math.max(0, borders.top.width ?? 1); + fragmentEl.style.paddingTop = `${topSpace + topWidth}px`; + } + if (borders.bottom) { + const bottomSpace = Math.max(0, borders.bottom.space ?? 0); + const bottomWidth = Math.max(0, borders.bottom.width ?? 1); + fragmentEl.style.paddingBottom = `${bottomSpace + bottomWidth}px`; + } + if (borders.left) { + const leftSpace = Math.max(0, borders.left.space ?? 0); + const leftWidth = Math.max(0, borders.left.width ?? 1); + fragmentEl.style.paddingLeft = `${leftSpace + leftWidth}px`; + } + if (borders.right) { + const rightSpace = Math.max(0, borders.right.space ?? 0); + const rightWidth = Math.max(0, borders.right.width ?? 1); + fragmentEl.style.paddingRight = `${rightSpace + rightWidth}px`; + } + } + const paraIndent = block.attrs?.indent; const paraIndentLeft = paraIndent?.left ?? 0; const paraIndentRight = paraIndent?.right ?? 0; @@ -5912,6 +5938,34 @@ const createParagraphDecorationLayers = ( ): { shadingLayer?: HTMLElement; borderLayer?: HTMLElement } => { if (!attrs?.borders && !attrs?.shading) return {}; const borderBox = getParagraphBorderBox(fragmentWidth, attrs.indent); + + // Calculate border spacing to position the border layer correctly + // The border should be drawn at the inner edge of the space (between space and content) + const borders = attrs.borders; + let topOffset = 0; + let bottomOffset = 0; + let leftOffset = 0; + let rightOffset = 0; + + if (borders) { + if (borders.top) { + const space = Math.max(0, borders.top.space ?? 0); + topOffset = space; // Border at the inner edge of the space + } + if (borders.bottom) { + const space = Math.max(0, borders.bottom.space ?? 0); + bottomOffset = space; + } + if (borders.left) { + const space = Math.max(0, borders.left.space ?? 0); + leftOffset = space; + } + if (borders.right) { + const space = Math.max(0, borders.right.space ?? 0); + rightOffset = space; + } + } + const baseStyles = { position: 'absolute', top: '0px', @@ -5936,6 +5990,13 @@ const createParagraphDecorationLayers = ( borderLayer.classList.add('superdoc-paragraph-border'); Object.assign(borderLayer.style, baseStyles); borderLayer.style.zIndex = '1'; + + // Adjust positioning to account for border space + borderLayer.style.top = `${topOffset}px`; + borderLayer.style.bottom = `${bottomOffset}px`; + borderLayer.style.left = `${borderBox.leftInset + leftOffset}px`; + borderLayer.style.width = `${Math.max(0, borderBox.width - leftOffset - rightOffset)}px`; + applyParagraphBorderStyles(borderLayer, attrs.borders); } diff --git a/packages/superdoc/src/types.ts b/packages/superdoc/src/types.ts index 98095c38c..c4c82c4b3 100644 --- a/packages/superdoc/src/types.ts +++ b/packages/superdoc/src/types.ts @@ -1 +1 @@ -export type * from '@superdoc/super-editor/types'; +export type * from '@superdoc/super-editor/src/types.js'; From 1061dc4c634f97291f1c51d99da9c9e8c1b0f37b Mon Sep 17 00:00:00 2001 From: Johannes Wilm Date: Mon, 26 Jan 2026 19:02:48 +0100 Subject: [PATCH 2/2] fix: revert types path --- packages/superdoc/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdoc/src/types.ts b/packages/superdoc/src/types.ts index c4c82c4b3..98095c38c 100644 --- a/packages/superdoc/src/types.ts +++ b/packages/superdoc/src/types.ts @@ -1 +1 @@ -export type * from '@superdoc/super-editor/src/types.js'; +export type * from '@superdoc/super-editor/types';