sw/inc/cmdid.h | 1 sw/inc/unoprnms.hxx | 1 sw/qa/core/unocore/unocore.cxx | 42 ++++++++++++++++ sw/source/core/unocore/unomap.cxx | 1 sw/source/core/unocore/unotext.cxx | 47 +++++++++++++++++++ sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx | 37 +++----------- 6 files changed, 100 insertions(+), 29 deletions(-)
New commits: commit cfd3b14fd4e1ee889dd356523e5cdaa639786d37 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Fri Aug 2 08:23:45 2024 +0200 Commit: Miklos Vajna <vmik...@collabora.com> CommitDate: Fri Aug 2 10:37:31 2024 +0200 tdf#162295 DOCX import: optimize the check if the header/footer is empty The original DOCX bugdoc has some 813 shapes and 150 sections, which means that isContentEmpty() in the domain mapper is a significant cost. We don't have layout at import time, so checking if a shape has its anchor in this header/footer is still expensive, but using internal sw API makes the 2 other checks constant and the shape check is faster this way. - old cost: 16638 ms - new cost: 11910 ms (72% of baseline) Change-Id: I22f75ef82b0e2f618e425e076377eea3640b83cc Reviewed-on: https://gerrit.libreoffice.org/c/core/+/171390 Reviewed-by: Miklos Vajna <vmik...@collabora.com> Tested-by: Jenkins diff --git a/sw/inc/cmdid.h b/sw/inc/cmdid.h index bd36f0bdd3b3..1e2cc3d10991 100644 --- a/sw/inc/cmdid.h +++ b/sw/inc/cmdid.h @@ -678,6 +678,7 @@ class SwUINumRuleItem; #define FN_UNO_GRAPHIC_PREVIEW (FN_EXTRA2 + 130) #define FN_UNO_LINEBREAK (FN_EXTRA2 + 131) #define FN_UNO_CONTENT_CONTROL (FN_EXTRA2 + 132) +#define FN_UNO_IS_CONTENT_EMPTY (FN_EXTRA2 + 133) // Area: Help // Region: Traveling & Selection diff --git a/sw/inc/unoprnms.hxx b/sw/inc/unoprnms.hxx index 7ed25b929247..e3d7e073c24d 100644 --- a/sw/inc/unoprnms.hxx +++ b/sw/inc/unoprnms.hxx @@ -939,6 +939,7 @@ inline constexpr OUString UNO_NAME_DATE_STRING = u"DateString"_ustr; inline constexpr OUString UNO_NAME_PARA_ID = u"ParaId"_ustr; inline constexpr OUString UNO_NAME_PARA_ID_PARENT = u"ParaIdParent"_ustr; inline constexpr OUString UNO_NAME_CONTENT_CONTROL_TYPE = u"ContentControlType"_ustr; +inline constexpr OUString UNO_NAME_IS_CONTENT_EMPTY = u"IsContentEmpty"_ustr; #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/qa/core/unocore/unocore.cxx b/sw/qa/core/unocore/unocore.cxx index 83f8605f3920..0d60108cb2f3 100644 --- a/sw/qa/core/unocore/unocore.cxx +++ b/sw/qa/core/unocore/unocore.cxx @@ -32,6 +32,7 @@ #include <textcontentcontrol.hxx> #include <frmmgr.hxx> #include <fmtcntnt.hxx> +#include <strings.hrc> using namespace ::com::sun::star; @@ -1085,6 +1086,47 @@ CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testWrapTextAtFlyStart) CPPUNIT_ASSERT(bWrapTextAtFlyStart); } +CPPUNIT_TEST_FIXTURE(SwCoreUnocoreTest, testEmptyHeader) +{ + // Empty header: IsContentEmpty is true. + createSwDoc(); + uno::Sequence<beans::PropertyValue> aInsertArgs + = { comphelper::makePropertyValue(u"PageStyle"_ustr, SwResId(STR_POOLPAGE_STANDARD)) }; + dispatchCommand(mxComponent, u".uno:InsertPageHeader"_ustr, aInsertArgs); + uno::Reference<beans::XPropertySet> xPageStyle(getStyles("PageStyles")->getByName("Standard"), + uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xHeaderText(xPageStyle->getPropertyValue("HeaderText"), + uno::UNO_QUERY); + bool bIsContentEmpty = false; + xHeaderText->getPropertyValue("IsContentEmpty") >>= bIsContentEmpty; + CPPUNIT_ASSERT(bIsContentEmpty); + + // Header has 1 paragraph with text: IsContentEmpty is false. + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->Insert("text"); + bIsContentEmpty = true; + xHeaderText->getPropertyValue("IsContentEmpty") >>= bIsContentEmpty; + CPPUNIT_ASSERT(!bIsContentEmpty); + + // Header has 2 paragraphs: IsContentEmpty is false. + pWrtShell->SelAll(); + pWrtShell->DelRight(); + pWrtShell->SplitNode(); + bIsContentEmpty = true; + xHeaderText->getPropertyValue("IsContentEmpty") >>= bIsContentEmpty; + CPPUNIT_ASSERT(!bIsContentEmpty); + + // Header has an anchoed object: IsContentEmpty is false. + pWrtShell->SelAll(); + pWrtShell->DelRight(); + SwFlyFrameAttrMgr aMgr(true, pWrtShell, Frmmgr_Type::TEXT, nullptr); + RndStdIds eAnchor = RndStdIds::FLY_AT_PARA; + aMgr.InsertFlyFrame(eAnchor, aMgr.GetPos(), aMgr.GetSize()); + bIsContentEmpty = true; + xHeaderText->getPropertyValue("IsContentEmpty") >>= bIsContentEmpty; + CPPUNIT_ASSERT(!bIsContentEmpty); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/unocore/unomap.cxx b/sw/source/core/unocore/unomap.cxx index 55e1632c57e3..977028ceb9de 100644 --- a/sw/source/core/unocore/unomap.cxx +++ b/sw/source/core/unocore/unomap.cxx @@ -1369,6 +1369,7 @@ std::span<const SfxItemPropertyMapEntry> SwUnoPropertyMapProvider::GetPropertyMa static SfxItemPropertyMapEntry const aTextMap[] = { REDLINE_NODE_PROPERTIES + { UNO_NAME_IS_CONTENT_EMPTY, FN_UNO_IS_CONTENT_EMPTY, cppu::UnoType<bool>::get(), PropertyAttribute::MAYBEVOID|PropertyAttribute::READONLY, 0 }, }; m_aMapEntriesArr[nPropertyId] = aTextMap; } diff --git a/sw/source/core/unocore/unotext.cxx b/sw/source/core/unocore/unotext.cxx index e569b4c40f18..de9789faf3eb 100644 --- a/sw/source/core/unocore/unotext.cxx +++ b/sw/source/core/unocore/unotext.cxx @@ -1032,6 +1032,53 @@ SwXText::getPropertyValue( } } break; + case FN_UNO_IS_CONTENT_EMPTY: + { + const SwStartNode* pStartNode = GetStartNode(); + SwNodeOffset nStartIndex = pStartNode->GetIndex(); + SwNodeOffset nEndIndex = pStartNode->EndOfSectionIndex(); + if (nEndIndex - nStartIndex > SwNodeOffset(2)) + { + // More than 1 node between the start and end one: not empty. + aRet <<= false; + return aRet; + } + + if (nEndIndex - nStartIndex == SwNodeOffset(2)) + { + SwPaM aPaM(*pStartNode); + aPaM.Move(fnMoveForward, GoInNode); + SwTextNode* pTextNode = aPaM.Start()->GetNode().GetTextNode(); + if (pTextNode && !pTextNode->GetText().isEmpty()) + { + // 1 node, but that text node has text: not empty. + aRet <<= false; + return aRet; + } + } + + sw::FrameFormats<sw::SpzFrameFormat*>& rFormats = *GetDoc()->GetSpzFrameFormats(); + for(sw::SpzFrameFormat* pFormat: rFormats) + { + const SwFormatAnchor& rAnchor = pFormat->GetAnchor(); + const SwNode* pAnchorNode = rAnchor.GetAnchorNode(); + if (!pAnchorNode) + { + continue; + } + + SwNodeOffset nAnchorIndex = pAnchorNode->GetIndex(); + if (nAnchorIndex > nStartIndex && nAnchorIndex < nEndIndex) + { + // This fly or draw format has an anchor in the node section: not empty. + aRet <<= false; + return aRet; + } + } + // Otherwise empty. + aRet <<= true; + } + break; } return aRet; } diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx index 3a4307228a8f..344dbe261341 100644 --- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx +++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx @@ -3791,41 +3791,20 @@ void DomainMapper_Impl::ConvertHeaderFooterToTextFrame(bool bDynamicHeightTop, b namespace { // Determines if the XText content is empty (no text, no shapes, no tables) -bool isContentEmpty(uno::Reference<text::XText> const& xText, uno::Reference<text::XTextDocument> const& xTextDocument) +bool isContentEmpty(uno::Reference<text::XText> const& xText) { if (!xText.is()) return true; // no XText means it's empty - uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(xTextDocument, uno::UNO_QUERY); - auto xDrawPage = xDrawPageSupplier->getDrawPage(); - if (xDrawPage && xDrawPage->hasElements()) + uno::Reference<beans::XPropertySet> xTextProperties(xText, uno::UNO_QUERY); + if (!xTextProperties.is()) { - for (sal_Int32 i = 0, nCount = xDrawPage->getCount(); i < nCount; ++i) - { - uno::Reference<text::XTextContent> xShape(xDrawPage->getByIndex(i), uno::UNO_QUERY); - if (xShape.is()) - { - uno::Reference<text::XTextRange> xAnchor = xShape->getAnchor(); - if (xAnchor.is() && xAnchor->getText() == xText) - return false; - } - } + return true; } - uno::Reference<container::XEnumerationAccess> xEnumAccess(xText->getText(), uno::UNO_QUERY); - uno::Reference<container::XEnumeration> xEnum = xEnumAccess->createEnumeration(); - while (xEnum->hasMoreElements()) - { - auto xObject = xEnum->nextElement(); - uno::Reference<text::XTextTable> const xTextTable(xObject, uno::UNO_QUERY); - if (xTextTable.is()) - return false; - - uno::Reference<text::XTextRange> const xParagraph(xObject, uno::UNO_QUERY); - if (xParagraph.is() && !xParagraph->getString().isEmpty()) - return false; - } - return true; + bool bContentEmpty{}; + xTextProperties->getPropertyValue("IsContentEmpty") >>= bContentEmpty; + return bContentEmpty; } } // end anonymous namespace @@ -3969,7 +3948,7 @@ void DomainMapper_Impl::checkIfHeaderFooterIsEmpty(PagePartType ePagePartType, P if (!xPageStyle.is()) return; - bool bEmpty = isContentEmpty(m_aTextAppendStack.top().xTextAppend, GetTextDocument()); + bool bEmpty = isContentEmpty(m_aTextAppendStack.top().xTextAppend); if (eType == PageType::FIRST && bEmpty) {