From fe3154cc1a420f53ffd46ec5adb2d4fdc4070f9f Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Sat, 3 Oct 2026 11:39:22 +0100 Subject: [PATCH] fix(docx): take a cell's last line hanging past its row out of the gap under the row --- CHANGELOG.md | 9 + docs/recipes/docx-export.md | 6 +- .../libreoffice-windows-lines.tsv | 290 ++++++++--------- .../docx-fidelity/libreoffice-windows.tsv | 6 +- .../docx-fidelity/word-windows-lines.tsv | 292 +++++++++--------- .../resources/docx-fidelity/word-windows.tsv | 6 +- .../semantic/docx/DocxSemanticBackend.java | 45 +++ .../semantic/docx/DocxRowOverhangTest.java | 152 +++++++++ 8 files changed, 507 insertions(+), 299 deletions(-) create mode 100644 render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxRowOverhangTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 90c2d7878..9890ce6c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,15 @@ follow semantic versioning; release dates are ISO 8601. ### Public API +- **A row's cell whose last line hangs below the row takes it out of the gap under the row in + DOCX.** A line held to an icon reaches as far below the page's line as the icon's ink, and + gives that room back out of the space above what follows. The last line of a cell had + nothing following it there: Word made the row that much taller than the page's. + `TimelineMinimal`'s last contact line, its icon lowered beside the text, ended 1.8pt below + the header row, and moved the whole page under the header down that much. That reach now comes out of the + cell's own space under it first, and what passes the row comes out of the gap under the row. + `TimelineMinimal`'s median drift falls from 1.44 to 0.36pt in Word and from 1.40 to 0.45pt in + LibreOffice, its cover letter's from 1.30 to 0.29pt and 1.32 to 0.33pt. - **A spacer is as tall in DOCX as on the page.** A spacer is written as an empty paragraph a tenth of a point tall, which Word keeps (measured: ten such lines between eleven lines of text added 0.96pt), and its whole height was then owed below it as well. Every spacer stood a diff --git a/docs/recipes/docx-export.md b/docs/recipes/docx-export.md index 97634dd5f..415f360da 100644 --- a/docs/recipes/docx-export.md +++ b/docs/recipes/docx-export.md @@ -465,8 +465,10 @@ page.addParagraph(p -> p holds. A paragraph of several lines, a line holding only a picture — not seated on the page's baseline, having no text — a list item, a table's text cell and a line set - beside another are grown as above. At the end of a cell, the ink below the last line is - not taken from the cell's bottom: the row is that much taller. LibreOffice stands such a picture on + beside another are grown as above. At the end of a cell, the ink below the last line comes + out of the space the cell holds under it. In a row, what passes that, less the room the row + leaves the cell on the page, makes Word's row taller and comes out of the gap under the row; + in a panel or a stack written as columns it still makes the table taller. LibreOffice stands such a picture on the baseline, higher than the page does, and cuts what passes the line's top: a lowered icon stands up to its drop too high there, its top cut, where its text is in place. - **What an icon is.** An SVG icon — an emoji among them — is drawn into a transparent diff --git a/qa/src/test/resources/docx-fidelity/libreoffice-windows-lines.tsv b/qa/src/test/resources/docx-fidelity/libreoffice-windows-lines.tsv index ae9b8445a..9e52e775d 100644 --- a/qa/src/test/resources/docx-fidelity/libreoffice-windows-lines.tsv +++ b/qa/src/test/resources/docx-fidelity/libreoffice-windows-lines.tsv @@ -1539,74 +1539,74 @@ cv-timeline_minimal 0:89674e6f130e -0.19 +442055551000 cv-timeline_minimal 0:e0f850305ded -0.19 jordan.rivera@example.com cv-timeline_minimal 0:84308bea4540 -0.19 linkedin cv-timeline_minimal 0:c0b0109d9439 -0.19 github -cv-timeline_minimal 0:68bf0109c6f8 1.90 education -cv-timeline_minimal 0:54654b90a771 1.40 msccomputerscience|universityof -cv-timeline_minimal 0:a57111bb5a18 1.40 manchester-2019-2021 -cv-timeline_minimal 0:bf13436325bd 1.40 distinction.thesis:composablelay -cv-timeline_minimal 0:4c7268c098b3 1.40 primitivesfordeterministicdocume -cv-timeline_minimal 0:6154cc9024ae 1.40 rendering. -cv-timeline_minimal 0:17dd75fc575f 1.40 bscsoftwareengineering|imperialc -cv-timeline_minimal 0:220e665a6446 1.40 london-2015-2019 -cv-timeline_minimal 0:048a150be6e7 1.40 first-classhonours.specialisatio -cv-timeline_minimal 0:f9d33eb2a1a2 1.40 compilersandstaticanalysis. -cv-timeline_minimal 0:dddc36d8bb44 1.40 oraclejavacertification|professi -cv-timeline_minimal 0:6c065bee3945 1.40 track-2023-2024 -cv-timeline_minimal 0:40f2bfbae245 1.40 java17platformdeep-dive:records, -cv-timeline_minimal 0:6cba964fe5fe 1.40 types,patternmatching,virtualthr -cv-timeline_minimal 0:5f94c50dc5bf 1.90 technicalskills -cv-timeline_minimal 0:5d6819554c7a 1.40 languages:java21,kotlin,groovy,p -cv-timeline_minimal 0:4ec7c53222c8 1.40 sql -cv-timeline_minimal 0:db84150190bb 1.40 document&print:pdfbox,apachepoi -cv-timeline_minimal 0:69d03fef7130 1.40 (docx/xlsx),itext,postscript,icc -cv-timeline_minimal 0:689d68053b81 1.40 profiles,fontmetrics -cv-timeline_minimal 0:8ca175286c7b 1.40 layoutengines:customdsldesign, -cv-timeline_minimal 0:b6f2e7d194af 1.40 semanticlayouttrees,pagination,s -cv-timeline_minimal 0:e8951d583db6 1.40 testing,visualregression -cv-timeline_minimal 0:3b7a3ab3b6ee 1.40 build&infrastructure:maven,gradl -cv-timeline_minimal 0:d176e972b547 1.40 githubactions,jitpack,docker,jmh -cv-timeline_minimal 0:3d119f794292 1.40 benchmarking -cv-timeline_minimal 0:b7b6a102880a 1.40 testing:junit5,assertj,pdfbox-ba -cv-timeline_minimal 0:2f0dced56e7f 1.40 pngdiff,layout-graphsnapshots,mu -cv-timeline_minimal 0:c001aa5cc6fb 1.40 testing(pitest) -cv-timeline_minimal 0:90c057c30e8f 1.40 distribution:mavencentral,sonaty -cv-timeline_minimal 0:7347a19cb48d 1.40 ossrh,gpgsigning,jitpack,semanti -cv-timeline_minimal 0:b13180079f20 1.40 versioningdiscipline -cv-timeline_minimal 0:2577c0f557b2 1.90 projects -cv-timeline_minimal 0:bd82e94c4024 1.40 graphcompose(java21,pdfbox,maven -cv-timeline_minimal 0:54b7bb3a12a0 1.40 jmh):declarativejavapdflayouteng -cv-timeline_minimal 0:83ee0a75c2d4 1.40 semanticdsl,slot-basedtemplates, -cv-timeline_minimal 0:235802baf524 1.40 snapshottesting.powersproduction -cv-timeline_minimal 0:44925c9e42ee 1.40 invoice/proposalpipelinesforhiri -cv-timeline_minimal 0:ae7902eee4b2 1.40 templatestudio(kotlin,composedes -cv-timeline_minimal 0:f15436034237 1.40 pdfboxpngdiff):internaltoolfor -cv-timeline_minimal 0:24a195694090 1.40 evaluatingcv,proposal,andinvoice -cv-timeline_minimal 0:dbcc5c321887 1.40 layoutlint(java21,javaparser,spo -cv-timeline_minimal 0:a1446929a276 1.40 staticanalyserthatflagsfragileau -cv-timeline_minimal 0:d91205080ec3 1.40 patterns(deeplynestedrows,untype -cv-timeline_minimal 0:d31cf3fc9dae 1.40 offsets,implicitpagebreaks)befor -cv-timeline_minimal 0:7b985a1d2b08 1.40 shiptoproduction. -cv-timeline_minimal 0:14e902e0e2ff 1.40 chromeforge(java,graphcompose, -cv-timeline_minimal 0:f87a435df3ac 1.40 pandocbridge):editorial-magazine -cv-timeline_minimal 0:73678fb1255e 1.40 documenttoolkitbuiltongraphcompo -cv-timeline_minimal 0:a0c280e181e2 1.40 cinematiccovers,pullquotes,multi -cv-timeline_minimal 0:d9f1b263312f 1.40 flow,sidebarcallouts. -cv-timeline_minimal 0:b944906ed84c 1.85 professionalsummary -cv-timeline_minimal 0:4bc9e0d2344b 1.30 platformengineerwith10+yearsbuil -cv-timeline_minimal 0:453eef50417c 1.32 anddeveloper-facingtemplatesyste -cv-timeline_minimal 0:39881b36a9c0 1.34 authoringdsls,andturningbrittlep -cv-timeline_minimal 0:83064bdc95f5 1.36 scale. -cv-timeline_minimal 0:c2f296ef30a3 1.93 professionalexperience -cv-timeline_minimal 0:68c08b2a5966 1.63 seniorplatformengineer|northwind -cv-timeline_minimal 0:64c4b52dceb1 1.37 ledthereusabledocument-generatio -cv-timeline_minimal 0:1f193cd1d987 1.36 productteams.reducedtemplatemain -cv-timeline_minimal 0:9ccc3e904e5a 1.35 favourofonecanonicalengine. -cv-timeline_minimal 0:fb53bd1bfeb9 1.59 softwareengineer|brightleaflabs- -cv-timeline_minimal 0:632618b20dab 1.33 builtbackendservicesandproductio -cv-timeline_minimal 0:5d86afffe129 1.32 month.drovethemigrationfromitext -cv-timeline_minimal 0:70dd947a4558 1.31 cuttingp99renderlatencyfrom1.4st -cv-timeline_minimal 0:8a9898d2c965 1.55 backendengineer|helixprintco-201 -cv-timeline_minimal 0:a485c37fe7ea 1.29 maintainedahigh-volumeinvoice-pr -cv-timeline_minimal 0:5a136b6b9d8c 1.28 testharnessthatgatedeverytemplat +cv-timeline_minimal 0:68bf0109c6f8 0.05 education +cv-timeline_minimal 0:54654b90a771 -0.45 msccomputerscience|universityof +cv-timeline_minimal 0:a57111bb5a18 -0.45 manchester-2019-2021 +cv-timeline_minimal 0:bf13436325bd -0.45 distinction.thesis:composablelay +cv-timeline_minimal 0:4c7268c098b3 -0.45 primitivesfordeterministicdocume +cv-timeline_minimal 0:6154cc9024ae -0.45 rendering. +cv-timeline_minimal 0:17dd75fc575f -0.45 bscsoftwareengineering|imperialc +cv-timeline_minimal 0:220e665a6446 -0.45 london-2015-2019 +cv-timeline_minimal 0:048a150be6e7 -0.45 first-classhonours.specialisatio +cv-timeline_minimal 0:f9d33eb2a1a2 -0.45 compilersandstaticanalysis. +cv-timeline_minimal 0:dddc36d8bb44 -0.45 oraclejavacertification|professi +cv-timeline_minimal 0:6c065bee3945 -0.45 track-2023-2024 +cv-timeline_minimal 0:40f2bfbae245 -0.45 java17platformdeep-dive:records, +cv-timeline_minimal 0:6cba964fe5fe -0.45 types,patternmatching,virtualthr +cv-timeline_minimal 0:5f94c50dc5bf 0.05 technicalskills +cv-timeline_minimal 0:5d6819554c7a -0.45 languages:java21,kotlin,groovy,p +cv-timeline_minimal 0:4ec7c53222c8 -0.45 sql +cv-timeline_minimal 0:db84150190bb -0.45 document&print:pdfbox,apachepoi +cv-timeline_minimal 0:69d03fef7130 -0.45 (docx/xlsx),itext,postscript,icc +cv-timeline_minimal 0:689d68053b81 -0.45 profiles,fontmetrics +cv-timeline_minimal 0:8ca175286c7b -0.45 layoutengines:customdsldesign, +cv-timeline_minimal 0:b6f2e7d194af -0.45 semanticlayouttrees,pagination,s +cv-timeline_minimal 0:e8951d583db6 -0.45 testing,visualregression +cv-timeline_minimal 0:3b7a3ab3b6ee -0.45 build&infrastructure:maven,gradl +cv-timeline_minimal 0:d176e972b547 -0.45 githubactions,jitpack,docker,jmh +cv-timeline_minimal 0:3d119f794292 -0.45 benchmarking +cv-timeline_minimal 0:b7b6a102880a -0.45 testing:junit5,assertj,pdfbox-ba +cv-timeline_minimal 0:2f0dced56e7f -0.45 pngdiff,layout-graphsnapshots,mu +cv-timeline_minimal 0:c001aa5cc6fb -0.45 testing(pitest) +cv-timeline_minimal 0:90c057c30e8f -0.45 distribution:mavencentral,sonaty +cv-timeline_minimal 0:7347a19cb48d -0.45 ossrh,gpgsigning,jitpack,semanti +cv-timeline_minimal 0:b13180079f20 -0.45 versioningdiscipline +cv-timeline_minimal 0:2577c0f557b2 0.05 projects +cv-timeline_minimal 0:bd82e94c4024 -0.45 graphcompose(java21,pdfbox,maven +cv-timeline_minimal 0:54b7bb3a12a0 -0.45 jmh):declarativejavapdflayouteng +cv-timeline_minimal 0:83ee0a75c2d4 -0.45 semanticdsl,slot-basedtemplates, +cv-timeline_minimal 0:235802baf524 -0.45 snapshottesting.powersproduction +cv-timeline_minimal 0:44925c9e42ee -0.45 invoice/proposalpipelinesforhiri +cv-timeline_minimal 0:ae7902eee4b2 -0.45 templatestudio(kotlin,composedes +cv-timeline_minimal 0:f15436034237 -0.45 pdfboxpngdiff):internaltoolfor +cv-timeline_minimal 0:24a195694090 -0.45 evaluatingcv,proposal,andinvoice +cv-timeline_minimal 0:dbcc5c321887 -0.45 layoutlint(java21,javaparser,spo +cv-timeline_minimal 0:a1446929a276 -0.45 staticanalyserthatflagsfragileau +cv-timeline_minimal 0:d91205080ec3 -0.45 patterns(deeplynestedrows,untype +cv-timeline_minimal 0:d31cf3fc9dae -0.45 offsets,implicitpagebreaks)befor +cv-timeline_minimal 0:7b985a1d2b08 -0.45 shiptoproduction. +cv-timeline_minimal 0:14e902e0e2ff -0.45 chromeforge(java,graphcompose, +cv-timeline_minimal 0:f87a435df3ac -0.45 pandocbridge):editorial-magazine +cv-timeline_minimal 0:73678fb1255e -0.45 documenttoolkitbuiltongraphcompo +cv-timeline_minimal 0:a0c280e181e2 -0.45 cinematiccovers,pullquotes,multi +cv-timeline_minimal 0:d9f1b263312f -0.45 flow,sidebarcallouts. +cv-timeline_minimal 0:b944906ed84c 0.00 professionalsummary +cv-timeline_minimal 0:4bc9e0d2344b -0.55 platformengineerwith10+yearsbuil +cv-timeline_minimal 0:453eef50417c -0.53 anddeveloper-facingtemplatesyste +cv-timeline_minimal 0:39881b36a9c0 -0.51 authoringdsls,andturningbrittlep +cv-timeline_minimal 0:83064bdc95f5 -0.49 scale. +cv-timeline_minimal 0:c2f296ef30a3 0.08 professionalexperience +cv-timeline_minimal 0:68c08b2a5966 -0.22 seniorplatformengineer|northwind +cv-timeline_minimal 0:64c4b52dceb1 -0.48 ledthereusabledocument-generatio +cv-timeline_minimal 0:1f193cd1d987 -0.49 productteams.reducedtemplatemain +cv-timeline_minimal 0:9ccc3e904e5a -0.50 favourofonecanonicalengine. +cv-timeline_minimal 0:fb53bd1bfeb9 -0.26 softwareengineer|brightleaflabs- +cv-timeline_minimal 0:632618b20dab -0.52 builtbackendservicesandproductio +cv-timeline_minimal 0:5d86afffe129 -0.53 month.drovethemigrationfromitext +cv-timeline_minimal 0:70dd947a4558 -0.54 cuttingp99renderlatencyfrom1.4st +cv-timeline_minimal 0:8a9898d2c965 -0.30 backendengineer|helixprintco-201 +cv-timeline_minimal 0:a485c37fe7ea -0.56 maintainedahigh-volumeinvoice-pr +cv-timeline_minimal 0:5a136b6b9d8c -0.57 testharnessthatgatedeverytemplat cv-timeline_minimal 1:676dfdd56ad2 0.20 additionalinformation cv-timeline_minimal 1:ecc25e73b19b -0.30 languages:english(fluent),german cv-timeline_minimal 1:ac7f0ee6a49e -0.30 (intermediate),spanish(basic) @@ -1625,75 +1625,75 @@ cv-violet_grid 0:77b27b9ce612 -0.11 +1(415)555-7842 cv-violet_grid 0:12d25ebdca93 -0.11 sanfrancisco,ca,usa cv-violet_grid 0:8a2bdbd153f4 -0.11 sofiamartinez.design cv-violet_grid 0:84308bea4540 -0.12 linkedin -cv-violet_grid 0:30b943b69458 0.17 user-centereddesignerwith5+years -cv-violet_grid 0:c5d00ee335d1 0.16 icombineresearch,designthinking, -cv-violet_grid 0:62fd7c29c3c0 0.14 passionateaboutcollaboratingwith -cv-violet_grid 0:32c1cf49a2fe 0.14 skills -cv-violet_grid 0:b96067918254 0.24 uxresearch -cv-violet_grid 0:9058674fd905 0.29 userinterviews,surveys, -cv-violet_grid 0:0cdd38014d79 0.28 personas,journeymaps, -cv-violet_grid 0:61e4ba28d469 0.28 competitiveanalysis -cv-violet_grid 0:db5c6756c4b6 8.09 sitemaps,userflows,card -cv-violet_grid 0:6c7ad1834a60 8.08 sorting,contentstrategy -cv-violet_grid 0:187737d59250 0.24 wireframing -cv-violet_grid 0:2286e5aa328a 0.29 lowtohigh-fidelity -cv-violet_grid 0:5c4b21cb1af8 0.24 prototyping -cv-violet_grid 0:1abfa9dc1773 0.29 interactiveprototypes, -cv-violet_grid 0:1ddc6c379a54 0.28 microinteractions, -cv-violet_grid 0:d1d99c54cf40 0.28 transitions -cv-violet_grid 0:7a38f8d94009 0.24 usabilitytesting -cv-violet_grid 0:63af69ae99e6 0.29 testplanning,usability -cv-violet_grid 0:f67c409cc44f 0.28 tests,heuristicevaluation, -cv-violet_grid 0:438e1d3264dd 0.28 insights&iteration -cv-violet_grid 0:7e02941d76f5 0.24 designsystems -cv-violet_grid 0:a0d27161913a 0.29 componentlibraries, -cv-violet_grid 0:82374d275507 0.28 designtokens,pattern -cv-violet_grid 0:caf9f906b16a 0.28 documentation -cv-violet_grid 0:f9d35d43770d 0.26 tools -cv-violet_grid 0:5cb2b3b6143c 0.32 figmafigjamadobexdsketchmironoti -cv-violet_grid 0:53e5e7c5a884 0.30 experience -cv-violet_grid 0:5a0774260e0c 0.42 2022-present -cv-violet_grid 0:204f460767ff 0.38 seniorux/uidesigner|novafin(fint -cv-violet_grid 0:318bba211bdc 0.43 sanfrancisco,ca -cv-violet_grid 0:318bba211bdc#1 9.35 sanfrancisco,ca -cv-violet_grid 0:3883e88c63c3 0.42 ledend-to-enddesignforapersonalf -cv-violet_grid 0:ccc00663f089 2.20 conducteduserresearch,builtperso -cv-violet_grid 0:7d0f908c7e5f 3.98 designedwireframes,prototypes,an -cv-violet_grid 0:9b57c4a85aad 5.77 establishedandmaintainedascalabl -cv-violet_grid 0:6894eafb99c2 7.55 collaboratedwithproductmanagers, -cv-violet_grid 0:01e4455cec51 9.34 2020-2022 -cv-violet_grid 0:24d722f0cfbe 9.30 uxdesigner|brighthealth(healthte -cv-violet_grid 0:530707fb0897 9.34 redesignedpatientportalexperienc -cv-violet_grid 0:b2e27a1d9270 11.12 plannedandfacilitatedusabilityte -cv-violet_grid 0:ab80a52d70fe 12.90 partneredwithengineerstoensurefe -cv-violet_grid 0:11cbf615189b 14.69 createdreusablecomponentsandstyl -cv-violet_grid 0:7e5081a3ed48 16.48 2018-2020 -cv-violet_grid 0:73f7bbf7cd60 16.44 juniorux/uidesigner|lumendigital -cv-violet_grid 0:c6b06045332f 16.49 austin,tx -cv-violet_grid 0:5ea8234925c5 16.48 supporteddiscoveryworkshops,user -cv-violet_grid 0:46f00c76528c 18.26 designedresponsivewebsitesandlan -cv-violet_grid 0:f3cea36a9d83 20.04 collaboratedwithcopywritersandde -cv-violet_grid 0:d94eccbb1fbe 21.81 selectedprojects -cv-violet_grid 0:45dc8bf81211 21.76 novafinmobileapp|personalfinance -cv-violet_grid 0:d398b29d3dbb 21.81 2023 -cv-violet_grid 0:e66c1ce573d3 21.67 ledux/uidesignforbudgeting,goalt -cv-violet_grid 0:0ed8829e68d0 21.67 thatinformedasimplifiednavigatio -cv-violet_grid 0:ee9bd17ebccf 21.67 22%increaseinweeklyactiveusers. -cv-violet_grid 0:28b4603d784a 21.75 brighthealthpatientportal|health -cv-violet_grid 0:1bea20e1df19 21.80 2021 -cv-violet_grid 0:999ddb26bed6 21.66 redesignedkeypatientflowsincludi -cv-violet_grid 0:073d15633c0b 21.66 systemandcomponentlibraryusedacr -cv-violet_grid 0:68bf0109c6f8 21.91 education -cv-violet_grid 0:6cb574fa10b6 21.91 languages -cv-violet_grid 0:0cf7506cf94a 19.58 bacheloroffineartsingraphicdesig -cv-violet_grid 0:00ab7a6aa5f1 19.59 californiacollegeofthearts -cv-violet_grid 0:7e2c21bb41db 19.58 sanfrancisco,ca|2014-2018 -cv-violet_grid 0:b6234d2ea0d6 21.23 english -cv-violet_grid 0:bef32d2c315a 37.97 native -cv-violet_grid 0:bef32d2c315a#1 5.71 native -cv-violet_grid 0:9c9b13446cd9 22.46 spanish -cv-violet_grid 0:e35242c809c8 23.69 portuguese -cv-violet_grid 0:c5d1de4964d6 23.69 conversational +cv-violet_grid 0:30b943b69458 -0.33 user-centereddesignerwith5+years +cv-violet_grid 0:c5d00ee335d1 -0.34 icombineresearch,designthinking, +cv-violet_grid 0:62fd7c29c3c0 -0.36 passionateaboutcollaboratingwith +cv-violet_grid 0:32c1cf49a2fe -0.36 skills +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:187737d59250 -0.26 wireframing +cv-violet_grid 0:2286e5aa328a -0.21 lowtohigh-fidelity +cv-violet_grid 0:5c4b21cb1af8 -0.26 prototyping +cv-violet_grid 0:1abfa9dc1773 -0.21 interactiveprototypes, +cv-violet_grid 0:1ddc6c379a54 -0.22 microinteractions, +cv-violet_grid 0:d1d99c54cf40 -0.22 transitions +cv-violet_grid 0:7a38f8d94009 -0.26 usabilitytesting +cv-violet_grid 0:63af69ae99e6 -0.21 testplanning,usability +cv-violet_grid 0:f67c409cc44f -0.22 tests,heuristicevaluation, +cv-violet_grid 0:438e1d3264dd -0.22 insights&iteration +cv-violet_grid 0:7e02941d76f5 -0.26 designsystems +cv-violet_grid 0:a0d27161913a -0.21 componentlibraries, +cv-violet_grid 0:82374d275507 -0.22 designtokens,pattern +cv-violet_grid 0:caf9f906b16a -0.22 documentation +cv-violet_grid 0:f9d35d43770d -0.24 tools +cv-violet_grid 0:5cb2b3b6143c -0.18 figmafigjamadobexdsketchmironoti +cv-violet_grid 0:53e5e7c5a884 -0.20 experience +cv-violet_grid 0:5a0774260e0c -0.08 2022-present +cv-violet_grid 0:204f460767ff -0.12 seniorux/uidesigner|novafin(fint +cv-violet_grid 0:318bba211bdc -0.07 sanfrancisco,ca +cv-violet_grid 0:318bba211bdc#1 8.85 sanfrancisco,ca +cv-violet_grid 0:3883e88c63c3 -0.08 ledend-to-enddesignforapersonalf +cv-violet_grid 0:ccc00663f089 1.70 conducteduserresearch,builtperso +cv-violet_grid 0:7d0f908c7e5f 3.48 designedwireframes,prototypes,an +cv-violet_grid 0:9b57c4a85aad 5.27 establishedandmaintainedascalabl +cv-violet_grid 0:6894eafb99c2 7.05 collaboratedwithproductmanagers, +cv-violet_grid 0:01e4455cec51 8.84 2020-2022 +cv-violet_grid 0:24d722f0cfbe 8.80 uxdesigner|brighthealth(healthte +cv-violet_grid 0:530707fb0897 8.84 redesignedpatientportalexperienc +cv-violet_grid 0:b2e27a1d9270 10.62 plannedandfacilitatedusabilityte +cv-violet_grid 0:ab80a52d70fe 12.40 partneredwithengineerstoensurefe +cv-violet_grid 0:11cbf615189b 14.19 createdreusablecomponentsandstyl +cv-violet_grid 0:7e5081a3ed48 15.98 2018-2020 +cv-violet_grid 0:73f7bbf7cd60 15.94 juniorux/uidesigner|lumendigital +cv-violet_grid 0:c6b06045332f 15.99 austin,tx +cv-violet_grid 0:5ea8234925c5 15.98 supporteddiscoveryworkshops,user +cv-violet_grid 0:46f00c76528c 17.76 designedresponsivewebsitesandlan +cv-violet_grid 0:f3cea36a9d83 19.54 collaboratedwithcopywritersandde +cv-violet_grid 0:d94eccbb1fbe 21.31 selectedprojects +cv-violet_grid 0:45dc8bf81211 21.26 novafinmobileapp|personalfinance +cv-violet_grid 0:d398b29d3dbb 21.31 2023 +cv-violet_grid 0:e66c1ce573d3 21.17 ledux/uidesignforbudgeting,goalt +cv-violet_grid 0:0ed8829e68d0 21.17 thatinformedasimplifiednavigatio +cv-violet_grid 0:ee9bd17ebccf 21.17 22%increaseinweeklyactiveusers. +cv-violet_grid 0:28b4603d784a 21.25 brighthealthpatientportal|health +cv-violet_grid 0:1bea20e1df19 21.30 2021 +cv-violet_grid 0:999ddb26bed6 21.16 redesignedkeypatientflowsincludi +cv-violet_grid 0:073d15633c0b 21.16 systemandcomponentlibraryusedacr +cv-violet_grid 0:68bf0109c6f8 21.41 education +cv-violet_grid 0:6cb574fa10b6 21.41 languages +cv-violet_grid 0:0cf7506cf94a 19.08 bacheloroffineartsingraphicdesig +cv-violet_grid 0:00ab7a6aa5f1 19.09 californiacollegeofthearts +cv-violet_grid 0:7e2c21bb41db 19.08 sanfrancisco,ca|2014-2018 +cv-violet_grid 0:b6234d2ea0d6 20.73 english +cv-violet_grid 0:bef32d2c315a 37.47 native +cv-violet_grid 0:bef32d2c315a#1 5.21 native +cv-violet_grid 0:9c9b13446cd9 21.96 spanish +cv-violet_grid 0:e35242c809c8 23.19 portuguese +cv-violet_grid 0:c5d1de4964d6 23.19 conversational invoice-classic 0:c26c22b94338 0.49 graphcomposestudio invoice-classic 0:c26c22b94338#1 0.30 graphcomposestudio invoice-classic 0:2a08ce5567fb 0.35 18layoutstreet @@ -3568,20 +3568,20 @@ letter-sidebar_portrait 0:7c5a9817c868 -0.43 iwouldwelcometheopportunitytobri letter-sidebar_portrait 0:4012eb4b05db -0.41 group. letter-sidebar_portrait 0:80a6a628ad52 -0.14 sincerely, letter-timeline_minimal 0:8f1010120ed6 0.15 jordanrivera -letter-timeline_minimal 0:8f1010120ed6#1 1.52 jordanrivera +letter-timeline_minimal 0:8f1010120ed6#1 -0.33 jordanrivera letter-timeline_minimal 0:2d58f0cadc2d -0.40 platformengineer letter-timeline_minimal 0:fbeea547d99e -0.19 london,uk letter-timeline_minimal 0:89674e6f130e -0.19 +442055551000 letter-timeline_minimal 0:e0f850305ded -0.19 jordan.rivera@example.com letter-timeline_minimal 0:84308bea4540 -0.19 linkedin letter-timeline_minimal 0:c0b0109d9439 -0.19 github -letter-timeline_minimal 0:bef81b541365 1.52 dearhiringteamatnorthwindsystems -letter-timeline_minimal 0:ab54410ead82 1.32 iamexcitedtosharemyinterestinthe -letter-timeline_minimal 0:9185168d2cf8 1.32 document-generationsystemsthatba -letter-timeline_minimal 0:a463bced3db2 1.32 ienjoytranslatingfuzzyworkflowre -letter-timeline_minimal 0:dd474e249222 1.32 makeadoptioneasierfortherestofth -letter-timeline_minimal 0:a71e98216baa 1.52 iwouldwelcometheopportunitytobri -letter-timeline_minimal 0:80a6a628ad52 1.52 sincerely, +letter-timeline_minimal 0:bef81b541365 -0.33 dearhiringteamatnorthwindsystems +letter-timeline_minimal 0:ab54410ead82 -0.53 iamexcitedtosharemyinterestinthe +letter-timeline_minimal 0:9185168d2cf8 -0.53 document-generationsystemsthatba +letter-timeline_minimal 0:a463bced3db2 -0.53 ienjoytranslatingfuzzyworkflowre +letter-timeline_minimal 0:dd474e249222 -0.53 makeadoptioneasierfortherestofth +letter-timeline_minimal 0:a71e98216baa -0.33 iwouldwelcometheopportunitytobri +letter-timeline_minimal 0:80a6a628ad52 -0.33 sincerely, proposal-editorial 0:5205c2a261bc 0.81 northline proposal-editorial 0:da0daaba4b15 0.33 studio proposal-editorial 0:ecd1378bc9dc -0.31 proposal diff --git a/qa/src/test/resources/docx-fidelity/libreoffice-windows.tsv b/qa/src/test/resources/docx-fidelity/libreoffice-windows.tsv index 50dff2b4c..e3dc3e3aa 100644 --- a/qa/src/test/resources/docx-fidelity/libreoffice-windows.tsv +++ b/qa/src/test/resources/docx-fidelity/libreoffice-windows.tsv @@ -24,8 +24,8 @@ cv-sidebar_portrait 1 1 49 49 2.78 3.02 49 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 1.40 1.40 0 -cv-violet_grid 1 2 80 76 5.71 21.81 40 +cv-timeline_minimal 2 2 89 86 0.45 0.50 0 +cv-violet_grid 1 2 80 76 5.21 21.31 39 invoice-classic 1 1 56 56 0.47 1.17 0 invoice-classic_long 2 2 166 166 0.47 0.87 0 invoice-consulting 1 1 92 92 0.79 1.41 3 @@ -55,7 +55,7 @@ letter-monogram_sidebar 1 1 12 12 0.23 0.37 0 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 +letter-timeline_minimal 1 1 15 15 0.33 0.53 0 proposal-editorial 2 2 132 129 43.44 45.33 72 proposal-indigo 1 1 72 70 0.27 0.65 0 proposal-modern 1 1 44 44 0.41 0.75 0 diff --git a/qa/src/test/resources/docx-fidelity/word-windows-lines.tsv b/qa/src/test/resources/docx-fidelity/word-windows-lines.tsv index 3f48f3458..c2bf1c2b8 100644 --- a/qa/src/test/resources/docx-fidelity/word-windows-lines.tsv +++ b/qa/src/test/resources/docx-fidelity/word-windows-lines.tsv @@ -1632,74 +1632,74 @@ cv-timeline_minimal 0:89674e6f130e 0.06 +442055551000 cv-timeline_minimal 0:e0f850305ded 0.00 jordan.rivera@example.com cv-timeline_minimal 0:84308bea4540 0.06 linkedin cv-timeline_minimal 0:c0b0109d9439 0.02 github -cv-timeline_minimal 0:68bf0109c6f8 1.78 education -cv-timeline_minimal 0:54654b90a771 1.36 msccomputerscience|universityof -cv-timeline_minimal 0:a57111bb5a18 1.44 manchester-2019-2021 -cv-timeline_minimal 0:bf13436325bd 1.44 distinction.thesis:composablelay -cv-timeline_minimal 0:4c7268c098b3 1.52 primitivesfordeterministicdocume -cv-timeline_minimal 0:6154cc9024ae 1.48 rendering. -cv-timeline_minimal 0:17dd75fc575f 1.36 bscsoftwareengineering|imperialc -cv-timeline_minimal 0:220e665a6446 1.44 london-2015-2019 -cv-timeline_minimal 0:048a150be6e7 1.44 first-classhonours.specialisatio -cv-timeline_minimal 0:f9d33eb2a1a2 1.52 compilersandstaticanalysis. -cv-timeline_minimal 0:dddc36d8bb44 1.40 oraclejavacertification|professi -cv-timeline_minimal 0:6c065bee3945 1.48 track-2023-2024 -cv-timeline_minimal 0:40f2bfbae245 1.39 java17platformdeep-dive:records, -cv-timeline_minimal 0:6cba964fe5fe 1.47 types,patternmatching,virtualthr -cv-timeline_minimal 0:5f94c50dc5bf 1.85 technicalskills -cv-timeline_minimal 0:5d6819554c7a 1.43 languages:java21,kotlin,groovy,p -cv-timeline_minimal 0:4ec7c53222c8 1.51 sql -cv-timeline_minimal 0:db84150190bb 1.39 document&print:pdfbox,apachepoi -cv-timeline_minimal 0:69d03fef7130 1.47 (docx/xlsx),itext,postscript,icc -cv-timeline_minimal 0:689d68053b81 1.55 profiles,fontmetrics -cv-timeline_minimal 0:8ca175286c7b 1.43 layoutengines:customdsldesign, -cv-timeline_minimal 0:b6f2e7d194af 1.51 semanticlayouttrees,pagination,s -cv-timeline_minimal 0:e8951d583db6 1.47 testing,visualregression -cv-timeline_minimal 0:3b7a3ab3b6ee 1.47 build&infrastructure:maven,gradl -cv-timeline_minimal 0:d176e972b547 1.57 githubactions,jitpack,docker,jmh -cv-timeline_minimal 0:3d119f794292 1.53 benchmarking -cv-timeline_minimal 0:b7b6a102880a 1.41 testing:junit5,assertj,pdfbox-ba -cv-timeline_minimal 0:2f0dced56e7f 1.49 pngdiff,layout-graphsnapshots,mu -cv-timeline_minimal 0:c001aa5cc6fb 1.57 testing(pitest) -cv-timeline_minimal 0:90c057c30e8f 1.45 distribution:mavencentral,sonaty -cv-timeline_minimal 0:7347a19cb48d 1.53 ossrh,gpgsigning,jitpack,semanti -cv-timeline_minimal 0:b13180079f20 1.49 versioningdiscipline -cv-timeline_minimal 0:2577c0f557b2 1.87 projects -cv-timeline_minimal 0:bd82e94c4024 1.45 graphcompose(java21,pdfbox,maven -cv-timeline_minimal 0:54b7bb3a12a0 1.53 jmh):declarativejavapdflayouteng -cv-timeline_minimal 0:83ee0a75c2d4 1.49 semanticdsl,slot-basedtemplates, -cv-timeline_minimal 0:235802baf524 1.45 snapshottesting.powersproduction -cv-timeline_minimal 0:44925c9e42ee 1.53 invoice/proposalpipelinesforhiri -cv-timeline_minimal 0:ae7902eee4b2 1.40 templatestudio(kotlin,composedes -cv-timeline_minimal 0:f15436034237 1.48 pdfboxpngdiff):internaltoolfor -cv-timeline_minimal 0:24a195694090 1.56 evaluatingcv,proposal,andinvoice -cv-timeline_minimal 0:dbcc5c321887 1.48 layoutlint(java21,javaparser,spo -cv-timeline_minimal 0:a1446929a276 1.56 staticanalyserthatflagsfragileau -cv-timeline_minimal 0:d91205080ec3 1.52 patterns(deeplynestedrows,untype -cv-timeline_minimal 0:d31cf3fc9dae 1.48 offsets,implicitpagebreaks)befor -cv-timeline_minimal 0:7b985a1d2b08 1.56 shiptoproduction. -cv-timeline_minimal 0:14e902e0e2ff 1.43 chromeforge(java,graphcompose, -cv-timeline_minimal 0:f87a435df3ac 1.51 pandocbridge):editorial-magazine -cv-timeline_minimal 0:73678fb1255e 1.47 documenttoolkitbuiltongraphcompo -cv-timeline_minimal 0:a0c280e181e2 1.55 cinematiccovers,pullquotes,multi -cv-timeline_minimal 0:d9f1b263312f 1.51 flow,sidebarcallouts. -cv-timeline_minimal 0:b944906ed84c 1.74 professionalsummary -cv-timeline_minimal 0:4bc9e0d2344b 1.36 platformengineerwith10+yearsbuil -cv-timeline_minimal 0:453eef50417c 1.40 anddeveloper-facingtemplatesyste -cv-timeline_minimal 0:39881b36a9c0 1.44 authoringdsls,andturningbrittlep -cv-timeline_minimal 0:83064bdc95f5 1.36 scale. -cv-timeline_minimal 0:c2f296ef30a3 1.82 professionalexperience -cv-timeline_minimal 0:68c08b2a5966 1.66 seniorplatformengineer|northwind -cv-timeline_minimal 0:64c4b52dceb1 1.34 ledthereusabledocument-generatio -cv-timeline_minimal 0:1f193cd1d987 1.34 productteams.reducedtemplatemain -cv-timeline_minimal 0:9ccc3e904e5a 1.37 favourofonecanonicalengine. -cv-timeline_minimal 0:fb53bd1bfeb9 1.65 softwareengineer|brightleaflabs- -cv-timeline_minimal 0:632618b20dab 1.33 builtbackendservicesandproductio -cv-timeline_minimal 0:5d86afffe129 1.33 month.drovethemigrationfromitext -cv-timeline_minimal 0:70dd947a4558 1.33 cuttingp99renderlatencyfrom1.4st -cv-timeline_minimal 0:8a9898d2c965 1.61 backendengineer|helixprintco-201 -cv-timeline_minimal 0:a485c37fe7ea 1.29 maintainedahigh-volumeinvoice-pr -cv-timeline_minimal 0:5a136b6b9d8c 1.29 testharnessthatgatedeverytemplat +cv-timeline_minimal 0:68bf0109c6f8 -0.02 education +cv-timeline_minimal 0:54654b90a771 -0.44 msccomputerscience|universityof +cv-timeline_minimal 0:a57111bb5a18 -0.36 manchester-2019-2021 +cv-timeline_minimal 0:bf13436325bd -0.48 distinction.thesis:composablelay +cv-timeline_minimal 0:4c7268c098b3 -0.40 primitivesfordeterministicdocume +cv-timeline_minimal 0:6154cc9024ae -0.44 rendering. +cv-timeline_minimal 0:17dd75fc575f -0.44 bscsoftwareengineering|imperialc +cv-timeline_minimal 0:220e665a6446 -0.36 london-2015-2019 +cv-timeline_minimal 0:048a150be6e7 -0.48 first-classhonours.specialisatio +cv-timeline_minimal 0:f9d33eb2a1a2 -0.40 compilersandstaticanalysis. +cv-timeline_minimal 0:dddc36d8bb44 -0.52 oraclejavacertification|professi +cv-timeline_minimal 0:6c065bee3945 -0.44 track-2023-2024 +cv-timeline_minimal 0:40f2bfbae245 -0.41 java17platformdeep-dive:records, +cv-timeline_minimal 0:6cba964fe5fe -0.33 types,patternmatching,virtualthr +cv-timeline_minimal 0:5f94c50dc5bf -0.07 technicalskills +cv-timeline_minimal 0:5d6819554c7a -0.49 languages:java21,kotlin,groovy,p +cv-timeline_minimal 0:4ec7c53222c8 -0.41 sql +cv-timeline_minimal 0:db84150190bb -0.41 document&print:pdfbox,apachepoi +cv-timeline_minimal 0:69d03fef7130 -0.33 (docx/xlsx),itext,postscript,icc +cv-timeline_minimal 0:689d68053b81 -0.37 profiles,fontmetrics +cv-timeline_minimal 0:8ca175286c7b -0.49 layoutengines:customdsldesign, +cv-timeline_minimal 0:b6f2e7d194af -0.41 semanticlayouttrees,pagination,s +cv-timeline_minimal 0:e8951d583db6 -0.33 testing,visualregression +cv-timeline_minimal 0:3b7a3ab3b6ee -0.45 build&infrastructure:maven,gradl +cv-timeline_minimal 0:d176e972b547 -0.35 githubactions,jitpack,docker,jmh +cv-timeline_minimal 0:3d119f794292 -0.39 benchmarking +cv-timeline_minimal 0:b7b6a102880a -0.39 testing:junit5,assertj,pdfbox-ba +cv-timeline_minimal 0:2f0dced56e7f -0.31 pngdiff,layout-graphsnapshots,mu +cv-timeline_minimal 0:c001aa5cc6fb -0.35 testing(pitest) +cv-timeline_minimal 0:90c057c30e8f -0.47 distribution:mavencentral,sonaty +cv-timeline_minimal 0:7347a19cb48d -0.39 ossrh,gpgsigning,jitpack,semanti +cv-timeline_minimal 0:b13180079f20 -0.31 versioningdiscipline +cv-timeline_minimal 0:2577c0f557b2 -0.05 projects +cv-timeline_minimal 0:bd82e94c4024 -0.47 graphcompose(java21,pdfbox,maven +cv-timeline_minimal 0:54b7bb3a12a0 -0.39 jmh):declarativejavapdflayouteng +cv-timeline_minimal 0:83ee0a75c2d4 -0.31 semanticdsl,slot-basedtemplates, +cv-timeline_minimal 0:235802baf524 -0.35 snapshottesting.powersproduction +cv-timeline_minimal 0:44925c9e42ee -0.39 invoice/proposalpipelinesforhiri +cv-timeline_minimal 0:ae7902eee4b2 -0.40 templatestudio(kotlin,composedes +cv-timeline_minimal 0:f15436034237 -0.32 pdfboxpngdiff):internaltoolfor +cv-timeline_minimal 0:24a195694090 -0.36 evaluatingcv,proposal,andinvoice +cv-timeline_minimal 0:dbcc5c321887 -0.44 layoutlint(java21,javaparser,spo +cv-timeline_minimal 0:a1446929a276 -0.36 staticanalyserthatflagsfragileau +cv-timeline_minimal 0:d91205080ec3 -0.28 patterns(deeplynestedrows,untype +cv-timeline_minimal 0:d31cf3fc9dae -0.32 offsets,implicitpagebreaks)befor +cv-timeline_minimal 0:7b985a1d2b08 -0.36 shiptoproduction. +cv-timeline_minimal 0:14e902e0e2ff -0.37 chromeforge(java,graphcompose, +cv-timeline_minimal 0:f87a435df3ac -0.29 pandocbridge):editorial-magazine +cv-timeline_minimal 0:73678fb1255e -0.33 documenttoolkitbuiltongraphcompo +cv-timeline_minimal 0:a0c280e181e2 -0.37 cinematiccovers,pullquotes,multi +cv-timeline_minimal 0:d9f1b263312f -0.29 flow,sidebarcallouts. +cv-timeline_minimal 0:b944906ed84c -0.06 professionalsummary +cv-timeline_minimal 0:4bc9e0d2344b -0.44 platformengineerwith10+yearsbuil +cv-timeline_minimal 0:453eef50417c -0.52 anddeveloper-facingtemplatesyste +cv-timeline_minimal 0:39881b36a9c0 -0.48 authoringdsls,andturningbrittlep +cv-timeline_minimal 0:83064bdc95f5 -0.44 scale. +cv-timeline_minimal 0:c2f296ef30a3 -0.10 professionalexperience +cv-timeline_minimal 0:68c08b2a5966 -0.26 seniorplatformengineer|northwind +cv-timeline_minimal 0:64c4b52dceb1 -0.46 ledthereusabledocument-generatio +cv-timeline_minimal 0:1f193cd1d987 -0.46 productteams.reducedtemplatemain +cv-timeline_minimal 0:9ccc3e904e5a -0.43 favourofonecanonicalengine. +cv-timeline_minimal 0:fb53bd1bfeb9 -0.27 softwareengineer|brightleaflabs- +cv-timeline_minimal 0:632618b20dab -0.47 builtbackendservicesandproductio +cv-timeline_minimal 0:5d86afffe129 -0.47 month.drovethemigrationfromitext +cv-timeline_minimal 0:70dd947a4558 -0.47 cuttingp99renderlatencyfrom1.4st +cv-timeline_minimal 0:8a9898d2c965 -0.31 backendengineer|helixprintco-201 +cv-timeline_minimal 0:a485c37fe7ea -0.51 maintainedahigh-volumeinvoice-pr +cv-timeline_minimal 0:5a136b6b9d8c -0.51 testharnessthatgatedeverytemplat cv-timeline_minimal 1:676dfdd56ad2 0.06 additionalinformation cv-timeline_minimal 1:ecc25e73b19b -0.36 languages:english(fluent),german cv-timeline_minimal 1:ac7f0ee6a49e -0.28 (intermediate),spanish(basic) @@ -1718,76 +1718,76 @@ cv-violet_grid 0:77b27b9ce612 -0.21 +1(415)555-7842 cv-violet_grid 0:12d25ebdca93 -0.12 sanfrancisco,ca,usa cv-violet_grid 0:8a2bdbd153f4 -0.13 sofiamartinez.design cv-violet_grid 0:84308bea4540 -0.17 linkedin -cv-violet_grid 0:30b943b69458 0.11 user-centereddesignerwith5+years -cv-violet_grid 0:c5d00ee335d1 0.06 icombineresearch,designthinking, -cv-violet_grid 0:62fd7c29c3c0 0.12 passionateaboutcollaboratingwith -cv-violet_grid 0:32c1cf49a2fe 0.26 skills -cv-violet_grid 0:b96067918254 0.25 uxresearch -cv-violet_grid 0:9058674fd905 0.20 userinterviews,surveys, -cv-violet_grid 0:0cdd38014d79 0.24 personas,journeymaps, -cv-violet_grid 0:61e4ba28d469 0.20 competitiveanalysis -cv-violet_grid 0:db5c6756c4b6 8.00 sitemaps,userflows,card -cv-violet_grid 0:6c7ad1834a60 8.07 sorting,contentstrategy -cv-violet_grid 0:187737d59250 0.25 wireframing -cv-violet_grid 0:2286e5aa328a 0.20 lowtohigh-fidelity -cv-violet_grid 0:5c4b21cb1af8 0.25 prototyping -cv-violet_grid 0:1abfa9dc1773 0.20 interactiveprototypes, -cv-violet_grid 0:1ddc6c379a54 0.24 microinteractions, -cv-violet_grid 0:d1d99c54cf40 0.20 transitions -cv-violet_grid 0:7a38f8d94009 0.25 usabilitytesting -cv-violet_grid 0:63af69ae99e6 0.20 testplanning,usability -cv-violet_grid 0:f67c409cc44f 0.24 tests,heuristicevaluation, -cv-violet_grid 0:438e1d3264dd 0.20 insights&iteration -cv-violet_grid 0:7e02941d76f5 0.25 designsystems -cv-violet_grid 0:a0d27161913a 0.20 componentlibraries, -cv-violet_grid 0:82374d275507 0.24 designtokens,pattern -cv-violet_grid 0:caf9f906b16a 0.20 documentation -cv-violet_grid 0:f9d35d43770d 0.44 tools -cv-violet_grid 0:5cb2b3b6143c 0.39 figmafigjamadobexdsketchmironoti -cv-violet_grid 0:53e5e7c5a884 0.43 experience -cv-violet_grid 0:5a0774260e0c 0.48 2022-present -cv-violet_grid 0:204f460767ff 0.43 seniorux/uidesigner|novafin(fint -cv-violet_grid 0:318bba211bdc 0.60 sanfrancisco,ca -cv-violet_grid 0:318bba211bdc#1 0.44 sanfrancisco,ca -cv-violet_grid 0:3883e88c63c3 0.51 ledend-to-enddesignforapersonalf -cv-violet_grid 0:ccc00663f089 0.46 conducteduserresearch,builtperso -cv-violet_grid 0:7d0f908c7e5f 0.41 designedwireframes,prototypes,an -cv-violet_grid 0:9b57c4a85aad 0.49 establishedandmaintainedascalabl -cv-violet_grid 0:6894eafb99c2 0.46 collaboratedwithproductmanagers, -cv-violet_grid 0:01e4455cec51 0.44 2020-2022 -cv-violet_grid 0:24d722f0cfbe 0.39 uxdesigner|brighthealth(healthte -cv-violet_grid 0:530707fb0897 0.47 redesignedpatientportalexperienc -cv-violet_grid 0:b2e27a1d9270 0.42 plannedandfacilitatedusabilityte -cv-violet_grid 0:ab80a52d70fe 0.37 partneredwithengineerstoensurefe -cv-violet_grid 0:11cbf615189b 0.45 createdreusablecomponentsandstyl -cv-violet_grid 0:7e5081a3ed48 0.31 2018-2020 -cv-violet_grid 0:73f7bbf7cd60 0.26 juniorux/uidesigner|lumendigital -cv-violet_grid 0:c6b06045332f 0.43 austin,tx -cv-violet_grid 0:5ea8234925c5 0.34 supporteddiscoveryworkshops,user -cv-violet_grid 0:46f00c76528c 0.41 designedresponsivewebsitesandlan -cv-violet_grid 0:f3cea36a9d83 0.36 collaboratedwithcopywritersandde -cv-violet_grid 0:d94eccbb1fbe 0.31 selectedprojects -cv-violet_grid 0:45dc8bf81211 0.35 novafinmobileapp|personalfinance -cv-violet_grid 0:d398b29d3dbb 0.43 2023 -cv-violet_grid 0:e66c1ce573d3 0.27 ledux/uidesignforbudgeting,goalt -cv-violet_grid 0:0ed8829e68d0 0.19 thatinformedasimplifiednavigatio -cv-violet_grid 0:ee9bd17ebccf 0.23 22%increaseinweeklyactiveusers. -cv-violet_grid 0:28b4603d784a 0.35 brighthealthpatientportal|health -cv-violet_grid 0:1bea20e1df19 0.43 2021 -cv-violet_grid 0:999ddb26bed6 0.15 redesignedkeypatientflowsincludi -cv-violet_grid 0:073d15633c0b 0.19 systemandcomponentlibraryusedacr -cv-violet_grid 0:68bf0109c6f8 0.53 education -cv-violet_grid 0:6cb574fa10b6 0.53 languages -cv-violet_grid 0:0cf7506cf94a -1.99 bacheloroffineartsingraphicdesig -cv-violet_grid 0:00ab7a6aa5f1 -1.95 californiacollegeofthearts -cv-violet_grid 0:7e2c21bb41db -1.96 sanfrancisco,ca|2014-2018 -cv-violet_grid 0:b6234d2ea0d6 -0.90 english -cv-violet_grid 0:bef32d2c315a -0.77 native -cv-violet_grid 0:bef32d2c315a#1 -0.81 native -cv-violet_grid 0:9c9b13446cd9 -0.82 spanish -cv-violet_grid 0:e35242c809c8 -0.85 portuguese -cv-violet_grid 0:c5d1de4964d6 -0.72 conversational -cv-violet_grid 0:1de4fede4f63 -0.61 idesignwithempathy,iteratewithpu +cv-violet_grid 0:30b943b69458 -0.37 user-centereddesignerwith5+years +cv-violet_grid 0:c5d00ee335d1 -0.42 icombineresearch,designthinking, +cv-violet_grid 0:62fd7c29c3c0 -0.36 passionateaboutcollaboratingwith +cv-violet_grid 0:32c1cf49a2fe -0.22 skills +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:187737d59250 -0.23 wireframing +cv-violet_grid 0:2286e5aa328a -0.28 lowtohigh-fidelity +cv-violet_grid 0:5c4b21cb1af8 -0.23 prototyping +cv-violet_grid 0:1abfa9dc1773 -0.28 interactiveprototypes, +cv-violet_grid 0:1ddc6c379a54 -0.36 microinteractions, +cv-violet_grid 0:d1d99c54cf40 -0.28 transitions +cv-violet_grid 0:7a38f8d94009 -0.23 usabilitytesting +cv-violet_grid 0:63af69ae99e6 -0.28 testplanning,usability +cv-violet_grid 0:f67c409cc44f -0.36 tests,heuristicevaluation, +cv-violet_grid 0:438e1d3264dd -0.28 insights&iteration +cv-violet_grid 0:7e02941d76f5 -0.23 designsystems +cv-violet_grid 0:a0d27161913a -0.28 componentlibraries, +cv-violet_grid 0:82374d275507 -0.36 designtokens,pattern +cv-violet_grid 0:caf9f906b16a -0.28 documentation +cv-violet_grid 0:f9d35d43770d -0.16 tools +cv-violet_grid 0:5cb2b3b6143c -0.09 figmafigjamadobexdsketchmironoti +cv-violet_grid 0:53e5e7c5a884 -0.05 experience +cv-violet_grid 0:5a0774260e0c 0.00 2022-present +cv-violet_grid 0:204f460767ff -0.05 seniorux/uidesigner|novafin(fint +cv-violet_grid 0:318bba211bdc 0.00 sanfrancisco,ca +cv-violet_grid 0:318bba211bdc#1 -0.04 sanfrancisco,ca +cv-violet_grid 0:3883e88c63c3 0.03 ledend-to-enddesignforapersonalf +cv-violet_grid 0:ccc00663f089 -0.02 conducteduserresearch,builtperso +cv-violet_grid 0:7d0f908c7e5f -0.07 designedwireframes,prototypes,an +cv-violet_grid 0:9b57c4a85aad 0.01 establishedandmaintainedascalabl +cv-violet_grid 0:6894eafb99c2 -0.02 collaboratedwithproductmanagers, +cv-violet_grid 0:01e4455cec51 -0.04 2020-2022 +cv-violet_grid 0:24d722f0cfbe -0.09 uxdesigner|brighthealth(healthte +cv-violet_grid 0:530707fb0897 -0.01 redesignedpatientportalexperienc +cv-violet_grid 0:b2e27a1d9270 -0.06 plannedandfacilitatedusabilityte +cv-violet_grid 0:ab80a52d70fe -0.11 partneredwithengineerstoensurefe +cv-violet_grid 0:11cbf615189b -0.15 createdreusablecomponentsandstyl +cv-violet_grid 0:7e5081a3ed48 -0.17 2018-2020 +cv-violet_grid 0:73f7bbf7cd60 -0.22 juniorux/uidesigner|lumendigital +cv-violet_grid 0:c6b06045332f -0.05 austin,tx +cv-violet_grid 0:5ea8234925c5 -0.14 supporteddiscoveryworkshops,user +cv-violet_grid 0:46f00c76528c -0.19 designedresponsivewebsitesandlan +cv-violet_grid 0:f3cea36a9d83 -0.12 collaboratedwithcopywritersandde +cv-violet_grid 0:d94eccbb1fbe -0.17 selectedprojects +cv-violet_grid 0:45dc8bf81211 -0.25 novafinmobileapp|personalfinance +cv-violet_grid 0:d398b29d3dbb -0.17 2023 +cv-violet_grid 0:e66c1ce573d3 -0.33 ledux/uidesignforbudgeting,goalt +cv-violet_grid 0:0ed8829e68d0 -0.29 thatinformedasimplifiednavigatio +cv-violet_grid 0:ee9bd17ebccf -0.25 22%increaseinweeklyactiveusers. +cv-violet_grid 0:28b4603d784a -0.25 brighthealthpatientportal|health +cv-violet_grid 0:1bea20e1df19 -0.17 2021 +cv-violet_grid 0:999ddb26bed6 -0.33 redesignedkeypatientflowsincludi +cv-violet_grid 0:073d15633c0b -0.29 systemandcomponentlibraryusedacr +cv-violet_grid 0:68bf0109c6f8 -0.07 education +cv-violet_grid 0:6cb574fa10b6 -0.07 languages +cv-violet_grid 0:0cf7506cf94a -2.47 bacheloroffineartsingraphicdesig +cv-violet_grid 0:00ab7a6aa5f1 -2.43 californiacollegeofthearts +cv-violet_grid 0:7e2c21bb41db -2.44 sanfrancisco,ca|2014-2018 +cv-violet_grid 0:b6234d2ea0d6 -1.38 english +cv-violet_grid 0:bef32d2c315a -1.37 native +cv-violet_grid 0:bef32d2c315a#1 -1.29 native +cv-violet_grid 0:9c9b13446cd9 -1.30 spanish +cv-violet_grid 0:e35242c809c8 -1.33 portuguese +cv-violet_grid 0:c5d1de4964d6 -1.20 conversational +cv-violet_grid 0:1de4fede4f63 -1.09 idesignwithempathy,iteratewithpu invoice-classic 0:c26c22b94338 0.51 graphcomposestudio invoice-classic 0:c26c22b94338#1 0.34 graphcomposestudio invoice-classic 0:2a08ce5567fb 0.41 18layoutstreet @@ -3666,20 +3666,20 @@ letter-sidebar_portrait 0:7c5a9817c868 -0.41 iwouldwelcometheopportunitytobri letter-sidebar_portrait 0:4012eb4b05db -0.44 group. letter-sidebar_portrait 0:80a6a628ad52 -0.07 sincerely, letter-timeline_minimal 0:8f1010120ed6 -0.16 jordanrivera -letter-timeline_minimal 0:8f1010120ed6#1 1.63 jordanrivera +letter-timeline_minimal 0:8f1010120ed6#1 -0.29 jordanrivera letter-timeline_minimal 0:2d58f0cadc2d -0.40 platformengineer letter-timeline_minimal 0:fbeea547d99e 0.04 london,uk letter-timeline_minimal 0:89674e6f130e -0.02 +442055551000 letter-timeline_minimal 0:e0f850305ded 0.06 jordan.rivera@example.com letter-timeline_minimal 0:84308bea4540 0.00 linkedin letter-timeline_minimal 0:c0b0109d9439 0.06 github -letter-timeline_minimal 0:bef81b541365 1.60 dearhiringteamatnorthwindsystems -letter-timeline_minimal 0:ab54410ead82 1.39 iamexcitedtosharemyinterestinthe -letter-timeline_minimal 0:9185168d2cf8 1.39 document-generationsystemsthatba -letter-timeline_minimal 0:a463bced3db2 1.30 ienjoytranslatingfuzzyworkflowre -letter-timeline_minimal 0:dd474e249222 1.30 makeadoptioneasierfortherestofth -letter-timeline_minimal 0:a71e98216baa 1.57 iwouldwelcometheopportunitytobri -letter-timeline_minimal 0:80a6a628ad52 1.63 sincerely, +letter-timeline_minimal 0:bef81b541365 -0.32 dearhiringteamatnorthwindsystems +letter-timeline_minimal 0:ab54410ead82 -0.53 iamexcitedtosharemyinterestinthe +letter-timeline_minimal 0:9185168d2cf8 -0.53 document-generationsystemsthatba +letter-timeline_minimal 0:a463bced3db2 -0.50 ienjoytranslatingfuzzyworkflowre +letter-timeline_minimal 0:dd474e249222 -0.50 makeadoptioneasierfortherestofth +letter-timeline_minimal 0:a71e98216baa -0.23 iwouldwelcometheopportunitytobri +letter-timeline_minimal 0:80a6a628ad52 -0.29 sincerely, proposal-editorial 0:5205c2a261bc 0.29 northline proposal-editorial 0:da0daaba4b15 0.20 studio proposal-editorial 0:ecd1378bc9dc -0.30 proposal diff --git a/qa/src/test/resources/docx-fidelity/word-windows.tsv b/qa/src/test/resources/docx-fidelity/word-windows.tsv index f64a3f25b..4150300ff 100644 --- a/qa/src/test/resources/docx-fidelity/word-windows.tsv +++ b/qa/src/test/resources/docx-fidelity/word-windows.tsv @@ -24,8 +24,8 @@ cv-sidebar_portrait 1 1 49 49 0.08 0.27 0 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 1.44 1.57 0 -cv-violet_grid 1 1 80 77 0.35 0.81 2 +cv-timeline_minimal 2 2 89 86 0.36 0.48 0 +cv-violet_grid 1 1 80 77 0.23 1.30 5 invoice-classic 1 1 56 56 0.48 1.22 0 invoice-classic_long 2 2 166 166 0.51 0.88 0 invoice-consulting 1 1 92 92 0.91 1.41 3 @@ -55,7 +55,7 @@ letter-monogram_sidebar 1 1 12 12 0.14 0.20 0 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 +letter-timeline_minimal 1 1 15 15 0.29 0.53 0 proposal-editorial 2 2 132 130 0.50 1.81 11 proposal-indigo 1 1 72 70 0.40 0.62 0 proposal-modern 1 1 44 44 0.35 0.73 0 diff --git a/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackend.java b/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackend.java index 8fffd5eb1..34eeeb199 100644 --- a/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackend.java +++ b/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxSemanticBackend.java @@ -222,6 +222,10 @@ public final class DocxSemanticBackend implements SemanticBackend { private double hangingBelow; private XWPFParagraph hangingOver; private double hangingOverBy; + // How far the last line of the cell written last hangs below everything the cell writes + // under it, in points: Word's cell is that much taller than its content on the page (see + // writeInCell). The row it stands in reads it. + private double cellOverhang; /** * The tables whose first row text stands above, by cell — see {@link #standsAboveItsCell}. @@ -8575,6 +8579,7 @@ private void writeRow(XWPFDocument document, RowNode node) throws Exception { // MerchantInvoice's due-date text stood against its card's top without it. boolean inAPanel = surfaceBehind != null && panelCell != null; com.demcha.compose.document.layout.PlacedNode placedRow = layout.placement(node); + double rowOverhang = 0; // A row has no fill of its own: inside a panel it is a table nested in the panel's // cell, and a cell with no shading shows the panel's through it. for (int i = 0; i < node.children().size(); i++) { @@ -8587,6 +8592,7 @@ private void writeRow(XWPFDocument document, RowNode node) throws Exception { currentCellWidth = usableWidthOf(cell, i, 1); leftMarginInCell = placedColumns ? child : null; cellHang = -hang; + cellOverhang = 0; try { writeRowCellChild(cell, child); } finally { @@ -8594,6 +8600,8 @@ private void writeRow(XWPFDocument document, RowNode node) throws Exception { leftMarginInCell = previousHeld; cellHang = 0; } + rowOverhang = Math.max(rowOverhang, cellOverhang - roomBelowInItsRow(child, placedRow, node)); + cellOverhang = 0; applyRowVerticalAlign(cell, node.verticalAlign()); } giveTheLastCellItsInk(table, node, placedColumns); @@ -8608,6 +8616,33 @@ private void writeRow(XWPFDocument document, RowNode node) throws Exception { holdRowAtLeast(row, placedRow.placementHeight() - node.padding().top() - node.padding().bottom()); } indentTable(table); + // A cell's last line hanging past the row's foot makes Word's row that much taller, and + // takes as much of the gap under it, as text hanging below a band does (writeLinePair): + // TimelineMinimal's last contact line, held to its icon, stood 1.8pt below the row, and + // the page under the header with it. + // A card's border below another cell (borderBelow) makes the row taller too: Word's row + // is as tall as the taller of the two, so only what passes the border is added to it. + if (rowOverhang - borderBelow > 0) { + hangingBelow = Math.max(hangingBelow, rowOverhang - borderBelow); + } + } + + /** + * How much shorter a row's child is than the row's content box on the page, in points: room a + * line hanging below the child has before it makes Word's row taller. {@code +∞} when the + * layout does not say, so nothing is taken for it. + */ + private double roomBelowInItsRow(DocumentNode child, com.demcha.compose.document.layout.PlacedNode placedRow, + RowNode row) { + com.demcha.compose.document.layout.PlacedNode placed = layout.placement(child); + if (placedRow == null || placed == null || placedRow.startPage() != placedRow.endPage() + || placed.startPage() != placed.endPage()) { + return Double.POSITIVE_INFINITY; + } + // The row's room less the child's height, wherever the row aligns it: Word grows a row + // only when a cell holds more than the row, however the cell sets its content in it. + return Math.max(0, placedRow.placementHeight() - row.padding().top() - row.padding().bottom() + - placed.placementHeight() - child.margin().top() - child.margin().bottom()); } /** @@ -10350,6 +10385,7 @@ private void writeInCell(XWPFTableCell cell, CellContent content) throws Excepti insetRight = 0; cellTextShift = cellHang; cellHang = 0; + double overhang = 0; try { content.write(); // A cell of space and nothing written — padding round a drawing the export keeps @@ -10359,6 +10395,14 @@ private void writeInCell(XWPFTableCell cell, CellContent content) throws Excepti && carriedSpacingBefore + pendingSpacingAfter > 0 && cell.getBodyElements().isEmpty()) { holdToHairline(newBodyParagraph(cell.getXWPFDocument())); } + // A line hanging below the cell's last block takes the space the cell owes under it + // first, as it takes the gap above the next block in the flow; what it reaches past + // that makes Word's cell taller than its content on the page. + double hang = hangingBelow; + // What a pull keeps from being written is no room for it (flushSpacingAfter). + double taken = Math.min(Math.max(0, pendingSpacingAfter - pullBelow), hang); + pendingSpacingAfter -= taken; + overhang = hang - taken; // A cell ends where it ends: its last gap cannot land on whatever the body // writes next, and the body's cannot land inside it. flushSpacingAfter(); @@ -10379,6 +10423,7 @@ private void writeInCell(XWPFTableCell cell, CellContent content) throws Excepti cellTextShift = previousTextShift; tableCloser = previousCloser; } + cellOverhang = overhang; } /** Whether anything under a node was written in an earlier layer's stand-in. */ diff --git a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxRowOverhangTest.java b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxRowOverhangTest.java new file mode 100644 index 000000000..b8a92f1c4 --- /dev/null +++ b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxRowOverhangTest.java @@ -0,0 +1,152 @@ +package com.demcha.compose.document.backend.semantic.docx; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.InlineImageAlignment; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.document.svg.SvgIcon; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.poi.xwpf.usermodel.XWPFParagraph; +import org.apache.poi.xwpf.usermodel.XWPFTableCell; +import org.junit.jupiter.api.Test; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSpacing; + +import java.io.ByteArrayInputStream; +import java.util.function.Consumer; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * A cell's last line hanging past its row's foot in Word takes that much of the gap under the row. + * + *

A line held to its icon reaches as far below the page's line as the icon's ink does, and + * gives that room back out of the space above what follows ({@link DocxSemanticBackend}'s + * {@code hangingBelow}). The last line of a cell had no "what follows" there: the cell ended, + * Word made the row that much taller than the page's, and {@code TimelineMinimal}'s page under + * its header, the last contact line's icon lowered beside it, stood 1.8pt low.

+ * + * @author Artem Demchyshyn + */ +class DocxRowOverhangTest { + + private static final SvgIcon ICON = SvgIcon.parse("" + + ""); + private static final DocumentTextStyle SMALL = DocumentTextStyle.DEFAULT.withSize(7); + private static final double GAP = 10; + + @Test + void theTallestCellsHangingLineTakesItsReachOutOfTheGapUnderTheRow() throws Exception { + Exported exported = export(left -> left.addParagraph(p -> p.textStyle(SMALL).text("Name")), + DocxRowOverhangTest::contact); + try (XWPFDocument document = exported.document()) { + XWPFTableCell cell = document.getTables().get(0).getRow(0).getCell(1); + XWPFParagraph iconed = cell.getParagraphs().get(cell.getParagraphs().size() - 1); + long down = reachBelow(iconed, exported.pageLine()); + + assertThat(iconed.getCTP().getPPr().getSpacing().getLineRule()).as("held to its icon") + .isEqualTo(org.openxmlformats.schemas.wordprocessingml.x2006.main.STLineSpacingRule.EXACT); + assertThat(down).as("the line reaches below the page's").isPositive(); + assertThat(before(after(document))).as("the gap less that reach").isEqualTo(Math.round(GAP * 20) - down); + } + } + + @Test + void aHangingLineWithRoomUnderItInItsRowTakesNothing() throws Exception { + // The cell beside it is taller: the line hangs into the row's own room, and Word's row + // is as tall as the page's. + try (XWPFDocument document = export(DocxRowOverhangTest::contact, + right -> { + for (int i = 0; i < 4; i++) { + right.addParagraph(p -> p.textStyle(SMALL).text("Line")); + } + }).document()) { + assertThat(before(after(document))).isEqualTo(Math.round(GAP * 20)); + } + } + + @Test + void aShorterCellSetAtTheRowsFootHasItsRoomAllTheSame() throws Exception { + // Set at the row's foot, the shorter cell's line hangs past the row on the page; Word sets + // the cell's content at the foot too, and the row is as tall as its taller cell. + try (XWPFDocument document = export(DocxRowOverhangTest::contact, + right -> { + for (int i = 0; i < 4; i++) { + right.addParagraph(p -> p.textStyle(SMALL).text("Line")); + } + }, com.demcha.compose.document.node.RowVerticalAlign.BOTTOM).document()) { + assertThat(before(after(document))).isEqualTo(Math.round(GAP * 20)); + } + } + + @Test + void aHangingLineTakesItsReachOutOfItsCellsPaddingFirst() throws Exception { + // The cell holds 5pt under its last line: the line hangs into it, which is written that + // much shorter, and the row is as tall as the page's. + Exported exported = export(left -> left.addParagraph(p -> p.textStyle(SMALL).text("Name")), + right -> { + right.padding(DocumentInsets.bottom(5)); + contact(right); + }); + try (XWPFDocument document = exported.document()) { + XWPFTableCell cell = document.getTables().get(0).getRow(0).getCell(1); + XWPFParagraph iconed = cell.getParagraphs().get(cell.getParagraphs().size() - 1); + long down = reachBelow(iconed, exported.pageLine()); + CTSpacing spacing = iconed.getCTP().getPPr().getSpacing(); + + assertThat(spacing.isSetAfter() ? DocxTwips.of(spacing.getAfter()) : 0) + .as("the padding less the reach").isEqualTo(5 * 20 - down); + assertThat(before(after(document))).isEqualTo(Math.round(GAP * 20)); + } + } + + /** A contact stack whose last line holds an icon lowered as TimelineMinimal's are. */ + private static void contact(SectionBuilder section) { + section.spacing(3); + section.addParagraph(p -> p.textStyle(SMALL).text("London")); + section.addParagraph(p -> p.name("Iconed").textStyle(SMALL).inlineText("GitHub ", SMALL) + .inlineSvgIcon(ICON, 10.5, InlineImageAlignment.CENTER, -1.35, null)); + } + + /** The export, and the height the layout gives the icon line. */ + private record Exported(XWPFDocument document, double pageLine) { + } + + private static Exported export(Consumer left, Consumer right) throws Exception { + return export(left, right, com.demcha.compose.document.node.RowVerticalAlign.TOP); + } + + private static Exported export(Consumer left, Consumer right, + com.demcha.compose.document.node.RowVerticalAlign align) throws Exception { + try (DocumentSession session = GraphCompose.document().pageSize(400, 400) + .margin(DocumentInsets.of(20)).create()) { + session.pageFlow(page -> page + .spacing(GAP) + .addRow(row -> row.verticalAlign(align).addSection("Left", left).addSection("Right", right)) + .addParagraph(p -> p.textStyle(SMALL).text("After"))); + double pageLine = session.layoutGraph().nodes().stream() + .filter(node -> "Iconed".equals(node.semanticName())) + .findFirst().orElseThrow().placementHeight(); + return new Exported(new XWPFDocument(new ByteArrayInputStream(session.export(new DocxSemanticBackend()))), + pageLine); + } + } + + /** How far a line held to its icon reaches below the page's line, in twips: line less page and up. */ + private static long reachBelow(XWPFParagraph iconed, double pageLine) { + CTSpacing spacing = iconed.getCTP().getPPr().getSpacing(); + long up = 3 * 20 - (spacing.isSetBefore() ? DocxTwips.of(spacing.getBefore()) : 0); + long line = DocxTwips.of(spacing.getLine()); + return line - up - Math.round(pageLine * 20); + } + + private static XWPFParagraph after(XWPFDocument document) { + return document.getParagraphs().stream().filter(p -> p.getText().equals("After")).findFirst().orElseThrow(); + } + + private static long before(XWPFParagraph paragraph) { + CTSpacing spacing = paragraph.getCTP().getPPr() == null ? null : paragraph.getCTP().getPPr().getSpacing(); + return spacing == null || !spacing.isSetBefore() ? 0 : DocxTwips.of(spacing.getBefore()); + } +}