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 centred or right-aligned line that fills its box keeps to one line in Word.** Word sets
a size in half points, so a 6.8pt line is set at 7pt, 2.9% wider, and no longer fits a box it
fills. A flush-left paragraph was already given that much more measure; a centred or
right-aligned one of one line now is too, at both edges or at its left, so the line stays
where the page sets it. In a table cell the left edge gives no more than its indent, since
Word draws no text past a cell's left edge, and a line short of that moves by the rest.
`VioletGrid`'s "INFORMATION ARCHITECTURE" broke onto two lines in its skill tile, and the
tile's text stood 7.5pt low under it. Lines more than 2pt off in Word fall from 2 to none
across the corpus. In LibreOffice, which still sets `VioletGrid` on two pages, its median
falls from 5.2pt to 0.4pt.
- **A table or a row keeps its own margins in DOCX.** Its left margin is now written into its
indent (`w:tblInd`), as a paragraph's is: it was dropped everywhere, and `PaymentsInvoice`'s
bank details stood 36.7pt left of the page's in Word. A table indented in a cell takes that
Expand Down
12 changes: 9 additions & 3 deletions docs/recipes/docx-export.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,9 +309,15 @@ of several lines stays a point short of that, unless a line needs the room, so a
broke off by a fraction of a point is not pulled back up. A paragraph of one line is never
narrowed, and has room for a line three hundredths wider than its own: Word sets a face other
than the page's, or emboldens one whose file does not say it is bold, a little wider, and a
line of one broken in Word is a line more. A centred or right-aligned paragraph keeps
the page's measure, as moving its other edge would move its lines. A list's items, a line pair's
line, text over the flow and a header's or footer's line keep the page's measure too.
line of one broken in Word is a line more. A centred or right-aligned paragraph of one line
whose line, as Word sets it and three hundredths wider, does not fit its box is given that
room: at both edges, half each, or at its left edge, so the line stays where the page sets
it. In a table cell, where Word draws no text past the cell's left edge, the left indent
gives no more than it has, and the right edge gives the rest, the line moving by that much.
Any other centred or right-aligned paragraph keeps the page's measure: in one of several
lines a wider measure takes another word onto a line, and a short line's box already has
room for it. A list's items, a line pair's line, text over the flow and a header's or
footer's line keep the page's measure too.

## Named styles, so the document can be restyled

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1727,8 +1727,9 @@ cv-violet_grid 0:b96067918254 -0.26 uxresearch
cv-violet_grid 0:9058674fd905 -0.21 userinterviews,surveys,
cv-violet_grid 0:0cdd38014d79 -0.22 personas,journeymaps,
cv-violet_grid 0:61e4ba28d469 -0.22 competitiveanalysis
cv-violet_grid 0:db5c6756c4b6 7.59 sitemaps,userflows,card
cv-violet_grid 0:6c7ad1834a60 7.58 sorting,contentstrategy
cv-violet_grid 0:3de6be58cb9f -0.26 informationarchitecture
cv-violet_grid 0:db5c6756c4b6 -0.21 sitemaps,userflows,card
cv-violet_grid 0:6c7ad1834a60 -0.22 sorting,contentstrategy
cv-violet_grid 0:187737d59250 -0.26 wireframing
cv-violet_grid 0:2286e5aa328a -0.21 lowtohigh-fidelity
cv-violet_grid 0:5c4b21cb1af8 -0.26 prototyping
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ cv-slate_orange 1 1 87 86 3.27 3.87 86
cv-teal_pulse 1 1 57 57 0.22 0.64 0
cv-terracotta_rail 1 1 102 102 0.44 0.94 0
cv-timeline_minimal 2 2 89 86 0.45 0.50 0
cv-violet_grid 1 2 80 76 5.21 21.41 39
cv-violet_grid 1 2 80 77 0.36 21.41 37
invoice-classic 1 1 56 56 0.12 0.30 0
invoice-classic_long 2 2 166 166 0.20 0.27 0
invoice-consulting 1 1 92 92 0.67 0.96 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1633,8 +1633,9 @@ cv-violet_grid 0:b96067918254 -0.26 uxresearch
cv-violet_grid 0:9058674fd905 -0.21 userinterviews,surveys,
cv-violet_grid 0:0cdd38014d79 -0.22 personas,journeymaps,
cv-violet_grid 0:61e4ba28d469 -0.22 competitiveanalysis
cv-violet_grid 0:db5c6756c4b6 7.59 sitemaps,userflows,card
cv-violet_grid 0:6c7ad1834a60 7.58 sorting,contentstrategy
cv-violet_grid 0:3de6be58cb9f -0.26 informationarchitecture
cv-violet_grid 0:db5c6756c4b6 -0.21 sitemaps,userflows,card
cv-violet_grid 0:6c7ad1834a60 -0.22 sorting,contentstrategy
cv-violet_grid 0:187737d59250 -0.26 wireframing
cv-violet_grid 0:2286e5aa328a -0.21 lowtohigh-fidelity
cv-violet_grid 0:5c4b21cb1af8 -0.26 prototyping
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ cv-slate_orange 1 1 87 86 3.27 3.87 86
cv-teal_pulse 1 1 57 57 0.22 0.64 0
cv-terracotta_rail 1 1 102 102 0.44 0.94 0
cv-timeline_minimal 2 2 89 86 0.45 0.50 0
cv-violet_grid 1 2 80 76 5.21 21.41 39
cv-violet_grid 1 2 80 77 0.36 21.41 37
invoice-classic 1 1 56 56 0.17 0.32 0
invoice-classic_long 2 2 166 166 0.22 0.27 0
invoice-consulting 1 1 92 92 0.67 1.01 0
Expand Down
5 changes: 3 additions & 2 deletions qa/src/test/resources/docx-fidelity/word-windows-lines.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -1726,8 +1726,9 @@ cv-violet_grid 0:b96067918254 -0.23 uxresearch
cv-violet_grid 0:9058674fd905 -0.28 userinterviews,surveys,
cv-violet_grid 0:0cdd38014d79 -0.36 personas,journeymaps,
cv-violet_grid 0:61e4ba28d469 -0.28 competitiveanalysis
cv-violet_grid 0:db5c6756c4b6 7.52 sitemaps,userflows,card
cv-violet_grid 0:6c7ad1834a60 7.47 sorting,contentstrategy
cv-violet_grid 0:3de6be58cb9f -0.23 informationarchitecture
cv-violet_grid 0:db5c6756c4b6 -0.28 sitemaps,userflows,card
cv-violet_grid 0:6c7ad1834a60 -0.36 sorting,contentstrategy
cv-violet_grid 0:187737d59250 -0.23 wireframing
cv-violet_grid 0:2286e5aa328a -0.28 lowtohigh-fidelity
cv-violet_grid 0:5c4b21cb1af8 -0.23 prototyping
Expand Down
2 changes: 1 addition & 1 deletion qa/src/test/resources/docx-fidelity/word-windows.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ cv-slate_orange 1 1 87 85 0.53 1.18 0
cv-teal_pulse 1 1 57 57 0.11 0.38 0
cv-terracotta_rail 1 1 102 102 0.42 0.87 0
cv-timeline_minimal 2 2 89 86 0.36 0.48 0
cv-violet_grid 1 1 80 77 0.22 1.09 2
cv-violet_grid 1 1 80 78 0.23 0.37 0
invoice-classic 1 1 56 56 0.22 0.36 0
invoice-classic_long 2 2 166 166 0.27 0.34 0
invoice-consulting 1 1 92 92 0.70 1.02 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4707,25 +4707,93 @@ private boolean letTheLineStandOut(XWPFParagraph para, ParagraphNode node, doubl
* The glyphs are left as Word sets them — a scale would stay on the text a reader types
* next — and the right indent gives the line the same share more room, or takes it.</p>
*
* <p>Only a paragraph set flush left: moving a centred or right-aligned line's other edge
* would move the line off where the page sets it. A list's items, a line pair, text over
* the flow and a header's or footer's line are written elsewhere and keep the page's
* measure.</p>
* <p>A paragraph set flush left has its measure moved at its right edge. A centred or
* right-aligned one is given room only for a line of its own that Word sets past its box
* or within three hundredths of it ({@link #ONE_LINE_FACE_SLACK}), at both edges, half each,
* or at its left, so the line stays where the page sets it — in a cell, where Word draws no
* text past the left edge, the right edge gives what the left indent cannot:
* {@code VioletGrid}'s 6.8pt "INFORMATION ARCHITECTURE", set at 7pt and centred in a tile
* it fills, broke onto two lines, and the tile's text stood 7.5pt low under it. A centred
* paragraph of several lines is left as it is — a wider measure there takes another word
* onto a line Word already breaks elsewhere than the page — and so is a short line, whose
* box has room for it: measured, {@code MerchantInvoice}'s centred amounts, their cells'
* indents taken past the cells' edges, stood 0.12pt off their place. A list's items, a line pair, text over the flow and
* a header's or footer's line are written elsewhere and keep the page's measure.</p>
*
* @param room the width the paragraph's text is written in, in points
*/
private void measureAtWordsSize(XWPFParagraph para, ParagraphNode node, double room) {
if (node.align() == TextAlign.CENTER || node.align() == TextAlign.RIGHT || !Double.isFinite(room)) {
boolean flushLeft = node.align() != TextAlign.CENTER && node.align() != TextAlign.RIGHT;
if (!Double.isFinite(room) || !flushLeft && layout.lineCount(node) != 1) {
return;
}
double more = wordsMeasure(node, room) - room;
double more = (flushLeft ? wordsMeasure(node, room) : oneLinesMeasure(node, room)) - room;
if (!Double.isFinite(more) || Math.abs(more) < 0.05) {
return;
}
CTPPr properties = para.getCTP().isSetPPr() ? para.getCTP().getPPr() : para.getCTP().addNewPPr();
CTInd indent = properties.isSetInd() ? properties.getInd() : properties.addNewInd();
long right = twipsOf(indent.isSetRight() ? indent.getRight() : null);
indent.setRight(BigInteger.valueOf(right - Math.round(more * POINT_TO_TWIP)));
long twips = Math.round(more * POINT_TO_TWIP);
// The edge the text does not lean on moves: a centred line's two, half each.
long fromTheLeft = node.align() == TextAlign.RIGHT ? twips
: node.align() == TextAlign.CENTER ? twips / 2
: 0;
long left = twipsOf(indent.isSetLeft() ? indent.getLeft() : null);
if (currentCell != null) {
// Word draws no text past a cell's left edge (see leftIndentTwips): what the left
// indent cannot give, the right one does, and the line moves by that much. A hanging
// first line already starts that much further left.
long hanging = twipsOf(indent.isSetHanging() ? indent.getHanging() : null);
fromTheLeft = Math.min(fromTheLeft, Math.max(0, left - hanging));
}
long fromTheRight = twips - fromTheLeft;
if (fromTheLeft != 0) {
indent.setLeft(BigInteger.valueOf(left - fromTheLeft));
}
if (fromTheRight != 0) {
long right = twipsOf(indent.isSetRight() ? indent.getRight() : null);
indent.setRight(BigInteger.valueOf(right - fromTheRight));
}
}

/**
* The measure a paragraph of one line needs in Word, in points: its box, or its line as wide
* as Word sets it and a few hundredths more ({@link #ONE_LINE_FACE_SLACK}), whichever is
* wider.
*/
private double oneLinesMeasure(ParagraphNode node, double room) {
double set = 0;
for (com.demcha.compose.document.layout.payloads.ParagraphLine line : layout.lines(node)) {
// A line of pictures alone is set at their size, in Word as on the page.
boolean text = line.spans().stream().anyMatch(span -> span.width() > 0
&& span instanceof com.demcha.compose.document.layout.payloads.ParagraphTextSpan run
&& run.textStyle() != null && run.textStyle().size() > 0);
if (text) {
set = Math.max(set, widthAtWordsSize(line));
}
}
return Math.max(room, set * (1 + ONE_LINE_FACE_SLACK));
}

/**
* How wide Word sets a line the page laid out: its text grown or shrunk to Word's size, its
* tracking, pictures and shapes as the page has them.
*/
private static double widthAtWordsSize(com.demcha.compose.document.layout.payloads.ParagraphLine line) {
double set = 0;
for (com.demcha.compose.document.layout.payloads.ParagraphSpan span : line.spans()) {
if (!(span.width() > 0)) {
continue;
}
if (span instanceof com.demcha.compose.document.layout.payloads.ParagraphTextSpan run
&& run.textStyle() != null && run.textStyle().size() > 0) {
double tracking = run.textStyle().letterSpacing() * run.text().codePointCount(0, run.text().length());
set += (span.width() - tracking) * wordsSize(run.textStyle().size()) / run.textStyle().size() + tracking;
} else {
set += span.width();
}
}
return set;
}

/** How far inside the page's measure, grown or shrunk as Word sets it, Word's is held, in points. */
Expand Down Expand Up @@ -4771,22 +4839,15 @@ private double wordsMeasure(ParagraphNode node, double room) {
for (com.demcha.compose.document.layout.payloads.ParagraphLine line : layout.lines(node)) {
broken++;
double asked = 0;
double set = 0;
boolean text = false;
for (com.demcha.compose.document.layout.payloads.ParagraphSpan span : line.spans()) {
if (!(span.width() > 0)) {
continue;
}
asked += span.width();
if (span instanceof com.demcha.compose.document.layout.payloads.ParagraphTextSpan run
&& run.textStyle() != null && run.textStyle().size() > 0) {
double tracking = run.textStyle().letterSpacing() * run.text().codePointCount(0, run.text().length());
set += (span.width() - tracking) * wordsSize(run.textStyle().size()) / run.textStyle().size() + tracking;
text = true;
} else {
set += span.width();
if (span.width() > 0) {
asked += span.width();
text |= span instanceof com.demcha.compose.document.layout.payloads.ParagraphTextSpan run
&& run.textStyle() != null && run.textStyle().size() > 0;
}
}
double set = widthAtWordsSize(line);
if (text) {
share = Double.isNaN(share) ? set / asked : Math.max(share, set / asked);
fits = Math.max(fits, set);
Expand Down
Loading
Loading