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

### Public API

- **A row's fixed columns keep their widths in DOCX when they add up to less than the row.**
The last column ran to the row's edge: `EditorialProposal`'s deliverables, a 194pt column in a
535pt band, came out 272pt wide, and Word set "Responsive design for desktop, tablet & mobile"
on one line where the page wraps it, and every item under it a line higher. The scope of work's
descriptions and `PaymentsInvoice`'s item text, both the last of a row's fixed columns, wrapped
later than the page the same way. The last column now keeps its stated width, and the table is
no wider than its columns. In Word,
`EditorialProposal` finds 128 of its 132 lines where it found 120, and those more than 2pt off
fall from 22 to 19; `PaymentsInvoice` finds 104 of 104, none past 2pt, where it found 100.

- **`MidnightNavy`'s sidebar stands where the page sets it in Word.** Three causes, each its own:
- Its monogram centres two initials in a 76pt ring, two layers of one shape container. Only a
container of one layer was set in from its outline, so the initials were written from the
Expand Down
2 changes: 1 addition & 1 deletion docs/recipes/docx-export.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ creation date is real metadata.
| Inline chips (`inlineCode(...)`, `inlineChip(...)`, `highlight(...)`) | The chip's fill becomes the run's own `w:shd`, in a paragraph and in a list item alike. Its shape does not travel — see "What a chip keeps and loses" below |
| Images | Embedded pictures at the node's declared size |
| Links and anchors | A `linkTarget` becomes a `w:hyperlink` — a relationship for an address, `w:anchor` for one of the document's own anchors — and a run's own link wins over the paragraph's — in a list item as much as in a paragraph. An `anchor(...)` becomes a bookmark wrapping that paragraph's text, named as Word requires; on a section, container, table or image it wraps everything the block wrote, from the start of its first paragraph to the end of its last, so a link to a block lands on its first line. A `bookmark(...)` outline level becomes Word's own `HeadingN` style, which is what puts the paragraph in the Navigation Pane, the outline view and a generated table of contents. The style states the outline level and nothing else, so the paragraph keeps its own formatting. The role comes from what the document declared, never from how big the text is |
| Rows | A one-row table spanning the content width, so editors keep the side-by-side layout. The row's slots become the column grid when they are weights, an even split or fixed columns; the gap and the row's padding ride in the neighbouring column and come back out as that cell's margin; a cell holds whatever its child is, written as it is anywhere else. The row's `verticalAlign` is every cell's `w:vAlign`, so a child shorter than the row sits at its middle or bottom as on the page — a table of contents' leader on its entry's baseline. The row is kept whole across a page break, as the layout keeps it. A row is held at least as tall as the page makes it inside a painted panel, and anywhere its tallest child is a drawing Word holds nothing of in its cell — a badge beside a heading. A section or other container in any cell — a row's, a table's, a panel's — that pulls its first line up with a negative top edge writes that one-line paragraph as much shorter, its text seated where the page sets it — no more than the room above its letters, as Word draws an exact line's text only inside the line |
| Rows | A one-row table spanning the content width — or only its columns, where fixed columns leave part of the row empty — so editors keep the side-by-side layout. The row's slots become the column grid when they are weights, an even split or fixed columns; fixed columns that add up to less than the row leave the rest of it empty, the last one at its own width and the table no wider than its columns, so its text wraps where the page wraps it; the gap and the row's padding ride in the neighbouring column and come back out as that cell's margin; a cell holds whatever its child is, written as it is anywhere else. The row's `verticalAlign` is every cell's `w:vAlign`, so a child shorter than the row sits at its middle or bottom as on the page — a table of contents' leader on its entry's baseline. The row is kept whole across a page break, as the layout keeps it. A row is held at least as tall as the page makes it inside a painted panel, and anywhere its tallest child is a drawing Word holds nothing of in its cell — a badge beside a heading. A section or other container in any cell — a row's, a table's, a panel's — that pulls its first line up with a negative top edge writes that one-line paragraph as much shorter, its text seated where the page sets it — no more than the room above its letters, as Word draws an exact line's text only inside the line |
| Sections / containers | Children written in order. A timeline with its markers on the rail (`markerOnRail()`) lays each entry's body out in the header row's content column, below the row; the body is written in the flow, indented to that column where the page puts it. A container with a fill, per-side borders or a uniform stroke is a one-cell table carrying them, its padding as the cell's margins, so a card keeps its panel — see "What a panel keeps and loses" below. A `keepTogether()` or `keepWithNext()` block the layout placed on one page stays on one page in Word too (`w:keepLines` + `w:keepNext`, and a row that may not split for a panel) |
| Spacers | An empty paragraph a tenth of a point tall; the spacer's height is the space above the next block, or below this paragraph when a table follows |
| Page breaks | Explicit Word page breaks |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3061,7 +3061,9 @@ invoice-payments 0:1a7b308d8df6 0.24 quantity
invoice-payments 0:d1062d072acd 0.24 unitprice
invoice-payments 0:7c512b48ba8a 0.24 vat
invoice-payments 0:2da123241332 0.24 amount(gbp)
invoice-payments 0:3ab6c1099bcc 5.43 subscriptionbilling
invoice-payments 0:3ab6c1099bcc 0.43 subscriptionbilling
invoice-payments 0:c8e69cd6e074 0.57 subscriptionmanagementandrecurri
invoice-payments 0:0c95c7ece1ce 0.41 billing
invoice-payments 0:96d6b62c061d 0.40 £250.00
invoice-payments 0:96d6b62c061d#1 0.41 £250.00
invoice-payments 0:dc934fe30a94 0.40 20%
Expand All @@ -3070,7 +3072,9 @@ invoice-payments 0:dc934fe30a94#2 0.42 20%
invoice-payments 0:dc934fe30a94#3 0.41 20%
invoice-payments 0:dc934fe30a94#4 0.40 20%
invoice-payments 0:dc934fe30a94#5 0.59 20%
invoice-payments 0:0a9acaea6477 5.43 paymentprocessing
invoice-payments 0:0a9acaea6477 0.43 paymentprocessing
invoice-payments 0:b8a3c21545b4 0.57 onlinecardpaymentsprocessing
invoice-payments 0:b42561a38a06 0.41 (volumetier)
invoice-payments 0:351704ebff51 0.40 £1,180.00
invoice-payments 0:351704ebff51#1 0.41 £1,180.00
invoice-payments 0:b3bc34f90a55 0.42 fraudscreening
Expand Down Expand Up @@ -3618,24 +3622,32 @@ proposal-editorial 0:e8228bfeba0e 0.15 foundationforfuture
proposal-editorial 0:1fa35c7a1402 -0.85 growth.
proposal-editorial 0:bc981abcdf59 -1.33 scopeofwork
proposal-editorial 0:28450edd9a4f -0.25 discovery&research
proposal-editorial 0:ce9012b74aa2 0.00 stakeholderinterviews,brandaudit
proposal-editorial 0:730c3e278933 -0.28 audienceinsightstoinformstrategy
proposal-editorial 0:12bff3036b66 -0.16 visualidentityrefinement
proposal-editorial 0:937ad523a90c 0.09 refinelogosystem,colorpalette,ty
proposal-editorial 0:9b232e66f46c -0.19 guidelinesforacohesive,modernide
proposal-editorial 0:fc5134bf5fd1 -0.07 websiteux/uidesign
proposal-editorial 0:5d58b01c7096 0.18 designaresponsive,accessibility-
proposal-editorial 0:5d6068f6a879 -0.10 navigationandclearconversionpath
proposal-editorial 0:32001e20b2ed 0.02 contentstructure&messaging
proposal-editorial 0:b3c948ec3e36 0.27 definesitearchitecture,keymessag
proposal-editorial 0:231428fde7aa -0.01 communicatevalueandbuildtrust.
proposal-editorial 0:8c7703bb6fb7 0.11 launchsupport
proposal-editorial 0:34f9fe468474 0.36 developerhandoff,qasupport,train
proposal-editorial 0:2b4cf15f6f16 0.08 toensureasmoothlaunch.
proposal-editorial 0:026890c58de9 0.01 northlinestudio|northlinestudio.
proposal-editorial 1:e35c32bb5ed8 -46.41 deliverables
proposal-editorial 1:23eb79a19b39 -45.38 brandstrategysummary
proposal-editorial 1:aaa2a6073d69 -45.38 refinedlogosuite&brandguidelines
proposal-editorial 1:d626f7532564 -45.38 color,typography&designsystem
proposal-editorial 1:018ec557a513 -45.38 websiteuxwireframes
proposal-editorial 1:5ed4970fa285 -45.38 high-fidelitywebsitedesigns
proposal-editorial 1:8b712e7e93bf -57.80 contentstrategy&pagetemplates
proposal-editorial 1:e9bf06d9b130 -57.80 developer-readyassets&specificat
proposal-editorial 1:e93e7ba95ceb -57.80 launchchecklist&trainingsession
proposal-editorial 1:a579d6d1c12e -45.48 responsivedesignfordesktop,table
proposal-editorial 1:d524c1a0811d -45.13 mobile
proposal-editorial 1:8b712e7e93bf -44.40 contentstrategy&pagetemplates
proposal-editorial 1:e9bf06d9b130 -44.40 developer-readyassets&specificat
proposal-editorial 1:e93e7ba95ceb -44.40 launchchecklist&trainingsession
proposal-editorial 1:94d192b3a326 -46.32 timeline
proposal-editorial 1:02195b8e9896 -45.06 phase
proposal-editorial 1:c51faa148557 -45.06 focus
Expand Down
4 changes: 2 additions & 2 deletions qa/src/test/resources/docx-fidelity/libreoffice-windows.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ invoice-metered 1 1 99 99 0.25 0.69 0
invoice-modern 1 1 54 54 0.32 0.52 0
invoice-modern_long 2 2 162 162 0.35 0.52 0
invoice-obsidian 1 1 81 81 0.29 0.84 0
invoice-payments 1 1 104 100 0.41 0.91 2
invoice-payments 1 1 104 104 0.41 0.90 0
invoice-platform 1 1 111 111 0.27 0.43 0
invoice-subscription 1 1 81 81 0.42 0.73 0
invoice-workspace 1 1 90 90 0.35 1.17 0
Expand All @@ -56,7 +56,7 @@ letter-nordic_clean 1 1 15 15 0.16 0.38 0
letter-panel 1 1 13 13 0.15 0.21 0
letter-sidebar_portrait 1 1 12 12 0.43 0.51 0
letter-timeline_minimal 1 1 15 15 1.32 1.52 0
proposal-editorial 2 2 132 119 43.27 45.18 68
proposal-editorial 2 2 132 127 43.26 45.15 70
proposal-indigo 1 1 72 70 0.57 27.88 16
proposal-modern 1 1 44 44 0.41 0.75 0
proposal-northline 2 2 132 132 0.38 1.62 6
Expand Down
22 changes: 17 additions & 5 deletions qa/src/test/resources/docx-fidelity/word-windows-lines.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -3159,7 +3159,9 @@ invoice-payments 0:1a7b308d8df6 0.33 quantity
invoice-payments 0:d1062d072acd 0.33 unitprice
invoice-payments 0:7c512b48ba8a 0.33 vat
invoice-payments 0:2da123241332 0.33 amount(gbp)
invoice-payments 0:3ab6c1099bcc 5.47 subscriptionbilling
invoice-payments 0:3ab6c1099bcc 0.55 subscriptionbilling
invoice-payments 0:c8e69cd6e074 0.72 subscriptionmanagementandrecurri
invoice-payments 0:0c95c7ece1ce 0.52 billing
invoice-payments 0:96d6b62c061d 0.54 £250.00
invoice-payments 0:96d6b62c061d#1 0.50 £250.00
invoice-payments 0:dc934fe30a94 0.54 20%
Expand All @@ -3168,7 +3170,9 @@ invoice-payments 0:dc934fe30a94#2 0.51 20%
invoice-payments 0:dc934fe30a94#3 0.51 20%
invoice-payments 0:dc934fe30a94#4 0.51 20%
invoice-payments 0:dc934fe30a94#5 0.62 20%
invoice-payments 0:0a9acaea6477 5.52 paymentprocessing
invoice-payments 0:0a9acaea6477 0.48 paymentprocessing
invoice-payments 0:b8a3c21545b4 0.64 onlinecardpaymentsprocessing
invoice-payments 0:b42561a38a06 0.56 (volumetier)
invoice-payments 0:351704ebff51 0.47 £1,180.00
invoice-payments 0:351704ebff51#1 0.54 £1,180.00
invoice-payments 0:b3bc34f90a55 0.48 fraudscreening
Expand Down Expand Up @@ -3716,14 +3720,20 @@ proposal-editorial 0:e8228bfeba0e 0.33 foundationforfuture
proposal-editorial 0:1fa35c7a1402 -0.68 growth.
proposal-editorial 0:bc981abcdf59 0.10 scopeofwork
proposal-editorial 0:28450edd9a4f -0.19 discovery&research
proposal-editorial 0:ce9012b74aa2 0.14 stakeholderinterviews,brandaudit
proposal-editorial 0:730c3e278933 -0.07 audienceinsightstoinformstrategy
proposal-editorial 0:12bff3036b66 0.00 visualidentityrefinement
proposal-editorial 0:937ad523a90c 0.21 refinelogosystem,colorpalette,ty
proposal-editorial 0:9b232e66f46c -0.03 guidelinesforacohesive,modernide
proposal-editorial 0:fc5134bf5fd1 0.04 websiteux/uidesign
proposal-editorial 0:5d58b01c7096 0.37 designaresponsive,accessibility-
proposal-editorial 0:5d6068f6a879 0.01 navigationandclearconversionpath
proposal-editorial 0:32001e20b2ed 0.08 contentstructure&messaging
proposal-editorial 0:b3c948ec3e36 0.41 definesitearchitecture,keymessag
proposal-editorial 0:231428fde7aa 0.17 communicatevalueandbuildtrust.
proposal-editorial 0:8c7703bb6fb7 0.24 launchsupport
proposal-editorial 0:34f9fe468474 0.45 developerhandoff,qasupport,train
proposal-editorial 0:2b4cf15f6f16 0.21 toensureasmoothlaunch.
proposal-editorial 0:026890c58de9 0.12 northlinestudio|northlinestudio.
proposal-editorial 1:5205c2a261bc 0.41 northline
proposal-editorial 1:da0daaba4b15 0.32 studio
Expand All @@ -3734,9 +3744,11 @@ proposal-editorial 1:aaa2a6073d69 0.10 refinedlogosuite&brandguidelines
proposal-editorial 1:d626f7532564 0.06 color,typography&designsystem
proposal-editorial 1:018ec557a513 0.14 websiteuxwireframes
proposal-editorial 1:5ed4970fa285 0.10 high-fidelitywebsitedesigns
proposal-editorial 1:8b712e7e93bf -12.28 contentstrategy&pagetemplates
proposal-editorial 1:e9bf06d9b130 -12.32 developer-readyassets&specificat
proposal-editorial 1:e93e7ba95ceb -12.36 launchchecklist&trainingsession
proposal-editorial 1:a579d6d1c12e 0.38 responsivedesignfordesktop,table
proposal-editorial 1:d524c1a0811d 0.65 mobile
proposal-editorial 1:8b712e7e93bf 1.04 contentstrategy&pagetemplates
proposal-editorial 1:e9bf06d9b130 1.12 developer-readyassets&specificat
proposal-editorial 1:e93e7ba95ceb 1.08 launchchecklist&trainingsession
proposal-editorial 1:94d192b3a326 0.47 timeline
proposal-editorial 1:02195b8e9896 0.50 phase
proposal-editorial 1:c51faa148557 0.50 focus
Expand Down
4 changes: 2 additions & 2 deletions qa/src/test/resources/docx-fidelity/word-windows.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ invoice-metered 1 1 99 99 0.37 0.77 0
invoice-modern 1 1 54 54 0.31 0.57 0
invoice-modern_long 2 2 162 162 0.44 0.60 0
invoice-obsidian 1 1 81 81 0.26 0.62 0
invoice-payments 1 1 104 100 0.50 1.10 2
invoice-payments 1 1 104 104 0.51 1.02 0
invoice-platform 1 1 111 111 0.19 0.41 0
invoice-subscription 1 1 81 81 0.47 0.64 0
invoice-workspace 1 1 90 90 0.44 1.26 0
Expand All @@ -56,7 +56,7 @@ letter-nordic_clean 1 1 15 15 0.16 0.35 0
letter-panel 1 1 13 13 0.17 0.25 0
letter-sidebar_portrait 1 1 12 12 0.41 0.52 0
letter-timeline_minimal 1 1 15 15 1.30 1.63 0
proposal-editorial 2 2 132 120 0.58 2.20 22
proposal-editorial 2 2 132 128 0.50 2.09 19
proposal-indigo 1 1 72 70 0.43 27.75 16
proposal-modern 1 1 44 44 0.35 0.73 0
proposal-northline 2 2 132 132 0.36 1.75 6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8851,8 +8851,9 @@ private void writeRowCellChild(XWPFTableCell cell, DocumentNode child) throws Ex
*
* <p>A row occupies the whole width it is offered — {@code measureRow} returns the
* available width unconditionally, whatever its children measure — so the carrier
* gets the content width. Left at POI's size-to-content default the pair collapses
* around its text instead.</p>
* gets the content width, unless fixed columns add up to less and leave the rest of
* the row empty ({@link #holdTheLastFixedColumn}): then it is as wide as its columns.
* Left at POI's size-to-content default the pair collapses around its text instead.</p>
*
* <p>How that width divides is arithmetic the document already carries, for every
* distribution except one. Weights, an even split and fixed columns are shares of the
Expand Down Expand Up @@ -8882,8 +8883,9 @@ private RowGeometry applyRowGeometry(XWPFTable table, RowNode node, double hang)
// The layout placed each child, so every way a row can divide — the two that
// measure their children included — is already answered.
List<CellColumn> columns = new ArrayList<>(withRowEditorSlack(node, placedColumns(node, starts)));
holdTheLastFixedColumn(node, columns);
double taken = takeHang(node, columns, hang);
setTableWidth(table, starts[0] - taken);
setTableWidth(table, Math.min(starts[0], widthOf(columns) + taken) - taken);
writeRowColumns(table, columns);
return new RowGeometry(true, taken);
}
Expand All @@ -8899,11 +8901,47 @@ private RowGeometry applyRowGeometry(XWPFTable table, RowNode node, double hang)
}
List<CellColumn> columns = new ArrayList<>(statedColumns(node, slots));
double taken = takeHang(node, columns, hang);
setTableWidth(table, available - taken);
setTableWidth(table, Math.min(available, widthOf(columns) + taken) - taken);
writeRowColumns(table, columns);
return new RowGeometry(false, taken);
}

/**
* Holds a row's last column to its fixed width, where the row states one, rather than to
* the row's edge.
*
* <p>Fixed columns that add up to less than the row leave the rest of it empty, and the
* last column's text wraps at its own width: {@code EditorialProposal}'s deliverables, a
* column of 194pt in a band of 535, wrapped "Responsive design for desktop, tablet &amp;
* mobile" onto two lines. Run to the row's edge, the cell was 272pt wide, and Word set the
* item on one line and every item under it a line higher.</p>
*/
private static void holdTheLastFixedColumn(RowNode node, List<CellColumn> columns) {
List<DocumentRowColumn> specs = node.columns();
if (columns.isEmpty() || specs.size() != columns.size()
|| specs.get(specs.size() - 1).type() != DocumentRowColumn.Type.FIXED) {
return;
}
int last = columns.size() - 1;
CellColumn column = columns.get(last);
// The column starts where its child does, past the child's left margin; its slot
// starts that margin earlier.
double margin = node.children().size() == columns.size() ? node.children().get(last).margin().left() : 0;
double held = column.leading() - margin + specs.get(last).value() + column.trailing();
if (held < column.width() - 0.01) {
columns.set(last, new CellColumn(held, column.leading(), column.trailing()));
}
}

/** The width a row's columns take together, in points. */
private static double widthOf(List<CellColumn> columns) {
double width = 0;
for (CellColumn column : columns) {
width += column.width();
}
return width;
}

/**
* How a row's carrier was divided.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,20 @@ void aRowWithNoAutoColumnIsWrittenAsPlaced() throws Exception {

assertThat(exported.gridTwips(0)).isEqualTo(exported.placedTwips(0));
assertThat(exported.gridTwips(1)).isEqualTo(exported.placedTwips(1));
assertThat(DocxTwips.of(exported.table().getCTTbl().getTblPr().getTblW().getW()))
.as("columns that fill the row keep the table at its width")
.isEqualTo(exported.placedTwips(0) + exported.placedTwips(1));
}

@Test
void aRowWithNoWeightColumnHasNoPointToGiveAndIsWrittenAsPlaced() throws Exception {
void aRowWithNoWeightColumnHasNoPointToGiveItsFixedColumnAtItsWidth() throws Exception {
ExportedRow exported = exportRow(row -> row
.columns(DocumentRowColumn.auto(), DocumentRowColumn.fixed(80))
.addParagraph("Label").addParagraph("Value"));

assertThat(exported.gridTwips(0)).isEqualTo(exported.placedTwips(0));
assertThat(exported.gridTwips(1)).isEqualTo(exported.placedTwips(1));
assertThat(exported.gridTwips(1)).as("the fixed column at its own width, the rest of the row empty")
.isEqualTo(80 * 20L);
}

@Test
Expand Down
Loading
Loading