2045 lines
77 KiB
C++
2045 lines
77 KiB
C++
/* -*- 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: */
|