sw/qa/extras/layout/layout4.cxx | 15 +++++++++-- sw/qa/extras/odfexport/odfexport4.cxx | 12 +++++++++ sw/source/core/text/itrform2.cxx | 19 ++++++++++++--- sw/source/core/text/portxt.cxx | 43 +++++++++++++++++++++++----------- sw/source/core/text/portxt.hxx | 2 + 5 files changed, 72 insertions(+), 19 deletions(-)
New commits: commit b36f181bb71c395f9c48c6358bea550ac4767839 Author: Mike Kaganski <[email protected]> AuthorDate: Sat Oct 11 01:55:17 2025 +0500 Commit: Mike Kaganski <[email protected]> CommitDate: Sat Oct 11 12:05:21 2025 +0200 tdf#168777: Trailing space must show all decorations, also for ODF Commit 853e13f9146e83b959bc53152ec103470d55fb4f (tdf#57187: make sure to put trailing blanks to hole portion in narrow lines, 2023-12-30) caused a regression, where trailing whitespace lost its decorations. Commit a877c6ce97f73906c39cf800bc61d7d99847361c (tdf#164487: Introduce "Show underline" MS Word compatibility option, 2025-02-26) took care of the Word compatibility case, and introduced a compatibility flag matching respective flag in Word. It only handled underline, as the flag name in Word implied. It turns out, that (1) Word's flag, despite its name, controls all decorations of trailing writespace (highlignting, strike-out); and (2) before the original commit, Writer's default was to show all the decorations as well. Which means, that there is only one case when the decorations must be omitted: if "Word-compatible trailing blanks" is enabled, but "Underline Word-compatible trailing blanks" is off. This change is intended to show trailing space's underline, overline, strike-out, border, and highlighting correctly. Change-Id: I68d0aaaa0a7b9610ec817728bd4acad92ef26fe1 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192191 Tested-by: Jenkins Reviewed-by: Mike Kaganski <[email protected]> diff --git a/sw/qa/extras/layout/layout4.cxx b/sw/qa/extras/layout/layout4.cxx index 56865788eb5c..421ddfd93521 100644 --- a/sw/qa/extras/layout/layout4.cxx +++ b/sw/qa/extras/layout/layout4.cxx @@ -994,14 +994,20 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter4, testTdf57187_Tdf158900) u"PortionType::Break"); // tdf#158900: Check that the break after a long line with trailing spaces is kept on same line. - // Without the fix in place, this would fail: the line had only 2 portions (text + hole), + // Without the fix in place, this would fail: the line had only text and hole portions, // and the break was on a separate third line - assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[3]/*", 3); + assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[3]/*", 4); assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[3]/*[1]", "type", u"PortionType::Text"); assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[3]/*[2]", "type", u"PortionType::Hole"); + assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[3]/*[2]", "show-underline", + u"true"); assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[3]/*[3]", "type", + u"PortionType::Hole"); + assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[3]/*[3]", "show-underline", + u"false"); + assertXPath(pXmlDoc, "/root/page/body/txt/SwParaPortion/SwLineLayout[3]/*[4]", "type", u"PortionType::Break"); } @@ -1742,10 +1748,13 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter4, TestTdf162614) assertXPath(pXmlDoc, "//page[1]/body/tab/row/cell/tab/row/cell[2]/txt", "offset", u"0"); assertXPath(pXmlDoc, "//page[1]/body/tab/row/cell/tab/row/cell[2]/txt/infos/bounds", "height", u"276"); assertXPath(pXmlDoc, "//page[1]/body/tab/row/cell/tab/row/cell[2]/txt/infos/prtBounds", "height", u"276"); - assertXPath(pXmlDoc, "//page[1]/body/tab/row/cell/tab/row/cell[2]/txt/SwParaPortion/SwLineLayout/*", 2); + assertXPath(pXmlDoc, "//page[1]/body/tab/row/cell/tab/row/cell[2]/txt/SwParaPortion/SwLineLayout/*", 3); assertXPath(pXmlDoc, "//page[1]/body/tab/row/cell/tab/row/cell[2]/txt/SwParaPortion/SwLineLayout/*[1]", "type", u"PortionType::Text"); assertXPath(pXmlDoc, "//page[1]/body/tab/row/cell/tab/row/cell[2]/txt/SwParaPortion/SwLineLayout/*[1]", "portion", u"Table2.B1"); assertXPath(pXmlDoc, "//page[1]/body/tab/row/cell/tab/row/cell[2]/txt/SwParaPortion/SwLineLayout/*[2]", "type", u"PortionType::Hole"); + assertXPath(pXmlDoc, "//page[1]/body/tab/row/cell/tab/row/cell[2]/txt/SwParaPortion/SwLineLayout/*[2]", "show-underline", u"true"); + assertXPath(pXmlDoc, "//page[1]/body/tab/row/cell/tab/row/cell[2]/txt/SwParaPortion/SwLineLayout/*[3]", "type", u"PortionType::Hole"); + assertXPath(pXmlDoc, "//page[1]/body/tab/row/cell/tab/row/cell[2]/txt/SwParaPortion/SwLineLayout/*[3]", "show-underline", u"false"); // Two top-level tables on page 2 assertXPath(pXmlDoc, "//page[2]/body/tab", 2); diff --git a/sw/qa/extras/odfexport/odfexport4.cxx b/sw/qa/extras/odfexport/odfexport4.cxx index 91d51ef40d48..98c607cad9f6 100644 --- a/sw/qa/extras/odfexport/odfexport4.cxx +++ b/sw/qa/extras/odfexport/odfexport4.cxx @@ -1507,6 +1507,18 @@ CPPUNIT_TEST_FIXTURE(Test, testMsWordUlTrailSpace) xFactory->createInstance(u"com.sun.star.document.Settings"_ustr), uno::UNO_QUERY_THROW); CPPUNIT_ASSERT_EQUAL(uno::Any(false), xSettings->getPropertyValue(u"MsWordUlTrailSpace"_ustr)); + + // tdf#168777: Add a line with trailing spaces; the line must have 'show-underline' set, + // because Writer's default was always painting all decorations on the trailing blanks. + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->Insert("foo" + RepeatedUChar(' ', 1000)); + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "//SwLineLayout", 1); + assertXPath(pXmlDoc, "//SwLineLayout/SwLinePortion", 1); + assertXPath(pXmlDoc, "//SwLineLayout/SwHolePortion", 2); + // First hole portion is in the main text area, the second on margins and beyond + assertXPath(pXmlDoc, "//SwLineLayout/SwHolePortion[1]", "show-underline", u"true"); + assertXPath(pXmlDoc, "//SwLineLayout/SwHolePortion[2]", "show-underline", u"false"); } } diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx index 1d972f7976f8..2424ddbcc992 100644 --- a/sw/source/core/text/itrform2.cxx +++ b/sw/source/core/text/itrform2.cxx @@ -3307,9 +3307,22 @@ void SwTextFormatter::MergeCharacterBorder( SwLinePortion& rPortion, SwLinePorti } // Get next portion's font - bool bSeek = false; - if (!rInf.IsFull() && // Not the last portion of the line (in case of line break) - rInf.GetIdx() + rPortion.GetLen() != TextFrameIndex(rInf.GetText().getLength())) // Not the last portion of the paragraph + bool bSeek; + if (rPortion.GetNextPortion() && rPortion.GetNextPortion()->IsHolePortion()) + { + // Regardless of rInf.IsFull(), which may be true here, consider the next hole portion. + // If it wants to show decorations: do seek; if it doesn't: don't seek (last case allows + // to have proper right border at the margin). + bSeek = static_cast<SwHolePortion*>(rPortion.GetNextPortion())->ShowUnderline(); + } + else + { + // The border can be merged when this is not the last portion of the line (in case of line + // break), and not the last portion of the paragraph. + bSeek = !rInf.IsFull() + && rInf.GetIdx() + rPortion.GetLen() != TextFrameIndex(rInf.GetText().getLength()); + } + if (bSeek) { bSeek = Seek(rInf.GetIdx() + rPortion.GetLen()); } diff --git a/sw/source/core/text/portxt.cxx b/sw/source/core/text/portxt.cxx index a88b7f670502..9561132861ff 100644 --- a/sw/source/core/text/portxt.cxx +++ b/sw/source/core/text/portxt.cxx @@ -248,11 +248,13 @@ static void GetLimitedStringPart(const SwTextFormatInfo& rInf, TextFrameIndex nI } } -static bool IsMsWordUlTrailSpace(const SwTextFormatInfo& rInf) +static bool IsPaintTrailingSpaceDecorations(const SwTextFormatInfo& rInf) { + // We underline (and show background of) the trailing whitespace, when either Word-compatible + // trailing blanks are off, or underline Word-compatible trailing space is on. const auto& settings = rInf.GetTextFrame()->GetDoc().getIDocumentSettingAccess(); - return settings.get(DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS) - && settings.get(DocumentSettingId::MS_WORD_UL_TRAIL_SPACE); + return !settings.get(DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS) + || settings.get(DocumentSettingId::MS_WORD_UL_TRAIL_SPACE); } SwTextPortion * SwTextPortion::CopyLinePortion(const SwLinePortion &rPortion) @@ -737,17 +739,28 @@ bool SwTextPortion::Format_( SwTextFormatInfo &rInf ) SwTwips nExtraWidth(nTotalExtraWidth); SwTwips nExtraWidthOutOfLine(0); SwTwips nAvailableLineWidth(rInf.GetLineWidth() - Width()); - const bool bMsWordUlTrailSpace(IsMsWordUlTrailSpace(rInf)); - if (nExtraWidth > nAvailableLineWidth && bMsWordUlTrailSpace) + bool bPaintSpaceDecorations(IsPaintTrailingSpaceDecorations(rInf)); + if (nExtraWidth > nAvailableLineWidth && bPaintSpaceDecorations) { GetLimitedStringPart(rInf, pGuess->BreakPos(), nTotalExtraLen, GetMaxComp(rInf), nTotalExtraWidth, nAvailableLineWidth, nExtraLen, nExtraWidth); - nExtraLenOutOfLine = nTotalExtraLen - nExtraLen; - nExtraWidthOutOfLine = nTotalExtraWidth - nExtraWidth; + if (nExtraLen) + { + nExtraLenOutOfLine = nTotalExtraLen - nExtraLen; + nExtraWidthOutOfLine = nTotalExtraWidth - nExtraWidth; + } + else + { + // Not a single space fits to the available width - don't create decorated + // hole portion + nExtraLen = nTotalExtraLen; + nExtraWidth = nTotalExtraWidth; + bPaintSpaceDecorations = false; + } } - SwHolePortion* pNew = new SwHolePortion(*this, bMsWordUlTrailSpace); + SwHolePortion* pNew = new SwHolePortion(*this, bPaintSpaceDecorations); pNew->SetLen(nExtraLen); pNew->ExtraBlankWidth(nExtraWidth); Insert( pNew ); @@ -1153,13 +1166,12 @@ void SwHolePortion::Paint( const SwTextPaintInfo &rInf ) const const SwFont* pOrigFont = rInf.GetFont(); std::unique_ptr<SwFont> pHoleFont; std::optional<SwFontSave> oFontSave; - if( (!m_bShowUnderline && pOrigFont->GetUnderline() != LINESTYLE_NONE) - || pOrigFont->GetOverline() != LINESTYLE_NONE - || pOrigFont->GetStrikeout() != STRIKEOUT_NONE ) + if( !m_bShowUnderline && (pOrigFont->GetUnderline() != LINESTYLE_NONE + || pOrigFont->GetOverline() != LINESTYLE_NONE + || pOrigFont->GetStrikeout() != STRIKEOUT_NONE) ) { pHoleFont.reset(new SwFont( *pOrigFont )); - if (!m_bShowUnderline) - pHoleFont->SetUnderline(LINESTYLE_NONE); + pHoleFont->SetUnderline(LINESTYLE_NONE); pHoleFont->SetOverline( LINESTYLE_NONE ); pHoleFont->SetStrikeout( STRIKEOUT_NONE ); oFontSave.emplace( rInf, pHoleFont.get() ); @@ -1171,6 +1183,11 @@ void SwHolePortion::Paint( const SwTextPaintInfo &rInf ) const } else { + if (m_bShowUnderline) + { + rInf.DrawBackBrush(*this); + rInf.DrawBorder(*this); + } // tdf#43244: Paint spaces even at end of line, // but only if this paint is not called for pdf export, to keep that pdf export intact rInf.DrawText(*this, rInf.GetLen()); diff --git a/sw/source/core/text/portxt.hxx b/sw/source/core/text/portxt.hxx index ef7a4edcef9a..579928994ad5 100644 --- a/sw/source/core/text/portxt.hxx +++ b/sw/source/core/text/portxt.hxx @@ -78,6 +78,8 @@ public: virtual SwPositiveSize GetTextSize(const SwTextSizeInfo& rInfo) const override; virtual void Paint( const SwTextPaintInfo &rInf ) const override; + bool ShowUnderline() const { return m_bShowUnderline; } + // Accessibility: pass information about this portion to the PortionHandler virtual void HandlePortion( SwPortionHandler& rPH ) const override;
