tdf#100037 vml import: set fly-anchored AS_CHARs above fly's zOrder

Normally an AS_CHAR is the lowest in the heaven-layer zOrder,
but if it is inside a fly, it should be just above the fly's zOrder.

In order to get at the parent's properties,
I had to remove the stack abstraction
so I changed the stack to a vector.
That seemed a lot saner than popping and re-pushing.

make CppunitTest_sw_ooxmlexport18 \
    CPPUNIT_TEST_NAME=testTdf100037_inlineZOrder2
make CppunitTest_sw_ooxmlexport18 \
    CPPUNIT_TEST_NAME=testTdf100037_inlineZOrder3

Change-Id: Idc159e8203b3f304133a9b110c135e4d0f001dbc
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/168565
Reviewed-by: Justin Luth <jluth@mail.com>
Tested-by: Jenkins
diff --git a/sw/qa/extras/ooxmlexport/data/tdf100037_inlineZOrder2.docx b/sw/qa/extras/ooxmlexport/data/tdf100037_inlineZOrder2.docx
new file mode 100644
index 0000000..e30824b
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/tdf100037_inlineZOrder2.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/data/tdf100037_inlineZOrder3.docx b/sw/qa/extras/ooxmlexport/data/tdf100037_inlineZOrder3.docx
new file mode 100644
index 0000000..d97bd7e
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/tdf100037_inlineZOrder3.docx
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
index 50a9dc3..ff3e7a3b 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
@@ -1076,6 +1076,50 @@ DECLARE_OOXMLEXPORT_TEST(testTdf100037_inlineZOrder, "tdf100037_inlineZOrder.doc
    CPPUNIT_ASSERT_EQUAL(OUString("Frame1"), getProperty<OUString>(zOrder1, "LinkDisplayName"));
}

DECLARE_OOXMLEXPORT_TEST(testTdf100037_inlineZOrder2, "tdf100037_inlineZOrder2.docx")
{
    // given a yellow floating textbox-with-image overlapped by a blue textbox-with-image,
    // the inline image should take its zOrder from the textbox it is in.
    if (isExported())
        return; // we don't export images inside of draw textboxes I guess

    uno::Reference<beans::XPropertySet> zOrder0(getShape(1), uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> zOrder1(getShape(2), uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> zOrder2(getShape(3), uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> zOrder3(getShape(4), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), getProperty<sal_Int32>(zOrder0, "ZOrder")); // lower
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), getProperty<sal_Int32>(zOrder1, "ZOrder"));
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), getProperty<sal_Int32>(zOrder2, "ZOrder"));
    CPPUNIT_ASSERT_EQUAL(sal_Int32(3), getProperty<sal_Int32>(zOrder3, "ZOrder")); // higher
    // yellow textbox (Frame1) is the lowest
    CPPUNIT_ASSERT_EQUAL(OUString("Frame1"), getProperty<OUString>(zOrder0, "LinkDisplayName"));
    //CPPUNIT_ASSERT_EQUAL(OUString("Image1"), getProperty<OUString>(zOrder1, "Name"));
    CPPUNIT_ASSERT_EQUAL(OUString("Frame2"), getProperty<OUString>(zOrder2, "LinkDisplayName"));
    // CPPUNIT_ASSERT_EQUAL(OUString("Image2"), getProperty<OUString>(zOrder3, "LinkDisplayName"));
}

DECLARE_OOXMLEXPORT_TEST(testTdf100037_inlineZOrder3, "tdf100037_inlineZOrder3.docx")
{
    // given a yellow floating textbox-with-image that overlaps a blue textbox-with-image,
    // the inline image should take its zOrder from the textbox it is in.
    if (isExported())
        return; // we don't export images inside of draw textboxes I guess

    uno::Reference<beans::XPropertySet> zOrder0(getShape(1), uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> zOrder1(getShape(2), uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> zOrder2(getShape(3), uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> zOrder3(getShape(4), uno::UNO_QUERY);
    CPPUNIT_ASSERT_EQUAL(sal_Int32(0), getProperty<sal_Int32>(zOrder0, "ZOrder")); // lower
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), getProperty<sal_Int32>(zOrder1, "ZOrder"));
    CPPUNIT_ASSERT_EQUAL(sal_Int32(2), getProperty<sal_Int32>(zOrder2, "ZOrder"));
    CPPUNIT_ASSERT_EQUAL(sal_Int32(3), getProperty<sal_Int32>(zOrder3, "ZOrder")); // higher
    // blue textbox (Frame2) is the lowest
    CPPUNIT_ASSERT_EQUAL(OUString("Frame2"), getProperty<OUString>(zOrder0, "LinkDisplayName"));
    // CPPUNIT_ASSERT_EQUAL(OUString("Image2"), getProperty<OUString>(zOrder1, "LinkDisplayName"));
    CPPUNIT_ASSERT_EQUAL(OUString("Frame1"), getProperty<OUString>(zOrder2, "LinkDisplayName"));
    // CPPUNIT_ASSERT_EQUAL(OUString("Image1"), getProperty<OUString>(zOrder3, "LinkDisplayName"));
}

DECLARE_OOXMLEXPORT_TEST(testTdf155903, "tdf155903.odt")
{
    // Without the accompanying fix in place, this test would have crashed,
diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
index 25612d082..7f02644 100644
--- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
+++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.cxx
@@ -3469,9 +3469,9 @@ void DomainMapper_Impl::appendOLE( const OUString& rStreamName, const std::share
            // gives a better ( visually ) result
            xOLE->setPropertyValue(getPropertyName( PROP_ANCHOR_TYPE ),  uno::Any( text::TextContentAnchorType_AS_CHARACTER ) );
        // remove ( if valid ) associated shape ( used for graphic replacement )
        SAL_WARN_IF(m_aAnchoredStack.empty(), "writerfilter.dmapper", "no anchor stack");
        if (!m_aAnchoredStack.empty())
            m_aAnchoredStack.top( ).bToRemove = true;
        SAL_WARN_IF(m_vAnchoredStack.empty(), "writerfilter.dmapper", "no anchor stack");
        if (!m_vAnchoredStack.empty())
            m_vAnchoredStack.back().bToRemove = true;
        RemoveLastParagraph();
        SAL_WARN_IF(m_aTextAppendStack.empty(), "writerfilter.dmapper", "no text stack");
        if (!m_aTextAppendStack.empty())
@@ -4737,14 +4737,14 @@ void DomainMapper_Impl::PushShapeContext( const uno::Reference< drawing::XShape 
            // shapes for OLE objects.
            m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xShape, uno::UNO_QUERY), uno::Reference<text::XTextCursor>()));
            uno::Reference<text::XTextContent> xTxtContent(xShape, uno::UNO_QUERY);
            m_aAnchoredStack.push(AnchoredContext(xTxtContent));
            m_vAnchoredStack.push_back(AnchoredContext(xTxtContent));
        }
        else if (xSInfo->supportsService(u"com.sun.star.drawing.OLE2Shape"_ustr))
        {
            // OLE2Shape from oox should be converted to a TextEmbeddedObject for sw.
            m_aTextAppendStack.push(TextAppendContext(uno::Reference<text::XTextAppend>(xShape, uno::UNO_QUERY), uno::Reference<text::XTextCursor>()));
            uno::Reference<text::XTextContent> xTextContent(xShape, uno::UNO_QUERY);
            m_aAnchoredStack.push(AnchoredContext(xTextContent));
            m_vAnchoredStack.push_back(AnchoredContext(xTextContent));
            uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY);

            rtl::Reference<SwXTextEmbeddedObject> xEmbedded = m_xTextDocument->createTextEmbeddedObject();
@@ -4752,7 +4752,7 @@ void DomainMapper_Impl::PushShapeContext( const uno::Reference< drawing::XShape 
            xEmbedded->setPropertyValue(getPropertyName(PROP_EMBEDDED_OBJECT), xShapePropertySet->getPropertyValue(getPropertyName(PROP_EMBEDDED_OBJECT)));
            xEmbedded->setPropertyValue(getPropertyName(PROP_ANCHOR_TYPE), uno::Any(text::TextContentAnchorType_AS_CHARACTER));
            // So that the original bitmap-only shape will be replaced by the embedded object.
            m_aAnchoredStack.top().bToRemove = true;
            m_vAnchoredStack.back().bToRemove = true;
            m_aTextAppendStack.pop();
            appendTextContent(m_StreamStateStack.top().xEmbedded, uno::Sequence<beans::PropertyValue>());
        }
@@ -4772,7 +4772,7 @@ void DomainMapper_Impl::PushShapeContext( const uno::Reference< drawing::XShape 

            // Add the shape to the anchored objects stack
            uno::Reference< text::XTextContent > xTxtContent( xShape, uno::UNO_QUERY_THROW );
            m_aAnchoredStack.push( AnchoredContext(xTxtContent) );
            m_vAnchoredStack.push_back(AnchoredContext(xTxtContent));

            uno::Reference<beans::XPropertySet> xShapePropertySet(xShape, uno::UNO_QUERY_THROW);
#ifdef DBG_UTIL
@@ -4832,7 +4832,29 @@ void DomainMapper_Impl::PushShapeContext( const uno::Reference< drawing::XShape 
                    xShapePropertySet->setPropertyValue(getPropertyName(PROP_BOTTOM_MARGIN),
                                                        aPropMargin->second);

                sal_Int64 zOrder = SAL_MIN_INT64;
                sal_Int64 zOrder = SAL_MIN_INT64; // lowest in heaven-layer: AS_CHAR in body text
                // AS_CHARs anchored inside a fly should be just above the fly's zOrder
                if (m_vAnchoredStack.size() > 1)
                {
                    uno::Reference<beans::XPropertySet> xParentPropertySet(
                        m_vAnchoredStack[m_vAnchoredStack.size() - 2].xTextContent,
                        uno::UNO_QUERY_THROW);
                    uno::Sequence<beans::PropertyValue> aGrabBag;
                    xParentPropertySet->getPropertyValue(u"FrameInteropGrabBag"_ustr) >>= aGrabBag;
                    for (const auto& rProp : aGrabBag)
                    {
                        if (rProp.Name == "VML-Z-ORDER")
                        {
                            rProp.Value >>= zOrder;
                            ++zOrder;
                            GraphicZOrderHelper::adjustRelativeHeight(zOrder, /*IsZIndex=*/true,
                                                                      zOrder < 0,
                                                                      IsInHeaderFooter());
                            xShapePropertySet->setPropertyValue(getPropertyName(PROP_OPAQUE),
                                                                uno::Any(zOrder >= 0));
                        }
                    }
                }
                xShapePropertySet->setPropertyValue(u"ZOrder"_ustr,
                    uno::Any(rZOrderHelper.findZOrder(zOrder, /*LastDuplicateWins*/true)));
                rZOrderHelper.addItem(xShapePropertySet, zOrder);
@@ -4933,19 +4955,19 @@ void DomainMapper_Impl::PopShapeContext()
        getTableManager().endLevel();
        popTableManager();
    }
    if ( m_aAnchoredStack.empty() )
    if (m_vAnchoredStack.empty())
        return;

    // For OLE object replacement shape, the text append context was already removed
    // or the OLE object couldn't be inserted.
    if ( !m_aAnchoredStack.top().bToRemove )
    if (!m_vAnchoredStack.back().bToRemove)
    {
        RemoveLastParagraph();
        if (!m_aTextAppendStack.empty())
            m_aTextAppendStack.pop();
    }

    uno::Reference< text::XTextContent > xObj = m_aAnchoredStack.top( ).xTextContent;
    uno::Reference<text::XTextContent> xObj = m_vAnchoredStack.back().xTextContent;
    try
    {
        appendTextContent( xObj, uno::Sequence< beans::PropertyValue >() );
@@ -4958,7 +4980,7 @@ void DomainMapper_Impl::PopShapeContext()
    const uno::Reference<drawing::XShape> xShape( xObj, uno::UNO_QUERY_THROW );
    // Remove the shape if required (most likely replacement shape for OLE object)
    // or anchored to a discarded header or footer
    if ( m_xTextDocument && (m_aAnchoredStack.top().bToRemove || m_bDiscardHeaderFooter) )
    if (m_xTextDocument && (m_vAnchoredStack.back().bToRemove || m_bDiscardHeaderFooter))
    {
        try
        {
@@ -4995,7 +5017,7 @@ void DomainMapper_Impl::PopShapeContext()
        }
    }

    m_aAnchoredStack.pop();
    m_vAnchoredStack.pop_back();
}

bool DomainMapper_Impl::IsSdtEndBefore()
diff --git a/sw/source/writerfilter/dmapper/DomainMapper_Impl.hxx b/sw/source/writerfilter/dmapper/DomainMapper_Impl.hxx
index f6041112..c76d1f6 100644
--- a/sw/source/writerfilter/dmapper/DomainMapper_Impl.hxx
+++ b/sw/source/writerfilter/dmapper/DomainMapper_Impl.hxx
@@ -555,7 +555,7 @@ private:
    css::uno::Reference<css::text::XText> m_xBodyText;

    std::stack<TextAppendContext>                                                   m_aTextAppendStack;
    std::stack<AnchoredContext>                                                     m_aAnchoredStack;
    std::vector<AnchoredContext> m_vAnchoredStack;
public: // DomainMapper needs it
    std::stack<SubstreamContext> m_StreamStateStack;
private:
@@ -906,7 +906,7 @@ public:
    bool        IsNumberingImport() const { return m_bInNumberingImport;}
    void        SetAnyTableImport( bool bSet ) { m_bInAnyTableImport = bSet;}
    bool        IsAnyTableImport()const { return m_bInAnyTableImport;}
    bool        IsInShape()const { return m_aAnchoredStack.size() > 0;}
    bool        IsInShape()const { return m_vAnchoredStack.size() > 0;}

    void PushShapeContext(const css::uno::Reference<css::drawing::XShape>& xShape);
    void PopShapeContext();