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;
 

Reply via email to