oox/source/export/drawingml.cxx                       |   14 +--
 sw/qa/extras/layout/data/hidden-para-before-table.rtf |    5 +
 sw/qa/extras/layout/layout3.cxx                       |   14 +++
 sw/source/core/text/redlnitr.cxx                      |   74 ++++++++++++------
 4 files changed, 76 insertions(+), 31 deletions(-)

New commits:
commit 56c029f7d880cbc605349510ea6898fdc33b24be
Author:     Mike Kaganski <[email protected]>
AuthorDate: Thu Oct 9 18:11:17 2025 +0500
Commit:     Thorsten Behrens <[email protected]>
CommitDate: Tue Oct 14 23:46:40 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]>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192179
    Tested-by: allotropia jenkins <[email protected]>
    Reviewed-by: Thorsten Behrens <[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/layout3.cxx b/sw/qa/extras/layout/layout3.cxx
index b0b80dcd41e7..c300b9a69787 100644
--- a/sw/qa/extras/layout/layout3.cxx
+++ b/sw/qa/extras/layout/layout3.cxx
@@ -4059,6 +4059,20 @@ CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, 
TestTdf152839_firstRows)
     CPPUNIT_ASSERT_LESSEQUAL(sal_Int32(230), nHeight);
 }
 
+CPPUNIT_TEST_FIXTURE(SwLayoutWriter3, 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);
+}
+
 } // end of anonymous namespace
 
 CPPUNIT_PLUGIN_IMPLEMENT();
diff --git a/sw/source/core/text/redlnitr.cxx b/sw/source/core/text/redlnitr.cxx
index 82def89f5f6a..df35d2d6836c 100644
--- a/sw/source/core/text/redlnitr.cxx
+++ b/sw/source/core/text/redlnitr.cxx
@@ -110,6 +110,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)
@@ -250,30 +261,49 @@ 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;
-                m_oParagraphBreak.reset();
-                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;
+            m_oParagraphBreak.reset();
+            return false;
         }
     }
 };
commit 345c06dffd0eb96e282c7f0ce5c915d111a76c5a
Author:     Mike Kaganski <[email protected]>
AuthorDate: Thu Oct 9 12:25:09 2025 +0500
Commit:     Thorsten Behrens <[email protected]>
CommitDate: Tue Oct 14 23:46:27 2025 +0200

    Related: tdf#80224 A blind attempt to fix conditions
    
    ... introduced in commit 43679f94b45f4d9e120c64a3fb5cc3ee77f12b11
    (Fix tdf#80224 Custom text color changed to black on .PPTX export,
    2015-08-25).
    
    Those conditions, presumably, intended to use GetProperty in the
    non-bCheckDirect case. But the implementation effectively made the
    bCheckDirect case also call GetProperty, when state check fails,
    which, in the end, has the same effect, as calling GetProperty in
    all cases.
    
    I attempted to fix it like this:
    
        bCheckDirect ? GetDirectProperty(rXPropSet, rXPropState, u"Foo"_ustr)
                     : GetProperty(rXPropSet, u"Foo"_ustr)
    
    but it failed CppunitTest_chart2_export3 then:
    
        Test name: testFormattedChartTitles::TestBody
        equality assertion failed
        - Expected: 1
        - Actual  : 0
        - In <>, XPath 
'/c:chartSpace/c:chart/c:title/c:tx/c:rich/a:p[1]/a:r[5]/a:rPr/a:uFillTx' 
number of nodes is incorrect
    
    So this just simplifies the calls to what they effectively did all
    this time.
    
    There is no failing document; I found this accidentally, reading
    the code.
    
    Change-Id: I6581c23e395a74140daa30a06cdca27b57f4e6a6
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192111
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <[email protected]>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/192178
    Tested-by: allotropia jenkins <[email protected]>
    Reviewed-by: Thorsten Behrens <[email protected]>

diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx
index cd90db98f0bd..09555e364740 100644
--- a/oox/source/export/drawingml.cxx
+++ b/oox/source/export/drawingml.cxx
@@ -2468,7 +2468,7 @@ static OUString lcl_GetTarget(const 
css::uno::Reference<css::frame::XModel>& xMo
 }
 
 void DrawingML::WriteRunProperties( const Reference< XPropertySet >& rRun, 
bool bIsField, sal_Int32 nElement,
-                                    bool bCheckDirect,bool& 
rbOverridingCharHeight, sal_Int32& rnCharHeight,
+                                    bool /*bCheckDirect*/,bool& 
rbOverridingCharHeight, sal_Int32& rnCharHeight,
                                     sal_Int16 nScriptType, const Reference< 
XPropertySet >& rXShapePropSet)
 {
     Reference< XPropertySet > rXPropSet = rRun;
@@ -2528,8 +2528,7 @@ void DrawingML::WriteRunProperties( const Reference< 
XPropertySet >& rRun, bool
                 break;
         }
 
-    if ((bCheckDirect && GetDirectProperty(rXPropSet, rXPropState, 
u"CharUnderline"_ustr))
-        || GetProperty(rXPropSet, u"CharUnderline"_ustr))
+    if (GetProperty(rXPropSet, u"CharUnderline"_ustr))
     {
         switch ( *o3tl::doAccess<sal_Int16>(mAny) )
         {
@@ -2587,8 +2586,7 @@ void DrawingML::WriteRunProperties( const Reference< 
XPropertySet >& rRun, bool
         }
     }
 
-    if ((bCheckDirect && GetDirectProperty(rXPropSet, rXPropState, 
u"CharStrikeout"_ustr))
-        || GetProperty(rXPropSet, u"CharStrikeout"_ustr))
+    if (GetProperty(rXPropSet, u"CharStrikeout"_ustr))
     {
         switch ( *o3tl::doAccess<sal_Int16>(mAny) )
         {
@@ -2698,8 +2696,7 @@ void DrawingML::WriteRunProperties( const Reference< 
XPropertySet >& rRun, bool
     else
     {
         // mso doesn't like text color to be placed after typeface
-        if ((bCheckDirect && GetDirectProperty(rXPropSet, rXPropState, 
u"CharColor"_ustr))
-            || GetProperty(rXPropSet, u"CharColor"_ustr))
+        if (GetProperty(rXPropSet, u"CharColor"_ustr))
         {
             ::Color color( ColorTransparency, 
*o3tl::doAccess<sal_uInt32>(mAny) );
             SAL_INFO("oox.shape", "run color: " << sal_uInt32(color) << " 
auto: " << sal_uInt32(COL_AUTO));
@@ -2784,8 +2781,7 @@ void DrawingML::WriteRunProperties( const Reference< 
XPropertySet >& rRun, bool
     }
 
     if (underline
-        && ((bCheckDirect && GetDirectProperty(rXPropSet, rXPropState, 
u"CharUnderlineColor"_ustr))
-            || GetProperty(rXPropSet, u"CharUnderlineColor"_ustr)))
+        && (GetProperty(rXPropSet, u"CharUnderlineColor"_ustr)))
     {
         ::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny));
         // if color is automatic, then we shouldn't write information about 
color but to take color from character

Reply via email to