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