sw/qa/extras/rtfexport/data/tdf158586_pageBreak1_header.rtf | 17 + sw/qa/extras/rtfexport/rtfexport8.cxx | 13 writerfilter/source/dmapper/DomainMapper.cxx | 9 writerfilter/source/dmapper/DomainMapper_Impl.cxx | 171 +++++------- writerfilter/source/dmapper/DomainMapper_Impl.hxx | 80 ++--- writerfilter/source/dmapper/SdtHelper.cxx | 4 6 files changed, 154 insertions(+), 140 deletions(-)
New commits: commit 23d9c8ea006ad62b757f2610df1ad1c73c53b624 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed Jan 31 14:47:22 2024 +0100 Commit: Caolán McNamara <caolan.mcnam...@collabora.com> CommitDate: Tue Feb 6 20:33:53 2024 +0100 writerfilter: move members to SubstreamContext writerfilter: move m_bParaHadField to SubstreamContext Change-Id: Ie15e35d304a423bfa3d7b7ead71015d5ec1228d4 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/162839 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 4913812baeabd44b46302e54b73a227e760c688a) writerfilter: use SubstreamContext for all substreams <vmiklos> possibly just nobody needed that so far. could be some more general SubstreamContext, i don't see an obvious problem reusing that at more places. Change-Id: If0749155452f65f8dfc4ac2b10f91bb8e48a6b2b Reviewed-on: https://gerrit.libreoffice.org/c/core/+/162840 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 95b01848b18283fd2f903c982108ccdb8efee022) writerfilter: move m_bFirstParagraphInCell to SubstreamContext This is a change to set it for all substreams. Change-Id: I44ed9a5485000f40f8ccfe3ec885ef8f05f5aab2 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/162841 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 30323c813977eb0127251848fecd2532dce75749) writerfilter: replace members w/ SubstreamContext::eSubstreamType This should not change any behaviour. Change-Id: Ic970f0e1b6401119d875c9e811589b9c210e0c34 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/162842 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 992f7114ab8645fb5b7a22b5f974a95fe7be7712) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/162933 Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com> diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx index 6909e3925846..31448f77b596 100644 --- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx +++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx @@ -357,10 +357,7 @@ DomainMapper_Impl::DomainMapper_Impl( m_bInStyleSheetImport( false ), m_bInNumberingImport(false), m_bInAnyTableImport( false ), - m_eInHeaderFooterImport( HeaderFooterImportState::none ), m_bDiscardHeaderFooter( false ), - m_bInFootOrEndnote(false), - m_bInFootnote(false), m_bHasFootnoteStyle(false), m_bCheckFootnoteStyle(false), m_eSkipFootnoteState(SkipFootnoteSeparator::OFF), @@ -381,7 +378,6 @@ DomainMapper_Impl::DomainMapper_Impl( m_bIsPreviousParagraphFramed( false ), m_bIsLastParaInSection( false ), m_bIsLastSectionGroup( false ), - m_bIsInComments( false ), m_bParaSectpr( false ), m_bUsingEnhancedFields( false ), m_bSdt(false), @@ -402,11 +398,7 @@ DomainMapper_Impl::DomainMapper_Impl( m_bIgnoreNextTab(false), m_bIsSplitPara(false), m_bIsActualParagraphFramed( false ), - m_bParaHadField(false), - m_bSaveParaHadField(false), m_bParaAutoBefore(false), - m_bFirstParagraphInCell(true), - m_bSaveFirstParagraphInCell(false), m_bParaWithInlineObject(false), m_bSaxError(false) { @@ -964,7 +956,7 @@ bool DomainMapper_Impl::GetIsFirstParagraphInSection( bool bAfterRedline ) const // and none of them should be considered the first para in section. return ( bAfterRedline ? m_bIsFirstParaInSectionAfterRedline : m_bIsFirstParaInSection ) && !IsInShape() - && !m_bIsInComments + && !IsInComments() && !IsInFootOrEndnote(); } @@ -2361,7 +2353,7 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con { if ( GetIsFirstParagraphInShape() || (GetIsFirstParagraphInSection() && GetSectionContext() && GetSectionContext()->IsFirstSection()) || - (m_bFirstParagraphInCell + (m_StreamStateStack.top().bFirstParagraphInCell && 0 < m_StreamStateStack.top().nTableDepth && m_StreamStateStack.top().nTableDepth == m_nTableCellDepth)) { @@ -2616,7 +2608,8 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con else { uno::Reference<text::XTextCursor> xCursor; - if (m_bParaHadField && !m_bIsInComments && !m_xTOCMarkerCursor.is()) + if (m_StreamStateStack.top().bParaHadField + && !IsInComments() && !m_xTOCMarkerCursor.is()) { // Workaround to make sure char props of the field are not lost. // Not relevant for editeng-based comments. @@ -2658,9 +2651,10 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con TOOLS_WARN_EXCEPTION("writerfilter", "DomainMapper_Impl::finishParagraph NumberingRules"); } } - else if ( m_xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("NumberingStyleName") && + else if (m_xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("NumberingStyleName") // don't update before tables - (m_StreamStateStack.top().nTableDepth == 0 || !m_bFirstParagraphInCell)) + && (m_StreamStateStack.top().nTableDepth == 0 + || !m_StreamStateStack.top().bFirstParagraphInCell)) { aCurrentNumberingName = GetListStyleName(nListId); m_xPreviousParagraph->getPropertyValue("NumberingStyleName") >>= aPreviousNumberingName; @@ -2844,7 +2838,7 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con css::uno::Reference<css::beans::XPropertySet> xParaProps(xTextRange, uno::UNO_QUERY); // table style precedence and not hidden shapes anchored to hidden empty table paragraphs - if (xParaProps && !m_bIsInComments + if (xParaProps && !IsInComments() && (0 < m_StreamStateStack.top().nTableDepth || !m_aAnchoredObjectAnchors.empty())) { @@ -2996,7 +2990,7 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con m_previousRedline.clear(); m_bParaChanged = false; - if (m_bIsInComments && pParaContext) + if (IsInComments() && pParaContext) { if (const OUString sParaId = pParaContext->props().GetParaId(); !sParaId.isEmpty()) { @@ -3019,15 +3013,15 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con } SetIsOutsideAParagraph(true); - m_bParaHadField = false; + m_StreamStateStack.top().bParaHadField = false; // don't overwrite m_bFirstParagraphInCell in table separator nodes // and in text boxes anchored to the first paragraph of table cells if (0 < m_StreamStateStack.top().nTableDepth && m_StreamStateStack.top().nTableDepth == m_nTableCellDepth - && !IsInShape() && !m_bIsInComments) + && !IsInShape() && !IsInComments()) { - m_bFirstParagraphInCell = false; + m_StreamStateStack.top().bFirstParagraphInCell = false; } m_bParaAutoBefore = false; @@ -3199,7 +3193,7 @@ void DomainMapper_Impl::applyToggleAttributes(const PropertyMapPtr& pPropertyMap { applyToggleAttributes(pPropertyMap); // If we are in comments, then disable CharGrabBag, comment text doesn't support that. - uno::Sequence< beans::PropertyValue > aValues = pPropertyMap->GetPropertyValues(/*bCharGrabBag=*/!m_bIsInComments); + uno::Sequence<beans::PropertyValue> aValues = pPropertyMap->GetPropertyValues(/*bCharGrabBag=*/!IsInComments()); if (IsInTOC() || m_bStartIndex || m_bStartBibliography) for( auto& rValue : asNonConstRange(aValues) ) @@ -3791,9 +3785,6 @@ bool isContentEmpty(uno::Reference<text::XText> const& xText, uno::Reference<tex void DomainMapper_Impl::PushPageHeaderFooter(PagePartType ePagePartType, PageType eType) { - m_bSaveParaHadField = m_bParaHadField; - m_StreamStateStack.emplace(); - bool bHeader = ePagePartType == PagePartType::Header; const PropertyIds ePropIsOn = bHeader ? PROP_HEADER_IS_ON: PROP_FOOTER_IS_ON; @@ -3803,7 +3794,7 @@ void DomainMapper_Impl::PushPageHeaderFooter(PagePartType ePagePartType, PageTyp const PropertyIds ePropTextRight = bHeader ? PROP_HEADER_TEXT: PROP_FOOTER_TEXT; m_bDiscardHeaderFooter = true; - m_eInHeaderFooterImport = bHeader ? HeaderFooterImportState::header : HeaderFooterImportState::footer; + m_StreamStateStack.top().eSubstreamType = bHeader ? SubstreamType::Header : SubstreamType::Footer; //get the section context SectionPropertyMap* pSectionContext = GetSectionContext();; @@ -3961,21 +3952,13 @@ void DomainMapper_Impl::PopPageHeaderFooter(PagePartType ePagePartType, PageType } m_bDiscardHeaderFooter = false; } - m_eInHeaderFooterImport = HeaderFooterImportState::none; - - assert(!m_StreamStateStack.empty()); - m_StreamStateStack.pop(); - - m_bParaHadField = m_bSaveParaHadField; } void DomainMapper_Impl::PushFootOrEndnote( bool bIsFootnote ) { - SAL_WARN_IF(m_bInFootOrEndnote, "writerfilter.dmapper", "PushFootOrEndnote() is called from another foot or endnote"); - m_bInFootOrEndnote = true; - m_bInFootnote = bIsFootnote; + SAL_WARN_IF(m_StreamStateStack.top().eSubstreamType != SubstreamType::Body, "writerfilter.dmapper", "PushFootOrEndnote() is called from another foot or endnote"); + m_StreamStateStack.top().eSubstreamType = bIsFootnote ? SubstreamType::Footnote : SubstreamType::Endnote; m_bCheckFirstFootnoteTab = true; - m_bSaveFirstParagraphInCell = m_bFirstParagraphInCell; try { // Redlines outside the footnote should not affect footnote content @@ -4240,7 +4223,7 @@ void DomainMapper_Impl::PushAnnotation() { try { - m_bIsInComments = true; + m_StreamStateStack.top().eSubstreamType = SubstreamType::Annotation; if (!GetTextFactory().is()) return; m_xAnnotationField.set( GetTextFactory()->createInstance( "com.sun.star.text.TextField.Annotation" ), @@ -4502,16 +4485,13 @@ void DomainMapper_Impl::PopFootOrEndnote() } m_aRedlines.pop(); m_eSkipFootnoteState = SkipFootnoteSeparator::OFF; - m_bInFootOrEndnote = m_bInFootnote = false; m_pFootnoteContext = nullptr; - m_bFirstParagraphInCell = m_bSaveFirstParagraphInCell; } void DomainMapper_Impl::PopAnnotation() { RemoveLastParagraph(); - m_bIsInComments = false; m_aTextAppendStack.pop(); try @@ -4991,7 +4971,7 @@ void DomainMapper_Impl::ClearPreviousParagraph() m_xPreviousParagraph.clear(); // next table paragraph will be first paragraph in a cell - m_bFirstParagraphInCell = true; + m_StreamStateStack.top().bFirstParagraphInCell = true; } void DomainMapper_Impl::HandleAltChunk(const OUString& rStreamName) @@ -5941,7 +5921,7 @@ uno::Reference<beans::XPropertySet> DomainMapper_Impl::FindOrCreateFieldMaster(c void DomainMapper_Impl::PushFieldContext() { - m_bParaHadField = true; + m_StreamStateStack.top().bParaHadField = true; if(m_bDiscardHeaderFooter) return; #ifdef DBG_UTIL @@ -7054,7 +7034,7 @@ void DomainMapper_Impl::handleToc m_bStartTOC = true; pContext->SetTOC(xTOC); - m_bParaHadField = false; + m_StreamStateStack.top().bParaHadField = false; if (!xTOC) return; @@ -7287,7 +7267,7 @@ void DomainMapper_Impl::handleBibliography xTOC->setPropertyValue(getPropertyName( PROP_TITLE ), uno::Any(OUString())); pContext->SetTOC( xTOC ); - m_bParaHadField = false; + m_StreamStateStack.top().bParaHadField = false; uno::Reference< text::XTextContent > xToInsert( xTOC, uno::UNO_QUERY ); appendTextContent(xToInsert, uno::Sequence< beans::PropertyValue >() ); @@ -7330,7 +7310,7 @@ void DomainMapper_Impl::handleIndex } } pContext->SetTOC( xTOC ); - m_bParaHadField = false; + m_StreamStateStack.top().bParaHadField = false; uno::Reference< text::XTextContent > xToInsert( xTOC, uno::UNO_QUERY ); appendTextContent(xToInsert, uno::Sequence< beans::PropertyValue >() ); @@ -8348,7 +8328,7 @@ void DomainMapper_Impl::CloseFieldCommand() } } else - m_bParaHadField = false; + m_StreamStateStack.top().bParaHadField = false; } } catch( const uno::Exception& ) @@ -8918,8 +8898,11 @@ void DomainMapper_Impl::StartOrEndBookmark( const OUString& rId ) // keep bookmark range, if it doesn't exceed cell boundary uno::Reference< text::XTextRange > xStart = xCursor->getStart(); xCursor->goLeft( 1, false ); - if (m_StreamStateStack.top().nTableDepth == 0 || !m_bFirstParagraphInCell) + if (m_StreamStateStack.top().nTableDepth == 0 + || !m_StreamStateStack.top().bFirstParagraphInCell) + { xCursor->gotoRange(xStart, true ); + } } uno::Reference< container::XNamed > xBkmNamed( xBookmark, uno::UNO_QUERY_THROW ); SAL_WARN_IF(aBookmarkIter->second.m_sBookmarkName.isEmpty(), "writerfilter.dmapper", "anonymous bookmark"); @@ -9757,6 +9740,8 @@ void DomainMapper_Impl::substream(Id rName, appendTableHandler(); getTableManager().startLevel(); + m_StreamStateStack.emplace(); + //import of page header/footer //Ensure that only one header/footer per section is pushed @@ -9787,8 +9772,12 @@ void DomainMapper_Impl::substream(Id rName, case NS_ooxml::LN_annotation : PushAnnotation(); break; + default: + assert(false); // unexpected? } + assert(m_StreamStateStack.top().eSubstreamType != SubstreamType::Body); + try { ref->resolve(m_rDMapper); @@ -9828,6 +9817,9 @@ void DomainMapper_Impl::substream(Id rName, break; } + assert(!m_StreamStateStack.empty()); + m_StreamStateStack.pop(); + getTableManager().endLevel(); popTableManager(); m_bHasFtn = bHasFtn; diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx b/writerfilter/source/dmapper/DomainMapper_Impl.hxx index aad946e81702..3417147a92f3 100644 --- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx +++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx @@ -146,6 +146,16 @@ enum StoredRedlines NONE }; +enum class SubstreamType +{ + Body, + Header, + Footer, + Footnote, + Endnote, + Annotation, +}; + /** * Storage for state that is relevant outside a header/footer, but not inside it. * @@ -157,6 +167,7 @@ enum StoredRedlines */ struct SubstreamContext { + SubstreamType eSubstreamType = SubstreamType::Body; bool bTextInserted = false; /** * This contains the raw table depth. nTableDepth > 0 is the same as @@ -170,6 +181,10 @@ struct SubstreamContext bool bIsColumnBreakDeferred = false; bool bIsPageBreakDeferred = false; sal_Int32 nLineBreaksDeferred = 0; + /// Current paragraph had at least one field in it. + bool bParaHadField = false; + /// Current paragraph in a table is first paragraph of a cell + bool bFirstParagraphInCell = true; }; /// Information about a paragraph to be finished after a field end. @@ -543,15 +558,7 @@ private: bool m_bInStyleSheetImport; //in import of fonts, styles, lists or lfos bool m_bInNumberingImport; //in import of numbering (i.e. numbering.xml) bool m_bInAnyTableImport; //in import of fonts, styles, lists or lfos - enum class HeaderFooterImportState - { - none, - header, - footer, - } m_eInHeaderFooterImport; bool m_bDiscardHeaderFooter; - bool m_bInFootOrEndnote; - bool m_bInFootnote; PropertyMapPtr m_pFootnoteContext; bool m_bHasFootnoteStyle; bool m_bCheckFootnoteStyle; @@ -599,7 +606,6 @@ private: bool m_bIsPreviousParagraphFramed; bool m_bIsLastParaInSection; bool m_bIsLastSectionGroup; - bool m_bIsInComments; /// If the current paragraph contains section property definitions. bool m_bParaSectpr; bool m_bUsingEnhancedFields; @@ -751,7 +757,7 @@ public: /// Getter method for m_bSdt. bool GetSdt() const { return m_bSdt;} bool GetParaChanged() const { return m_bParaChanged;} - bool GetParaHadField() const { return m_bParaHadField; } + bool GetParaHadField() const { return m_StreamStateStack.top().bParaHadField; } bool GetRemoveThisPara() const { return m_bRemoveThisParagraph; } void deferBreak( BreakType deferredBreakType ); @@ -871,7 +877,7 @@ public: css::uno::Reference<css::drawing::XShape> PopPendingShape(); void PopPageHeaderFooter(PagePartType ePagePartType, PageType eType); - bool IsInHeaderFooter() const { return m_eInHeaderFooterImport != HeaderFooterImportState::none; } + bool IsInHeaderFooter() const { auto const type(m_StreamStateStack.top().eSubstreamType); return type == SubstreamType::Header || type == SubstreamType::Footer; } void ConvertHeaderFooterToTextFrame(bool, bool); static void fillEmptyFrameProperties(std::vector<css::beans::PropertyValue>& rFrameProperties, bool bSetAnchorToChar); @@ -879,8 +885,8 @@ public: void PushFootOrEndnote( bool bIsFootnote ); void PopFootOrEndnote(); - bool IsInFootOrEndnote() const { return m_bInFootOrEndnote; } - bool IsInFootnote() const { return IsInFootOrEndnote() && m_bInFootnote; } + bool IsInFootOrEndnote() const { auto const type(m_StreamStateStack.top().eSubstreamType); return type == SubstreamType::Footnote || type == SubstreamType::Endnote; } + bool IsInFootnote() const { return m_StreamStateStack.top().eSubstreamType == SubstreamType::Footnote; } void StartCustomFootnote(const PropertyMapPtr pContext); void EndCustomFootnote(); @@ -1026,7 +1032,7 @@ public: void SetInFootnoteProperties(bool bSet) { m_bIsInFootnoteProperties = bSet;} bool IsInFootnoteProperties() const { return m_bIsInFootnoteProperties;} - bool IsInComments() const { return m_bIsInComments; }; + bool IsInComments() const { return m_StreamStateStack.top().eSubstreamType == SubstreamType::Annotation; }; std::vector<css::beans::PropertyValue> MakeFrameProperties(const ParagraphProperties& rProps); void CheckUnregisteredFrameConversion(bool bPreventOverlap = false); @@ -1207,15 +1213,9 @@ private: // Start a new index section; if needed, finish current paragraph css::uno::Reference<css::beans::XPropertySet> StartIndexSectionChecked(const OUString& sServiceName); std::vector<css::uno::Reference< css::drawing::XShape > > m_vTextFramesForChaining ; - /// Current paragraph had at least one field in it. - bool m_bParaHadField; - bool m_bSaveParaHadField; css::uno::Reference<css::beans::XPropertySet> m_xPreviousParagraph; /// Current paragraph has automatic before spacing. bool m_bParaAutoBefore; - /// Current paragraph in a table is first paragraph of a cell - bool m_bFirstParagraphInCell; - bool m_bSaveFirstParagraphInCell; /// Current paragraph had at least one inline object in it. bool m_bParaWithInlineObject; /// SAXException was seen so document will be abandoned commit 0a53b7f64dddc31b705080bb21230aae99212062 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed Jan 31 14:33:46 2024 +0100 Commit: Caolán McNamara <caolan.mcnam...@collabora.com> CommitDate: Tue Feb 6 20:33:43 2024 +0100 tdf#158586 writerfilter: RTF import: fix \page \sect \skbnone w/ header The problem was not fixed yet for the less-minimized bugzilla attachment where the sections contain headers and footers. What happened there is that first \page caused a deferred page break, then \sect and sectBreak() delayed-read the header substream and the \par in the header resets all the deferred break flags. Add the deferred break to an already existing Context class, and remove the direct members in DomainMapper_Impl in favor of always using the m_StreamStateStack. Probably this problem cannot occur for DOCX import, because it imports header/footer eagerly where the reference element is, and sectPr is before any runs that contain breaks in the same paragraph element. Change-Id: Iba971955e9cf0c398d416518e72d99307d3e1cfd Reviewed-on: https://gerrit.libreoffice.org/c/core/+/162833 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 17e2c7226a73675d69febf0915aaeae61ad8e9f1) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/162823 Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com> diff --git a/sw/qa/extras/rtfexport/data/tdf158586_pageBreak1_header.rtf b/sw/qa/extras/rtfexport/data/tdf158586_pageBreak1_header.rtf new file mode 100644 index 000000000000..cd8eefabb276 --- /dev/null +++ b/sw/qa/extras/rtfexport/data/tdf158586_pageBreak1_header.rtf @@ -0,0 +1,17 @@ +{ tf1 + +\paperw8419\paperh5953 + +\spltpgpar + +\ltrpar \sectd + +{\headerl \pard\plain \par} + +\pard\plain \wrapdefault\pvmrg\posxlbsw0bsh0\phcol \posyilbslock\dxfrtext10 + +\page \sect \sectd \sbknone + +\pard\plain Second page +\par +} diff --git a/sw/qa/extras/rtfexport/rtfexport8.cxx b/sw/qa/extras/rtfexport/rtfexport8.cxx index ebc2c3d2bf3f..167539197051 100644 --- a/sw/qa/extras/rtfexport/rtfexport8.cxx +++ b/sw/qa/extras/rtfexport/rtfexport8.cxx @@ -85,6 +85,19 @@ DECLARE_RTFEXPORT_TEST(testTdf158586_1, "tdf158586_pageBreak1.rtf") assertXPathContent(pLayout, "/root/page[2]/body//txt"_ostr, "Second page"); } +DECLARE_RTFEXPORT_TEST(testTdf158586_1header, "tdf158586_pageBreak1_header.rtf") +{ + // None of the specified text frame settings initiates a real text frame - page break not lost + CPPUNIT_ASSERT_EQUAL(2, getPages()); + CPPUNIT_ASSERT_EQUAL(2, getParagraphs()); + + // There should be no empty carriage return at the start of the second page + const auto& pLayout = parseLayoutDump(); + // on import there is a section on page 2; on reimport there is no section + // (probably not an important difference?) + assertXPathContent(pLayout, "/root/page[2]/body//txt"_ostr, "Second page"); +} + DECLARE_RTFEXPORT_TEST(testTdf158586_lostFrame, "tdf158586_lostFrame.rtf") { // The anchor and align properties are sufficient to define a frame diff --git a/writerfilter/source/dmapper/DomainMapper.cxx b/writerfilter/source/dmapper/DomainMapper.cxx index 34e3c04e35a8..b9093998a4d1 100644 --- a/writerfilter/source/dmapper/DomainMapper.cxx +++ b/writerfilter/source/dmapper/DomainMapper.cxx @@ -3258,7 +3258,8 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) * section is a table. So in case first element is a table add a dummy para * and remove it again when lcl_endSectionGroup is called */ - if(m_pImpl->m_nTableDepth == 0 && m_pImpl->GetIsFirstParagraphInSection() + if (m_pImpl->m_StreamStateStack.top().nTableDepth == 0 + && m_pImpl->GetIsFirstParagraphInSection() && !m_pImpl->GetIsDummyParaAddedForTableInSection() && !m_pImpl->GetIsTextFrameInserted() && !m_pImpl->GetIsPreviousParagraphFramed() && !IsInHeaderFooter()) { @@ -3266,7 +3267,7 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) } // if first paragraph style in table has break-before-page, transfer that setting to the table itself. - if( m_pImpl->m_nTableDepth == 0 ) + if (m_pImpl->m_StreamStateStack.top().nTableDepth == 0) { const uno::Any aBreakType(style::BreakType_PAGE_BEFORE); const PropertyMapPtr pParagraphProps = m_pImpl->GetTopContextOfType(CONTEXT_PARAGRAPH); @@ -3287,11 +3288,11 @@ void DomainMapper::sprmWithProps( Sprm& rSprm, const PropertyMapPtr& rContext ) } } - m_pImpl->m_nTableDepth++; + m_pImpl->m_StreamStateStack.top().nTableDepth++; } break; case NS_ooxml::LN_tblEnd: - m_pImpl->m_nTableDepth--; + m_pImpl->m_StreamStateStack.top().nTableDepth--; break; case NS_ooxml::LN_tcStart: m_pImpl->m_nTableCellDepth++; diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx index 5342e7a21b83..6909e3925846 100644 --- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx +++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx @@ -341,9 +341,6 @@ DomainMapper_Impl::DomainMapper_Impl( m_bSetCitation( false ), m_bSetDateValue( false ), m_bIsFirstSection( true ), - m_bIsColumnBreakDeferred( false ), - m_bIsPageBreakDeferred( false ), - m_nLineBreaksDeferred( 0 ), m_bSdtEndDeferred(false), m_bParaSdtEndDeferred(false), m_bStartTOC(false), @@ -352,7 +349,6 @@ DomainMapper_Impl::DomainMapper_Impl( m_bStartIndex(false), m_bStartBibliography(false), m_nStartGenericField(0), - m_bTextInserted(false), m_bTextDeleted(false), m_nLastRedlineMovedID(1), m_sCurrentPermId(0), @@ -399,7 +395,6 @@ DomainMapper_Impl::DomainMapper_Impl( m_bIsNewDoc(!rMediaDesc.getUnpackedValueOrDefault("InsertMode", false)), m_bIsAltChunk(rMediaDesc.getUnpackedValueOrDefault("AltChunkMode", false)), m_bIsReadGlossaries(rMediaDesc.getUnpackedValueOrDefault("ReadGlossaries", false)), - m_nTableDepth(0), m_nTableCellDepth(0), m_bHasFtn(false), m_bHasFtnSep(false), @@ -415,6 +410,7 @@ DomainMapper_Impl::DomainMapper_Impl( m_bParaWithInlineObject(false), m_bSaxError(false) { + m_StreamStateStack.emplace(); // add state for document body m_aBaseUrl = rMediaDesc.getUnpackedValueOrDefault( utl::MediaDescriptor::PROP_DOCUMENTBASEURL, OUString()); if (m_aBaseUrl.isEmpty()) { @@ -454,6 +450,7 @@ DomainMapper_Impl::DomainMapper_Impl( DomainMapper_Impl::~DomainMapper_Impl() { + assert(!m_StreamStateStack.empty()); ChainTextFrames(); // Don't remove last paragraph when pasting, sw expects that empty paragraph. if (m_bIsNewDoc) @@ -1599,21 +1596,22 @@ ListsManager::Pointer const & DomainMapper_Impl::GetListTable() void DomainMapper_Impl::deferBreak( BreakType deferredBreakType) { + assert(!m_StreamStateStack.empty()); switch (deferredBreakType) { case LINE_BREAK: - m_nLineBreaksDeferred++; + m_StreamStateStack.top().nLineBreaksDeferred++; break; case COLUMN_BREAK: - m_bIsColumnBreakDeferred = true; + m_StreamStateStack.top().bIsColumnBreakDeferred = true; break; case PAGE_BREAK: // See SwWW8ImplReader::HandlePageBreakChar(), page break should be // ignored inside tables. - if (m_nTableDepth > 0) + if (0 < m_StreamStateStack.top().nTableDepth) return; - m_bIsPageBreakDeferred = true; + m_StreamStateStack.top().bIsPageBreakDeferred = true; break; default: return; @@ -1622,14 +1620,15 @@ void DomainMapper_Impl::deferBreak( BreakType deferredBreakType) bool DomainMapper_Impl::isBreakDeferred( BreakType deferredBreakType ) { + assert(!m_StreamStateStack.empty()); switch (deferredBreakType) { case LINE_BREAK: - return m_nLineBreaksDeferred > 0; + return 0 < m_StreamStateStack.top().nLineBreaksDeferred; case COLUMN_BREAK: - return m_bIsColumnBreakDeferred; + return m_StreamStateStack.top().bIsColumnBreakDeferred; case PAGE_BREAK: - return m_bIsPageBreakDeferred; + return m_StreamStateStack.top().bIsPageBreakDeferred; default: return false; } @@ -1637,17 +1636,18 @@ bool DomainMapper_Impl::isBreakDeferred( BreakType deferredBreakType ) void DomainMapper_Impl::clearDeferredBreak(BreakType deferredBreakType) { + assert(!m_StreamStateStack.empty()); switch (deferredBreakType) { case LINE_BREAK: - assert(m_nLineBreaksDeferred > 0); - m_nLineBreaksDeferred--; + assert(0 < m_StreamStateStack.top().nLineBreaksDeferred); + m_StreamStateStack.top().nLineBreaksDeferred--; break; case COLUMN_BREAK: - m_bIsColumnBreakDeferred = false; + m_StreamStateStack.top().bIsColumnBreakDeferred = false; break; case PAGE_BREAK: - m_bIsPageBreakDeferred = false; + m_StreamStateStack.top().bIsPageBreakDeferred = false; break; default: break; @@ -1656,9 +1656,10 @@ void DomainMapper_Impl::clearDeferredBreak(BreakType deferredBreakType) void DomainMapper_Impl::clearDeferredBreaks() { - m_nLineBreaksDeferred = 0; - m_bIsColumnBreakDeferred = false; - m_bIsPageBreakDeferred = false; + assert(!m_StreamStateStack.empty()); + m_StreamStateStack.top().nLineBreaksDeferred = 0; + m_StreamStateStack.top().bIsColumnBreakDeferred = false; + m_StreamStateStack.top().bIsPageBreakDeferred = false; } void DomainMapper_Impl::setSdtEndDeferred(bool bSdtEndDeferred) @@ -2360,7 +2361,9 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con { if ( GetIsFirstParagraphInShape() || (GetIsFirstParagraphInSection() && GetSectionContext() && GetSectionContext()->IsFirstSection()) || - (m_bFirstParagraphInCell && m_nTableDepth > 0 && m_nTableDepth == m_nTableCellDepth) ) + (m_bFirstParagraphInCell + && 0 < m_StreamStateStack.top().nTableDepth + && m_StreamStateStack.top().nTableDepth == m_nTableCellDepth)) { // export requires grabbag to match top_margin, so keep them in sync if (nBeforeAutospacing && bIsAutoSet) @@ -2657,7 +2660,7 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con } else if ( m_xPreviousParagraph->getPropertySetInfo()->hasPropertyByName("NumberingStyleName") && // don't update before tables - (m_nTableDepth == 0 || !m_bFirstParagraphInCell)) + (m_StreamStateStack.top().nTableDepth == 0 || !m_bFirstParagraphInCell)) { aCurrentNumberingName = GetListStyleName(nListId); m_xPreviousParagraph->getPropertyValue("NumberingStyleName") >>= aPreviousNumberingName; @@ -2812,7 +2815,7 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con // tdf#77417 trim right white spaces in table cells in 2010 compatibility mode sal_Int32 nMode = GetSettingsTable()->GetWordCompatibilityMode(); - if ( m_nTableDepth > 0 && nMode > 0 && nMode <= 14 ) + if (0 < m_StreamStateStack.top().nTableDepth && 0 < nMode && nMode <= 14) { // skip new line xCur->goLeft(1, false); @@ -2842,7 +2845,8 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con // table style precedence and not hidden shapes anchored to hidden empty table paragraphs if (xParaProps && !m_bIsInComments - && (m_nTableDepth > 0 || !m_aAnchoredObjectAnchors.empty())) + && (0 < m_StreamStateStack.top().nTableDepth + || !m_aAnchoredObjectAnchors.empty())) { // table style has got bigger precedence than docDefault style // collect these pending paragraph properties to process in endTable() @@ -2852,7 +2856,7 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con uno::Reference<text::XTextCursor> xCur2 = xTextRange->getText()->createTextCursorByRange(xCur); uno::Reference<text::XParagraphCursor> xParaCursor(xCur2, uno::UNO_QUERY_THROW); xParaCursor->gotoStartOfParagraph(false); - if (m_nTableDepth > 0) + if (0 < m_StreamStateStack.top().nTableDepth) { TableParagraph aPending{xParaCursor, xCur, pParaContext, xParaProps}; getTableManager().getCurrentParagraphs()->push_back(aPending); @@ -3019,8 +3023,12 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con // don't overwrite m_bFirstParagraphInCell in table separator nodes // and in text boxes anchored to the first paragraph of table cells - if (m_nTableDepth > 0 && m_nTableDepth == m_nTableCellDepth && !IsInShape() && !m_bIsInComments) + if (0 < m_StreamStateStack.top().nTableDepth + && m_StreamStateStack.top().nTableDepth == m_nTableCellDepth + && !IsInShape() && !m_bIsInComments) + { m_bFirstParagraphInCell = false; + } m_bParaAutoBefore = false; m_bParaWithInlineObject = false; @@ -3262,7 +3270,7 @@ void DomainMapper_Impl::applyToggleAttributes(const PropertyMapPtr& pPropertyMap SAL_WARN_IF(!xTextRange.is(), "writerfilter.dmapper", "insertTextPortion failed"); if (!xTextRange.is()) throw uno::Exception("insertTextPortion failed", nullptr); - m_bTextInserted = true; + m_StreamStateStack.top().bTextInserted = true; xTOCTextCursor->gotoRange(xTextRange->getEnd(), true); if (m_nStartGenericField == 0) { @@ -3784,9 +3792,7 @@ bool isContentEmpty(uno::Reference<text::XText> const& xText, uno::Reference<tex void DomainMapper_Impl::PushPageHeaderFooter(PagePartType ePagePartType, PageType eType) { m_bSaveParaHadField = m_bParaHadField; - m_aHeaderFooterStack.push(HeaderFooterContext(m_bTextInserted, m_nTableDepth)); - m_bTextInserted = false; - m_nTableDepth = 0; + m_StreamStateStack.emplace(); bool bHeader = ePagePartType == PagePartType::Header; @@ -3957,12 +3963,8 @@ void DomainMapper_Impl::PopPageHeaderFooter(PagePartType ePagePartType, PageType } m_eInHeaderFooterImport = HeaderFooterImportState::none; - if (!m_aHeaderFooterStack.empty()) - { - m_bTextInserted = m_aHeaderFooterStack.top().getTextInserted(); - m_nTableDepth = m_aHeaderFooterStack.top().getTableDepth(); - m_aHeaderFooterStack.pop(); - } + assert(!m_StreamStateStack.empty()); + m_StreamStateStack.pop(); m_bParaHadField = m_bSaveParaHadField; } @@ -4121,7 +4123,7 @@ void DomainMapper_Impl::CreateRedline(uno::Reference<text::XTextRange> const& xR } // store frame and (possible floating) table redline data for restoring them after frame conversion enum StoredRedlines eType; - if (m_bIsActualParagraphFramed || m_nTableDepth > 0) + if (m_bIsActualParagraphFramed || 0 < m_StreamStateStack.top().nTableDepth) eType = StoredRedlines::FRAME; else if (IsInFootOrEndnote()) eType = IsInFootnote() ? StoredRedlines::FOOTNOTE : StoredRedlines::ENDNOTE; @@ -4977,7 +4979,7 @@ bool DomainMapper_Impl::IsDiscardHeaderFooter() const void DomainMapper_Impl::ClearPreviousParagraph() { // in table cells, set bottom auto margin of last paragraph to 0, except in paragraphs with numbering - if ((m_nTableDepth == (m_nTableCellDepth + 1)) + if ((m_StreamStateStack.top().nTableDepth == (m_nTableCellDepth + 1)) && m_xPreviousParagraph.is() && hasTableManager() && getTableManager().isCellLastParaAfterAutospacing()) { @@ -5984,18 +5986,6 @@ void DomainMapper_Impl::SetFieldLocked() m_aFieldStack.back()->SetFieldLocked(); } -HeaderFooterContext::HeaderFooterContext(bool bTextInserted, sal_Int32 nTableDepth) - : m_bTextInserted(bTextInserted) - , m_nTableDepth(nTableDepth) -{ -} - -bool HeaderFooterContext::getTextInserted() const -{ - return m_bTextInserted; -} - -sal_Int32 HeaderFooterContext::getTableDepth() const { return m_nTableDepth; } FieldContext::FieldContext(uno::Reference< text::XTextRange > xStart) : m_bFieldCommandCompleted(false) @@ -8680,7 +8670,7 @@ void DomainMapper_Impl::PopFieldContext() } m_bStartedTOC = false; m_aTextAppendStack.pop(); - m_bTextInserted = false; + m_StreamStateStack.top().bTextInserted = false; m_bParaChanged = true; // the paragraph must stay anyway } m_bStartTOC = false; @@ -8802,9 +8792,9 @@ void DomainMapper_Impl::PopFieldContext() { --m_nStartGenericField; PopFieldmark(m_aTextAppendStack, xCrsr, pContext->GetFieldId()); - if(m_bTextInserted) + if (m_StreamStateStack.top().bTextInserted) { - m_bTextInserted = false; + m_StreamStateStack.top().bTextInserted = false; } } } @@ -8884,8 +8874,10 @@ void DomainMapper_Impl::StartOrEndBookmark( const OUString& rId ) * iff the first element in the section is a table. If the dummy para is not added yet, then add it; * So bookmark is not attached to the wrong paragraph. */ - if(hasTableManager() && getTableManager().isInCell() && m_nTableDepth == 0 && GetIsFirstParagraphInSection() - && !GetIsDummyParaAddedForTableInSection() &&!GetIsTextFrameInserted()) + if (hasTableManager() && getTableManager().isInCell() + && m_StreamStateStack.top().nTableDepth == 0 + && GetIsFirstParagraphInSection() + && !GetIsDummyParaAddedForTableInSection() && !GetIsTextFrameInserted()) { AddDummyParaForTableInSection(); } @@ -8926,7 +8918,7 @@ void DomainMapper_Impl::StartOrEndBookmark( const OUString& rId ) // keep bookmark range, if it doesn't exceed cell boundary uno::Reference< text::XTextRange > xStart = xCursor->getStart(); xCursor->goLeft( 1, false ); - if (m_nTableDepth == 0 || !m_bFirstParagraphInCell) + if (m_StreamStateStack.top().nTableDepth == 0 || !m_bFirstParagraphInCell) xCursor->gotoRange(xStart, true ); } uno::Reference< container::XNamed > xBkmNamed( xBookmark, uno::UNO_QUERY_THROW ); @@ -9005,7 +8997,8 @@ void DomainMapper_Impl::startOrEndPermissionRange(sal_Int32 permissinId) * if the first element in the section is a table. If the dummy para is not added yet, then add it; * So permission is not attached to the wrong paragraph. */ - if (getTableManager().isInCell() && m_nTableDepth == 0 && GetIsFirstParagraphInSection() + if (getTableManager().isInCell() + && m_StreamStateStack.top().nTableDepth == 0 && GetIsFirstParagraphInSection() && !GetIsDummyParaAddedForTableInSection() && !GetIsTextFrameInserted()) { AddDummyParaForTableInSection(); diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx b/writerfilter/source/dmapper/DomainMapper_Impl.hxx index fbca4aa1f3f0..aad946e81702 100644 --- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx +++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx @@ -152,16 +152,24 @@ enum StoredRedlines * In case some state of DomainMapper_Impl should be reset before handling the * header/footer and should be restored once handling of header/footer is done, * then you can use this class to do so. + * + * note: presumably more state should be moved here. */ -class HeaderFooterContext +struct SubstreamContext { - bool m_bTextInserted; - sal_Int32 m_nTableDepth; - -public: - explicit HeaderFooterContext(bool bTextInserted, sal_Int32 nTableDepth); - bool getTextInserted() const; - sal_Int32 getTableDepth() const; + bool bTextInserted = false; + /** + * This contains the raw table depth. nTableDepth > 0 is the same as + * getTableManager().isInTable(), unless we're in the first paragraph of a + * table, or first paragraph after a table, as the table manager is only + * updated once we ended the paragraph (and know if the para has the + * inTbl SPRM or not). + */ + sal_Int32 nTableDepth = 0; + // deferred breaks need to be saved for RTF, probably not for DOCX + bool bIsColumnBreakDeferred = false; + bool bIsPageBreakDeferred = false; + sal_Int32 nLineBreaksDeferred = 0; }; /// Information about a paragraph to be finished after a field end. @@ -460,7 +468,9 @@ private: std::stack<TextAppendContext> m_aTextAppendStack; std::stack<AnchoredContext> m_aAnchoredStack; - std::stack<HeaderFooterContext> m_aHeaderFooterStack; +public: // DomainMapper needs it + std::stack<SubstreamContext> m_StreamStateStack; +private: std::stack<std::pair<TextAppendContext, PagePartType>> m_aHeaderFooterTextAppendStack; std::deque<FieldContextPtr> m_aFieldStack; @@ -471,9 +481,6 @@ private: bool m_bSetCitation; bool m_bSetDateValue; bool m_bIsFirstSection; - bool m_bIsColumnBreakDeferred; - bool m_bIsPageBreakDeferred; - sal_Int32 m_nLineBreaksDeferred; /// If we want to set "sdt end" on the next character context. bool m_bSdtEndDeferred; /// If we want to set "paragraph sdt end" on the next paragraph context. @@ -485,7 +492,6 @@ private: bool m_bStartIndex; bool m_bStartBibliography; unsigned int m_nStartGenericField; - bool m_bTextInserted; bool m_bTextDeleted; LineNumberSettings m_aLineNumberSettings; @@ -1104,14 +1110,6 @@ public: /// Document background color, applied to every page style. std::optional<sal_Int32> m_oBackgroundColor; - /** - * This contains the raw table depth. m_nTableDepth > 0 is the same as - * getTableManager().isInTable(), unless we're in the first paragraph of a - * table, or first paragraph after a table, as the table manager is only - * updated once we ended the paragraph (and know if the para has the - * inTbl SPRM or not). - */ - sal_Int32 m_nTableDepth; /// Raw table cell depth. sal_Int32 m_nTableCellDepth; diff --git a/writerfilter/source/dmapper/SdtHelper.cxx b/writerfilter/source/dmapper/SdtHelper.cxx index 922ac5bea106..09e7903b5f98 100644 --- a/writerfilter/source/dmapper/SdtHelper.cxx +++ b/writerfilter/source/dmapper/SdtHelper.cxx @@ -355,7 +355,7 @@ void SdtHelper::createPlainTextControl() try { bool bIsInTable = (m_rDM_Impl.hasTableManager() && m_rDM_Impl.getTableManager().isInTable()) - != (m_rDM_Impl.m_nTableDepth > 0) + != (0 < m_rDM_Impl.m_StreamStateStack.top().nTableDepth) && m_rDM_Impl.GetIsDummyParaAddedForTableInSection(); if (bIsInTable) xCrsr->goRight(1, false); @@ -459,7 +459,7 @@ void SdtHelper::createDateContentControl() // tdf#138093: Date selector reset, if placed inside table // Modified to XOR relationship and adding dummy paragraph conditions bool bIsInTable = (m_rDM_Impl.hasTableManager() && m_rDM_Impl.getTableManager().isInTable()) - != (m_rDM_Impl.m_nTableDepth > 0) + != (0 < m_rDM_Impl.m_StreamStateStack.top().nTableDepth) && m_rDM_Impl.GetIsDummyParaAddedForTableInSection(); if (bIsInTable) xCrsr->goRight(1, false);