sw/qa/extras/ooxmlexport/data/tdf170003_bottomSpacing.docx |binary sw/qa/extras/ooxmlexport/ooxmlexport19.cxx | 5 sw/qa/extras/ooxmlexport/ooxmlexport25.cxx | 9 + sw/source/writerfilter/dmapper/PropertyMap.cxx | 71 +++++++++---- sw/source/writerfilter/dmapper/PropertyMap.hxx | 2 5 files changed, 67 insertions(+), 20 deletions(-)
New commits: commit e983fc5918d399226b7aba17d0488e9720453571 Author: Justin Luth <[email protected]> AuthorDate: Mon Dec 22 20:22:11 2025 -0500 Commit: Justin Luth <[email protected]> CommitDate: Sat Dec 27 16:24:02 2025 +0100 tdf#170003 writerfilter: no belowSpacing emulation with tables This patch fixes my 25.2.6 commit 1326e09d019f05a82265f15c26288b4ffb7dc0c2 tdf#167657 writerfilter: only move sectPr bottomMargin after pageBreak It turns out that m_xPreStartingRange is a bit of a disaster. First of all, it seems to be set even if gotoPreviousParagraph fails. That can't be a good thing. Secondly (and the problem for me), it jumps over tables, so if the page break happens after a table then I was applying the below spacing to an earlier paragraph. make CppunitTest_sw_ooxmlexport25 \ CPPUNIT_TEST_NAME=testTdf170003_bottomSpacing Change-Id: I883dd397413001fafb000e1e90c0e6738b3b4c87 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/196143 Reviewed-by: Justin Luth <[email protected]> Tested-by: Jenkins (cherry picked from commit 82936569e0e17705b6de501d7151549c51520fb9) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/196246 diff --git a/sw/qa/extras/ooxmlexport/data/tdf170003_bottomSpacing.docx b/sw/qa/extras/ooxmlexport/data/tdf170003_bottomSpacing.docx new file mode 100644 index 000000000000..9f48b950122b Binary files /dev/null and b/sw/qa/extras/ooxmlexport/data/tdf170003_bottomSpacing.docx differ diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport19.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport19.cxx index 5dd58fd57536..523169f604ee 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport19.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport19.cxx @@ -1097,6 +1097,11 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf155945) // - Actual : 423 CPPUNIT_ASSERT_EQUAL(sal_Int32(0), getProperty<sal_Int32>(getParagraph(2), u"ParaBottomMargin"_ustr)); + + //tdf#170003: sectPr emulation: move sectPr bottom margin to the paragraph before the page break + // Without a fix in place, this was 0. + CPPUNIT_ASSERT_EQUAL(sal_Int32(423), + getProperty<sal_Int32>(getParagraph(1), u"ParaBottomMargin"_ustr)); } CPPUNIT_TEST_FIXTURE(Test, testTdf133560) diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx index 714329a85aa6..1dfc47a7c51c 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport25.cxx @@ -95,6 +95,15 @@ DECLARE_OOXMLEXPORT_TEST(testTdf169986_bottomSpacing, "tdf169986_bottomSpacing.d CPPUNIT_ASSERT_EQUAL(1, getPages()); } +DECLARE_OOXMLEXPORT_TEST(testTdf170003_bottomSpacing, "tdf170003_bottomSpacing.docx") +{ + // Given a document with a table before the page break, and a sectPr with a huge bottom spacing + + // This must be 200 twips / 0.35cm / 353 mm100, not sectPr's 2000 twips / 3.53 cm / 3530 mm100 + CPPUNIT_ASSERT_EQUAL(sal_Int32(353), + getProperty<sal_Int32>(getParagraph(1), u"ParaBottomMargin"_ustr)); +} + DECLARE_OOXMLEXPORT_TEST(testTdf167657_sectPr_bottomSpacing, "tdf167657_sectPr_bottomSpacing.docx") { // given with a continuous break sectPr with no belowSpacing diff --git a/sw/source/writerfilter/dmapper/PropertyMap.cxx b/sw/source/writerfilter/dmapper/PropertyMap.cxx index d04774fbab64..6d7c2d4768fa 100644 --- a/sw/source/writerfilter/dmapper/PropertyMap.cxx +++ b/sw/source/writerfilter/dmapper/PropertyMap.cxx @@ -1542,6 +1542,54 @@ void SectionPropertyMap::CreateEvenOddPageStyleCopy(DomainMapper_Impl& rDM_Impl, m_sPageStyleName = evenOddStyleName; // And use it instead of the original one (which is set as follow of this one). } +void SectionPropertyMap::EmulateSectPrBelowSpacing(DomainMapper_Impl& rDM_Impl) +{ + if (!m_xStartingRange || !m_xPreStartingRange || rDM_Impl.IsInFootOrEndnote()) + return; + + SectionPropertyMap* pPrevSection = rDM_Impl.GetLastSectionContext(); + if (!pPrevSection || !pPrevSection->GetBelowSpacing().has_value()) + return; // no consolidated spacing to emulate + + // MS Word is excessively consistent about consolidating paragraph top and bottom spacing. + // They even consolidate spacing between section breaks! + // The complication here is that the sectPr paragraph (which Writer needs to discard) + // defines the bottom spacing that exists at the end of the section. + + // Emulation: apply the previous section's belowSpacing + // to the last paragraph in that section. + // This emulation works because below spacing before a page break normally has no relevance. + + // m_xPreStartingRange may have skipped over a table as the last thing before the break! + // If so, then the below spacing can be ignored since tables don't have below spacing. + // Also, if m_xStartingRange starts with a table (which also doesn't have above spacing) + // then again the below spacing can be ignored since no consolidation is needed. + auto pCursor = dynamic_cast<SwXTextCursor*>(m_xStartingRange.get()); + if (!pCursor || pCursor->GetPaM()->GetPointNode().FindTableNode()) + return; // no emulation needed: section starts with a table (i.e. a zero top margin) + + SwPaM aPaM(pCursor->GetPaM()->GetPointNode()); // at start of section, contentIndex(0) + if (!aPaM.Move(fnMoveBackward, GoInContent)) // now at end of previous section + assert(false && "impossible: with a previous section, this must be able to move backwards"); + + if (aPaM.GetPointNode().FindTableNode()) + return; // no emulation possible: DOCX tables don't have bottom spacing + // TODO: somehow emulate on tables - but only if it is round-trippable... + + 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", "Failed to transfer below spacing to last para."); + } +} + void SectionPropertyMap::CloseSectionGroup( DomainMapper_Impl& rDM_Impl ) { SectionPropertyMap* pPrevSection = rDM_Impl.GetLastSectionContext(); @@ -1941,26 +1989,7 @@ 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() - && !rDM_Impl.IsInFootOrEndnote()) - { - 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."); - } - } + EmulateSectPrBelowSpacing(rDM_Impl); try { @@ -2176,6 +2205,8 @@ void SectionPropertyMap::SetStart( const uno::Reference< text::XTextRange >& xRa { uno::Reference<text::XParagraphCursor> const xPCursor( m_xStartingRange->getText()->createTextCursorByRange(m_xStartingRange), uno::UNO_QUERY_THROW); + // CAUTION: gotoPreviousParagraph skips over tables, + // so this range does not necessarily indicate the end of the previous section xPCursor->gotoPreviousParagraph(false); m_xPreStartingRange = xPCursor; } diff --git a/sw/source/writerfilter/dmapper/PropertyMap.hxx b/sw/source/writerfilter/dmapper/PropertyMap.hxx index ad066ecf5d6a..31ee213ccc4b 100644 --- a/sw/source/writerfilter/dmapper/PropertyMap.hxx +++ b/sw/source/writerfilter/dmapper/PropertyMap.hxx @@ -341,6 +341,8 @@ private: void CreateEvenOddPageStyleCopy(DomainMapper_Impl& rDM_Impl, PageBreakType eBreakType); + void EmulateSectPrBelowSpacing(DomainMapper_Impl& rDM_Impl); + void PrepareHeaderFooterProperties(); bool HasHeader() const;
