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

### Public API

- **A DOCX export names a panel's left edge its table cell holds in.**
- The page sets a panel in a table cell with its left border centred on the cell's text edge,
half of it outside, or further out by a margin below zero. LibreOffice starts a nested table
at its cell's text, and Word does in most cells, so the panel stands up to that much right of
the page's: its left border and fill, and with a margin below zero its text too. The report
said nothing.
- The panel's note (`left edge`) names how far left of the cell's text the page sets it, up to
which the editors move it.
- Measured on Windows, 3pt accents:
- LibreOffice set them 1.5pt right of the page's, and a box's in a ruled table cell 1pt.
- Word set them 1.55 to 1.73pt right in a row's cell and in a table cell, filled or not, and
0.04 to 0.17pt in a filled panel's cell or a row inside one.
- Engineering Resume's 2pt and 3pt card accents stood 1.05 and 1.71pt right in Word.
- A panel a placed row holds as a column had its left margin, which the row's cell already
starts past, counted again in its table's indent. It is counted once.
- 75 panels of the corpus's 62 documents gain the note. None of the DOCX changes.
- **A DOCX export ends a panel where the page ends it.**
- A panel's text is given a point of slack past the page's width, so an editor setting it in its
own face keeps the page's line breaks. The slack widened the panel's table, so its fill and
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/backend-capability-matrix.md

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion docs/recipes/docx-export.md
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,14 @@ How it lands:
right margin cannot take in, a shape container's included, since its padding is set round its
outline, still passes the cell's edge where the cell's own margin does not hold it, and is
named with it, about how far: reckoned, as Word drew them, with the table starting at the
cell's text and its whole border outside it.
cell's text and its whole border outside it. On the left, the page centres a panel's left
border on its cell's text edge, half of it outside, or sets the panel further out by a margin
below zero. LibreOffice starts a nested table at its cell's text, and Word does in most cells:
the panel stands up to that much right of the page's, its left border and fill, and with a
margin below zero its text too, and the report names how far (`left edge`). Measured, 3pt
accents stood 1.5pt right of the page's in LibreOffice; in Word, 1.55 to 1.73pt in a row's
cell and in a table cell, filled or not, and 0.04 to 0.17pt in a filled panel's cell or a row
inside one.
- **Keeping together.** A `keepTogether()` panel the layout placed on one page is a row
Word may not split. Anchors on the blocks inside a panel carry as they do anywhere else, and
so do their keeps, but Word and LibreOffice do not hold them where the panel's row breaks over
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,7 @@ private int startPageOf(DocumentNode node) {
private DocumentNode leftMarginInCell;
// What a row nested in a cell still hangs left once its first column has given what it can
// (takeHang), handed to each of its cells: Word keeps a nested table inside its cell
// whatever the table's indent.
// whatever the table's indent, except in a filled panel's cell (leftEdgeHungOut).
private double cellHang;
// The cell being filled takes its row's hang as cellTextShift (a negative number): its
// paragraphs' text moves left by it as far as each one's own indent goes, and never past
Expand Down Expand Up @@ -6970,6 +6970,29 @@ private void writePanel(XWPFDocument document, DocumentNode node, ContainerPaint
/** What the report calls a panel's right end, where its table is written past the page's. */
private static final String RIGHT_END = "right end";

/** What the report calls a panel's left edge, where the page sets it left of its cell's text. */
private static final String LEFT_EDGE = "left edge";

/**
* The report's note on a panel in a table cell that the page sets left of the cell's text —
* its left border centred on the text's edge, or a margin below zero.
*
* <p>Its table is written that far left of the cell's text. LibreOffice starts a nested table
* at its cell's text, and Word may. Measured on 3pt accents: LibreOffice set them 1.5pt right
* of the page's, a box's in a ruled table cell 1pt; Word 1.55 to 1.73pt right in a row's cell
* and in a table cell, filled or not, and 0.04 to 0.17pt in a filled panel's cell or a row
* inside one. A margin below zero moves the whole table, its text with it.</p>
*
* @param hung how far left of the cell's text the page sets it, in points
* @param bled whether a margin below zero sets the panel there, not its left border alone
*/
private static String leftEdgeHungOut(double hung, boolean bled) {
return (bled ? "the panel" : "its left edge") + ", which the page sets " + pointsOf(hung)
+ "pt left of its table cell's text, comes out up to about that much right of the page's"
+ (bled ? ", its text with it" : "") + ": LibreOffice starts a nested table at its cell's text, "
+ "and Word may";
}

/**
* Whether a panel paints up to its right end: a fill, or a border along its top, its foot or
* its right side. A left border alone stands where the page draws it, however wide the table.
Expand All @@ -6983,13 +7006,14 @@ private static boolean paintsItsRightEnd(DocumentColor fill, DocumentBorders bor
* What a panel in a cell whose left border hangs left of the cell's text gives up of its
* table's width, in points.
*
* <p>Word starts such a panel half that border in from the cell's text, and a table that then
* runs past the cell widens the cell, fixed layout or not: CompactMono's rail, a 3pt accent at
* its column's edge, pushed the main column 2.3pt right in Word. It gives up what runs past,
* no more than its left side had given its text — the half border the table is wider by, and
* what of the other half its margin could give back — so its text keeps the page's width and
* the slack. A panel bled left by a negative margin keeps the rest, as its text would
* otherwise narrow and wrap.</p>
* <p>Word, in a row's or a table's cell, starts such a panel half that border in from the
* cell's text (inside a filled panel it sets it where it is written: see
* {@link #leftEdgeHungOut}), and a table that then runs past the cell widens the cell, fixed
* layout or not: CompactMono's rail, a 3pt accent at its column's edge, pushed the main
* column 2.3pt right in Word. It gives up what runs past, no more than its left side had given
* its text — the half border the table is wider by, and what of the other half its margin
* could give back — so its text keeps the page's width and the slack. A panel bled left by a
* negative margin keeps the rest, as its text would otherwise narrow and wrap.</p>
*
* @param outer the table's width, in points
* @param indent where the table starts from the cell's text, in points
Expand Down Expand Up @@ -7221,24 +7245,26 @@ private void writePanelPiece(XWPFDocument document, DocumentNode node, Container
double width = sized.width();
DocumentInsets margins = insideTheBorders(padding, borders);

double edge = insetLeft + margin.left();
// A cell of a row the layout placed already starts past the child's left margin.
double edge = insetLeft + (node == leftMarginInCell ? margin.left() - node.margin().left() : margin.left());
// In the body the indent places the text, the padding in, and the margin is the whole
// padding (see writePanel).
boolean inTheBody = currentCell == null;
double indent = inTheBody ? edge + padding.left() : 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 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. The cell's own right margin, where the cell draws no rule of its
// own at that edge, holds what passes the cell's text — a row's gap beside the cell: held
// in at the text, the right borders of Panel's left-column cards and of Obsidian's,
// Metered's and Workspace's cards beside a gap stood 0.6 to 0.9pt left of the page's in
// Word, and 0.3 to 0.6pt right of it held at the edge. A ruled cell's margin, which its
// rule may cover, is left to the text.
// In a row's or a table's cell outside a filled panel, Word starts a nested table no
// further left than the cell's text (leftEdgeHungOut). It draws the 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 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
// 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. The cell's
// own right margin, where the cell draws no rule of its own at that edge, holds what
// passes the cell's text — a row's gap beside the cell: held in at the text, the right
// borders of Panel's left-column cards and of Obsidian's, Metered's and Workspace's cards
// beside a gap stood 0.6 to 0.9pt left of the page's in Word, and 0.3 to 0.6pt right of
// it held at the edge. A ruled cell's margin, which its rule may cover, is left to the
// text.
double shortOfTheEdge = 0;
// What of the border its right margin cannot take in, and the cell cannot hold, passes
// the cell's edge.
Expand Down Expand Up @@ -7322,6 +7348,11 @@ private void writePanelPiece(XWPFDocument document, DocumentNode node, Container
if (rightBorder != null) {
report.add(DocxExportReport.Severity.APPROXIMATED, RIGHT_BORDER, layout.pathOf(node), rightBorder);
}
// Less than a tenth of a point is not seen (RIGHT_END).
if (currentCell != null && -indent >= 0.1) {
report.add(DocxExportReport.Severity.APPROXIMATED, LEFT_EDGE, layout.pathOf(node),
leftEdgeHungOut(-indent, edge < 0));
}
}
applyCellPadding(cell, new DocumentInsets(margins.top(), Math.max(0, marginHeld - slackTaken),
margins.bottom(), inTheBody ? padding.left() : margins.left()));
Expand Down Expand Up @@ -7471,7 +7502,7 @@ private void writePanelPiece(XWPFDocument document, DocumentNode node, Container
: tableProperties.addNewTblInd();
tableIndent.setType(STTblWidth.DXA);
// Signed: a nested panel with no margin starts half its border left of the cell, and
// Word starts it at the cell's text.
// LibreOffice, and Word in most cells, start it at the cell's text (leftEdgeHungOut).
tableIndent.setW(BigInteger.valueOf(Math.round(indent * POINT_TO_TWIP)));
}
if (last) {
Expand Down Expand Up @@ -16905,7 +16936,8 @@ private boolean paintsAPanelOfItsOwn(RowNode node) {
* panel's cell holds the padding as margins, which go no further than its edge.</li>
* <li>Its margin below zero above or below, or at a side in a cell: a panel owes the space
* round it on its own, where its columns' table nets the margin against the padding, and
* Word starts a table no further left than its cell's text.</li>
* Word, except in a filled panel's cell, starts a table no further left than its cell's
* text.</li>
* <li>In a band of overlapping layers, or where a stack's column measures the space above it
* or below it to the text inside it — the first block of a later layer, or the last a
* layer below resumes after: the space would hold the row's padding, and so would its
Expand Down Expand Up @@ -17047,10 +17079,10 @@ private void writeRow(XWPFDocument document, RowNode node, boolean inItsPanel) t
// never draws.
hideTableGrid(table);
// Nested in a cell, a row hanging left cannot move out of it: Word keeps a nested table
// in its cell whatever its indent. Its first column gives the hang what it can, and its
// cells' text takes the rest (see cellHang). SerifHeadline hangs each column's section
// heading left by its dash, and in Word the titles stood that far right of the page's,
// 17pt in the main column.
// in its cell whatever its indent, except in a filled panel's cell (leftEdgeHungOut). Its
// first column gives the hang what it can, and its cells' text takes the rest (see
// cellHang). SerifHeadline hangs each column's section heading left by its dash, and in
// Word the titles stood that far right of the page's, 17pt in the main column.
double hang = currentCell != null ? Math.max(0, -insetLeft) : 0;
RowGeometry geometry = applyRowGeometry(table, node, hang, inItsPanel);
boolean placedColumns = geometry.placed();
Expand Down Expand Up @@ -19411,7 +19443,8 @@ private static XWPFTable withAGrid(XWPFTable table) {
* page's, 17pt in the main column.</p>
*/
private void indentTable(XWPFTable table) {
// Nested in a cell, a hang is its cells' (writeRow): Word keeps the table in the cell.
// Nested in a cell, a hang is its cells' (writeRow): Word keeps the table in the cell,
// except in a filled panel's (leftEdgeHungOut).
if (!(Math.abs(insetLeft) > 0.01) || currentCell != null && insetLeft < 0
|| table.getRows().isEmpty() || table.getRow(0).getTableCells().isEmpty()) {
return;
Expand Down
Loading
Loading