Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions packages/layout-engine/painters/dom/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
61 changes: 61 additions & 0 deletions packages/layout-engine/painters/dom/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Comment on lines +2039 to +2043

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Account for border padding in layout sizing

The new fragment-level padding reserves space for borders, but the layout engine still measures line widths/heights using the original fragment size. Because fragments are absolutely positioned, this shrinks the usable content box without reflowing lines, so paragraphs with border spacing and lines near full width (especially justified lines) can render into the right border/space, and top/bottom padding can push text beyond the measured height, overlapping the next fragment. Consider incorporating border space into measurement or adjusting line widths/positions when this padding is applied.

Useful? React with 👍 / 👎.

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;
Expand Down Expand Up @@ -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',
Expand All @@ -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);
}

Expand Down
Loading