diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 09:44:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 09:44:04 +0000 |
commit | eb358d77291eba677141bab113dc27d7aabb0f3e (patch) | |
tree | 2e96f3b5d0c79beaeb536bbf05c3b8564846e65f /writerfilter | |
parent | Adding debian version 4:24.2.1-4. (diff) | |
download | libreoffice-eb358d77291eba677141bab113dc27d7aabb0f3e.tar.xz libreoffice-eb358d77291eba677141bab113dc27d7aabb0f3e.zip |
Merging upstream version 4:24.2.2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'writerfilter')
17 files changed, 314 insertions, 49 deletions
diff --git a/writerfilter/CppunitTest_writerfilter_rtftok.mk b/writerfilter/CppunitTest_writerfilter_rtftok.mk index 2c91cbb8cb..d0a1e37bc1 100644 --- a/writerfilter/CppunitTest_writerfilter_rtftok.mk +++ b/writerfilter/CppunitTest_writerfilter_rtftok.mk @@ -13,6 +13,7 @@ $(eval $(call gb_CppunitTest_CppunitTest,writerfilter_rtftok)) $(eval $(call gb_CppunitTest_use_externals,writerfilter_rtftok,\ boost_headers \ + libxml2 \ )) $(eval $(call gb_CppunitTest_add_exception_objects,writerfilter_rtftok, \ diff --git a/writerfilter/qa/cppunittests/dmapper/DomainMapperTableHandler.cxx b/writerfilter/qa/cppunittests/dmapper/DomainMapperTableHandler.cxx index b72b819139..5924fb90bc 100644 --- a/writerfilter/qa/cppunittests/dmapper/DomainMapperTableHandler.cxx +++ b/writerfilter/qa/cppunittests/dmapper/DomainMapperTableHandler.cxx @@ -7,7 +7,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include <test/unoapi_test.hxx> +#include <test/unoapixml_test.hxx> #include <com/sun/star/beans/XPropertySet.hpp> #include <com/sun/star/table/BorderLine2.hpp> @@ -19,17 +19,18 @@ #include <com/sun/star/style/BreakType.hpp> #include <com/sun/star/text/XTextViewCursorSupplier.hpp> #include <com/sun/star/text/XPageCursor.hpp> +#include <com/sun/star/qa/XDumper.hpp> using namespace ::com::sun::star; namespace { /// Tests for writerfilter/source/dmapper/DomainMapperTableHandler.cxx. -class Test : public UnoApiTest +class Test : public UnoApiXmlTest { public: Test() - : UnoApiTest("/writerfilter/qa/cppunittests/dmapper/data/") + : UnoApiXmlTest("/writerfilter/qa/cppunittests/dmapper/data/") { } }; @@ -197,6 +198,37 @@ CPPUNIT_TEST_FIXTURE(Test, testDOCXFloatingTableFootnoteRedline) uno::Reference<drawing::XDrawPage> xDrawPage = xModel->getDrawPage(); CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0), xDrawPage->getCount()); } + +CPPUNIT_TEST_FIXTURE(Test, testDOCXFloatingTableHeaderBodyOverlap) +{ + // Given a document with a floating table in a header, the floating table extends the header + // frame: + // When importing that document: + loadFromFile(u"floattable-header-overlap.docx"); + + // Then make sure the fly bottom is less than the top of the body text: + uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY); + css::uno::Reference<qa::XDumper> xDumper(xModel->getCurrentController(), uno::UNO_QUERY); + OString aDump = xDumper->dump("layout").toUtf8(); + auto pCharBuffer = reinterpret_cast<const xmlChar*>(aDump.getStr()); + xmlDocUniquePtr pXmlDoc(xmlParseDoc(pCharBuffer)); + sal_Int32 nFlyBottom = getXPath(pXmlDoc, "//fly/infos/bounds"_ostr, "bottom"_ostr).toInt32(); + // Body text top is body top + height of the first line, that's just a fly portion (kind of a + // top margin). + sal_Int32 nBodyTop + = getXPath(pXmlDoc, "//page[1]/body/txt[1]/infos/bounds"_ostr, "top"_ostr).toInt32(); + // Without the accompanying fix in place, this test would have failed, the first line was not a + // fly portion but it was actual text, above the floating table. + assertXPath(pXmlDoc, "//page[1]/body/txt[1]/SwParaPortion/SwLineLayout[1]/child::*"_ostr, + "type"_ostr, "PortionType::Fly"); + sal_Int32 nBodyFlyPortionHeight + = getXPath(pXmlDoc, "//page[1]/body/txt[1]/SwParaPortion/SwLineLayout[1]"_ostr, + "height"_ostr) + .toInt32(); + sal_Int32 nBodyTextTop = nBodyTop + nBodyFlyPortionHeight; + // Fly bottom was 3063, body text top was 7148. + CPPUNIT_ASSERT_LESS(nBodyTextTop, nFlyBottom); +} } CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/writerfilter/qa/cppunittests/dmapper/DomainMapper_Impl.cxx b/writerfilter/qa/cppunittests/dmapper/DomainMapper_Impl.cxx index d0693746f2..16aa5cbfb2 100644 --- a/writerfilter/qa/cppunittests/dmapper/DomainMapper_Impl.cxx +++ b/writerfilter/qa/cppunittests/dmapper/DomainMapper_Impl.cxx @@ -366,6 +366,51 @@ CPPUNIT_TEST_FIXTURE(Test, testFloattableSectend) // i.e. the first table was lost. CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), xTables->getCount()); } + +CPPUNIT_TEST_FIXTURE(Test, testRedlinedShapeThenSdt) +{ + // Given a file with a second paragraph where text is followed by a redline, then an SDT: + // When importing that document: + loadFromFile(u"redlined-shape-sdt.docx"); + + // Then make sure the content control doesn't start at para start: + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<container::XEnumerationAccess> xParaEnumAccess(xTextDocument->getText(), + uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xParaEnum = xParaEnumAccess->createEnumeration(); + xParaEnum->nextElement(); + uno::Reference<container::XEnumerationAccess> xPortionEnumAccess(xParaEnum->nextElement(), + uno::UNO_QUERY); + uno::Reference<container::XEnumeration> xPortionEnum = xPortionEnumAccess->createEnumeration(); + + uno::Reference<beans::XPropertySet> xPortion(xPortionEnum->nextElement(), uno::UNO_QUERY); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: Text + // - Actual : ContentControl + // i.e. the content control started at para start. + CPPUNIT_ASSERT_EQUAL(u"Text"_ustr, + xPortion->getPropertyValue("TextPortionType").get<OUString>()); + xPortion.set(xPortionEnum->nextElement(), uno::UNO_QUERY); + // Redline start+end pair, containing a pair of text portions with an anchored object in the + // middle. + CPPUNIT_ASSERT_EQUAL(u"Redline"_ustr, + xPortion->getPropertyValue("TextPortionType").get<OUString>()); + xPortion.set(xPortionEnum->nextElement(), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(u"Text"_ustr, + xPortion->getPropertyValue("TextPortionType").get<OUString>()); + xPortion.set(xPortionEnum->nextElement(), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(u"Frame"_ustr, + xPortion->getPropertyValue("TextPortionType").get<OUString>()); + xPortion.set(xPortionEnum->nextElement(), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(u"Text"_ustr, + xPortion->getPropertyValue("TextPortionType").get<OUString>()); + xPortion.set(xPortionEnum->nextElement(), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(u"Redline"_ustr, + xPortion->getPropertyValue("TextPortionType").get<OUString>()); + xPortion.set(xPortionEnum->nextElement(), uno::UNO_QUERY); + CPPUNIT_ASSERT_EQUAL(u"ContentControl"_ustr, + xPortion->getPropertyValue("TextPortionType").get<OUString>()); +} } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/qa/cppunittests/dmapper/data/floattable-header-overlap.docx b/writerfilter/qa/cppunittests/dmapper/data/floattable-header-overlap.docx Binary files differnew file mode 100644 index 0000000000..1230b9f4e0 --- /dev/null +++ b/writerfilter/qa/cppunittests/dmapper/data/floattable-header-overlap.docx diff --git a/writerfilter/qa/cppunittests/dmapper/data/redlined-shape-sdt.docx b/writerfilter/qa/cppunittests/dmapper/data/redlined-shape-sdt.docx Binary files differnew file mode 100644 index 0000000000..ea7f4a5bd6 --- /dev/null +++ b/writerfilter/qa/cppunittests/dmapper/data/redlined-shape-sdt.docx diff --git a/writerfilter/qa/cppunittests/rtftok/data/floattable-then-sect-break.rtf b/writerfilter/qa/cppunittests/rtftok/data/floattable-then-sect-break.rtf new file mode 100644 index 0000000000..7ad179608c --- /dev/null +++ b/writerfilter/qa/cppunittests/rtftok/data/floattable-then-sect-break.rtf @@ -0,0 +1,12 @@ +{\rtf1 +\paperw11907\paperh16840\margl896\margr2104\margt1440\margb720 +\pard\plain doc start\par +\trowd\tpvpara\tphmrg\tposnegy-922\tdfrmtxtLeft180\tdfrmtxtRight180\cellx5670 +\pard\intbl A1\cell +\row +\trowd\tpvpara\tphmrg\tposnegy-922\tdfrmtxtLeft180\tdfrmtxtRight180\cellx5670 +\pard\intbl A2\cell +\row +\pard\sect +\pard\plain doc end\par +} diff --git a/writerfilter/qa/cppunittests/rtftok/rtfdispatchsymbol.cxx b/writerfilter/qa/cppunittests/rtftok/rtfdispatchsymbol.cxx index 8317e50824..bf3ecc937e 100644 --- a/writerfilter/qa/cppunittests/rtftok/rtfdispatchsymbol.cxx +++ b/writerfilter/qa/cppunittests/rtftok/rtfdispatchsymbol.cxx @@ -7,22 +7,23 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include <test/unoapi_test.hxx> +#include <test/unoapixml_test.hxx> #include <com/sun/star/text/XTextDocument.hpp> #include <com/sun/star/beans/XPropertySet.hpp> #include <com/sun/star/style/ParagraphAdjust.hpp> +#include <com/sun/star/qa/XDumper.hpp> using namespace ::com::sun::star; namespace { /// Tests for writerfilter/source/rtftok/rtfdispatchsymbol.cxx. -class Test : public UnoApiTest +class Test : public UnoApiXmlTest { public: Test() - : UnoApiTest("/writerfilter/qa/cppunittests/rtftok/data/") + : UnoApiXmlTest("/writerfilter/qa/cppunittests/rtftok/data/") { } }; @@ -64,6 +65,25 @@ CPPUNIT_TEST_FIXTURE(Test, testCenterAfterPage) // i.e. the paragraph alignment on the second page was lost. CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int16>(style::ParagraphAdjust_CENTER), eActual); } + +CPPUNIT_TEST_FIXTURE(Test, testFloattableThenSectBreak) +{ + // Given a document with a floating table, immediately followed by \sect: + // When loading that file: + loadFromFile(u"floattable-then-sect-break.rtf"); + + // Then make sure that the floating table is on the first page: + uno::Reference<frame::XModel> xModel(mxComponent, uno::UNO_QUERY); + css::uno::Reference<qa::XDumper> xDumper(xModel->getCurrentController(), uno::UNO_QUERY); + OString aDump = xDumper->dump("layout").toUtf8(); + auto pCharBuffer = reinterpret_cast<const xmlChar*>(aDump.getStr()); + xmlDocUniquePtr pXmlDoc(xmlParseDoc(pCharBuffer)); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 1 + // - Actual : 0 + // i.e. the floating table was on the 2nd page, not on the 1st page. + assertXPath(pXmlDoc, "/root/page[1]/sorted_objs/fly"_ostr, 1); +} } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx index e63d1cf961..0ecc670155 100644 --- a/writerfilter/source/dmapper/DomainMapperTableHandler.cxx +++ b/writerfilter/source/dmapper/DomainMapperTableHandler.cxx @@ -39,6 +39,7 @@ #include <com/sun/star/text/WritingMode2.hpp> #include <com/sun/star/text/XTextField.hpp> #include <com/sun/star/text/XTextRangeCompare.hpp> +#include <com/sun/star/text/RelOrientation.hpp> #include <com/sun/star/beans/XPropertySet.hpp> #include <com/sun/star/beans/XPropertyState.hpp> #include <com/sun/star/container/XEnumeration.hpp> @@ -1585,8 +1586,25 @@ void DomainMapperTableHandler::endTable(unsigned int nestedTableLevel) // Floating tables inside a table always stay inside the cell. // Also extend the header/footer area if needed, so an in-header floating table // typically doesn't overlap with body test. + bool bIsFollowingTextFlow = true; + + sal_Int16 nVertOrientRelation{}; + auto it = std::find_if(aFrameProperties.begin(), aFrameProperties.end(), + [](const beans::PropertyValue& rPropertyValue) -> bool + { return rPropertyValue.Name == "VertOrientRelation"; }); + if (it != aFrameProperties.end()) + { + it->Value >>= nVertOrientRelation; + if (nVertOrientRelation == text::RelOrientation::PAGE_FRAME) + { + // If vertical relation is page, follow-text-flow is not useful and causes + // unwanted wrap of body text around in-header floating table, so avoid it. + bIsFollowingTextFlow = false; + } + } + aFrameProperties.push_back( - comphelper::makePropertyValue("IsFollowingTextFlow", true)); + comphelper::makePropertyValue("IsFollowingTextFlow", bIsFollowingTextFlow)); } // A text frame created for floating tables is always allowed to split. diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx index 58568a9105..05f3fae675 100644 --- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx +++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx @@ -1003,6 +1003,10 @@ void DomainMapper_Impl::PushSdt() return; } + // This may delete text, so call it early, before we would set our start position, which may be + // invalidated by a delete. + MergeAtContentImageRedlineWithNext(xTextAppend); + uno::Reference<text::XText> xText = xTextAppend->getText(); if (!xText.is()) { @@ -1033,7 +1037,41 @@ void DomainMapper_Impl::PopSdt() uno::Reference<text::XTextRange> xStart = aPosition.m_xTextRange; uno::Reference<text::XTextRange> xEnd = GetTopTextAppend()->getEnd(); uno::Reference<text::XText> xText = xEnd->getText(); - uno::Reference<text::XTextCursor> xCursor = xText->createTextCursorByRange(xStart); + + uno::Reference<text::XTextCursor> xCursor; + try + { + xCursor = xText->createTextCursorByRange(xStart); + } + catch (const uno::RuntimeException&) + { + TOOLS_WARN_EXCEPTION("writerfilter", "DomainMapper_Impl::DomainMapper_Impl::PopSdt: createTextCursorByRange() failed"); + // We redline form controls and that gets us confused when + // we process the SDT around the placeholder. What seems to + // happen is we lose the text-range when we pop the SDT position. + // Here, we reset the text-range when we fail to create the + // cursor from the top SDT position. + if (m_aTextAppendStack.empty()) + { + return; + } + + uno::Reference<text::XTextAppend> xTextAppend = m_aTextAppendStack.top().xTextAppend; + if (!xTextAppend.is()) + { + return; + } + + uno::Reference<text::XText> xText2 = xTextAppend->getText(); + if (!xText2.is()) + { + return; + } + + // Reset to the start. + xCursor = xText2->createTextCursorByRange(xTextAppend->getStart()); + } + if (!xCursor) { SAL_WARN("writerfilter.dmapper", "DomainMapper_Impl::PopSdt: no start position"); @@ -2553,6 +2591,7 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con lcl_AddRange(pToBeSavedProperties, xTextAppend, rAppendContext); } } + applyToggleAttributes(pPropertyMap); // for paragraph marker formatting std::vector<beans::PropertyValue> aProperties; if (pPropertyMap) { @@ -3026,6 +3065,7 @@ void DomainMapper_Impl::finishParagraph( const PropertyMapPtr& pPropertyMap, con } +// TODO this does not yet take table styles into account void DomainMapper_Impl::applyToggleAttributes(const PropertyMapPtr& pPropertyMap) { std::optional<PropertyMap::Property> charStyleProperty = pPropertyMap->getProperty(PROP_CHAR_STYLE_NAME); @@ -3166,6 +3206,42 @@ void DomainMapper_Impl::applyToggleAttributes(const PropertyMapPtr& pPropertyMap } } +void DomainMapper_Impl::MergeAtContentImageRedlineWithNext(const css::uno::Reference<css::text::XTextAppend>& xTextAppend) +{ + // remove workaround for change tracked images, if they are part of a redline, + // i.e. if the next run is a tracked change with the same type, author and date, + // as in the change tracking of the image. + if ( m_bRedlineImageInPreviousRun ) + { + auto pCurrentRedline = m_aRedlines.top().size() > 0 + ? m_aRedlines.top().back() + : GetTopContextOfType(CONTEXT_CHARACTER) && + GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().size() > 0 + ? GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().back() + : nullptr; + if ( m_previousRedline && pCurrentRedline && + // same redline + (m_previousRedline->m_nToken & 0xffff) == (pCurrentRedline->m_nToken & 0xffff) && + m_previousRedline->m_sAuthor == pCurrentRedline->m_sAuthor && + m_previousRedline->m_sDate == pCurrentRedline->m_sDate ) + { + uno::Reference< text::XTextCursor > xCursor = xTextAppend->getEnd()->getText( )->createTextCursor( ); + assert(xCursor.is()); + xCursor->gotoEnd(false); + xCursor->goLeft(2, true); + if ( xCursor->getString() == u"" ) + { + xCursor->goRight(1, true); + xCursor->setString(""); + xCursor->gotoEnd(false); + xCursor->goLeft(1, true); + xCursor->setString(""); + } + } + + m_bRedlineImageInPreviousRun = false; + } +} void DomainMapper_Impl::appendTextPortion( const OUString& rString, const PropertyMapPtr& pPropertyMap ) { @@ -3195,39 +3271,7 @@ void DomainMapper_Impl::applyToggleAttributes(const PropertyMapPtr& pPropertyMap rValue.Value <<= false; } - // remove workaround for change tracked images, if they are part of a redline, - // i.e. if the next run is a tracked change with the same type, author and date, - // as in the change tracking of the image. - if ( m_bRedlineImageInPreviousRun ) - { - auto pCurrentRedline = m_aRedlines.top().size() > 0 - ? m_aRedlines.top().back() - : GetTopContextOfType(CONTEXT_CHARACTER) && - GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().size() > 0 - ? GetTopContextOfType(CONTEXT_CHARACTER)->Redlines().back() - : nullptr; - if ( m_previousRedline && pCurrentRedline && - // same redline - (m_previousRedline->m_nToken & 0xffff) == (pCurrentRedline->m_nToken & 0xffff) && - m_previousRedline->m_sAuthor == pCurrentRedline->m_sAuthor && - m_previousRedline->m_sDate == pCurrentRedline->m_sDate ) - { - uno::Reference< text::XTextCursor > xCursor = xTextAppend->getEnd()->getText( )->createTextCursor( ); - assert(xCursor.is()); - xCursor->gotoEnd(false); - xCursor->goLeft(2, true); - if ( xCursor->getString() == u"" ) - { - xCursor->goRight(1, true); - xCursor->setString(""); - xCursor->gotoEnd(false); - xCursor->goLeft(1, true); - xCursor->setString(""); - } - } - - m_bRedlineImageInPreviousRun = false; - } + MergeAtContentImageRedlineWithNext(xTextAppend); uno::Reference< text::XTextRange > xTextRange; if (m_aTextAppendStack.top().xInsertPosition.is()) @@ -3880,7 +3924,12 @@ void DomainMapper_Impl::prepareHeaderFooterContent(uno::Reference<beans::XProper m_aHeaderFooterTextAppendStack.push(std::make_pair(TextAppendContext(xTextAppend, xTextCursor), ePagePartType)); } -/** Checks if the header and footer content on the text appennd stack is empty. +bool DomainMapper_Impl::SeenHeaderFooter(PagePartType const partType, PageType const pageType) const +{ + return m_HeaderFooterSeen.find({partType, pageType}) != m_HeaderFooterSeen.end(); +} + +/** Checks if the header and footer content on the text appended stack is empty. */ void DomainMapper_Impl::checkIfHeaderFooterIsEmpty(PagePartType ePagePartType, PageType eType) { @@ -3938,7 +3987,10 @@ void DomainMapper_Impl::PopPageHeaderFooter(PagePartType ePagePartType, PageType // content is not copied from the previous section SectionPropertyMap* pSectionContext = GetSectionContext(); if (pSectionContext) + { pSectionContext->clearHeaderFooterLinkToPrevious(ePagePartType, eType); + m_HeaderFooterSeen.emplace(ePagePartType, eType); + } if (!m_aTextAppendStack.empty()) { diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.hxx b/writerfilter/source/dmapper/DomainMapper_Impl.hxx index 082eda8fc5..8bd51d0add 100644 --- a/writerfilter/source/dmapper/DomainMapper_Impl.hxx +++ b/writerfilter/source/dmapper/DomainMapper_Impl.hxx @@ -638,6 +638,8 @@ private: css::uno::Reference< css::text::XTextCursor > m_xTOCMarkerCursor; + ::std::set<::std::pair<PagePartType, PageType>> m_HeaderFooterSeen; + //annotation import css::uno::Reference< css::beans::XPropertySet > m_xAnnotationField; sal_Int32 m_nAnnotationId; @@ -793,6 +795,7 @@ public: void finishParagraph( const PropertyMapPtr& pPropertyMap, const bool bRemove = false, const bool bNoNumbering = false); void applyToggleAttributes( const PropertyMapPtr& pPropertyMap ); + void MergeAtContentImageRedlineWithNext(const css::uno::Reference<css::text::XTextAppend>& xTextAppend); void appendTextPortion( const OUString& rString, const PropertyMapPtr& pPropertyMap ); void appendTextContent(const css::uno::Reference<css::text::XTextContent>&, const css::uno::Sequence<css::beans::PropertyValue>&); void appendOLE( const OUString& rStreamName, const std::shared_ptr<OLEHandler>& pOleHandler ); @@ -1213,6 +1216,8 @@ public: bool IsParaWithInlineObject() const { return m_StreamStateStack.top().bParaWithInlineObject; } + bool SeenHeaderFooter(PagePartType, PageType) const; + css::uno::Reference< css::embed::XStorage > m_xDocumentStorage; /// Handles <w:altChunk>. diff --git a/writerfilter/source/dmapper/GraphicImport.cxx b/writerfilter/source/dmapper/GraphicImport.cxx index eebd1b8228..dc2a7935eb 100644 --- a/writerfilter/source/dmapper/GraphicImport.cxx +++ b/writerfilter/source/dmapper/GraphicImport.cxx @@ -270,6 +270,7 @@ public: std::optional<sal_Int32> m_oEffectExtentTop; std::optional<sal_Int32> m_oEffectExtentRight; std::optional<sal_Int32> m_oEffectExtentBottom; + std::optional<text::GraphicCrop> m_oCrop; GraphicImport_Impl(GraphicImportType & rImportType, DomainMapper& rDMapper, std::pair<OUString, OUString>& rPositionOffsets, @@ -795,6 +796,9 @@ void GraphicImport::lcl_attribute(Id nName, Value& rValue) case NS_ooxml::LN_CT_WrapSquare_wrapText: //90928; handleWrapTextValue(rValue.getInt()); break; + case NS_ooxml::LN_CT_BlipFillProperties_srcRect: + m_pImpl->m_oCrop.emplace(rValue.getAny().get<text::GraphicCrop>()); + break; case NS_ooxml::LN_shape: { uno::Reference< drawing::XShape> xShape; @@ -857,7 +861,13 @@ void GraphicImport::lcl_attribute(Id nName, Value& rValue) text::GraphicCrop aGraphicCrop( 0, 0, 0, 0 ); uno::Reference< beans::XPropertySet > xSourceGraphProps( xShape, uno::UNO_QUERY ); uno::Any aAny = xSourceGraphProps->getPropertyValue("GraphicCrop"); - if(aAny >>= aGraphicCrop) { + if (m_pImpl->m_oCrop) + { // RTF: RTFValue from resolvePict() + xGraphProps->setPropertyValue("GraphicCrop", + uno::Any(*m_pImpl->m_oCrop)); + } + else if (aAny >>= aGraphicCrop) + { // DOCX: imported in oox BlipFillContext xGraphProps->setPropertyValue("GraphicCrop", uno::Any( aGraphicCrop ) ); } diff --git a/writerfilter/source/dmapper/PropertyMap.cxx b/writerfilter/source/dmapper/PropertyMap.cxx index 9c3e031e40..8f7a301986 100644 --- a/writerfilter/source/dmapper/PropertyMap.cxx +++ b/writerfilter/source/dmapper/PropertyMap.cxx @@ -514,7 +514,7 @@ void SectionPropertyMap::removeXTextContent(uno::Reference<text::XText> const& r xParagraph->dispose(); } -/** Set the header/footer sharing as defined by titlePage and eveoAndOdd flags +/** Set the header/footer sharing as defined by titlePage and evenAndOdd flags * in the document and clear the content of anything not written during the import. */ void SectionPropertyMap::setHeaderFooterProperties(DomainMapper_Impl& rDM_Impl) @@ -914,7 +914,9 @@ void copyHeaderFooterTextProperty(const uno::Reference<beans::XPropertySet>& xSo } // Copies all the header and footer content and relevant flags from the source style to the target. -void completeCopyHeaderFooter(const uno::Reference<beans::XPropertySet>& xSourceStyle, const uno::Reference<beans::XPropertySet>& xTargetStyle) +void completeCopyHeaderFooter(const uno::Reference<beans::XPropertySet>& xSourceStyle, + const uno::Reference<beans::XPropertySet>& xTargetStyle, + bool const bMissingHeader, bool const bMissingFooter) { if (!xSourceStyle.is() || !xTargetStyle.is()) return; @@ -960,6 +962,25 @@ void completeCopyHeaderFooter(const uno::Reference<beans::XPropertySet>& xSource copyHeaderFooterTextProperty(xSourceStyle, xTargetStyle, PROP_FOOTER_TEXT_LEFT); copyHeaderFooterTextProperty(xSourceStyle, xTargetStyle, PROP_FOOTER_TEXT); } + // tdf#153196 the copy is used for the first page, the source will be used + // on subsequent pages, so clear source's first page header/footer + if (!bSourceFirstIsShared) + { + xSourceStyle->setPropertyValue(sFirstIsShared, uno::Any(true)); + } + // if there is *only* a first-footer, and no previous section from which + // to inherit a footer, the import process has created an empty footer + // that didn't exist in the file; remove it + if (bSourceHasHeader && bMissingHeader) + { + xSourceStyle->setPropertyValue(sHeaderIsOn, uno::Any(false)); + } + if (bSourceHasFooter && bMissingFooter) + { + // setting "FooterIsShared" to true here does nothing, because it causes + // left footer to be stashed, which means it will be exported anyway + xSourceStyle->setPropertyValue(sFooterIsOn, uno::Any(false)); + } } // Copy headers and footers from the previous page style. @@ -1429,7 +1450,14 @@ void SectionPropertyMap::CreateEvenOddPageStyleCopy(DomainMapper_Impl& rDM_Impl, rDM_Impl.GetPageStyles()->insertByName(evenOddStyleName, uno::Any(evenOddStyle)); if (rDM_Impl.IsNewDoc()) - completeCopyHeaderFooter(pageProperties, evenOddStyle); + { + bool const bEvenAndOdd(rDM_Impl.GetSettingsTable()->GetEvenAndOddHeaders()); + completeCopyHeaderFooter(pageProperties, evenOddStyle, + !rDM_Impl.SeenHeaderFooter(PagePartType::Header, PageType::RIGHT) + && (!bEvenAndOdd || !rDM_Impl.SeenHeaderFooter(PagePartType::Header, PageType::LEFT)), + !rDM_Impl.SeenHeaderFooter(PagePartType::Footer, PageType::RIGHT) + && (!bEvenAndOdd || !rDM_Impl.SeenHeaderFooter(PagePartType::Footer, PageType::LEFT))); + } if (eBreakType == PageBreakType::Even) evenOddStyle->setPropertyValue(getPropertyName(PROP_PAGE_STYLE_LAYOUT), uno::Any(style::PageStyleLayout_LEFT)); diff --git a/writerfilter/source/rtftok/rtfdispatchsymbol.cxx b/writerfilter/source/rtftok/rtfdispatchsymbol.cxx index 0f01d79f5c..b40fd55dde 100644 --- a/writerfilter/source/rtftok/rtfdispatchsymbol.cxx +++ b/writerfilter/source/rtftok/rtfdispatchsymbol.cxx @@ -140,10 +140,21 @@ RTFError RTFDocumentImpl::dispatchSymbol(RTFKeyword nKeyword) } else { - if (m_bNeedCr) + bool bPendingFloatingTable = false; + RTFValue::Pointer_t pTblpPr + = m_aStates.top().getTableRowSprms().find(NS_ooxml::LN_CT_TblPrBase_tblpPr); + if (pTblpPr) + { + // We have a pending floating table, provide an anchor for it still in this + // section. + bPendingFloatingTable = true; + } + + if (m_bNeedCr || bPendingFloatingTable) { // tdf#158586 don't dispatch \par here, it eats deferred page breaks setNeedPar(true); } + sectBreak(); if (m_nResetBreakOnSectBreak != RTFKeyword::invalid) { @@ -371,7 +382,8 @@ RTFError RTFDocumentImpl::dispatchSymbol(RTFKeyword nKeyword) = m_aStates.top().getSectionSprms().find(NS_ooxml::LN_EG_SectPrContents_titlePg); if (((pBreak && pBreak->getInt() - == static_cast<sal_Int32>(NS_ooxml::LN_Value_ST_SectionMark_continuous)) + == static_cast<sal_Int32>(NS_ooxml::LN_Value_ST_SectionMark_continuous) + && m_bHadSect) // tdf#158983 before first \sect, ignore \sbknone! || m_nResetBreakOnSectBreak == RTFKeyword::SBKNONE) && !(pTitlePg && pTitlePg->getInt())) { diff --git a/writerfilter/source/rtftok/rtfdocumentimpl.cxx b/writerfilter/source/rtftok/rtfdocumentimpl.cxx index e82674930b..f6165736f3 100644 --- a/writerfilter/source/rtftok/rtfdocumentimpl.cxx +++ b/writerfilter/source/rtftok/rtfdocumentimpl.cxx @@ -1069,6 +1069,16 @@ void RTFDocumentImpl::resolvePict(bool const bInline, uno::Reference<drawing::XS RTFSprms aAttributes; // shape attribute RTFSprms aPicAttributes; + if (m_aStates.top().getPicture().nCropT != 0 || m_aStates.top().getPicture().nCropB != 0 + || m_aStates.top().getPicture().nCropL != 0 || m_aStates.top().getPicture().nCropR != 0) + { + text::GraphicCrop const crop{ m_aStates.top().getPicture().nCropT, + m_aStates.top().getPicture().nCropB, + m_aStates.top().getPicture().nCropL, + m_aStates.top().getPicture().nCropR }; + auto const pCrop = new RTFValue(crop); + aPicAttributes.set(NS_ooxml::LN_CT_BlipFillProperties_srcRect, pCrop); + } auto pShapeValue = new RTFValue(xShape); aPicAttributes.set(NS_ooxml::LN_shape, pShapeValue); // pic sprm @@ -2967,7 +2977,11 @@ RTFError RTFDocumentImpl::beforePopState(RTFParserState& rState) case Destination::SHAPE: m_bNeedFinalPar = true; m_bNeedCr = m_bNeedCrOrig; - if (rState.getFrame().hasProperties()) + // tdf#47036 insert paragraph break for graphic object inside text + // frame at start of document - TODO: the object may actually be + // anchored inside the text frame and this ends up putting the + // anchor in the body, but better than losing the shape... + if (rState.getFrame().hasProperties() && m_pSdrImport->isTextGraphicObject()) { // parBreak() modifies m_aStates.top() so we can't apply resetFrame() directly on aState resetFrame(); diff --git a/writerfilter/source/rtftok/rtfsdrimport.hxx b/writerfilter/source/rtftok/rtfsdrimport.hxx index 16f7f9c319..b06803bd0f 100644 --- a/writerfilter/source/rtftok/rtfsdrimport.hxx +++ b/writerfilter/source/rtftok/rtfsdrimport.hxx @@ -77,6 +77,7 @@ public: void popParent(); css::uno::Reference<css::drawing::XShape> const& getCurrentShape() const { return m_xShape; } bool isFakePict() const { return m_bFakePict; } + bool isTextGraphicObject() const { return m_bTextGraphicObject; } private: void createShape(const OUString& rService, css::uno::Reference<css::drawing::XShape>& xShape, diff --git a/writerfilter/source/rtftok/rtfvalue.cxx b/writerfilter/source/rtftok/rtfvalue.cxx index 7a9137f5db..6654a3f2ec 100644 --- a/writerfilter/source/rtftok/rtfvalue.cxx +++ b/writerfilter/source/rtftok/rtfvalue.cxx @@ -85,6 +85,11 @@ RTFValue::RTFValue(const RTFPicture& rPicture) { } +RTFValue::RTFValue(text::GraphicCrop const& rCrop) + : m_oCrop(rCrop) +{ +} + RTFValue::~RTFValue() = default; int RTFValue::getInt() const { return m_nValue; } @@ -110,6 +115,10 @@ uno::Any RTFValue::getAny() const ret <<= m_xStream; else if (m_xObject.is()) ret <<= m_xObject; + else if (m_oCrop) + { + ret <<= *m_oCrop; + } else ret <<= static_cast<sal_Int32>(m_nValue); return ret; diff --git a/writerfilter/source/rtftok/rtfvalue.hxx b/writerfilter/source/rtftok/rtfvalue.hxx index 4f37a5dcbc..6c87c61a1d 100644 --- a/writerfilter/source/rtftok/rtfvalue.hxx +++ b/writerfilter/source/rtftok/rtfvalue.hxx @@ -11,6 +11,10 @@ #include <dmapper/resourcemodel.hxx> +#include <com/sun/star/text/GraphicCrop.hpp> + +#include <optional> + namespace com::sun::star { namespace embed @@ -49,6 +53,7 @@ public: explicit RTFValue(css::uno::Reference<css::embed::XEmbeddedObject> xObject); explicit RTFValue(const RTFShape& aShape); explicit RTFValue(const RTFPicture& rPicture); + explicit RTFValue(css::text::GraphicCrop const& rCrop); ~RTFValue() override; void setString(const OUString& sValue); int getInt() const override; @@ -79,6 +84,7 @@ private: bool m_bForceString = false; mutable tools::SvRef<RTFShape> m_pShape; mutable tools::SvRef<RTFPicture> m_pPicture; + ::std::optional<css::text::GraphicCrop> m_oCrop; }; } // namespace writerfilter::rtftok |