tdf#158556: provide objects anchored to node as a hidden property

This introduces a hidden property of SwXParagraph, named
OOXMLImport_AnchoredShapes.

Testing on my system, starting the main process first, then launching
another one like

  time soffice path/to/bugdoc.docx

... so that it only measures time spent in import, gave the following
figures:

LibreOffice 7.5.0.3 (TDF build):
real    1m49.016s
user    0m0.000s
sys     0m0.000s

LibreOffice 7.6.0.3 (TDF build):
real    8m37.386s
user    0m0.000s
sys     0m0.000s

Current master (my no-debug build):
real    10m6.776s
user    0m0.000s
sys     0m0.000s

Current master with this patch (my no-debug build):
real    5m41.524s
user    0m0.000s
sys     0m0.015s

Indeed, it is not as fast as it used to be; and the fix doesn't really
remove the quadratic complexity, just uses faster iteration. If there
is a way to directly list objects anchored to a given paragraph, rather
than iterating over all objects checking their anchors, that would get
much faster, but that would be a rather large change.

Change-Id: Ie50515815e85fdce498d065185199c9b31d95794
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/160813
Tested-by: Mike Kaganski <mike.kaganski@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
diff --git a/sw/source/core/inc/unoparaframeenum.hxx b/sw/source/core/inc/unoparaframeenum.hxx
index 81a653a..c44e0f6 100644
--- a/sw/source/core/inc/unoparaframeenum.hxx
+++ b/sw/source/core/inc/unoparaframeenum.hxx
@@ -31,6 +31,8 @@ class SwNodeIndex;
class SwPaM;
class SwFrameFormat;

namespace com { namespace sun { namespace star { namespace text { class XTextContent; } } } }

namespace sw
{
    struct FrameClient final : public SwClient
@@ -70,6 +72,8 @@ struct SwXParaFrameEnumeration
    static rtl::Reference<SwXParaFrameEnumeration> Create(const SwPaM& rPaM, const enum ParaFrameMode eParaFrameMode, SwFrameFormat* const pFormat = nullptr);
};

css::uno::Reference<css::text::XTextContent> FrameClientToXTextContent(sw::FrameClient* pClient);

#endif // INCLUDED_SW_SOURCE_CORE_INC_UNOPARAFRAMEENUM_HXX

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/unocore/unoobj2.cxx b/sw/source/core/unocore/unoobj2.cxx
index e4f5ccd..7614f0a 100644
--- a/sw/source/core/unocore/unoobj2.cxx
+++ b/sw/source/core/unocore/unoobj2.cxx
@@ -1788,9 +1788,17 @@ bool SwXParaFrameEnumerationImpl::CreateNextObject()
    if (m_vFrames.empty())
        return false;

    SwFrameFormat* const pFormat = static_cast<SwFrameFormat*>(
            m_vFrames.front()->GetRegisteredIn());
    m_xNextObject.set(FrameClientToXTextContent(m_vFrames.front().get()));
    m_vFrames.pop_front();
    return m_xNextObject.is();
}

uno::Reference<text::XTextContent> FrameClientToXTextContent(sw::FrameClient* pClient)
{
    assert(pClient);

    uno::Reference<text::XTextContent> xRet;
    SwFrameFormat* const pFormat = static_cast<SwFrameFormat*>(pClient->GetRegisteredIn());
    // the format should be valid here, otherwise the client
    // would have been removed by PurgeFrameClients
    // check for a shape first
@@ -1799,33 +1807,32 @@ bool SwXParaFrameEnumerationImpl::CreateNextObject()
        SdrObject* pObject(nullptr);
        pFormat->CallSwClientNotify(sw::FindSdrObjectHint(pObject));
        if(pObject)
            m_xNextObject.set(pObject->getUnoShape(), uno::UNO_QUERY);
            xRet.set(pObject->getUnoShape(), uno::UNO_QUERY);
    }
    else
    {
        const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx();
        OSL_ENSURE(pIdx, "where is the index?");
        SwNode const*const pNd =
            m_pUnoCursor->GetDoc().GetNodes()[ pIdx->GetIndex() + 1 ];
        SwNode const* const pNd = pIdx->GetNodes()[pIdx->GetIndex() + 1];

        if (!pNd->IsNoTextNode())
        {
            m_xNextObject = static_cast<SwXFrame*>(SwXTextFrame::CreateXTextFrame(
            xRet = static_cast<SwXFrame*>(SwXTextFrame::CreateXTextFrame(
                        *pFormat->GetDoc(), pFormat).get());
        }
        else if (pNd->IsGrfNode())
        {
            m_xNextObject.set(SwXTextGraphicObject::CreateXTextGraphicObject(
            xRet.set(SwXTextGraphicObject::CreateXTextGraphicObject(
                        *pFormat->GetDoc(), pFormat));
        }
        else
        {
            assert(pNd->IsOLENode());
            m_xNextObject.set(SwXTextEmbeddedObject::CreateXTextEmbeddedObject(
            xRet.set(SwXTextEmbeddedObject::CreateXTextEmbeddedObject(
                        *pFormat->GetDoc(), pFormat));
        }
    }
    return m_xNextObject.is();
    return xRet;
}

sal_Bool SAL_CALL
diff --git a/sw/source/core/unocore/unoparagraph.cxx b/sw/source/core/unocore/unoparagraph.cxx
index 68dbe34..dab5270 100644
--- a/sw/source/core/unocore/unoparagraph.cxx
+++ b/sw/source/core/unocore/unoparagraph.cxx
@@ -54,6 +54,7 @@

#include <com/sun/star/drawing/BitmapMode.hpp>
#include <comphelper/propertyvalue.hxx>
#include <comphelper/sequence.hxx>
#include <comphelper/servicehelper.hxx>
#include <editeng/unoipset.hxx>
#include <svl/listener.hxx>
@@ -550,6 +551,23 @@ uno::Sequence< uno::Any > SwXParagraph::Impl::GetPropertyValues_Impl(
            continue;
        }

        if (pPropertyNames[nProp] == "OOXMLImport_AnchoredShapes")
        {
            // A hack to provide list of anchored objects fast
            // See reanchorObjects in writerfilter/source/dmapper/DomainMapper_Impl.cxx
            FrameClientSortList_t aFrames;
            CollectFrameAtNode(rTextNode, aFrames, false); // Frames anchored to paragraph
            CollectFrameAtNode(rTextNode, aFrames, true); // Frames anchored to character
            std::vector<uno::Reference<text::XTextContent>> aRet;
            aRet.reserve(aFrames.size());
            for (const auto& rFrame : aFrames)
                if (auto xContent = FrameClientToXTextContent(rFrame.pFrameClient.get()))
                    aRet.push_back(xContent);

            pValues[nProp] <<= comphelper::containerToSequence(aRet);
            continue;
        }

        SfxItemPropertyMapEntry const*const pEntry =
            rMap.getByName( pPropertyNames[nProp] );
        if (!pEntry)
diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index 679a1a3..3a0c5a6 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -750,6 +750,59 @@ void DomainMapper_Impl::AddDummyParaForTableInSection()
     return sName;
 }

static void reanchorObjects(const uno::Reference<uno::XInterface>& xFrom,
                            const uno::Reference<text::XTextRange>& xTo,
                            const uno::Reference<drawing::XDrawPage>& xDrawPage)
{
    std::vector<uno::Reference<text::XTextContent>> aShapes;
    bool bFastPathDone = false;
    if (uno::Reference<beans::XPropertySet> xProps{ xFrom, uno::UNO_QUERY })
    {
        try
        {
            // See SwXParagraph::Impl::GetPropertyValues_Impl
            uno::Sequence<uno::Reference<text::XTextContent>> aSeq;
            xProps->getPropertyValue(u"OOXMLImport_AnchoredShapes"_ustr) >>= aSeq;
            aShapes.insert(aShapes.end(), aSeq.begin(), aSeq.end());
            bFastPathDone = true;
        }
        catch (const uno::Exception&)
        {
        }
    }

    if (!bFastPathDone)
    {
        // Can this happen? Fallback to slow DrawPage iteration and range comparison
        uno::Reference<text::XTextRange> xRange(xFrom, uno::UNO_QUERY_THROW);
        uno::Reference<text::XTextRangeCompare> xCompare(xRange->getText(), uno::UNO_QUERY_THROW);

        const sal_Int32 count = xDrawPage->getCount();
        for (sal_Int32 i = 0; i < count; ++i)
        {
            try
            {
                uno::Reference<text::XTextContent> xShape(xDrawPage->getByIndex(i),
                                                          uno::UNO_QUERY_THROW);
                uno::Reference<text::XTextRange> xAnchor(xShape->getAnchor(), uno::UNO_SET_THROW);
                if (xCompare->compareRegionStarts(xAnchor, xRange) <= 0
                    && xCompare->compareRegionEnds(xAnchor, xRange) >= 0)
                {
                    aShapes.push_back(xShape);
                }
            }
            catch (const uno::Exception&)
            {
                // Can happen e.g. in compareRegion*, when the shape is in a header,
                // and paragraph in body
            }
        }
    }

    for (const auto& xShape : aShapes)
        xShape->attach(xTo);
}

void DomainMapper_Impl::RemoveLastParagraph( )
{
    if (m_bDiscardHeaderFooter)
@@ -828,31 +881,7 @@ void DomainMapper_Impl::RemoveLastParagraph( )
                auto xEnumeration = xEA->createEnumeration();
                uno::Reference<text::XTextRange> xPrevParagraph(xEnumeration->nextElement(),
                                                                uno::UNO_QUERY_THROW);

                uno::Reference<text::XTextRange> xParaRange(xParagraph, uno::UNO_QUERY_THROW);
                uno::Reference<text::XTextRangeCompare> xRegionCompare(xParaRange->getText(),
                                                                       uno::UNO_QUERY_THROW);
                const sal_Int32 count = xDrawPage->getCount();
                for (sal_Int32 i = 0; i < count; ++i)
                {
                    try
                    {
                        uno::Reference<text::XTextContent> xShape(xDrawPage->getByIndex(i),
                                                                  uno::UNO_QUERY_THROW);
                        uno::Reference<text::XTextRange> xAnchor(xShape->getAnchor(),
                                                                 uno::UNO_SET_THROW);
                        if (xRegionCompare->compareRegionStarts(xAnchor, xParaRange) <= 0
                            && xRegionCompare->compareRegionEnds(xAnchor, xParaRange) >= 0)
                        {
                            xShape->attach(xPrevParagraph);
                        }
                    }
                    catch (const uno::Exception&)
                    {
                        // Can happen e.g. in compareRegion*, when the shape is in a header,
                        // and paragraph in body
                    }
                }
                reanchorObjects(xParagraph, xPrevParagraph, xDrawPage);
            }

            xParagraph->dispose();