sw/qa/extras/rtfexport/data/tdf162342.rtf            |    7 ++
 sw/qa/extras/rtfexport/rtfexport8.cxx                |   64 +++++++++++++++++++
 sw/source/filter/ww8/attributeoutputbase.hxx         |    2 
 sw/source/filter/ww8/docxattributeoutput.cxx         |    3 
 sw/source/filter/ww8/docxattributeoutput.hxx         |    2 
 sw/source/filter/ww8/rtfattributeoutput.cxx          |    9 +-
 sw/source/filter/ww8/rtfattributeoutput.hxx          |    4 -
 sw/source/filter/ww8/ww8atr.cxx                      |    3 
 sw/source/filter/ww8/ww8attributeoutput.hxx          |    2 
 sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx |   34 ++++++++--
 10 files changed, 112 insertions(+), 18 deletions(-)

New commits:
commit 1567f48ec9fe096da386bdaf370bc95414c8d923
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Mon Jul 28 01:29:18 2025 +0500
Commit:     Mike Kaganski <mike.kagan...@collabora.com>
CommitDate: Mon Jul 28 16:35:19 2025 +0200

    tdf#162342: workaround out-of-order context stack pop
    
    Turns out, the context stack is populated in one order, but emptied in
    another. Commit e63a75e00dcd797b0bd061add3c72dcb7b353007 (tdf#158586
    writerfilter: RTF import: fix \page \sect \skbnone, 2024-02-01) added
    yet another case, which resulted in m_pTopContext being empty at the
    moment when section properties should have been applied (see "needed
    for page properties" comment in DomainMapper::sprmWithProps; that code
    wasn't reached because of (!rContext) check above).
    
    But that isn't the only case; I tried to assert in non-RTF cases, and
    it fired in `make check`, too.
    
    So here is an UGLY HACK. I repair the stack when it's popped out of
    order. This pleads for a proper fix, but the effort will be far from
    trivial.
    
    Change-Id: I1a5c646188f3da4842d103b8edeb11b801fea830
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/188449
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>

diff --git a/sw/qa/extras/rtfexport/data/tdf162342.rtf 
b/sw/qa/extras/rtfexport/data/tdf162342.rtf
new file mode 100644
index 000000000000..e04fdc6aeb96
--- /dev/null
+++ b/sw/qa/extras/rtfexport/data/tdf162342.rtf
@@ -0,0 +1,7 @@
+{ tf1
+\margl720 \margr360 \margt720 \margb720 \paperw6120 \paperh7920
+First
+\page
+Second
+\page
+Third}
\ No newline at end of file
diff --git a/sw/qa/extras/rtfexport/rtfexport8.cxx 
b/sw/qa/extras/rtfexport/rtfexport8.cxx
index fbb9acdfb000..c2aa3ef1ec2b 100644
--- a/sw/qa/extras/rtfexport/rtfexport8.cxx
+++ b/sw/qa/extras/rtfexport/rtfexport8.cxx
@@ -1211,6 +1211,70 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf167569_2)
     }
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testTdf162342)
+{
+    // Given an RTF with some page settings, and two page breaks:
+    createSwDoc("tdf162342.rtf");
+
+    {
+        xmlDocUniquePtr pLayout = parseLayoutDump();
+        assertXPath(pLayout, "//page['pass 1']", 3);
+
+        // Without the fix, this failed with
+        // - Expected: 6120
+        // - Actual  : 12240
+        // i.e., the page geometry was ignored.
+        assertXPath(pLayout, "//page[1]['pass 1']/infos/bounds", "width", 
u"6120");
+        assertXPath(pLayout, "//page[1]['pass 1']/infos/bounds", "height", 
u"7937"); // Not 7920?
+        assertXPath(pLayout, "//page[1]['pass 1']/infos/prtBounds", "left", 
u"720");
+        assertXPath(pLayout, "//page[1]['pass 1']/infos/prtBounds", "top", 
u"720");
+        assertXPath(pLayout, "//page[1]['pass 1']/infos/prtBounds", "width", 
u"5040");
+        assertXPath(pLayout, "//page[1]['pass 1']/infos/prtBounds", "height", 
u"6497");
+
+        assertXPath(pLayout, "//page[2]['pass 1']/infos/bounds", "width", 
u"6120");
+        assertXPath(pLayout, "//page[2]['pass 1']/infos/bounds", "height", 
u"7937");
+        assertXPath(pLayout, "//page[2]['pass 1']/infos/prtBounds", "left", 
u"720");
+        assertXPath(pLayout, "//page[2]['pass 1']/infos/prtBounds", "top", 
u"720");
+        assertXPath(pLayout, "//page[2]['pass 1']/infos/prtBounds", "width", 
u"5040");
+        assertXPath(pLayout, "//page[2]['pass 1']/infos/prtBounds", "height", 
u"6497");
+
+        assertXPath(pLayout, "//page[3]['pass 1']/infos/bounds", "width", 
u"6120");
+        assertXPath(pLayout, "//page[3]['pass 1']/infos/bounds", "height", 
u"7937");
+        assertXPath(pLayout, "//page[3]['pass 1']/infos/prtBounds", "left", 
u"720");
+        assertXPath(pLayout, "//page[3]['pass 1']/infos/prtBounds", "top", 
u"720");
+        assertXPath(pLayout, "//page[3]['pass 1']/infos/prtBounds", "width", 
u"5040");
+        assertXPath(pLayout, "//page[3]['pass 1']/infos/prtBounds", "height", 
u"6497");
+    }
+
+    saveAndReload(mpFilter);
+
+    {
+        xmlDocUniquePtr pLayout = parseLayoutDump();
+        assertXPath(pLayout, "//page['pass 2']", 3);
+
+        assertXPath(pLayout, "//page[1]['pass 2']/infos/bounds", "width", 
u"6120");
+        assertXPath(pLayout, "//page[1]['pass 2']/infos/bounds", "height", 
u"7937");
+        assertXPath(pLayout, "//page[1]['pass 2']/infos/prtBounds", "left", 
u"720");
+        assertXPath(pLayout, "//page[1]['pass 2']/infos/prtBounds", "top", 
u"720");
+        assertXPath(pLayout, "//page[1]['pass 2']/infos/prtBounds", "width", 
u"5040");
+        assertXPath(pLayout, "//page[1]['pass 2']/infos/prtBounds", "height", 
u"6497");
+
+        assertXPath(pLayout, "//page[2]['pass 2']/infos/bounds", "width", 
u"6120");
+        assertXPath(pLayout, "//page[2]['pass 2']/infos/bounds", "height", 
u"7937");
+        assertXPath(pLayout, "//page[2]['pass 2']/infos/prtBounds", "left", 
u"720");
+        assertXPath(pLayout, "//page[2]['pass 2']/infos/prtBounds", "top", 
u"720");
+        assertXPath(pLayout, "//page[2]['pass 2']/infos/prtBounds", "width", 
u"5040");
+        assertXPath(pLayout, "//page[2]['pass 2']/infos/prtBounds", "height", 
u"6497");
+
+        assertXPath(pLayout, "//page[3]['pass 2']/infos/bounds", "width", 
u"6120");
+        assertXPath(pLayout, "//page[3]['pass 2']/infos/bounds", "height", 
u"7937");
+        assertXPath(pLayout, "//page[3]['pass 2']/infos/prtBounds", "left", 
u"720");
+        assertXPath(pLayout, "//page[3]['pass 2']/infos/prtBounds", "top", 
u"720");
+        assertXPath(pLayout, "//page[3]['pass 2']/infos/prtBounds", "width", 
u"5040");
+        assertXPath(pLayout, "//page[3]['pass 2']/infos/prtBounds", "height", 
u"6497");
+    }
+}
+
 } // end of anonymous namespace
 CPPUNIT_PLUGIN_IMPLEMENT();
 
diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx 
b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
index 9abbeb3dac0b..4c7384697659 100644
--- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
+++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
@@ -1370,13 +1370,39 @@ void    DomainMapper_Impl::PopProperties(ContextType 
eId)
     }
 
     m_aPropertyStacks[eId].pop();
+    {
+        // !!!FIXME!!!
+        // We sometimes pop out of order. Just try to comment out this fixing 
block, keeping the
+        // assert; and run `make check`. Sigh! In that case, popping from 
m_aContextStack would
+        // make it (and m_pTopContext) out-of-sync. This must be an abort; but 
let's try to fix
+        // the stack instead, as a temporary (?) measure. I don't replace the 
stack with a more
+        // easy-to-manipulate container: I don't optimize for invalid case.
+        // !!!FIXME!!!
+        SAL_WARN_IF(m_aContextStack.top() != eId, "writerfilter.dmapper", "pop 
out of order");
+        std::stack<ContextType> tmp;
+        while (m_aContextStack.top() != eId)
+        {
+            tmp.push(m_aContextStack.top());
+            m_aContextStack.pop();
+        }
+        m_aContextStack.pop();
+        while (!tmp.empty())
+        {
+            m_aContextStack.push(tmp.top());
+            tmp.pop();
+        }
+        m_aContextStack.push(eId); // to pop below
+    }
+    assert(m_aContextStack.top() == eId);
     m_aContextStack.pop();
-    if(!m_aContextStack.empty() && 
!m_aPropertyStacks[m_aContextStack.top()].empty())
-
-            m_pTopContext = m_aPropertyStacks[m_aContextStack.top()].top();
+    if (!m_aContextStack.empty() && 
!m_aPropertyStacks[m_aContextStack.top()].empty())
+    {
+        m_pTopContext = m_aPropertyStacks[m_aContextStack.top()].top();
+    }
     else
     {
-        // OSL_ENSURE(eId == CONTEXT_SECTION, "this should happen at a section 
context end");
+        SAL_WARN_IF(eId != CONTEXT_SECTION, "writerfilter.dmapper",
+                    "this should happen at a section context end");
         m_pTopContext.clear();
     }
 }
commit d1fb9ff22f6bb55482d459e3940ffb6be3be0197
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Sun Jul 27 16:54:43 2025 +0500
Commit:     Mike Kaganski <mike.kagan...@collabora.com>
CommitDate: Mon Jul 28 16:35:11 2025 +0200

    tdf#165564: refactor the "Removes additional spaces in export" part
    
    Commit 90ce84a5dd371c804af19e97896a5e19a3ebf8ca (tdf#165564 RTF:Fix
    export and import of footnotes/endnotes, 2025-05-08) workarounded
    an extra space added after \super keyword, when EndRunProperties()
    didn't produce an output. To do that, it introduced a return value
    from EndRunProperties, checked in TextFootnote_Impl.
    
    This change simplifies this, reverting a part of the said commit,
    which added that return value, in favor of dropping an unnecessary
    space after {\super, which was retained for some reason in commit
    49877dc91b2f91baa656facd462ac1b1e832f182 (sw:rtf - timely export
    of footnote char properties, 2017-08-24). There is no need in the
    space between keywords - only between the last keyword and text.
    
    Change-Id: If13bf760feb9cb42cb39fc7de39eed6944a869ea
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/188432
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    Tested-by: Jenkins

diff --git a/sw/source/filter/ww8/attributeoutputbase.hxx 
b/sw/source/filter/ww8/attributeoutputbase.hxx
index 4472a156a72a..a05f4fe221f7 100644
--- a/sw/source/filter/ww8/attributeoutputbase.hxx
+++ b/sw/source/filter/ww8/attributeoutputbase.hxx
@@ -160,7 +160,7 @@ public:
     virtual void StartRunProperties() = 0;
 
     /// Called after we end outputting the attributes.
-    virtual bool EndRunProperties( const SwRedlineData* pRedlineData ) = 0;
+    virtual void EndRunProperties( const SwRedlineData* pRedlineData ) = 0;
 
     /// docx requires footnoteRef/endnoteRef tag at the beginning of each of 
them
     virtual bool FootnoteEndnoteRefTag() { return false; };
diff --git a/sw/source/filter/ww8/docxattributeoutput.cxx 
b/sw/source/filter/ww8/docxattributeoutput.cxx
index 6d1daf3243cb..156669c65607 100644
--- a/sw/source/filter/ww8/docxattributeoutput.cxx
+++ b/sw/source/filter/ww8/docxattributeoutput.cxx
@@ -3687,7 +3687,7 @@ void DocxAttributeOutput::WriteCollectedRunProperties()
     }
 }
 
-bool DocxAttributeOutput::EndRunProperties( const SwRedlineData* pRedlineData )
+void DocxAttributeOutput::EndRunProperties( const SwRedlineData* pRedlineData )
 {
     // Call the 'Redline' function. This will add redline (change-tracking) 
information that regards to run properties.
     // This includes changes like 'Bold', 'Underline', 'Strikethrough' etc.
@@ -3726,7 +3726,6 @@ bool DocxAttributeOutput::EndRunProperties( const 
SwRedlineData* pRedlineData )
     WritePostponedOLE();
 
     WritePostponedActiveXControl(true);
-    return false;
 }
 
 void DocxAttributeOutput::GetSdtEndBefore(const SdrObject* pSdrObj)
diff --git a/sw/source/filter/ww8/docxattributeoutput.hxx 
b/sw/source/filter/ww8/docxattributeoutput.hxx
index af72cd3315d0..5a00c9838b11 100644
--- a/sw/source/filter/ww8/docxattributeoutput.hxx
+++ b/sw/source/filter/ww8/docxattributeoutput.hxx
@@ -232,7 +232,7 @@ public:
     virtual void StartRunProperties() override;
 
     /// Called after we end outputting the attributes.
-    virtual bool EndRunProperties( const SwRedlineData* pRedlineData ) 
override;
+    virtual void EndRunProperties( const SwRedlineData* pRedlineData ) 
override;
 
     virtual bool FootnoteEndnoteRefTag() override;
 
diff --git a/sw/source/filter/ww8/rtfattributeoutput.cxx 
b/sw/source/filter/ww8/rtfattributeoutput.cxx
index 5c37c5dd3797..2af984b8fc42 100644
--- a/sw/source/filter/ww8/rtfattributeoutput.cxx
+++ b/sw/source/filter/ww8/rtfattributeoutput.cxx
@@ -475,11 +475,10 @@ void RtfAttributeOutput::StartRunProperties()
                "formatting is not empty");
 }
 
-bool RtfAttributeOutput::EndRunProperties(const SwRedlineData* 
/*pRedlineData*/)
+void RtfAttributeOutput::EndRunProperties(const SwRedlineData* 
/*pRedlineData*/)
 {
     const OString aProperties = MoveProperties(ForRun);
     m_aRun->append(aProperties);
-    return !aProperties.isEmpty();
 }
 
 // Very much like AttributeOutputBase::OutputItem
@@ -3455,9 +3454,9 @@ void RtfAttributeOutput::TextFootnote_Impl(const 
SwFormatFootnote& rFootnote)
 {
     SAL_INFO("sw.rtf", __func__ << " start");
 
-    m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_SUPER " ");
-    if (EndRunProperties(nullptr))
-        m_aRun->append(' ');
+    m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_SUPER);
+    EndRunProperties(nullptr);
+    m_aRun->append(' ');
     WriteTextFootnoteNumStr(rFootnote);
     m_aRun->append("{" OOO_STRING_SVTOOLS_RTF_IGNORE 
OOO_STRING_SVTOOLS_RTF_FOOTNOTE);
     if (rFootnote.IsEndNote() || m_rExport.m_rDoc.GetFootnoteInfo().m_ePos == 
FTNPOS_CHAPTER)
diff --git a/sw/source/filter/ww8/rtfattributeoutput.hxx 
b/sw/source/filter/ww8/rtfattributeoutput.hxx
index 3871b21547ff..289773a1477d 100644
--- a/sw/source/filter/ww8/rtfattributeoutput.hxx
+++ b/sw/source/filter/ww8/rtfattributeoutput.hxx
@@ -82,8 +82,8 @@ public:
     /// Called before we start outputting the attributes.
     void StartRunProperties() override;
 
-    /// Called after we end outputting the attributes, returns true if 
commands were added.
-    bool EndRunProperties(const SwRedlineData* pRedlineData) override;
+    /// Called after we end outputting the attributes.
+    void EndRunProperties(const SwRedlineData* pRedlineData) override;
 
     /// Output text (inside a run).
     void RunText(const OUString& rText, rtl_TextEncoding eCharSet = 
RTL_TEXTENCODING_UTF8,
diff --git a/sw/source/filter/ww8/ww8atr.cxx b/sw/source/filter/ww8/ww8atr.cxx
index 3f84782f1dc9..8807c02a4d42 100644
--- a/sw/source/filter/ww8/ww8atr.cxx
+++ b/sw/source/filter/ww8/ww8atr.cxx
@@ -1195,7 +1195,7 @@ void WW8AttributeOutput::EndRun( const SwTextNode* 
/*pNode*/, sal_Int32 nPos, sa
     }
 }
 
-bool WW8AttributeOutput::EndRunProperties( const SwRedlineData* pRedlineData )
+void WW8AttributeOutput::EndRunProperties( const SwRedlineData* pRedlineData )
 {
     Redline( pRedlineData );
 
@@ -1214,7 +1214,6 @@ bool WW8AttributeOutput::EndRunProperties( const 
SwRedlineData* pRedlineData )
                 m_rWW8Export.m_pO->size(), m_rWW8Export.m_pO->data() );
     }
     m_rWW8Export.m_pO->clear();
-    return false;
 }
 
 void WW8AttributeOutput::RunText( const OUString& rText, rtl_TextEncoding 
eCharSet, const OUString& /*rSymbolFont*/ )
diff --git a/sw/source/filter/ww8/ww8attributeoutput.hxx 
b/sw/source/filter/ww8/ww8attributeoutput.hxx
index 521aed8f8b23..113c086f7828 100644
--- a/sw/source/filter/ww8/ww8attributeoutput.hxx
+++ b/sw/source/filter/ww8/ww8attributeoutput.hxx
@@ -64,7 +64,7 @@ public:
     virtual void StartRunProperties() override;
 
     /// After we end outputting the attributes.
-    virtual bool EndRunProperties( const SwRedlineData* pRedlineData ) 
override;
+    virtual void EndRunProperties( const SwRedlineData* pRedlineData ) 
override;
 
     /// Output text.
     virtual void RunText( const OUString& rText, rtl_TextEncoding eCharSet = 
RTL_TEXTENCODING_UTF8, const OUString& rSymbolFont = OUString() ) override;

Reply via email to