sw/qa/extras/layout/data/hidden-para-before-table.rtf |    5 +
 sw/qa/extras/layout/layout5.cxx                       |   14 +++
 sw/source/core/text/redlnitr.cxx                      |   72 ++++++++++++------
 3 files changed, 70 insertions(+), 21 deletions(-)

New commits:
commit 800c79f8efcc8ba150a108104adceee41c7a8802
Author:     Mike Kaganski <[email protected]>
AuthorDate: Thu Oct 9 18:11:17 2025 +0500
Commit:     Miklos Vajna <[email protected]>
CommitDate: Fri Oct 10 09:06:56 2025 +0200

    tdf#168116: an ugly hack to handle linebreak-before-hidden-paragraph-mark
    
    Word eliminates a completely empty line ending with a hidden paragraph
    mark, which may appear when a paragraph has a line break followed by such
    a break, even when the following is not another paragraph, but a table.
    This change detects this event, and removes the linebreak instead.
    
    Known problems:
    
    1. If there is a completely hidden paragraph between the paragraph with
    trailing line break and hidden mark, this doesn't work. E.g., this RTF
    markup:
    
    AAAA\line
    {\par}
    {\par}
    \intbl BBBB    
    Still, this change allows to fix the bugdoc layout.
    
    2. When formatting marks are shown, the paragraph ends with pilcrow, not
    a linebreak symbol. This is minor.
    
    Change-Id: I66b06cdac9ab41bcccffab669f7caffe0a23b686
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192104
    Tested-by: Jenkins CollaboraOffice <[email protected]>
    Reviewed-by: Miklos Vajna <[email protected]>

diff --git a/sw/qa/extras/layout/data/hidden-para-before-table.rtf 
b/sw/qa/extras/layout/data/hidden-para-before-table.rtf
new file mode 100644
index 000000000000..1456504bec84
--- /dev/null
+++ b/sw/qa/extras/layout/data/hidden-para-before-table.rtf
@@ -0,0 +1,5 @@
+{ tf1
+AAAA\line
+{\par}
+\intbl BBBB+}
\ No newline at end of file
diff --git a/sw/qa/extras/layout/layout5.cxx b/sw/qa/extras/layout/layout5.cxx
index 3869a52b448a..5feeb4d3c80e 100644
--- a/sw/qa/extras/layout/layout5.cxx
+++ b/sw/qa/extras/layout/layout5.cxx
@@ -1980,6 +1980,20 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf166181)
     CPPUNIT_ASSERT_LESS(sal_Int32(500), height.toInt32());
 }
 
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter5, testTdf168116)
+{
+    // Given a paragraph with a line break immediately followed by a hidden 
paragraph mark:
+    createSwDoc("hidden-para-before-table.rtf");
+    auto pXmlDoc = parseLayoutDump();
+
+    assertXPath(pXmlDoc, "//body");
+    assertXPathChildren(pXmlDoc, "//body", 4);
+    // It must have a "merged" data; without the fix, this failed:
+    assertXPath(pXmlDoc, "//body/txt[1]/merged");
+    // It must only have one line; without the fix, there were two (the second 
empty):
+    assertXPath(pXmlDoc, "//body/txt[1]/SwParaPortion/SwLineLayout", 1);
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/redlnitr.cxx b/sw/source/core/text/redlnitr.cxx
index 60545116aa21..d31a45e5df8c 100644
--- a/sw/source/core/text/redlnitr.cxx
+++ b/sw/source/core/text/redlnitr.cxx
@@ -106,6 +106,17 @@ public:
     // Note: caller is responsible for checking for immediately adjacent hides
     bool Next()
     {
+        // A special case (tdf#168116 hack): the node should hide its 
paragraph break, but it was
+        // impossible; yet, the last paragraph character (
) was hidden instead.
+        if (m_oParagraphBreak
+            && m_oParagraphBreak->first.GetNodeIndex() == 
m_oParagraphBreak->second.GetNodeIndex())
+        {
+            // This is a call to Next after we returned the fake position for 
the last hidden
+            // paragraph mark. We are done.
+            m_pStartPos = nullptr;
+            m_pEndPos = nullptr;
+            return false;
+        }
         SwPosition const* pNextRedlineHide(nullptr);
         assert(m_pEndPos);
         if (m_isHideRedlines)
@@ -244,29 +255,48 @@ public:
                 }
                 return false;
             };
-            if (m_isHideParagraphBreaks
-                && m_pEndPos->GetNode().IsTextNode() // ooo27109-1.sxw
-                // only merge if next node is also text node
-                && 
m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->IsTextNode()
-                && hasHiddenItem(*m_pEndPos->GetNode().GetTextNode())
-                // no merge if there's a page break on any node
-                && 
!hasBreakBefore(*m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->GetTextNode())
-                // first node, see SwTextFrame::GetBreak()
-                && !hasBreakAfter(*m_Start.GetNode().GetTextNode()))
-            {
-                m_oParagraphBreak.emplace(
-                    SwPosition(*m_pEndPos->GetNode().GetTextNode(), 
m_pEndPos->GetNode().GetTextNode()->Len()),
-                    
SwPosition(*m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex()+1]->GetTextNode(), 
0));
-                m_pStartPos = &m_oParagraphBreak->first;
-                m_pEndPos = &m_oParagraphBreak->second;
-                return true;
-            }
-            else // nothing
+
+            if (auto pTextNode = m_pEndPos->GetNode().GetTextNode();
+                pTextNode // ooo27109-1.sxw
+                && m_isHideParagraphBreaks
+                && hasHiddenItem(*pTextNode))
             {
-                m_pStartPos = nullptr;
-                m_pEndPos = nullptr;
-                return false;
+                auto pNextNode = 
m_pEndPos->GetNodes()[m_pEndPos->GetNodeIndex() + 1];
+                if (pNextNode->IsTextNode() // only merge if next node is also 
text node
+                    // no merge if there's a page break on any node
+                    && !hasBreakBefore(*pNextNode->GetTextNode())
+                    // first node, see SwTextFrame::GetBreak()
+                    && !hasBreakAfter(*m_Start.GetNode().GetTextNode()))
+                {
+                    m_oParagraphBreak.emplace(SwPosition(*pTextNode, 
pTextNode->Len()),
+                                              
SwPosition(*pNextNode->GetTextNode(), 0));
+                    m_pStartPos = &m_oParagraphBreak->first;
+                    m_pEndPos = &m_oParagraphBreak->second;
+                    return true;
+                }
+                // A special case (tdf#168116 hack): a line break immediately 
before hidden marker,
+                // which itself cannot be hidden. Hide the preceding line 
break instead.
+                if (pTextNode->Len() > 0
+                    && pTextNode->GetText()[pTextNode->Len() - 1] == '
')
+                {
+                    SwPosition nodeEnd(*pTextNode, pTextNode->Len());
+                    // Only if we didn't include the line break in the 
previous result
+                    if (*m_pEndPos < nodeEnd)
+                    {
+                        // This fake m_oParagraphBreak can easily be 
distinguished from the proper
+                        // one created a few lined above: both positions are 
inside the same node.
+                        m_oParagraphBreak.emplace(SwPosition(*pTextNode, 
pTextNode->Len() - 1),
+                                                  nodeEnd);
+                        m_pStartPos = &m_oParagraphBreak->first;
+                        m_pEndPos = &m_oParagraphBreak->second;
+                        return true;
+                    }
+                }
             }
+
+            m_pStartPos = nullptr;
+            m_pEndPos = nullptr;
+            return false;
         }
     }
 };

Reply via email to