Skip to content
Merged
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
6 changes: 6 additions & 0 deletions lib/components/document/src/Cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,12 @@ export class Cell extends Component<CellProps, CellChild> {
"insideH": docxml:ct-border(${QNS.w}insideH),
"insideV": docxml:ct-border(${QNS.w}insideV)
},
"margin": ./${QNS.w}tcPr/${QNS.w}tcMar/map {
"top": docxml:length(${QNS.w}top/@${QNS.w}w, 'twip'),
"start": docxml:length((${QNS.w}start|${QNS.w}left)[1]/@${QNS.w}w, 'twip'),
"bottom": docxml:length(${QNS.w}bottom/@${QNS.w}w, 'twip'),
"end": docxml:length((${QNS.w}end|${QNS.w}right)[1]/@${QNS.w}w, 'twip')
},
"verticalAlignment": ./${QNS.w}tcPr/${QNS.w}vAlign/@${QNS.w}val/string(),
"insertion": ./${QNS.w}tcPr/${QNS.w}cellIns/map {
"id": @${QNS.w}id/number(),
Expand Down
53 changes: 53 additions & 0 deletions lib/components/document/test/Cell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,59 @@ describe('Cell', () => {
});
});

describe('Cell - tcMar (per-cell margins)', () => {
const dom = create(`<w:tbl xmlns:w="${NamespaceUri.w}">
<w:tr>
<w:tc xid="modern">
<w:tcPr>
<w:tcMar>
<w:top w:w="28" w:type="dxa"/>
<w:start w:w="14" w:type="dxa"/>
<w:bottom w:w="28" w:type="dxa"/>
<w:end w:w="14" w:type="dxa"/>
</w:tcMar>
</w:tcPr>
<w:p/>
</w:tc>
<w:tc xid="legacy">
<w:tcPr>
<w:tcMar>
<w:left w:w="14" w:type="dxa"/>
<w:right w:w="14" w:type="dxa"/>
</w:tcMar>
</w:tcPr>
<w:p/>
</w:tc>
</w:tr>
</w:tbl>`);

describe('modern start/end', () => {
const cell = Cell.fromNode(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
evaluateXPathToFirstNode('.//*[@xid="modern"]', dom)!,
emptyContext
);
it('parses all four sides', () => {
expect(cell?.props.margin?.top?.twip).toBe(28);
expect(cell?.props.margin?.start?.twip).toBe(14);
expect(cell?.props.margin?.bottom?.twip).toBe(28);
expect(cell?.props.margin?.end?.twip).toBe(14);
});
});

describe('legacy left/right', () => {
const cell = Cell.fromNode(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
evaluateXPathToFirstNode('.//*[@xid="legacy"]', dom)!,
emptyContext
);
it('maps left/right onto start/end', () => {
expect(cell?.props.margin?.start?.twip).toBe(14);
expect(cell?.props.margin?.end?.twip).toBe(14);
});
});
});

describe('Cell - with colspan', () => {
const tableNode = create(`
<w:tbl xmlns:w="${NamespaceUri.w}">
Expand Down
66 changes: 63 additions & 3 deletions lib/properties/src/table-cell-properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ export type TableCellProperties = {
*/
tr2bl?: null | Border<LineBorderType>;
};
/**
* The margins (internal padding) of this cell, overriding the table-level
* default cell margins (`w:tblCellMar`). Corresponds to `w:tcMar` in OOXML.
*
* Each side is a {@link Length}. Omitted sides fall back to the table default.
*/
margin?: null | {
top?: null | Length;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Documentation?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The documentation for this prop is in line 48 to 53

/**
* The leading (left, in left-to-right) margin. Serialized as `w:start`;
* both `w:start` and the legacy `w:left` are accepted when reading.
*/
start?: null | Length;
bottom?: null | Length;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Documentation?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The documentation for this prop is in line 48 to 53

/**
* The trailing (right, in left-to-right) margin. Serialized as `w:end`;
* both `w:end` and the legacy `w:right` are accepted when reading.
*/
end?: null | Length;
};
/**
* The vertical alignment of this cell.
*/
Expand Down Expand Up @@ -127,6 +147,12 @@ export function tableCellPropertiesFromNode(
"insideH": docxml:ct-border(${QNS.w}insideH),
"insideV": docxml:ct-border(${QNS.w}insideV)
},
"margin": ./${QNS.w}tcMar/map {
"top": docxml:length(${QNS.w}top/@${QNS.w}w, 'twip'),
"start": docxml:length((${QNS.w}start|${QNS.w}left)[1]/@${QNS.w}w, 'twip'),
"bottom": docxml:length(${QNS.w}bottom/@${QNS.w}w, 'twip'),
"end": docxml:length((${QNS.w}end|${QNS.w}right)[1]/@${QNS.w}w, 'twip')
},
"verticalAlignment": ./${QNS.w}vAlign/@${QNS.w}val/string(),
"insertion": ./${QNS.w}cellIns/map {
"id": @${QNS.w}id/number(),
Expand All @@ -147,7 +173,7 @@ export function tableCellPropertiesFromNode(
}
`,
node
)
)
: {};

if (props.change) {
Expand Down Expand Up @@ -218,6 +244,24 @@ export function tableCellPropertiesToNode(
docxml:ct-border(fn:QName("${NamespaceUri.w}", "insideH"), $borders('insideH')),
docxml:ct-border(fn:QName("${NamespaceUri.w}", "insideV"), $borders('insideV'))
} else (),
if (exists($margin)) then element ${QNS.w}tcMar {
if (exists($margin('top'))) then element ${QNS.w}top {
attribute ${QNS.w}w { $margin('top') },
attribute ${QNS.w}type { "dxa" }
} else (),
if (exists($margin('start'))) then element ${QNS.w}start {
attribute ${QNS.w}w { $margin('start') },
attribute ${QNS.w}type { "dxa" }
} else (),
if (exists($margin('bottom'))) then element ${QNS.w}bottom {
attribute ${QNS.w}w { $margin('bottom') },
attribute ${QNS.w}type { "dxa" }
} else (),
if (exists($margin('end'))) then element ${QNS.w}end {
attribute ${QNS.w}w { $margin('end') },
attribute ${QNS.w}type { "dxa" }
} else ()
} else (),
if (exists($verticalAlignment)) then element ${QNS.w}vAlign {
attribute ${QNS.w}val { $verticalAlignment }
} else (),
Expand Down Expand Up @@ -247,7 +291,23 @@ export function tableCellPropertiesToNode(
tl2br: null,
tr2bl: null,
...tcpr.borders,
}
}
: null,
margin: tcpr.margin
? {
top: tcpr.margin.top
? Math.round(tcpr.margin.top.twip)
: null,
start: tcpr.margin.start
? Math.round(tcpr.margin.start.twip)
: null,
bottom: tcpr.margin.bottom
? Math.round(tcpr.margin.bottom.twip)
: null,
end: tcpr.margin.end
? Math.round(tcpr.margin.end.twip)
: null,
}
: null,
verticalAlignment: tcpr.verticalAlignment || null,
change: tcpr.change
Expand All @@ -260,7 +320,7 @@ export function tableCellPropertiesToNode(
? new Date(tcpr.change.date).toISOString()
: undefined,
node: tableCellPropertiesToNode(tcpr.change, true),
}
}
: null,
insertion: tcpr.insertion
? new CellInsertion(tcpr.insertion).toNode()
Expand Down
40 changes: 40 additions & 0 deletions lib/properties/test/table-cell-properties.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,46 @@ describe('Table cell formatting', () => {
});
});

describe('Table cell margins (w:tcMar)', () => {
test(
`<w:tcPr ${ALL_NAMESPACE_DECLARATIONS}>
<w:tcMar>
<w:top w:w="28" w:type="dxa"/>
<w:start w:w="14" w:type="dxa"/>
<w:bottom w:w="28" w:type="dxa"/>
<w:end w:w="14" w:type="dxa"/>
</w:tcMar>
</w:tcPr>`,
{
margin: {
top: twip(28),
start: twip(14),
bottom: twip(28),
end: twip(14),
},
}
);

describe('Legacy "left"/"right"', () => {
test(
`<w:tcPr ${ALL_NAMESPACE_DECLARATIONS}>
<w:tcMar>
<w:left w:w="14" w:type="dxa"/>
<w:right w:w="14" w:type="dxa"/>
</w:tcMar>
</w:tcPr>`,
{
margin: {
top: null,
start: twip(14),
bottom: null,
end: twip(14),
},
}
);
});
});

describe('Table cell insertion', () => {
const date = new Date();
test(
Expand Down