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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ follow semantic versioning; release dates are ISO 8601.

### Public API

- **A bordered panel in a table cell shows its right border in Word.** Word draws a table's right
border outside its right edge — measured in its PDF, a table ending at 566.0pt had its right
border from 566.2 to 566.9 — and on screen cuts off what passes its cell's edge and draws the
cell's gridline there. `EditorialProposal`'s glance card, as wide as its column, lost its right
border on screen. A bordered panel reaching its cell's text edge now ends its whole right border
plus half a point short of that edge, the points taken from its right margin and no more than
that margin holds: its text stands where it did and wraps as it did, and its right border is
drawn on screen and on paper wherever its margin is wide enough.
- **A ruled table's rows are as tall in Word as on the page, and a table that states no rule is
ruled as the page rules it.** Word makes room in a table for its horizontal rules — a rule
between two rows half in each, the rule above the table and the one below it whole in their
Expand Down
6 changes: 5 additions & 1 deletion docs/recipes/docx-export.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,11 @@ How it lands:
text of a card land within a pixel of the page's.
- **Nesting.** A panel inside a panel is a table inside its cell. So is a row, with no fill
of its own, so the panel shows through it. A table keeps its own cell fills, and a cell
no style fills is written white, as the page draws it on the card.
no style fills is written white, as the page draws it on the card. Word draws a table's
right border outside its right edge and, on screen, cuts off what passes its cell's edge
and draws the cell's gridline there; a bordered panel reaching its cell's text edge ends its
whole right border plus half a point short of that edge, the points taken from its right
margin and no more than it holds, so its text keeps its place and width.
- **Keeping together.** A `keepTogether()` panel the layout placed on one page is a row
Word may not split. Anchors and keeps on the blocks inside a panel carry as they do
anywhere else.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2840,6 +2840,12 @@ private void writePanel(XWPFDocument document, DocumentNode node, ContainerPaint
}
}

/**
* The room a panel in a cell leaves past its right border, in points: Word draws the cell's
* gridline on screen at the cell's edge, over a border that meets it.
*/
private static final double CLEAR_OF_THE_GRIDLINE_POINTS = 0.5;

/** Writes one table of a panel: the whole panel, or the part of it between page breaks. */
private void writePanelPiece(XWPFDocument document, DocumentNode node, ContainerPaint paint,
List<DocumentNode> children, boolean first, boolean last) throws Exception {
Expand All @@ -2863,11 +2869,33 @@ private void writePanelPiece(XWPFDocument document, DocumentNode node, Container
holdTheSpaceAboveATable(document);
double width = panelWidth(node);

double edge = insetLeft + margin.left();
double indent = currentCell == null ? edge + padding.left() - halfLeft : edge - halfLeft;
// In a cell, Word starts a nested table no further left than the cell's text, draws its
// right border outside the table's right edge — measured in its PDF, a table ending at
// 566.0pt had its right border from 566.2 to 566.9 — and on screen cuts off whatever
// passes the cell's edge and draws the cell's gridline over it: a panel as wide as its
// cell lost its right border, EditorialProposal's glance card. A panel with a right
// border that reaches its cell's text edge ends that border and a little more short of
// it, the points taken from its right margin and no more than it holds, so its text keeps
// its place and its width.
double shortOfTheEdge = 0;
double room = currentCell != null && Double.isFinite(currentCellWidth) && strokeWidth(borders.right()) > 0
? currentCellWidth - insetRight - Math.max(0, indent) - strokeWidth(borders.right())
- CLEAR_OF_THE_GRIDLINE_POINTS
: Double.NaN;

XWPFTable table = newTable(document, 1, 1);
hideTableGrid(table);
XWPFTableCell cell = table.getRow(0).getCell(0);
if (Double.isFinite(width) && width > 0) {
double outer = width + halfLeft + halfRight;
if (room > 0 && outer > room) {
// No more than the right margin holds: past that the text would narrow and wrap,
// which is worse than a border the cell's edge covers.
shortOfTheEdge = Math.min(outer - room, insideTheBorders(padding, borders).right());
outer -= shortOfTheEdge;
}
setTableWidth(table, outer);
writeGrid(table, new double[]{outer});
CTTcPr properties = cellProperties(cell);
Expand All @@ -2877,7 +2905,9 @@ private void writePanelPiece(XWPFDocument document, DocumentNode node, Container
}
applyCellPaint(cell, paint.fill(), null);
paintCellSides(cell, paint.borders());
applyCellPadding(cell, insideTheBorders(padding, borders));
DocumentInsets margins = insideTheBorders(padding, borders);
applyCellPadding(cell, new DocumentInsets(margins.top(), Math.max(0, margins.right() - shortOfTheEdge),
margins.bottom(), margins.left()));
if (node.keepTogether() && layout.onOnePage(node)) {
table.getRow(0).setCantSplitRow(true);
}
Expand Down Expand Up @@ -2916,15 +2946,14 @@ private void writePanelPiece(XWPFDocument document, DocumentNode node, Container
holdRowAtLeast(table.getRow(0), placed.placementHeight());
}

double edge = insetLeft + margin.left();
double indent = currentCell == null ? edge + padding.left() - halfLeft : edge - halfLeft;
if (indent != 0) {
CTTblPr tableProperties = table.getCTTbl().getTblPr();
CTTblWidth tableIndent = tableProperties.isSetTblInd()
? tableProperties.getTblInd()
: tableProperties.addNewTblInd();
tableIndent.setType(STTblWidth.DXA);
// Signed: a nested panel with no margin starts half its border left of the cell.
// Signed: a nested panel with no margin starts half its border left of the cell, and
// Word starts it at the cell's text.
tableIndent.setW(BigInteger.valueOf(Math.round(indent * POINT_TO_TWIP)));
}
if (last) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,70 @@ void aNestedCardWithNoMarginStartsHalfItsBorderLeftOfTheCell() throws Exception
}
}

@Test
void aNestedCardWithTooThinAMarginGivesUpNoMoreThanItHolds() throws Exception {
// 1pt of padding against a 1.5pt border: the margin can give back 0.25pt, not the 3.5
// the border and its clearance would need. The text keeps its width; the border moves in
// only by that quarter point.
try (XWPFDocument document = export(page -> page.addSection("Outer", outer -> outer
.fillColor(SURFACE)
.addSection("Inner", inner -> inner
.stroke(com.demcha.compose.document.style.DocumentStroke.of(ACCENT, 1.5))
.padding(DocumentInsets.of(1))
.addParagraph(p -> p.text("Inner")))))) {
XWPFTableCell host = card(document);
var hostProperties = host.getCTTc().getTcPr();
long room = DocxTwips.of(hostProperties.getTcW().getW())
- DocxTwips.of(hostProperties.getTcMar().getLeft().getW())
- DocxTwips.of(hostProperties.getTcMar().getRight().getW());
XWPFTable nested = host.getTables().get(0);
long width = DocxTwips.of(nested.getCTTbl().getTblPr().getTblW().getW());
var margins = nested.getRow(0).getCell(0).getCTTc().getTcPr().getTcMar();

assertThat(DocxTwips.of(margins.getRight().getW())).as("all its right margin given up").isZero();
// As wide as its cell, every border inside its margins: the room less 1pt of padding
// each side, and the four half borders the margins give back.
assertThat(width - DocxTwips.of(margins.getLeft().getW()) - DocxTwips.of(margins.getRight().getW()))
.as("its text as wide as on the page")
.isCloseTo(room - 40 + 60, org.assertj.core.data.Offset.offset(3L));
}
}

@Test
void aNestedCardAsWideAsItsCellEndsItsRightBorderInsideItAndKeepsItsTextsWidth() throws Exception {
// Word starts a nested table no further left than its cell's text, draws its right border
// outside its right edge and, on screen, cuts off what passes the cell's edge and draws
// the cell's gridline there: a card as wide as its cell lost its right border. The points
// it gives up come off its right margin.
try (XWPFDocument document = export(page -> page.addSection("Outer", outer -> outer
.fillColor(SURFACE)
.addSection("Inner", inner -> inner
.stroke(com.demcha.compose.document.style.DocumentStroke.of(ACCENT, 1.5))
.padding(DocumentInsets.of(10))
.addParagraph(p -> p.text("Inner")))))) {
XWPFTableCell host = card(document);
XWPFTable nested = host.getTables().get(0);
var hostProperties = host.getCTTc().getTcPr();
long room = DocxTwips.of(hostProperties.getTcW().getW())
- DocxTwips.of(hostProperties.getTcMar().getLeft().getW())
- DocxTwips.of(hostProperties.getTcMar().getRight().getW());
var tableProperties = nested.getCTTbl().getTblPr();
long start = Math.max(0, tableProperties.isSetTblInd() ? DocxTwips.of(tableProperties.getTblInd().getW()) : 0);
long width = DocxTwips.of(tableProperties.getTblW().getW());
var margins = nested.getRow(0).getCell(0).getCTTc().getTcPr().getTcMar();
long text = width - DocxTwips.of(margins.getLeft().getW()) - DocxTwips.of(margins.getRight().getW());

// Word draws the right border, 1.5pt or 30 twips, outside the table's edge, and the
// cell's gridline half a point further. The text is as wide as the card written as
// wide as its cell, every border inside the cell's margins: its room less its 10pt
// padding each side, and the four half borders the margins give back.
assertThat(start + width + 30 + 10).as("its right border inside the cell, clear of its gridline")
.isLessThanOrEqualTo(room);
assertThat(text).as("its text as wide as on the page")
.isCloseTo(room - 400 + 60, org.assertj.core.data.Offset.offset(3L));
}
}

@Test
void aCardWithNoCanvasLeavesItsWidthToTheEditor() throws Exception {
try (XWPFDocument document = DocxExports.withoutCanvas(page -> page.addSection("Card", card -> card
Expand Down
Loading