sw/qa/extras/ooxmlexport/data/tdf167657_sectPr_bottomSpacing.docx |binary
 sw/qa/extras/ooxmlexport/ooxmlexport25.cxx                        |   14 
+++++++
 sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx              |   18 
+++------
 sw/source/writerfilter/dmapper/PropertyMap.cxx                    |   20 
++++++++++
 sw/source/writerfilter/dmapper/PropertyMap.hxx                    |    7 +++
 5 files changed, 47 insertions(+), 12 deletions(-)

New commits:
commit 1326e09d019f05a82265f15c26288b4ffb7dc0c2
Author:     Justin Luth <justin.l...@collabora.com>
AuthorDate: Thu Jul 24 10:04:07 2025 -0400
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Mon Jul 28 09:29:51 2025 +0200

    tdf#167657 writerfilter: only move sectPr bottomMargin after pageBreak
    
    This fixes a 24.2.4 regression from
    my commit 5ff1fd13f1879105ecc0541c55f30cea32ae54f4.
    
    The problem was that I treated all sectPr's as page breaks,
    and that meant that the below spacing of the last paragraph
    before a continuous section break was being unintentionally changed.
    
    This is a bit tricky because at the time of the section break
    we have no idea WHY the new break started.
    Only MUCH LATER at the end of the entire section can we discover
    whether it was a page break instead of a continuous (or column) break.
    Thankfully someone has already calculated and stored
    "the last paragraph before this section started".
    
    A continuous section break's above and below spacing
    should have no direct effect on layout, but it does affect
    how much the next paragraph's top spacing collapses.
    
    TODO: somehow the below spacing from a continuous section break
    needs to reduce the top spacing from the following paragraph.
    At this point I can't imagine how this could reliably happen.
    
    make CppunitTest_sw_ooxmlexport25 \
        CPPUNIT_TEST_NAME=testTdf167657_sectPr_bottomSpacing
    
    Change-Id: Ifd908e2104f7e1efb86947dd88e7ea6874346d58
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/188298
    Reviewed-by: Justin Luth <jl...@mail.com>
    Tested-by: Jenkins
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/188299
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/sw/qa/extras/ooxmlexport/data/tdf167657_sectPr_bottomSpacing.docx 
b/sw/qa/extras/ooxmlexport/data/tdf167657_sectPr_bottomSpacing.docx
new file mode 100644
index 000000000000..1df416b7ac48
Binary files /dev/null and 
b/sw/qa/extras/ooxmlexport/data/tdf167657_sectPr_bottomSpacing.docx differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx 
b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
index 974517decb75..2f16a4eac671 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx
@@ -55,6 +55,20 @@ DECLARE_OOXMLEXPORT_TEST(testTdf166510_sectPr_bottomSpacing, 
"tdf166510_sectPr_b
     CPPUNIT_ASSERT_EQUAL(sal_Int32(4253), nHeight);
 }
 
+DECLARE_OOXMLEXPORT_TEST(testTdf167657_sectPr_bottomSpacing, 
"tdf167657_sectPr_bottomSpacing.docx")
+{
+    // given with a continuous break sectPr with no belowSpacing
+    CPPUNIT_ASSERT_EQUAL(1, getPages());
+
+    auto pXmlDoc = parseLayoutDump();
+
+    // Since there is no page break, the prior paragraph's belowSpacing should 
not be zero'd out.
+    // NOTE: apparently layout assigns the belowSpacing to the following 
section, not body/txt[1],
+    sal_Int32 nHeight = getXPath(pXmlDoc, "//section/infos/bounds", 
"height").toInt32();
+    // Without the fix, the section's height (showing no bottom margin for the 
1st para) was 309
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(1409), nHeight);
+}
+
 DECLARE_OOXMLEXPORT_TEST(testTdf165478_bottomAligned, 
"tdf165478_bottomAligned.docx")
 {
     // given a layoutInCell, wrap-through image, paragraph-anchored to a 
bottom-aligned cell
diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx 
b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
index 1e02d468f70d..a464e357be7d 100644
--- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
+++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
@@ -10294,21 +10294,15 @@ void DomainMapper_Impl::handleSectPrBeforeRemoval()
     if (!pSectPrContext)
         return;
 
-    // transfer below-spacing to previous paragraph - needed to layout 
above-spacing after pageBreak
-    if (!m_StreamStateStack.top().xPreviousParagraph.is())
+    SectionPropertyMap* pSectionContext = GetSectionContext();
+    if (!pSectionContext)
         return;
 
-    uno::Any aBelowSpacing = GetAnyProperty(PROP_PARA_BOTTOM_MARGIN, 
pSectPrContext);
-    if (!aBelowSpacing.hasValue())
-        aBelowSpacing <<= sal_Int32(0);
-
-    std::optional<uno::Any> oPrevBelowSpacing;
-    OUString sProp(getPropertyName(PROP_PARA_BOTTOM_MARGIN));
-    if 
(m_StreamStateStack.top().xPreviousParagraph->getPropertySetInfo()->hasPropertyByName(sProp))
-        oPrevBelowSpacing = 
m_StreamStateStack.top().xPreviousParagraph->getPropertyValue(sProp);
+    sal_Int32 nBelowSpacing = 0;
+    GetAnyProperty(PROP_PARA_BOTTOM_MARGIN, pSectPrContext) >>= nBelowSpacing;
 
-    if (!oPrevBelowSpacing.has_value() || aBelowSpacing != *oPrevBelowSpacing)
-        m_StreamStateStack.top().xPreviousParagraph->setPropertyValue(sProp, 
aBelowSpacing);
+    // store this section's unused belowSpacing so that emulation can use it 
later
+    pSectionContext->SetBelowSpacing(nBelowSpacing);
 }
 
 OUString DomainMapper_Impl::getFontNameForTheme(const Id id)
diff --git a/sw/source/writerfilter/dmapper/PropertyMap.cxx 
b/sw/source/writerfilter/dmapper/PropertyMap.cxx
index 6e6f5f5791b4..37802ac80a03 100644
--- a/sw/source/writerfilter/dmapper/PropertyMap.cxx
+++ b/sw/source/writerfilter/dmapper/PropertyMap.cxx
@@ -1941,6 +1941,26 @@ void SectionPropertyMap::CloseSectionGroup( 
DomainMapper_Impl& rDM_Impl )
         ApplyBorderToPageStyles( rDM_Impl, m_eBorderApply, m_eBorderOffsetFrom 
);
         ApplyPaperSource(rDM_Impl);
 
+        // Emulation: now apply the previous section's unused belowSpacing
+        // to the last paragraph before the section page break
+        // so that the consolidation/collapse of this section's 1st 
paragraph's aboveSpacing
+        // works correctly (since below spacing before a page break otherwise 
has no relevance).
+        if (pPrevSection && pPrevSection->GetBelowSpacing().has_value() && 
m_xPreStartingRange.is())
+        {
+            try
+            {
+                const uno::Any 
aBelowSpacingOfPrevSection(*pPrevSection->GetBelowSpacing());
+                const OUString sProp(getPropertyName(PROP_PARA_BOTTOM_MARGIN));
+                uno::Reference<beans::XPropertySet> 
xLastParaInPrevSection(m_xPreStartingRange,
+                                                                           
uno::UNO_QUERY_THROW);
+                xLastParaInPrevSection->setPropertyValue(sProp, 
aBelowSpacingOfPrevSection);
+            }
+            catch (uno::Exception&)
+            {
+                TOOLS_WARN_EXCEPTION("writerfilter", "Transfer below spacing 
to last para.");
+            }
+        }
+
         try
         {
             //now apply this break at the first paragraph of this section
diff --git a/sw/source/writerfilter/dmapper/PropertyMap.hxx 
b/sw/source/writerfilter/dmapper/PropertyMap.hxx
index 88c3097931de..3424c5143d58 100644
--- a/sw/source/writerfilter/dmapper/PropertyMap.hxx
+++ b/sw/source/writerfilter/dmapper/PropertyMap.hxx
@@ -304,6 +304,10 @@ private:
     sal_Int32                                       m_nPaperSourceFirst;
     sal_Int32                                       m_nPaperSourceOther;
 
+    // Although an empty sectPr's spacing doesn't directly affect layout,
+    // below spacing is needed to consolidate/collapse the top spacing of the 
following paragraph.
+    std::optional<sal_Int32> m_oBelowSpacing;
+
     bool                                            m_bDynamicHeightTop;
     bool                                            m_bDynamicHeightBottom;
 
@@ -415,6 +419,9 @@ public:
 
     void SetPaperSource(sal_Int32 first, sal_Int32 other) { 
m_nPaperSourceFirst = first; m_nPaperSourceOther = other;}
 
+    const std::optional<sal_Int32>& GetBelowSpacing() const { return 
m_oBelowSpacing; }
+    void SetBelowSpacing(sal_Int32 nSet) { m_oBelowSpacing = nSet; }
+
     void addRelativeWidthShape( const 
css::uno::Reference<css::drawing::XShape>& xShape ) { 
m_xRelativeWidthShapes.push_back( xShape ); }
 
     // determine which style gets the borders

Reply via email to