sw/qa/extras/rtfexport/data/tdf168533-pard-in-frame.rtf |   12 ++++++++
 sw/qa/extras/rtfexport/rtfexport8.cxx                   |   23 ++++++++++++++++
 sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx       |    3 ++
 sw/source/writerfilter/rtftok/rtfdocumentimpl.hxx       |    2 +
 4 files changed, 40 insertions(+)

New commits:
commit 8566b6167c5f788bf5849a461e4a52f66b6e3682
Author:     Mike Kaganski <mike.kagan...@collabora.com>
AuthorDate: Wed Sep 24 15:27:06 2025 +0500
Commit:     Thorsten Behrens <thorsten.behr...@collabora.com>
CommitDate: Thu Sep 25 11:20:10 2025 +0200

    tdf#168533: make sure to emit frame properties, when they changed
    
    ... because of popping state.
    
    In the RTF markup:
    
      \pard\pvpara\phpara\posx3500\posy0bsw3800\wraparound
      Frame 2\par
      {\pard Still frame 2}\par
    
    the frame is created first: the frame properties are collected, m_bNeedPap
    is set to true, and the frame settings are pushed in checkNeedPap, called
    from RTFDocumentImpl::text() when "Frame 2" text arrives; that resets
    m_bNeedPap. When checkNeedPap is called from \par handler code, m_bNeedPap
    is false, and it bails out.
    
    Then opening brace creates a new state on the stack; \pard resets all the
    paragraph properties to default, including frame properties; then text
    "Still frame 2" arrives, calling checkNeedPap, which doesn't send frame
    properties, because there's none at this moment; it resets m_bNeedPap.
    Then closing brace pops the state from stack, returning to the state with
    the frame properties; and \par handler in RTFDocumentImpl::dispatchSymbol
    calls checkNeedPap. But at this point, m_bNeedPap was still false, so it
    bailed out, and didn't send the frame properties - so in the end, the
    current paragraph never obtained frame properties, and got imported into
    the main text flow.
    
    This change makes sure, that whenever popping the state changes the frame
    properties, m_bNeedPap is set, to ensure emitting it when time comes.
    
    Change-Id: I61b95e2818a66f54d7026db3cbb85e92135807c9
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/191439
    Tested-by: Jenkins
    Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com>
    (cherry picked from commit 202d41e0fb2432ca286953d5f7464289fcb52e30)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/191453
    Reviewed-by: Thorsten Behrens <thorsten.behr...@collabora.com>

diff --git a/sw/qa/extras/rtfexport/data/tdf168533-pard-in-frame.rtf 
b/sw/qa/extras/rtfexport/data/tdf168533-pard-in-frame.rtf
new file mode 100644
index 000000000000..44ef618ca6fe
--- /dev/null
+++ b/sw/qa/extras/rtfexport/data/tdf168533-pard-in-frame.rtf
@@ -0,0 +1,12 @@
+{ tf1
+
+\pard\pvpara\phpara\posx3500\posy0bsw3800\wraparound
+Frame 1\par
+{\pard Outside frame 1\par}
+\pard\par
+
+\pard\pvpara\phpara\posx3500\posy0bsw3800\wraparound
+Frame 2\par
+{\pard Still frame 2}\par
+\pard\par
+}
\ No newline at end of file
diff --git a/sw/qa/extras/rtfexport/rtfexport8.cxx 
b/sw/qa/extras/rtfexport/rtfexport8.cxx
index a02e6e04886f..d267ad765f94 100644
--- a/sw/qa/extras/rtfexport/rtfexport8.cxx
+++ b/sw/qa/extras/rtfexport/rtfexport8.cxx
@@ -1305,6 +1305,29 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf168181)
                          getProperty<OUString>(getRun(getParagraph(1), 1), 
u"HyperLinkURL"_ustr));
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testTdf168533)
+{
+    // Given an RTF with frame definitions, followed by braces with \pard 
inside, and then \par
+    // inside and outside the braces:
+    createSwDoc("tdf168533-pard-in-frame.rtf");
+    CPPUNIT_ASSERT_EQUAL(2, getShapes());
+
+    // 1. {\pard Outside frame 1\par}
+    // \par sees the \pard, which reset frame properties, and so the paragraph 
is outside any frame
+    CPPUNIT_ASSERT_EQUAL(u"Frame 1"_ustr, 
getShape(1).queryThrow<text::XText>()->getString());
+    CPPUNIT_ASSERT_EQUAL(u"Outside frame 1"_ustr, 
getParagraph(1)->getString());
+
+    // 2. {\pard Still frame 2}\par
+    // \pard has reset the frame properties; but it goes out of scope with the 
closing brace, and
+    // \par sees the parent-in-stack state's frame properties, so the text is 
kept in frame
+    // Without the fix, it failed with
+    // - Expected: Frame 2
+    // Still frame 2
+    // - Actual  : Frame 2
+    CPPUNIT_ASSERT_EQUAL(u"Frame 2" SAL_NEWLINE_STRING "Still frame 2"_ustr,
+                         getShape(2).queryThrow<text::XText>()->getString());
+}
+
 } // end of anonymous namespace
 CPPUNIT_PLUGIN_IMPLEMENT();
 
diff --git a/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx 
b/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx
index c9dc19203670..6044e427ee79 100644
--- a/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx
+++ b/sw/source/writerfilter/rtftok/rtfdocumentimpl.cxx
@@ -3860,6 +3860,9 @@ RTFError RTFDocumentImpl::popState()
 
     afterPopState(aState);
 
+    if (!m_aStates.empty() && aState.getFrame() != m_aStates.top().getFrame())
+        m_bNeedPap = true;
+
     if (aState.getCurrentBuffer() == &m_aSuperBuffer)
     {
         OSL_ASSERT(!m_aStates.empty() && m_aStates.top().getCurrentBuffer() == 
nullptr);
diff --git a/sw/source/writerfilter/rtftok/rtfdocumentimpl.hxx 
b/sw/source/writerfilter/rtftok/rtfdocumentimpl.hxx
index 12e4aa64d151..931ee12df8bf 100644
--- a/sw/source/writerfilter/rtftok/rtfdocumentimpl.hxx
+++ b/sw/source/writerfilter/rtftok/rtfdocumentimpl.hxx
@@ -386,6 +386,8 @@ public:
     void setSprm(Id nId, Id nValue);
     /// If we got tokens indicating we're in a frame.
     bool hasProperties() const;
+
+    bool operator==(const RTFFrame&) const = default;
 };
 
 /// State of the parser, which gets saved / restored when changing groups.

Reply via email to