diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b602ca47..89c2d7770 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,22 @@ follow semantic versioning; release dates are ISO 8601. ### Public API +- **A DOCX list without `hangingIndent` wraps where the page does.** The page sets an item's + first line as the marker, a space and the text, and every wrapped line after the spaces that + cover the marker and that space; the Word list's levels were a stated 9pt column, a tab + after the marker: `ModernInvoice`'s and `ClassicInvoice`'s notes wrapped 2.6pt left of the + page's, `MidnightNavy`'s and `SlateOrange`'s bullets 3.7 and 2.6pt right. The top level of a + list the layout placed that does not nest now takes the width of those spaces in the list's + style as its indent and hanging, its marker followed by a space (`w:suff`) in the list's face + and size. Where Word sets that size to the half point, an item that wraps is measured in the + same proportion, less a point and never short of its widest line as Word sets it, so it + breaks at the page's words: in the page's column alone, + `TerracottaRail`'s 8.6pt items, set at 8.5, took a word onto a line the page breaks it from, + and the column under them rose 11.5pt. The item's own `w:ind` is its right side alone, so it + keeps its level's column. Lines more than 2pt off across fall from 72 to 49 in Word, and in + LibreOffice from 77 to 54 on Windows and 358 to 335 on Linux; nothing moves down, and four + lines more are found in `MidnightNavy` in both editors and in `OrangeOps` in Word and on + Linux, their items now breaking as the page breaks them. - **A panel with a left border in a DOCX cell no longer widens the cell in Word.** A panel's table hangs half its left border left of the text it is written in, so both editors put its text where the page does. In a cell, Word starts the table half that border in from the diff --git a/assets/readme/examples/word-export-companion.docx b/assets/readme/examples/word-export-companion.docx index 47b5f58ab..647103153 100644 Binary files a/assets/readme/examples/word-export-companion.docx and b/assets/readme/examples/word-export-companion.docx differ diff --git a/docs/architecture/backend-capability-matrix.md b/docs/architecture/backend-capability-matrix.md index 97bab68b1..0fdf905f5 100644 --- a/docs/architecture/backend-capability-matrix.md +++ b/docs/architecture/backend-capability-matrix.md @@ -63,7 +63,7 @@ Payload records live in `core` under | Capability (payload) | PDF (fixed) | PPTX (fixed) | DOCX (semantic) | |---|---|---|---| | Paragraph — pre-wrapped lines, runs, alignment (`ParagraphFragmentPayload`) | ✅ `PdfParagraphFragmentRenderHandler` | ✅ `PptxParagraphFragmentRenderHandler` (one absolute, wrap-disabled frame per measured line) | ⚠️ semantic paragraphs (`DocxSemanticBackend`) — each run keeps its own style, falling back to the paragraph's when it has none; a `linkTarget` becomes a `w:hyperlink`, with a relationship for an address or `w:anchor` for one of the document's own anchors, and a run's own link wins over the paragraph's; a paragraph seated off its baseline (`TextVerticalAlign`) has its runs raised or lowered in the line (`w:position`) by the PDF backend's own correction (`ParagraphSeating`), one shift for the paragraph where the page seats each line by its own; Word and LibreOffice stand an exact line's baseline four fifths of the way down it whatever the face, where the page sets it the face's ascent down, so a paragraph whose face puts the two half a point or more apart — Spectral's, not Lato's — has its text moved to the page's baseline in the same position, matched at its middle line (not yet a list item's or a table text cell's; a picture among it moves with it in Word and stays on its own baseline in LibreOffice); lines a container stacks over one another tighter than their face each end halfway between their letters and the next line's (Word draws an exact line's text on screen only inside the line; its PDF export does not cut it), and the last layer of a shape container on one page, where its line runs past the foot, ends at the foot or below its letters; letters two lines share are split halfway so the page does not move, and a stack that holds a picture keeps its lines' own heights; a `bulletOffset` of spaces becomes the paragraph's indent (`w:ind` left, hanging or first line, by `indentStrategy`) in the flow and in cells, not yet over the flow, in an overlay's left-and-right pair or in a header or footer; one with letters in it is not written, its wrapped lines still set after the spaces that cover it | -| List hanging indent — a marker column and a content column (`ListBuilder.hangingIndent(true)`, `markerGap(...)`) | ✅ marker and content emitted as separate `ParagraphFragmentPayload` fragments at the resolved `markerX` / `contentX` | ✅ the same fragments — the fixed-layout pipeline resolves the geometry before either backend sees it | ⚠️ the top level only. `DocxSemanticBackend` exports a list as a real Word list — `numbering.xml`, `w:numPr` per item, the level carrying the marker — or, with rich items or a drawn marker, as paragraphs; content and nesting are unaffected. With the flag, the top level's marker column is the layout's — the marker's width and `markerGap`, the text and its wrapped lines where the page sets them — where the gap covers what Word may set the marker wider: a picture at its written size, its edges included, or text in the page's face (embedded, or a standard one Word sets in the same widths) grown to its half-point size, half a point clear. A Word list's level then indents and hangs by that column; a list of paragraphs writes the marker, a tab to a stop there, and hangs the item there. Word places content at absolute indents and has no relative-advance primitive, so without the layout's measure the gap could not be honoured; a list that nests items, a marker the gap does not clear, and a list without the flag keep the stated column (180 twips, plus 120 per nesting level) | +| List hanging indent — a marker column and a content column (`ListBuilder.hangingIndent(true)`, `markerGap(...)`) | ✅ marker and content emitted as separate `ParagraphFragmentPayload` fragments at the resolved `markerX` / `contentX` | ✅ the same fragments — the fixed-layout pipeline resolves the geometry before either backend sees it | ⚠️ the top level only. `DocxSemanticBackend` exports a list as a real Word list — `numbering.xml`, `w:numPr` per item, the level carrying the marker — or, with rich items or a drawn marker, as paragraphs; content and nesting are unaffected. With the flag, the top level's marker column is the layout's — the marker's width and `markerGap`, the text and its wrapped lines where the page sets them — where the gap covers what Word may set the marker wider: a picture at its written size, its edges included, or text in the page's face (embedded, or a standard one Word sets in the same widths) grown to its half-point size, half a point clear. A Word list's level then indents and hangs by that column; a list of paragraphs writes the marker, a tab to a stop there, and hangs the item there. Word places content at absolute indents and has no relative-advance primitive, so without the layout's measure the gap could not be honoured; a Word list without the flag that the layout placed and that does not nest takes the page's column too, the spaces the page sets its wrapped lines after, its marker followed by a space (`w:suff`) and an item that wraps measured at Word's half-point size; a list that nests items, a list built as a tree of items (laid out flattened), and a marker the gap does not clear keep the stated column (180 twips, plus 120 per nesting level) | | Inline code/badge chips (`InlineBackground` on text spans) | ✅ `PdfParagraphFragmentRenderHandler` | ✅ `PptxParagraphFragmentRenderHandler` | ⚠️ `DocxSemanticBackend` — the fill becomes the run's own `w:shd`, in a paragraph and in a list item alike, so a badge still reads as a badge. What Word has no way to say is the shape: shading covers the glyph box, so the corner radius and the padding that widens the run on the page are not in the file, and the export records both. A `w:shd` fill is opaque, so a translucent chip is flattened first against what this export wrote underneath it — the paragraph's shading, the cell's, or the page — so the chip agrees with the file it is in, which on a white page is the colour the PDF shows. It stops being translucent, and that is recorded with the rest | | Inline images (`ParagraphImageSpan`) | ✅ `PdfParagraphFragmentRenderHandler` | ✅ `PptxParagraphFragmentRenderHandler` | ✅ `DocxSemanticBackend.writeInlinePicture` (a picture in its own run where it sits among the words, at its size, inside the run's or the paragraph's link; raised or lowered by `w:position` to where the page's alignment and `baselineOffset` put it, from the layout's measure of the paragraph's first line — in a list, the list's text on a line as tall as the item's own tallest picture; LibreOffice ignores `w:position` on a picture and stands it on the baseline, so a picture the export draws itself (icon, emoji, shape) that the page raises carries the rise as transparent rows and needs no `w:position`, while one the page lowers stands in LibreOffice higher than on the page by as much as the page lowers it — up to the text's descent for a centred icon as tall as its line; the editor clips a picture to an exact line height, so a paragraph holding a picture that leaves its text — past the ascent or the descent, in Word's placement or on the baseline — has its lines written at least the height the picture reaches, grown by the editor rather than clipped, every line of the paragraph since Word has one line height for it, and each as tall as the editor's font makes it — for 14pt text about 2.5pt taller than the page's in LibreOffice; a picture inside its text in both editors keeps the exact height; a paragraph of one line of text in a Word paragraph of its own, with room above for its pictures' reach, keeps an exact line at the page's height of it, the pictures set in it where the page puts them in Word and what their ink reaches past it taken from the gaps around it, and in LibreOffice a lowered picture there stands higher and loses what passes the line's top; its description is the text it stands for or empty) | | Inline vector shapes (`ParagraphShapeSpan`) | ✅ `PdfParagraphFragmentRenderHandler` | ⚠️ `PptxParagraphFragmentRenderHandler` + `PptxInlineGeometry` (distinct per-corner radii render with the top-left radius — single-adjust preset) | ⚠️ `DocxSemanticBackend.writeInlinePicture` + `DocxShapePictures` (a transparent PNG drawn by the shared `InlineSvgRasters` from the outline, fill and stroke — every outline kind, each layer centred in the run's box — placed as an inline picture is; the picture takes as far as the stroked ink reaches past the outline — half the stroke on an edge, more at a sharp corner's miter — and a pixel on each side, measured side by side, and is lowered by what it takes below, so no edge is cut and a shape takes that much more room in the line; a list marker that draws a disc is its picture; at the top level of a `hangingIndent(true)` list that does not nest it is followed by a tab to where the layout starts the item's text, the item's lines hanging there, when the picture clears that stop, else by a space) | diff --git a/docs/recipes/docx-export.md b/docs/recipes/docx-export.md index 7c48f9ac1..1c4b715a3 100644 --- a/docs/recipes/docx-export.md +++ b/docs/recipes/docx-export.md @@ -388,10 +388,26 @@ past it. (A face the export may not embed is the reader's editor's to substitute the marker wider.) A Word list's top level then takes that column as its indent and hanging; a list that is not one — rich items, a drawn marker — writes the marker, a tab to a stop there, and hangs the item's lines there, so its wrapped lines stand under its text as on the -page. A marker made of runs keeps its space. A list that nests items keeps stated columns -throughout — its nested levels are not measured, and one kept stated could stand left of its -parent's text. Otherwise the column is a stated constant, 180 twips plus 120 for each nesting -level, chosen near the single space the old text form left. +page. A marker made of runs keeps its space. + +A Word list without `hangingIndent` takes the page's column too. The page sets its first line +as the marker, a space and the text, and every wrapped line after the spaces that cover the +marker and that space; the top level's indent and hanging are the width of those spaces in +the list's style, and its marker is followed by a space rather than a tab (`w:suff`), so +both lines stand where the page sets them. Such a level names the list's face and size, which +Word draws the marker and its space in. Where Word sets the list's size to the half point, +wider or narrower than the page's, an item that wraps is given a right indent that grows or +shrinks its measure in the same proportion, less a point and never short of its widest line +as Word sets it and a point more, so it breaks at the page's words; an item of one line, and +one whose lines the layout does not report — every item of a list one of whose items runs +onto the next page —, is only given room. The item's own indent is its right side alone, so +it keeps its level's column and follows the level when it is edited. A list built as a tree of +items is laid out flattened, its markers in its text, and keeps the stated column. + +A list that nests items keeps stated columns throughout — its nested levels are not +measured, and one kept stated could stand left of its parent's text. So does any other list +the layout did not place: the column is then a stated constant, 180 twips plus 120 for each +nesting level, chosen near the single space the old text form left. ## What a panel keeps and loses diff --git a/qa/src/test/resources/docx-fidelity/libreoffice-linux-lines.tsv b/qa/src/test/resources/docx-fidelity/libreoffice-linux-lines.tsv index 29d1d115d..56a4b7288 100644 --- a/qa/src/test/resources/docx-fidelity/libreoffice-linux-lines.tsv +++ b/qa/src/test/resources/docx-fidelity/libreoffice-linux-lines.tsv @@ -605,15 +605,19 @@ cv-midnight_navy 0:086c0e036e67 -0.56 -0.67 2021–present cv-midnight_navy 0:009b5b6479fa -0.48 0.07 starwavesolutions cv-midnight_navy 0:60cebc1ec80c -0.48 -0.63 newyork,ny cv-midnight_navy 0:60cebc1ec80c#1 -0.28 -0.63 newyork,ny +cv-midnight_navy 0:02cf5f2e28f2 -1.04 0.07 ·developandimplementcomprehensiv +cv-midnight_navy 0:1dfea8c13a62 -1.02 0.10 awarenessby40%andleadgenerationb cv-midnight_navy 0:e0ee3cbb7b53 -1.01 0.07 ·manageateamof6marketingprofessi -cv-midnight_navy 0:8ba85a74e016 -0.99 3.75 anddesignteamstodrivecampaignsuc +cv-midnight_navy 0:8ba85a74e016 -0.99 0.10 anddesignteamstodrivecampaignsuc cv-midnight_navy 0:407f710db042 -0.98 0.07 ·overseedigitalcampaignsacrossse -cv-midnight_navy 0:308aef77a1c3 -0.96 3.75 resultingina25%increaseinroi. +cv-midnight_navy 0:308aef77a1c3 -0.96 0.10 resultingina25%increaseinroi. +cv-midnight_navy 0:40f5daa37f9a -0.95 0.07 ·analyzemarkettrendsandcompetito +cv-midnight_navy 0:af67fd7a2e8b -0.93 0.10 optimizemarketingefforts. cv-midnight_navy 0:7e121dceb78a -0.42 0.07 seniormarketingspecialist cv-midnight_navy 0:f7d1049ec64b -0.36 -0.50 2018–2021 cv-midnight_navy 0:4b8292fda43e -0.28 0.07 brightlinetechnologies cv-midnight_navy 0:a246f7ba3f71 -0.84 0.07 ·executedmulti-channelmarketingc -cv-midnight_navy 0:58b162e46dda -0.82 3.75 engagementby30%. +cv-midnight_navy 0:58b162e46dda -0.82 0.10 engagementby30%. cv-midnight_navy 0:ef77f2daef0f -0.23 0.07 ·managedsocialmediachannelsandco cv-midnight_navy 0:a112a7b3c16b -0.21 0.07 ·coordinatedwithcreativeteamstod cv-midnight_navy 0:990b9f22bb48 -0.25 0.07 marketingcoordinator @@ -908,19 +912,19 @@ cv-navy_sidebar 0:f97eeb2fe120 -0.20 0.10 brightwavesolutions,newyork,ny cv-navy_sidebar 0:f97eeb2fe120#1 -0.22 0.10 brightwavesolutions,newyork,ny cv-navy_sidebar 0:f97eeb2fe120#2 -0.25 0.10 brightwavesolutions,newyork,ny cv-navy_sidebar 0:a4beef0c22d9 -0.45 0.10 •developandimplementintegratedma -cv-navy_sidebar 0:234eaa6417f7 -0.45 -0.11 awarenessby35%andleadgenerationb +cv-navy_sidebar 0:234eaa6417f7 -0.45 0.09 awarenessby35%andleadgenerationb cv-navy_sidebar 0:dfd245470d6e -0.45 0.10 •manageateamof4marketingspeciali -cv-navy_sidebar 0:94d0b8d13763 -0.45 -0.11 anddesignteamstoalignstrategies. +cv-navy_sidebar 0:94d0b8d13763 -0.45 0.09 anddesignteamstoalignstrategies. cv-navy_sidebar 0:5dabf21df91b -0.45 0.10 •analyzecampaignperformanceusing -cv-navy_sidebar 0:1cb810ca6189 -0.45 -0.11 optimizeroiandreporting. +cv-navy_sidebar 0:1cb810ca6189 -0.45 0.09 optimizeroiandreporting. cv-navy_sidebar 0:3b172b9ab75e -0.26 0.10 marketingspecialist cv-navy_sidebar 0:d514872487af -0.08 0.15 jun2020-dec2021 cv-navy_sidebar 0:a13f57f4d861 -0.47 0.10 •executeddigitalmarketingcampaig -cv-navy_sidebar 0:4b308a27b878 -0.47 -0.11 channels. +cv-navy_sidebar 0:4b308a27b878 -0.47 0.09 channels. cv-navy_sidebar 0:39b87fda4a82 -0.47 0.10 •conductedmarketresearchandcompe -cv-navy_sidebar 0:b088780ed500 -0.47 -0.11 andinformstrategy. +cv-navy_sidebar 0:b088780ed500 -0.47 0.09 andinformstrategy. cv-navy_sidebar 0:bafd8ea30ebe -0.47 0.10 •createdengagingcontentforblogs, -cv-navy_sidebar 0:88a47597543f -0.47 -0.11 engagementby40%. +cv-navy_sidebar 0:88a47597543f -0.47 0.09 engagementby40%. cv-navy_sidebar 0:dcf0600524d2 -0.29 0.10 marketingintern cv-navy_sidebar 0:73a85d4a71b0 -0.11 0.13 jan2020-may2020 cv-navy_sidebar 0:e31c3dfebb27 -0.25 0.10 •assistedinplanningandexecutingm @@ -928,7 +932,7 @@ cv-navy_sidebar 0:f7ee178c4b93 -0.25 0.10 •supportedcontentcreationandsoci cv-navy_sidebar 0:3ffec90452c5 -0.25 0.10 •analyzeddataandpreparedreportst cv-navy_sidebar 0:6ee18b42fbf1 -0.26 0.10 achievements cv-navy_sidebar 0:cff13cc228a6 -0.45 0.10 •increasedwebsitetrafficby60%wit -cv-navy_sidebar 0:fae41a639f96 -0.45 -0.11 strategy. +cv-navy_sidebar 0:fae41a639f96 -0.45 0.09 strategy. cv-navy_sidebar 0:b46da9584f4f -0.20 0.10 •launchedasuccessfulemailmarketi cv-navy_sidebar 0:3654c4d2721e -0.20 0.10 •received“employeeoftheyear”awar cv-navy_sidebar 0:e831beb04c00 -0.21 0.10 certifications @@ -1056,13 +1060,17 @@ cv-orange_ops 0:360626c9f718 0.27 0.17 workexperience cv-orange_ops 0:03f6d636aa6b 0.28 0.20 mar2021–present cv-orange_ops 0:24c0d4863b50 0.27 0.17 expresslinkdistributionltd|birmi cv-orange_ops 0:873c40886362 0.20 0.17 •overseedailywarehouseoperations -cv-orange_ops 0:e789c025e038 0.19 0.77 staffacrossinbound,storage,picki +cv-orange_ops 0:e789c025e038 0.19 0.17 staffacrossinbound,storage,picki cv-orange_ops 0:005412ac77a3 0.27 0.17 •ensuretimelyandaccurateorderful cv-orange_ops 0:4e9c4aed8e43 0.24 0.17 •monitorandmaintaininventoryaccu -cv-orange_ops 0:1509c3cbd20f 0.24 0.77 variances. +cv-orange_ops 0:1509c3cbd20f 0.24 0.17 variances. cv-orange_ops 0:928837818f67 0.31 0.17 •analysekpisandoperationaldatato +cv-orange_ops 0:70ed682534c9 0.28 0.17 •coordinatewithtransportandcusto +cv-orange_ops 0:901159ffc9e3 0.28 0.17 improveservicelevels. +cv-orange_ops 0:947008cdcf98 0.30 0.17 •driveacultureofsafetyandaccount +cv-orange_ops 0:e6b1b813ff0b 0.30 0.17 compliancewithh&sregulations. cv-orange_ops 0:c8a3bdb04f89 0.32 0.17 •leadcontinuousimprovementinitia -cv-orange_ops 0:e28f5bf00122 0.31 0.77 productivity. +cv-orange_ops 0:e28f5bf00122 0.31 0.17 productivity. cv-orange_ops 0:bfb9273fea4d 0.32 0.17 warehouseteamleader cv-orange_ops 0:9a8c213939be 0.36 0.20 may2017–feb2021 cv-orange_ops 0:df1f256d362d 0.35 0.17 midlandgatelogistics|coventry,uk @@ -1450,7 +1458,7 @@ cv-slate_orange 0:a1707ef6164e 0.41 0.07 salesassociate cv-slate_orange 0:2bc613d4e516 0.37 0.96 mar2019–may2020 cv-slate_orange 0:bb7253ff7002 0.36 0.07 urbanoutfitters,austin,tx cv-slate_orange 0:aaa52372c317 -0.06 0.07 •consistentlyexceededsalestarget -cv-slate_orange 0:198e638fe8f3 -0.05 2.69 personalizedcustomerservice. +cv-slate_orange 0:198e638fe8f3 -0.05 0.09 personalizedcustomerservice. cv-slate_orange 0:dabead95ee7e 0.34 0.07 •builtandmaintainedstrongcustome cv-slate_orange 0:ff3f5f9ca7a0 0.33 0.07 •merchandisedfloordisplaysandcon cv-slate_orange 0:05a39f2908ba 0.32 0.07 •recognizedas“topperformer”forq4 @@ -1577,25 +1585,25 @@ cv-terracotta_rail 0:c2f296ef30a3 -0.30 0.11 professionalexperience cv-terracotta_rail 0:086c0e036e67 -0.30 -1.14 2021–present cv-terracotta_rail 0:b3af3b56129a -0.33 0.06 northlinestudio,bristol,uk cv-terracotta_rail 0:27819bba9c0f -0.54 0.06 •leaddesignpackagesfromconceptth -cv-terracotta_rail 0:d49fddeb9701 -0.57 0.25 andmixed-useprojects. +cv-terracotta_rail 0:d49fddeb9701 -0.57 0.05 andmixed-useprojects. cv-terracotta_rail 0:1d6340aa301d -0.59 0.06 •coordinatestructural,m&e,landsc -cv-terracotta_rail 0:7062411e212a -0.61 0.25 buildablesolutions. +cv-terracotta_rail 0:7062411e212a -0.61 0.05 buildablesolutions. cv-terracotta_rail 0:bf75379f5486 -0.63 0.06 •prepareandmanageplanningapplica -cv-terracotta_rail 0:894e988cdf94 -0.65 0.25 documentation. -cv-terracotta_rail 0:894e988cdf94#1 -0.99 0.25 documentation. +cv-terracotta_rail 0:894e988cdf94 -0.65 0.05 documentation. +cv-terracotta_rail 0:894e988cdf94#1 -0.99 0.05 documentation. cv-terracotta_rail 0:40c00971356d -0.67 0.06 •presentandcommunicatedesignprop -cv-terracotta_rail 0:a218287e182e -0.69 0.25 anddrivingprojectsforward. +cv-terracotta_rail 0:a218287e182e -0.69 0.05 anddrivingprojectsforward. cv-terracotta_rail 0:f2e40fc1edb7 -0.49 0.06 architect cv-terracotta_rail 0:f7d1049ec64b -0.49 -0.94 2018–2021 cv-terracotta_rail 0:59e0b0e4edc9 -0.52 0.06 urbanformpartners,bath,uk cv-terracotta_rail 0:4f2dbefcbec0 -0.74 0.06 •developedconceptandtechnicaldes -cv-terracotta_rail 0:e2a5e1a23401 -0.76 0.25 educationprojects. +cv-terracotta_rail 0:e2a5e1a23401 -0.76 0.05 educationprojects. cv-terracotta_rail 0:dedc7bfc0f49 -0.78 0.06 •preparedcoordinateddrawingsets, -cv-terracotta_rail 0:abba33f53e5a -0.80 0.25 construction. +cv-terracotta_rail 0:abba33f53e5a -0.80 0.05 construction. cv-terracotta_rail 0:91a14d5e3005 -0.62 0.06 •supportedprojectdeliveryonsite, cv-terracotta_rail 0:6b3142466e26 -0.84 0.06 •coordinatedwithcontractorsandco -cv-terracotta_rail 0:1150d4ba642c -0.86 0.25 intent. -cv-terracotta_rail 0:1150d4ba642c#1 -0.95 0.25 intent. +cv-terracotta_rail 0:1150d4ba642c -0.86 0.05 intent. +cv-terracotta_rail 0:1150d4ba642c#1 -0.95 0.05 intent. cv-terracotta_rail 0:2d2cd24f87d7 -0.67 0.06 partiiarchitecturalassistant cv-terracotta_rail 0:3b8048983ecc -0.67 -0.94 2016–2018 cv-terracotta_rail 0:ebd31e5e7505 -0.69 0.06 axisdesignworkshop,cardiff,uk @@ -1837,16 +1845,16 @@ invoice-classic 0:11239872d178 0.08 8.40 total invoice-classic 0:45a5ad5ad430 0.08 8.40 gbp6,180 invoice-classic 0:ab5aa97074c4 0.05 0.10 notes invoice-classic 0:b78eb53cbd11 0.12 0.10 •pleaseincludetheinvoicenumberon -invoice-classic 0:8c8fd62f15a7 0.12 -2.58 yourremittanceadvice. +invoice-classic 0:8c8fd62f15a7 0.12 0.12 yourremittanceadvice. invoice-classic 0:f73f1b89e354 0.12 0.10 •allworkwasdeliveredasagreedduri -invoice-classic 0:49c1ececed52 0.12 -2.58 theaprilimplementationwindow. +invoice-classic 0:49c1ececed52 0.12 0.12 theaprilimplementationwindow. invoice-classic 0:fb854bac39c8 0.05 0.06 paymentterms invoice-classic 0:24274f39b50b 0.12 0.06 •paymentduewithin14calendardays. invoice-classic 0:c438440ba377 0.12 0.06 •banktransferpreferred;contact -invoice-classic 0:434cc906ae90 0.12 -2.61 billing@graphcompose.devfor -invoice-classic 0:8822ea375315 0.12 -2.61 remittancedetails. +invoice-classic 0:434cc906ae90 0.12 0.09 billing@graphcompose.devfor +invoice-classic 0:8822ea375315 0.12 0.09 remittancedetails. invoice-classic 0:1a7b5c840e25 0.12 0.06 •latepaymentsmaydelayadditional -invoice-classic 0:1631a06d245f 0.12 -2.61 templatecustomizationwork. +invoice-classic 0:1631a06d245f 0.12 0.09 templatecustomizationwork. invoice-classic 0:e3337beaa171 0.10 0.10 thankyouforchoosinggraphcomposef invoice-classic_long 0:c26c22b94338 0.49 0.10 graphcomposestudio invoice-classic_long 0:c26c22b94338#1 0.25 0.06 graphcomposestudio @@ -2005,14 +2013,14 @@ invoice-classic_long 1:11239872d178 0.18 8.40 total invoice-classic_long 1:90b9dec01ea9 0.18 8.40 gbp53,868 invoice-classic_long 1:ab5aa97074c4 0.15 0.10 notes invoice-classic_long 1:970fb4bf9d95 0.22 0.10 •sprints1-3weredeliveredunderthe -invoice-classic_long 1:a781fa6c76d2 0.22 -2.58 frameworkagreement. +invoice-classic_long 1:a781fa6c76d2 0.22 0.12 frameworkagreement. invoice-classic_long 1:c980fb1ebf98 0.22 0.10 •hardwareandthird-partylicencesa -invoice-classic_long 1:be1dea5817a5 0.22 -2.58 billedseparately. +invoice-classic_long 1:be1dea5817a5 0.22 0.12 billedseparately. invoice-classic_long 1:fb854bac39c8 0.15 0.06 paymentterms invoice-classic_long 1:24274f39b50b 0.22 0.06 •paymentduewithin14calendardays. invoice-classic_long 1:c438440ba377 0.22 0.06 •banktransferpreferred;contact -invoice-classic_long 1:434cc906ae90 0.22 -2.61 billing@graphcompose.devfor -invoice-classic_long 1:8822ea375315 0.22 -2.61 remittancedetails. +invoice-classic_long 1:434cc906ae90 0.22 0.09 billing@graphcompose.devfor +invoice-classic_long 1:8822ea375315 0.22 0.09 remittancedetails. invoice-classic_long 1:e3337beaa171 0.20 0.10 thankyouforchoosinggraphcomposef invoice-consulting 0:d44ceaea97d1 0.27 0.10 level8,1collinsstreet invoice-consulting 0:78a0bb33347f 0.29 0.10 melbournevic3000australia @@ -2866,17 +2874,17 @@ invoice-modern 0:11239872d178 -0.02 1.36 total invoice-modern 0:45a5ad5ad430 -0.02 1.36 gbp6,180 invoice-modern 0:ab5aa97074c4 -0.05 0.10 notes invoice-modern 0:b78eb53cbd11 0.02 0.10 •pleaseincludetheinvoicenumberon -invoice-modern 0:8c8fd62f15a7 0.02 -2.58 yourremittanceadvice. +invoice-modern 0:8c8fd62f15a7 0.02 0.12 yourremittanceadvice. invoice-modern 0:0c98130c526f 0.02 0.10 •allworkwasdeliveredasagreed -invoice-modern 0:2e9787ed79bf 0.02 -2.58 duringtheaprilimplementation -invoice-modern 0:e41212011d98 0.02 -2.58 window. +invoice-modern 0:2e9787ed79bf 0.02 0.12 duringtheaprilimplementation +invoice-modern 0:e41212011d98 0.02 0.12 window. invoice-modern 0:fb854bac39c8 -0.05 0.11 paymentterms invoice-modern 0:24274f39b50b 0.02 0.11 •paymentduewithin14calendardays. invoice-modern 0:c438440ba377 0.02 0.11 •banktransferpreferred;contact -invoice-modern 0:434cc906ae90 0.02 -2.56 billing@graphcompose.devfor -invoice-modern 0:8822ea375315 0.02 -2.56 remittancedetails. +invoice-modern 0:434cc906ae90 0.02 0.14 billing@graphcompose.devfor +invoice-modern 0:8822ea375315 0.02 0.14 remittancedetails. invoice-modern 0:1a7b5c840e25 0.02 0.11 •latepaymentsmaydelayadditional -invoice-modern 0:1631a06d245f 0.02 -2.56 templatecustomizationwork. +invoice-modern 0:1631a06d245f 0.02 0.14 templatecustomizationwork. invoice-modern 0:e3337beaa171 0.00 0.10 thankyouforchoosinggraphcomposef invoice-modern_long 0:52d6e3de4fa0 0.00 12.05 invoice invoice-modern_long 0:61a964e78950 0.30 12.05 invoicegc-2026-042issued04may202 @@ -3031,14 +3039,14 @@ invoice-modern_long 1:11239872d178 0.18 1.40 total invoice-modern_long 1:90b9dec01ea9 0.18 1.35 gbp53,868 invoice-modern_long 1:ab5aa97074c4 0.15 0.10 notes invoice-modern_long 1:970fb4bf9d95 0.22 0.10 •sprints1-3weredeliveredunderthe -invoice-modern_long 1:a781fa6c76d2 0.22 -2.58 frameworkagreement. +invoice-modern_long 1:a781fa6c76d2 0.22 0.12 frameworkagreement. invoice-modern_long 1:c980fb1ebf98 0.22 0.10 •hardwareandthird-partylicencesa -invoice-modern_long 1:be1dea5817a5 0.22 -2.58 billedseparately. +invoice-modern_long 1:be1dea5817a5 0.22 0.12 billedseparately. invoice-modern_long 1:fb854bac39c8 0.15 0.11 paymentterms invoice-modern_long 1:24274f39b50b 0.22 0.11 •paymentduewithin14calendardays. invoice-modern_long 1:c438440ba377 0.22 0.11 •banktransferpreferred;contact -invoice-modern_long 1:434cc906ae90 0.22 -2.56 billing@graphcompose.devfor -invoice-modern_long 1:8822ea375315 0.22 -2.56 remittancedetails. +invoice-modern_long 1:434cc906ae90 0.22 0.14 billing@graphcompose.devfor +invoice-modern_long 1:8822ea375315 0.22 0.14 remittancedetails. invoice-modern_long 1:e3337beaa171 0.20 0.10 thankyouforchoosinggraphcomposef invoice-obsidian 0:07f15cde5b18 0.14 0.09 kestrel invoice-obsidian 0:52d6e3de4fa0 0.12 0.09 invoice diff --git a/qa/src/test/resources/docx-fidelity/libreoffice-linux.tsv b/qa/src/test/resources/docx-fidelity/libreoffice-linux.tsv index 2b559acbc..5d81c6796 100644 --- a/qa/src/test/resources/docx-fidelity/libreoffice-linux.tsv +++ b/qa/src/test/resources/docx-fidelity/libreoffice-linux.tsv @@ -9,14 +9,14 @@ cv-compact_mono 1 1 58 58 0.27 0.93 0 cv-editorial_blue 2 2 82 82 0.08 0.30 0 cv-engineering_resume 1 1 57 57 0.60 1.62 0 cv-executive 2 2 60 60 0.26 0.50 0 -cv-midnight_navy 1 1 86 80 0.28 0.63 0 +cv-midnight_navy 1 1 86 84 0.28 0.87 0 cv-minimal_underlined 2 2 59 57 0.71 13.90 9 cv-mint_editorial 2 2 59 59 0.27 0.60 0 cv-modern_professional 2 2 61 59 0.12 0.41 0 cv-monogram_sidebar 1 1 53 50 0.25 0.62 0 cv-navy_sidebar 1 1 71 71 0.20 0.45 0 cv-nordic_clean 1 1 63 58 0.22 0.52 0 -cv-orange_ops 1 1 108 102 0.24 0.53 0 +cv-orange_ops 1 1 108 106 0.26 0.53 0 cv-panel 1 1 79 73 0.27 0.49 0 cv-professional_sidebar 1 1 68 64 0.36 0.58 0 cv-serif_headline 1 1 103 103 0.44 0.88 0 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 f5d209a11..3287e6c01 100644 --- a/qa/src/test/resources/docx-fidelity/libreoffice-windows-lines.tsv +++ b/qa/src/test/resources/docx-fidelity/libreoffice-windows-lines.tsv @@ -605,15 +605,19 @@ cv-midnight_navy 0:086c0e036e67 -0.56 -0.62 2021–present cv-midnight_navy 0:009b5b6479fa -0.48 0.07 starwavesolutions cv-midnight_navy 0:60cebc1ec80c -0.48 -0.58 newyork,ny cv-midnight_navy 0:60cebc1ec80c#1 -0.28 -0.58 newyork,ny +cv-midnight_navy 0:02cf5f2e28f2 -1.04 0.07 ·developandimplementcomprehensiv +cv-midnight_navy 0:1dfea8c13a62 -1.02 0.10 awarenessby40%andleadgenerationb cv-midnight_navy 0:e0ee3cbb7b53 -1.01 0.07 ·manageateamof6marketingprofessi -cv-midnight_navy 0:8ba85a74e016 -0.99 3.75 anddesignteamstodrivecampaignsuc +cv-midnight_navy 0:8ba85a74e016 -0.99 0.10 anddesignteamstodrivecampaignsuc cv-midnight_navy 0:407f710db042 -0.98 0.07 ·overseedigitalcampaignsacrossse -cv-midnight_navy 0:308aef77a1c3 -0.96 3.75 resultingina25%increaseinroi. +cv-midnight_navy 0:308aef77a1c3 -0.96 0.10 resultingina25%increaseinroi. +cv-midnight_navy 0:40f5daa37f9a -0.95 0.07 ·analyzemarkettrendsandcompetito +cv-midnight_navy 0:af67fd7a2e8b -0.93 0.10 optimizemarketingefforts. cv-midnight_navy 0:7e121dceb78a -0.42 0.07 seniormarketingspecialist cv-midnight_navy 0:f7d1049ec64b -0.36 -0.45 2018–2021 cv-midnight_navy 0:4b8292fda43e -0.28 0.07 brightlinetechnologies cv-midnight_navy 0:a246f7ba3f71 -0.84 0.07 ·executedmulti-channelmarketingc -cv-midnight_navy 0:58b162e46dda -0.82 3.75 engagementby30%. +cv-midnight_navy 0:58b162e46dda -0.82 0.10 engagementby30%. cv-midnight_navy 0:ef77f2daef0f -0.23 0.07 ·managedsocialmediachannelsandco cv-midnight_navy 0:a112a7b3c16b -0.21 0.07 ·coordinatedwithcreativeteamstod cv-midnight_navy 0:990b9f22bb48 -0.25 0.07 marketingcoordinator @@ -908,19 +912,19 @@ cv-navy_sidebar 0:f97eeb2fe120 -0.20 0.10 brightwavesolutions,newyork,ny cv-navy_sidebar 0:f97eeb2fe120#1 -0.22 0.10 brightwavesolutions,newyork,ny cv-navy_sidebar 0:f97eeb2fe120#2 -0.25 0.10 brightwavesolutions,newyork,ny cv-navy_sidebar 0:a4beef0c22d9 -0.45 0.10 •developandimplementintegratedma -cv-navy_sidebar 0:234eaa6417f7 -0.45 -0.11 awarenessby35%andleadgenerationb +cv-navy_sidebar 0:234eaa6417f7 -0.45 0.09 awarenessby35%andleadgenerationb cv-navy_sidebar 0:dfd245470d6e -0.45 0.10 •manageateamof4marketingspeciali -cv-navy_sidebar 0:94d0b8d13763 -0.45 -0.11 anddesignteamstoalignstrategies. +cv-navy_sidebar 0:94d0b8d13763 -0.45 0.09 anddesignteamstoalignstrategies. cv-navy_sidebar 0:5dabf21df91b -0.45 0.10 •analyzecampaignperformanceusing -cv-navy_sidebar 0:1cb810ca6189 -0.45 -0.11 optimizeroiandreporting. +cv-navy_sidebar 0:1cb810ca6189 -0.45 0.09 optimizeroiandreporting. cv-navy_sidebar 0:3b172b9ab75e -0.26 0.10 marketingspecialist cv-navy_sidebar 0:d514872487af -0.08 0.15 jun2020-dec2021 cv-navy_sidebar 0:a13f57f4d861 -0.47 0.10 •executeddigitalmarketingcampaig -cv-navy_sidebar 0:4b308a27b878 -0.47 -0.11 channels. +cv-navy_sidebar 0:4b308a27b878 -0.47 0.09 channels. cv-navy_sidebar 0:39b87fda4a82 -0.47 0.10 •conductedmarketresearchandcompe -cv-navy_sidebar 0:b088780ed500 -0.47 -0.11 andinformstrategy. +cv-navy_sidebar 0:b088780ed500 -0.47 0.09 andinformstrategy. cv-navy_sidebar 0:bafd8ea30ebe -0.47 0.10 •createdengagingcontentforblogs, -cv-navy_sidebar 0:88a47597543f -0.47 -0.11 engagementby40%. +cv-navy_sidebar 0:88a47597543f -0.47 0.09 engagementby40%. cv-navy_sidebar 0:dcf0600524d2 -0.29 0.10 marketingintern cv-navy_sidebar 0:73a85d4a71b0 -0.11 0.18 jan2020-may2020 cv-navy_sidebar 0:e31c3dfebb27 -0.25 0.10 •assistedinplanningandexecutingm @@ -928,7 +932,7 @@ cv-navy_sidebar 0:f7ee178c4b93 -0.25 0.10 •supportedcontentcreationandsoci cv-navy_sidebar 0:3ffec90452c5 -0.25 0.10 •analyzeddataandpreparedreportst cv-navy_sidebar 0:6ee18b42fbf1 -0.26 0.10 achievements cv-navy_sidebar 0:cff13cc228a6 -0.45 0.10 •increasedwebsitetrafficby60%wit -cv-navy_sidebar 0:fae41a639f96 -0.45 -0.11 strategy. +cv-navy_sidebar 0:fae41a639f96 -0.45 0.09 strategy. cv-navy_sidebar 0:b46da9584f4f -0.20 0.10 •launchedasuccessfulemailmarketi cv-navy_sidebar 0:3654c4d2721e -0.20 0.10 •received“employeeoftheyear”awar cv-navy_sidebar 0:e831beb04c00 -0.21 0.10 certifications @@ -1353,7 +1357,7 @@ cv-slate_orange 0:a1707ef6164e 0.41 0.07 salesassociate cv-slate_orange 0:2bc613d4e516 0.37 1.01 mar2019–may2020 cv-slate_orange 0:bb7253ff7002 0.36 0.07 urbanoutfitters,austin,tx cv-slate_orange 0:aaa52372c317 -0.06 0.07 •consistentlyexceededsalestarget -cv-slate_orange 0:198e638fe8f3 -0.05 2.69 personalizedcustomerservice. +cv-slate_orange 0:198e638fe8f3 -0.05 0.09 personalizedcustomerservice. cv-slate_orange 0:dabead95ee7e 0.34 0.07 •builtandmaintainedstrongcustome cv-slate_orange 0:ff3f5f9ca7a0 0.33 0.07 •merchandisedfloordisplaysandcon cv-slate_orange 0:05a39f2908ba 0.32 0.07 •recognizedas“topperformer”forq4 @@ -1480,25 +1484,25 @@ cv-terracotta_rail 0:c2f296ef30a3 -0.30 0.11 professionalexperience cv-terracotta_rail 0:086c0e036e67 -0.30 -1.14 2021–present cv-terracotta_rail 0:b3af3b56129a -0.33 0.06 northlinestudio,bristol,uk cv-terracotta_rail 0:27819bba9c0f -0.54 0.06 •leaddesignpackagesfromconceptth -cv-terracotta_rail 0:d49fddeb9701 -0.57 0.25 andmixed-useprojects. +cv-terracotta_rail 0:d49fddeb9701 -0.57 0.05 andmixed-useprojects. cv-terracotta_rail 0:1d6340aa301d -0.59 0.06 •coordinatestructural,m&e,landsc -cv-terracotta_rail 0:7062411e212a -0.61 0.25 buildablesolutions. +cv-terracotta_rail 0:7062411e212a -0.61 0.05 buildablesolutions. cv-terracotta_rail 0:bf75379f5486 -0.63 0.06 •prepareandmanageplanningapplica -cv-terracotta_rail 0:894e988cdf94 -0.65 0.25 documentation. -cv-terracotta_rail 0:894e988cdf94#1 -0.99 0.25 documentation. +cv-terracotta_rail 0:894e988cdf94 -0.65 0.05 documentation. +cv-terracotta_rail 0:894e988cdf94#1 -0.99 0.05 documentation. cv-terracotta_rail 0:40c00971356d -0.67 0.06 •presentandcommunicatedesignprop -cv-terracotta_rail 0:a218287e182e -0.69 0.25 anddrivingprojectsforward. +cv-terracotta_rail 0:a218287e182e -0.69 0.05 anddrivingprojectsforward. cv-terracotta_rail 0:f2e40fc1edb7 -0.49 0.06 architect cv-terracotta_rail 0:f7d1049ec64b -0.49 -0.94 2018–2021 cv-terracotta_rail 0:59e0b0e4edc9 -0.52 0.06 urbanformpartners,bath,uk cv-terracotta_rail 0:4f2dbefcbec0 -0.74 0.06 •developedconceptandtechnicaldes -cv-terracotta_rail 0:e2a5e1a23401 -0.76 0.25 educationprojects. +cv-terracotta_rail 0:e2a5e1a23401 -0.76 0.05 educationprojects. cv-terracotta_rail 0:dedc7bfc0f49 -0.78 0.06 •preparedcoordinateddrawingsets, -cv-terracotta_rail 0:abba33f53e5a -0.80 0.25 construction. +cv-terracotta_rail 0:abba33f53e5a -0.80 0.05 construction. cv-terracotta_rail 0:91a14d5e3005 -0.62 0.06 •supportedprojectdeliveryonsite, cv-terracotta_rail 0:6b3142466e26 -0.84 0.06 •coordinatedwithcontractorsandco -cv-terracotta_rail 0:1150d4ba642c -0.86 0.25 intent. -cv-terracotta_rail 0:1150d4ba642c#1 -0.95 0.25 intent. +cv-terracotta_rail 0:1150d4ba642c -0.86 0.05 intent. +cv-terracotta_rail 0:1150d4ba642c#1 -0.95 0.05 intent. cv-terracotta_rail 0:2d2cd24f87d7 -0.67 0.06 partiiarchitecturalassistant cv-terracotta_rail 0:3b8048983ecc -0.67 -0.94 2016–2018 cv-terracotta_rail 0:ebd31e5e7505 -0.69 0.06 axisdesignworkshop,cardiff,uk @@ -1740,16 +1744,16 @@ invoice-classic 0:11239872d178 0.13 1.10 total invoice-classic 0:45a5ad5ad430 0.13 1.10 gbp6,180 invoice-classic 0:ab5aa97074c4 0.10 0.10 notes invoice-classic 0:b78eb53cbd11 0.17 0.10 •pleaseincludetheinvoicenumberon -invoice-classic 0:8c8fd62f15a7 0.17 -2.58 yourremittanceadvice. +invoice-classic 0:8c8fd62f15a7 0.17 0.12 yourremittanceadvice. invoice-classic 0:f73f1b89e354 0.17 0.10 •allworkwasdeliveredasagreedduri -invoice-classic 0:49c1ececed52 0.17 -2.58 theaprilimplementationwindow. +invoice-classic 0:49c1ececed52 0.17 0.12 theaprilimplementationwindow. invoice-classic 0:fb854bac39c8 0.10 0.06 paymentterms invoice-classic 0:24274f39b50b 0.17 0.06 •paymentduewithin14calendardays. invoice-classic 0:c438440ba377 0.17 0.06 •banktransferpreferred;contact -invoice-classic 0:434cc906ae90 0.17 -2.61 billing@graphcompose.devfor -invoice-classic 0:8822ea375315 0.17 -2.61 remittancedetails. +invoice-classic 0:434cc906ae90 0.17 0.09 billing@graphcompose.devfor +invoice-classic 0:8822ea375315 0.17 0.09 remittancedetails. invoice-classic 0:1a7b5c840e25 0.17 0.06 •latepaymentsmaydelayadditional -invoice-classic 0:1631a06d245f 0.17 -2.61 templatecustomizationwork. +invoice-classic 0:1631a06d245f 0.17 0.09 templatecustomizationwork. invoice-classic 0:e3337beaa171 0.15 0.10 thankyouforchoosinggraphcomposef invoice-classic_long 0:c26c22b94338 0.49 0.10 graphcomposestudio invoice-classic_long 0:c26c22b94338#1 0.30 0.06 graphcomposestudio @@ -1908,14 +1912,14 @@ invoice-classic_long 1:11239872d178 0.18 1.10 total invoice-classic_long 1:90b9dec01ea9 0.18 1.10 gbp53,868 invoice-classic_long 1:ab5aa97074c4 0.15 0.10 notes invoice-classic_long 1:970fb4bf9d95 0.22 0.10 •sprints1-3weredeliveredunderthe -invoice-classic_long 1:a781fa6c76d2 0.22 -2.58 frameworkagreement. +invoice-classic_long 1:a781fa6c76d2 0.22 0.12 frameworkagreement. invoice-classic_long 1:c980fb1ebf98 0.22 0.10 •hardwareandthird-partylicencesa -invoice-classic_long 1:be1dea5817a5 0.22 -2.58 billedseparately. +invoice-classic_long 1:be1dea5817a5 0.22 0.12 billedseparately. invoice-classic_long 1:fb854bac39c8 0.15 0.06 paymentterms invoice-classic_long 1:24274f39b50b 0.22 0.06 •paymentduewithin14calendardays. invoice-classic_long 1:c438440ba377 0.22 0.06 •banktransferpreferred;contact -invoice-classic_long 1:434cc906ae90 0.22 -2.61 billing@graphcompose.devfor -invoice-classic_long 1:8822ea375315 0.22 -2.61 remittancedetails. +invoice-classic_long 1:434cc906ae90 0.22 0.09 billing@graphcompose.devfor +invoice-classic_long 1:8822ea375315 0.22 0.09 remittancedetails. invoice-classic_long 1:e3337beaa171 0.20 0.10 thankyouforchoosinggraphcomposef invoice-consulting 0:d44ceaea97d1 0.27 0.10 level8,1collinsstreet invoice-consulting 0:78a0bb33347f 0.29 0.10 melbournevic3000australia @@ -2765,17 +2769,17 @@ invoice-modern 0:11239872d178 -0.02 0.76 total invoice-modern 0:45a5ad5ad430 -0.02 0.76 gbp6,180 invoice-modern 0:ab5aa97074c4 -0.05 0.10 notes invoice-modern 0:b78eb53cbd11 0.02 0.10 •pleaseincludetheinvoicenumberon -invoice-modern 0:8c8fd62f15a7 0.02 -2.58 yourremittanceadvice. +invoice-modern 0:8c8fd62f15a7 0.02 0.12 yourremittanceadvice. invoice-modern 0:0c98130c526f 0.02 0.10 •allworkwasdeliveredasagreed -invoice-modern 0:2e9787ed79bf 0.02 -2.58 duringtheaprilimplementation -invoice-modern 0:e41212011d98 0.02 -2.58 window. +invoice-modern 0:2e9787ed79bf 0.02 0.12 duringtheaprilimplementation +invoice-modern 0:e41212011d98 0.02 0.12 window. invoice-modern 0:fb854bac39c8 -0.05 0.11 paymentterms invoice-modern 0:24274f39b50b 0.02 0.11 •paymentduewithin14calendardays. invoice-modern 0:c438440ba377 0.02 0.11 •banktransferpreferred;contact -invoice-modern 0:434cc906ae90 0.02 -2.56 billing@graphcompose.devfor -invoice-modern 0:8822ea375315 0.02 -2.56 remittancedetails. +invoice-modern 0:434cc906ae90 0.02 0.14 billing@graphcompose.devfor +invoice-modern 0:8822ea375315 0.02 0.14 remittancedetails. invoice-modern 0:1a7b5c840e25 0.02 0.11 •latepaymentsmaydelayadditional -invoice-modern 0:1631a06d245f 0.02 -2.56 templatecustomizationwork. +invoice-modern 0:1631a06d245f 0.02 0.14 templatecustomizationwork. invoice-modern 0:e3337beaa171 0.00 0.10 thankyouforchoosinggraphcomposef invoice-modern_long 0:52d6e3de4fa0 0.00 -1.90 invoice invoice-modern_long 0:61a964e78950 0.30 -1.90 invoicegc-2026-042issued04may202 @@ -2930,14 +2934,14 @@ invoice-modern_long 1:11239872d178 0.18 0.80 total invoice-modern_long 1:90b9dec01ea9 0.18 0.75 gbp53,868 invoice-modern_long 1:ab5aa97074c4 0.15 0.10 notes invoice-modern_long 1:970fb4bf9d95 0.22 0.10 •sprints1-3weredeliveredunderthe -invoice-modern_long 1:a781fa6c76d2 0.22 -2.58 frameworkagreement. +invoice-modern_long 1:a781fa6c76d2 0.22 0.12 frameworkagreement. invoice-modern_long 1:c980fb1ebf98 0.22 0.10 •hardwareandthird-partylicencesa -invoice-modern_long 1:be1dea5817a5 0.22 -2.58 billedseparately. +invoice-modern_long 1:be1dea5817a5 0.22 0.12 billedseparately. invoice-modern_long 1:fb854bac39c8 0.15 0.11 paymentterms invoice-modern_long 1:24274f39b50b 0.22 0.11 •paymentduewithin14calendardays. invoice-modern_long 1:c438440ba377 0.22 0.11 •banktransferpreferred;contact -invoice-modern_long 1:434cc906ae90 0.22 -2.56 billing@graphcompose.devfor -invoice-modern_long 1:8822ea375315 0.22 -2.56 remittancedetails. +invoice-modern_long 1:434cc906ae90 0.22 0.14 billing@graphcompose.devfor +invoice-modern_long 1:8822ea375315 0.22 0.14 remittancedetails. invoice-modern_long 1:e3337beaa171 0.20 0.10 thankyouforchoosinggraphcomposef invoice-obsidian 0:07f15cde5b18 0.14 0.09 kestrel invoice-obsidian 0:52d6e3de4fa0 0.12 0.09 invoice diff --git a/qa/src/test/resources/docx-fidelity/libreoffice-windows.tsv b/qa/src/test/resources/docx-fidelity/libreoffice-windows.tsv index 02e936101..c85cb30a6 100644 --- a/qa/src/test/resources/docx-fidelity/libreoffice-windows.tsv +++ b/qa/src/test/resources/docx-fidelity/libreoffice-windows.tsv @@ -9,7 +9,7 @@ cv-compact_mono 1 1 58 58 0.27 0.93 0 cv-editorial_blue 2 2 82 82 0.08 0.30 0 cv-engineering_resume 1 1 57 57 0.60 1.62 0 cv-executive 2 2 60 60 0.26 0.50 0 -cv-midnight_navy 1 1 86 80 0.28 0.63 0 +cv-midnight_navy 1 1 86 84 0.28 0.87 0 cv-minimal_underlined 2 2 59 57 0.71 13.90 9 cv-mint_editorial 2 2 59 59 0.27 0.60 0 cv-modern_professional 2 2 61 59 0.12 0.41 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 4cd848cff..cf6b31c58 100644 --- a/qa/src/test/resources/docx-fidelity/word-windows-lines.tsv +++ b/qa/src/test/resources/docx-fidelity/word-windows-lines.tsv @@ -605,15 +605,19 @@ cv-midnight_navy 0:086c0e036e67 -0.49 -0.57 2021–present cv-midnight_navy 0:009b5b6479fa -0.52 0.02 starwavesolutions cv-midnight_navy 0:60cebc1ec80c -0.52 -0.54 newyork,ny cv-midnight_navy 0:60cebc1ec80c#1 -0.22 -0.54 newyork,ny +cv-midnight_navy 0:02cf5f2e28f2 -1.06 0.02 ·developandimplementcomprehensiv +cv-midnight_navy 0:1dfea8c13a62 -0.85 0.10 awarenessby40%andleadgenerationb cv-midnight_navy 0:e0ee3cbb7b53 -0.92 0.02 ·manageateamof6marketingprofessi -cv-midnight_navy 0:8ba85a74e016 -0.86 3.70 anddesignteamstodrivecampaignsuc +cv-midnight_navy 0:8ba85a74e016 -0.86 0.10 anddesignteamstodrivecampaignsuc cv-midnight_navy 0:407f710db042 -0.81 0.02 ·overseedigitalcampaignsacrossse -cv-midnight_navy 0:308aef77a1c3 -0.87 3.70 resultingina25%increaseinroi. +cv-midnight_navy 0:308aef77a1c3 -0.87 0.10 resultingina25%increaseinroi. +cv-midnight_navy 0:40f5daa37f9a -0.82 0.02 ·analyzemarkettrendsandcompetito +cv-midnight_navy 0:af67fd7a2e8b -0.88 0.10 optimizemarketingefforts. cv-midnight_navy 0:7e121dceb78a -0.28 0.02 seniormarketingspecialist cv-midnight_navy 0:f7d1049ec64b -0.31 -0.46 2018–2021 cv-midnight_navy 0:4b8292fda43e -0.22 0.02 brightlinetechnologies cv-midnight_navy 0:a246f7ba3f71 -0.76 0.02 ·executedmulti-channelmarketingc -cv-midnight_navy 0:58b162e46dda -0.70 3.70 engagementby30%. +cv-midnight_navy 0:58b162e46dda -0.70 0.10 engagementby30%. cv-midnight_navy 0:ef77f2daef0f -0.16 0.02 ·managedsocialmediachannelsandco cv-midnight_navy 0:a112a7b3c16b -0.10 0.02 ·coordinatedwithcreativeteamstod cv-midnight_navy 0:990b9f22bb48 -0.10 0.02 marketingcoordinator @@ -908,19 +912,19 @@ cv-navy_sidebar 0:f97eeb2fe120 -0.15 0.03 brightwavesolutions,newyork,ny cv-navy_sidebar 0:f97eeb2fe120#1 -0.17 0.03 brightwavesolutions,newyork,ny cv-navy_sidebar 0:f97eeb2fe120#2 -0.21 0.03 brightwavesolutions,newyork,ny cv-navy_sidebar 0:a4beef0c22d9 -0.40 0.03 •developandimplementintegratedma -cv-navy_sidebar 0:234eaa6417f7 -0.43 -0.18 awarenessby35%andleadgenerationb +cv-navy_sidebar 0:234eaa6417f7 -0.43 0.06 awarenessby35%andleadgenerationb cv-navy_sidebar 0:dfd245470d6e -0.41 0.03 •manageateamof4marketingspeciali -cv-navy_sidebar 0:94d0b8d13763 -0.44 -0.18 anddesignteamstoalignstrategies. +cv-navy_sidebar 0:94d0b8d13763 -0.44 0.06 anddesignteamstoalignstrategies. cv-navy_sidebar 0:5dabf21df91b -0.30 0.03 •analyzecampaignperformanceusing -cv-navy_sidebar 0:1cb810ca6189 -0.33 -0.18 optimizeroiandreporting. +cv-navy_sidebar 0:1cb810ca6189 -0.33 0.06 optimizeroiandreporting. cv-navy_sidebar 0:3b172b9ab75e -0.20 0.03 marketingspecialist cv-navy_sidebar 0:d514872487af 0.10 0.00 jun2020-dec2021 cv-navy_sidebar 0:a13f57f4d861 -0.45 0.03 •executeddigitalmarketingcampaig -cv-navy_sidebar 0:4b308a27b878 -0.36 -0.18 channels. +cv-navy_sidebar 0:4b308a27b878 -0.36 0.06 channels. cv-navy_sidebar 0:39b87fda4a82 -0.34 0.03 •conductedmarketresearchandcompe -cv-navy_sidebar 0:b088780ed500 -0.35 -0.18 andinformstrategy. +cv-navy_sidebar 0:b088780ed500 -0.35 0.06 andinformstrategy. cv-navy_sidebar 0:bafd8ea30ebe -0.33 0.03 •createdengagingcontentforblogs, -cv-navy_sidebar 0:88a47597543f -0.36 -0.18 engagementby40%. +cv-navy_sidebar 0:88a47597543f -0.36 0.06 engagementby40%. cv-navy_sidebar 0:dcf0600524d2 -0.23 0.03 marketingintern cv-navy_sidebar 0:73a85d4a71b0 0.07 0.07 jan2020-may2020 cv-navy_sidebar 0:e31c3dfebb27 -0.13 0.03 •assistedinplanningandexecutingm @@ -928,7 +932,7 @@ cv-navy_sidebar 0:f7ee178c4b93 -0.11 0.03 •supportedcontentcreationandsoci cv-navy_sidebar 0:3ffec90452c5 -0.09 0.03 •analyzeddataandpreparedreportst cv-navy_sidebar 0:6ee18b42fbf1 -0.20 0.03 achievements cv-navy_sidebar 0:cff13cc228a6 -0.34 0.03 •increasedwebsitetrafficby60%wit -cv-navy_sidebar 0:fae41a639f96 -0.37 -0.18 strategy. +cv-navy_sidebar 0:fae41a639f96 -0.37 0.06 strategy. cv-navy_sidebar 0:b46da9584f4f -0.07 0.03 •launchedasuccessfulemailmarketi cv-navy_sidebar 0:3654c4d2721e -0.01 0.03 •received“employeeoftheyear”awar cv-navy_sidebar 0:e831beb04c00 -0.15 0.03 certifications @@ -1056,13 +1060,17 @@ cv-orange_ops 0:360626c9f718 0.34 0.18 workexperience cv-orange_ops 0:03f6d636aa6b 0.40 0.16 mar2021–present cv-orange_ops 0:24c0d4863b50 0.31 0.18 expresslinkdistributionltd|birmi cv-orange_ops 0:873c40886362 0.29 0.18 •overseedailywarehouseoperations -cv-orange_ops 0:e789c025e038 0.21 0.78 staffacrossinbound,storage,picki +cv-orange_ops 0:e789c025e038 0.21 0.18 staffacrossinbound,storage,picki cv-orange_ops 0:005412ac77a3 0.31 0.18 •ensuretimelyandaccurateorderful cv-orange_ops 0:4e9c4aed8e43 0.28 0.18 •monitorandmaintaininventoryaccu -cv-orange_ops 0:1509c3cbd20f 0.32 0.78 variances. +cv-orange_ops 0:1509c3cbd20f 0.32 0.18 variances. cv-orange_ops 0:928837818f67 0.42 0.18 •analysekpisandoperationaldatato +cv-orange_ops 0:70ed682534c9 0.27 0.18 •coordinatewithtransportandcusto +cv-orange_ops 0:901159ffc9e3 0.32 0.18 improveservicelevels. +cv-orange_ops 0:947008cdcf98 0.29 0.18 •driveacultureofsafetyandaccount +cv-orange_ops 0:e6b1b813ff0b 0.34 0.18 compliancewithh&sregulations. cv-orange_ops 0:c8a3bdb04f89 0.31 0.18 •leadcontinuousimprovementinitia -cv-orange_ops 0:e28f5bf00122 0.47 0.78 productivity. +cv-orange_ops 0:e28f5bf00122 0.47 0.18 productivity. cv-orange_ops 0:bfb9273fea4d 0.37 0.18 warehouseteamleader cv-orange_ops 0:9a8c213939be 0.44 0.12 may2017–feb2021 cv-orange_ops 0:df1f256d362d 0.47 0.18 midlandgatelogistics|coventry,uk @@ -1449,7 +1457,7 @@ cv-slate_orange 0:a1707ef6164e 0.52 0.02 salesassociate cv-slate_orange 0:2bc613d4e516 0.53 1.06 mar2019–may2020 cv-slate_orange 0:bb7253ff7002 0.47 0.02 urbanoutfitters,austin,tx cv-slate_orange 0:aaa52372c317 -0.08 0.02 •consistentlyexceededsalestarget -cv-slate_orange 0:198e638fe8f3 0.01 2.64 personalizedcustomerservice. +cv-slate_orange 0:198e638fe8f3 0.01 0.00 personalizedcustomerservice. cv-slate_orange 0:dabead95ee7e 0.41 0.02 •builtandmaintainedstrongcustome cv-slate_orange 0:ff3f5f9ca7a0 0.45 0.02 •merchandisedfloordisplaysandcon cv-slate_orange 0:05a39f2908ba 0.52 0.02 •recognizedas“topperformer”forq4 @@ -1576,25 +1584,25 @@ cv-terracotta_rail 0:c2f296ef30a3 -0.30 0.03 professionalexperience cv-terracotta_rail 0:086c0e036e67 -0.33 -1.24 2021–present cv-terracotta_rail 0:b3af3b56129a -0.23 0.11 northlinestudio,bristol,uk cv-terracotta_rail 0:27819bba9c0f -0.45 0.11 •leaddesignpackagesfromconceptth -cv-terracotta_rail 0:d49fddeb9701 -0.49 0.30 andmixed-useprojects. +cv-terracotta_rail 0:d49fddeb9701 -0.49 0.06 andmixed-useprojects. cv-terracotta_rail 0:1d6340aa301d -0.49 0.11 •coordinatestructural,m&e,landsc -cv-terracotta_rail 0:7062411e212a -0.56 0.30 buildablesolutions. +cv-terracotta_rail 0:7062411e212a -0.56 0.06 buildablesolutions. cv-terracotta_rail 0:bf75379f5486 -0.56 0.11 •prepareandmanageplanningapplica -cv-terracotta_rail 0:894e988cdf94 -0.63 0.30 documentation. -cv-terracotta_rail 0:894e988cdf94#1 -1.00 0.30 documentation. +cv-terracotta_rail 0:894e988cdf94 -0.63 0.06 documentation. +cv-terracotta_rail 0:894e988cdf94#1 -1.00 0.06 documentation. cv-terracotta_rail 0:40c00971356d -0.63 0.11 •presentandcommunicatedesignprop -cv-terracotta_rail 0:a218287e182e -0.70 0.30 anddrivingprojectsforward. +cv-terracotta_rail 0:a218287e182e -0.70 0.06 anddrivingprojectsforward. cv-terracotta_rail 0:f2e40fc1edb7 -0.44 0.11 architect cv-terracotta_rail 0:f7d1049ec64b -0.44 -0.99 2018–2021 cv-terracotta_rail 0:59e0b0e4edc9 -0.46 0.11 urbanformpartners,bath,uk cv-terracotta_rail 0:4f2dbefcbec0 -0.68 0.11 •developedconceptandtechnicaldes -cv-terracotta_rail 0:e2a5e1a23401 -0.75 0.30 educationprojects. +cv-terracotta_rail 0:e2a5e1a23401 -0.75 0.06 educationprojects. cv-terracotta_rail 0:dedc7bfc0f49 -0.75 0.11 •preparedcoordinateddrawingsets, -cv-terracotta_rail 0:abba33f53e5a -0.82 0.30 construction. +cv-terracotta_rail 0:abba33f53e5a -0.82 0.06 construction. cv-terracotta_rail 0:91a14d5e3005 -0.46 0.11 •supportedprojectdeliveryonsite, cv-terracotta_rail 0:6b3142466e26 -0.82 0.11 •coordinatedwithcontractorsandco -cv-terracotta_rail 0:1150d4ba642c -0.87 0.30 intent. -cv-terracotta_rail 0:1150d4ba642c#1 -0.93 0.30 intent. +cv-terracotta_rail 0:1150d4ba642c -0.87 0.06 intent. +cv-terracotta_rail 0:1150d4ba642c#1 -0.93 0.06 intent. cv-terracotta_rail 0:2d2cd24f87d7 -0.62 0.11 partiiarchitecturalassistant cv-terracotta_rail 0:3b8048983ecc -0.62 -0.99 2016–2018 cv-terracotta_rail 0:ebd31e5e7505 -0.52 0.11 axisdesignworkshop,cardiff,uk @@ -1837,16 +1845,16 @@ invoice-classic 0:11239872d178 0.19 0.98 total invoice-classic 0:45a5ad5ad430 0.19 1.01 gbp6,180 invoice-classic 0:ab5aa97074c4 0.21 0.04 notes invoice-classic 0:b78eb53cbd11 0.24 0.04 •pleaseincludetheinvoicenumberon -invoice-classic 0:8c8fd62f15a7 0.25 -2.64 yourremittanceadvice. +invoice-classic 0:8c8fd62f15a7 0.25 0.00 yourremittanceadvice. invoice-classic 0:f73f1b89e354 0.26 0.04 •allworkwasdeliveredasagreedduri -invoice-classic 0:49c1ececed52 0.15 -2.64 theaprilimplementationwindow. +invoice-classic 0:49c1ececed52 0.15 0.00 theaprilimplementationwindow. invoice-classic 0:fb854bac39c8 0.21 0.07 paymentterms invoice-classic 0:24274f39b50b 0.24 0.07 •paymentduewithin14calendardays. invoice-classic 0:c438440ba377 0.25 0.07 •banktransferpreferred;contact -invoice-classic 0:434cc906ae90 0.26 -2.60 billing@graphcompose.devfor -invoice-classic 0:8822ea375315 0.15 -2.60 remittancedetails. +invoice-classic 0:434cc906ae90 0.26 0.04 billing@graphcompose.devfor +invoice-classic 0:8822ea375315 0.15 0.04 remittancedetails. invoice-classic 0:1a7b5c840e25 0.16 0.07 •latepaymentsmaydelayadditional -invoice-classic 0:1631a06d245f 0.17 -2.60 templatecustomizationwork. +invoice-classic 0:1631a06d245f 0.17 0.04 templatecustomizationwork. invoice-classic 0:e3337beaa171 0.28 0.00 thankyouforchoosinggraphcomposef invoice-classic_long 0:c26c22b94338 0.51 0.00 graphcomposestudio invoice-classic_long 0:c26c22b94338#1 0.34 0.01 graphcomposestudio @@ -2005,14 +2013,14 @@ invoice-classic_long 1:11239872d178 0.25 0.98 total invoice-classic_long 1:90b9dec01ea9 0.25 1.01 gbp53,868 invoice-classic_long 1:ab5aa97074c4 0.27 0.04 notes invoice-classic_long 1:970fb4bf9d95 0.30 0.04 •sprints1-3weredeliveredunderthe -invoice-classic_long 1:a781fa6c76d2 0.31 -2.64 frameworkagreement. +invoice-classic_long 1:a781fa6c76d2 0.31 0.00 frameworkagreement. invoice-classic_long 1:c980fb1ebf98 0.32 0.04 •hardwareandthird-partylicencesa -invoice-classic_long 1:be1dea5817a5 0.33 -2.64 billedseparately. +invoice-classic_long 1:be1dea5817a5 0.33 0.00 billedseparately. invoice-classic_long 1:fb854bac39c8 0.27 0.07 paymentterms invoice-classic_long 1:24274f39b50b 0.30 0.07 •paymentduewithin14calendardays. invoice-classic_long 1:c438440ba377 0.31 0.07 •banktransferpreferred;contact -invoice-classic_long 1:434cc906ae90 0.32 -2.60 billing@graphcompose.devfor -invoice-classic_long 1:8822ea375315 0.33 -2.60 remittancedetails. +invoice-classic_long 1:434cc906ae90 0.32 0.04 billing@graphcompose.devfor +invoice-classic_long 1:8822ea375315 0.33 0.04 remittancedetails. invoice-classic_long 1:e3337beaa171 0.29 0.00 thankyouforchoosinggraphcomposef invoice-consulting 0:d44ceaea97d1 0.26 0.02 level8,1collinsstreet invoice-consulting 0:78a0bb33347f 0.33 0.02 melbournevic3000australia @@ -2866,17 +2874,17 @@ invoice-modern 0:11239872d178 0.09 1.09 total invoice-modern 0:45a5ad5ad430 0.09 1.11 gbp6,180 invoice-modern 0:ab5aa97074c4 0.09 0.00 notes invoice-modern 0:b78eb53cbd11 0.00 0.00 •pleaseincludetheinvoicenumberon -invoice-modern 0:8c8fd62f15a7 0.01 -2.68 yourremittanceadvice. +invoice-modern 0:8c8fd62f15a7 0.01 -0.04 yourremittanceadvice. invoice-modern 0:0c98130c526f 0.02 0.00 •allworkwasdeliveredasagreed -invoice-modern 0:2e9787ed79bf 0.03 -2.68 duringtheaprilimplementation -invoice-modern 0:e41212011d98 0.04 -2.68 window. +invoice-modern 0:2e9787ed79bf 0.03 -0.04 duringtheaprilimplementation +invoice-modern 0:e41212011d98 0.04 -0.04 window. invoice-modern 0:fb854bac39c8 0.09 0.07 paymentterms invoice-modern 0:24274f39b50b 0.00 0.07 •paymentduewithin14calendardays. invoice-modern 0:c438440ba377 0.01 0.07 •banktransferpreferred;contact -invoice-modern 0:434cc906ae90 0.02 -2.60 billing@graphcompose.devfor -invoice-modern 0:8822ea375315 0.03 -2.60 remittancedetails. +invoice-modern 0:434cc906ae90 0.02 0.04 billing@graphcompose.devfor +invoice-modern 0:8822ea375315 0.03 0.04 remittancedetails. invoice-modern 0:1a7b5c840e25 0.04 0.07 •latepaymentsmaydelayadditional -invoice-modern 0:1631a06d245f 0.05 -2.60 templatecustomizationwork. +invoice-modern 0:1631a06d245f 0.05 0.04 templatecustomizationwork. invoice-modern 0:e3337beaa171 0.09 -0.04 thankyouforchoosinggraphcomposef invoice-modern_long 0:52d6e3de4fa0 0.18 -2.04 invoice invoice-modern_long 0:61a964e78950 0.31 -2.04 invoicegc-2026-042issued04may202 @@ -3031,14 +3039,14 @@ invoice-modern_long 1:11239872d178 0.25 1.09 total invoice-modern_long 1:90b9dec01ea9 0.25 1.09 gbp53,868 invoice-modern_long 1:ab5aa97074c4 0.23 0.00 notes invoice-modern_long 1:970fb4bf9d95 0.26 0.00 •sprints1-3weredeliveredunderthe -invoice-modern_long 1:a781fa6c76d2 0.27 -2.68 frameworkagreement. +invoice-modern_long 1:a781fa6c76d2 0.27 -0.04 frameworkagreement. invoice-modern_long 1:c980fb1ebf98 0.28 0.00 •hardwareandthird-partylicencesa -invoice-modern_long 1:be1dea5817a5 0.29 -2.68 billedseparately. +invoice-modern_long 1:be1dea5817a5 0.29 -0.04 billedseparately. invoice-modern_long 1:fb854bac39c8 0.23 0.07 paymentterms invoice-modern_long 1:24274f39b50b 0.26 0.07 •paymentduewithin14calendardays. invoice-modern_long 1:c438440ba377 0.27 0.07 •banktransferpreferred;contact -invoice-modern_long 1:434cc906ae90 0.28 -2.60 billing@graphcompose.devfor -invoice-modern_long 1:8822ea375315 0.29 -2.60 remittancedetails. +invoice-modern_long 1:434cc906ae90 0.28 0.04 billing@graphcompose.devfor +invoice-modern_long 1:8822ea375315 0.29 0.04 remittancedetails. invoice-modern_long 1:e3337beaa171 0.33 -0.04 thankyouforchoosinggraphcomposef invoice-obsidian 0:07f15cde5b18 0.11 -0.04 kestrel invoice-obsidian 0:52d6e3de4fa0 0.15 -0.04 invoice diff --git a/qa/src/test/resources/docx-fidelity/word-windows.tsv b/qa/src/test/resources/docx-fidelity/word-windows.tsv index 8cba14c6a..2e0fb9033 100644 --- a/qa/src/test/resources/docx-fidelity/word-windows.tsv +++ b/qa/src/test/resources/docx-fidelity/word-windows.tsv @@ -9,14 +9,14 @@ cv-compact_mono 1 1 58 58 0.26 1.01 0 cv-editorial_blue 2 2 82 82 0.14 0.27 0 cv-engineering_resume 1 1 57 57 0.58 1.54 0 cv-executive 2 2 60 60 0.21 0.43 0 -cv-midnight_navy 1 1 86 80 0.20 0.70 0 +cv-midnight_navy 1 1 86 84 0.22 0.82 0 cv-minimal_underlined 2 2 59 59 0.71 1.23 0 cv-mint_editorial 2 2 59 59 0.13 0.31 0 cv-modern_professional 2 2 61 59 0.09 0.35 0 cv-monogram_sidebar 1 1 53 50 0.28 0.67 0 cv-navy_sidebar 1 1 71 69 0.13 0.36 0 cv-nordic_clean 1 1 63 58 0.22 0.61 0 -cv-orange_ops 1 1 108 102 0.29 0.71 0 +cv-orange_ops 1 1 108 106 0.29 0.71 0 cv-panel 1 1 79 73 0.34 0.67 0 cv-professional_sidebar 1 1 68 64 0.30 0.49 0 cv-serif_headline 1 1 103 103 0.50 1.03 0 diff --git a/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxLayoutMetrics.java b/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxLayoutMetrics.java index be900926f..1a119fa71 100644 --- a/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxLayoutMetrics.java +++ b/render-docx/src/main/java/com/demcha/compose/document/backend/semantic/docx/DocxLayoutMetrics.java @@ -368,36 +368,35 @@ OptionalDouble placedWidth(DocumentNode node) { } /** - * How many lines each item of a list was laid out on, in the order the items were placed. + * The lines each item of a list was laid out on, in the order the items were placed. * *

A list lays each item out as a fragment of its own. A list whose markers stand in a * column of their own lays the marker out as one more fragment, level with its item's - * text and as tall: the two are one item, and the marker's single line does not make - * the item a wrapped one.

+ * text and as tall: the two are one item, its text's lines the item's, and the marker's + * single line does not make the item a wrapped one.

* * @param list a list node - * @return the line count of every item, empty when the list laid out nothing + * @return every item's lines, empty when the list laid out nothing */ - List itemLineCounts(DocumentNode list) { - List counts = new ArrayList<>(); + List> itemLines(DocumentNode list) { + List> items = new ArrayList<>(); PlacedFragment previous = null; for (PlacedFragment fragment : fragmentsOf(list)) { if (!(fragment.payload() instanceof ParagraphFragmentPayload paragraph)) { continue; } - int lines = paragraph.lines().size(); boolean sameItem = previous != null && previous.pageIndex() == fragment.pageIndex() && Math.abs(previous.y() - fragment.y()) < 0.01 && Math.abs(previous.height() - fragment.height()) < 0.01; - if (sameItem) { - counts.set(counts.size() - 1, Math.max(counts.get(counts.size() - 1), lines)); - } else { - counts.add(lines); + if (!sameItem) { + items.add(paragraph.lines()); + } else if (paragraph.lines().size() > items.get(items.size() - 1).size()) { + items.set(items.size() - 1, paragraph.lines()); } previous = fragment; } - return counts; + return items; } /** 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 9c3ba3cf9..58affe72e 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 @@ -334,6 +334,8 @@ public final class DocxSemanticBackend implements SemanticBackend { listNumbering = new java.util.IdentityHashMap<>(); // The marker column, in twips, of the list definitions whose top level takes the page's. private final java.util.Map measuredColumns = new java.util.HashMap<>(); + // Of those, the ones whose column is the page's prefix of spaces, the marker a space ahead. + private final java.util.Set prefixColumns = new java.util.HashSet<>(); // What the engine already measured for the nodes being written: line heights and // resolved column widths. Empty when the export was handed no layout. private DocxLayoutMetrics layout = DocxLayoutMetrics.EMPTY; @@ -391,10 +393,11 @@ public final class DocxSemanticBackend implements SemanticBackend { private double listLineGap; /** - * How many lines each item of the list being written still to come was laid out on, - * first item first; empty when they cannot be told apart, and then no item gets the gap. + * The lines each item of the list being written still to come was laid out on, first item + * first; empty when they cannot be told apart, and then no item gets the gap. */ - private java.util.ArrayDeque listItemLines = new java.util.ArrayDeque<>(); + private java.util.ArrayDeque> listItemLines = + new java.util.ArrayDeque<>(); /** Whether the list being written has an item above the one about to be written. */ private boolean anItemWasWritten; @@ -703,6 +706,7 @@ private byte[] write(List sections, Path outputFile) throws Exc picturesDrawnBeside.clear(); listNumbering.clear(); measuredColumns.clear(); + prefixColumns.clear(); report = new DocxExportReport.Builder(); bookmarkNames = new DocxBookmarkNames(); headingLevels = headingLevelsIn(whole); @@ -2698,6 +2702,11 @@ private BigInteger numberingFor(XWPFDocument document, return null; } long column = measuredColumn(list, levels.get(0)); + boolean afterASpace = false; + if (column == 0) { + column = prefixColumn(list); + afterASpace = column > 0; + } CTAbstractNum abstractNum = CTAbstractNum.Factory.newInstance(); abstractNum.setAbstractNumId(BigInteger.valueOf(listNumbering.size())); for (int depth = 0; depth < levels.size(); depth++) { @@ -2715,6 +2724,10 @@ private BigInteger numberingFor(XWPFDocument document, indent.setLeft(BigInteger.valueOf(measured ? column : (long) LIST_HANGING_TWIPS + (long) LIST_NESTING_STEP_TWIPS * depth)); indent.setHanging(BigInteger.valueOf(measured ? column : LIST_HANGING_TWIPS)); + if (measured && afterASpace) { + // The page sets the first line's text a space after the marker, not at a column. + followTheMarkerWithASpace(level); + } if (measured && list.textStyle() != null) { // Word draws the marker in the paragraph mark's style, and an item at an exact // line leaves its mark the document's: a list set smaller, or in another face, @@ -2729,6 +2742,9 @@ private BigInteger numberingFor(XWPFDocument document, if (column > 0) { measuredColumns.put(numId, column); } + if (afterASpace) { + prefixColumns.add(numId); + } return numId; } @@ -2751,6 +2767,39 @@ private long measuredColumn(com.demcha.compose.document.node.ListNode list, Stri return Math.round(toText.getAsDouble() * POINT_TO_TWIP); } + /** + * Writes {@code w:suff="space"} on a list level, where the schema puts it, before its + * {@code w:lvlText}: the marker is followed by a space rather than a tab to the level's + * column. The schema classes POI ships hold no type for it. + */ + private static void followTheMarkerWithASpace(CTLvl level) { + String w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; + try (org.apache.xmlbeans.XmlCursor cursor = level.getLvlText().newCursor()) { + cursor.beginElement(new javax.xml.namespace.QName(w, "suff", "w")); + cursor.insertAttributeWithValue(new javax.xml.namespace.QName(w, "val", "w"), "space"); + } + } + + /** + * The column a list without {@code hangingIndent} sets its wrapped lines at, in twips, or 0 + * where it keeps the stated one: the spaces covering its marker and the space after it, + * which the page puts before every wrapped line ({@code ParagraphWrapping}), measured in the + * list's style. Its first line is the marker, a space and the text, which a level whose + * marker a space follows sets as the page does. Only a list the layout placed, with a + * marker it sets, that does not nest: a nested level's prefix is not measured. + */ + private long prefixColumn(com.demcha.compose.document.node.ListNode list) { + // A list of item trees, even leaves, is laid out flattened, its markers in its text and + // no prefix before a wrapped line. + if (list.hangingIndent() || !list.nestedItems().isEmpty() || !layout.placed(list) || !list.marker().isVisible() + || list.marker().isRich() || list.marker().prefix().isBlank()) { + return 0; + } + String spaces = spacesCovering(list.textStyle(), list.marker().prefix()); + double width = spaces.isEmpty() ? 0 : styleWidth(list.textStyle(), spaces); + return width > 0 ? Math.round(width * POINT_TO_TWIP) : 0; + } + /** * The one marker each nesting depth uses, or {@code null} when the list cannot be a * Word list. @@ -2852,13 +2901,14 @@ private void writeList(XWPFDocument document, boolean previousItemWritten = anItemWasWritten; anItemWasWritten = false; double previousLineGap = listLineGap; - java.util.ArrayDeque previousItemLines = listItemLines; + java.util.ArrayDeque> previousItemLines = + listItemLines; // The list's lineSpacing stands between the lines of an item that wraps, and only // there: each item is a paragraph of its own, so each is given it by its own lines // (see applyLineGap). The items are matched to the layout's in order; where the // two do not count the same items, none is given it. listLineGap = layout.lineGap(list); - List laidOut = layout.itemLineCounts(list); + List> laidOut = layout.itemLines(list); listItemLines = laidOut.size() == itemCount(list) ? new java.util.ArrayDeque<>(laidOut) : new java.util.ArrayDeque<>(); @@ -2886,11 +2936,12 @@ private void writeList(XWPFDocument document, } /** Puts the list's gap between the lines of the item just started, if it wraps. */ - private void applyItemLineGap(XWPFParagraph item) { - Integer lines = listItemLines.poll(); + private List applyItemLineGap(XWPFParagraph item) { + List lines = listItemLines.poll(); if (lines != null) { - applyLineGap(item, listLineGap, lines); + applyLineGap(item, listLineGap, lines.size()); } + return lines; } /** How many items a list writes, nested ones included. */ @@ -3002,11 +3053,14 @@ private void writeListLine(XWPFDocument document, DocumentTextStyle style, spaceBeforeTheNextItem(); XWPFParagraph para = newBodyParagraph(document); applyLineHeight(para, lineHeight); - applyItemLineGap(para); + List lines = applyItemLineGap(para); if (numId != null) { para.setNumID(numId); para.setNumILvl(BigInteger.valueOf(depth)); indentListItemInside(para, depth, numId); + if (depth == 0 && prefixColumns.contains(numId)) { + measureTheItemAtWordsSize(para, style, lines, measuredColumns.get(numId)); + } } XWPFRun run = para.createRun(); applyStyle(run, style); @@ -3014,6 +3068,53 @@ private void writeListLine(XWPFDocument document, DocumentTextStyle style, styleTheMark(para, style); } + /** + * Sets a list item's lines in a measure as much wider or narrower than the page's as Word + * sets its text, as {@link #measureAtWordsSize} does a paragraph's. + * + *

An item in the page's prefix column has exactly the page's measure, and Word sets a + * size the page gives to the tenth at the half point: measured in that column alone, + * {@code TerracottaRail}'s 8.6pt items, set at 8.5, took onto a line a word the page breaks + * onto the next, and everything under them stood 11.5pt high. Held a point short, as a + * paragraph's is, and never short of + * its widest line as Word sets it and a point more, which would take a word off a line the + * page fills to the point; an item of one line broke no word, and is given room and never + * narrowed, and so is one whose lines are not known.

+ * + * @param lines the lines the page set the item in, or {@code null} unknown + * @param column the item's column, in twips + */ + private void measureTheItemAtWordsSize(XWPFParagraph para, DocumentTextStyle style, + List lines, + long column) { + double room = availableWidth() - column / POINT_TO_TWIP; + if (style == null || !(style.size() > 0) || !Double.isFinite(room) || !(room > 0)) { + return; + } + double share = wordsSize(style.size()) / style.size(); + if (Math.abs(share - 1) < 1e-9) { + return; + } + // Each line as Word sets it, from the column: the page's line holds the prefix the + // column stands for, the marker and its space or the spaces covering them. + double fits = 0; + for (com.demcha.compose.document.layout.payloads.ParagraphLine line : lines == null ? List.of() : lines) { + fits = Math.max(fits, widthAtWordsSize(line) - column / POINT_TO_TWIP); + } + double more = lines == null || lines.size() <= 1 + ? Math.max(0, Math.max(room * (share - 1), fits * (1 + ONE_LINE_FACE_SLACK) - room)) + : Math.max(room * (share - 1) - WORDS_MEASURE_CLEARANCE, fits + WORDS_MEASURE_CLEARANCE - room); + if (Math.abs(more) < 0.05) { + return; + } + CTPPr properties = para.getCTP().isSetPPr() ? para.getCTP().getPPr() : para.getCTP().addNewPPr(); + // The right side alone: Word and LibreOffice take each side of w:ind on its own, so the + // item keeps its level's column and follows the level when an editor changes it. + 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))); + } + /** * Keeps a numbered item's own indent when it sits inside a padded container. * diff --git a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxHangingLeftTest.java b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxHangingLeftTest.java index f65acbbe7..ecac824ff 100644 --- a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxHangingLeftTest.java +++ b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxHangingLeftTest.java @@ -117,7 +117,8 @@ void aListInACellOfAHangingRowMovesLeftWithTheTextBesideIt() throws Exception { assertThat(firstColumn + title / 20.0).as("the title where the page starts it, less the hang") .isCloseTo(DASH + TITLE_PADDING - HANG, offset(0.1)); - assertThat(item - title).as("the level's own indent, and nothing else between them").isEqualTo(180L); + assertThat(item - title).as("the level's own indent, and nothing else between them") + .isEqualTo(DocxListLevels.column(document)); } } diff --git a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListHangingIndentTest.java b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListHangingIndentTest.java index 8ff5ab046..4cec09e9b 100644 --- a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListHangingIndentTest.java +++ b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListHangingIndentTest.java @@ -169,7 +169,10 @@ void theFlagChangesTheLevelsColumnAndNothingOnTheParagraph() throws Exception { assertThat(properties.isSetTabs()).as("no tab stops").isFalse(); assertThat(paragraph.getRuns()).as("one run, as before").hasSize(1); } - assertThat(indentsOf(without)).as("the stated column").containsExactly(180, 180); + List prefix = indentsOf(without); + assertThat(prefix.get(0)).as("the page's prefix of spaces, the marker a space ahead") + .isEqualTo(prefix.get(1)).isNotEqualTo(180); + assertThat(DocxListLevels.markerFollowedByASpace(without)).isTrue(); List measured = indentsOf(withFlag); assertThat(measured.get(0)).as("the column the layout set the bullet in, the 16pt gap and the bullet") .isEqualTo(measured.get(1)).isGreaterThan(16 * 20); diff --git a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListLevels.java b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListLevels.java new file mode 100644 index 000000000..ab75fd5a9 --- /dev/null +++ b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListLevels.java @@ -0,0 +1,42 @@ +package com.demcha.compose.document.backend.semantic.docx; + +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTLvl; + +import java.math.BigInteger; + +/** + * Reads a list definition's top level back from an exported document: the first one written, + * which is the first list the export made a Word list. + */ +final class DocxListLevels { + + private DocxListLevels() { + } + + /** The top level of the document's first list definition. */ + static CTLvl levelZero(XWPFDocument document) { + return document.getNumbering().getAbstractNum(BigInteger.ZERO).getAbstractNum().getLvlArray(0); + } + + /** The column the document's first list sets its top level in, its left indent, in twips. */ + static long column(XWPFDocument document) { + return DocxTwips.of(levelZero(document).getPPr().getInd().getLeft()); + } + + /** Whether the document's first list follows its top level's marker with a space, not a tab. */ + static boolean markerFollowedByASpace(XWPFDocument document) { + try (org.apache.xmlbeans.XmlCursor cursor = levelZero(document).newCursor()) { + if (!cursor.toFirstChild()) { + return false; + } + do { + if ("suff".equals(cursor.getName().getLocalPart())) { + return "space".equals(cursor.getAttributeText(new javax.xml.namespace.QName( + "http://schemas.openxmlformats.org/wordprocessingml/2006/main", "val"))); + } + } while (cursor.toNextSibling()); + return false; + } + } +} diff --git a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListMarkerGapTest.java b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListMarkerGapTest.java index 0caab904e..bff5cdb41 100644 --- a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListMarkerGapTest.java +++ b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListMarkerGapTest.java @@ -54,8 +54,11 @@ void aWordListOfATextMarkerTakesTheColumnTheLayoutSetsIt() throws Exception { assertThat(column[0]).as("the column, as wide as it hangs").isEqualTo(column[1]); assertThat(column[0]).as("the bullet's width and the gap").isGreaterThan(Math.round(GAP * 20)); - assertThat(levelZero(stated)).as("a list the layout set no column for keeps the stated one") - .containsExactly(180, 180); + long[] prefix = levelZero(stated); + assertThat(prefix[0]).as("a list without the flag takes the page's prefix of spaces") + .isEqualTo(prefix[1]).isNotEqualTo(column[0]).isNotEqualTo(180); + assertThat(DocxListLevels.markerFollowedByASpace(stated)).as("its marker a space ahead").isTrue(); + assertThat(DocxListLevels.markerFollowedByASpace(wide)).as("a column's marker a tab ahead").isFalse(); } } diff --git a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListNumberingTest.java b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListNumberingTest.java index 63a416deb..1b378bc17 100644 --- a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListNumberingTest.java +++ b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxListNumberingTest.java @@ -255,8 +255,8 @@ void aListsOwnSidesHoldItsItemsIn() throws Exception { var indent = item.getCTP().getPPr().getInd(); assertThat(DocxTwips.of(indent.getLeft())).as("the margin, then the level's own indent") - .isEqualTo(30 * 20L + 180); - assertThat(DocxTwips.of(indent.getHanging())).isEqualTo(180L); + .isEqualTo(30 * 20L + DocxListLevels.column(document)); + assertThat(DocxTwips.of(indent.getHanging())).isEqualTo(DocxListLevels.column(document)); assertThat(DocxTwips.of(indent.getRight())).isEqualTo(20 * 20L); } } @@ -300,7 +300,85 @@ void aListInACellKeepsAllOfItsLeftMargin() throws Exception { .filter(p -> p.getText().equals("alpha")) .findFirst().orElseThrow(); - assertThat(DocxTwips.of(item.getCTP().getPPr().getInd().getLeft())).isEqualTo(30 * 20L + 180); + assertThat(DocxTwips.of(item.getCTP().getPPr().getInd().getLeft())) + .isEqualTo(30 * 20L + DocxListLevels.column(document)); + } + } + + @Test + void aListWithoutTheFlagWrapsItsItemsWhereThePageDoes() throws Exception { + // The page sets the marker, a space and the text, and every wrapped line after the + // spaces covering the marker: ModernInvoice's notes wrapped at a stated 9pt in Word, + // 2.7pt left of the page's. + long[] columns = new long[2]; + for (int i = 0; i < 2; i++) { + DocumentTextStyle style = DocumentTextStyle.DEFAULT.withSize(i == 0 ? 10 : 20); + try (XWPFDocument document = export(flow -> flow.addList(list -> list.bullet().textStyle(style) + .items("Alpha", "Beta")))) { + var level = DocxListLevels.levelZero(document); + columns[i] = DocxListLevels.column(document); + + assertThat(DocxTwips.of(level.getPPr().getInd().getHanging())).as("hung as far as it starts") + .isEqualTo(columns[i]); + assertThat(DocxListLevels.markerFollowedByASpace(document)).as("the marker a space ahead").isTrue(); + assertThat(level.getRPr().getSzArray(0).getVal().toString()) + .as("the marker and its space in the list's size").isEqualTo(String.valueOf(i == 0 ? 20 : 40)); + } + } + assertThat(columns[1]).as("spaces twice the size, twice as wide").isCloseTo(2 * columns[0], + org.assertj.core.data.Offset.offset(2L)); + assertThat(columns[0]).as("not the stated 9pt").isNotEqualTo(180L); + } + + @Test + void aListThatNestsKeepsTheStatedColumnsWithoutTheFlag() throws Exception { + // A nested level's prefix is not measured: kept stated, a top level at the page's would + // stand right of the text of the items under it. + try (XWPFDocument document = export(flow -> flow.add(nested(2)))) { + assertThat(DocxListLevels.column(document)).isEqualTo(180L); + assertThat(DocxListLevels.markerFollowedByASpace(document)).isFalse(); + } + // A tree of leaves is laid out flattened too, its marker in its text, no prefix before + // a wrapped line. + try (XWPFDocument document = export(flow -> flow.addList(list -> list.bullet() + .addItem("Leaf", leaf -> { }).addItem("Another", leaf -> { })))) { + assertThat(DocxListLevels.column(document)).isEqualTo(180L); + assertThat(DocxListLevels.markerFollowedByASpace(document)).isFalse(); + } + } + + @Test + void anItemInThePagesPrefixColumnIsMeasuredAtWordsSize() throws Exception { + // In the page's column alone, TerracottaRail's 8.6pt items, set at 8.5, took onto a line a + // word the page breaks onto the next. Narrowed as much as Word sets them narrower, and a + // point; widened where Word sets them wider; an item of one line is never narrowed. Only + // the right side is the item's own: its column stays the level's. + String wrapping = "Coordinated with contractors and consultants to maintain programme, budget and design " + + "intent across every stage of the work, from the first sketch to the last handover."; + for (double size : new double[] {8.6, 8.4}) { + try (XWPFDocument document = export(flow -> flow.addList(list -> list.bullet() + .textStyle(DocumentTextStyle.DEFAULT.withSize(size)).items(wrapping, "One line")))) { + List items = items(document); + double room = 523 - DocxListLevels.column(document) / 20.0; + double proportional = -(room * (8.5 / size - 1) - 1); + var long_ = items.get(0).getCTP().getPPr().getInd(); + var short_ = items.get(1).getCTP().getPPr().getInd(); + double right = DocxTwips.of(long_.getRight()) / 20.0; + + assertThat(long_.isSetLeft() || long_.isSetHanging() || long_.isSetFirstLine()) + .as("no column of its own at %spt, so it follows its level", size).isFalse(); + if (size < 8.5) { + assertThat(right).as("widened at least as Word sets it wider, less a point") + .isLessThanOrEqualTo(proportional + 0.05); + } else { + // Its widest line fills its measure to within the point held short: narrowed + // in proportion, that line would lose its last word in Word. + assertThat(right).as("narrowed, but not past its widest line and a point") + .isPositive().isLessThan(proportional - 1); + } + assertThat(short_ == null || !short_.isSetRight() || DocxTwips.of(short_.getRight()) <= 0) + .as("the item of one line at %spt is not narrowed", size).isTrue(); + } } } diff --git a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxPanelInsetTest.java b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxPanelInsetTest.java index b55f17a93..75954497d 100644 --- a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxPanelInsetTest.java +++ b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxPanelInsetTest.java @@ -98,8 +98,8 @@ void aListInAPaddedSectionKeepsItsHangingIndent() throws Exception { assertThat(DocxTwips.of(properties.getInd().getLeft())) .as("the section's inset plus the level's own indent") - .isEqualTo(14 * 20L + 180L); - assertThat(DocxTwips.of(properties.getInd().getHanging())).isEqualTo(180L); + .isEqualTo(14 * 20L + DocxListLevels.column(document)); + assertThat(DocxTwips.of(properties.getInd().getHanging())).isEqualTo(DocxListLevels.column(document)); } } diff --git a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxParagraphSidesTest.java b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxParagraphSidesTest.java index 6ff752d52..c3c425c6e 100644 --- a/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxParagraphSidesTest.java +++ b/render-docx/src/test/java/com/demcha/compose/document/backend/semantic/docx/DocxParagraphSidesTest.java @@ -135,7 +135,8 @@ void aListIsHeldInByAllOfItsLeftSideInACellAndOnALayer() throws Exception { assertThat(indent).as("the item carries the padding past its level, layered %s", layered).isNotNull(); assertThat(DocxTwips.of(indent.getLeft())) - .as("the level's 9pt and all 10pt of the padding, layered %s", layered).isEqualTo(180 + 200); + .as("the level's column and all 10pt of the padding, layered %s", layered) + .isEqualTo(DocxListLevels.column(document) + 200); assertThat(DocxTwips.of(indent.getRight())) .as("30pt, less the editor's 2pt in a cell, layered %s", layered) .isEqualTo(layered ? 30 * 20L : 28 * 20L);