tdf#159158 writerfilter: headers always under other z-orders

I guessed that a negative z-order will be below
a positive relativeHeight when it is in the header.
It seems like a reasonable assumption.
No unit tests hit this situation AFAIK.

make CppunitTest_sw_ooxmlexport18 \
    CPPUNIT_TEST_NAME=testTdf159158_zOrder_headerBehind

Change-Id: I06e31f3df413ad9c32791c8f4b940831630131dd
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/164105
Tested-by: Jenkins
Reviewed-by: Justin Luth <jluth@mail.com>
diff --git a/sw/qa/extras/ooxmlexport/data/tdf159158_zOrder_headerBehind.odt b/sw/qa/extras/ooxmlexport/data/tdf159158_zOrder_headerBehind.odt
new file mode 100644
index 0000000..a4bf98a
--- /dev/null
+++ b/sw/qa/extras/ooxmlexport/data/tdf159158_zOrder_headerBehind.odt
Binary files differ
diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
index 084863c..91fa26e 100644
--- a/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
+++ b/sw/qa/extras/ooxmlexport/ooxmlexport18.cxx
@@ -1032,6 +1032,21 @@ DECLARE_OOXMLEXPORT_TEST(testTdf159158_zOrder_behindDocB, "tdf159158_zOrder_behi
        CPPUNIT_ASSERT_EQUAL(OUString("5-Point Star Blue"), getProperty<OUString>(zOrder1,"Name"));
}

DECLARE_OOXMLEXPORT_TEST(testTdf159158_zOrder_headerBehind, "tdf159158_zOrder_headerBehind.odt")
{
    // given a blue star (not marked as behind text) anchored in the header
    // and an overlapping yellow rectangle anchored in the body text.
    // (note that in ODT format the star is on top, but for DOCX format it must be behind (hidden)
    uno::Reference<beans::XPropertySet> zOrder0(getShape(1), uno::UNO_QUERY);
    uno::Reference<beans::XPropertySet> zOrder1(getShape(2), 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")); // higher
    // I don't know why the star is the lowest order in ODT import (maybe header weirdness),
    // but it certainly needs to be the lowest on docx round-trip (also for header weirdness)
    CPPUNIT_ASSERT_EQUAL(OUString("StarInHeader"), getProperty<OUString>(zOrder0, "Name"));
    CPPUNIT_ASSERT_EQUAL(OUString("RectangleInBody"), getProperty<OUString>(zOrder1,"Name"));
}

DECLARE_OOXMLEXPORT_TEST(testTdf155903, "tdf155903.odt")
{
    // Without the accompanying fix in place, this test would have crashed,
diff --git a/writerfilter/inc/dmapper/GraphicZOrderHelper.hxx b/writerfilter/inc/dmapper/GraphicZOrderHelper.hxx
index c6a308d..b5bb27e 100644
--- a/writerfilter/inc/dmapper/GraphicZOrderHelper.hxx
+++ b/writerfilter/inc/dmapper/GraphicZOrderHelper.hxx
@@ -17,11 +17,15 @@ class GraphicZOrderHelper
{
public:
    void addItem(css::uno::Reference<css::beans::XPropertySet> const& props,
                 sal_Int32 relativeHeight);
    sal_Int32 findZOrder(sal_Int32 relativeHeight, bool bOldStyle = false);
                 sal_Int64 relativeHeight);

    // must run adjustRelativeHeight before findZOrder - to set zOrder priorities
    static void adjustRelativeHeight(sal_Int64& rRelativeHeight, bool bIsZIndex, bool bIsBehindText,
                                     bool bIsInHeader);
    sal_Int32 findZOrder(sal_Int64 relativeHeight, bool bOldStyle = false);

private:
    using Items = std::map<sal_Int32, css::uno::Reference<css::beans::XPropertySet>>;
    using Items = std::map<sal_Int64, css::uno::Reference<css::beans::XPropertySet>>;
    Items m_items;
};

diff --git a/writerfilter/source/dmapper/DomainMapper_Impl.cxx b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
index 45ff009..bbdfbd3 100644
--- a/writerfilter/source/dmapper/DomainMapper_Impl.cxx
+++ b/writerfilter/source/dmapper/DomainMapper_Impl.cxx
@@ -4763,8 +4763,10 @@ void DomainMapper_Impl::PushShapeContext( const uno::Reference< drawing::XShape 
                    if (rProp.Name == "VML-Z-ORDER")
                    {
                        GraphicZOrderHelper& rZOrderHelper = m_rDMapper.graphicZOrderHelper();
                        sal_Int32 zOrder(0);
                        sal_Int64 zOrder(0);
                        rProp.Value >>= zOrder;
                        GraphicZOrderHelper::adjustRelativeHeight(zOrder, /*IsZIndex=*/true,
                                                                  zOrder < 0, IsInHeaderFooter());
                        xShapePropertySet->setPropertyValue("ZOrder",
                            uno::Any(rZOrderHelper.findZOrder(zOrder, /*LastDuplicateWins*/true)));
                        rZOrderHelper.addItem(xShapePropertySet, zOrder);
@@ -4808,8 +4810,10 @@ void DomainMapper_Impl::PushShapeContext( const uno::Reference< drawing::XShape 
                    if (rProp.Name == "VML-Z-ORDER")
                    {
                        GraphicZOrderHelper& rZOrderHelper = m_rDMapper.graphicZOrderHelper();
                        sal_Int32 zOrder(0);
                        sal_Int64 zOrder(0);
                        rProp.Value >>= zOrder;
                        GraphicZOrderHelper::adjustRelativeHeight(zOrder, /*IsZIndex=*/true,
                                                                  zOrder < 0, IsInHeaderFooter());
                        xShapePropertySet->setPropertyValue("ZOrder",
                            uno::Any(rZOrderHelper.findZOrder(zOrder, /*LastDuplicateWins*/true)));
                        rZOrderHelper.addItem(xShapePropertySet, zOrder);
diff --git a/writerfilter/source/dmapper/GraphicHelpers.cxx b/writerfilter/source/dmapper/GraphicHelpers.cxx
index d01ac5b..32e13b0 100644
--- a/writerfilter/source/dmapper/GraphicHelpers.cxx
+++ b/writerfilter/source/dmapper/GraphicHelpers.cxx
@@ -270,11 +270,65 @@ text::WrapTextMode WrapHandler::getWrapMode( ) const
}


void GraphicZOrderHelper::addItem(uno::Reference<beans::XPropertySet> const& props, sal_Int32 const relativeHeight)
void GraphicZOrderHelper::addItem(uno::Reference<beans::XPropertySet> const& props,
                                  sal_Int64 const relativeHeight)
{
    m_items[ relativeHeight ] = props;
}

void GraphicZOrderHelper::adjustRelativeHeight(sal_Int64& rRelativeHeight, bool bIsZIndex,
                                               bool bIsBehindText, bool bIsInHeader)
{
    // zOrder can be defined either by z-index (VML) or by relativeHeight (DML).
    // z-index indicates background with a negative value,
    // while relativeHeight indicates background with behindDoc = true.
    //
    // In general, all z-index-defined shapes appear on top of relativeHeight graphics
    // regardless of the value.

    // priority order
    // above text:  positive sal_Int32 z-index (opaque/in front of text)
    //              relativeHeight (represented here as a negative sal_Int32, but still opaque)
    // behind body: negative sal_Int32 z-index (!opaque/in the background)
    //              behindText relativeHeight
    //              (in header) positive z-index
    //              (in header) relativeHeight
    //              (in header) negative z-index
    //              (in header) behindText

    const sal_Int64 nMaxUnsignedInt32 = SAL_MAX_UINT32;
    if (!bIsInHeader)
    {
        if (bIsZIndex)
        {
            // this number is already fine
            // positive values are the only positive values coming into zOrder,
            // and negative values will be !opaque (below text)
        }
        else if (!bIsBehindText)
        {
            assert (rRelativeHeight < 0);
            // this number is already fine - will be above text, but relativeHeight is negative
        }
        else
        {
            // reduce to negative level 1 to force below a negative z-index
            rRelativeHeight -= nMaxUnsignedInt32;
        }
    }
    else // bIsInHeader
    {
        if (bIsZIndex && !bIsBehindText)
            rRelativeHeight -= nMaxUnsignedInt32 * 2; // reduce to negative level 2
        else if (!bIsBehindText)
            rRelativeHeight -= nMaxUnsignedInt32 * 3; // reduce to negative level 3
        else if (bIsZIndex)
            rRelativeHeight -= nMaxUnsignedInt32 * 4; // reduce to negative level 4
        else
            rRelativeHeight -= nMaxUnsignedInt32 * 5; // reduce to negative level 5
    }
}

// The relativeHeight value in .docx is an arbitrary number, where only the relative ordering matters.
// But in Writer, the z-order is index in 0..(numitems-1) range, so whenever a new item needs to be
// added in the proper z-order, it is necessary to find the proper index.
@@ -282,7 +336,7 @@ void GraphicZOrderHelper::addItem(uno::Reference<beans::XPropertySet> const& pro
// The key to this function is that later on, when setPropertyValue("ZOrder", <returned sal_Int32>),
// SW also automatically increments ALL zOrders >= the one returned for this fly.
// Thus, getProperty PROP_Z_ORDER for relativeHeight "x" can return different values for itemZOrder.
sal_Int32 GraphicZOrderHelper::findZOrder( sal_Int32 relativeHeight, bool bOldStyle )
sal_Int32 GraphicZOrderHelper::findZOrder(sal_Int64 relativeHeight, bool bOldStyle)
{
    // std::map is iterated sorted by key
    auto it = std::find_if(m_items.cbegin(), m_items.cend(),
diff --git a/writerfilter/source/dmapper/GraphicImport.cxx b/writerfilter/source/dmapper/GraphicImport.cxx
index bee2074..386ac69 100644
--- a/writerfilter/source/dmapper/GraphicImport.cxx
+++ b/writerfilter/source/dmapper/GraphicImport.cxx
@@ -210,7 +210,7 @@ public:
    sal_Int32 m_nTopPosition;

    bool      m_bUseSimplePos;
    std::optional<sal_Int32> m_oZOrder;
    std::optional<sal_Int64> m_oZOrder;

    sal_Int16 m_nHoriOrient;
    sal_Int16 m_nHoriRelation;
@@ -390,36 +390,19 @@ public:

    void applyZOrder(uno::Reference<beans::XPropertySet> const & xGraphicObjectProperties) const
    {
        std::optional<sal_Int32> oZOrder = m_oZOrder;
        std::optional<sal_Int64> oZOrder = m_oZOrder;
        if (m_rGraphicImportType == GraphicImportType::IMPORT_AS_DETECTED_INLINE
            && !m_rDomainMapper.IsInShape())
        {
            oZOrder = SAL_MIN_INT32;
            oZOrder = SAL_MIN_INT64;
        }
        else if (!oZOrder)
            return;
        else
        {
            // tdf#120760 Send objects with behinddoc=true to the back.

            // zOrder can be defined either by z-index or by relativeHeight.
            // z-index indicates background with a negative value,
            // while relativeHeight indicates background with BehindDoc = true.
            //
            // In general, all z-index-defined shapes appear on top of relativeHeight graphics
            // regardless of the value.
            // So we have to try to put all relativeHeights as far back as possible,
            // and this has already partially happened because they were already made to be negative
            // but now the behindDoc relativeHeights need to be forced to the very back.
            //
            // Subtract even more so behindDoc relativeHeights will be behind
            // foreground relativeHeights and also behind all of the negative z-indexes
            // (especially needed for IsInHeaderFooter, as EVERYTHING is forced to the background).
            //
            // relativeHeight already removed 0x1E00 0000, so can subtract another 0x6200 0000
            const bool bBehindText = m_bBehindDoc && !m_bOpaque;
            if (bBehindText)
                oZOrder = *oZOrder - 0x62000000;
            GraphicZOrderHelper::adjustRelativeHeight(*oZOrder, /*IsZIndex=*/false, bBehindText,
                                                      m_rDomainMapper.IsInHeaderFooter());
        }

        // TODO: it is possible that RTF has been wrong all along as well. Always true here?