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