Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Content fidelity] Support cellPadding and cellSpacing attributes in Content Model #2808

Closed
wants to merge 9 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ export const tableProcessor: ElementProcessor<HTMLTableElement> = (

if (needCalcHeight) {
rowPositions[rowEnd] =
rowPositions[row] + rect.height / zoomScale;
rowPositions[row] +
(rect.height - getPaddingInCell(td)) / zoomScale;
}
}
}
Expand Down Expand Up @@ -340,3 +341,17 @@ function processColGroup(

return hasColGroup;
}

function getPaddingInCell(td: HTMLTableCellElement) {
let result = 0;

if (!td.style.paddingTop?.endsWith('%')) {
result += parseInt(td.style.paddingTop) || 0;
}

if (!td.style.paddingBottom?.endsWith('%')) {
result += parseInt(td.style.paddingBottom) || 0;
}

return result;
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,19 @@ export const paddingFormatHandler: FormatHandler<PaddingFormat & DirectionFormat
});
},
apply: (format, element, context) => {
const tagName = element.tagName;
const isTableCell = tagName == 'TD' || tagName == 'TH';
const isList = tagName == 'OL' || tagName == 'UL';

if (context.tableFormat?.alreadyWroteCellPadding && isTableCell) {
return;
}

PaddingKeys.forEach(key => {
const value = format[key];
let defaultValue: string | undefined = undefined;

if (element.tagName == 'OL' || element.tagName == 'UL') {
if (isList) {
if (
(format.direction == 'rtl' && key == 'paddingRight') ||
(format.direction != 'rtl' && key == 'paddingLeft')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { SpacingFormat } from 'roosterjs-content-model-types';
const BorderCollapsed = 'collapse';
const BorderSeparate = 'separate';
const CellPadding = 'cellPadding';
const CellSpacing = 'cellSpacing';

/**
* @internal
Expand All @@ -12,11 +13,16 @@ export const tableSpacingFormatHandler: FormatHandler<SpacingFormat> = {
parse: (format, element) => {
if (element.style.borderCollapse == BorderCollapsed) {
format.borderCollapse = true;
} else {
const cellPadding = element.getAttribute(CellPadding);
if (cellPadding) {
format.borderCollapse = true;
}
}

const cellPadding = element.getAttribute(CellPadding);
if (cellPadding) {
format.cellPadding = cellPadding;
}

const cellSpacing = element.style.borderSpacing || element.getAttribute(CellSpacing);
if (cellSpacing) {
format.cellSpacing = cellSpacing;
}

if (element.style.borderCollapse == BorderSeparate) {
Expand All @@ -33,5 +39,13 @@ export const tableSpacingFormatHandler: FormatHandler<SpacingFormat> = {
element.style.borderSpacing = '0';
element.style.boxSizing = 'border-box';
}

if (format.cellSpacing) {
element.setAttribute(CellSpacing, format.cellSpacing);
}

if (format.cellPadding) {
element.setAttribute(CellPadding, format.cellPadding);
}
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export const handleTable: ContentModelBlockHandler<ContentModelTable> = (
// Empty table, do not create TABLE element and just return
return refNode;
}
const previousTableContext = context.tableFormat;
context.tableFormat = {};

let tableNode = context.allowCacheElement ? table.cachedElement : undefined;

Expand All @@ -44,6 +46,14 @@ export const handleTable: ContentModelBlockHandler<ContentModelTable> = (
applyFormat(tableNode, context.formatAppliers.table, table.format, context);
applyFormat(tableNode, context.formatAppliers.tableBorder, table.format, context);
applyFormat(tableNode, context.formatAppliers.dataset, table.dataset, context);

if (!tableNode.cellPadding) {
const cellPadding = getSameCellPadding(table);
if (cellPadding) {
tableNode.cellPadding = cellPadding;
context.tableFormat.alreadyWroteCellPadding = true;
}
}
}

context.onNodeCreated?.(table, tableNode);
Expand Down Expand Up @@ -150,6 +160,39 @@ export const handleTable: ContentModelBlockHandler<ContentModelTable> = (
}

context.domIndexer?.onTable(tableNode, table);
context.tableFormat = previousTableContext;

return refNode;
};

function getSameCellPadding(table: ContentModelTable) {
let cellPadding: string | undefined;

const firstCell = table.rows[0]?.cells[0];
const { paddingBottom, paddingLeft, paddingRight, paddingTop } = firstCell.format;

if (
paddingBottom == paddingLeft &&
paddingBottom == paddingRight &&
paddingBottom == paddingTop
) {
cellPadding = paddingBottom;
}

if (
cellPadding &&
table.rows.every(row =>
row.cells.every(
cell =>
cell.format.paddingBottom == cellPadding &&
cell.format.paddingLeft == cellPadding &&
cell.format.paddingRight == cellPadding &&
cell.format.paddingTop == cellPadding
)
)
) {
return cellPadding.match(/\d+/)?.[0];
}

return undefined;
}
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,88 @@ describe('paddingFormatHandler.apply', () => {

expect(ol.outerHTML).toBe('<ol></ol>');
});

it('Table Cell handle padding', () => {
const tableCell = document.createElement('td');

format.paddingBottom = '10px';
format.paddingTop = '10px';
format.paddingLeft = '10px';
format.paddingRight = '10px';

const contextToUse: ModelToDomContext = Object.assign({}, context, <
Partial<ModelToDomContext>
>{
tableFormat: {
alreadyWroteCellPadding: false,
},
});

paddingFormatHandler.apply(format, tableCell, contextToUse);

expect(tableCell.outerHTML).toBe('<td style="padding: 10px;"></td>');
});

it('Table Header handle padding', () => {
const tableCell = document.createElement('th');

format.paddingBottom = '10px';
format.paddingTop = '10px';
format.paddingLeft = '10px';
format.paddingRight = '10px';

const contextToUse: ModelToDomContext = Object.assign({}, context, <
Partial<ModelToDomContext>
>{
tableFormat: {
alreadyWroteCellPadding: false,
},
});

paddingFormatHandler.apply(format, tableCell, contextToUse);

expect(tableCell.outerHTML).toBe('<th style="padding: 10px;"></th>');
});

it('Table Cell handle padding, padding was already applied in table', () => {
const tableCell = document.createElement('td');

format.paddingBottom = '10px';
format.paddingTop = '10px';
format.paddingLeft = '10px';
format.paddingRight = '10px';

const contextToUse: ModelToDomContext = Object.assign({}, context, <
Partial<ModelToDomContext>
>{
tableFormat: {
alreadyWroteCellPadding: true,
},
});

paddingFormatHandler.apply(format, tableCell, contextToUse);

expect(tableCell.outerHTML).toBe('<td></td>');
});

it('Table Header handle padding, padding was already applied in table', () => {
const tableCell = document.createElement('th');

format.paddingBottom = '10px';
format.paddingTop = '10px';
format.paddingLeft = '10px';
format.paddingRight = '10px';

const contextToUse: ModelToDomContext = Object.assign({}, context, <
Partial<ModelToDomContext>
>{
tableFormat: {
alreadyWroteCellPadding: true,
},
});

paddingFormatHandler.apply(format, tableCell, contextToUse);

expect(tableCell.outerHTML).toBe('<th></th>');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,16 @@ describe('tableSpacingFormatHandler.parse', () => {
expect(format).toEqual({ borderSeparate: true });
});

it('Set border collapsed if element contains cellpadding attribute', () => {
it('Set cellpadding attribute', () => {
div.setAttribute('cellPadding', '0');
tableSpacingFormatHandler.parse(format, div, context, {});
expect(format).toEqual({ borderCollapse: true });
expect(format).toEqual({ cellPadding: '0' });
});

it('Set cellspacing attribute', () => {
div.setAttribute('cellSpacing', '0');
tableSpacingFormatHandler.parse(format, div, context, {});
expect(format).toEqual({ cellSpacing: '0' });
});
});

Expand Down Expand Up @@ -69,4 +75,16 @@ describe('tableSpacingFormatHandler.apply', () => {
'<div style="border-collapse: separate; border-spacing: 0px; box-sizing: border-box;"></div>'
);
});

it('Set cellpadding attribute', () => {
format.cellPadding = '5';
tableSpacingFormatHandler.apply(format, div, context);
expect(div.outerHTML).toEqual('<div cellpadding="5"></div>');
});

it('Set cellspacing attribute', () => {
format.cellSpacing = '5';
tableSpacingFormatHandler.apply(format, div, context);
expect(div.outerHTML).toEqual('<div cellspacing="5"></div>');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ContentModelTable,
ContentModelTableRow,
ModelToDomContext,
PaddingFormat,
} from 'roosterjs-content-model-types';

describe('handleTable', () => {
Expand Down Expand Up @@ -615,4 +616,80 @@ describe('handleTable', () => {
expect(div.innerHTML).toBe('<table><tbody><tr><td></td></tr></tbody></table>');
expect(onTableSpy).toHaveBeenCalledWith(div.firstChild, tableModel);
});

it('Regular 2 * 2 table + Cellpadding', () => {
const tdModel1 = createTableCell(1, 1, false);
const tdModel2 = createTableCell(1, 1, false);
const tdModel3 = createTableCell(1, 1, false);
const tdModel4 = createTableCell(1, 1, false);
runTest(
{
blockType: 'Table',
rows: [
{ format: {}, height: 0, cells: [tdModel1, tdModel2] },
{ format: {}, height: 0, cells: [tdModel3, tdModel4] },
],
format: {
cellPadding: '10',
},
widths: [],
dataset: {},
},
'<table cellpadding="10"><tbody><tr><td></td><td></td></tr><tr><td></td><td></td></tr></tbody></table>'
);
});

it('Regular 2 * 2 table all cells contains same padding, so change to use cellpadding', () => {
const padding: PaddingFormat = {
paddingBottom: '10px',
paddingLeft: '10px',
paddingRight: '10px',
paddingTop: '10px',
};

const tdModel1 = createTableCell(1, 1, false, padding);
const tdModel2 = createTableCell(1, 1, false, padding);
const tdModel3 = createTableCell(1, 1, false, padding);
const tdModel4 = createTableCell(1, 1, false, padding);
runTest(
{
blockType: 'Table',
rows: [
{ format: {}, height: 0, cells: [tdModel1, tdModel2] },
{ format: {}, height: 0, cells: [tdModel3, tdModel4] },
],
format: {},
widths: [],
dataset: {},
},
'<table cellpadding="10"><tbody><tr><td></td><td></td></tr><tr><td></td><td></td></tr></tbody></table>'
);
});

it('Regular 2 * 2 table all cells do not contain same padding, do not use cell padding', () => {
const padding: PaddingFormat = {
paddingBottom: '10px',
paddingLeft: '10px',
paddingRight: '10px',
paddingTop: '10px',
};

const tdModel1 = createTableCell(1, 1, false, padding);
const tdModel2 = createTableCell(1, 1, false, padding);
const tdModel3 = createTableCell(1, 1, false, padding);
const tdModel4 = createTableCell(1, 1, false, { ...padding, paddingBottom: '11px' });
runTest(
{
blockType: 'Table',
rows: [
{ format: {}, height: 0, cells: [tdModel1, tdModel2] },
{ format: {}, height: 0, cells: [tdModel3, tdModel4] },
],
format: {},
widths: [],
dataset: {},
},
'<table><tbody><tr><td style="padding: 10px;"></td><td style="padding: 10px;"></td></tr><tr><td style="padding: 10px;"></td><td style="padding: 10px 10px 11px;"></td></tr></tbody></table>'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,7 @@ describe(ID, () => {
width: '157pt' as any,
useBorderBox: true,
borderCollapse: true,
cellSpacing: '0px',
} as any,
dataset: {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ describe(ID, () => {
useBorderBox: true,
borderCollapse: true,
textIndent: '0px',
cellSpacing: '0px',
},
dataset: {
tablestyle: 'MsoTableGrid',
Expand Down
Loading
Loading