/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */
 
#include "diagramlayoutatoms.hxx"
 
#include <set>
 
#include "layoutatomvisitorbase.hxx"
 
#include <basegfx/numeric/ftools.hxx>
#include <sal/log.hxx>
 
#include <o3tl/unit_conversion.hxx>
#include <oox/helper/attributelist.hxx>
#include <oox/token/properties.hxx>
#include <drawingml/fillproperties.hxx>
#include <drawingml/lineproperties.hxx>
#include <drawingml/textbody.hxx>
#include <drawingml/textparagraph.hxx>
#include <drawingml/textrun.hxx>
#include <drawingml/customshapeproperties.hxx>
#include <com/sun/star/drawing/TextFitToSizeType.hpp>
 
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::xml::sax;
using namespace ::oox::core;
 
namespace
{
/// Looks up the value of the rInternalName -> nProperty key in rProperties.
std::optional<sal_Int32> findProperty(const oox::drawingml::LayoutPropertyMap& rProperties,
                                      const OUString& rInternalName, sal_Int32 nProperty)
{
    std::optional<sal_Int32> oRet;
 
    auto it = rProperties.find(rInternalName);
    if (it != rProperties.end())
    {
        const oox::drawingml::LayoutProperty& rProperty = it->second;
        auto itProperty = rProperty.find(nProperty);
        if (itProperty != rProperty.end())
            oRet = itProperty->second;
    }
 
    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 which UNO property should be set for a given constraint type.
sal_Int32 getPropertyFromConstraint(sal_Int32 nConstraint)
{
    switch (nConstraint)
    {
        case oox::XML_lMarg:
            return oox::PROP_TextLeftDistance;
        case oox::XML_rMarg:
            return oox::PROP_TextRightDistance;
        case oox::XML_tMarg:
            return oox::PROP_TextUpperDistance;
        case oox::XML_bMarg:
            return oox::PROP_TextLowerDistance;
    }
 
    return 0;
}
 
/**
 * Determines if pShape is (or contains) a presentation of a data node of type
 * nType.
 */
bool containsDataNodeType(const oox::drawingml::ShapePtr& pShape, sal_Int32 nType)
{
    if (pShape->getDataNodeType() == nType)
        return true;
 
    for (const auto& pChild : pShape->getChildren())
    {
        if (containsDataNodeType(pChild, nType))
            return true;
    }
 
    return false;
}
}
 
namespace oox::drawingml {
void SnakeAlg::layoutShapeChildren(const AlgAtom& rAlg, const ShapePtr& rShape,
                                   const std::vector<Constraint>& rConstraints)
{
    if (rShape->getChildren().empty() || rShape->getSize().Width == 0
        || rShape->getSize().Height == 0)
        return;
 
    // Parse constraints.
    double fChildAspectRatio = rShape->getChildren()[0]->getAspectRatio();
    double fShapeHeight = rShape->getSize().Height;
    double fShapeWidth = rShape->getSize().Width;
    // Check if we have a child aspect ratio. If so, need to shrink one dimension to
    // achieve that ratio.
    if (fChildAspectRatio && fShapeHeight && fChildAspectRatio < (fShapeWidth / fShapeHeight))
    {
        fShapeWidth = fShapeHeight * fChildAspectRatio;
    }
 
    double fSpaceFromConstraint = 1.0;
    LayoutPropertyMap aPropertiesByName;
    std::map<sal_Int32, LayoutProperty> aPropertiesByType;
    LayoutProperty& rParent = aPropertiesByName[u""_ustr];
    rParent[XML_w] = fShapeWidth;
    rParent[XML_h] = fShapeHeight;
    for (const auto& rConstr : rConstraints)
    {
        if (rConstr.mnRefType == XML_w || rConstr.mnRefType == XML_h)
        {
            if (rConstr.mnType == XML_sp && rConstr.msForName.isEmpty())
                fSpaceFromConstraint = rConstr.mfFactor;
        }
 
        auto itRefForName = aPropertiesByName.find(rConstr.msRefForName);
        if (itRefForName == aPropertiesByName.end())
        {
            continue;
        }
 
        auto it = itRefForName->second.find(rConstr.mnRefType);
        if (it == itRefForName->second.end())
        {
            continue;
        }
 
        if (rConstr.mfValue != 0.0)
        {
            continue;
        }
 
        sal_Int32 nValue = it->second * rConstr.mfFactor;
 
        if (rConstr.mnPointType == XML_none)
        {
            aPropertiesByName[rConstr.msForName][rConstr.mnType] = nValue;
        }
        else
        {
            aPropertiesByType[rConstr.mnPointType][rConstr.mnType] = nValue;
        }
    }
 
    std::vector<sal_Int32> aShapeWidths(rShape->getChildren().size());
    for (size_t i = 0; i < rShape->getChildren().size(); ++i)
    {
        ShapePtr pChild = rShape->getChildren()[i];
        if (!pChild->getDataNodeType())
        {
            // TODO handle the case when the requirement applies by name, not by point type.
            aShapeWidths[i] = fShapeWidth;
            continue;
        }
 
        auto itNodeType = aPropertiesByType.find(pChild->getDataNodeType());
        if (itNodeType == aPropertiesByType.end())
        {
            aShapeWidths[i] = fShapeWidth;
            continue;
        }
 
        auto it = itNodeType->second.find(XML_w);
        if (it == itNodeType->second.end())
        {
            aShapeWidths[i] = fShapeWidth;
            continue;
        }
 
        aShapeWidths[i] = it->second;
    }
 
    bool bSpaceFromConstraints = fSpaceFromConstraint != 1.0;
 
    const AlgAtom::ParamMap& rMap = rAlg.getMap();
    const sal_Int32 nDir = rMap.count(XML_grDir) ? rMap.find(XML_grDir)->second : XML_tL;
    sal_Int32 nIncX = 1;
    sal_Int32 nIncY = 1;
    bool bHorizontal = true;
    switch (nDir)
    {
        case XML_tL:
            nIncX = 1;
            nIncY = 1;
            break;
        case XML_tR:
            nIncX = -1;
            nIncY = 1;
            break;
        case XML_bL:
            nIncX = 1;
            nIncY = -1;
            bHorizontal = false;
            break;
        case XML_bR:
            nIncX = -1;
            nIncY = -1;
            bHorizontal = false;
            break;
    }
 
    sal_Int32 nCount = rShape->getChildren().size();
    // Defaults in case not provided by constraints.
    double fSpace = bSpaceFromConstraints ? fSpaceFromConstraint : 0.3;
    double fAspectRatio = 0.54; // diagram should not spill outside, earlier it was 0.6
 
    sal_Int32 nCol = 1;
    sal_Int32 nRow = 1;
    sal_Int32 nMaxRowWidth = 0;
    if (nCount <= fChildAspectRatio)
        // Child aspect ratio request (width/height) is N, and we have at most N shapes.
        // This means we don't need multiple columns.
        nRow = nCount;
    else
    {
        for (; nRow < nCount; nRow++)
        {
            nCol = std::ceil(static_cast<double>(nCount) / nRow);
            sal_Int32 nRowWidth = 0;
            for (sal_Int32 i = 0; i < nCol; ++i)
            {
                if (i >= nCount)
                {
                    break;
                }
 
                nRowWidth += aShapeWidths[i];
            }
            double fTotalShapesHeight = fShapeHeight * nRow;
            if (nRowWidth && fTotalShapesHeight / nRowWidth >= fAspectRatio)
            {
                if (nRowWidth > nMaxRowWidth)
                {
                    nMaxRowWidth = nRowWidth;
                }
                break;
            }
        }
    }
 
    SAL_INFO("oox.drawingml", "Snake layout grid: " << nCol << "x" << nRow);
 
    sal_Int32 nWidth = rShape->getSize().Width / (nCol + (nCol - 1) * fSpace);
    awt::Size aChildSize(nWidth, nWidth * fAspectRatio);
    if (nCol == 1 && nRow > 1)
    {
        // We have a single column, so count the height based on the parent height, not
        // based on width.
        // Space occurs inside children; also double amount of space is needed outside (on
        // both sides), if the factor comes from a constraint.
        sal_Int32 nNumSpaces = -1;
        if (bSpaceFromConstraints)
            nNumSpaces += 4;
        sal_Int32 nHeight = rShape->getSize().Height / (nRow + (nRow + nNumSpaces) * fSpace);
 
        if (fChildAspectRatio > 1)
        {
            // Shrink width if the aspect ratio requires it.
            nWidth = std::min(rShape->getSize().Width,
                              static_cast<sal_Int32>(nHeight * fChildAspectRatio));
            aChildSize = awt::Size(nWidth, nHeight);
        }
 
        bHorizontal = false;
    }
 
    awt::Point aCurrPos(0, 0);
    if (nIncX == -1)
        aCurrPos.X = rShape->getSize().Width - aChildSize.Width;
    if (nIncY == -1)
        aCurrPos.Y = rShape->getSize().Height - aChildSize.Height;
    else if (bSpaceFromConstraints)
    {
        if (!bHorizontal)
        {
            // Initial vertical offset to have upper spacing (outside, so double amount).
            aCurrPos.Y = aChildSize.Height * fSpace * 2;
        }
    }
 
    sal_Int32 nStartX = aCurrPos.X;
    sal_Int32 nColIdx = 0, index = 0;
 
    const sal_Int32 aContDir
        = rMap.count(XML_contDir) ? rMap.find(XML_contDir)->second : XML_sameDir;
 
    switch (aContDir)
    {
        case XML_sameDir:
        {
            sal_Int32 nRowHeight = 0;
            for (auto& aCurrShape : rShape->getChildren())
            {
                aCurrShape->setPosition(aCurrPos);
                awt::Size aCurrSize(aChildSize);
                // aShapeWidths items are a portion of nMaxRowWidth. We want the same ratio,
                // based on the original parent width, ignoring the aspect ratio request.
                bool bWidthsFromConstraints
                    = nCount >= 2 && rShape->getChildren()[1]->getDataNodeType() == XML_sibTrans;
                if (bWidthsFromConstraints && nMaxRowWidth)
                {
                    double fWidthFactor = static_cast<double>(aShapeWidths[index]) / nMaxRowWidth;
                    // We can only work from constraints if spacing is represented by a real
                    // child shape.
                    aCurrSize.Width = rShape->getSize().Width * fWidthFactor;
                }
                if (fChildAspectRatio)
                {
                    aCurrSize.Height = aCurrSize.Width / fChildAspectRatio;
 
                    // Child shapes are not allowed to leave their parent.
                    aCurrSize.Height = std::min<sal_Int32>(
                        aCurrSize.Height, rShape->getSize().Height / (nRow + (nRow - 1) * fSpace));
                }
                if (aCurrSize.Height > nRowHeight)
                {
                    nRowHeight = aCurrSize.Height;
                }
                aCurrShape->setSize(aCurrSize);
                aCurrShape->setChildSize(aCurrSize);
 
                index++; // counts index of child, helpful for positioning.
 
                if (index % nCol == 0 || ((index / nCol) + 1) != nRow)
                    aCurrPos.X += nIncX * (aCurrSize.Width + fSpace * aCurrSize.Width);
 
                if (++nColIdx == nCol) // condition for next row
                {
                    // if last row, then position children according to number of shapes.
                    if ((index + 1) % nCol != 0 && (index + 1) >= 3
                        && ((index + 1) / nCol + 1) == nRow && nCount != nRow * nCol)
                    {
                        // position first child of last row
                        if (bWidthsFromConstraints)
                        {
                            aCurrPos.X = nStartX;
                        }
                        else
                        {
                            // Can assume that all child shape has the same width.
                            aCurrPos.X
                                = nStartX
                                  + (nIncX * (aCurrSize.Width + fSpace * aCurrSize.Width)) / 2;
                        }
                    }
                    else
                        // if not last row, positions first child of that row
                        aCurrPos.X = nStartX;
                    aCurrPos.Y += nIncY * (nRowHeight + fSpace * nRowHeight);
                    nColIdx = 0;
                    nRowHeight = 0;
                }
 
                // positions children in the last row.
                if (index % nCol != 0 && index >= 3 && ((index / nCol) + 1) == nRow)
                    aCurrPos.X += (nIncX * (aCurrSize.Width + fSpace * aCurrSize.Width));
            }
            break;
        }
        case XML_revDir:
            for (auto& aCurrShape : rShape->getChildren())
            {
                aCurrShape->setPosition(aCurrPos);
                aCurrShape->setSize(aChildSize);
                aCurrShape->setChildSize(aChildSize);
 
                index++; // counts index of child, helpful for positioning.
 
                /*
                   index%col -> tests node is at last column
                   ((index/nCol)+1)!=nRow) -> tests node is at last row or not
                   ((index/nCol)+1)%2!=0 -> tests node is at row which is multiple of 2, important for revDir
                   num!=nRow*nCol -> tests how last row nodes should be spread.
                   */
 
                if ((index % nCol == 0 || ((index / nCol) + 1) != nRow)
                    && ((index / nCol) + 1) % 2 != 0)
                    aCurrPos.X += (aChildSize.Width + fSpace * aChildSize.Width);
                else if (index % nCol != 0
                         && ((index / nCol) + 1) != nRow) // child other than placed at last column
                    aCurrPos.X -= (aChildSize.Width + fSpace * aChildSize.Width);
 
                if (++nColIdx == nCol) // condition for next row
                {
                    // if last row, then position children according to number of shapes.
                    if ((index + 1) % nCol != 0 && (index + 1) >= 4
                        && ((index + 1) / nCol + 1) == nRow && nCount != nRow * nCol
                        && ((index / nCol) + 1) % 2 == 0)
                        // position first child of last row
                        aCurrPos.X -= aChildSize.Width * 3 / 2;
                    else if ((index + 1) % nCol != 0 && (index + 1) >= 4
                             && ((index + 1) / nCol + 1) == nRow && nCount != nRow * nCol
                             && ((index / nCol) + 1) % 2 != 0)
                        aCurrPos.X = nStartX
                                     + (nIncX * (aChildSize.Width + fSpace * aChildSize.Width)) / 2;
                    else if (((index / nCol) + 1) % 2 != 0)
                        aCurrPos.X = nStartX;
 
                    aCurrPos.Y += nIncY * (aChildSize.Height + fSpace * aChildSize.Height);
                    nColIdx = 0;
                }
 
                // positions children in the last row.
                if (index % nCol != 0 && index >= 3 && ((index / nCol) + 1) == nRow
                    && ((index / nCol) + 1) % 2 == 0)
                    //if row%2=0 then start from left else
                    aCurrPos.X -= (nIncX * (aChildSize.Width + fSpace * aChildSize.Width));
                else if (index % nCol != 0 && index >= 3 && ((index / nCol) + 1) == nRow
                         && ((index / nCol) + 1) % 2 != 0)
                    // start from right
                    aCurrPos.X += (nIncX * (aChildSize.Width + fSpace * aChildSize.Width));
            }
            break;
    }
}
 
void PyraAlg::layoutShapeChildren(const ShapePtr& rShape)
{
    if (rShape->getChildren().empty() || rShape->getSize().Width == 0
        || rShape->getSize().Height == 0)
        return;
 
    // const sal_Int32 nDir = maMap.count(XML_linDir) ? maMap.find(XML_linDir)->second : XML_fromT;
    // const sal_Int32 npyraAcctPos = maMap.count(XML_pyraAcctPos) ? maMap.find(XML_pyraAcctPos)->second : XML_bef;
    // const sal_Int32 ntxDir = maMap.count(XML_txDir) ? maMap.find(XML_txDir)->second : XML_fromT;
    // const sal_Int32 npyraLvlNode = maMap.count(XML_pyraLvlNode) ? maMap.find(XML_pyraLvlNode)->second : XML_level;
    // uncomment when use in code.
 
    sal_Int32 nCount = rShape->getChildren().size();
    double fAspectRatio = 0.32;
 
    awt::Size aChildSize = rShape->getSize();
    aChildSize.Width /= nCount;
    aChildSize.Height /= nCount;
 
    awt::Point aCurrPos(0, 0);
    aCurrPos.X = fAspectRatio * aChildSize.Width * (nCount - 1);
    aCurrPos.Y = fAspectRatio * aChildSize.Height;
 
    for (auto& aCurrShape : rShape->getChildren())
    {
        aCurrShape->setPosition(aCurrPos);
        if (nCount > 1)
        {
            aCurrPos.X -= aChildSize.Height / (nCount - 1);
        }
        aChildSize.Width += aChildSize.Height;
        aCurrShape->setSize(aChildSize);
        aCurrShape->setChildSize(aChildSize);
        aCurrPos.Y += (aChildSize.Height);
    }
}
 
bool CompositeAlg::inferFromLayoutProperty(const LayoutProperty& rMap, sal_Int32 nRefType,
                                           sal_Int32& rValue)
{
    switch (nRefType)
    {
        case XML_r:
        {
            auto it = rMap.find(XML_l);
            if (it == rMap.end())
            {
                return false;
            }
            sal_Int32 nLeft = it->second;
            it = rMap.find(XML_w);
            if (it == rMap.end())
            {
                return false;
            }
            rValue = nLeft + it->second;
            return true;
        }
        default:
            break;
    }
 
    return false;
}
 
void CompositeAlg::applyConstraintToLayout(const Constraint& rConstraint,
                                           LayoutPropertyMap& rProperties)
{
    // TODO handle the case when we have ptType="...", not forName="...".
    if (rConstraint.msForName.isEmpty())
    {
        return;
    }
 
    const LayoutPropertyMap::const_iterator aRef = rProperties.find(rConstraint.msRefForName);
    if (aRef == rProperties.end())
        return;
 
    const LayoutProperty::const_iterator aRefType = aRef->second.find(rConstraint.mnRefType);
    sal_Int32 nInferredValue = 0;
    if (aRefType != aRef->second.end())
    {
        // Reference is found directly.
        rProperties[rConstraint.msForName][rConstraint.mnType]
            = aRefType->second * rConstraint.mfFactor;
    }
    else if (inferFromLayoutProperty(aRef->second, rConstraint.mnRefType, nInferredValue))
    {
        // Reference can be inferred.
        rProperties[rConstraint.msForName][rConstraint.mnType]
            = nInferredValue * rConstraint.mfFactor;
    }
    else
    {
        // Reference not found, assume a fixed value.
        // Values are never in EMU, while oox::drawingml::Shape position and size are always in
        // EMU.
        const double fValue = o3tl::convert(rConstraint.mfValue,
                                            isFontUnit(rConstraint.mnRefType) ? o3tl::Length::pt
                                                                              : o3tl::Length::mm,
                                            o3tl::Length::emu);
        rProperties[rConstraint.msForName][rConstraint.mnType] = fValue;
    }
}
 
void CompositeAlg::layoutShapeChildren(AlgAtom& rAlg, const ShapePtr& rShape,
                                       const std::vector<Constraint>& rConstraints)
{
    LayoutPropertyMap aProperties;
    LayoutProperty& rParent = aProperties[u""_ustr];
 
    sal_Int32 nParentXOffset = 0;
 
    // Track min/max vertical positions, so we can center everything at the end, if needed.
    sal_Int32 nVertMin = std::numeric_limits<sal_Int32>::max();
    sal_Int32 nVertMax = 0;
 
    if (rAlg.getAspectRatio() != 1.0)
    {
        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;
    }
    else
    {
        // Shrink width to be only as large as height.
        rParent[XML_w] = std::min(rShape->getSize().Width, rShape->getSize().Height);
        rParent[XML_h] = rShape->getSize().Height;
        if (rParent[XML_w] < rShape->getSize().Width)
            nParentXOffset = (rShape->getSize().Width - rParent[XML_w]) / 2;
        rParent[XML_l] = nParentXOffset;
        rParent[XML_t] = 0;
        rParent[XML_r] = rShape->getSize().Width - rParent[XML_l];
        rParent[XML_b] = rShape->getSize().Height;
    }
 
    for (const auto& rConstr : rConstraints)
    {
        // Apply direct constraints for all layout nodes.
        applyConstraintToLayout(rConstr, aProperties);
    }
 
    for (auto& aCurrShape : rShape->getChildren())
    {
        // Apply constraints from the current layout node for this child shape.
        // Previous child shapes may have changed aProperties.
        for (const auto& rConstr : rConstraints)
        {
            if (rConstr.msForName != aCurrShape->getInternalName())
            {
                continue;
            }
 
            applyConstraintToLayout(rConstr, aProperties);
        }
 
        // Apply constraints from the child layout node for this child shape.
        // This builds on top of the own parent state + the state of previous shapes in the
        // same composite algorithm.
        const LayoutNode& rLayoutNode = rAlg.getLayoutNode();
        for (const auto& pDirectChild : rLayoutNode.getChildren())
        {
            auto pLayoutNode = dynamic_cast<LayoutNode*>(pDirectChild.get());
            if (!pLayoutNode)
            {
                continue;
            }
 
            if (pLayoutNode->getName() != aCurrShape->getInternalName())
            {
                continue;
            }
 
            for (const auto& pChild : pLayoutNode->getChildren())
            {
                auto pConstraintAtom = dynamic_cast<ConstraintAtom*>(pChild.get());
                if (!pConstraintAtom)
                {
                    continue;
                }
 
                const Constraint& rConstraint = pConstraintAtom->getConstraint();
                if (!rConstraint.msForName.isEmpty())
                {
                    continue;
                }
 
                if (!rConstraint.msRefForName.isEmpty())
                {
                    continue;
                }
 
                // Either an absolute value or a factor of a property.
                if (rConstraint.mfValue == 0.0 && rConstraint.mnRefType == XML_none)
                {
                    continue;
                }
 
                Constraint aConstraint(rConstraint);
                aConstraint.msForName = pLayoutNode->getName();
                aConstraint.msRefForName = pLayoutNode->getName();
 
                applyConstraintToLayout(aConstraint, aProperties);
            }
        }
 
        awt::Size aSize = rShape->getSize();
        awt::Point aPos(0, 0);
 
        const LayoutPropertyMap::const_iterator aPropIt
            = aProperties.find(aCurrShape->getInternalName());
        if (aPropIt != aProperties.end())
        {
            const LayoutProperty& rProp = aPropIt->second;
            LayoutProperty::const_iterator it, it2;
 
            if ((it = rProp.find(XML_w)) != rProp.end())
                aSize.Width = std::min(it->second, rShape->getSize().Width);
            if ((it = rProp.find(XML_h)) != rProp.end())
                aSize.Height = std::min(it->second, rShape->getSize().Height);
 
            if ((it = rProp.find(XML_l)) != rProp.end())
                aPos.X = it->second;
            else if ((it = rProp.find(XML_ctrX)) != rProp.end())
                aPos.X = it->second - aSize.Width / 2;
            else if ((it = rProp.find(XML_r)) != rProp.end())
                aPos.X = it->second - aSize.Width;
 
            if ((it = rProp.find(XML_t)) != rProp.end())
                aPos.Y = it->second;
            else if ((it = rProp.find(XML_ctrY)) != rProp.end())
                aPos.Y = it->second - aSize.Height / 2;
            else if ((it = rProp.find(XML_b)) != rProp.end())
                aPos.Y = it->second - aSize.Height;
 
            if ((it = rProp.find(XML_l)) != rProp.end() && (it2 = rProp.find(XML_r)) != rProp.end())
                aSize.Width = it2->second - it->second;
            if ((it = rProp.find(XML_t)) != rProp.end() && (it2 = rProp.find(XML_b)) != rProp.end())
                aSize.Height = it2->second - it->second;
 
            aPos.X += nParentXOffset;
            aSize.Width = std::min(aSize.Width, rShape->getSize().Width - aPos.X);
            aSize.Height = std::min(aSize.Height, rShape->getSize().Height - aPos.Y);
        }
        else
            SAL_WARN("oox.drawingml", "composite layout properties not found for shape "
                                          << aCurrShape->getInternalName());
 
        aCurrShape->setSize(aSize);
        aCurrShape->setChildSize(aSize);
        aCurrShape->setPosition(aPos);
 
        nVertMin = std::min(aPos.Y, nVertMin);
        nVertMax = std::max(aPos.Y + aSize.Height, nVertMax);
 
        NamedShapePairs& rDiagramFontHeights
            = rAlg.getLayoutNode().getDiagram().getDiagramFontHeights();
        auto it = rDiagramFontHeights.find(aCurrShape->getInternalName());
        if (it != rDiagramFontHeights.end())
        {
            // Internal name matches: put drawingml::Shape to the relevant group, for
            // synchronized font height handling.
            it->second.insert({ aCurrShape, {} });
        }
    }
 
    // See if all vertical space is used or we have to center the content.
    if (!(nVertMin >= 0 && nVertMin <= nVertMax && nVertMax <= rParent[XML_h]))
        return;
 
    sal_Int32 nDiff = rParent[XML_h] - (nVertMax - nVertMin);
    if (nDiff > 0)
    {
        for (auto& aCurrShape : rShape->getChildren())
        {
            awt::Point aPosition = aCurrShape->getPosition();
            aPosition.Y += nDiff / 2;
            aCurrShape->setPosition(aPosition);
        }
    }
}
 
IteratorAttr::IteratorAttr( )
    : mnCnt( -1 )
    , mbHideLastTrans( true )
    , mnPtType( 0 )
    , mnSt( 0 )
    , mnStep( 1 )
{
}
 
void IteratorAttr::loadFromXAttr( const Reference< XFastAttributeList >& xAttr )
{
    AttributeList attr( xAttr );
    maAxis = attr.getTokenList(XML_axis);
    mnCnt = attr.getInteger( XML_cnt, -1 );
    mbHideLastTrans = attr.getBool( XML_hideLastTrans, true );
    mnSt = attr.getInteger( XML_st, 0 );
    mnStep = attr.getInteger( XML_step, 1 );
 
    // better to keep first token instead of error when multiple values
    std::vector<sal_Int32> aPtTypes = attr.getTokenList(XML_ptType);
    mnPtType = aPtTypes.empty() ? XML_all : aPtTypes.front();
}
 
ConditionAttr::ConditionAttr()
    : mnFunc( 0 )
    , mnArg( 0 )
    , mnOp( 0 )
    , mnVal( 0 )
{
}
 
void ConditionAttr::loadFromXAttr( const Reference< XFastAttributeList >& xAttr )
{
    mnFunc = xAttr->getOptionalValueToken( XML_func, 0 );
    mnArg = xAttr->getOptionalValueToken( XML_arg, XML_none );
    mnOp = xAttr->getOptionalValueToken( XML_op, 0 );
    msVal = xAttr->getOptionalValue( XML_val );
    mnVal = xAttr->getOptionalValueToken( XML_val, 0 );
}
 
void LayoutAtom::dump(int level)
{
    SAL_INFO("oox.drawingml",  "level = " << level << " - " << msName << " of type " << typeid(*this).name() );
    for (const auto& pAtom : getChildren())
        pAtom->dump(level + 1);
}
 
ForEachAtom::ForEachAtom(LayoutNode& rLayoutNode, const Reference< XFastAttributeList >& xAttributes) :
    LayoutAtom(rLayoutNode)
{
    maIter.loadFromXAttr(xAttributes);
}
 
void ForEachAtom::accept( LayoutAtomVisitor& rVisitor )
{
    rVisitor.visit(*this);
}
 
LayoutAtomPtr ForEachAtom::getRefAtom()
{
    if (!msRef.isEmpty())
    {
        const LayoutAtomMap& rLayoutAtomMap = getLayoutNode().getDiagram().getLayout()->getLayoutAtomMap();
        LayoutAtomMap::const_iterator pRefAtom = rLayoutAtomMap.find(msRef);
        if (pRefAtom != rLayoutAtomMap.end())
            return pRefAtom->second;
        else
            SAL_WARN("oox.drawingml", "ForEach reference \"" << msRef << "\" not found");
    }
    return LayoutAtomPtr();
}
 
void ChooseAtom::accept( LayoutAtomVisitor& rVisitor )
{
    rVisitor.visit(*this);
}
 
ConditionAtom::ConditionAtom(LayoutNode& rLayoutNode, bool isElse, const Reference< XFastAttributeList >& xAttributes) :
    LayoutAtom(rLayoutNode),
    mIsElse(isElse)
{
    maIter.loadFromXAttr( xAttributes );
    maCond.loadFromXAttr( xAttributes );
}
 
bool ConditionAtom::compareResult(sal_Int32 nOperator, sal_Int32 nFirst, sal_Int32 nSecond)
{
    switch (nOperator)
    {
    case XML_equ: return nFirst == nSecond;
    case XML_gt:  return nFirst >  nSecond;
    case XML_gte: return nFirst >= nSecond;
    case XML_lt:  return nFirst <  nSecond;
    case XML_lte: return nFirst <= nSecond;
    case XML_neq: return nFirst != nSecond;
    default:
        SAL_WARN("oox.drawingml", "unsupported operator: " << nOperator);
        return false;
    }
}
 
namespace
{
/**
 * Takes the connection list from rLayoutNode, navigates from rFrom on an edge
 * of type nType, using a direction determined by bSourceToDestination.
 */
OUString navigate(LayoutNode& rLayoutNode, svx::diagram::TypeConstant nType, std::u16string_view rFrom,
                  bool bSourceToDestination)
{
    for (const auto& rConnection : rLayoutNode.getDiagram().getData()->getConnections())
    {
        if (rConnection.mnXMLType != nType)
            continue;
 
        if (bSourceToDestination)
        {
            if (rConnection.msSourceId == rFrom)
                return rConnection.msDestId;
        }
        else
        {
            if (rConnection.msDestId == rFrom)
                return rConnection.msSourceId;
        }
    }
 
    return OUString();
}
 
sal_Int32 calcMaxDepth(std::u16string_view rNodeName, const svx::diagram::Connections& rConnections)
{
    sal_Int32 nMaxLength = 0;
    for (auto const& aCxn : rConnections)
        if (aCxn.mnXMLType == svx::diagram::TypeConstant::XML_parOf && aCxn.msSourceId == rNodeName)
            nMaxLength = std::max(nMaxLength, calcMaxDepth(aCxn.msDestId, rConnections) + 1);
 
    return nMaxLength;
}
}
 
sal_Int32 ConditionAtom::getNodeCount(const svx::diagram::Point* pPresPoint) const
{
    sal_Int32 nCount = 0;
    OUString sNodeId = pPresPoint->msPresentationAssociationId;
 
    // HACK: special case - count children of first child
    if (maIter.maAxis.size() == 2 && maIter.maAxis[0] == XML_ch && maIter.maAxis[1] == XML_ch)
        sNodeId = navigate(mrLayoutNode, svx::diagram::TypeConstant::XML_parOf, sNodeId, /*bSourceToDestination*/ true);
 
    if (!sNodeId.isEmpty())
    {
        for (const auto& aCxn : mrLayoutNode.getDiagram().getData()->getConnections())
            if (aCxn.mnXMLType == svx::diagram::TypeConstant::XML_parOf && aCxn.msSourceId == sNodeId)
                nCount++;
    }
 
    return nCount;
}
 
bool ConditionAtom::getDecision(const svx::diagram::Point* pPresPoint) const
{
    if (mIsElse)
        return true;
    if (!pPresPoint)
        return false;
 
    switch (maCond.mnFunc)
    {
    case XML_var:
    {
        if (maCond.mnArg == XML_dir)
            return compareResult(maCond.mnOp, pPresPoint->mnDirection, maCond.mnVal);
        else if (maCond.mnArg == XML_hierBranch)
        {
            sal_Int32 nHierarchyBranch = pPresPoint->moHierarchyBranch.value_or(XML_std);
            if (!pPresPoint->moHierarchyBranch.has_value())
            {
                // If <dgm:hierBranch> is missing in the current presentation
                // point, ask the parent.
                OUString aParent = navigate(mrLayoutNode, svx::diagram::TypeConstant::XML_presParOf, pPresPoint->msModelId,
                                            /*bSourceToDestination*/ false);
                DiagramData::PointNameMap& rPointNameMap
                    = mrLayoutNode.getDiagram().getData()->getPointNameMap();
                auto it = rPointNameMap.find(aParent);
                if (it != rPointNameMap.end())
                {
                    const svx::diagram::Point* pParent = it->second;
                    if (pParent->moHierarchyBranch.has_value())
                        nHierarchyBranch = pParent->moHierarchyBranch.value();
                }
            }
            return compareResult(maCond.mnOp, nHierarchyBranch, maCond.mnVal);
        }
        break;
    }
 
    case XML_cnt:
        return compareResult(maCond.mnOp, getNodeCount(pPresPoint), maCond.msVal.toInt32());
 
    case XML_maxDepth:
    {
        sal_Int32 nMaxDepth = calcMaxDepth(pPresPoint->msPresentationAssociationId, mrLayoutNode.getDiagram().getData()->getConnections());
        return compareResult(maCond.mnOp, nMaxDepth, maCond.msVal.toInt32());
    }
 
    case XML_depth:
    case XML_pos:
    case XML_revPos:
    case XML_posEven:
    case XML_posOdd:
        // TODO
    default:
        SAL_WARN("oox.drawingml", "unknown function " << maCond.mnFunc);
        break;
    }
 
    return true;
}
 
void ConditionAtom::accept( LayoutAtomVisitor& rVisitor )
{
    rVisitor.visit(*this);
}
 
void ConstraintAtom::accept( LayoutAtomVisitor& rVisitor )
{
    rVisitor.visit(*this);
}
 
void RuleAtom::accept( LayoutAtomVisitor& rVisitor )
{
    rVisitor.visit(*this);
}
 
void ConstraintAtom::parseConstraint(std::vector<Constraint>& rConstraints,
                                     bool bRequireForName) const
{
    // Allowlist for cases where empty forName is handled.
    if (bRequireForName)
    {
        switch (maConstraint.mnType)
        {
            case XML_sp:
            case XML_lMarg:
            case XML_rMarg:
            case XML_tMarg:
            case XML_bMarg:
                bRequireForName = false;
                break;
        }
        switch (maConstraint.mnPointType)
        {
            case XML_sibTrans:
                bRequireForName = false;
                break;
        }
    }
 
    if (bRequireForName && maConstraint.msForName.isEmpty())
        return;
 
    // accepting only basic equality constraints
    if ((maConstraint.mnOperator == XML_none || maConstraint.mnOperator == XML_equ)
        && maConstraint.mnType != XML_none)
    {
        rConstraints.push_back(maConstraint);
    }
}
 
void RuleAtom::parseRule(std::vector<Rule>& rRules) const
{
    if (!maRule.msForName.isEmpty())
    {
        rRules.push_back(maRule);
    }
}
 
void AlgAtom::accept( LayoutAtomVisitor& rVisitor )
{
    rVisitor.visit(*this);
}
 
sal_Int32 AlgAtom::getConnectorType()
{
    sal_Int32 nConnRout = 0;
    sal_Int32 nBegSty = 0;
    sal_Int32 nEndSty = 0;
    if (maMap.count(oox::XML_connRout))
        nConnRout = maMap.find(oox::XML_connRout)->second;
    if (maMap.count(oox::XML_begSty))
        nBegSty = maMap.find(oox::XML_begSty)->second;
    if (maMap.count(oox::XML_endSty))
        nEndSty = maMap.find(oox::XML_endSty)->second;
 
    if (nConnRout == oox::XML_bend)
        return 0; // was oox::XML_bentConnector3 - connectors are hidden in org chart as they don't work anyway
    if (nBegSty == oox::XML_arr && nEndSty == oox::XML_arr)
        return oox::XML_leftRightArrow;
    if (nBegSty == oox::XML_arr)
        return oox::XML_leftArrow;
    if (nEndSty == oox::XML_arr)
        return oox::XML_rightArrow;
 
    return oox::XML_rightArrow;
}
 
sal_Int32 AlgAtom::getVerticalShapesCount(const ShapePtr& rShape)
{
    if (rShape->getChildren().empty())
        return (rShape->getSubType() != XML_conn) ? 1 : 0;
 
    sal_Int32 nDir = XML_fromL;
    if (mnType == XML_hierRoot)
        nDir = XML_fromT;
    else if (maMap.count(XML_linDir))
        nDir = maMap.find(XML_linDir)->second;
 
    const sal_Int32 nSecDir = maMap.count(XML_secLinDir) ? maMap.find(XML_secLinDir)->second : 0;
 
    sal_Int32 nCount = 0;
    if (nDir == XML_fromT || nDir == XML_fromB)
    {
        for (const ShapePtr& pChild : rShape->getChildren())
            nCount += pChild->getVerticalShapesCount();
    }
    else if ((nDir == XML_fromL || nDir == XML_fromR) && nSecDir == XML_fromT)
    {
        for (const ShapePtr& pChild : rShape->getChildren())
            nCount += pChild->getVerticalShapesCount();
        nCount = (nCount + 1) / 2;
    }
    else
    {
        for (const ShapePtr& pChild : rShape->getChildren())
            nCount = std::max(nCount, pChild->getVerticalShapesCount());
    }
 
    return nCount;
}
 
namespace
{
/// Does the first data node of this shape have customized text properties?
bool HasCustomText(const ShapePtr& rShape, LayoutNode& rLayoutNode)
{
    const PresPointShapeMap& rPresPointShapeMap
        = rLayoutNode.getDiagram().getLayout()->getPresPointShapeMap();
    const DiagramData::StringMap& rPresOfNameMap
        = rLayoutNode.getDiagram().getData()->getPresOfNameMap();
    const DiagramData::PointNameMap& rPointNameMap
        = rLayoutNode.getDiagram().getData()->getPointNameMap();
    // Get the first presentation node of the shape.
    const svx::diagram::Point* pPresNode = nullptr;
    for (const auto& rPair : rPresPointShapeMap)
    {
        if (rPair.second == rShape)
        {
            pPresNode = rPair.first;
            break;
        }
    }
    // Get the first data node of the presentation node.
    svx::diagram::Point* pDataNode = nullptr;
    if (pPresNode)
    {
        auto itPresToData = rPresOfNameMap.find(pPresNode->msModelId);
        if (itPresToData != rPresOfNameMap.end())
        {
            for (const auto& rPair : itPresToData->second)
            {
                const DiagramData::SourceIdAndDepth& rItem = rPair.second;
                auto it = rPointNameMap.find(rItem.msSourceId);
                if (it != rPointNameMap.end())
                {
                    pDataNode = it->second;
                    break;
                }
            }
        }
    }
 
    // If we have a data node, see if its text is customized or not.
    if (pDataNode)
    {
        return pDataNode->mbCustomText;
    }
 
    return false;
}
}
 
void AlgAtom::layoutShape(const ShapePtr& rShape, const std::vector<Constraint>& rConstraints,
                          const std::vector<Rule>& rRules)
{
    if (mnType != XML_lin)
    {
        // TODO Handle spacing from constraints for non-lin algorithms as well.
        std::erase_if(
            rShape->getChildren(),
                           [](const ShapePtr& aChild) {
                               return aChild->getServiceName() == "com.sun.star.drawing.GroupShape"
                                      && aChild->getChildren().empty();
                           });
    }
 
    switch(mnType)
    {
        case XML_composite:
        {
            CompositeAlg::layoutShapeChildren(*this, rShape, rConstraints);
            break;
        }
 
        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();
 
                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[u""_ustr];
            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];
            // keep center position
            awt::Point aPos = rShape->getPosition();
            aPos.X += (rShape->getSize().Width - aSize.Width) / 2;
            aPos.Y += (rShape->getSize().Height - aSize.Height) / 2;
            rShape->setPosition(aPos);
            rShape->setSize(aSize);
            break;
        }
 
        case XML_cycle:
        {
            if (rShape->getChildren().empty())
                break;
 
            const sal_Int32 nStartAngle = maMap.count(XML_stAng) ? maMap.find(XML_stAng)->second : 0;
            const sal_Int32 nSpanAngle = maMap.count(XML_spanAng) ? maMap.find(XML_spanAng)->second : 360;
            const sal_Int32 nRotationPath = maMap.count(XML_rotPath) ? maMap.find(XML_rotPath)->second : XML_none;
            const sal_Int32 nctrShpMap = maMap.count(XML_ctrShpMap) ? maMap.find(XML_ctrShpMap)->second : XML_none;
            const awt::Size aCenter(rShape->getSize().Width / 2, rShape->getSize().Height / 2);
            const awt::Size aChildSize(rShape->getSize().Width / 4, rShape->getSize().Height / 4);
            const awt::Size aConnectorSize(rShape->getSize().Width / 12, rShape->getSize().Height / 12);
            const sal_Int32 nRadius = std::min(
                (rShape->getSize().Width - aChildSize.Width) / 2,
                (rShape->getSize().Height - aChildSize.Height) / 2);
 
            std::vector<oox::drawingml::ShapePtr> aCycleChildren = rShape->getChildren();
 
            if (nctrShpMap == XML_fNode)
            {
                // first node placed in center, others around
                oox::drawingml::ShapePtr pCenterShape = aCycleChildren.front();
                aCycleChildren.erase(aCycleChildren.begin());
                const awt::Point aCurrPos(aCenter.Width - aChildSize.Width / 2,
                                          aCenter.Height - aChildSize.Height / 2);
                pCenterShape->setPosition(aCurrPos);
                pCenterShape->setSize(aChildSize);
                pCenterShape->setChildSize(aChildSize);
            }
 
            const sal_Int32 nShapes = aCycleChildren.size();
            if (nShapes)
            {
                const sal_Int32 nConnectorRadius = nRadius * cos(basegfx::deg2rad(nSpanAngle / nShapes));
                const sal_Int32 nConnectorAngle = nSpanAngle > 0 ? 0 : 180;
 
                sal_Int32 idx = 0;
                for (auto & aCurrShape : aCycleChildren)
                {
                    const double fAngle = static_cast<double>(idx)*nSpanAngle/nShapes + nStartAngle;
                    awt::Size aCurrSize = aChildSize;
                    sal_Int32 nCurrRadius = nRadius;
                    if (aCurrShape->getSubType() == XML_conn)
                    {
                        aCurrSize = aConnectorSize;
                        nCurrRadius = nConnectorRadius;
                    }
                    const awt::Point aCurrPos(
                        aCenter.Width + nCurrRadius*sin(basegfx::deg2rad(fAngle)) - aCurrSize.Width/2,
                        aCenter.Height - nCurrRadius*cos(basegfx::deg2rad(fAngle)) - aCurrSize.Height/2);
 
                    aCurrShape->setPosition(aCurrPos);
                    aCurrShape->setSize(aCurrSize);
                    aCurrShape->setChildSize(aCurrSize);
 
                    if (nRotationPath == XML_alongPath)
                        aCurrShape->setRotation(fAngle * PER_DEGREE);
 
                    // connectors should be handled in conn, but we don't have
                    // reference to previous and next child, so it's easier here
                    if (aCurrShape->getSubType() == XML_conn)
                        aCurrShape->setRotation((nConnectorAngle + fAngle) * PER_DEGREE);
 
                    idx++;
                }
            }
            break;
        }
 
        case XML_hierChild:
        case XML_hierRoot:
        {
            if (rShape->getChildren().empty() || rShape->getSize().Width == 0 || rShape->getSize().Height == 0)
                break;
 
            // hierRoot is the manager -> employees vertical linear path,
            // hierChild is the first employee -> last employee horizontal
            // linear path.
            sal_Int32 nDir = XML_fromL;
            if (mnType == XML_hierRoot)
                nDir = XML_fromT;
            else if (maMap.count(XML_linDir))
                nDir = maMap.find(XML_linDir)->second;
 
            const sal_Int32 nSecDir = maMap.count(XML_secLinDir) ? maMap.find(XML_secLinDir)->second : 0;
 
            sal_Int32 nCount = rShape->getChildren().size();
 
            if (mnType == XML_hierChild)
            {
                // Connectors should not influence the size of non-connect shapes.
                nCount = std::count_if(
                    rShape->getChildren().begin(), rShape->getChildren().end(),
                    [](const ShapePtr& pShape) { return pShape->getSubType() != XML_conn; });
            }
 
            const double fSpaceWidth = 0.1;
            const double fSpaceHeight = 0.3;
 
            if (mnType == XML_hierRoot && nCount == 3)
            {
                // Order assistant nodes above employee nodes.
                std::vector<ShapePtr>& rChildren = rShape->getChildren();
                if (!containsDataNodeType(rChildren[1], XML_asst)
                    && containsDataNodeType(rChildren[2], XML_asst))
                    std::swap(rChildren[1], rChildren[2]);
            }
 
            sal_Int32 nHorizontalShapesCount = 1;
            if (nSecDir == XML_fromT)
                nHorizontalShapesCount = 2;
            else if (nDir == XML_fromL || nDir == XML_fromR)
                nHorizontalShapesCount = nCount;
 
            awt::Size aChildSize = rShape->getSize();
            aChildSize.Height /= (rShape->getVerticalShapesCount() + (rShape->getVerticalShapesCount() - 1) * fSpaceHeight);
            aChildSize.Width /= (nHorizontalShapesCount + (nHorizontalShapesCount - 1) * fSpaceWidth);
 
            awt::Size aConnectorSize = aChildSize;
            aConnectorSize.Width = 1;
 
            awt::Point aChildPos(0, 0);
 
            // indent children to show they are descendants, not siblings
            if (mnType == XML_hierChild && nHorizontalShapesCount == 1)
            {
                const double fChildIndent = 0.1;
                aChildPos.X = aChildSize.Width * fChildIndent;
                aChildSize.Width *= (1 - 2 * fChildIndent);
            }
 
            sal_Int32 nIdx = 0;
            sal_Int32 nRowHeight = 0;
            for (auto& pChild : rShape->getChildren())
            {
                pChild->setPosition(aChildPos);
 
                if (mnType == XML_hierChild && pChild->getSubType() == XML_conn)
                {
                    // Connectors should not influence the position of
                    // non-connect shapes.
                    pChild->setSize(aConnectorSize);
                    pChild->setChildSize(aConnectorSize);
                    continue;
                }
 
                awt::Size aCurrSize = aChildSize;
                aCurrSize.Height *= pChild->getVerticalShapesCount() + (pChild->getVerticalShapesCount() - 1) * fSpaceHeight;
 
                pChild->setSize(aCurrSize);
                pChild->setChildSize(aCurrSize);
 
                if (nDir == XML_fromT || nDir == XML_fromB)
                    aChildPos.Y += aCurrSize.Height + aChildSize.Height * fSpaceHeight;
                else
                    aChildPos.X += aCurrSize.Width + aCurrSize.Width * fSpaceWidth;
 
                nRowHeight = std::max(nRowHeight, aCurrSize.Height);
 
                if (nSecDir == XML_fromT && nIdx % 2 == 1)
                {
                    aChildPos.X = 0;
                    aChildPos.Y += nRowHeight + aChildSize.Height * fSpaceHeight;
                    nRowHeight = 0;
                }
 
                nIdx++;
            }
 
            break;
        }
 
        case XML_lin:
        {
            // spread children evenly across one axis, stretch across second
 
            if (rShape->getChildren().empty() || rShape->getSize().Width == 0 || rShape->getSize().Height == 0)
                break;
 
            const sal_Int32 nDir = maMap.count(XML_linDir) ? maMap.find(XML_linDir)->second : XML_fromL;
            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);
 
            double fCount = rShape->getChildren().size();
            sal_Int32 nConnectorAngle = 0;
            switch (nDir)
            {
            case XML_fromL: nConnectorAngle = 0; break;
            case XML_fromR: nConnectorAngle = 180; break;
            case XML_fromT: nConnectorAngle = 270; break;
            case XML_fromB: nConnectorAngle = 90; break;
            }
 
            awt::Size aSpaceSize;
 
            // Find out which constraint 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;
                    if (rProperty[XML_w] > rShape->getSize().Width)
                    {
                        rProperty[XML_w] = rShape->getSize().Width;
                    }
                }
                if (rConstraint.mnType == XML_h)
                {
                    rProperty[XML_h] = rShape->getSize().Height * rConstraint.mfFactor;
                    if (rProperty[XML_h] > rShape->getSize().Height)
                    {
                        rProperty[XML_h] = rShape->getSize().Height;
                    }
                }
 
                if (rConstraint.mnType == XML_primFontSz && rConstraint.mnFor == XML_des
                    && rConstraint.mnOperator == XML_equ)
                {
                    NamedShapePairs& rDiagramFontHeights
                        = getLayoutNode().getDiagram().getDiagramFontHeights();
                    auto it = rDiagramFontHeights.find(rConstraint.msForName);
                    if (it == rDiagramFontHeights.end())
                    {
                        // Start tracking all shapes with this internal name: they'll have the same
                        // font height.
                        rDiagramFontHeights[rConstraint.msForName] = {};
                    }
                }
 
                // TODO: get values from differently named constraints as well
                if (rConstraint.msForName == "sp" || rConstraint.msForName == "space" || rConstraint.msForName == "sibTrans")
                {
                    if (rConstraint.mnType == XML_w)
                        aSpaceSize.Width = rShape->getSize().Width * rConstraint.mfFactor;
                    if (rConstraint.mnType == XML_h)
                        aSpaceSize.Height = rShape->getSize().Height * rConstraint.mfFactor;
                }
            }
 
            // first approximation of children size
            std::set<OUString> aChildrenToShrink;
            for (const auto& rRule : rRules)
            {
                // Consider rules: when scaling down, only change children where the rule allows
                // doing so.
                aChildrenToShrink.insert(rRule.msForName);
            }
 
            if (nDir == XML_fromT || nDir == XML_fromB)
            {
                // TODO consider rules for vertical linear layout as well.
                aChildrenToShrink.clear();
            }
 
            if (!aChildrenToShrink.empty())
            {
                // Have scaling info from rules: then only count scaled children.
                // Also count children which are a fraction of a scaled child.
                std::set<OUString> aChildrenToShrinkDeps;
                for (auto& aCurrShape : rShape->getChildren())
                {
                    if (aChildrenToShrink.find(aCurrShape->getInternalName())
                        == aChildrenToShrink.end())
                    {
                        if (fCount > 1.0)
                        {
                            fCount -= 1.0;
 
                            bool bIsDependency = false;
                            double fFactor = 0;
                            for (const auto& rConstraint : rConstraints)
                            {
                                if (rConstraint.msForName != aCurrShape->getInternalName())
                                {
                                    continue;
                                }
 
                                if ((nDir == XML_fromL || nDir == XML_fromR) && rConstraint.mnType != XML_w)
                                {
                                    continue;
                                }
                                if ((nDir == XML_fromL || nDir == XML_fromR) && rConstraint.mnType == XML_w)
                                {
                                    fFactor = rConstraint.mfFactor;
                                }
 
                                if ((nDir == XML_fromT || nDir == XML_fromB) && rConstraint.mnType != XML_h)
                                {
                                    continue;
                                }
                                if ((nDir == XML_fromT || nDir == XML_fromB) && rConstraint.mnType == XML_h)
                                {
                                    fFactor = rConstraint.mfFactor;
                                }
 
                                if (aChildrenToShrink.find(rConstraint.msRefForName) == aChildrenToShrink.end())
                                {
                                    continue;
                                }
 
                                // At this point we have a child with a size which is a factor of an
                                // other child which will be scaled.
                                fCount += rConstraint.mfFactor;
                                aChildrenToShrinkDeps.insert(aCurrShape->getInternalName());
                                bIsDependency = true;
                                break;
                            }
 
                            if (!bIsDependency && aCurrShape->getServiceName() == "com.sun.star.drawing.GroupShape")
                            {
                                bool bScaleDownEmptySpacing = false;
                                if (nDir == XML_fromL || nDir == XML_fromR)
                                {
                                    std::optional<sal_Int32> oWidth = findProperty(aProperties, aCurrShape->getInternalName(), XML_w);
                                    bScaleDownEmptySpacing = oWidth.has_value() && oWidth.value() > 0;
                                }
                                if (!bScaleDownEmptySpacing && (nDir == XML_fromT || nDir == XML_fromB))
                                {
                                    std::optional<sal_Int32> oHeight = findProperty(aProperties, aCurrShape->getInternalName(), XML_h);
                                    bScaleDownEmptySpacing = oHeight.has_value() && oHeight.value() > 0;
                                }
                                if (bScaleDownEmptySpacing && aCurrShape->getChildren().empty())
                                {
                                    fCount += fFactor;
                                    aChildrenToShrinkDeps.insert(aCurrShape->getInternalName());
                                }
                            }
                        }
                    }
                }
 
                aChildrenToShrink.insert(aChildrenToShrinkDeps.begin(), aChildrenToShrinkDeps.end());
 
                // No manual spacing: spacings are children as well.
                aSpaceSize = awt::Size();
            }
            else
            {
                // TODO Handle spacing from constraints without rules as well.
                std::erase_if(
                    rShape->getChildren(),
                                   [](const ShapePtr& aChild) {
                                       return aChild->getServiceName()
                                                  == "com.sun.star.drawing.GroupShape"
                                              && aChild->getChildren().empty();
                                   });
                fCount = rShape->getChildren().size();
            }
            awt::Size aChildSize = rShape->getSize();
            if (nDir == XML_fromL || nDir == XML_fromR)
                aChildSize.Width /= fCount;
            else if (nDir == XML_fromT || nDir == XML_fromB)
                aChildSize.Height /= fCount;
 
            awt::Point aCurrPos(0, 0);
            if (nIncX == -1)
                aCurrPos.X = rShape->getSize().Width - aChildSize.Width;
            if (nIncY == -1)
                aCurrPos.Y = rShape->getSize().Height - aChildSize.Height;
 
            // See if children requested more than 100% space in total: scale
            // down in that case.
            awt::Size aTotalSize;
            for (const auto & aCurrShape : rShape->getChildren())
            {
                std::optional<sal_Int32> oWidth = findProperty(aProperties, aCurrShape->getInternalName(), XML_w);
                std::optional<sal_Int32> oHeight = findProperty(aProperties, aCurrShape->getInternalName(), XML_h);
                awt::Size aSize = aChildSize;
                if (oWidth.has_value())
                    aSize.Width = oWidth.value();
                if (oHeight.has_value())
                    aSize.Height = oHeight.value();
                aTotalSize.Width += aSize.Width;
                aTotalSize.Height += aSize.Height;
            }
 
            aTotalSize.Width += (fCount-1) * aSpaceSize.Width;
            aTotalSize.Height += (fCount-1) * aSpaceSize.Height;
 
            double fWidthScale = 1.0;
            double fHeightScale = 1.0;
            if (nIncX && aTotalSize.Width > rShape->getSize().Width)
                fWidthScale = static_cast<double>(rShape->getSize().Width) / aTotalSize.Width;
            if (nIncY && aTotalSize.Height > rShape->getSize().Height)
                fHeightScale = static_cast<double>(rShape->getSize().Height) / aTotalSize.Height;
            aSpaceSize.Width *= fWidthScale;
            aSpaceSize.Height *= fHeightScale;
 
            for (auto& aCurrShape : rShape->getChildren())
            {
                // Extract properties relevant for this shape from constraints.
                std::optional<sal_Int32> oWidth = findProperty(aProperties, aCurrShape->getInternalName(), XML_w);
                std::optional<sal_Int32> oHeight = findProperty(aProperties, aCurrShape->getInternalName(), XML_h);
 
                awt::Size aSize = aChildSize;
                if (oWidth.has_value())
                    aSize.Width = oWidth.value();
                if (oHeight.has_value())
                    aSize.Height = oHeight.value();
                if (aChildrenToShrink.empty()
                    || aChildrenToShrink.find(aCurrShape->getInternalName())
                           != aChildrenToShrink.end())
                {
                    aSize.Width *= fWidthScale;
                }
                if (aChildrenToShrink.empty()
                    || aChildrenToShrink.find(aCurrShape->getInternalName())
                           != aChildrenToShrink.end())
                {
                    aSize.Height *= fHeightScale;
                }
                aCurrShape->setSize(aSize);
                aCurrShape->setChildSize(aSize);
 
                // center in the other axis - probably some parameter controls it
                if (nIncX)
                    aCurrPos.Y = (rShape->getSize().Height - aSize.Height) / 2;
                if (nIncY)
                    aCurrPos.X = (rShape->getSize().Width - aSize.Width) / 2;
                if (aCurrPos.X < 0)
                {
                    aCurrPos.X = 0;
                }
                if (aCurrPos.Y < 0)
                {
                    aCurrPos.Y = 0;
                }
 
                aCurrShape->setPosition(aCurrPos);
 
                aCurrPos.X += nIncX * (aSize.Width + aSpaceSize.Width);
                aCurrPos.Y += nIncY * (aSize.Height + aSpaceSize.Height);
 
                // connectors should be handled in conn, but we don't have
                // reference to previous and next child, so it's easier here
                if (aCurrShape->getSubType() == XML_conn)
                    aCurrShape->setRotation(nConnectorAngle * PER_DEGREE);
            }
 
            // Newer shapes are behind older ones by default. Reverse this if requested.
            sal_Int32 nChildOrder = XML_b;
            const LayoutNode* pParentLayoutNode = nullptr;
            for (LayoutAtomPtr pAtom = getParent(); pAtom; pAtom = pAtom->getParent())
            {
                auto pLayoutNode = dynamic_cast<LayoutNode*>(pAtom.get());
                if (pLayoutNode)
                {
                    pParentLayoutNode = pLayoutNode;
                    break;
                }
            }
            if (pParentLayoutNode)
            {
                nChildOrder = pParentLayoutNode->getChildOrder();
            }
            if (nChildOrder == XML_t)
            {
                std::reverse(rShape->getChildren().begin(), rShape->getChildren().end());
            }
 
            break;
        }
 
        case XML_pyra:
        {
            PyraAlg::layoutShapeChildren(rShape);
            break;
        }
 
        case XML_snake:
        {
            SnakeAlg::layoutShapeChildren(*this, rShape, rConstraints);
            break;
        }
 
        case XML_sp:
        {
            // HACK: Handled one level higher. Or rather, planned to
            // HACK: text should appear only in tx node; we're assigning it earlier, so let's remove it here
            rShape->setTextBody(TextBodyPtr());
            break;
        }
 
        case XML_tx:
        {
            // adjust text alignment
 
            // Parse constraints, only self margins as a start.
            double fFontSize = 0;
            for (const auto& rConstr : rConstraints)
            {
                if (rConstr.mnRefType == XML_w)
                {
                    if (!rConstr.msForName.isEmpty())
                        continue;
 
                    sal_Int32 nProperty = getPropertyFromConstraint(rConstr.mnType);
                    if (!nProperty)
                        continue;
 
                    // PowerPoint takes size as points, but gives margin as MMs.
                    double fFactor = convertPointToMms(rConstr.mfFactor);
 
                    // DrawingML works in EMUs, UNO API works in MM100s.
                    sal_Int32 nValue = o3tl::convert(rShape->getSize().Width * fFactor,
                                                     o3tl::Length::emu, o3tl::Length::mm100);
 
                    rShape->getShapeProperties().setProperty(nProperty, nValue);
                }
                if (rConstr.mnType == XML_primFontSz)
                    fFontSize = rConstr.mfValue;
            }
 
            TextBodyPtr pTextBody = rShape->getTextBody();
            if (!pTextBody || pTextBody->isEmpty())
                break;
 
            // adjust text size to fit shape
            if (fFontSize != 0)
            {
                for (auto& aParagraph : pTextBody->getParagraphs())
                    for (auto& aRun : aParagraph->getRuns())
                        if (!aRun->getTextCharacterProperties().moHeight.has_value())
                            aRun->getTextCharacterProperties().moHeight = fFontSize * 100;
            }
 
            if (!HasCustomText(rShape, getLayoutNode()))
            {
                // No customized text properties: enable autofit.
                pTextBody->getTextProperties().maPropertyMap.setProperty(
                    PROP_TextFitToSize, drawing::TextFitToSizeType_AUTOFIT);
            }
 
            // ECMA-376-1:2016 21.4.7.5 ST_AutoTextRotation (Auto Text Rotation)
            const sal_Int32 nautoTxRot = maMap.count(XML_autoTxRot) ? maMap.find(XML_autoTxRot)->second : XML_upr;
            sal_Int32 nShapeRot = rShape->getRotation();
            while (nShapeRot < 0)
                nShapeRot += 360 * PER_DEGREE;
            while (nShapeRot > 360 * PER_DEGREE)
                nShapeRot -= 360 * PER_DEGREE;
 
            switch(nautoTxRot)
            {
                case XML_upr:
                {
                    int n90x = 0;
                    if (nShapeRot >= 315 * PER_DEGREE)
                        /* keep 0 */;
                    else if (nShapeRot > 225 * PER_DEGREE)
                        n90x = -3;
                    else if (nShapeRot >= 135 * PER_DEGREE)
                        n90x = -2;
                    else if (nShapeRot > 45 * PER_DEGREE)
                        n90x = -1;
                    pTextBody->getTextProperties().moTextPreRotation = n90x * 90 * PER_DEGREE;
                }
                break;
                case XML_grav:
                {
                    if (nShapeRot > (90 * PER_DEGREE) && nShapeRot < (270 * PER_DEGREE))
                        pTextBody->getTextProperties().moTextPreRotation = -180 * PER_DEGREE;
                }
                break;
                case XML_none:
                break;
            }
 
            const sal_Int32 atxAnchorVert = maMap.count(XML_txAnchorVert) ? maMap.find(XML_txAnchorVert)->second : XML_mid;
 
            switch(atxAnchorVert)
            {
                case XML_t:
                pTextBody->getTextProperties().meVA = css::drawing::TextVerticalAdjust_TOP;
                break;
                case XML_b:
                pTextBody->getTextProperties().meVA = css::drawing::TextVerticalAdjust_BOTTOM;
                break;
                case XML_mid:
                // text centered vertically by default
                default:
                pTextBody->getTextProperties().meVA = css::drawing::TextVerticalAdjust_CENTER;
                break;
            }
 
            pTextBody->getTextProperties().maPropertyMap.setProperty(PROP_TextVerticalAdjust, pTextBody->getTextProperties().meVA);
 
            // normalize list level
            sal_Int32 nBaseLevel = pTextBody->getParagraphs().front()->getProperties().getLevel();
            for (auto & aParagraph : pTextBody->getParagraphs())
            {
                if (aParagraph->getProperties().getLevel() < nBaseLevel)
                    nBaseLevel = aParagraph->getProperties().getLevel();
            }
 
            // Start bullets at:
            // 1 - top level
            // 2 - with children (default)
            int nStartBulletsAtLevel = 2;
            ParamMap::const_iterator aBulletLvl = maMap.find(XML_stBulletLvl);
            if (aBulletLvl != maMap.end())
                nStartBulletsAtLevel = aBulletLvl->second;
            nStartBulletsAtLevel--;
 
            bool isBulletList = false;
            for (auto & aParagraph : pTextBody->getParagraphs())
            {
                sal_Int32 nLevel = aParagraph->getProperties().getLevel() - nBaseLevel;
                aParagraph->getProperties().setLevel(nLevel);
                if (nLevel >= nStartBulletsAtLevel)
                {
                    if (!aParagraph->getProperties().getParaLeftMargin().has_value())
                    {
                        sal_Int32 nLeftMargin
                            = o3tl::convert(285750 * (nLevel - nStartBulletsAtLevel + 1),
                                            o3tl::Length::emu, o3tl::Length::mm100);
                        aParagraph->getProperties().getParaLeftMargin() = nLeftMargin;
                    }
 
                    if (!aParagraph->getProperties().getFirstLineIndentation().has_value())
                        aParagraph->getProperties().getFirstLineIndentation()
                            = o3tl::convert(-285750, o3tl::Length::emu, o3tl::Length::mm100);
 
                    // It is not possible to change the bullet style for text.
                    aParagraph->getProperties().getBulletList().setBulletChar(u"•"_ustr);
                    aParagraph->getProperties().getBulletList().setSuffixNone();
                    isBulletList = true;
                }
            }
 
            // explicit alignment
            ParamMap::const_iterator aDir = maMap.find(XML_parTxLTRAlign);
            // TODO: XML_parTxRTLAlign
            if (aDir != maMap.end())
            {
                css::style::ParagraphAdjust aAlignment = GetParaAdjust(aDir->second);
                for (auto & aParagraph : pTextBody->getParagraphs())
                    aParagraph->getProperties().setParaAdjust(aAlignment);
            }
            else if (!isBulletList)
            {
                // if not list use default alignment - centered
                for (auto & aParagraph : pTextBody->getParagraphs())
                    aParagraph->getProperties().setParaAdjust(css::style::ParagraphAdjust::ParagraphAdjust_CENTER);
            }
            break;
        }
 
        default:
            break;
    }
 
    SAL_INFO(
        "oox.drawingml",
        "Layouting shape " << rShape->getInternalName() << ", alg type: " << mnType << ", ("
        << rShape->getPosition().X << "," << rShape->getPosition().Y << ","
        << rShape->getSize().Width << "," << rShape->getSize().Height << ")");
}
 
void LayoutNode::accept( LayoutAtomVisitor& rVisitor )
{
    rVisitor.visit(*this);
}
 
bool LayoutNode::setupShape( const ShapePtr& rShape, const svx::diagram::Point* pPresNode, sal_Int32 nCurrIdx ) const
{
    SAL_INFO(
        "oox.drawingml",
        "Filling content from layout node named \"" << msName
            << "\", modelId \"" << pPresNode->msModelId << "\"");
 
    // have the presentation node - now, need the actual data node:
    const DiagramData::StringMap::const_iterator aNodeName = mrDgm.getData()->getPresOfNameMap().find(
        pPresNode->msModelId);
    if( aNodeName != mrDgm.getData()->getPresOfNameMap().end() )
    {
        // Calculate the depth of what is effectively the topmost element.
        sal_Int32 nMinDepth = std::numeric_limits<sal_Int32>::max();
        for (const auto& rPair : aNodeName->second)
        {
            if (rPair.second.mnDepth < nMinDepth)
                nMinDepth = rPair.second.mnDepth;
        }
 
        for (const auto& rPair : aNodeName->second)
        {
            const DiagramData::SourceIdAndDepth& rItem = rPair.second;
            DiagramData::PointNameMap& rMap = mrDgm.getData()->getPointNameMap();
            // pPresNode is the presentation node of the aDataNode2 data node.
            DiagramData::PointNameMap::const_iterator aDataNode2 = rMap.find(rItem.msSourceId);
            if (aDataNode2 == rMap.end())
            {
                //busted, skip it
                continue;
            }
 
            Shape* pDataNode2Shape(mrDgm.getData()->getOrCreateAssociatedShape(*aDataNode2->second));
            if (nullptr == pDataNode2Shape)
            {
                //busted, skip it
                continue;
            }
 
            rShape->setDataNodeType(aDataNode2->second->mnXMLType);
 
            if (rItem.mnDepth == 0)
            {
                // grab shape attr from topmost element(s)
                rShape->getShapeProperties() = pDataNode2Shape->getShapeProperties();
                rShape->getLineProperties() = pDataNode2Shape->getLineProperties();
                rShape->getFillProperties() = pDataNode2Shape->getFillProperties();
                rShape->getCustomShapeProperties() = pDataNode2Shape->getCustomShapeProperties();
                rShape->setMasterTextListStyle( pDataNode2Shape->getMasterTextListStyle() );
 
                SAL_INFO(
                    "oox.drawingml",
                    "Custom shape with preset type "
                        << (rShape->getCustomShapeProperties()
                            ->getShapePresetType())
                        << " added for layout node named \"" << msName
                        << "\"");
            }
            else if (rItem.mnDepth == nMinDepth)
            {
                // If no real topmost element, then take properties from the one that's the closest
                // to topmost.
                rShape->getLineProperties() = pDataNode2Shape->getLineProperties();
                rShape->getFillProperties() = pDataNode2Shape->getFillProperties();
            }
 
            // append text with right outline level
            if( pDataNode2Shape->getTextBody() &&
                !pDataNode2Shape->getTextBody()->getParagraphs().empty() &&
                !pDataNode2Shape->getTextBody()->getParagraphs().front()->getRuns().empty() )
            {
                TextBodyPtr pTextBody=rShape->getTextBody();
                if( !pTextBody )
                {
                    pTextBody = std::make_shared<TextBody>();
 
                    // also copy text attrs
                    pTextBody->getTextListStyle() =
                        pDataNode2Shape->getTextBody()->getTextListStyle();
                    pTextBody->getTextProperties() =
                        pDataNode2Shape->getTextBody()->getTextProperties();
 
                    rShape->setTextBody(pTextBody);
                }
 
                const TextParagraphVector& rSourceParagraphs
                    = pDataNode2Shape->getTextBody()->getParagraphs();
                for (const auto& pSourceParagraph : rSourceParagraphs)
                {
                    TextParagraph& rPara = pTextBody->addParagraph();
                    if (rItem.mnDepth != -1)
                        rPara.getProperties().setLevel(rItem.mnDepth);
 
                    for (const auto& pRun : pSourceParagraph->getRuns())
                        rPara.addRun(pRun);
                    const TextBodyPtr& rBody = pDataNode2Shape->getTextBody();
                    rPara.getProperties().apply(rBody->getParagraphs().front()->getProperties());
                }
            }
        }
    }
    else
    {
        SAL_INFO(
            "oox.drawingml",
            "ShapeCreationVisitor::visit: no data node name found while"
                " processing shape type "
                << rShape->getCustomShapeProperties()->getShapePresetType()
                << " for layout node named \"" << msName << "\"");
        Shape* pPresNodeShape(mrDgm.getData()->getOrCreateAssociatedShape(*pPresNode));
        if (nullptr != pPresNodeShape)
            rShape->getFillProperties().assignUsed(pPresNodeShape->getFillProperties());
    }
 
    // TODO(Q1): apply styling & coloring - take presentation
    // point's presStyleLbl for both style & color
    // if not found use layout node's styleLbl
    // however, docs are a bit unclear on this
    OUString aStyleLabel = pPresNode->msPresentationLayoutStyleLabel;
    if (aStyleLabel.isEmpty())
        aStyleLabel = msStyleLabel;
    if( !aStyleLabel.isEmpty() )
    {
        const DiagramQStyleMap::const_iterator aStyle = mrDgm.getStyles().find(aStyleLabel);
        if( aStyle != mrDgm.getStyles().end() )
        {
            const DiagramStyle& rStyle = aStyle->second;
            rShape->getShapeStyleRefs()[XML_fillRef] = rStyle.maFillStyle;
            rShape->getShapeStyleRefs()[XML_lnRef] = rStyle.maLineStyle;
            rShape->getShapeStyleRefs()[XML_effectRef] = rStyle.maEffectStyle;
            rShape->getShapeStyleRefs()[XML_fontRef] = rStyle.maTextStyle;
        }
        else
        {
            SAL_WARN("oox.drawingml", "Style " << aStyleLabel << " not found");
        }
 
        const DiagramColorMap::const_iterator aColor = mrDgm.getColors().find(aStyleLabel);
        if( aColor != mrDgm.getColors().end() )
        {
            // Take the nth color from the color list in case we are the nth shape in a
            // <dgm:forEach> loop.
            const DiagramColor& rColor=aColor->second;
            if( !rColor.maFillColors.empty() )
                rShape->getShapeStyleRefs()[XML_fillRef].maPhClr = DiagramColor::getColorByIndex(rColor.maFillColors, nCurrIdx);
            if( !rColor.maLineColors.empty() )
                rShape->getShapeStyleRefs()[XML_lnRef].maPhClr = DiagramColor::getColorByIndex(rColor.maLineColors, nCurrIdx);
            if( !rColor.maEffectColors.empty() )
                rShape->getShapeStyleRefs()[XML_effectRef].maPhClr = DiagramColor::getColorByIndex(rColor.maEffectColors, nCurrIdx);
            if( !rColor.maTextFillColors.empty() )
                rShape->getShapeStyleRefs()[XML_fontRef].maPhClr = DiagramColor::getColorByIndex(rColor.maTextFillColors, nCurrIdx);
        }
    }
 
    // even if no data node found, successful anyway. it's
    // contained at the layoutnode
    return true;
}
 
const LayoutNode* LayoutNode::getParentLayoutNode() const
{
    for (LayoutAtomPtr pAtom = getParent(); pAtom; pAtom = pAtom->getParent())
    {
        auto pLayoutNode = dynamic_cast<LayoutNode*>(pAtom.get());
        if (pLayoutNode)
            return pLayoutNode;
    }
 
    return nullptr;
}
 
void ShapeAtom::accept( LayoutAtomVisitor& rVisitor )
{
    rVisitor.visit(*this);
}
 
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V581 The conditional expressions of the 'if' statements situated alongside each other are identical. Check lines: 1603, 1609.

V1048 The 'nIncX' variable was assigned the same value.

V1048 The 'nIncY' variable was assigned the same value.

V1048 The 'nIncY' variable was assigned the same value.

V1048 The 'nIncX' variable was assigned the same value.