diff options
Diffstat (limited to 'oox/source/drawingml/diagram/diagramlayoutatoms.cxx')
-rw-r--r-- | oox/source/drawingml/diagram/diagramlayoutatoms.cxx | 2045 |
1 files changed, 2045 insertions, 0 deletions
diff --git a/oox/source/drawingml/diagram/diagramlayoutatoms.cxx b/oox/source/drawingml/diagram/diagramlayoutatoms.cxx new file mode 100644 index 0000000000..6ee4b88322 --- /dev/null +++ b/oox/source/drawingml/diagram/diagramlayoutatoms.cxx @@ -0,0 +1,2045 @@ +/* -*- 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[""]; + 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[""]; + + 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[""]; + 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: */ |