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

### Public API

- **`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
ring's top: they stood 9.6pt high, and the name and the whole column under the ring 19.3pt.
A container's paragraph layers standing one under another are now set in as one, from the
top of the first and the foot of the last.
- Each skill is a row: its label, and a meter drawn where the page draws it, so nothing is
written in the meter's cell. The paragraph a cell must end with was a line of the document's
font, and each row stood 2.1pt taller than the page's. A cell left with nothing in it now
holds a hairline.
- Each language's five dots are inline shapes, written as pictures wider than their boxes on
either side — a quarter point, and a ring's half stroke more — so each pushed the next 0.5pt
or more further along: the fifth broke onto a second line, each language row 9.7pt taller.
The text beside a shape now gives that room back, its letters set closer in whole tenths of
a point, and each dot's picture stands where the page puts its box. The page draws half the
last ring's stroke past its cell, where Word cuts a cell's content; a row's last cell is
widened by as far as the line ending in a shape reaches past it.

In Word, lines more than 2pt off fall from 30 to 0, and the p90 drift from 19.86pt to 0.58.
`CharcoalGold` finds 83 of its 85 lines in Word where it found 77. In LibreOffice the p90 falls
from 17.11pt to 3.24; two skills, which the rows' growth had carried onto the page's line,
now stand at the 2.7pt every line of the sheet stands at there.

- **`SlateOrange`'s sidebar stands where the page sets it in Word.** Each skill is a row with a
12.4pt icon on a line of its own, no text in it, and Word sizes that line itself. The
paragraph's mark, set in the icon cell's style at 10.5pt, reached below the icon, so each row
Expand Down Expand Up @@ -1521,6 +1544,12 @@ follow semantic versioning; release dates are ISO 8601.

### Tests

- **The DOCX fidelity corpus measures a line from its first letter.** A space opened a line
(`PdfLines`), and Word writes one for an empty paragraph: drawn just before a line of text
beside it, a few points off its baseline, it gave that line its own. The lines measured past
2pt in Word fall from 9 to 0 in `SlateOrange`, from 14 to 2 in `VioletGrid` and from 2 to 0
in `OrangeOps`; the text did not move. LibreOffice's measurement is unchanged.

- **The DOCX fidelity corpus reports which presets are set as the page sets them.** Each run
writes `measured-<editor>-acceptance.md` (`FidelityAcceptance`): a preset passes when the editor
sets it on the page's pages, finds 95% of its lines, sets them a median of 1pt or less from
Expand Down
15 changes: 14 additions & 1 deletion docs/recipes/docx-export.md
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,20 @@ tint it was flattened to. Recorded, like the other two.
containers round it, and a container's one layer set in from its
content's top and bottom — a
photo centred in a ring wider than it — keeps that much space above and
below it. What a table cell composes
below it; so do its paragraph layers written one under another — each
starting at or below the one before, or held as a stack of lines — as one,
from the top of the first and the foot of the last: a monogram's two
initials centred in a ring. A row's cell left with nothing written in it, its
content all drawn where the page draws it — a skill's meter beside its
label — ends with a hairline paragraph, not a line of the document's font.
An inline shape is written as a picture a quarter point wider than its box
on either side, and its ink's overhang wider still, and Word sets a picture
at its width whatever its run's spacing: the text before it gives back the
room on the left and the text after it the room on the right, its letters set
closer in whole tenths of a point, so the shape and what follows stand where
the page puts them. A run too long for a tenth a letter gives back nothing.
A row's last cell whose line ends in a shape reaching past the cell is
widened by that much, as Word cuts a cell's content at its edge. What a table cell composes
(`DocumentTableCell.node(...)`) has no place of its own in the layout:
its drawing belongs to the table, and the table draws it — an icon, a
tile, a disc under a number — anchored to the page where the page draws
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,34 @@ void linesOfTheSameLettersArePairedNearestFirst() {
assertThat(found.getValue().drift()).isEqualTo(0.5);
}

@Test
void aLineStandsWhereItsFirstLetterDoesNotWhereASpaceBeforeItDoes() throws Exception {
// An empty paragraph's space, drawn just before a label 4pt lower beside it.
Path page = Files.createTempFile("first-letter", ".pdf");
try (org.apache.pdfbox.pdmodel.PDDocument document = new org.apache.pdfbox.pdmodel.PDDocument()) {
org.apache.pdfbox.pdmodel.PDPage sheet = new org.apache.pdfbox.pdmodel.PDPage();
document.addPage(sheet);
var font = new org.apache.pdfbox.pdmodel.font.PDType1Font(
org.apache.pdfbox.pdmodel.font.Standard14Fonts.FontName.HELVETICA);
try (var content = new org.apache.pdfbox.pdmodel.PDPageContentStream(document, sheet)) {
content.beginText();
content.setFont(font, 10);
content.newLineAtOffset(50, 700);
content.showText(" ");
content.newLineAtOffset(2, -4);
content.showText("Label");
content.endText();
}
document.save(page.toFile());
}
double height = 792;

PdfLines.Line line = PdfLines.of(page).lines().get(0);
assertThat(line.key()).isEqualTo("label");
assertThat(line.baseline()).isCloseTo(height - 696, org.assertj.core.data.Offset.offset(0.01));
assertThat(line.x()).isCloseTo(52, org.assertj.core.data.Offset.offset(0.01));
}

@Test
void twoColumnsOnOneBaselineAreTwoLines() throws Exception {
Path page = pdf("columns", flow -> flow.addRow("Row", row -> row
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,24 @@ protected void processTextPosition(TextPosition glyph) {
double y = glyph.getYDirAdj();
double left = glyph.getXDirAdj();
double size = Math.max(1, glyph.getFontSizeInPt());
String letters = glyph.getUnicode();
// A space opens no line: Word writes one for an empty paragraph, and one drawn just
// before a line of text beside it, on a baseline a few points off, gave that line
// the space's baseline instead of its letters'.
boolean blank = letters == null
|| letters.codePoints().allMatch(c -> Character.isWhitespace(c) || Character.isSpaceChar(c));
boolean sameLine = !Double.isNaN(baseline)
&& Math.abs(y - baseline) < size * 0.5
&& left >= lastX - 1
&& left - lastEnd < size * 1.5;
if (!sameLine) {
endLine();
if (blank) {
return;
}
baseline = y;
x = left;
}
String letters = glyph.getUnicode();
if (letters != null) {
letters.codePoints()
.filter(c -> !Character.isWhitespace(c) && !Character.isSpaceChar(c))
Expand Down
66 changes: 33 additions & 33 deletions qa/src/test/resources/docx-fidelity/libreoffice-linux-lines.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -559,41 +559,41 @@ cv-executive 1:f20997652453 -3.10 languages:english(fluent),german
cv-executive 1:ec9d3622395e -3.10 workeligibility:eligibletoworkin
cv-executive 1:dd20ae01c3cf -3.10 opensource:maintainerofgraphcomp
cv-executive 1:33c35e9d236d -3.10 speaking:jvmsummit2024,devoxxuk2
cv-midnight_navy 0:21d2636ad3df -16.37 alexmorgan
cv-midnight_navy 0:5518402feccc -17.05 marketingmanager
cv-midnight_navy 0:21d2636ad3df 3.33 alexmorgan
cv-midnight_navy 0:5518402feccc 2.65 marketingmanager
cv-midnight_navy 0:5518402feccc#1 2.33 marketingmanager
cv-midnight_navy 0:093e7d5fdbaa -17.12 contact
cv-midnight_navy 0:01dcad830244 -16.44 +1(555)123-4567
cv-midnight_navy 0:3eef38c97138 -16.45 alex.morgan@email.com
cv-midnight_navy 0:d5a0d0abdc64 -16.45 newyork,ny,usa
cv-midnight_navy 0:84308bea4540 -16.46 linkedin
cv-midnight_navy 0:0e6a8e0b849e -16.47 portfolio
cv-midnight_navy 0:68bf0109c6f8 -17.11 education
cv-midnight_navy 0:6e1119579227 -17.18 masterofbusiness
cv-midnight_navy 0:512afb6c7631 -17.18 administration
cv-midnight_navy 0:09b0e2130c1e -16.68 universityofchicagoboothschoolof
cv-midnight_navy 0:11d404179596 -16.69 business
cv-midnight_navy 0:3b8048983ecc -17.07 2016–2018
cv-midnight_navy 0:093e7d5fdbaa 2.58 contact
cv-midnight_navy 0:01dcad830244 3.26 +1(555)123-4567
cv-midnight_navy 0:3eef38c97138 3.25 alex.morgan@email.com
cv-midnight_navy 0:d5a0d0abdc64 3.25 newyork,ny,usa
cv-midnight_navy 0:84308bea4540 3.24 linkedin
cv-midnight_navy 0:0e6a8e0b849e 3.23 portfolio
cv-midnight_navy 0:68bf0109c6f8 2.59 education
cv-midnight_navy 0:6e1119579227 2.52 masterofbusiness
cv-midnight_navy 0:512afb6c7631 2.52 administration
cv-midnight_navy 0:09b0e2130c1e 3.02 universityofchicagoboothschoolof
cv-midnight_navy 0:11d404179596 3.01 business
cv-midnight_navy 0:3b8048983ecc 2.63 2016–2018
cv-midnight_navy 0:3b8048983ecc#1 2.76 2016–2018
cv-midnight_navy 0:84c2243138f7 -17.20 bachelorofsciencein
cv-midnight_navy 0:e2a530e251d3 -17.19 marketing
cv-midnight_navy 0:1f676d444ffb -16.99 bostonuniversity
cv-midnight_navy 0:8789bd1a8f91 -17.07 2012–2016
cv-midnight_navy 0:32c1cf49a2fe -17.15 skills
cv-midnight_navy 0:4a3750fc1d48 -15.95 strategicplanning
cv-midnight_navy 0:0d676ed982b4 -13.85 digitalmarketing
cv-midnight_navy 0:c29cfa799c8d -11.74 marketresearch
cv-midnight_navy 0:2c5646de026b -9.64 brandmanagement
cv-midnight_navy 0:2f594f65f16c -7.53 dataanalysis
cv-midnight_navy 0:340455fbac25 -5.43 projectmanagement
cv-midnight_navy 0:96fa20c5c60d -3.33 googleanalytics
cv-midnight_navy 0:9bc298c12dc3 -1.22 seo/sem
cv-midnight_navy 0:a95ee8d8cf67 0.88 microsoftofficesuite
cv-midnight_navy 0:d4e16994df90 2.99 communication
cv-midnight_navy 0:6cb574fa10b6 3.88 languages
cv-midnight_navy 0:b6234d2ea0d6 8.86 english
cv-midnight_navy 0:9c9b13446cd9 18.59 spanish
cv-midnight_navy 0:21bb36ad0638 28.32 french
cv-midnight_navy 0:84c2243138f7 2.50 bachelorofsciencein
cv-midnight_navy 0:e2a530e251d3 2.51 marketing
cv-midnight_navy 0:1f676d444ffb 2.71 bostonuniversity
cv-midnight_navy 0:8789bd1a8f91 2.63 2012–2016
cv-midnight_navy 0:32c1cf49a2fe 2.55 skills
cv-midnight_navy 0:4a3750fc1d48 2.70 strategicplanning
cv-midnight_navy 0:0d676ed982b4 2.70 digitalmarketing
cv-midnight_navy 0:c29cfa799c8d 2.71 marketresearch
cv-midnight_navy 0:2c5646de026b 2.71 brandmanagement
cv-midnight_navy 0:2f594f65f16c 2.72 dataanalysis
cv-midnight_navy 0:340455fbac25 2.72 projectmanagement
cv-midnight_navy 0:96fa20c5c60d 2.72 googleanalytics
cv-midnight_navy 0:9bc298c12dc3 2.73 seo/sem
cv-midnight_navy 0:a95ee8d8cf67 2.73 microsoftofficesuite
cv-midnight_navy 0:d4e16994df90 2.74 communication
cv-midnight_navy 0:6cb574fa10b6 2.58 languages
cv-midnight_navy 0:b6234d2ea0d6 2.71 english
cv-midnight_navy 0:9c9b13446cd9 2.74 spanish
cv-midnight_navy 0:21bb36ad0638 2.77 french
cv-midnight_navy 0:b944906ed84c 2.44 professionalsummary
cv-midnight_navy 0:05b2b441ce7b 3.20 results-drivenmarketingmanagerwi
cv-midnight_navy 0:66d5ff95c72a 3.18 executingdata-drivenmarketingstr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ cv-compact_mono 1 1 58 58 0.27 0.93 0
cv-editorial_blue 2 2 82 82 0.11 1.78 2
cv-engineering_resume 1 1 57 57 0.60 1.62 0
cv-executive 2 2 60 60 0.24 3.10 15
cv-midnight_navy 1 1 86 80 3.12 17.11 74
cv-midnight_navy 1 1 86 80 2.72 3.24 76
cv-minimal_underlined 2 2 59 57 0.69 13.40 9
cv-mint_editorial 2 2 59 59 0.27 0.60 0
cv-modern_professional 2 2 61 59 0.12 0.59 5
Expand Down
66 changes: 33 additions & 33 deletions qa/src/test/resources/docx-fidelity/libreoffice-windows-lines.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -558,41 +558,41 @@ cv-executive 1:f20997652453 -3.10 languages:english(fluent),german
cv-executive 1:ec9d3622395e -3.10 workeligibility:eligibletoworkin
cv-executive 1:dd20ae01c3cf -3.10 opensource:maintainerofgraphcomp
cv-executive 1:33c35e9d236d -3.10 speaking:jvmsummit2024,devoxxuk2
cv-midnight_navy 0:21d2636ad3df -16.37 alexmorgan
cv-midnight_navy 0:5518402feccc -17.05 marketingmanager
cv-midnight_navy 0:21d2636ad3df 3.33 alexmorgan
cv-midnight_navy 0:5518402feccc 2.65 marketingmanager
cv-midnight_navy 0:5518402feccc#1 2.33 marketingmanager
cv-midnight_navy 0:093e7d5fdbaa -17.12 contact
cv-midnight_navy 0:01dcad830244 -16.44 +1(555)123-4567
cv-midnight_navy 0:3eef38c97138 -16.45 alex.morgan@email.com
cv-midnight_navy 0:d5a0d0abdc64 -16.45 newyork,ny,usa
cv-midnight_navy 0:84308bea4540 -16.46 linkedin
cv-midnight_navy 0:0e6a8e0b849e -16.47 portfolio
cv-midnight_navy 0:68bf0109c6f8 -17.11 education
cv-midnight_navy 0:6e1119579227 -17.18 masterofbusiness
cv-midnight_navy 0:512afb6c7631 -17.18 administration
cv-midnight_navy 0:09b0e2130c1e -16.68 universityofchicagoboothschoolof
cv-midnight_navy 0:11d404179596 -16.69 business
cv-midnight_navy 0:3b8048983ecc -17.07 2016–2018
cv-midnight_navy 0:093e7d5fdbaa 2.58 contact
cv-midnight_navy 0:01dcad830244 3.26 +1(555)123-4567
cv-midnight_navy 0:3eef38c97138 3.25 alex.morgan@email.com
cv-midnight_navy 0:d5a0d0abdc64 3.25 newyork,ny,usa
cv-midnight_navy 0:84308bea4540 3.24 linkedin
cv-midnight_navy 0:0e6a8e0b849e 3.23 portfolio
cv-midnight_navy 0:68bf0109c6f8 2.59 education
cv-midnight_navy 0:6e1119579227 2.52 masterofbusiness
cv-midnight_navy 0:512afb6c7631 2.52 administration
cv-midnight_navy 0:09b0e2130c1e 3.02 universityofchicagoboothschoolof
cv-midnight_navy 0:11d404179596 3.01 business
cv-midnight_navy 0:3b8048983ecc 2.63 2016–2018
cv-midnight_navy 0:3b8048983ecc#1 2.76 2016–2018
cv-midnight_navy 0:84c2243138f7 -17.20 bachelorofsciencein
cv-midnight_navy 0:e2a530e251d3 -17.19 marketing
cv-midnight_navy 0:1f676d444ffb -16.99 bostonuniversity
cv-midnight_navy 0:8789bd1a8f91 -17.07 2012–2016
cv-midnight_navy 0:32c1cf49a2fe -17.15 skills
cv-midnight_navy 0:4a3750fc1d48 -15.95 strategicplanning
cv-midnight_navy 0:0d676ed982b4 -13.85 digitalmarketing
cv-midnight_navy 0:c29cfa799c8d -11.74 marketresearch
cv-midnight_navy 0:2c5646de026b -9.64 brandmanagement
cv-midnight_navy 0:2f594f65f16c -7.53 dataanalysis
cv-midnight_navy 0:340455fbac25 -5.43 projectmanagement
cv-midnight_navy 0:96fa20c5c60d -3.33 googleanalytics
cv-midnight_navy 0:9bc298c12dc3 -1.22 seo/sem
cv-midnight_navy 0:a95ee8d8cf67 0.88 microsoftofficesuite
cv-midnight_navy 0:d4e16994df90 2.99 communication
cv-midnight_navy 0:6cb574fa10b6 3.88 languages
cv-midnight_navy 0:b6234d2ea0d6 8.86 english
cv-midnight_navy 0:9c9b13446cd9 18.59 spanish
cv-midnight_navy 0:21bb36ad0638 28.32 french
cv-midnight_navy 0:84c2243138f7 2.50 bachelorofsciencein
cv-midnight_navy 0:e2a530e251d3 2.51 marketing
cv-midnight_navy 0:1f676d444ffb 2.71 bostonuniversity
cv-midnight_navy 0:8789bd1a8f91 2.63 2012–2016
cv-midnight_navy 0:32c1cf49a2fe 2.55 skills
cv-midnight_navy 0:4a3750fc1d48 2.70 strategicplanning
cv-midnight_navy 0:0d676ed982b4 2.70 digitalmarketing
cv-midnight_navy 0:c29cfa799c8d 2.71 marketresearch
cv-midnight_navy 0:2c5646de026b 2.71 brandmanagement
cv-midnight_navy 0:2f594f65f16c 2.72 dataanalysis
cv-midnight_navy 0:340455fbac25 2.72 projectmanagement
cv-midnight_navy 0:96fa20c5c60d 2.72 googleanalytics
cv-midnight_navy 0:9bc298c12dc3 2.73 seo/sem
cv-midnight_navy 0:a95ee8d8cf67 2.73 microsoftofficesuite
cv-midnight_navy 0:d4e16994df90 2.74 communication
cv-midnight_navy 0:6cb574fa10b6 2.58 languages
cv-midnight_navy 0:b6234d2ea0d6 2.71 english
cv-midnight_navy 0:9c9b13446cd9 2.74 spanish
cv-midnight_navy 0:21bb36ad0638 2.77 french
cv-midnight_navy 0:b944906ed84c 2.44 professionalsummary
cv-midnight_navy 0:05b2b441ce7b 3.20 results-drivenmarketingmanagerwi
cv-midnight_navy 0:66d5ff95c72a 3.18 executingdata-drivenmarketingstr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ cv-compact_mono 1 1 58 58 0.27 0.93 0
cv-editorial_blue 2 2 82 81 0.41 10.06 9
cv-engineering_resume 1 1 57 57 0.60 1.62 0
cv-executive 2 2 60 60 0.24 3.10 15
cv-midnight_navy 1 1 86 80 3.12 17.11 74
cv-midnight_navy 1 1 86 80 2.72 3.24 76
cv-minimal_underlined 2 2 59 57 0.69 13.40 9
cv-mint_editorial 2 2 59 59 0.27 0.60 0
cv-modern_professional 2 2 61 59 0.12 0.59 5
Expand Down
Loading
Loading