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 | 36 ++++++++++ sw/source/writerfilter/dmapper/PropertyMap.hxx | 10 ++ 5 files changed, 65 insertions(+), 13 deletions(-)
New commits: commit 71c924f6b2ce62cc67a4917ec9fd7ef7d492297d Author: Justin Luth <justin.l...@collabora.com> AuthorDate: Thu Jul 24 10:04:07 2025 -0400 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Tue Jul 29 08:49:17 2025 +0200 tdf#167657 writerfilter: only move sectPr bottomMargin after pageBreak This 25.2 backport includes the new variable "m_xPreStartingRange" that was introduced in commit a2a88f0073bdd5119745679daa61469e87002579. This fixes a 25.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> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/188516 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 88769ef361ac..ba841ffc3641 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 fa00afddc8ee..6a3e600ae6bc 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx @@ -10186,21 +10186,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 fc3ee8ff3a38..9e341212b955 100644 --- a/sw/source/writerfilter/dmapper/PropertyMap.cxx +++ b/sw/source/writerfilter/dmapper/PropertyMap.cxx @@ -1901,6 +1901,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 @@ -2104,6 +2124,22 @@ sal_Int32 SectionPropertyMap::GetPageWidth() const return getProperty( PROP_WIDTH )->second.get<sal_Int32>(); } +//create a pre-starting-range +void SectionPropertyMap::SetStart( const uno::Reference< text::XTextRange >& xRange ) +{ + m_xStartingRange = xRange; + try + { + uno::Reference<text::XParagraphCursor> const xPCursor( + m_xStartingRange->getText()->createTextCursorByRange(m_xStartingRange), uno::UNO_QUERY_THROW); + xPCursor->gotoPreviousParagraph(false); + m_xPreStartingRange = xPCursor; + } + catch (const uno::Exception&) + { + } +} + StyleSheetPropertyMap::StyleSheetPropertyMap() : mnListLevel( -1 ) , mnOutlineLevel( -1 ) diff --git a/sw/source/writerfilter/dmapper/PropertyMap.hxx b/sw/source/writerfilter/dmapper/PropertyMap.hxx index 14e37c48b5cb..ced70328b996 100644 --- a/sw/source/writerfilter/dmapper/PropertyMap.hxx +++ b/sw/source/writerfilter/dmapper/PropertyMap.hxx @@ -256,6 +256,7 @@ private: bool m_bIsFirstSection; css::uno::Reference< css::text::XTextRange > m_xStartingRange; + css::uno::Reference< css::text::XTextRange > m_xPreStartingRange; OUString m_sPageStyleName; rtl::Reference<SwXPageStyle> m_aPageStyle; @@ -303,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; @@ -355,7 +360,7 @@ public: bool IsFirstSection() const { return m_bIsFirstSection; } - void SetStart( const css::uno::Reference< css::text::XTextRange >& xRange ) { m_xStartingRange = xRange; } + void SetStart( const css::uno::Reference< css::text::XTextRange >& xRange ); const css::uno::Reference< css::text::XTextRange >& GetStartingRange() const { return m_xStartingRange; } @@ -414,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