/* -*- 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 #include "layoutatomvisitorbase.hxx" #include #include #include #include #include #include #include #include #include #include #include #include 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 findProperty(const oox::drawingml::LayoutPropertyMap& rProperties, const OUString& rInternalName, sal_Int32 nProperty) { std::optional 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& 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 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 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(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(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(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( 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& 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::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(pDirectChild.get()); if (!pLayoutNode) { continue; } if (pLayoutNode->getName() != aCurrShape->getInternalName()) { continue; } for (const auto& pChild : pLayoutNode->getChildren()) { auto pConstraintAtom = dynamic_cast(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 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 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& 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& 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& rConstraints, const std::vector& 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 aDirectConstraints; const LayoutNode& rLayoutNode = getLayoutNode(); for (const auto& pChild : rLayoutNode.getChildren()) { auto pConstraintAtom = dynamic_cast(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 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(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& 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 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 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 oWidth = findProperty(aProperties, aCurrShape->getInternalName(), XML_w); bScaleDownEmptySpacing = oWidth.has_value() && oWidth.value() > 0; } if (!bScaleDownEmptySpacing && (nDir == XML_fromT || nDir == XML_fromB)) { std::optional 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 oWidth = findProperty(aProperties, aCurrShape->getInternalName(), XML_w); std::optional 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(rShape->getSize().Width) / aTotalSize.Width; if (nIncY && aTotalSize.Height > rShape->getSize().Height) fHeightScale = static_cast(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 oWidth = findProperty(aProperties, aCurrShape->getInternalName(), XML_w); std::optional 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(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::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(); // 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 // 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(pAtom.get()); if (pLayoutNode) return pLayoutNode; } return nullptr; } void ShapeAtom::accept( LayoutAtomVisitor& rVisitor ) { rVisitor.visit(*this); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */