Related: tdf#117761 oox smartart: backport fixes related to process types

This is a combination of 9 commits.

This is the 1st commit:

oox smartart, accent process: add support for reading values from constraints

(cherry picked from commit b389aafee9cfba9dc4dfa552347be39ff9fe41b2)

This is the commit #2:

oox smartart, accent process: add support for zorder offsets

(cherry picked from commit cd348a6244a092c251a8e1362cd78de562d7bef6)

This is the commit #3:

oox smartart, accent process: fix overlapping shape pairs

(cherry picked from commit 67e062aa5e5946d4985921fe2b6f87766f363ddc)

This is the commit #4:

oox smartart, accent process: handle multiple runs from a data point

(cherry picked from commit cfa76f538a44d4396574ece59e8a3953c22c6eb7)

This is the commit #5:

oox smartart, accent process: handle followSib axis of forEach

(cherry picked from commit aedc5427e4b6645ff3257e523c33190cf5e1934d)

This is the commit #6:

oox smartart, accent process: handle connector shape between pairs

(cherry picked from commit 7f66a340933339974b5c6d70af4ae3c17e4f001a)

This is the commit #7:

oox smartart, accent process: adjust size of connector from constraints

(cherry picked from commit ddc2786831367577967e806d603f337a2e42806a)

This is the commit #8:

oox smartart, continuous block process: read space width from constraint

(cherry picked from commit ee6787fc5597b7f730c4ee3a1f2a1b261d0a5644)

Conflicts:
	oox/source/drawingml/diagram/diagramlayoutatoms.cxx

This is the commit #9:

oox smartart, accent process: fix missing bullets and large para indent

(cherry picked from commit 6277a767f33bb5327408dafff2fed199087e938d)

Change-Id: I60bbee75f3e834551ebb1963a2f42101f3bd91d4
Reviewed-on: https://gerrit.libreoffice.org/65352
Tested-by: Jenkins
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
Tested-by: Caolán McNamara <caolanm@redhat.com>
diff --git a/include/oox/drawingml/shape.hxx b/include/oox/drawingml/shape.hxx
index 6028a11..e04a58beb 100644
--- a/include/oox/drawingml/shape.hxx
+++ b/include/oox/drawingml/shape.hxx
@@ -214,6 +214,14 @@
    const LinkedTxbxAttr&     getLinkedTxbxAttributes() { return maLinkedTxbxAttr; };
    bool                isLinkedTxbx() { return mbHasLinkedTxbx; };

    void setZOrder(sal_Int32 nZOrder) { mnZOrder = nZOrder; }

    sal_Int32 getZOrder() const { return mnZOrder; }

    void setZOrderOff(sal_Int32 nZOrderOff) { mnZOrderOff = nZOrderOff; }

    sal_Int32 getZOrderOff() const { return mnZOrderOff; }

protected:

    css::uno::Reference< css::drawing::XShape > const &
@@ -327,6 +335,12 @@
    bool                            mbHasLinkedTxbx; // this text box has linked text box ?

    css::uno::Sequence<css::beans::PropertyValue> maDiagramDoms;

    /// Z-Order.
    sal_Int32 mnZOrder = 0;

    /// Z-Order offset.
    sal_Int32 mnZOrderOff = 0;
};

} }
diff --git a/oox/source/drawingml/diagram/diagramlayoutatoms.cxx b/oox/source/drawingml/diagram/diagramlayoutatoms.cxx
index c1aaf6e..5024709 100644
--- a/oox/source/drawingml/diagram/diagramlayoutatoms.cxx
+++ b/oox/source/drawingml/diagram/diagramlayoutatoms.cxx
@@ -54,6 +54,50 @@

    return oRet;
}

/**
 * Determines if nUnit is a font unit (measured in points) or not (measured in
 * millimeters).
 */
bool isFontUnit(sal_Int32 nUnit)
{
    return nUnit == oox::XML_primFontSz || nUnit == oox::XML_secFontSz;
}

/// Determines the connector shape type from a linear alg.
sal_Int32 getConnectorType(const oox::drawingml::LayoutNode* pNode)
{
    sal_Int32 nType = oox::XML_rightArrow;

    if (!pNode)
        return nType;

    for (const auto& pChild : pNode->getChildren())
    {
        auto pAlgAtom = dynamic_cast<oox::drawingml::AlgAtom*>(pChild.get());
        if (!pAlgAtom)
            continue;

        if (pAlgAtom->getType() != oox::XML_lin)
            continue;

        sal_Int32 nDir = oox::XML_fromL;
        if (pAlgAtom->getMap().count(oox::XML_linDir))
            nDir = pAlgAtom->getMap().find(oox::XML_linDir)->second;

        switch (nDir)
        {
            case oox::XML_fromL:
                nType = oox::XML_rightArrow;
                break;
            case oox::XML_fromR:
                nType = oox::XML_leftArrow;
                break;
        }
    }

    return nType;
}
}

namespace oox { namespace drawingml {
@@ -269,13 +313,15 @@
    rVisitor.visit(*this);
}

void ConstraintAtom::parseConstraint(std::vector<Constraint>& rConstraints) const
void ConstraintAtom::parseConstraint(std::vector<Constraint>& rConstraints,
                                     bool bRequireForName) const
{
    if (bRequireForName && maConstraint.msForName.isEmpty())
        return;

    // accepting only basic equality constraints
    if (!maConstraint.msForName.isEmpty() &&
        (maConstraint.mnOperator == XML_none || maConstraint.mnOperator == XML_equ) &&
        maConstraint.mnType != XML_none &&
        maConstraint.mfValue == 0)
    if ((maConstraint.mnOperator == XML_none || maConstraint.mnOperator == XML_equ)
        && maConstraint.mnType != XML_none)
    {
        rConstraints.push_back(maConstraint);
    }
@@ -290,7 +336,7 @@
                           const std::vector<Constraint>& rOwnConstraints ) const
{
    // Algorithm result may depend on the parent constraints as well.
    std::vector<Constraint> aParentConstraints;
    std::vector<Constraint> aMergedConstraints;
    const LayoutNode* pParent = getLayoutNode().getParentLayoutNode();
    if (pParent)
    {
@@ -298,10 +344,12 @@
        {
            auto pConstraintAtom = dynamic_cast<ConstraintAtom*>(pChild.get());
            if (pConstraintAtom)
                pConstraintAtom->parseConstraint(aParentConstraints);
                pConstraintAtom->parseConstraint(aMergedConstraints, /*bRequireForName=*/true);
        }
    }
    const std::vector<Constraint>& rConstraints = rOwnConstraints.empty() ? aParentConstraints : rOwnConstraints;
    aMergedConstraints.insert(aMergedConstraints.end(), rOwnConstraints.begin(),
                              rOwnConstraints.end());
    const std::vector<Constraint>& rConstraints = aMergedConstraints;

    switch(mnType)
    {
@@ -327,7 +375,19 @@
                    if (aRefType != aRef->second.end())
                        aProperties[rConstr.msForName][rConstr.mnType] = aRefType->second * rConstr.mfFactor;
                    else
                        aProperties[rConstr.msForName][rConstr.mnType] = 0; // TODO: val
                    {
                        // Values are never in EMU, while oox::drawingml::Shape
                        // position and size are always in EMU.
                        double fUnitFactor = 0;
                        if (isFontUnit(rConstr.mnRefType))
                            // Points -> EMU.
                            fUnitFactor = EMU_PER_PT;
                        else
                            // Millimeters -> EMU.
                            fUnitFactor = EMU_PER_HMM * 100;
                        aProperties[rConstr.msForName][rConstr.mnType]
                            = rConstr.mfValue * fUnitFactor;
                    }
                }
            }

@@ -376,7 +436,54 @@
        }

        case XML_conn:
        {
            if (rShape->getSubType() == XML_conn)
            {
                // There is no shape type "conn", replace it by an arrow based
                // on the direction of the parent linear layout.
                sal_Int32 nType = getConnectorType(pParent);

                rShape->setSubType(nType);
                rShape->getCustomShapeProperties()->setShapePresetType(nType);
            }

            // Parse constraints to adjust the size.
            std::vector<Constraint> aDirectConstraints;
            const LayoutNode& rLayoutNode = getLayoutNode();
            for (const auto& pChild : rLayoutNode.getChildren())
            {
                auto pConstraintAtom = dynamic_cast<ConstraintAtom*>(pChild.get());
                if (pConstraintAtom)
                    pConstraintAtom->parseConstraint(aDirectConstraints, /*bRequireForName=*/false);
            }

            LayoutPropertyMap aProperties;
            LayoutProperty& rParent = aProperties[""];
            rParent[XML_w] = rShape->getSize().Width;
            rParent[XML_h] = rShape->getSize().Height;
            rParent[XML_l] = 0;
            rParent[XML_t] = 0;
            rParent[XML_r] = rShape->getSize().Width;
            rParent[XML_b] = rShape->getSize().Height;
            for (const auto& rConstr : aDirectConstraints)
            {
                const LayoutPropertyMap::const_iterator aRef
                    = aProperties.find(rConstr.msRefForName);
                if (aRef != aProperties.end())
                {
                    const LayoutProperty::const_iterator aRefType
                        = aRef->second.find(rConstr.mnRefType);
                    if (aRefType != aRef->second.end())
                        aProperties[rConstr.msForName][rConstr.mnType]
                            = aRefType->second * rConstr.mfFactor;
                }
            }
            awt::Size aSize;
            aSize.Width = rParent[XML_w];
            aSize.Height = rParent[XML_h];
            rShape->setSize(aSize);
            break;
        }

        case XML_cycle:
        {
@@ -428,10 +535,25 @@
            const sal_Int32 nIncX = nDir==XML_fromL ? 1 : (nDir==XML_fromR ? -1 : 0);
            const sal_Int32 nIncY = nDir==XML_fromT ? 1 : (nDir==XML_fromB ? -1 : 0);

            // TODO: get values from constraints
            sal_Int32 nCount = rShape->getChildren().size();
            double fSpace = 0.3;

            // Find out which contraint is relevant for which (internal) name.
            LayoutPropertyMap aProperties;
            for (const auto& rConstraint : rConstraints)
            {
                if (rConstraint.msForName.isEmpty())
                    continue;

                LayoutProperty& rProperty = aProperties[rConstraint.msForName];
                if (rConstraint.mnType == XML_w)
                    rProperty[XML_w] = rShape->getSize().Width * rConstraint.mfFactor;

                // TODO: get values from differently named constraints as well
                if (rConstraint.msForName == "sibTrans" && rConstraint.mnType == XML_w)
                    fSpace = rConstraint.mfFactor;
            }

            awt::Size aChildSize = rShape->getSize();
            if (nDir == XML_fromL || nDir == XML_fromR)
                aChildSize.Width /= (nCount + (nCount-1)*fSpace);
@@ -444,18 +566,6 @@
            if (nIncY == -1)
                aCurrPos.Y = rShape->getSize().Height - aChildSize.Height;

            // Find out which contraint is relevant for which (internal) name.
            LayoutPropertyMap aProperties;
            for (const auto& rConstraint : rConstraints)
            {
                if (rConstraint.msForName.isEmpty())
                    continue;

                LayoutProperty& rProperty = aProperties[rConstraint.msForName];
                if (rConstraint.mnType == XML_w)
                    rProperty[XML_w] = rShape->getSize().Width * rConstraint.mfFactor;
            }

            // See if children requested more than 100% space in total: scale
            // down in that case.
            sal_Int32 nTotalWidth = 0;
@@ -500,7 +610,7 @@
                aSize.Width *= fWidthScale;
                aCurrShape->setSize(aSize);

                aCurrShape->setChildSize(aChildSize);
                aCurrShape->setChildSize(aSize);
                aCurrPos.X += nIncX * (aSize.Width + fSpace*aSize.Width);
                aCurrPos.Y += nIncY * (aChildSize.Height + fSpace*aChildSize.Height);
            }
@@ -711,13 +821,27 @@
            }

            ParamMap::const_iterator aBulletLvl = maMap.find(XML_stBulletLvl);
            int nStartBulletsAtLevel = 0;
            if (aBulletLvl != maMap.end())
            {
                nBaseLevel -= aBulletLvl->second;
                nStartBulletsAtLevel = aBulletLvl->second;
            }

            for (auto & aParagraph : pTextBody->getParagraphs())
            {
                sal_Int32 nLevel = aParagraph->getProperties().getLevel();
                aParagraph->getProperties().setLevel(nLevel - nBaseLevel);
                if (nStartBulletsAtLevel > 0 && nLevel >= nStartBulletsAtLevel)
                {
                    // It is not possible to change the bullet style for text.
                    sal_Int32 nLeftMargin = 285750 * (nLevel - nStartBulletsAtLevel) / EMU_PER_HMM;
                    aParagraph->getProperties().getParaLeftMargin() = nLeftMargin;
                    aParagraph->getProperties().getFirstLineIndentation() = -285750 / EMU_PER_HMM;
                    OUString aBulletChar = OUString::fromUtf8(u8"•");
                    aParagraph->getProperties().getBulletList().setBulletChar(aBulletChar);
                    aParagraph->getProperties().getBulletList().setSuffixNone();
                }
            }

            // explicit alignment
@@ -821,8 +945,10 @@
                if( aVecIter->second != -1 )
                    rPara.getProperties().setLevel(aVecIter->second);

                rPara.addRun(
                    aDataNode2->second->mpShape->getTextBody()->getParagraphs().front()->getRuns().front());
                std::shared_ptr<TextParagraph> pSourceParagraph
                    = aDataNode2->second->mpShape->getTextBody()->getParagraphs().front();
                for (const auto& pRun : pSourceParagraph->getRuns())
                    rPara.addRun(pRun);
                rPara.getProperties().apply(
                    aDataNode2->second->mpShape->getTextBody()->getParagraphs().front()->getProperties());
            }
diff --git a/oox/source/drawingml/diagram/diagramlayoutatoms.hxx b/oox/source/drawingml/diagram/diagramlayoutatoms.hxx
index 3d4d9c0..500495b 100644
--- a/oox/source/drawingml/diagram/diagramlayoutatoms.hxx
+++ b/oox/source/drawingml/diagram/diagramlayoutatoms.hxx
@@ -141,7 +141,7 @@
    virtual void accept( LayoutAtomVisitor& ) override;
    Constraint& getConstraint()
        { return maConstraint; }
    void parseConstraint(std::vector<Constraint>& rConstraints) const;
    void parseConstraint(std::vector<Constraint>& rConstraints, bool bRequireForName) const;
private:
    Constraint maConstraint;
};
@@ -162,6 +162,13 @@
        { maMap[nType]=nVal; }
    void layoutShape( const ShapePtr& rShape,
                      const std::vector<Constraint>& rConstraints ) const;

    /// Gives access to <dgm:alg type="..."/>.
    sal_Int32 getType() const { return mnType; }

    /// Gives access to <dgm:param type="..." val="..."/>.
    const ParamMap& getMap() const { return maMap; }

private:
    sal_Int32 mnType;
    ParamMap  maMap;
diff --git a/oox/source/drawingml/diagram/layoutatomvisitors.cxx b/oox/source/drawingml/diagram/layoutatomvisitors.cxx
index ce8e6ab..49a664c 100644
--- a/oox/source/drawingml/diagram/layoutatomvisitors.cxx
+++ b/oox/source/drawingml/diagram/layoutatomvisitors.cxx
@@ -46,6 +46,14 @@

void ShapeCreationVisitor::visit(ForEachAtom& rAtom)
{
    if (rAtom.iterator().mnAxis == XML_followSib)
    {
        // If the axis is the follow sibling, then the last atom should not be
        // visited.
        if (mnCurrIdx + mnCurrStep >= mnCurrCnt)
            return;
    }

    const std::vector<LayoutAtomPtr>& rChildren=rAtom.getChildren();

    sal_Int32 nChildren=1;
@@ -65,7 +73,11 @@
        rAtom.iterator().mnCnt==-1 ? nChildren : rAtom.iterator().mnCnt);

    const sal_Int32 nOldIdx=mnCurrIdx;
    const sal_Int32 nOldStep = mnCurrStep;
    const sal_Int32 nOldCnt = mnCurrCnt;
    const sal_Int32 nStep=rAtom.iterator().mnStep;
    mnCurrStep = nStep;
    mnCurrCnt = nCnt;
    for( mnCurrIdx=0; mnCurrIdx<nCnt && nStep>0; mnCurrIdx+=nStep )
    {
        // TODO there is likely some conditions
@@ -75,6 +87,8 @@

    // and restore idx
    mnCurrIdx = nOldIdx;
    mnCurrStep = nOldStep;
    mnCurrCnt = nOldCnt;
}

void ShapeCreationVisitor::visit(ConditionAtom& rAtom)
@@ -166,6 +180,38 @@
        std::remove_if(pCurrParent->getChildren().begin(), pCurrParent->getChildren().end(),
            [] (const ShapePtr & aChild) { return aChild->getServiceName() == "com.sun.star.drawing.GroupShape" && aChild->getChildren().empty(); }),
        pCurrParent->getChildren().end());

    // Offset the children from their default z-order stacking, if necessary.
    std::vector<ShapePtr>& rChildren = pCurrParent->getChildren();
    for (size_t i = 0; i < rChildren.size(); ++i)
        rChildren[i]->setZOrder(i);

    for (size_t i = 0; i < rChildren.size(); ++i)
    {
        const ShapePtr& pChild = rChildren[i];
        sal_Int32 nZOrderOff = pChild->getZOrderOff();
        if (nZOrderOff <= 0)
            continue;

        // Increase my ZOrder by nZOrderOff.
        pChild->setZOrder(pChild->getZOrder() + nZOrderOff);
        pChild->setZOrderOff(0);

        for (sal_Int32 j = 0; j < nZOrderOff; ++j)
        {
            size_t nIndex = i + j + 1;
            if (nIndex >= rChildren.size())
                break;

            // Decrease the ZOrder of the next nZOrderOff elements by one.
            const ShapePtr& pNext = rChildren[nIndex];
            pNext->setZOrder(pNext->getZOrder() - 1);
        }
    }

    // Now that the ZOrders are adjusted, sort the children.
    std::sort(rChildren.begin(), rChildren.end(),
              [](const ShapePtr& a, const ShapePtr& b) { return a->getZOrder() < b->getZOrder(); });
}

void ShapeCreationVisitor::visit(ShapeAtom& /*rAtom*/)
@@ -235,7 +281,7 @@
void ShapeLayoutingVisitor::visit(ConstraintAtom& rAtom)
{
    if (meLookFor == CONSTRAINT)
        rAtom.parseConstraint(maConstraints);
        rAtom.parseConstraint(maConstraints, /*bRequireForName=*/true);
}

void ShapeLayoutingVisitor::visit(AlgAtom& rAtom)
diff --git a/oox/source/drawingml/diagram/layoutatomvisitors.hxx b/oox/source/drawingml/diagram/layoutatomvisitors.hxx
index 2997391..f395f6a 100644
--- a/oox/source/drawingml/diagram/layoutatomvisitors.hxx
+++ b/oox/source/drawingml/diagram/layoutatomvisitors.hxx
@@ -33,6 +33,8 @@
    ShapePtr mpParentShape;
    const Diagram& mrDgm;
    sal_Int32 mnCurrIdx;
    sal_Int32 mnCurrStep = 0;
    sal_Int32 mnCurrCnt = 0;
    const dgm::Point* mpCurrentNode;

    void defaultVisit(LayoutAtom const & rAtom);
diff --git a/oox/source/drawingml/diagram/layoutnodecontext.cxx b/oox/source/drawingml/diagram/layoutnodecontext.cxx
index 257f490..ad62ba5 100644
--- a/oox/source/drawingml/diagram/layoutnodecontext.cxx
+++ b/oox/source/drawingml/diagram/layoutnodecontext.cxx
@@ -209,6 +209,8 @@

        pShape->setDiagramRotation(rAttribs.getInteger(XML_rot, 0) * PER_DEGREE);

        pShape->setZOrderOff(rAttribs.getInteger(XML_zOrderOff, 0));

        ShapeAtomPtr pAtom( new ShapeAtom(mpNode->getLayoutNode(), pShape) );
        LayoutAtom::connect(mpNode, pAtom);
        return new ShapeContext( *this, ShapePtr(), pShape );
diff --git a/oox/source/drawingml/shape.cxx b/oox/source/drawingml/shape.cxx
index 2926614..16bc511 100644
--- a/oox/source/drawingml/shape.cxx
+++ b/oox/source/drawingml/shape.cxx
@@ -175,6 +175,8 @@
, maLinkedTxbxAttr()
, mbHasLinkedTxbx(false)
, maDiagramDoms( pSourceShape->maDiagramDoms )
, mnZOrder(pSourceShape->mnZOrder)
, mnZOrderOff(pSourceShape->mnZOrderOff)
{}

Shape::~Shape()
diff --git a/sd/qa/unit/data/pptx/smartart-accent-process.pptx b/sd/qa/unit/data/pptx/smartart-accent-process.pptx
new file mode 100644
index 0000000..8710e7f
--- /dev/null
+++ b/sd/qa/unit/data/pptx/smartart-accent-process.pptx
Binary files differ
diff --git a/sd/qa/unit/data/pptx/smartart-continuous-block-process.pptx b/sd/qa/unit/data/pptx/smartart-continuous-block-process.pptx
new file mode 100644
index 0000000..b2ef58f
--- /dev/null
+++ b/sd/qa/unit/data/pptx/smartart-continuous-block-process.pptx
Binary files differ
diff --git a/sd/qa/unit/import-tests-smartart.cxx b/sd/qa/unit/import-tests-smartart.cxx
index d561f13..b83de20 100644
--- a/sd/qa/unit/import-tests-smartart.cxx
+++ b/sd/qa/unit/import-tests-smartart.cxx
@@ -14,6 +14,8 @@
#include <com/sun/star/style/ParagraphAdjust.hpp>
#include <com/sun/star/text/XText.hpp>

#include <comphelper/sequenceashashmap.hxx>

using namespace ::com::sun::star;

class SdImportTestSmartArt : public SdModelTestBase
@@ -29,6 +31,8 @@
    void testVertialBoxList();
    void testVertialBracketList();
    void testTableList();
    void testAccentProcess();
    void testContinuousBlockProcess();

    CPPUNIT_TEST_SUITE(SdImportTestSmartArt);

@@ -42,6 +46,8 @@
    CPPUNIT_TEST(testVertialBoxList);
    CPPUNIT_TEST(testVertialBracketList);
    CPPUNIT_TEST(testTableList);
    CPPUNIT_TEST(testAccentProcess);
    CPPUNIT_TEST(testContinuousBlockProcess);

    CPPUNIT_TEST_SUITE_END();
};
@@ -276,6 +282,126 @@
    xDocShRef->DoClose();
}

void SdImportTestSmartArt::testAccentProcess()
{
    sd::DrawDocShellRef xDocShRef = loadURL(
        m_directories.getURLFromSrc("/sd/qa/unit/data/pptx/smartart-accent-process.pptx"), PPTX);
    uno::Reference<drawing::XShapes> xGroup(getShapeFromPage(0, 0, xDocShRef), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xGroup.is());
    // 3 children: first pair, connector, second pair.
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(3), xGroup->getCount());
    uno::Reference<drawing::XShape> xGroupShape(xGroup, uno::UNO_QUERY);
    CPPUNIT_ASSERT(xGroupShape.is());

    // The pair is a parent (shape + text) and a child, so 3 shapes in total.
    // The order is importent, first is at the back, last is at the front.
    uno::Reference<drawing::XShapes> xFirstPair(xGroup->getByIndex(0), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xFirstPair.is());
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(3), xFirstPair->getCount());

    uno::Reference<text::XText> xFirstParentText(xFirstPair->getByIndex(1), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xFirstParentText.is());
    CPPUNIT_ASSERT_EQUAL(OUString("a"), xFirstParentText->getString());
    uno::Reference<drawing::XShape> xFirstParent(xFirstParentText, uno::UNO_QUERY);
    CPPUNIT_ASSERT(xFirstParent.is());
    int nFirstParentTop = xFirstParent->getPosition().Y;

    uno::Reference<text::XText> xFirstChildText(xFirstPair->getByIndex(2), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xFirstChildText.is());
    CPPUNIT_ASSERT_EQUAL(OUString("b"), xFirstChildText->getString());
    uno::Reference<drawing::XShape> xFirstChild(xFirstChildText, uno::UNO_QUERY);
    CPPUNIT_ASSERT(xFirstChildText.is());

    {
        uno::Reference<container::XEnumerationAccess> xParasAccess(xFirstChildText, uno::UNO_QUERY);
        uno::Reference<container::XEnumeration> xParas = xParasAccess->createEnumeration();
        uno::Reference<beans::XPropertySet> xPara(xParas->nextElement(), uno::UNO_QUERY);
        // Without the accompanying fix in place, this test would have failed
        // with 'Expected: 0; Actual  : 1270', i.e. there was a large
        // unexpected left margin.
        CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0),
                             xPara->getPropertyValue("ParaLeftMargin").get<sal_Int32>());

        uno::Reference<container::XIndexAccess> xRules(xPara->getPropertyValue("NumberingRules"),
                                                       uno::UNO_QUERY);
        comphelper::SequenceAsHashMap aRule(xRules->getByIndex(1));
        CPPUNIT_ASSERT_EQUAL(OUString::fromUtf8(u8"•"), aRule["BulletChar"].get<OUString>());
    }

    int nFirstChildTop = xFirstChild->getPosition().Y;
    int nFirstChildRight = xFirstChild->getPosition().X + xFirstChild->getSize().Width;

    // First child is below the first parent.
    // Without the accompanying fix in place, this test would have failed with
    // 'Expected less than: 3881, Actual  : 3881', i.e. xFirstChild was not
    // below xFirstParent (a good position is 9081).
    CPPUNIT_ASSERT_LESS(nFirstChildTop, nFirstParentTop);

    // Make sure that we have an arrow shape between the two pairs.
    uno::Reference<beans::XPropertySet> xArrow(xGroup->getByIndex(1), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xArrow.is());
    comphelper::SequenceAsHashMap aCustomShapeGeometry(
        xArrow->getPropertyValue("CustomShapeGeometry"));
    // Without the accompanying fix in place, this test would have failed, i.e.
    // the custom shape lacked a type -> arrow was not visible.
    CPPUNIT_ASSERT(aCustomShapeGeometry["Type"].has<OUString>());
    OUString aType = aCustomShapeGeometry["Type"].get<OUString>();
    CPPUNIT_ASSERT_EQUAL(OUString("ooxml-rightArrow"), aType);

    // Make sure that height of the arrow is less than its width.
    uno::Reference<drawing::XShape> xArrowShape(xArrow, uno::UNO_QUERY);
    CPPUNIT_ASSERT(xArrowShape.is());
    awt::Size aArrowSize = xArrowShape->getSize();
    CPPUNIT_ASSERT_LESS(aArrowSize.Width, aArrowSize.Height);

    uno::Reference<drawing::XShapes> xSecondPair(xGroup->getByIndex(2), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xSecondPair.is());
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(3), xSecondPair->getCount());
    uno::Reference<text::XText> xSecondParentText(xSecondPair->getByIndex(1), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xFirstParentText.is());
    // Without the accompanying fix in place, this test would have failed with
    // 'Expected: cc; Actual  : c', i.e. non-first runs on data points were ignored.
    CPPUNIT_ASSERT_EQUAL(OUString("cc"), xSecondParentText->getString());
    uno::Reference<drawing::XShape> xSecondParent(xSecondParentText, uno::UNO_QUERY);
    CPPUNIT_ASSERT(xSecondParent.is());
    int nSecondParentLeft = xSecondParent->getPosition().X;
    // Without the accompanying fix in place, this test would have failed with
    // 'Expected less than: 12700; Actual  : 18540', i.e. the "b" and "c"
    // shapes overlapped.
    CPPUNIT_ASSERT_LESS(nSecondParentLeft, nFirstChildRight);

    xDocShRef->DoClose();
}

void SdImportTestSmartArt::testContinuousBlockProcess()
{
    sd::DrawDocShellRef xDocShRef = loadURL(
        m_directories.getURLFromSrc("/sd/qa/unit/data/pptx/smartart-continuous-block-process.pptx"),
        PPTX);
    uno::Reference<drawing::XShapes> xGroup(getShapeFromPage(0, 0, xDocShRef), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xGroup.is());
    // 2 children: background, foreground.
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), xGroup->getCount());

    uno::Reference<drawing::XShapes> xLinear(xGroup->getByIndex(1), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xLinear.is());
    // 3 children: A, B and C.
    CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(3), xLinear->getCount());

    uno::Reference<text::XText> xA(xLinear->getByIndex(0), uno::UNO_QUERY);
    CPPUNIT_ASSERT(xA.is());
    CPPUNIT_ASSERT_EQUAL(OUString("A"), xA->getString());
    uno::Reference<drawing::XShape> xAShape(xA, uno::UNO_QUERY);
    CPPUNIT_ASSERT(xAShape.is());
    // Without the accompanying fix in place, this test would have failed: the
    // theoretically correct value is 5462 mm100 (16933 is the total width, and
    // need to divide that to 1, 0.5, 1, 0.5 and 1 units), while the old value
    // was 4703 and the new one is 5461.
    CPPUNIT_ASSERT_GREATER(static_cast<sal_Int32>(5000), xAShape->getSize().Width);

    xDocShRef->DoClose();
}

CPPUNIT_TEST_SUITE_REGISTRATION(SdImportTestSmartArt);

CPPUNIT_PLUGIN_IMPLEMENT();