diff options
Diffstat (limited to 'oox/source/drawingml/diagram')
24 files changed, 6081 insertions, 0 deletions
diff --git a/oox/source/drawingml/diagram/constraintlistcontext.cxx b/oox/source/drawingml/diagram/constraintlistcontext.cxx new file mode 100644 index 0000000000..9233c9235c --- /dev/null +++ b/oox/source/drawingml/diagram/constraintlistcontext.cxx @@ -0,0 +1,78 @@ +/* -*- 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 "constraintlistcontext.hxx" +#include <oox/helper/attributelist.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/tokens.hxx> + +using namespace ::oox::core; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; + +namespace oox::drawingml { + +// CT_ConstraintLists +ConstraintListContext::ConstraintListContext( ContextHandler2Helper const & rParent, + const LayoutAtomPtr &pNode ) + : ContextHandler2( rParent ) + , mpNode( pNode ) +{ + assert( pNode && "Node must NOT be NULL" ); +} + +ConstraintListContext::~ConstraintListContext() +{ +} + +ContextHandlerRef +ConstraintListContext::onCreateContext( ::sal_Int32 aElement, + const AttributeList& rAttribs ) +{ + switch( aElement ) + { + case DGM_TOKEN( constr ): + { + auto pNode = std::make_shared<ConstraintAtom>(mpNode->getLayoutNode()); + LayoutAtom::connect(mpNode, pNode); + + Constraint& rConstraint = pNode->getConstraint(); + rConstraint.mnFor = rAttribs.getToken( XML_for, XML_none ); + rConstraint.msForName = rAttribs.getStringDefaulted( XML_forName); + rConstraint.mnPointType = rAttribs.getToken( XML_ptType, XML_none ); + rConstraint.mnType = rAttribs.getToken( XML_type, XML_none ); + rConstraint.mnRefFor = rAttribs.getToken( XML_refFor, XML_none ); + rConstraint.msRefForName = rAttribs.getStringDefaulted( XML_refForName); + rConstraint.mnRefType = rAttribs.getToken( XML_refType, XML_none ); + rConstraint.mnRefPointType = rAttribs.getToken( XML_refPtType, XML_none ); + rConstraint.mfFactor = rAttribs.getDouble( XML_fact, 1.0 ); + rConstraint.mfValue = rAttribs.getDouble( XML_val, 0.0 ); + rConstraint.mnOperator = rAttribs.getToken( XML_op, XML_none ); + break; + } + default: + break; + } + + return this; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/constraintlistcontext.hxx b/oox/source/drawingml/diagram/constraintlistcontext.hxx new file mode 100644 index 0000000000..ba3b593555 --- /dev/null +++ b/oox/source/drawingml/diagram/constraintlistcontext.hxx @@ -0,0 +1,43 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_CONSTRAINTLISTCONTEXT_HXX +#define INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_CONSTRAINTLISTCONTEXT_HXX + +#include <oox/core/contexthandler2.hxx> +#include "diagramlayoutatoms.hxx" + +namespace oox::drawingml { + +class ConstraintListContext : public ::oox::core::ContextHandler2 +{ +public: + ConstraintListContext( ContextHandler2Helper const & rParent, const LayoutAtomPtr &pNode ); + virtual ~ConstraintListContext() override; + + virtual ::oox::core::ContextHandlerRef onCreateContext( ::sal_Int32 Element, const AttributeList& rAttribs ) override; +private: + LayoutAtomPtr mpNode; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/datamodel.cxx b/oox/source/drawingml/diagram/datamodel.cxx new file mode 100644 index 0000000000..97691af43f --- /dev/null +++ b/oox/source/drawingml/diagram/datamodel.cxx @@ -0,0 +1,468 @@ +/* -*- 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 "datamodel.hxx" + +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <drawingml/fillproperties.hxx> +#include <drawingml/textbody.hxx> +#include <drawingml/textparagraph.hxx> +#include <drawingml/textrun.hxx> +#include <oox/drawingml/shape.hxx> +#include <com/sun/star/beans/XPropertyState.hpp> +#include <com/sun/star/drawing/FillStyle.hpp> +#include <com/sun/star/drawing/LineStyle.hpp> +#include <editeng/unoprnms.hxx> + +#include <unordered_set> + +using namespace ::com::sun::star; + +namespace oox::drawingml { + +Shape* DiagramData::getOrCreateAssociatedShape(const svx::diagram::Point& rPoint, bool bCreateOnDemand) const +{ + if(maPointShapeMap.end() == maPointShapeMap.find(rPoint.msModelId)) + { + const_cast<DiagramData*>(this)->maPointShapeMap[rPoint.msModelId] = ShapePtr(); + } + + const ShapePtr& rShapePtr = maPointShapeMap.find(rPoint.msModelId)->second; + + if(!rShapePtr && bCreateOnDemand) + { + const_cast<ShapePtr&>(rShapePtr) = std::make_shared<Shape>(); + + // If we did create a new oox::drawingml::Shape, directly apply + // available data from the Diagram ModelData to it as preparation + restoreDataFromModelToShapeAfterReCreation(rPoint, *rShapePtr); + } + + return rShapePtr.get(); +} + +void DiagramData::restoreDataFromModelToShapeAfterReCreation(const svx::diagram::Point& rPoint, Shape& rNewShape) +{ + // If we did create a new oox::drawingml::Shape, directly apply + // available data from the Diagram ModelData to it as preparation + + // This is e.g. the Text, but may get more (styles?) + if(!rPoint.msTextBody->msText.isEmpty()) + { + TextBodyPtr aNewTextBody(std::make_shared<TextBody>()); + rNewShape.setTextBody(aNewTextBody); + TextRunPtr pTextRun = std::make_shared<TextRun>(); + pTextRun->getText() = rPoint.msTextBody->msText; + aNewTextBody->addParagraph().addRun(pTextRun); + + if(!rPoint.msTextBody->maTextProps.empty()) + { + oox::PropertyMap& rTargetMap(aNewTextBody->getTextProperties().maPropertyMap); + + for (auto const& prop : rPoint.msTextBody->maTextProps) + { + const sal_Int32 nPropId(oox::PropertyMap::getPropertyId(prop.first)); + if(nPropId > 0) + rTargetMap.setAnyProperty(nPropId, prop.second); + } + } + } +} + +static void addProperty(const OUString& rName, + const css::uno::Reference< css::beans::XPropertySetInfo >& xInfo, + std::vector< std::pair< OUString, css::uno::Any >>& rTarget, + const css::uno::Reference< css::beans::XPropertySet >& xPropSet ) +{ + if(xInfo->hasPropertyByName(rName)) + rTarget.push_back(std::pair(OUString(rName), xPropSet->getPropertyValue(rName))); +} + +void DiagramData::secureStyleDataFromShapeToModel(::oox::drawingml::Shape& rShape) +{ + const std::vector< ShapePtr >& rChildren(rShape.getChildren()); + + if(!rChildren.empty()) + { + // group shape + for (auto& child : rChildren) + { + secureStyleDataFromShapeToModel(*child); + } + + // if group shape we are done. Do not secure properties for group shapes + return; + } + + // we need a XShape + const css::uno::Reference< css::drawing::XShape > &rXShape(rShape.getXShape()); + if(!rXShape) + return; + + // we need a ModelID for association + if(rShape.getDiagramDataModelID().isEmpty()) + return; + + // define target to save to + svx::diagram::PointStyle* pTarget(nullptr); + const bool bIsBackgroundShape(rShape.getDiagramDataModelID() == msBackgroundShapeModelID); + + if(bIsBackgroundShape) + { + // if BackgroundShape, create properties & set as target + if(!maBackgroundShapeStyle) + maBackgroundShapeStyle = std::make_shared< svx::diagram::PointStyle >(); + pTarget = maBackgroundShapeStyle.get(); + } + else + { + // if Shape, seek association + for (auto & point : maPoints) + { + if(point.msModelId == rShape.getDiagramDataModelID()) + { + // found - create properties & set as target + pTarget = point.msPointStylePtr.get(); + + // we are done, there is no 2nd shape with the same ModelID by definition + break; + } + } + } + + // no target -> nothing to do + if(nullptr == pTarget) + return; + +#ifdef DBG_UTIL + // to easier decide which additional properties may/should be preserved, + // create a full list of set properties to browse/decide (in debugger) + const css::uno::Reference< css::beans::XPropertyState > xAllPropStates(rXShape, css::uno::UNO_QUERY); + const css::uno::Reference< css::beans::XPropertySet > xAllPropSet( rXShape, css::uno::UNO_QUERY ); + const css::uno::Sequence< css::beans::Property > allSequence(xAllPropSet->getPropertySetInfo()->getProperties()); + std::vector< std::pair< OUString, css::uno::Any >> allSetProps; + for (auto& rProp : allSequence) + { + try + { + if (xAllPropStates->getPropertyState(rProp.Name) == css::beans::PropertyState::PropertyState_DIRECT_VALUE) + { + css::uno::Any aValue(xAllPropSet->getPropertyValue(rProp.Name)); + if(aValue.hasValue()) + allSetProps.push_back(std::pair(rProp.Name, aValue)); + } + } + catch (...) + { + } + } +#endif + + const css::uno::Reference< css::beans::XPropertySet > xPropSet( rXShape, css::uno::UNO_QUERY ); + if(!xPropSet) + return; + + const css::uno::Reference< css::lang::XServiceInfo > xServiceInfo( rXShape, css::uno::UNO_QUERY ); + if(!xServiceInfo) + return; + + const css::uno::Reference< css::beans::XPropertySetInfo > xInfo(xPropSet->getPropertySetInfo()); + if (!xInfo.is()) + return; + + // Note: The Text may also be secured here, so it may also be possible to + // secure/store it at PointStyle instead of at TextBody, same maybe evaluated + // for the text attributes - where when securing here the attributes would be + // in our UNO API format already. + // if(xServiceInfo->supportsService("com.sun.star.drawing.Text")) + // { + // css::uno::Reference< css::text::XText > xText(rXShape, css::uno::UNO_QUERY); + // const OUString aText(xText->getString()); + // + // if(!aText.isEmpty()) + // { + // } + // } + + // Add all kinds of properties that are needed to re-create the XShape. + // For now this is a minimal example-selection, it will need to be extended + // over time for all kind of cases/properties + + // text properties + if(!bIsBackgroundShape + && xServiceInfo->supportsService("com.sun.star.drawing.TextProperties")) + { + addProperty(UNO_NAME_CHAR_COLOR, xInfo, pTarget->maProperties, xPropSet); + addProperty(UNO_NAME_CHAR_HEIGHT, xInfo, pTarget->maProperties, xPropSet); + addProperty(UNO_NAME_CHAR_SHADOWED, xInfo, pTarget->maProperties, xPropSet); + addProperty(UNO_NAME_CHAR_WEIGHT, xInfo, pTarget->maProperties, xPropSet); + } + + // fill properties + if(xServiceInfo->supportsService("com.sun.star.drawing.FillProperties")) + { + css::drawing::FillStyle eFillStyle(css::drawing::FillStyle_NONE); + if (xInfo->hasPropertyByName(UNO_NAME_FILLSTYLE)) + xPropSet->getPropertyValue(UNO_NAME_FILLSTYLE) >>= eFillStyle; + + if(css::drawing::FillStyle_NONE != eFillStyle) + { + addProperty(UNO_NAME_FILLSTYLE, xInfo, pTarget->maProperties, xPropSet); + + switch(eFillStyle) + { + case css::drawing::FillStyle_SOLID: + { + addProperty(UNO_NAME_FILLCOLOR, xInfo, pTarget->maProperties, xPropSet); + break; + } + default: + case css::drawing::FillStyle_NONE: + case css::drawing::FillStyle_GRADIENT: + case css::drawing::FillStyle_HATCH: + case css::drawing::FillStyle_BITMAP: + break; + } + } + } + + // line properties + if(!bIsBackgroundShape + && xServiceInfo->supportsService("com.sun.star.drawing.LineProperties")) + { + css::drawing::LineStyle eLineStyle(css::drawing::LineStyle_NONE); + if (xInfo->hasPropertyByName(UNO_NAME_LINESTYLE)) + xPropSet->getPropertyValue(UNO_NAME_LINESTYLE) >>= eLineStyle; + + if(css::drawing::LineStyle_NONE != eLineStyle) + { + addProperty(UNO_NAME_LINESTYLE, xInfo, pTarget->maProperties, xPropSet); + addProperty(UNO_NAME_LINECOLOR, xInfo, pTarget->maProperties, xPropSet); + addProperty(UNO_NAME_LINEWIDTH, xInfo, pTarget->maProperties, xPropSet); + + switch(eLineStyle) + { + case css::drawing::LineStyle_SOLID: + break; + default: + case css::drawing::LineStyle_NONE: + case css::drawing::LineStyle_DASH: + break; + } + } + } +} + +void DiagramData::secureDataFromShapeToModelAfterDiagramImport(::oox::drawingml::Shape& rRootShape) +{ + const std::vector< ShapePtr >& rChildren(rRootShape.getChildren()); + + for (auto& child : rChildren) + { + secureStyleDataFromShapeToModel(*child); + } + + // After Diagram import, parts of the Diagram ModelData is at the + // oox::drawingml::Shape. Since these objects are temporary helpers, + // secure that data at the Diagram ModelData by copying. + + // This is currently mainly the Text, but may get more (styles?) + for (auto & point : maPoints) + { + Shape* pShapeCandidate(getOrCreateAssociatedShape(point)); + + if(nullptr != pShapeCandidate) + { + if(pShapeCandidate->getTextBody() && !pShapeCandidate->getTextBody()->isEmpty()) + { + point.msTextBody->msText = pShapeCandidate->getTextBody()->toString(); + + const uno::Sequence< beans::PropertyValue > aTextProps( + pShapeCandidate->getTextBody()->getTextProperties().maPropertyMap.makePropertyValueSequence()); + + for (auto const& prop : aTextProps) + point.msTextBody->maTextProps.push_back(std::pair(prop.Name, prop.Value)); + } + + // At this place a mechanism to find missing data should be added: + // Create a Shape from so-far secured data & compare it with the + // imported one. Report differences to allow extending the mechanism + // more easily. +#ifdef DBG_UTIL + // The original is pShapeCandidate, re-create potential new oox::drawingml::Shape + // as aNew to be able to compare these + ShapePtr aNew(std::make_shared<Shape>()); + restoreDataFromModelToShapeAfterReCreation(point, *aNew); + + // Unfortunately oox::drawingml::Shape has no operator==. I tried to add + // one, but that is too expensive. I stopped at oox::drawingml::Color. + // To compare it is necessary to use the debugger, or for single aspects + // of the oox data it might be possible to call local dump() methods at + // both instances to compare them/their output + + // bool bSame(aNew.get() == pShapeCandidate); +#endif + } + } +} + +void DiagramData::restoreStyleDataFromShapeToModel(::oox::drawingml::Shape& rShape) +{ + const std::vector< ShapePtr >& rChildren(rShape.getChildren()); + + if(!rChildren.empty()) + { + // group shape + for (auto& child : rChildren) + { + restoreStyleDataFromShapeToModel(*child); + } + + // if group shape we are done. Do not restore properties for group shapes + return; + } + + // we need a XShape + const css::uno::Reference< css::drawing::XShape > &rXShape(rShape.getXShape()); + if(!rXShape) + return; + + // we need a ModelID for association + if(rShape.getDiagramDataModelID().isEmpty()) + return; + + // define source to save to + svx::diagram::PointStyle* pSource(nullptr); + + if(rShape.getDiagramDataModelID() == msBackgroundShapeModelID) + { + // if BackgroundShape, set BackgroundShapeStyle as source + if(maBackgroundShapeStyle) + pSource = maBackgroundShapeStyle.get(); + } + else + { + // if Shape, seek association + for (auto & point : maPoints) + { + if(point.msModelId == rShape.getDiagramDataModelID()) + { + // found - create properties & set as source + pSource = point.msPointStylePtr.get(); + + // we are done, there is no 2nd shape with the same ModelID by definition + break; + } + } + } + + // no source -> nothing to do + if(nullptr == pSource) + return; + + // get target PropertySet of new XShape + css::uno::Reference<css::beans::XPropertySet> xPropSet(rXShape, css::uno::UNO_QUERY); + if(!xPropSet) + return; + + // apply properties + for (auto const& prop : pSource->maProperties) + { + xPropSet->setPropertyValue(prop.first, prop.second); + } +} + +void DiagramData::restoreDataFromShapeToModelAfterDiagramImport(::oox::drawingml::Shape& rRootShape) +{ + const std::vector< ShapePtr >& rChildren(rRootShape.getChildren()); + + for (auto& child : rChildren) + { + restoreStyleDataFromShapeToModel(*child); + } +} + +DiagramData::DiagramData() +: svx::diagram::DiagramData() +, mpBackgroundShapeFillProperties( std::make_shared<FillProperties>() ) +{ +} + +DiagramData::~DiagramData() +{ +} + +static void Connection_dump(const svx::diagram::Connection& rConnection) +{ + SAL_INFO( + "oox.drawingml", + "cnx modelId " << rConnection.msModelId << ", srcId " << rConnection.msSourceId << ", dstId " + << rConnection.msDestId << ", parTransId " << rConnection.msParTransId << ", presId " + << rConnection.msPresId << ", sibTransId " << rConnection.msSibTransId << ", srcOrd " + << rConnection.mnSourceOrder << ", dstOrd " << rConnection.mnDestOrder); +} + +static void Point_dump(const svx::diagram::Point& rPoint, const Shape* pShape) +{ + SAL_INFO( + "oox.drawingml", + "pt text " << pShape << ", cnxId " << rPoint.msCnxId << ", modelId " + << rPoint.msModelId << ", type " << rPoint.mnXMLType); +} + +void DiagramData::dump() const +{ + SAL_INFO("oox.drawingml", "Dgm: DiagramData # of cnx: " << maConnections.size() ); + for (const auto& rConnection : maConnections) + Connection_dump(rConnection); + + SAL_INFO("oox.drawingml", "Dgm: DiagramData # of pt: " << maPoints.size() ); + for (const auto& rPoint : maPoints) + Point_dump(rPoint, getOrCreateAssociatedShape(rPoint)); +} + +void DiagramData::buildDiagramDataModel(bool bClearOoxShapes) +{ + if(bClearOoxShapes) + { + // Delete/remove all existing oox::drawingml::Shape + maPointShapeMap.clear(); + } + + // call parent + svx::diagram::DiagramData::buildDiagramDataModel(bClearOoxShapes); + + if(bClearOoxShapes) + { + // re-create all existing oox::drawingml::Shape + svx::diagram::Points& rPoints = getPoints(); + + for (auto & point : rPoints) + { + // Create/get shape. Re-create here, that may also set needed + // and available data from the Diagram ModelData at the Shape + getOrCreateAssociatedShape(point, true); + } + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/datamodel.hxx b/oox/source/drawingml/diagram/datamodel.hxx new file mode 100644 index 0000000000..23104812ed --- /dev/null +++ b/oox/source/drawingml/diagram/datamodel.hxx @@ -0,0 +1,81 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_DATAMODEL_HXX +#define INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_DATAMODEL_HXX + +#include <map> +#include <memory> +#include <vector> + +#include <rtl/ustring.hxx> + +#include <svx/diagram/datamodel.hxx> +#include <oox/drawingml/drawingmltypes.hxx> +#include <oox/helper/helper.hxx> +#include <oox/token/tokens.hxx> + +namespace oox::drawingml { + +class DiagramData : public svx::diagram::DiagramData +{ +public: + typedef std::map< OUString, ShapePtr > PointShapeMap; + + DiagramData(); + virtual ~DiagramData(); + + // creates temporary processing data from model data + virtual void buildDiagramDataModel(bool bClearOoxShapes); + + FillPropertiesPtr& getBackgroundShapeFillProperties() { return mpBackgroundShapeFillProperties; } + virtual void dump() const; + + Shape* getOrCreateAssociatedShape(const svx::diagram::Point& rPoint, bool bCreateOnDemand = false) const; + + // get/set data between Diagram DataModel and oox::drawingml::Shape + void secureDataFromShapeToModelAfterDiagramImport(::oox::drawingml::Shape& rRootShape); + void restoreDataFromShapeToModelAfterDiagramImport(::oox::drawingml::Shape& rRootShape); + static void restoreDataFromModelToShapeAfterReCreation(const svx::diagram::Point& rPoint, Shape& rNewShape); + +protected: + void secureStyleDataFromShapeToModel(::oox::drawingml::Shape& rShape); + void restoreStyleDataFromShapeToModel(::oox::drawingml::Shape& rShape); + +private: + // The model definition, the parts *only* available in oox. Also look for already + // defined ModelData in svx::diagram::DiagramData + + // - FillStyle for Diagram Background (empty constructed, may stay empty) + FillPropertiesPtr mpBackgroundShapeFillProperties; + + // temporary processing data, deleted when using build(). Association + // map between oox::drawingml::Shape and svx::diagram::Point ModelData + PointShapeMap maPointShapeMap; +}; + +// Oox-local definition of DiagramData. Doing and using this on Oox +// allows to do much less static_cast(s) - if at all from svx::diagram::DiagramData +typedef std::shared_ptr< DiagramData > OoxDiagramDataPtr; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/datamodelcontext.cxx b/oox/source/drawingml/diagram/datamodelcontext.cxx new file mode 100644 index 0000000000..ba1826a94c --- /dev/null +++ b/oox/source/drawingml/diagram/datamodelcontext.cxx @@ -0,0 +1,384 @@ +/* -*- 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 "datamodelcontext.hxx" +#include <oox/helper/attributelist.hxx> +#include <drawingml/misccontexts.hxx> +#include <drawingml/shapepropertiescontext.hxx> +#include <drawingml/textbodycontext.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/tokens.hxx> + +using namespace ::oox::core; +using namespace ::com::sun::star::xml::sax; +using namespace ::com::sun::star::uno; + +namespace oox::drawingml { + +namespace { + +// CT_CxnList +class CxnListContext + : public ContextHandler2 +{ +public: + CxnListContext( ContextHandler2Helper const & rParent, + svx::diagram::Connections & aConnections ) + : ContextHandler2( rParent ) + , mrConnection( aConnections ) + { + } + + virtual ContextHandlerRef + onCreateContext( sal_Int32 aElementToken, + const AttributeList& rAttribs ) override + { + switch( aElementToken ) + { + case DGM_TOKEN( cxn ): + { + mrConnection.emplace_back( ); + svx::diagram::Connection& rConnection=mrConnection.back(); + + rConnection.mnXMLType = static_cast<svx::diagram::TypeConstant>(rAttribs.getToken( XML_type, XML_parOf )); + rConnection.msModelId = rAttribs.getStringDefaulted( XML_modelId ); + rConnection.msSourceId = rAttribs.getStringDefaulted( XML_srcId ); + rConnection.msDestId = rAttribs.getStringDefaulted( XML_destId ); + rConnection.msPresId = rAttribs.getStringDefaulted( XML_presId ); + rConnection.msSibTransId = rAttribs.getStringDefaulted( XML_sibTransId ); + rConnection.msParTransId = rAttribs.getStringDefaulted( XML_parTransId ); + rConnection.mnSourceOrder = rAttribs.getInteger( XML_srcOrd, 0 ); + rConnection.mnDestOrder = rAttribs.getInteger( XML_destOrd, 0 ); + + // skip CT_extLst + return nullptr; + } + default: + break; + } + + return this; + } +private: + svx::diagram::Connections& mrConnection; +}; + +// CT_presLayoutVars +class PresLayoutVarsContext + : public ContextHandler2 +{ +public: + PresLayoutVarsContext( ContextHandler2Helper const & rParent, + svx::diagram::Point & rPoint ) : + ContextHandler2( rParent ), + mrPoint( rPoint ) + { + } + virtual ContextHandlerRef + onCreateContext( sal_Int32 aElementToken, + const AttributeList& rAttribs ) override + { + switch( aElementToken ) + { + // TODO + case DGM_TOKEN( animLvl ): + case DGM_TOKEN( animOne ): + break; + case DGM_TOKEN( bulletEnabled ): + mrPoint.mbBulletEnabled = rAttribs.getBool( XML_val, false ); + break; + case DGM_TOKEN( chMax ): + mrPoint.mnMaxChildren = rAttribs.getInteger( XML_val, -1 ); + break; + case DGM_TOKEN( chPref ): + mrPoint.mnPreferredChildren = rAttribs.getInteger( XML_val, -1 ); + break; + case DGM_TOKEN( dir ): + mrPoint.mnDirection = rAttribs.getToken( XML_val, XML_norm ); + break; + case DGM_TOKEN( hierBranch ): + { + // need to convert from oox::OptValue to std::optional since 1st is not available in svx + const std::optional< sal_Int32 > aOptVal(rAttribs.getToken( XML_val )); + if(aOptVal.has_value()) + mrPoint.moHierarchyBranch = aOptVal.value(); + break; + } + case DGM_TOKEN( orgChart ): + mrPoint.mbOrgChartEnabled = rAttribs.getBool( XML_val, false ); + break; + case DGM_TOKEN( resizeHandles ): + mrPoint.mnResizeHandles = rAttribs.getToken( XML_val, XML_rel ); + break; + default: + break; + } + + return this; + } + +private: + svx::diagram::Point& mrPoint; +}; + +// CT_prSet +class PropertiesContext + : public ContextHandler2 +{ +public: + PropertiesContext( ContextHandler2Helper const & rParent, + svx::diagram::Point & rPoint, + const AttributeList& rAttribs ) : + ContextHandler2( rParent ), + mrPoint( rPoint ) + { + mrPoint.msColorTransformCategoryId = rAttribs.getStringDefaulted( XML_csCatId); + mrPoint.msColorTransformTypeId = rAttribs.getStringDefaulted( XML_csTypeId); + mrPoint.msLayoutCategoryId = rAttribs.getStringDefaulted( XML_loCatId); + mrPoint.msLayoutTypeId = rAttribs.getStringDefaulted( XML_loTypeId); + mrPoint.msPlaceholderText = rAttribs.getStringDefaulted( XML_phldrT); + mrPoint.msPresentationAssociationId = rAttribs.getStringDefaulted( XML_presAssocID); + mrPoint.msPresentationLayoutName = rAttribs.getStringDefaulted( XML_presName); + mrPoint.msPresentationLayoutStyleLabel = rAttribs.getStringDefaulted( XML_presStyleLbl); + mrPoint.msQuickStyleCategoryId = rAttribs.getStringDefaulted( XML_qsCatId); + mrPoint.msQuickStyleTypeId = rAttribs.getStringDefaulted( XML_qsTypeId); + + mrPoint.mnCustomAngle = rAttribs.getInteger( XML_custAng, -1 ); + mrPoint.mnPercentageNeighbourWidth = rAttribs.getInteger( XML_custLinFactNeighborX, -1 ); + mrPoint.mnPercentageNeighbourHeight = rAttribs.getInteger( XML_custLinFactNeighborY, -1 ); + mrPoint.mnPercentageOwnWidth = rAttribs.getInteger( XML_custLinFactX, -1 ); + mrPoint.mnPercentageOwnHeight = rAttribs.getInteger( XML_custLinFactY, -1 ); + mrPoint.mnIncludeAngleScale = rAttribs.getInteger( XML_custRadScaleInc, -1 ); + mrPoint.mnRadiusScale = rAttribs.getInteger( XML_custRadScaleRad, -1 ); + mrPoint.mnWidthScale = rAttribs.getInteger( XML_custScaleX, -1 ); + mrPoint.mnHeightScale = rAttribs.getInteger( XML_custScaleY, -1 ); + mrPoint.mnWidthOverride = rAttribs.getInteger( XML_custSzX, -1 ); + mrPoint.mnHeightOverride = rAttribs.getInteger( XML_custSzY, -1 ); + mrPoint.mnLayoutStyleCount = rAttribs.getInteger( XML_presStyleCnt, -1 ); + mrPoint.mnLayoutStyleIndex = rAttribs.getInteger( XML_presStyleIdx, -1 ); + + mrPoint.mbCoherent3DOffset = rAttribs.getBool( XML_coherent3DOff, false ); + mrPoint.mbCustomHorizontalFlip = rAttribs.getBool( XML_custFlipHor, false ); + mrPoint.mbCustomVerticalFlip = rAttribs.getBool( XML_custFlipVert, false ); + mrPoint.mbCustomText = rAttribs.getBool( XML_custT, false ); + mrPoint.mbIsPlaceholder = rAttribs.getBool( XML_phldr, false ); + } + + virtual ContextHandlerRef + onCreateContext( sal_Int32 aElementToken, + const AttributeList& ) override + { + switch( aElementToken ) + { + case DGM_TOKEN( presLayoutVars ): + return new PresLayoutVarsContext( *this, mrPoint ); + case DGM_TOKEN( style ): + // skip CT_shapeStyle + return nullptr; + default: + break; + } + return this; + } + +private: + svx::diagram::Point& mrPoint; +}; + +// CL_Pt +class PtContext + : public ContextHandler2 +{ +public: + PtContext( ContextHandler2Helper const& rParent, + const AttributeList& rAttribs, + svx::diagram::Point& rPoint, + DiagramData& rDiagramData): + ContextHandler2( rParent ), + mrPoint( rPoint ), + mrDiagramData( rDiagramData ) + { + mrPoint.msModelId = rAttribs.getStringDefaulted( XML_modelId ); + + // the default type is XML_node + const sal_Int32 nType = rAttribs.getToken( XML_type, XML_node ); + mrPoint.mnXMLType = static_cast<svx::diagram::TypeConstant>(nType); + + // ignore the cxnId unless it is this type. See 5.15.3.1.3 in Primer + if( ( nType == XML_parTrans ) || ( nType == XML_sibTrans ) ) + mrPoint.msCnxId = rAttribs.getStringDefaulted( XML_cxnId ); + } + + virtual ContextHandlerRef + onCreateContext( sal_Int32 aElementToken, + const AttributeList& rAttribs ) override + { + switch( aElementToken ) + { + case DGM_TOKEN( extLst ): + return nullptr; + case DGM_TOKEN( prSet ): + return new PropertiesContext( *this, mrPoint, rAttribs ); + case DGM_TOKEN( spPr ): + { + Shape* pShape(mrDiagramData.getOrCreateAssociatedShape(mrPoint, true)); + return new ShapePropertiesContext( *this, *pShape ); + } + case DGM_TOKEN( t ): + { + Shape* pShape(mrDiagramData.getOrCreateAssociatedShape(mrPoint, true)); + TextBodyPtr xTextBody = std::make_shared<TextBody>(); + pShape->setTextBody( xTextBody ); + return new TextBodyContext( *this, *xTextBody ); + } + default: + break; + } + return this; + } + +private: + svx::diagram::Point& mrPoint; + DiagramData& mrDiagramData; +}; + +// CT_PtList +class PtListContext + : public ContextHandler2 +{ +public: + PtListContext( ContextHandler2Helper const & rParent, svx::diagram::Points& rPoints, DiagramData& rDiagramData) : + ContextHandler2( rParent ), + mrPoints( rPoints ), + mrDiagramData( rDiagramData ) + {} + virtual ContextHandlerRef + onCreateContext( sal_Int32 aElementToken, + const AttributeList& rAttribs ) override + { + switch( aElementToken ) + { + case DGM_TOKEN( pt ): + { + // CT_Pt + mrPoints.emplace_back( ); + return new PtContext( *this, rAttribs, mrPoints.back(), mrDiagramData ); + } + default: + break; + } + return this; + } + +private: + svx::diagram::Points& mrPoints; + DiagramData& mrDiagramData; +}; + +// CT_BackgroundFormatting +class BackgroundFormattingContext + : public ContextHandler2 +{ +public: + BackgroundFormattingContext( ContextHandler2Helper const & rParent, OoxDiagramDataPtr const& pModel ) + : ContextHandler2( rParent ) + , mpDataModel( pModel ) + { + assert( pModel && "the data model MUST NOT be NULL" ); + } + + virtual ContextHandlerRef + onCreateContext( sal_Int32 aElementToken, + const AttributeList& rAttribs ) override + { + switch( aElementToken ) + { + case A_TOKEN( blipFill ): + case A_TOKEN( gradFill ): + case A_TOKEN( grpFill ): + case A_TOKEN( noFill ): + case A_TOKEN( pattFill ): + case A_TOKEN( solidFill ): + // EG_FillProperties + return FillPropertiesContext::createFillContext(*this, aElementToken, rAttribs, *mpDataModel->getBackgroundShapeFillProperties(), nullptr); + case A_TOKEN( effectDag ): + case A_TOKEN( effectLst ): + // TODO + // EG_EffectProperties + break; + default: + break; + } + return this; + } +private: + OoxDiagramDataPtr mpDataModel; +}; + +} + +DataModelContext::DataModelContext( ContextHandler2Helper const& rParent, + const OoxDiagramDataPtr& pDataModel ) + : ContextHandler2( rParent ) + , mpDataModel( pDataModel ) +{ + assert( pDataModel && "Data Model must not be NULL" ); +} + +DataModelContext::~DataModelContext() +{ + // some debug + mpDataModel->dump(); +} + +ContextHandlerRef +DataModelContext::onCreateContext( ::sal_Int32 aElement, + const AttributeList& rAttribs ) +{ + switch( aElement ) + { + case DGM_TOKEN( cxnLst ): + // CT_CxnList + return new CxnListContext( *this, mpDataModel->getConnections() ); + case DGM_TOKEN( ptLst ): + // CT_PtList + return new PtListContext( *this, mpDataModel->getPoints(), *mpDataModel ); + case DGM_TOKEN( bg ): + // CT_BackgroundFormatting + return new BackgroundFormattingContext( *this, mpDataModel ); + case DGM_TOKEN( whole ): + // CT_WholeE2oFormatting + // TODO + return nullptr; + case DGM_TOKEN( extLst ): + case A_TOKEN( ext ): + break; + case DSP_TOKEN( dataModelExt ): + mpDataModel->getExtDrawings().push_back( rAttribs.getStringDefaulted( XML_relId ) ); + break; + default: + break; + } + + return this; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/datamodelcontext.hxx b/oox/source/drawingml/diagram/datamodelcontext.hxx new file mode 100644 index 0000000000..9a5c323de8 --- /dev/null +++ b/oox/source/drawingml/diagram/datamodelcontext.hxx @@ -0,0 +1,45 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_DATAMODELCONTEXT_HXX +#define INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_DATAMODELCONTEXT_HXX + +#include <oox/core/contexthandler2.hxx> +#include "datamodel.hxx" + +namespace oox::drawingml { + +// CT_DataModel +class DataModelContext final : public ::oox::core::ContextHandler2 +{ +public: + DataModelContext( ::oox::core::ContextHandler2Helper const& rParent, const OoxDiagramDataPtr& pDataModelPtr ); + virtual ~DataModelContext() override; + + virtual ::oox::core::ContextHandlerRef onCreateContext( ::sal_Int32 Element, const ::oox::AttributeList& rAttribs ) override; + +private: + OoxDiagramDataPtr mpDataModel; +}; + +} + +#endif // INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_DATAMODELCONTEXT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/diagram.cxx b/oox/source/drawingml/diagram/diagram.cxx new file mode 100644 index 0000000000..029c2c56e9 --- /dev/null +++ b/oox/source/drawingml/diagram/diagram.cxx @@ -0,0 +1,453 @@ +/* -*- 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 <oox/drawingml/diagram/diagram.hxx> +#include "diagram.hxx" +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/drawing/XShapes.hpp> +#include <com/sun/star/xml/dom/XDocument.hpp> +#include <com/sun/star/xml/sax/XFastSAXSerializable.hpp> +#include <sal/log.hxx> +#include <editeng/unoprnms.hxx> +#include <drawingml/fillproperties.hxx> +#include <drawingml/customshapeproperties.hxx> +#include <o3tl/unit_conversion.hxx> +#include <oox/token/namespaces.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <svx/svdpage.hxx> +#include <oox/ppt/pptimport.hxx> +#include <comphelper/xmltools.hxx> + +#include "diagramlayoutatoms.hxx" +#include "layoutatomvisitors.hxx" +#include "diagramfragmenthandler.hxx" + +using namespace ::com::sun::star; + +namespace oox::drawingml { + +static void sortChildrenByZOrder(const ShapePtr& pShape) +{ + std::vector<ShapePtr>& rChildren = pShape->getChildren(); + + // Offset the children from their default z-order stacking, if necessary. + for (size_t i = 0; i < rChildren.size(); ++i) + rChildren[i]->setZOrder(i); + + for (size_t i = 0; i < rChildren.size(); ++i) + { + const ShapePtr& pChild = rChildren[i]; + sal_Int32 nZOrderOff = pChild->getZOrderOff(); + if (nZOrderOff <= 0) + continue; + + // Increase my ZOrder by nZOrderOff. + pChild->setZOrder(pChild->getZOrder() + nZOrderOff); + pChild->setZOrderOff(0); + + for (sal_Int32 j = 0; j < nZOrderOff; ++j) + { + size_t nIndex = i + j + 1; + if (nIndex >= rChildren.size()) + break; + + // Decrease the ZOrder of the next nZOrderOff elements by one. + const ShapePtr& pNext = rChildren[nIndex]; + pNext->setZOrder(pNext->getZOrder() - 1); + } + } + + // Now that the ZOrders are adjusted, sort the children. + std::sort(rChildren.begin(), rChildren.end(), + [](const ShapePtr& a, const ShapePtr& b) { return a->getZOrder() < b->getZOrder(); }); + + // Apply also for children. + for (const auto& rChild : rChildren) + sortChildrenByZOrder(rChild); +} + +/// Removes empty group shapes, now that their spacing influenced the layout. +static void removeUnneededGroupShapes(const ShapePtr& pShape) +{ + std::vector<ShapePtr>& rChildren = pShape->getChildren(); + + std::erase_if(rChildren, + [](const ShapePtr& aChild) { + return aChild->getServiceName() + == "com.sun.star.drawing.GroupShape" + && aChild->getChildren().empty(); + }); + + for (const auto& pChild : rChildren) + { + removeUnneededGroupShapes(pChild); + } +} + + +void Diagram::addTo( const ShapePtr & pParentShape ) +{ + if (pParentShape->getSize().Width == 0 || pParentShape->getSize().Height == 0) + SAL_WARN("oox.drawingml", "Diagram cannot be correctly laid out. Size: " + << pParentShape->getSize().Width << "x" << pParentShape->getSize().Height); + + pParentShape->setChildSize(pParentShape->getSize()); + + const svx::diagram::Point* pRootPoint = mpData->getRootPoint(); + if (mpLayout->getNode() && pRootPoint) + { + // create Shape hierarchy + ShapeCreationVisitor aCreationVisitor(*this, pRootPoint, pParentShape); + mpLayout->getNode()->setExistingShape(pParentShape); + mpLayout->getNode()->accept(aCreationVisitor); + + // layout shapes - now all shapes are created + ShapeLayoutingVisitor aLayoutingVisitor(*this, pRootPoint); + mpLayout->getNode()->accept(aLayoutingVisitor); + + sortChildrenByZOrder(pParentShape); + removeUnneededGroupShapes(pParentShape); + } + + ShapePtr pBackground = std::make_shared<Shape>("com.sun.star.drawing.CustomShape"); + pBackground->setSubType(XML_rect); + pBackground->getCustomShapeProperties()->setShapePresetType(XML_rect); + pBackground->setSize(pParentShape->getSize()); + pBackground->getFillProperties() = *mpData->getBackgroundShapeFillProperties(); + pBackground->setLocked(true); + + // create and set ModelID for BackgroundShape to allow later association + getData()->setBackgroundShapeModelID(OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8)); + pBackground->setDiagramDataModelID(getData()->getBackgroundShapeModelID()); + + auto& aChildren = pParentShape->getChildren(); + aChildren.insert(aChildren.begin(), pBackground); +} + +Diagram::Diagram() +: maDiagramFontHeights() +{ +} + +uno::Sequence<beans::PropertyValue> Diagram::getDomsAsPropertyValues() const +{ + sal_Int32 length = maMainDomMap.size(); + + if (maDataRelsMap.hasElements()) + ++length; + + uno::Sequence<beans::PropertyValue> aValue(length); + beans::PropertyValue* pValue = aValue.getArray(); + for (auto const& mainDom : maMainDomMap) + { + pValue->Name = mainDom.first; + pValue->Value <<= mainDom.second; + ++pValue; + } + + if (maDataRelsMap.hasElements()) + { + pValue->Name = "OOXDiagramDataRels"; + pValue->Value <<= maDataRelsMap; + ++pValue; + } + + return aValue; +} + +using ShapePairs + = std::map<std::shared_ptr<drawingml::Shape>, css::uno::Reference<css::drawing::XShape>>; + +void Diagram::syncDiagramFontHeights() +{ + // Each name represents a group of shapes, for which the font height should have the same + // scaling. + for (const auto& rNameAndPairs : maDiagramFontHeights) + { + // Find out the minimum scale within this group. + const ShapePairs& rShapePairs = rNameAndPairs.second; + double nMinScale = 100.0; + for (const auto& rShapePair : rShapePairs) + { + uno::Reference<beans::XPropertySet> xPropertySet(rShapePair.second, uno::UNO_QUERY); + if (xPropertySet.is()) + { + double nTextFitToSizeScale = 0.0; + xPropertySet->getPropertyValue("TextFitToSizeScale") >>= nTextFitToSizeScale; + if (nTextFitToSizeScale > 0 && nTextFitToSizeScale < nMinScale) + { + nMinScale = nTextFitToSizeScale; + } + } + } + + // Set that minimum scale for all members of the group. + if (nMinScale < 100.0) + { + for (const auto& rShapePair : rShapePairs) + { + uno::Reference<beans::XPropertySet> xPropertySet(rShapePair.second, uno::UNO_QUERY); + if (xPropertySet.is()) + { + xPropertySet->setPropertyValue("TextFitToSizeScale", uno::Any(nMinScale)); + } + } + } + } + + // no longer needed after processing + maDiagramFontHeights.clear(); +} + +static uno::Reference<xml::dom::XDocument> loadFragment( + core::XmlFilterBase& rFilter, + const OUString& rFragmentPath ) +{ + // load diagramming fragments into DOM representation, that later + // gets serialized back to SAX events and parsed + return rFilter.importFragment( rFragmentPath ); +} + +static uno::Reference<xml::dom::XDocument> loadFragment( + core::XmlFilterBase& rFilter, + const rtl::Reference< core::FragmentHandler >& rxHandler ) +{ + return loadFragment( rFilter, rxHandler->getFragmentPath() ); +} + +static void importFragment( core::XmlFilterBase& rFilter, + const uno::Reference<xml::dom::XDocument>& rXDom, + const OUString& rDocName, + const DiagramPtr& pDiagram, + const rtl::Reference< core::FragmentHandler >& rxHandler ) +{ + DiagramDomMap& rMainDomMap = pDiagram->getDomMap(); + rMainDomMap[rDocName] = rXDom; + + uno::Reference<xml::sax::XFastSAXSerializable> xSerializer( + rXDom, uno::UNO_QUERY_THROW); + + // now serialize DOM tree into internal data structures + rFilter.importFragment( rxHandler, xSerializer ); +} + +namespace +{ +/** + * A fragment handler that just counts the number of <dsp:sp> elements in a + * fragment. + */ +class DiagramShapeCounter : public oox::core::FragmentHandler2 +{ +public: + DiagramShapeCounter(oox::core::XmlFilterBase& rFilter, const OUString& rFragmentPath, + sal_Int32& nCounter); + oox::core::ContextHandlerRef onCreateContext(sal_Int32 nElement, + const AttributeList& rAttribs) override; + +private: + sal_Int32& m_nCounter; +}; + +DiagramShapeCounter::DiagramShapeCounter(oox::core::XmlFilterBase& rFilter, + const OUString& rFragmentPath, sal_Int32& nCounter) + : FragmentHandler2(rFilter, rFragmentPath) + , m_nCounter(nCounter) +{ +} + +oox::core::ContextHandlerRef DiagramShapeCounter::onCreateContext(sal_Int32 nElement, + const AttributeList& /*rAttribs*/) +{ + switch (nElement) + { + case DSP_TOKEN(drawing): + case DSP_TOKEN(spTree): + return this; + case DSP_TOKEN(sp): + ++m_nCounter; + break; + default: + break; + } + + return nullptr; +} +} + +void loadDiagram( ShapePtr const & pShape, + core::XmlFilterBase& rFilter, + const OUString& rDataModelPath, + const OUString& rLayoutPath, + const OUString& rQStylePath, + const OUString& rColorStylePath, + const oox::core::Relations& rRelations ) +{ + DiagramPtr pDiagram = std::make_shared<Diagram>(); + + OoxDiagramDataPtr pData = std::make_shared<DiagramData>(); + pDiagram->setData( pData ); + + DiagramLayoutPtr pLayout = std::make_shared<DiagramLayout>(*pDiagram); + pDiagram->setLayout( pLayout ); + + try + { + // set DiagramFontHeights at filter + rFilter.setDiagramFontHeights(&pDiagram->getDiagramFontHeights()); + + // data + if( !rDataModelPath.isEmpty() ) + { + rtl::Reference< core::FragmentHandler > xRefDataModel( + new DiagramDataFragmentHandler( rFilter, rDataModelPath, pData )); + + importFragment(rFilter, + loadFragment(rFilter,xRefDataModel), + "OOXData", + pDiagram, + xRefDataModel); + + pDiagram->getDataRelsMap() = pShape->resolveRelationshipsOfTypeFromOfficeDoc( rFilter, + xRefDataModel->getFragmentPath(), u"image" ); + + // Pass the info to pShape + for (auto const& extDrawing : pData->getExtDrawings()) + { + OUString aFragmentPath = rRelations.getFragmentPathFromRelId(extDrawing); + // Ignore RelIds which don't resolve to a fragment path. + if (aFragmentPath.isEmpty()) + continue; + + sal_Int32 nCounter = 0; + rtl::Reference<core::FragmentHandler> xCounter( + new DiagramShapeCounter(rFilter, aFragmentPath, nCounter)); + rFilter.importFragment(xCounter); + // Ignore ext drawings which don't actually have any shapes. + if (nCounter == 0) + continue; + + pShape->addExtDrawingRelId(extDrawing); + } + } + + // extLst is present, lets bet on that and ignore the rest of the data from here + if( pShape->getExtDrawings().empty() ) + { + // layout + if( !rLayoutPath.isEmpty() ) + { + rtl::Reference< core::FragmentHandler > xRefLayout( + new DiagramLayoutFragmentHandler( rFilter, rLayoutPath, pLayout )); + + importFragment(rFilter, + loadFragment(rFilter,xRefLayout), + "OOXLayout", + pDiagram, + xRefLayout); + } + + // style + if( !rQStylePath.isEmpty() ) + { + rtl::Reference< core::FragmentHandler > xRefQStyle( + new DiagramQStylesFragmentHandler( rFilter, rQStylePath, pDiagram->getStyles() )); + + importFragment(rFilter, + loadFragment(rFilter,xRefQStyle), + "OOXStyle", + pDiagram, + xRefQStyle); + } + } + else + { + // We still want to add the XDocuments to the DiagramDomMap + DiagramDomMap& rMainDomMap = pDiagram->getDomMap(); + rMainDomMap[OUString("OOXLayout")] = loadFragment(rFilter,rLayoutPath); + rMainDomMap[OUString("OOXStyle")] = loadFragment(rFilter,rQStylePath); + } + + // colors + if( !rColorStylePath.isEmpty() ) + { + rtl::Reference< core::FragmentHandler > xRefColorStyle( + new ColorFragmentHandler( rFilter, rColorStylePath, pDiagram->getColors() )); + + importFragment(rFilter, + loadFragment(rFilter,xRefColorStyle), + "OOXColor", + pDiagram, + xRefColorStyle); + } + + if( !pData->getExtDrawings().empty() ) + { + const DiagramColorMap::const_iterator aColor = pDiagram->getColors().find("node0"); + if( aColor != pDiagram->getColors().end() && !aColor->second.maTextFillColors.empty()) + { + // TODO(F1): well, actually, there might be *several* color + // definitions in it, after all it's called list. + pShape->setFontRefColorForNodes(DiagramColor::getColorByIndex(aColor->second.maTextFillColors, -1)); + } + } + + // collect data, init maps + // for Diagram import, do - for now - NOT clear all oox::drawingml::Shape + pData->buildDiagramDataModel(false); + + // diagram loaded. now lump together & attach to shape + pDiagram->addTo(pShape); + pShape->setDiagramDoms(pDiagram->getDomsAsPropertyValues()); + + // Get the oox::Theme definition and - if available - move/secure the + // original ImportData directly to the Diagram ModelData + std::shared_ptr<::oox::drawingml::Theme> aTheme(rFilter.getCurrentThemePtr()); + if(aTheme) + pData->setThemeDocument(aTheme->getFragment()); //getTempFile()); + + // Prepare support for the advanced DiagramHelper using Diagram & Theme data + pShape->prepareDiagramHelper(pDiagram, rFilter.getCurrentThemePtr()); + } + catch (...) + { + // unset DiagramFontHeights at filter if there was a failure + // to avoid dangling pointer + rFilter.setDiagramFontHeights(nullptr); + throw; + } +} + +const oox::drawingml::Color& +DiagramColor::getColorByIndex(const std::vector<oox::drawingml::Color>& rColors, sal_Int32 nIndex) +{ + assert(!rColors.empty()); + if (nIndex == -1) + { + return rColors[rColors.size() - 1]; + } + + return rColors[nIndex % rColors.size()]; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/diagram.hxx b/oox/source/drawingml/diagram/diagram.hxx new file mode 100644 index 0000000000..f58c762f6a --- /dev/null +++ b/oox/source/drawingml/diagram/diagram.hxx @@ -0,0 +1,171 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_DIAGRAM_HXX +#define INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_DIAGRAM_HXX + +#include <map> +#include <memory> +#include <vector> + +#include <rtl/ustring.hxx> + +#include "datamodel.hxx" +#include <oox/drawingml/shape.hxx> + +namespace com::sun::star { + namespace xml::dom { class XDocument; } +} + +namespace oox::drawingml { + +class Diagram; +class LayoutNode; +typedef std::shared_ptr< LayoutNode > LayoutNodePtr; +class LayoutAtom; +typedef std::shared_ptr< LayoutAtom > LayoutAtomPtr; +typedef std::map< OUString, css::uno::Reference<css::xml::dom::XDocument> > DiagramDomMap; +typedef std::map< OUString, LayoutAtomPtr > LayoutAtomMap; +typedef std::map< const svx::diagram::Point*, ShapePtr > PresPointShapeMap; + +class DiagramLayout +{ +public: + DiagramLayout(Diagram& rDgm) + : mrDgm(rDgm) + { + } + void setDefStyle( const OUString & sDefStyle ) + { msDefStyle = sDefStyle; } + void setMinVer( const OUString & sMinVer ) + { msMinVer = sMinVer; } + void setUniqueId( const OUString & sUniqueId ) + { msUniqueId = sUniqueId; } + void setTitle( const OUString & sTitle ) + { msTitle = sTitle; } + void setDesc( const OUString & sDesc ) + { msDesc = sDesc; } + Diagram& getDiagram() { return mrDgm; } + LayoutNodePtr & getNode() + { return mpNode; } + const LayoutNodePtr & getNode() const + { return mpNode; } + OoxDiagramDataPtr& getSampData() + { return mpSampData; } + const OoxDiagramDataPtr& getSampData() const + { return mpSampData; } + OoxDiagramDataPtr& getStyleData() + { return mpStyleData; } + const OoxDiagramDataPtr& getStyleData() const + { return mpStyleData; } + LayoutAtomMap & getLayoutAtomMap() + { return maLayoutAtomMap; } + PresPointShapeMap & getPresPointShapeMap() + { return maPresPointShapeMap; } + +private: + Diagram& mrDgm; + OUString msDefStyle; + OUString msMinVer; + OUString msUniqueId; + + OUString msTitle; + OUString msDesc; + LayoutNodePtr mpNode; + OoxDiagramDataPtr mpSampData; + OoxDiagramDataPtr mpStyleData; + // TODO + // catLst + // clrData + + LayoutAtomMap maLayoutAtomMap; + PresPointShapeMap maPresPointShapeMap; +}; + +typedef std::shared_ptr< DiagramLayout > DiagramLayoutPtr; + +struct DiagramStyle +{ + ShapeStyleRef maFillStyle; + ShapeStyleRef maLineStyle; + ShapeStyleRef maEffectStyle; + ShapeStyleRef maTextStyle; +}; + +typedef std::map<OUString,DiagramStyle> DiagramQStyleMap; + +struct DiagramColor +{ + std::vector<oox::drawingml::Color> maFillColors; + std::vector<oox::drawingml::Color> maLineColors; + std::vector<oox::drawingml::Color> maEffectColors; + std::vector<oox::drawingml::Color> maTextFillColors; + std::vector<oox::drawingml::Color> maTextLineColors; + std::vector<oox::drawingml::Color> maTextEffectColors; + + static const oox::drawingml::Color& + getColorByIndex(const std::vector<oox::drawingml::Color>& rColors, sal_Int32 nIndex); +}; + +typedef std::map<OUString,DiagramColor> DiagramColorMap; + +class Diagram +{ +public: + explicit Diagram(); + void setData( OoxDiagramDataPtr& pData ) + { mpData = pData; } + const OoxDiagramDataPtr& getData() const + { return mpData; } + void setLayout( const DiagramLayoutPtr & pLayout ) + { mpLayout = pLayout; } + const DiagramLayoutPtr& getLayout() const + { return mpLayout; } + + DiagramQStyleMap& getStyles() { return maStyles; } + const DiagramQStyleMap& getStyles() const { return maStyles; } + DiagramColorMap& getColors() { return maColors; } + const DiagramColorMap& getColors() const { return maColors; } + DiagramDomMap & getDomMap() { return maMainDomMap; } + css::uno::Sequence< css::uno::Sequence< css::uno::Any > > & getDataRelsMap() { return maDataRelsMap; } + void addTo( const ShapePtr & pShape ); + + css::uno::Sequence<css::beans::PropertyValue> getDomsAsPropertyValues() const; + oox::core::NamedShapePairs& getDiagramFontHeights() { return maDiagramFontHeights; } + void syncDiagramFontHeights(); + +private: + // This contains groups of shapes: automatic font size is the same in each group. + oox::core::NamedShapePairs maDiagramFontHeights; + + OoxDiagramDataPtr mpData; + DiagramLayoutPtr mpLayout; + DiagramQStyleMap maStyles; + DiagramColorMap maColors; + DiagramDomMap maMainDomMap; + css::uno::Sequence< css::uno::Sequence< css::uno::Any > > maDataRelsMap; +}; + +typedef std::shared_ptr< Diagram > DiagramPtr; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/diagramdefinitioncontext.cxx b/oox/source/drawingml/diagram/diagramdefinitioncontext.cxx new file mode 100644 index 0000000000..42d78d1f3b --- /dev/null +++ b/oox/source/drawingml/diagram/diagramdefinitioncontext.cxx @@ -0,0 +1,100 @@ +/* -*- 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 "diagramdefinitioncontext.hxx" +#include "datamodel.hxx" +#include "datamodelcontext.hxx" +#include "layoutnodecontext.hxx" +#include <oox/helper/attributelist.hxx> +#include <oox/token/namespaces.hxx> +#include <utility> + +using namespace ::oox::core; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; + +namespace oox::drawingml { + +// CT_DiagramDefinition +DiagramDefinitionContext::DiagramDefinitionContext( ContextHandler2Helper const & rParent, + const AttributeList& rAttributes, + DiagramLayoutPtr pLayout ) + : ContextHandler2( rParent ) + , mpLayout(std::move( pLayout )) +{ + mpLayout->setDefStyle( rAttributes.getStringDefaulted( XML_defStyle ) ); + OUString sValue = rAttributes.getStringDefaulted( XML_minVer ); + if( sValue.isEmpty() ) + { + sValue = "http://schemas.openxmlformats.org/drawingml/2006/diagram"; + } + mpLayout->setMinVer( sValue ); + mpLayout->setUniqueId( rAttributes.getStringDefaulted( XML_uniqueId ) ); +} + +DiagramDefinitionContext::~DiagramDefinitionContext() +{ + LayoutNodePtr node = mpLayout->getNode(); + if (node) + node->dump(); +} + +ContextHandlerRef +DiagramDefinitionContext::onCreateContext( ::sal_Int32 aElement, + const AttributeList& rAttribs ) +{ + switch( aElement ) + { + case DGM_TOKEN( title ): + mpLayout->setTitle( rAttribs.getStringDefaulted( XML_val ) ); + break; + case DGM_TOKEN( desc ): + mpLayout->setDesc( rAttribs.getStringDefaulted( XML_val ) ); + break; + case DGM_TOKEN( layoutNode ): + { + LayoutNodePtr pNode = std::make_shared<LayoutNode>(mpLayout->getDiagram()); + mpLayout->getNode() = pNode; + pNode->setChildOrder( rAttribs.getToken( XML_chOrder, XML_b ) ); + pNode->setMoveWith( rAttribs.getStringDefaulted( XML_moveWith ) ); + pNode->setStyleLabel( rAttribs.getStringDefaulted( XML_styleLbl ) ); + return new LayoutNodeContext( *this, rAttribs, pNode ); + } + case DGM_TOKEN( clrData ): + // TODO, does not matter for the UI. skip. + return nullptr; + case DGM_TOKEN( sampData ): + mpLayout->getSampData() = std::make_shared<DiagramData>(); + return new DataModelContext( *this, mpLayout->getSampData() ); + case DGM_TOKEN( styleData ): + mpLayout->getStyleData() = std::make_shared<DiagramData>(); + return new DataModelContext( *this, mpLayout->getStyleData() ); + case DGM_TOKEN( cat ): + case DGM_TOKEN( catLst ): + // TODO, does not matter for the UI + default: + break; + } + + return this; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/diagramdefinitioncontext.hxx b/oox/source/drawingml/diagram/diagramdefinitioncontext.hxx new file mode 100644 index 0000000000..0222998527 --- /dev/null +++ b/oox/source/drawingml/diagram/diagramdefinitioncontext.hxx @@ -0,0 +1,44 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_DIAGRAMDEFINITIONCONTEXT_HXX +#define INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_DIAGRAMDEFINITIONCONTEXT_HXX + +#include <oox/core/contexthandler2.hxx> +#include "diagram.hxx" + +namespace oox::drawingml { + +class DiagramDefinitionContext : public ::oox::core::ContextHandler2 +{ +public: + DiagramDefinitionContext( ::oox::core::ContextHandler2Helper const & rParent, const ::oox::AttributeList& rAttributes, DiagramLayoutPtr pLayout ); + virtual ~DiagramDefinitionContext() override; + + virtual ::oox::core::ContextHandlerRef onCreateContext( ::sal_Int32 Element, const ::oox::AttributeList& rAttribs ) override; + +private: + DiagramLayoutPtr mpLayout; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/diagramfragmenthandler.cxx b/oox/source/drawingml/diagram/diagramfragmenthandler.cxx new file mode 100644 index 0000000000..b65fca3f98 --- /dev/null +++ b/oox/source/drawingml/diagram/diagramfragmenthandler.cxx @@ -0,0 +1,235 @@ +/* -*- 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 "diagramdefinitioncontext.hxx" +#include "diagramfragmenthandler.hxx" +#include "datamodelcontext.hxx" +#include <drawingml/colorchoicecontext.hxx> +#include <oox/helper/attributelist.hxx> +#include <oox/token/namespaces.hxx> +#include <utility> + +using namespace ::oox::core; +using namespace ::com::sun::star::xml::sax; +using namespace ::com::sun::star::uno; + +namespace oox::drawingml { + +DiagramDataFragmentHandler::DiagramDataFragmentHandler( XmlFilterBase& rFilter, + const OUString& rFragmentPath, + OoxDiagramDataPtr xDataPtr ) + : FragmentHandler2( rFilter, rFragmentPath ) + , mpDataPtr(std::move( xDataPtr )) +{ +} + +DiagramDataFragmentHandler::~DiagramDataFragmentHandler( ) noexcept +{ + +} + +void SAL_CALL DiagramDataFragmentHandler::endDocument() +{ + +} + +ContextHandlerRef +DiagramDataFragmentHandler::onCreateContext( ::sal_Int32 aElement, + const AttributeList& ) +{ + switch( aElement ) + { + case DGM_TOKEN( dataModel ): + return new DataModelContext( *this, mpDataPtr ); + default: + break; + } + + return this; +} + +DiagramLayoutFragmentHandler::DiagramLayoutFragmentHandler( XmlFilterBase& rFilter, + const OUString& rFragmentPath, + DiagramLayoutPtr xDataPtr ) + : FragmentHandler2( rFilter, rFragmentPath ) + , mpDataPtr(std::move( xDataPtr )) +{ +} + +DiagramLayoutFragmentHandler::~DiagramLayoutFragmentHandler( ) noexcept +{ + +} + +void SAL_CALL DiagramLayoutFragmentHandler::endDocument() +{ + +} + +ContextHandlerRef +DiagramLayoutFragmentHandler::onCreateContext( ::sal_Int32 aElement, + const AttributeList& rAttribs ) +{ + switch( aElement ) + { + case DGM_TOKEN( layoutDef ): + return new DiagramDefinitionContext( *this, rAttribs, mpDataPtr ); + default: + break; + } + + return this; +} + +DiagramQStylesFragmentHandler::DiagramQStylesFragmentHandler( XmlFilterBase& rFilter, + const OUString& rFragmentPath, + DiagramQStyleMap& rStylesMap ) : + FragmentHandler2( rFilter, rFragmentPath ), + maStyleEntry(), + mrStylesMap( rStylesMap ) +{} + +::oox::core::ContextHandlerRef DiagramQStylesFragmentHandler::createStyleMatrixContext( + sal_Int32 nElement, + const AttributeList& rAttribs, + ShapeStyleRef& o_rStyle ) +{ + o_rStyle.mnThemedIdx = (nElement == A_TOKEN(fontRef)) ? + rAttribs.getToken( XML_idx, XML_none ) : rAttribs.getInteger( XML_idx, 0 ); + return new ColorContext( *this, o_rStyle.maPhClr ); +} + +::oox::core::ContextHandlerRef DiagramQStylesFragmentHandler::onCreateContext( sal_Int32 nElement, + const AttributeList& rAttribs ) +{ + // state-table like way of navigating the color fragment. we + // currently ignore everything except styleLbl in the styleDef + // element + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + return nElement == DGM_TOKEN(styleDef) ? this : nullptr; + case DGM_TOKEN(styleDef): + return nElement == DGM_TOKEN(styleLbl) ? this : nullptr; + case DGM_TOKEN(styleLbl): + return nElement == DGM_TOKEN(style) ? this : nullptr; + case DGM_TOKEN(style): + { + switch( nElement ) + { + case A_TOKEN(lnRef): // CT_StyleMatrixReference + return createStyleMatrixContext(nElement,rAttribs, + maStyleEntry.maLineStyle); + case A_TOKEN(fillRef): // CT_StyleMatrixReference + return createStyleMatrixContext(nElement,rAttribs, + maStyleEntry.maFillStyle); + case A_TOKEN(effectRef): // CT_StyleMatrixReference + return createStyleMatrixContext(nElement,rAttribs, + maStyleEntry.maEffectStyle); + case A_TOKEN(fontRef): // CT_FontReference + return createStyleMatrixContext(nElement,rAttribs, + maStyleEntry.maTextStyle); + } + return nullptr; + } + } + + return nullptr; +} + +void DiagramQStylesFragmentHandler::onStartElement( const AttributeList& rAttribs ) +{ + if( getCurrentElement() == DGM_TOKEN( styleLbl ) ) + { + maStyleName = rAttribs.getStringDefaulted( XML_name); + maStyleEntry = mrStylesMap[maStyleName]; + } +} + +void DiagramQStylesFragmentHandler::onEndElement( ) +{ + if( getCurrentElement() == DGM_TOKEN(styleLbl) ) + mrStylesMap[maStyleName] = maStyleEntry; +} + +ColorFragmentHandler::ColorFragmentHandler( ::oox::core::XmlFilterBase& rFilter, + const OUString& rFragmentPath, + DiagramColorMap& rColorsMap ) : + FragmentHandler2(rFilter,rFragmentPath), + maColorEntry(), + mrColorsMap(rColorsMap) +{} + +::oox::core::ContextHandlerRef ColorFragmentHandler::onCreateContext( sal_Int32 nElement, + const AttributeList& /*rAttribs*/ ) +{ + // state-table like way of navigating the color fragment. we + // currently ignore everything except styleLbl in the colorsDef + // element + switch( getCurrentElement() ) + { + case XML_ROOT_CONTEXT: + return nElement == DGM_TOKEN(colorsDef) ? this : nullptr; + case DGM_TOKEN(colorsDef): + return nElement == DGM_TOKEN(styleLbl) ? this : nullptr; + case DGM_TOKEN(styleLbl): + { + switch( nElement ) + { + // the actual colors - defer to color fragment handlers. + + case DGM_TOKEN(fillClrLst): + return new ColorsContext( *this, maColorEntry.maFillColors ); + case DGM_TOKEN(linClrLst): + return new ColorsContext( *this, maColorEntry.maLineColors ); + case DGM_TOKEN(effectClrLst): + return new ColorsContext( *this, maColorEntry.maEffectColors ); + case DGM_TOKEN(txFillClrLst): + return new ColorsContext( *this, maColorEntry.maTextFillColors ); + case DGM_TOKEN(txLinClrLst): + return new ColorsContext( *this, maColorEntry.maTextLineColors ); + case DGM_TOKEN(txEffectClrLst): + return new ColorsContext( *this, maColorEntry.maTextEffectColors ); + } + break; + } + } + + return nullptr; +} + +void ColorFragmentHandler::onStartElement( const AttributeList& rAttribs ) +{ + if( getCurrentElement() == DGM_TOKEN(styleLbl) ) + { + maColorName = rAttribs.getStringDefaulted( XML_name); + maColorEntry = mrColorsMap[maColorName]; + } +} + +void ColorFragmentHandler::onEndElement( ) +{ + if( getCurrentElement() == DGM_TOKEN(styleLbl) ) + mrColorsMap[maColorName] = maColorEntry; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/diagramfragmenthandler.hxx b/oox/source/drawingml/diagram/diagramfragmenthandler.hxx new file mode 100644 index 0000000000..caecb4886a --- /dev/null +++ b/oox/source/drawingml/diagram/diagramfragmenthandler.hxx @@ -0,0 +1,103 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_DIAGRAMFRAGMENTHANDLER_HXX +#define INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_DIAGRAMFRAGMENTHANDLER_HXX + +#include <oox/core/fragmenthandler2.hxx> + +#include "diagram.hxx" + +namespace oox::drawingml { + +class DiagramDataFragmentHandler : public ::oox::core::FragmentHandler2 +{ +public: + DiagramDataFragmentHandler(oox::core::XmlFilterBase& rFilter, const OUString& rFragmentPath, OoxDiagramDataPtr xDataPtr); + virtual ~DiagramDataFragmentHandler() noexcept override; + + virtual void SAL_CALL endDocument() override; + virtual ::oox::core::ContextHandlerRef onCreateContext( ::sal_Int32 Element, const ::oox::AttributeList& rAttribs ) override; + +private: + + OoxDiagramDataPtr mpDataPtr; +}; + +class DiagramLayoutFragmentHandler : public ::oox::core::FragmentHandler2 +{ +public: + DiagramLayoutFragmentHandler(oox::core::XmlFilterBase& rFilter, const OUString& rFragmentPath, DiagramLayoutPtr xDataPtr); + virtual ~DiagramLayoutFragmentHandler() noexcept override; + + virtual void SAL_CALL endDocument() override; + virtual ::oox::core::ContextHandlerRef onCreateContext( ::sal_Int32 Element, const ::oox::AttributeList& rAttribs ) override; + +private: + + DiagramLayoutPtr mpDataPtr; +}; + +class DiagramQStylesFragmentHandler : public ::oox::core::FragmentHandler2 +{ +public: + DiagramQStylesFragmentHandler( + oox::core::XmlFilterBase& rFilter, + const OUString& rFragmentPath, + DiagramQStyleMap& rStylesMap ); + + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + + virtual void onStartElement( const AttributeList& rAttribs ) override; + virtual void onEndElement() override; + +private: + ::oox::core::ContextHandlerRef createStyleMatrixContext(sal_Int32 nElement, + const AttributeList& rAttribs, + ShapeStyleRef& o_rStyle); + + OUString maStyleName; + DiagramStyle maStyleEntry; + DiagramQStyleMap& mrStylesMap; +}; + +class ColorFragmentHandler : public ::oox::core::FragmentHandler2 +{ +public: + ColorFragmentHandler( + ::oox::core::XmlFilterBase& rFilter, + const OUString& rFragmentPath, + DiagramColorMap& rColorMap ); + + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + + virtual void onStartElement( const AttributeList& rAttribs ) override; + virtual void onEndElement() override; + +private: + OUString maColorName; + DiagramColor maColorEntry; + DiagramColorMap& mrColorsMap; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/diagramhelper.cxx b/oox/source/drawingml/diagram/diagramhelper.cxx new file mode 100644 index 0000000000..3b25951bb9 --- /dev/null +++ b/oox/source/drawingml/diagram/diagramhelper.cxx @@ -0,0 +1,271 @@ +/* -*- 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 "diagramhelper.hxx" +#include "diagram.hxx" + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <oox/shape/ShapeFilterBase.hxx> +#include <oox/ppt/pptimport.hxx> +#include <drawingml/fillproperties.hxx> +#include <svx/svdmodel.hxx> +#include <comphelper/processfactory.hxx> +#include <oox/drawingml/themefragmenthandler.hxx> +#include <com/sun/star/xml/sax/XFastSAXSerializable.hpp> +#include <utility> + +using namespace ::com::sun::star; + +namespace oox::drawingml { + +bool AdvancedDiagramHelper::hasDiagramData() const +{ + return mpDiagramPtr && mpDiagramPtr->getData(); +} + +AdvancedDiagramHelper::AdvancedDiagramHelper( + std::shared_ptr< Diagram > xDiagramPtr, + std::shared_ptr<::oox::drawingml::Theme> xTheme, + css::awt::Size aImportSize) +: svx::diagram::IDiagramHelper() +, mpDiagramPtr(std::move(xDiagramPtr)) +, mpThemePtr(std::move(xTheme)) +, maImportSize(aImportSize) +{ +} + +AdvancedDiagramHelper::~AdvancedDiagramHelper() +{ +} + +void AdvancedDiagramHelper::reLayout(SdrObjGroup& rTarget) +{ + if(!mpDiagramPtr) + { + return; + } + + // Rescue/remember geometric transformation of existing Diagram + basegfx::B2DHomMatrix aTransformation; + basegfx::B2DPolyPolygon aPolyPolygon; + rTarget.TRGetBaseGeometry(aTransformation, aPolyPolygon); + + // create temporary oox::Shape as target. No longer needed is to keep/remember + // the original oox::Shape to do that. Use original Size and Pos from initial import + // to get the same layout(s) + oox::drawingml::ShapePtr pShapePtr = std::make_shared<Shape>( "com.sun.star.drawing.GroupShape" ); + pShapePtr->setDiagramType(); + pShapePtr->setSize(maImportSize); + + // Re-create the oox::Shapes for the diagram content + mpDiagramPtr->addTo(pShapePtr); + + // Delete all existing shapes in that group to prepare re-creation + rTarget.getChildrenOfSdrObject()->ClearSdrObjList(); + + // For re-creation we need to use ::addShape functionality from the + // oox import filter since currently Shape import is very tightly + // coupled to Shape creation. It converts a oox::Shape representation + // combined with an oox::Theme to incarnated XShapes representing the + // Diagram. + // To use that functionality, we have to create a temporary filter + // (based on ShapeFilterBase). Problems are that this needs to know + // the oox:Theme and a ComponentModel from TargetDocument. + // The DiagramHelper holds/delivers the oox::Theme to use, so + // it does not need to be re-imported from oox repeatedly. + // The ComponentModel can be derived from the existing XShape/GroupShape + // when knowing where to get it from, making it independent from app. + // + // NOTE: Using another (buffered) oox::Theme would allow to re-create + // using another theming in the future. + // NOTE: The incarnation of import filter (ShapeFilterBase) is only + // used for XShape creation, no xml snippets/data gets imported + // here. XShape creation may be isolated in the future. + SdrModel& rModel(rTarget.getSdrModelFromSdrObject()); + uno::Reference< uno::XInterface > const & rUnoModel(rModel.getUnoModel()); + css::uno::Reference<css::uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); + rtl::Reference<oox::shape::ShapeFilterBase> xFilter(new oox::shape::ShapeFilterBase(xContext)); + + // set oox::Theme at Filter. All LineStyle/FillStyle/Colors/Attributes + // will be taken from there + if(UseDiagramThemeData()) + xFilter->setCurrentTheme(getOrCreateThemePtr(xFilter)); + + css::uno::Reference< css::lang::XComponent > aComponentModel( rUnoModel, uno::UNO_QUERY ); + xFilter->setTargetDocument(aComponentModel); + + // set DiagramFontHeights + xFilter->setDiagramFontHeights(&mpDiagramPtr->getDiagramFontHeights()); + + // Prepare the target for the to-be-created XShapes + uno::Reference<drawing::XShapes> xShapes(rTarget.getUnoShape(), uno::UNO_QUERY_THROW); + + for (auto const& child : pShapePtr->getChildren()) + { + // Create all sub-shapes. This will recursively create needed geometry using + // filter-internal ::createShapes + child->addShape( + *xFilter, + xFilter->getCurrentTheme(), + xShapes, + aTransformation, + pShapePtr->getFillProperties()); + } + + // sync FontHeights + mpDiagramPtr->syncDiagramFontHeights(); + + // re-apply secured data from ModelData + if(UseDiagramModelData()) + mpDiagramPtr->getData()->restoreDataFromShapeToModelAfterDiagramImport(*pShapePtr); + + // Re-apply remembered geometry + rTarget.TRSetBaseGeometry(aTransformation, aPolyPolygon); +} + +OUString AdvancedDiagramHelper::getString() const +{ + if(hasDiagramData()) + { + return mpDiagramPtr->getData()->getString(); + } + + return OUString(); +} + +std::vector<std::pair<OUString, OUString>> AdvancedDiagramHelper::getChildren(const OUString& rParentId) const +{ + if(hasDiagramData()) + { + return mpDiagramPtr->getData()->getChildren(rParentId); + } + + return std::vector<std::pair<OUString, OUString>>(); +} + +OUString AdvancedDiagramHelper::addNode(const OUString& rText) +{ + OUString aRetval; + + if(hasDiagramData()) + { + aRetval = mpDiagramPtr->getData()->addNode(rText); + + // reset temporary buffered ModelData association lists & rebuild them + // and the Diagram DataModel + mpDiagramPtr->getData()->buildDiagramDataModel(true); + + // also reset temporary buffered layout data - that might + // still refer to changed oox::Shape data + mpDiagramPtr->getLayout()->getPresPointShapeMap().clear(); + } + + return aRetval; +} + +bool AdvancedDiagramHelper::removeNode(const OUString& rNodeId) +{ + bool bRetval(false); + + if(hasDiagramData()) + { + bRetval = mpDiagramPtr->getData()->removeNode(rNodeId); + + // reset temporary buffered ModelData association lists & rebuild them + // and the Diagram DataModel + mpDiagramPtr->getData()->buildDiagramDataModel(true); + + // also reset temporary buffered layout data - that might + // still refer to changed oox::Shape data + mpDiagramPtr->getLayout()->getPresPointShapeMap().clear(); + } + + return bRetval; +} + +svx::diagram::DiagramDataStatePtr AdvancedDiagramHelper::extractDiagramDataState() const +{ + if(!mpDiagramPtr) + { + return svx::diagram::DiagramDataStatePtr(); + } + + return mpDiagramPtr->getData()->extractDiagramDataState(); +} + +void AdvancedDiagramHelper::applyDiagramDataState(const svx::diagram::DiagramDataStatePtr& rState) +{ + if(!mpDiagramPtr) + { + return; + } + + mpDiagramPtr->getData()->applyDiagramDataState(rState); +} + +void AdvancedDiagramHelper::doAnchor(SdrObjGroup& rTarget, ::oox::drawingml::Shape& rRootShape) +{ + if(!mpDiagramPtr) + { + return; + } + + mpDiagramPtr->syncDiagramFontHeights(); + + // After Diagram import, parts of the Diagram ModelData is at the + // oox::drawingml::Shape. Since these objects are temporary helpers, + // secure that data at the Diagram ModelData by copying. + mpDiagramPtr->getData()->secureDataFromShapeToModelAfterDiagramImport(rRootShape); + + anchorToSdrObjGroup(rTarget); +} + +const std::shared_ptr< ::oox::drawingml::Theme >& AdvancedDiagramHelper::getOrCreateThemePtr( + rtl::Reference< oox::shape::ShapeFilterBase >& rxFilter) const +{ + // (Re-)Use already existing Theme if existing/imported if possible. + // If not, re-import Theme if data is available and thus possible + if(hasDiagramData() && (ForceThemePtrRecreation() || !mpThemePtr)) + { + // get the originally imported dom::XDocument + const uno::Reference< css::xml::dom::XDocument >& xThemeDocument(mpDiagramPtr->getData()->getThemeDocument()); + + if(xThemeDocument) + { + // reset local Theme ModelData *always* to get rid of former data that would + // else be added additionally + const_cast<AdvancedDiagramHelper*>(this)->mpThemePtr = std::make_shared<oox::drawingml::Theme>(); + auto pTheme = std::make_shared<model::Theme>(); + mpThemePtr->setTheme(pTheme); + + // import Theme ModelData + rxFilter->importFragment( + new ThemeFragmentHandler(*rxFilter, OUString(), *mpThemePtr, *pTheme), + uno::Reference< css::xml::sax::XFastSAXSerializable >( + xThemeDocument, + uno::UNO_QUERY_THROW)); + } + } + + return mpThemePtr; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/diagramhelper.hxx b/oox/source/drawingml/diagram/diagramhelper.hxx new file mode 100644 index 0000000000..626d40382d --- /dev/null +++ b/oox/source/drawingml/diagram/diagramhelper.hxx @@ -0,0 +1,94 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_OOX_DRAWINGML_DIAGRAM_DIAGRAMHELPER_HXX +#define INCLUDED_OOX_DRAWINGML_DIAGRAM_DIAGRAMHELPER_HXX + +#include <rtl/ustring.hxx> +#include <oox/drawingml/theme.hxx> +#include <oox/shape/ShapeFilterBase.hxx> +#include <svx/svdogrp.hxx> +#include <svx/diagram/IDiagramHelper.hxx> + +namespace svx { namespace diagram { + class DiagramDataState; +}} + +namespace oox::drawingml { + +class Diagram; + +// Advanced DiagramHelper +// +// This helper tries to hold all necessary data to re-layout +// all XShapes/SdrObjects of an already imported Diagram. The +// Diagram holds the SmarArt model data before it gets layouted, +// while Theme holds the oox Fill/Line/Style definitions to +// apply. +// Re-Layouting (re-creating) is rather complex, for detailed +// information see ::reLayout implementation. +// This helper class may/should be extended to: +// - deliver representative data from the Diagram-Model +// - modify it eventually +// - im/export Diagram model to other representations +class AdvancedDiagramHelper final : public svx::diagram::IDiagramHelper +{ + const std::shared_ptr< Diagram > mpDiagramPtr; + std::shared_ptr<::oox::drawingml::Theme> mpThemePtr; + + css::awt::Size maImportSize; + + bool hasDiagramData() const; + +public: + AdvancedDiagramHelper( + std::shared_ptr< Diagram > xDiagramPtr, + std::shared_ptr<::oox::drawingml::Theme> xTheme, + css::awt::Size aImportSize); + virtual ~AdvancedDiagramHelper(); + + // re-create XShapes + virtual void reLayout(SdrObjGroup& rTarget) override; + + // get text representation of data tree + virtual OUString getString() const override; + + // get children of provided data node + // use empty string for top-level nodes + // returns vector of (id, text) + virtual std::vector<std::pair<OUString, OUString>> getChildren(const OUString& rParentId) const override; + + // add/remove new top-level node to data model, returns its id + virtual OUString addNode(const OUString& rText) override; + virtual bool removeNode(const OUString& rNodeId) override; + + // Undo/Redo helpers to extract/restore Diagram-defining data + virtual std::shared_ptr< svx::diagram::DiagramDataState > extractDiagramDataState() const override; + virtual void applyDiagramDataState(const std::shared_ptr< svx::diagram::DiagramDataState >& rState) override; + + void doAnchor(SdrObjGroup& rTarget, ::oox::drawingml::Shape& rRootShape); + const std::shared_ptr< ::oox::drawingml::Theme >& getOrCreateThemePtr( + rtl::Reference< oox::shape::ShapeFilterBase>& rxFilter ) const; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/diagramlayoutatoms.cxx b/oox/source/drawingml/diagram/diagramlayoutatoms.cxx new file mode 100644 index 0000000000..6ee4b88322 --- /dev/null +++ b/oox/source/drawingml/diagram/diagramlayoutatoms.cxx @@ -0,0 +1,2045 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "diagramlayoutatoms.hxx" + +#include <set> + +#include "layoutatomvisitorbase.hxx" + +#include <basegfx/numeric/ftools.hxx> +#include <sal/log.hxx> + +#include <o3tl/unit_conversion.hxx> +#include <oox/helper/attributelist.hxx> +#include <oox/token/properties.hxx> +#include <drawingml/fillproperties.hxx> +#include <drawingml/lineproperties.hxx> +#include <drawingml/textbody.hxx> +#include <drawingml/textparagraph.hxx> +#include <drawingml/textrun.hxx> +#include <drawingml/customshapeproperties.hxx> +#include <com/sun/star/drawing/TextFitToSizeType.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; +using namespace ::oox::core; + +namespace +{ +/// Looks up the value of the rInternalName -> nProperty key in rProperties. +std::optional<sal_Int32> findProperty(const oox::drawingml::LayoutPropertyMap& rProperties, + const OUString& rInternalName, sal_Int32 nProperty) +{ + std::optional<sal_Int32> oRet; + + auto it = rProperties.find(rInternalName); + if (it != rProperties.end()) + { + const oox::drawingml::LayoutProperty& rProperty = it->second; + auto itProperty = rProperty.find(nProperty); + if (itProperty != rProperty.end()) + oRet = itProperty->second; + } + + return oRet; +} + +/** + * Determines if nUnit is a font unit (measured in points) or not (measured in + * millimeters). + */ +bool isFontUnit(sal_Int32 nUnit) +{ + return nUnit == oox::XML_primFontSz || nUnit == oox::XML_secFontSz; +} + +/// Determines which UNO property should be set for a given constraint type. +sal_Int32 getPropertyFromConstraint(sal_Int32 nConstraint) +{ + switch (nConstraint) + { + case oox::XML_lMarg: + return oox::PROP_TextLeftDistance; + case oox::XML_rMarg: + return oox::PROP_TextRightDistance; + case oox::XML_tMarg: + return oox::PROP_TextUpperDistance; + case oox::XML_bMarg: + return oox::PROP_TextLowerDistance; + } + + return 0; +} + +/** + * Determines if pShape is (or contains) a presentation of a data node of type + * nType. + */ +bool containsDataNodeType(const oox::drawingml::ShapePtr& pShape, sal_Int32 nType) +{ + if (pShape->getDataNodeType() == nType) + return true; + + for (const auto& pChild : pShape->getChildren()) + { + if (containsDataNodeType(pChild, nType)) + return true; + } + + return false; +} +} + +namespace oox::drawingml { +void SnakeAlg::layoutShapeChildren(const AlgAtom& rAlg, const ShapePtr& rShape, + const std::vector<Constraint>& rConstraints) +{ + if (rShape->getChildren().empty() || rShape->getSize().Width == 0 + || rShape->getSize().Height == 0) + return; + + // Parse constraints. + double fChildAspectRatio = rShape->getChildren()[0]->getAspectRatio(); + double fShapeHeight = rShape->getSize().Height; + double fShapeWidth = rShape->getSize().Width; + // Check if we have a child aspect ratio. If so, need to shrink one dimension to + // achieve that ratio. + if (fChildAspectRatio && fShapeHeight && fChildAspectRatio < (fShapeWidth / fShapeHeight)) + { + fShapeWidth = fShapeHeight * fChildAspectRatio; + } + + double fSpaceFromConstraint = 1.0; + LayoutPropertyMap aPropertiesByName; + std::map<sal_Int32, LayoutProperty> aPropertiesByType; + LayoutProperty& rParent = aPropertiesByName[""]; + rParent[XML_w] = fShapeWidth; + rParent[XML_h] = fShapeHeight; + for (const auto& rConstr : rConstraints) + { + if (rConstr.mnRefType == XML_w || rConstr.mnRefType == XML_h) + { + if (rConstr.mnType == XML_sp && rConstr.msForName.isEmpty()) + fSpaceFromConstraint = rConstr.mfFactor; + } + + auto itRefForName = aPropertiesByName.find(rConstr.msRefForName); + if (itRefForName == aPropertiesByName.end()) + { + continue; + } + + auto it = itRefForName->second.find(rConstr.mnRefType); + if (it == itRefForName->second.end()) + { + continue; + } + + if (rConstr.mfValue != 0.0) + { + continue; + } + + sal_Int32 nValue = it->second * rConstr.mfFactor; + + if (rConstr.mnPointType == XML_none) + { + aPropertiesByName[rConstr.msForName][rConstr.mnType] = nValue; + } + else + { + aPropertiesByType[rConstr.mnPointType][rConstr.mnType] = nValue; + } + } + + std::vector<sal_Int32> aShapeWidths(rShape->getChildren().size()); + for (size_t i = 0; i < rShape->getChildren().size(); ++i) + { + ShapePtr pChild = rShape->getChildren()[i]; + if (!pChild->getDataNodeType()) + { + // TODO handle the case when the requirement applies by name, not by point type. + aShapeWidths[i] = fShapeWidth; + continue; + } + + auto itNodeType = aPropertiesByType.find(pChild->getDataNodeType()); + if (itNodeType == aPropertiesByType.end()) + { + aShapeWidths[i] = fShapeWidth; + continue; + } + + auto it = itNodeType->second.find(XML_w); + if (it == itNodeType->second.end()) + { + aShapeWidths[i] = fShapeWidth; + continue; + } + + aShapeWidths[i] = it->second; + } + + bool bSpaceFromConstraints = fSpaceFromConstraint != 1.0; + + const AlgAtom::ParamMap& rMap = rAlg.getMap(); + const sal_Int32 nDir = rMap.count(XML_grDir) ? rMap.find(XML_grDir)->second : XML_tL; + sal_Int32 nIncX = 1; + sal_Int32 nIncY = 1; + bool bHorizontal = true; + switch (nDir) + { + case XML_tL: + nIncX = 1; + nIncY = 1; + break; + case XML_tR: + nIncX = -1; + nIncY = 1; + break; + case XML_bL: + nIncX = 1; + nIncY = -1; + bHorizontal = false; + break; + case XML_bR: + nIncX = -1; + nIncY = -1; + bHorizontal = false; + break; + } + + sal_Int32 nCount = rShape->getChildren().size(); + // Defaults in case not provided by constraints. + double fSpace = bSpaceFromConstraints ? fSpaceFromConstraint : 0.3; + double fAspectRatio = 0.54; // diagram should not spill outside, earlier it was 0.6 + + sal_Int32 nCol = 1; + sal_Int32 nRow = 1; + sal_Int32 nMaxRowWidth = 0; + if (nCount <= fChildAspectRatio) + // Child aspect ratio request (width/height) is N, and we have at most N shapes. + // This means we don't need multiple columns. + nRow = nCount; + else + { + for (; nRow < nCount; nRow++) + { + nCol = std::ceil(static_cast<double>(nCount) / nRow); + sal_Int32 nRowWidth = 0; + for (sal_Int32 i = 0; i < nCol; ++i) + { + if (i >= nCount) + { + break; + } + + nRowWidth += aShapeWidths[i]; + } + double fTotalShapesHeight = fShapeHeight * nRow; + if (nRowWidth && fTotalShapesHeight / nRowWidth >= fAspectRatio) + { + if (nRowWidth > nMaxRowWidth) + { + nMaxRowWidth = nRowWidth; + } + break; + } + } + } + + SAL_INFO("oox.drawingml", "Snake layout grid: " << nCol << "x" << nRow); + + sal_Int32 nWidth = rShape->getSize().Width / (nCol + (nCol - 1) * fSpace); + awt::Size aChildSize(nWidth, nWidth * fAspectRatio); + if (nCol == 1 && nRow > 1) + { + // We have a single column, so count the height based on the parent height, not + // based on width. + // Space occurs inside children; also double amount of space is needed outside (on + // both sides), if the factor comes from a constraint. + sal_Int32 nNumSpaces = -1; + if (bSpaceFromConstraints) + nNumSpaces += 4; + sal_Int32 nHeight = rShape->getSize().Height / (nRow + (nRow + nNumSpaces) * fSpace); + + if (fChildAspectRatio > 1) + { + // Shrink width if the aspect ratio requires it. + nWidth = std::min(rShape->getSize().Width, + static_cast<sal_Int32>(nHeight * fChildAspectRatio)); + aChildSize = awt::Size(nWidth, nHeight); + } + + bHorizontal = false; + } + + awt::Point aCurrPos(0, 0); + if (nIncX == -1) + aCurrPos.X = rShape->getSize().Width - aChildSize.Width; + if (nIncY == -1) + aCurrPos.Y = rShape->getSize().Height - aChildSize.Height; + else if (bSpaceFromConstraints) + { + if (!bHorizontal) + { + // Initial vertical offset to have upper spacing (outside, so double amount). + aCurrPos.Y = aChildSize.Height * fSpace * 2; + } + } + + sal_Int32 nStartX = aCurrPos.X; + sal_Int32 nColIdx = 0, index = 0; + + const sal_Int32 aContDir + = rMap.count(XML_contDir) ? rMap.find(XML_contDir)->second : XML_sameDir; + + switch (aContDir) + { + case XML_sameDir: + { + sal_Int32 nRowHeight = 0; + for (auto& aCurrShape : rShape->getChildren()) + { + aCurrShape->setPosition(aCurrPos); + awt::Size aCurrSize(aChildSize); + // aShapeWidths items are a portion of nMaxRowWidth. We want the same ratio, + // based on the original parent width, ignoring the aspect ratio request. + bool bWidthsFromConstraints + = nCount >= 2 && rShape->getChildren()[1]->getDataNodeType() == XML_sibTrans; + if (bWidthsFromConstraints && nMaxRowWidth) + { + double fWidthFactor = static_cast<double>(aShapeWidths[index]) / nMaxRowWidth; + // We can only work from constraints if spacing is represented by a real + // child shape. + aCurrSize.Width = rShape->getSize().Width * fWidthFactor; + } + if (fChildAspectRatio) + { + aCurrSize.Height = aCurrSize.Width / fChildAspectRatio; + + // Child shapes are not allowed to leave their parent. + aCurrSize.Height = std::min<sal_Int32>( + aCurrSize.Height, rShape->getSize().Height / (nRow + (nRow - 1) * fSpace)); + } + if (aCurrSize.Height > nRowHeight) + { + nRowHeight = aCurrSize.Height; + } + aCurrShape->setSize(aCurrSize); + aCurrShape->setChildSize(aCurrSize); + + index++; // counts index of child, helpful for positioning. + + if (index % nCol == 0 || ((index / nCol) + 1) != nRow) + aCurrPos.X += nIncX * (aCurrSize.Width + fSpace * aCurrSize.Width); + + if (++nColIdx == nCol) // condition for next row + { + // if last row, then position children according to number of shapes. + if ((index + 1) % nCol != 0 && (index + 1) >= 3 + && ((index + 1) / nCol + 1) == nRow && nCount != nRow * nCol) + { + // position first child of last row + if (bWidthsFromConstraints) + { + aCurrPos.X = nStartX; + } + else + { + // Can assume that all child shape has the same width. + aCurrPos.X + = nStartX + + (nIncX * (aCurrSize.Width + fSpace * aCurrSize.Width)) / 2; + } + } + else + // if not last row, positions first child of that row + aCurrPos.X = nStartX; + aCurrPos.Y += nIncY * (nRowHeight + fSpace * nRowHeight); + nColIdx = 0; + nRowHeight = 0; + } + + // positions children in the last row. + if (index % nCol != 0 && index >= 3 && ((index / nCol) + 1) == nRow) + aCurrPos.X += (nIncX * (aCurrSize.Width + fSpace * aCurrSize.Width)); + } + break; + } + case XML_revDir: + for (auto& aCurrShape : rShape->getChildren()) + { + aCurrShape->setPosition(aCurrPos); + aCurrShape->setSize(aChildSize); + aCurrShape->setChildSize(aChildSize); + + index++; // counts index of child, helpful for positioning. + + /* + index%col -> tests node is at last column + ((index/nCol)+1)!=nRow) -> tests node is at last row or not + ((index/nCol)+1)%2!=0 -> tests node is at row which is multiple of 2, important for revDir + num!=nRow*nCol -> tests how last row nodes should be spread. + */ + + if ((index % nCol == 0 || ((index / nCol) + 1) != nRow) + && ((index / nCol) + 1) % 2 != 0) + aCurrPos.X += (aChildSize.Width + fSpace * aChildSize.Width); + else if (index % nCol != 0 + && ((index / nCol) + 1) != nRow) // child other than placed at last column + aCurrPos.X -= (aChildSize.Width + fSpace * aChildSize.Width); + + if (++nColIdx == nCol) // condition for next row + { + // if last row, then position children according to number of shapes. + if ((index + 1) % nCol != 0 && (index + 1) >= 4 + && ((index + 1) / nCol + 1) == nRow && nCount != nRow * nCol + && ((index / nCol) + 1) % 2 == 0) + // position first child of last row + aCurrPos.X -= aChildSize.Width * 3 / 2; + else if ((index + 1) % nCol != 0 && (index + 1) >= 4 + && ((index + 1) / nCol + 1) == nRow && nCount != nRow * nCol + && ((index / nCol) + 1) % 2 != 0) + aCurrPos.X = nStartX + + (nIncX * (aChildSize.Width + fSpace * aChildSize.Width)) / 2; + else if (((index / nCol) + 1) % 2 != 0) + aCurrPos.X = nStartX; + + aCurrPos.Y += nIncY * (aChildSize.Height + fSpace * aChildSize.Height); + nColIdx = 0; + } + + // positions children in the last row. + if (index % nCol != 0 && index >= 3 && ((index / nCol) + 1) == nRow + && ((index / nCol) + 1) % 2 == 0) + //if row%2=0 then start from left else + aCurrPos.X -= (nIncX * (aChildSize.Width + fSpace * aChildSize.Width)); + else if (index % nCol != 0 && index >= 3 && ((index / nCol) + 1) == nRow + && ((index / nCol) + 1) % 2 != 0) + // start from right + aCurrPos.X += (nIncX * (aChildSize.Width + fSpace * aChildSize.Width)); + } + break; + } +} + +void PyraAlg::layoutShapeChildren(const ShapePtr& rShape) +{ + if (rShape->getChildren().empty() || rShape->getSize().Width == 0 + || rShape->getSize().Height == 0) + return; + + // const sal_Int32 nDir = maMap.count(XML_linDir) ? maMap.find(XML_linDir)->second : XML_fromT; + // const sal_Int32 npyraAcctPos = maMap.count(XML_pyraAcctPos) ? maMap.find(XML_pyraAcctPos)->second : XML_bef; + // const sal_Int32 ntxDir = maMap.count(XML_txDir) ? maMap.find(XML_txDir)->second : XML_fromT; + // const sal_Int32 npyraLvlNode = maMap.count(XML_pyraLvlNode) ? maMap.find(XML_pyraLvlNode)->second : XML_level; + // uncomment when use in code. + + sal_Int32 nCount = rShape->getChildren().size(); + double fAspectRatio = 0.32; + + awt::Size aChildSize = rShape->getSize(); + aChildSize.Width /= nCount; + aChildSize.Height /= nCount; + + awt::Point aCurrPos(0, 0); + aCurrPos.X = fAspectRatio * aChildSize.Width * (nCount - 1); + aCurrPos.Y = fAspectRatio * aChildSize.Height; + + for (auto& aCurrShape : rShape->getChildren()) + { + aCurrShape->setPosition(aCurrPos); + if (nCount > 1) + { + aCurrPos.X -= aChildSize.Height / (nCount - 1); + } + aChildSize.Width += aChildSize.Height; + aCurrShape->setSize(aChildSize); + aCurrShape->setChildSize(aChildSize); + aCurrPos.Y += (aChildSize.Height); + } +} + +bool CompositeAlg::inferFromLayoutProperty(const LayoutProperty& rMap, sal_Int32 nRefType, + sal_Int32& rValue) +{ + switch (nRefType) + { + case XML_r: + { + auto it = rMap.find(XML_l); + if (it == rMap.end()) + { + return false; + } + sal_Int32 nLeft = it->second; + it = rMap.find(XML_w); + if (it == rMap.end()) + { + return false; + } + rValue = nLeft + it->second; + return true; + } + default: + break; + } + + return false; +} + +void CompositeAlg::applyConstraintToLayout(const Constraint& rConstraint, + LayoutPropertyMap& rProperties) +{ + // TODO handle the case when we have ptType="...", not forName="...". + if (rConstraint.msForName.isEmpty()) + { + return; + } + + const LayoutPropertyMap::const_iterator aRef = rProperties.find(rConstraint.msRefForName); + if (aRef == rProperties.end()) + return; + + const LayoutProperty::const_iterator aRefType = aRef->second.find(rConstraint.mnRefType); + sal_Int32 nInferredValue = 0; + if (aRefType != aRef->second.end()) + { + // Reference is found directly. + rProperties[rConstraint.msForName][rConstraint.mnType] + = aRefType->second * rConstraint.mfFactor; + } + else if (inferFromLayoutProperty(aRef->second, rConstraint.mnRefType, nInferredValue)) + { + // Reference can be inferred. + rProperties[rConstraint.msForName][rConstraint.mnType] + = nInferredValue * rConstraint.mfFactor; + } + else + { + // Reference not found, assume a fixed value. + // Values are never in EMU, while oox::drawingml::Shape position and size are always in + // EMU. + const double fValue = o3tl::convert(rConstraint.mfValue, + isFontUnit(rConstraint.mnRefType) ? o3tl::Length::pt + : o3tl::Length::mm, + o3tl::Length::emu); + rProperties[rConstraint.msForName][rConstraint.mnType] = fValue; + } +} + +void CompositeAlg::layoutShapeChildren(AlgAtom& rAlg, const ShapePtr& rShape, + const std::vector<Constraint>& rConstraints) +{ + LayoutPropertyMap aProperties; + LayoutProperty& rParent = aProperties[""]; + + sal_Int32 nParentXOffset = 0; + + // Track min/max vertical positions, so we can center everything at the end, if needed. + sal_Int32 nVertMin = std::numeric_limits<sal_Int32>::max(); + sal_Int32 nVertMax = 0; + + if (rAlg.getAspectRatio() != 1.0) + { + rParent[XML_w] = rShape->getSize().Width; + rParent[XML_h] = rShape->getSize().Height; + rParent[XML_l] = 0; + rParent[XML_t] = 0; + rParent[XML_r] = rShape->getSize().Width; + rParent[XML_b] = rShape->getSize().Height; + } + else + { + // Shrink width to be only as large as height. + rParent[XML_w] = std::min(rShape->getSize().Width, rShape->getSize().Height); + rParent[XML_h] = rShape->getSize().Height; + if (rParent[XML_w] < rShape->getSize().Width) + nParentXOffset = (rShape->getSize().Width - rParent[XML_w]) / 2; + rParent[XML_l] = nParentXOffset; + rParent[XML_t] = 0; + rParent[XML_r] = rShape->getSize().Width - rParent[XML_l]; + rParent[XML_b] = rShape->getSize().Height; + } + + for (const auto& rConstr : rConstraints) + { + // Apply direct constraints for all layout nodes. + applyConstraintToLayout(rConstr, aProperties); + } + + for (auto& aCurrShape : rShape->getChildren()) + { + // Apply constraints from the current layout node for this child shape. + // Previous child shapes may have changed aProperties. + for (const auto& rConstr : rConstraints) + { + if (rConstr.msForName != aCurrShape->getInternalName()) + { + continue; + } + + applyConstraintToLayout(rConstr, aProperties); + } + + // Apply constraints from the child layout node for this child shape. + // This builds on top of the own parent state + the state of previous shapes in the + // same composite algorithm. + const LayoutNode& rLayoutNode = rAlg.getLayoutNode(); + for (const auto& pDirectChild : rLayoutNode.getChildren()) + { + auto pLayoutNode = dynamic_cast<LayoutNode*>(pDirectChild.get()); + if (!pLayoutNode) + { + continue; + } + + if (pLayoutNode->getName() != aCurrShape->getInternalName()) + { + continue; + } + + for (const auto& pChild : pLayoutNode->getChildren()) + { + auto pConstraintAtom = dynamic_cast<ConstraintAtom*>(pChild.get()); + if (!pConstraintAtom) + { + continue; + } + + const Constraint& rConstraint = pConstraintAtom->getConstraint(); + if (!rConstraint.msForName.isEmpty()) + { + continue; + } + + if (!rConstraint.msRefForName.isEmpty()) + { + continue; + } + + // Either an absolute value or a factor of a property. + if (rConstraint.mfValue == 0.0 && rConstraint.mnRefType == XML_none) + { + continue; + } + + Constraint aConstraint(rConstraint); + aConstraint.msForName = pLayoutNode->getName(); + aConstraint.msRefForName = pLayoutNode->getName(); + + applyConstraintToLayout(aConstraint, aProperties); + } + } + + awt::Size aSize = rShape->getSize(); + awt::Point aPos(0, 0); + + const LayoutPropertyMap::const_iterator aPropIt + = aProperties.find(aCurrShape->getInternalName()); + if (aPropIt != aProperties.end()) + { + const LayoutProperty& rProp = aPropIt->second; + LayoutProperty::const_iterator it, it2; + + if ((it = rProp.find(XML_w)) != rProp.end()) + aSize.Width = std::min(it->second, rShape->getSize().Width); + if ((it = rProp.find(XML_h)) != rProp.end()) + aSize.Height = std::min(it->second, rShape->getSize().Height); + + if ((it = rProp.find(XML_l)) != rProp.end()) + aPos.X = it->second; + else if ((it = rProp.find(XML_ctrX)) != rProp.end()) + aPos.X = it->second - aSize.Width / 2; + else if ((it = rProp.find(XML_r)) != rProp.end()) + aPos.X = it->second - aSize.Width; + + if ((it = rProp.find(XML_t)) != rProp.end()) + aPos.Y = it->second; + else if ((it = rProp.find(XML_ctrY)) != rProp.end()) + aPos.Y = it->second - aSize.Height / 2; + else if ((it = rProp.find(XML_b)) != rProp.end()) + aPos.Y = it->second - aSize.Height; + + if ((it = rProp.find(XML_l)) != rProp.end() && (it2 = rProp.find(XML_r)) != rProp.end()) + aSize.Width = it2->second - it->second; + if ((it = rProp.find(XML_t)) != rProp.end() && (it2 = rProp.find(XML_b)) != rProp.end()) + aSize.Height = it2->second - it->second; + + aPos.X += nParentXOffset; + aSize.Width = std::min(aSize.Width, rShape->getSize().Width - aPos.X); + aSize.Height = std::min(aSize.Height, rShape->getSize().Height - aPos.Y); + } + else + SAL_WARN("oox.drawingml", "composite layout properties not found for shape " + << aCurrShape->getInternalName()); + + aCurrShape->setSize(aSize); + aCurrShape->setChildSize(aSize); + aCurrShape->setPosition(aPos); + + nVertMin = std::min(aPos.Y, nVertMin); + nVertMax = std::max(aPos.Y + aSize.Height, nVertMax); + + NamedShapePairs& rDiagramFontHeights + = rAlg.getLayoutNode().getDiagram().getDiagramFontHeights(); + auto it = rDiagramFontHeights.find(aCurrShape->getInternalName()); + if (it != rDiagramFontHeights.end()) + { + // Internal name matches: put drawingml::Shape to the relevant group, for + // synchronized font height handling. + it->second.insert({ aCurrShape, {} }); + } + } + + // See if all vertical space is used or we have to center the content. + if (!(nVertMin >= 0 && nVertMin <= nVertMax && nVertMax <= rParent[XML_h])) + return; + + sal_Int32 nDiff = rParent[XML_h] - (nVertMax - nVertMin); + if (nDiff > 0) + { + for (auto& aCurrShape : rShape->getChildren()) + { + awt::Point aPosition = aCurrShape->getPosition(); + aPosition.Y += nDiff / 2; + aCurrShape->setPosition(aPosition); + } + } +} + +IteratorAttr::IteratorAttr( ) + : mnCnt( -1 ) + , mbHideLastTrans( true ) + , mnPtType( 0 ) + , mnSt( 0 ) + , mnStep( 1 ) +{ +} + +void IteratorAttr::loadFromXAttr( const Reference< XFastAttributeList >& xAttr ) +{ + AttributeList attr( xAttr ); + maAxis = attr.getTokenList(XML_axis); + mnCnt = attr.getInteger( XML_cnt, -1 ); + mbHideLastTrans = attr.getBool( XML_hideLastTrans, true ); + mnSt = attr.getInteger( XML_st, 0 ); + mnStep = attr.getInteger( XML_step, 1 ); + + // better to keep first token instead of error when multiple values + std::vector<sal_Int32> aPtTypes = attr.getTokenList(XML_ptType); + mnPtType = aPtTypes.empty() ? XML_all : aPtTypes.front(); +} + +ConditionAttr::ConditionAttr() + : mnFunc( 0 ) + , mnArg( 0 ) + , mnOp( 0 ) + , mnVal( 0 ) +{ +} + +void ConditionAttr::loadFromXAttr( const Reference< XFastAttributeList >& xAttr ) +{ + mnFunc = xAttr->getOptionalValueToken( XML_func, 0 ); + mnArg = xAttr->getOptionalValueToken( XML_arg, XML_none ); + mnOp = xAttr->getOptionalValueToken( XML_op, 0 ); + msVal = xAttr->getOptionalValue( XML_val ); + mnVal = xAttr->getOptionalValueToken( XML_val, 0 ); +} + +void LayoutAtom::dump(int level) +{ + SAL_INFO("oox.drawingml", "level = " << level << " - " << msName << " of type " << typeid(*this).name() ); + for (const auto& pAtom : getChildren()) + pAtom->dump(level + 1); +} + +ForEachAtom::ForEachAtom(LayoutNode& rLayoutNode, const Reference< XFastAttributeList >& xAttributes) : + LayoutAtom(rLayoutNode) +{ + maIter.loadFromXAttr(xAttributes); +} + +void ForEachAtom::accept( LayoutAtomVisitor& rVisitor ) +{ + rVisitor.visit(*this); +} + +LayoutAtomPtr ForEachAtom::getRefAtom() +{ + if (!msRef.isEmpty()) + { + const LayoutAtomMap& rLayoutAtomMap = getLayoutNode().getDiagram().getLayout()->getLayoutAtomMap(); + LayoutAtomMap::const_iterator pRefAtom = rLayoutAtomMap.find(msRef); + if (pRefAtom != rLayoutAtomMap.end()) + return pRefAtom->second; + else + SAL_WARN("oox.drawingml", "ForEach reference \"" << msRef << "\" not found"); + } + return LayoutAtomPtr(); +} + +void ChooseAtom::accept( LayoutAtomVisitor& rVisitor ) +{ + rVisitor.visit(*this); +} + +ConditionAtom::ConditionAtom(LayoutNode& rLayoutNode, bool isElse, const Reference< XFastAttributeList >& xAttributes) : + LayoutAtom(rLayoutNode), + mIsElse(isElse) +{ + maIter.loadFromXAttr( xAttributes ); + maCond.loadFromXAttr( xAttributes ); +} + +bool ConditionAtom::compareResult(sal_Int32 nOperator, sal_Int32 nFirst, sal_Int32 nSecond) +{ + switch (nOperator) + { + case XML_equ: return nFirst == nSecond; + case XML_gt: return nFirst > nSecond; + case XML_gte: return nFirst >= nSecond; + case XML_lt: return nFirst < nSecond; + case XML_lte: return nFirst <= nSecond; + case XML_neq: return nFirst != nSecond; + default: + SAL_WARN("oox.drawingml", "unsupported operator: " << nOperator); + return false; + } +} + +namespace +{ +/** + * Takes the connection list from rLayoutNode, navigates from rFrom on an edge + * of type nType, using a direction determined by bSourceToDestination. + */ +OUString navigate(LayoutNode& rLayoutNode, svx::diagram::TypeConstant nType, std::u16string_view rFrom, + bool bSourceToDestination) +{ + for (const auto& rConnection : rLayoutNode.getDiagram().getData()->getConnections()) + { + if (rConnection.mnXMLType != nType) + continue; + + if (bSourceToDestination) + { + if (rConnection.msSourceId == rFrom) + return rConnection.msDestId; + } + else + { + if (rConnection.msDestId == rFrom) + return rConnection.msSourceId; + } + } + + return OUString(); +} + +sal_Int32 calcMaxDepth(std::u16string_view rNodeName, const svx::diagram::Connections& rConnections) +{ + sal_Int32 nMaxLength = 0; + for (auto const& aCxn : rConnections) + if (aCxn.mnXMLType == svx::diagram::TypeConstant::XML_parOf && aCxn.msSourceId == rNodeName) + nMaxLength = std::max(nMaxLength, calcMaxDepth(aCxn.msDestId, rConnections) + 1); + + return nMaxLength; +} +} + +sal_Int32 ConditionAtom::getNodeCount(const svx::diagram::Point* pPresPoint) const +{ + sal_Int32 nCount = 0; + OUString sNodeId = pPresPoint->msPresentationAssociationId; + + // HACK: special case - count children of first child + if (maIter.maAxis.size() == 2 && maIter.maAxis[0] == XML_ch && maIter.maAxis[1] == XML_ch) + sNodeId = navigate(mrLayoutNode, svx::diagram::TypeConstant::XML_parOf, sNodeId, /*bSourceToDestination*/ true); + + if (!sNodeId.isEmpty()) + { + for (const auto& aCxn : mrLayoutNode.getDiagram().getData()->getConnections()) + if (aCxn.mnXMLType == svx::diagram::TypeConstant::XML_parOf && aCxn.msSourceId == sNodeId) + nCount++; + } + + return nCount; +} + +bool ConditionAtom::getDecision(const svx::diagram::Point* pPresPoint) const +{ + if (mIsElse) + return true; + if (!pPresPoint) + return false; + + switch (maCond.mnFunc) + { + case XML_var: + { + if (maCond.mnArg == XML_dir) + return compareResult(maCond.mnOp, pPresPoint->mnDirection, maCond.mnVal); + else if (maCond.mnArg == XML_hierBranch) + { + sal_Int32 nHierarchyBranch = pPresPoint->moHierarchyBranch.value_or(XML_std); + if (!pPresPoint->moHierarchyBranch.has_value()) + { + // If <dgm:hierBranch> is missing in the current presentation + // point, ask the parent. + OUString aParent = navigate(mrLayoutNode, svx::diagram::TypeConstant::XML_presParOf, pPresPoint->msModelId, + /*bSourceToDestination*/ false); + DiagramData::PointNameMap& rPointNameMap + = mrLayoutNode.getDiagram().getData()->getPointNameMap(); + auto it = rPointNameMap.find(aParent); + if (it != rPointNameMap.end()) + { + const svx::diagram::Point* pParent = it->second; + if (pParent->moHierarchyBranch.has_value()) + nHierarchyBranch = pParent->moHierarchyBranch.value(); + } + } + return compareResult(maCond.mnOp, nHierarchyBranch, maCond.mnVal); + } + break; + } + + case XML_cnt: + return compareResult(maCond.mnOp, getNodeCount(pPresPoint), maCond.msVal.toInt32()); + + case XML_maxDepth: + { + sal_Int32 nMaxDepth = calcMaxDepth(pPresPoint->msPresentationAssociationId, mrLayoutNode.getDiagram().getData()->getConnections()); + return compareResult(maCond.mnOp, nMaxDepth, maCond.msVal.toInt32()); + } + + case XML_depth: + case XML_pos: + case XML_revPos: + case XML_posEven: + case XML_posOdd: + // TODO + default: + SAL_WARN("oox.drawingml", "unknown function " << maCond.mnFunc); + break; + } + + return true; +} + +void ConditionAtom::accept( LayoutAtomVisitor& rVisitor ) +{ + rVisitor.visit(*this); +} + +void ConstraintAtom::accept( LayoutAtomVisitor& rVisitor ) +{ + rVisitor.visit(*this); +} + +void RuleAtom::accept( LayoutAtomVisitor& rVisitor ) +{ + rVisitor.visit(*this); +} + +void ConstraintAtom::parseConstraint(std::vector<Constraint>& rConstraints, + bool bRequireForName) const +{ + // Allowlist for cases where empty forName is handled. + if (bRequireForName) + { + switch (maConstraint.mnType) + { + case XML_sp: + case XML_lMarg: + case XML_rMarg: + case XML_tMarg: + case XML_bMarg: + bRequireForName = false; + break; + } + switch (maConstraint.mnPointType) + { + case XML_sibTrans: + bRequireForName = false; + break; + } + } + + if (bRequireForName && maConstraint.msForName.isEmpty()) + return; + + // accepting only basic equality constraints + if ((maConstraint.mnOperator == XML_none || maConstraint.mnOperator == XML_equ) + && maConstraint.mnType != XML_none) + { + rConstraints.push_back(maConstraint); + } +} + +void RuleAtom::parseRule(std::vector<Rule>& rRules) const +{ + if (!maRule.msForName.isEmpty()) + { + rRules.push_back(maRule); + } +} + +void AlgAtom::accept( LayoutAtomVisitor& rVisitor ) +{ + rVisitor.visit(*this); +} + +sal_Int32 AlgAtom::getConnectorType() +{ + sal_Int32 nConnRout = 0; + sal_Int32 nBegSty = 0; + sal_Int32 nEndSty = 0; + if (maMap.count(oox::XML_connRout)) + nConnRout = maMap.find(oox::XML_connRout)->second; + if (maMap.count(oox::XML_begSty)) + nBegSty = maMap.find(oox::XML_begSty)->second; + if (maMap.count(oox::XML_endSty)) + nEndSty = maMap.find(oox::XML_endSty)->second; + + if (nConnRout == oox::XML_bend) + return 0; // was oox::XML_bentConnector3 - connectors are hidden in org chart as they don't work anyway + if (nBegSty == oox::XML_arr && nEndSty == oox::XML_arr) + return oox::XML_leftRightArrow; + if (nBegSty == oox::XML_arr) + return oox::XML_leftArrow; + if (nEndSty == oox::XML_arr) + return oox::XML_rightArrow; + + return oox::XML_rightArrow; +} + +sal_Int32 AlgAtom::getVerticalShapesCount(const ShapePtr& rShape) +{ + if (rShape->getChildren().empty()) + return (rShape->getSubType() != XML_conn) ? 1 : 0; + + sal_Int32 nDir = XML_fromL; + if (mnType == XML_hierRoot) + nDir = XML_fromT; + else if (maMap.count(XML_linDir)) + nDir = maMap.find(XML_linDir)->second; + + const sal_Int32 nSecDir = maMap.count(XML_secLinDir) ? maMap.find(XML_secLinDir)->second : 0; + + sal_Int32 nCount = 0; + if (nDir == XML_fromT || nDir == XML_fromB) + { + for (const ShapePtr& pChild : rShape->getChildren()) + nCount += pChild->getVerticalShapesCount(); + } + else if ((nDir == XML_fromL || nDir == XML_fromR) && nSecDir == XML_fromT) + { + for (const ShapePtr& pChild : rShape->getChildren()) + nCount += pChild->getVerticalShapesCount(); + nCount = (nCount + 1) / 2; + } + else + { + for (const ShapePtr& pChild : rShape->getChildren()) + nCount = std::max(nCount, pChild->getVerticalShapesCount()); + } + + return nCount; +} + +namespace +{ +/// Does the first data node of this shape have customized text properties? +bool HasCustomText(const ShapePtr& rShape, LayoutNode& rLayoutNode) +{ + const PresPointShapeMap& rPresPointShapeMap + = rLayoutNode.getDiagram().getLayout()->getPresPointShapeMap(); + const DiagramData::StringMap& rPresOfNameMap + = rLayoutNode.getDiagram().getData()->getPresOfNameMap(); + const DiagramData::PointNameMap& rPointNameMap + = rLayoutNode.getDiagram().getData()->getPointNameMap(); + // Get the first presentation node of the shape. + const svx::diagram::Point* pPresNode = nullptr; + for (const auto& rPair : rPresPointShapeMap) + { + if (rPair.second == rShape) + { + pPresNode = rPair.first; + break; + } + } + // Get the first data node of the presentation node. + svx::diagram::Point* pDataNode = nullptr; + if (pPresNode) + { + auto itPresToData = rPresOfNameMap.find(pPresNode->msModelId); + if (itPresToData != rPresOfNameMap.end()) + { + for (const auto& rPair : itPresToData->second) + { + const DiagramData::SourceIdAndDepth& rItem = rPair.second; + auto it = rPointNameMap.find(rItem.msSourceId); + if (it != rPointNameMap.end()) + { + pDataNode = it->second; + break; + } + } + } + } + + // If we have a data node, see if its text is customized or not. + if (pDataNode) + { + return pDataNode->mbCustomText; + } + + return false; +} +} + +void AlgAtom::layoutShape(const ShapePtr& rShape, const std::vector<Constraint>& rConstraints, + const std::vector<Rule>& rRules) +{ + if (mnType != XML_lin) + { + // TODO Handle spacing from constraints for non-lin algorithms as well. + std::erase_if( + rShape->getChildren(), + [](const ShapePtr& aChild) { + return aChild->getServiceName() == "com.sun.star.drawing.GroupShape" + && aChild->getChildren().empty(); + }); + } + + switch(mnType) + { + case XML_composite: + { + CompositeAlg::layoutShapeChildren(*this, rShape, rConstraints); + break; + } + + case XML_conn: + { + if (rShape->getSubType() == XML_conn) + { + // There is no shape type "conn", replace it by an arrow based + // on the direction of the parent linear layout. + sal_Int32 nType = getConnectorType(); + + rShape->setSubType(nType); + rShape->getCustomShapeProperties()->setShapePresetType(nType); + } + + // Parse constraints to adjust the size. + std::vector<Constraint> aDirectConstraints; + const LayoutNode& rLayoutNode = getLayoutNode(); + for (const auto& pChild : rLayoutNode.getChildren()) + { + auto pConstraintAtom = dynamic_cast<ConstraintAtom*>(pChild.get()); + if (pConstraintAtom) + pConstraintAtom->parseConstraint(aDirectConstraints, /*bRequireForName=*/false); + } + + LayoutPropertyMap aProperties; + LayoutProperty& rParent = aProperties[""]; + rParent[XML_w] = rShape->getSize().Width; + rParent[XML_h] = rShape->getSize().Height; + rParent[XML_l] = 0; + rParent[XML_t] = 0; + rParent[XML_r] = rShape->getSize().Width; + rParent[XML_b] = rShape->getSize().Height; + for (const auto& rConstr : aDirectConstraints) + { + const LayoutPropertyMap::const_iterator aRef + = aProperties.find(rConstr.msRefForName); + if (aRef != aProperties.end()) + { + const LayoutProperty::const_iterator aRefType + = aRef->second.find(rConstr.mnRefType); + if (aRefType != aRef->second.end()) + aProperties[rConstr.msForName][rConstr.mnType] + = aRefType->second * rConstr.mfFactor; + } + } + awt::Size aSize; + aSize.Width = rParent[XML_w]; + aSize.Height = rParent[XML_h]; + // keep center position + awt::Point aPos = rShape->getPosition(); + aPos.X += (rShape->getSize().Width - aSize.Width) / 2; + aPos.Y += (rShape->getSize().Height - aSize.Height) / 2; + rShape->setPosition(aPos); + rShape->setSize(aSize); + break; + } + + case XML_cycle: + { + if (rShape->getChildren().empty()) + break; + + const sal_Int32 nStartAngle = maMap.count(XML_stAng) ? maMap.find(XML_stAng)->second : 0; + const sal_Int32 nSpanAngle = maMap.count(XML_spanAng) ? maMap.find(XML_spanAng)->second : 360; + const sal_Int32 nRotationPath = maMap.count(XML_rotPath) ? maMap.find(XML_rotPath)->second : XML_none; + const sal_Int32 nctrShpMap = maMap.count(XML_ctrShpMap) ? maMap.find(XML_ctrShpMap)->second : XML_none; + const awt::Size aCenter(rShape->getSize().Width / 2, rShape->getSize().Height / 2); + const awt::Size aChildSize(rShape->getSize().Width / 4, rShape->getSize().Height / 4); + const awt::Size aConnectorSize(rShape->getSize().Width / 12, rShape->getSize().Height / 12); + const sal_Int32 nRadius = std::min( + (rShape->getSize().Width - aChildSize.Width) / 2, + (rShape->getSize().Height - aChildSize.Height) / 2); + + std::vector<oox::drawingml::ShapePtr> aCycleChildren = rShape->getChildren(); + + if (nctrShpMap == XML_fNode) + { + // first node placed in center, others around + oox::drawingml::ShapePtr pCenterShape = aCycleChildren.front(); + aCycleChildren.erase(aCycleChildren.begin()); + const awt::Point aCurrPos(aCenter.Width - aChildSize.Width / 2, + aCenter.Height - aChildSize.Height / 2); + pCenterShape->setPosition(aCurrPos); + pCenterShape->setSize(aChildSize); + pCenterShape->setChildSize(aChildSize); + } + + const sal_Int32 nShapes = aCycleChildren.size(); + if (nShapes) + { + const sal_Int32 nConnectorRadius = nRadius * cos(basegfx::deg2rad(nSpanAngle / nShapes)); + const sal_Int32 nConnectorAngle = nSpanAngle > 0 ? 0 : 180; + + sal_Int32 idx = 0; + for (auto & aCurrShape : aCycleChildren) + { + const double fAngle = static_cast<double>(idx)*nSpanAngle/nShapes + nStartAngle; + awt::Size aCurrSize = aChildSize; + sal_Int32 nCurrRadius = nRadius; + if (aCurrShape->getSubType() == XML_conn) + { + aCurrSize = aConnectorSize; + nCurrRadius = nConnectorRadius; + } + const awt::Point aCurrPos( + aCenter.Width + nCurrRadius*sin(basegfx::deg2rad(fAngle)) - aCurrSize.Width/2, + aCenter.Height - nCurrRadius*cos(basegfx::deg2rad(fAngle)) - aCurrSize.Height/2); + + aCurrShape->setPosition(aCurrPos); + aCurrShape->setSize(aCurrSize); + aCurrShape->setChildSize(aCurrSize); + + if (nRotationPath == XML_alongPath) + aCurrShape->setRotation(fAngle * PER_DEGREE); + + // connectors should be handled in conn, but we don't have + // reference to previous and next child, so it's easier here + if (aCurrShape->getSubType() == XML_conn) + aCurrShape->setRotation((nConnectorAngle + fAngle) * PER_DEGREE); + + idx++; + } + } + break; + } + + case XML_hierChild: + case XML_hierRoot: + { + if (rShape->getChildren().empty() || rShape->getSize().Width == 0 || rShape->getSize().Height == 0) + break; + + // hierRoot is the manager -> employees vertical linear path, + // hierChild is the first employee -> last employee horizontal + // linear path. + sal_Int32 nDir = XML_fromL; + if (mnType == XML_hierRoot) + nDir = XML_fromT; + else if (maMap.count(XML_linDir)) + nDir = maMap.find(XML_linDir)->second; + + const sal_Int32 nSecDir = maMap.count(XML_secLinDir) ? maMap.find(XML_secLinDir)->second : 0; + + sal_Int32 nCount = rShape->getChildren().size(); + + if (mnType == XML_hierChild) + { + // Connectors should not influence the size of non-connect shapes. + nCount = std::count_if( + rShape->getChildren().begin(), rShape->getChildren().end(), + [](const ShapePtr& pShape) { return pShape->getSubType() != XML_conn; }); + } + + const double fSpaceWidth = 0.1; + const double fSpaceHeight = 0.3; + + if (mnType == XML_hierRoot && nCount == 3) + { + // Order assistant nodes above employee nodes. + std::vector<ShapePtr>& rChildren = rShape->getChildren(); + if (!containsDataNodeType(rChildren[1], XML_asst) + && containsDataNodeType(rChildren[2], XML_asst)) + std::swap(rChildren[1], rChildren[2]); + } + + sal_Int32 nHorizontalShapesCount = 1; + if (nSecDir == XML_fromT) + nHorizontalShapesCount = 2; + else if (nDir == XML_fromL || nDir == XML_fromR) + nHorizontalShapesCount = nCount; + + awt::Size aChildSize = rShape->getSize(); + aChildSize.Height /= (rShape->getVerticalShapesCount() + (rShape->getVerticalShapesCount() - 1) * fSpaceHeight); + aChildSize.Width /= (nHorizontalShapesCount + (nHorizontalShapesCount - 1) * fSpaceWidth); + + awt::Size aConnectorSize = aChildSize; + aConnectorSize.Width = 1; + + awt::Point aChildPos(0, 0); + + // indent children to show they are descendants, not siblings + if (mnType == XML_hierChild && nHorizontalShapesCount == 1) + { + const double fChildIndent = 0.1; + aChildPos.X = aChildSize.Width * fChildIndent; + aChildSize.Width *= (1 - 2 * fChildIndent); + } + + sal_Int32 nIdx = 0; + sal_Int32 nRowHeight = 0; + for (auto& pChild : rShape->getChildren()) + { + pChild->setPosition(aChildPos); + + if (mnType == XML_hierChild && pChild->getSubType() == XML_conn) + { + // Connectors should not influence the position of + // non-connect shapes. + pChild->setSize(aConnectorSize); + pChild->setChildSize(aConnectorSize); + continue; + } + + awt::Size aCurrSize = aChildSize; + aCurrSize.Height *= pChild->getVerticalShapesCount() + (pChild->getVerticalShapesCount() - 1) * fSpaceHeight; + + pChild->setSize(aCurrSize); + pChild->setChildSize(aCurrSize); + + if (nDir == XML_fromT || nDir == XML_fromB) + aChildPos.Y += aCurrSize.Height + aChildSize.Height * fSpaceHeight; + else + aChildPos.X += aCurrSize.Width + aCurrSize.Width * fSpaceWidth; + + nRowHeight = std::max(nRowHeight, aCurrSize.Height); + + if (nSecDir == XML_fromT && nIdx % 2 == 1) + { + aChildPos.X = 0; + aChildPos.Y += nRowHeight + aChildSize.Height * fSpaceHeight; + nRowHeight = 0; + } + + nIdx++; + } + + break; + } + + case XML_lin: + { + // spread children evenly across one axis, stretch across second + + if (rShape->getChildren().empty() || rShape->getSize().Width == 0 || rShape->getSize().Height == 0) + break; + + const sal_Int32 nDir = maMap.count(XML_linDir) ? maMap.find(XML_linDir)->second : XML_fromL; + const sal_Int32 nIncX = nDir==XML_fromL ? 1 : (nDir==XML_fromR ? -1 : 0); + const sal_Int32 nIncY = nDir==XML_fromT ? 1 : (nDir==XML_fromB ? -1 : 0); + + double fCount = rShape->getChildren().size(); + sal_Int32 nConnectorAngle = 0; + switch (nDir) + { + case XML_fromL: nConnectorAngle = 0; break; + case XML_fromR: nConnectorAngle = 180; break; + case XML_fromT: nConnectorAngle = 270; break; + case XML_fromB: nConnectorAngle = 90; break; + } + + awt::Size aSpaceSize; + + // Find out which constraint is relevant for which (internal) name. + LayoutPropertyMap aProperties; + for (const auto& rConstraint : rConstraints) + { + if (rConstraint.msForName.isEmpty()) + continue; + + LayoutProperty& rProperty = aProperties[rConstraint.msForName]; + if (rConstraint.mnType == XML_w) + { + rProperty[XML_w] = rShape->getSize().Width * rConstraint.mfFactor; + if (rProperty[XML_w] > rShape->getSize().Width) + { + rProperty[XML_w] = rShape->getSize().Width; + } + } + if (rConstraint.mnType == XML_h) + { + rProperty[XML_h] = rShape->getSize().Height * rConstraint.mfFactor; + if (rProperty[XML_h] > rShape->getSize().Height) + { + rProperty[XML_h] = rShape->getSize().Height; + } + } + + if (rConstraint.mnType == XML_primFontSz && rConstraint.mnFor == XML_des + && rConstraint.mnOperator == XML_equ) + { + NamedShapePairs& rDiagramFontHeights + = getLayoutNode().getDiagram().getDiagramFontHeights(); + auto it = rDiagramFontHeights.find(rConstraint.msForName); + if (it == rDiagramFontHeights.end()) + { + // Start tracking all shapes with this internal name: they'll have the same + // font height. + rDiagramFontHeights[rConstraint.msForName] = {}; + } + } + + // TODO: get values from differently named constraints as well + if (rConstraint.msForName == "sp" || rConstraint.msForName == "space" || rConstraint.msForName == "sibTrans") + { + if (rConstraint.mnType == XML_w) + aSpaceSize.Width = rShape->getSize().Width * rConstraint.mfFactor; + if (rConstraint.mnType == XML_h) + aSpaceSize.Height = rShape->getSize().Height * rConstraint.mfFactor; + } + } + + // first approximation of children size + std::set<OUString> aChildrenToShrink; + for (const auto& rRule : rRules) + { + // Consider rules: when scaling down, only change children where the rule allows + // doing so. + aChildrenToShrink.insert(rRule.msForName); + } + + if (nDir == XML_fromT || nDir == XML_fromB) + { + // TODO consider rules for vertical linear layout as well. + aChildrenToShrink.clear(); + } + + if (!aChildrenToShrink.empty()) + { + // Have scaling info from rules: then only count scaled children. + // Also count children which are a fraction of a scaled child. + std::set<OUString> aChildrenToShrinkDeps; + for (auto& aCurrShape : rShape->getChildren()) + { + if (aChildrenToShrink.find(aCurrShape->getInternalName()) + == aChildrenToShrink.end()) + { + if (fCount > 1.0) + { + fCount -= 1.0; + + bool bIsDependency = false; + double fFactor = 0; + for (const auto& rConstraint : rConstraints) + { + if (rConstraint.msForName != aCurrShape->getInternalName()) + { + continue; + } + + if ((nDir == XML_fromL || nDir == XML_fromR) && rConstraint.mnType != XML_w) + { + continue; + } + if ((nDir == XML_fromL || nDir == XML_fromR) && rConstraint.mnType == XML_w) + { + fFactor = rConstraint.mfFactor; + } + + if ((nDir == XML_fromT || nDir == XML_fromB) && rConstraint.mnType != XML_h) + { + continue; + } + if ((nDir == XML_fromT || nDir == XML_fromB) && rConstraint.mnType == XML_h) + { + fFactor = rConstraint.mfFactor; + } + + if (aChildrenToShrink.find(rConstraint.msRefForName) == aChildrenToShrink.end()) + { + continue; + } + + // At this point we have a child with a size which is a factor of an + // other child which will be scaled. + fCount += rConstraint.mfFactor; + aChildrenToShrinkDeps.insert(aCurrShape->getInternalName()); + bIsDependency = true; + break; + } + + if (!bIsDependency && aCurrShape->getServiceName() == "com.sun.star.drawing.GroupShape") + { + bool bScaleDownEmptySpacing = false; + if (nDir == XML_fromL || nDir == XML_fromR) + { + std::optional<sal_Int32> oWidth = findProperty(aProperties, aCurrShape->getInternalName(), XML_w); + bScaleDownEmptySpacing = oWidth.has_value() && oWidth.value() > 0; + } + if (!bScaleDownEmptySpacing && (nDir == XML_fromT || nDir == XML_fromB)) + { + std::optional<sal_Int32> oHeight = findProperty(aProperties, aCurrShape->getInternalName(), XML_h); + bScaleDownEmptySpacing = oHeight.has_value() && oHeight.value() > 0; + } + if (bScaleDownEmptySpacing && aCurrShape->getChildren().empty()) + { + fCount += fFactor; + aChildrenToShrinkDeps.insert(aCurrShape->getInternalName()); + } + } + } + } + } + + aChildrenToShrink.insert(aChildrenToShrinkDeps.begin(), aChildrenToShrinkDeps.end()); + + // No manual spacing: spacings are children as well. + aSpaceSize = awt::Size(); + } + else + { + // TODO Handle spacing from constraints without rules as well. + std::erase_if( + rShape->getChildren(), + [](const ShapePtr& aChild) { + return aChild->getServiceName() + == "com.sun.star.drawing.GroupShape" + && aChild->getChildren().empty(); + }); + fCount = rShape->getChildren().size(); + } + awt::Size aChildSize = rShape->getSize(); + if (nDir == XML_fromL || nDir == XML_fromR) + aChildSize.Width /= fCount; + else if (nDir == XML_fromT || nDir == XML_fromB) + aChildSize.Height /= fCount; + + awt::Point aCurrPos(0, 0); + if (nIncX == -1) + aCurrPos.X = rShape->getSize().Width - aChildSize.Width; + if (nIncY == -1) + aCurrPos.Y = rShape->getSize().Height - aChildSize.Height; + + // See if children requested more than 100% space in total: scale + // down in that case. + awt::Size aTotalSize; + for (const auto & aCurrShape : rShape->getChildren()) + { + std::optional<sal_Int32> oWidth = findProperty(aProperties, aCurrShape->getInternalName(), XML_w); + std::optional<sal_Int32> oHeight = findProperty(aProperties, aCurrShape->getInternalName(), XML_h); + awt::Size aSize = aChildSize; + if (oWidth.has_value()) + aSize.Width = oWidth.value(); + if (oHeight.has_value()) + aSize.Height = oHeight.value(); + aTotalSize.Width += aSize.Width; + aTotalSize.Height += aSize.Height; + } + + aTotalSize.Width += (fCount-1) * aSpaceSize.Width; + aTotalSize.Height += (fCount-1) * aSpaceSize.Height; + + double fWidthScale = 1.0; + double fHeightScale = 1.0; + if (nIncX && aTotalSize.Width > rShape->getSize().Width) + fWidthScale = static_cast<double>(rShape->getSize().Width) / aTotalSize.Width; + if (nIncY && aTotalSize.Height > rShape->getSize().Height) + fHeightScale = static_cast<double>(rShape->getSize().Height) / aTotalSize.Height; + aSpaceSize.Width *= fWidthScale; + aSpaceSize.Height *= fHeightScale; + + for (auto& aCurrShape : rShape->getChildren()) + { + // Extract properties relevant for this shape from constraints. + std::optional<sal_Int32> oWidth = findProperty(aProperties, aCurrShape->getInternalName(), XML_w); + std::optional<sal_Int32> oHeight = findProperty(aProperties, aCurrShape->getInternalName(), XML_h); + + awt::Size aSize = aChildSize; + if (oWidth.has_value()) + aSize.Width = oWidth.value(); + if (oHeight.has_value()) + aSize.Height = oHeight.value(); + if (aChildrenToShrink.empty() + || aChildrenToShrink.find(aCurrShape->getInternalName()) + != aChildrenToShrink.end()) + { + aSize.Width *= fWidthScale; + } + if (aChildrenToShrink.empty() + || aChildrenToShrink.find(aCurrShape->getInternalName()) + != aChildrenToShrink.end()) + { + aSize.Height *= fHeightScale; + } + aCurrShape->setSize(aSize); + aCurrShape->setChildSize(aSize); + + // center in the other axis - probably some parameter controls it + if (nIncX) + aCurrPos.Y = (rShape->getSize().Height - aSize.Height) / 2; + if (nIncY) + aCurrPos.X = (rShape->getSize().Width - aSize.Width) / 2; + if (aCurrPos.X < 0) + { + aCurrPos.X = 0; + } + if (aCurrPos.Y < 0) + { + aCurrPos.Y = 0; + } + + aCurrShape->setPosition(aCurrPos); + + aCurrPos.X += nIncX * (aSize.Width + aSpaceSize.Width); + aCurrPos.Y += nIncY * (aSize.Height + aSpaceSize.Height); + + // connectors should be handled in conn, but we don't have + // reference to previous and next child, so it's easier here + if (aCurrShape->getSubType() == XML_conn) + aCurrShape->setRotation(nConnectorAngle * PER_DEGREE); + } + + // Newer shapes are behind older ones by default. Reverse this if requested. + sal_Int32 nChildOrder = XML_b; + const LayoutNode* pParentLayoutNode = nullptr; + for (LayoutAtomPtr pAtom = getParent(); pAtom; pAtom = pAtom->getParent()) + { + auto pLayoutNode = dynamic_cast<LayoutNode*>(pAtom.get()); + if (pLayoutNode) + { + pParentLayoutNode = pLayoutNode; + break; + } + } + if (pParentLayoutNode) + { + nChildOrder = pParentLayoutNode->getChildOrder(); + } + if (nChildOrder == XML_t) + { + std::reverse(rShape->getChildren().begin(), rShape->getChildren().end()); + } + + break; + } + + case XML_pyra: + { + PyraAlg::layoutShapeChildren(rShape); + break; + } + + case XML_snake: + { + SnakeAlg::layoutShapeChildren(*this, rShape, rConstraints); + break; + } + + case XML_sp: + { + // HACK: Handled one level higher. Or rather, planned to + // HACK: text should appear only in tx node; we're assigning it earlier, so let's remove it here + rShape->setTextBody(TextBodyPtr()); + break; + } + + case XML_tx: + { + // adjust text alignment + + // Parse constraints, only self margins as a start. + double fFontSize = 0; + for (const auto& rConstr : rConstraints) + { + if (rConstr.mnRefType == XML_w) + { + if (!rConstr.msForName.isEmpty()) + continue; + + sal_Int32 nProperty = getPropertyFromConstraint(rConstr.mnType); + if (!nProperty) + continue; + + // PowerPoint takes size as points, but gives margin as MMs. + double fFactor = convertPointToMms(rConstr.mfFactor); + + // DrawingML works in EMUs, UNO API works in MM100s. + sal_Int32 nValue = o3tl::convert(rShape->getSize().Width * fFactor, + o3tl::Length::emu, o3tl::Length::mm100); + + rShape->getShapeProperties().setProperty(nProperty, nValue); + } + if (rConstr.mnType == XML_primFontSz) + fFontSize = rConstr.mfValue; + } + + TextBodyPtr pTextBody = rShape->getTextBody(); + if (!pTextBody || pTextBody->isEmpty()) + break; + + // adjust text size to fit shape + if (fFontSize != 0) + { + for (auto& aParagraph : pTextBody->getParagraphs()) + for (auto& aRun : aParagraph->getRuns()) + if (!aRun->getTextCharacterProperties().moHeight.has_value()) + aRun->getTextCharacterProperties().moHeight = fFontSize * 100; + } + + if (!HasCustomText(rShape, getLayoutNode())) + { + // No customized text properties: enable autofit. + pTextBody->getTextProperties().maPropertyMap.setProperty( + PROP_TextFitToSize, drawing::TextFitToSizeType_AUTOFIT); + } + + // ECMA-376-1:2016 21.4.7.5 ST_AutoTextRotation (Auto Text Rotation) + const sal_Int32 nautoTxRot = maMap.count(XML_autoTxRot) ? maMap.find(XML_autoTxRot)->second : XML_upr; + sal_Int32 nShapeRot = rShape->getRotation(); + while (nShapeRot < 0) + nShapeRot += 360 * PER_DEGREE; + while (nShapeRot > 360 * PER_DEGREE) + nShapeRot -= 360 * PER_DEGREE; + + switch(nautoTxRot) + { + case XML_upr: + { + int n90x = 0; + if (nShapeRot >= 315 * PER_DEGREE) + /* keep 0 */; + else if (nShapeRot > 225 * PER_DEGREE) + n90x = -3; + else if (nShapeRot >= 135 * PER_DEGREE) + n90x = -2; + else if (nShapeRot > 45 * PER_DEGREE) + n90x = -1; + pTextBody->getTextProperties().moTextPreRotation = n90x * 90 * PER_DEGREE; + } + break; + case XML_grav: + { + if (nShapeRot > (90 * PER_DEGREE) && nShapeRot < (270 * PER_DEGREE)) + pTextBody->getTextProperties().moTextPreRotation = -180 * PER_DEGREE; + } + break; + case XML_none: + break; + } + + const sal_Int32 atxAnchorVert = maMap.count(XML_txAnchorVert) ? maMap.find(XML_txAnchorVert)->second : XML_mid; + + switch(atxAnchorVert) + { + case XML_t: + pTextBody->getTextProperties().meVA = css::drawing::TextVerticalAdjust_TOP; + break; + case XML_b: + pTextBody->getTextProperties().meVA = css::drawing::TextVerticalAdjust_BOTTOM; + break; + case XML_mid: + // text centered vertically by default + default: + pTextBody->getTextProperties().meVA = css::drawing::TextVerticalAdjust_CENTER; + break; + } + + pTextBody->getTextProperties().maPropertyMap.setProperty(PROP_TextVerticalAdjust, pTextBody->getTextProperties().meVA); + + // normalize list level + sal_Int32 nBaseLevel = pTextBody->getParagraphs().front()->getProperties().getLevel(); + for (auto & aParagraph : pTextBody->getParagraphs()) + { + if (aParagraph->getProperties().getLevel() < nBaseLevel) + nBaseLevel = aParagraph->getProperties().getLevel(); + } + + // Start bullets at: + // 1 - top level + // 2 - with children (default) + int nStartBulletsAtLevel = 2; + ParamMap::const_iterator aBulletLvl = maMap.find(XML_stBulletLvl); + if (aBulletLvl != maMap.end()) + nStartBulletsAtLevel = aBulletLvl->second; + nStartBulletsAtLevel--; + + bool isBulletList = false; + for (auto & aParagraph : pTextBody->getParagraphs()) + { + sal_Int32 nLevel = aParagraph->getProperties().getLevel() - nBaseLevel; + aParagraph->getProperties().setLevel(nLevel); + if (nLevel >= nStartBulletsAtLevel) + { + if (!aParagraph->getProperties().getParaLeftMargin().has_value()) + { + sal_Int32 nLeftMargin + = o3tl::convert(285750 * (nLevel - nStartBulletsAtLevel + 1), + o3tl::Length::emu, o3tl::Length::mm100); + aParagraph->getProperties().getParaLeftMargin() = nLeftMargin; + } + + if (!aParagraph->getProperties().getFirstLineIndentation().has_value()) + aParagraph->getProperties().getFirstLineIndentation() + = o3tl::convert(-285750, o3tl::Length::emu, o3tl::Length::mm100); + + // It is not possible to change the bullet style for text. + aParagraph->getProperties().getBulletList().setBulletChar(u"•"_ustr); + aParagraph->getProperties().getBulletList().setSuffixNone(); + isBulletList = true; + } + } + + // explicit alignment + ParamMap::const_iterator aDir = maMap.find(XML_parTxLTRAlign); + // TODO: XML_parTxRTLAlign + if (aDir != maMap.end()) + { + css::style::ParagraphAdjust aAlignment = GetParaAdjust(aDir->second); + for (auto & aParagraph : pTextBody->getParagraphs()) + aParagraph->getProperties().setParaAdjust(aAlignment); + } + else if (!isBulletList) + { + // if not list use default alignment - centered + for (auto & aParagraph : pTextBody->getParagraphs()) + aParagraph->getProperties().setParaAdjust(css::style::ParagraphAdjust::ParagraphAdjust_CENTER); + } + break; + } + + default: + break; + } + + SAL_INFO( + "oox.drawingml", + "Layouting shape " << rShape->getInternalName() << ", alg type: " << mnType << ", (" + << rShape->getPosition().X << "," << rShape->getPosition().Y << "," + << rShape->getSize().Width << "," << rShape->getSize().Height << ")"); +} + +void LayoutNode::accept( LayoutAtomVisitor& rVisitor ) +{ + rVisitor.visit(*this); +} + +bool LayoutNode::setupShape( const ShapePtr& rShape, const svx::diagram::Point* pPresNode, sal_Int32 nCurrIdx ) const +{ + SAL_INFO( + "oox.drawingml", + "Filling content from layout node named \"" << msName + << "\", modelId \"" << pPresNode->msModelId << "\""); + + // have the presentation node - now, need the actual data node: + const DiagramData::StringMap::const_iterator aNodeName = mrDgm.getData()->getPresOfNameMap().find( + pPresNode->msModelId); + if( aNodeName != mrDgm.getData()->getPresOfNameMap().end() ) + { + // Calculate the depth of what is effectively the topmost element. + sal_Int32 nMinDepth = std::numeric_limits<sal_Int32>::max(); + for (const auto& rPair : aNodeName->second) + { + if (rPair.second.mnDepth < nMinDepth) + nMinDepth = rPair.second.mnDepth; + } + + for (const auto& rPair : aNodeName->second) + { + const DiagramData::SourceIdAndDepth& rItem = rPair.second; + DiagramData::PointNameMap& rMap = mrDgm.getData()->getPointNameMap(); + // pPresNode is the presentation node of the aDataNode2 data node. + DiagramData::PointNameMap::const_iterator aDataNode2 = rMap.find(rItem.msSourceId); + if (aDataNode2 == rMap.end()) + { + //busted, skip it + continue; + } + + Shape* pDataNode2Shape(mrDgm.getData()->getOrCreateAssociatedShape(*aDataNode2->second)); + if (nullptr == pDataNode2Shape) + { + //busted, skip it + continue; + } + + rShape->setDataNodeType(aDataNode2->second->mnXMLType); + + if (rItem.mnDepth == 0) + { + // grab shape attr from topmost element(s) + rShape->getShapeProperties() = pDataNode2Shape->getShapeProperties(); + rShape->getLineProperties() = pDataNode2Shape->getLineProperties(); + rShape->getFillProperties() = pDataNode2Shape->getFillProperties(); + rShape->getCustomShapeProperties() = pDataNode2Shape->getCustomShapeProperties(); + rShape->setMasterTextListStyle( pDataNode2Shape->getMasterTextListStyle() ); + + SAL_INFO( + "oox.drawingml", + "Custom shape with preset type " + << (rShape->getCustomShapeProperties() + ->getShapePresetType()) + << " added for layout node named \"" << msName + << "\""); + } + else if (rItem.mnDepth == nMinDepth) + { + // If no real topmost element, then take properties from the one that's the closest + // to topmost. + rShape->getLineProperties() = pDataNode2Shape->getLineProperties(); + rShape->getFillProperties() = pDataNode2Shape->getFillProperties(); + } + + // append text with right outline level + if( pDataNode2Shape->getTextBody() && + !pDataNode2Shape->getTextBody()->getParagraphs().empty() && + !pDataNode2Shape->getTextBody()->getParagraphs().front()->getRuns().empty() ) + { + TextBodyPtr pTextBody=rShape->getTextBody(); + if( !pTextBody ) + { + pTextBody = std::make_shared<TextBody>(); + + // also copy text attrs + pTextBody->getTextListStyle() = + pDataNode2Shape->getTextBody()->getTextListStyle(); + pTextBody->getTextProperties() = + pDataNode2Shape->getTextBody()->getTextProperties(); + + rShape->setTextBody(pTextBody); + } + + const TextParagraphVector& rSourceParagraphs + = pDataNode2Shape->getTextBody()->getParagraphs(); + for (const auto& pSourceParagraph : rSourceParagraphs) + { + TextParagraph& rPara = pTextBody->addParagraph(); + if (rItem.mnDepth != -1) + rPara.getProperties().setLevel(rItem.mnDepth); + + for (const auto& pRun : pSourceParagraph->getRuns()) + rPara.addRun(pRun); + const TextBodyPtr& rBody = pDataNode2Shape->getTextBody(); + rPara.getProperties().apply(rBody->getParagraphs().front()->getProperties()); + } + } + } + } + else + { + SAL_INFO( + "oox.drawingml", + "ShapeCreationVisitor::visit: no data node name found while" + " processing shape type " + << rShape->getCustomShapeProperties()->getShapePresetType() + << " for layout node named \"" << msName << "\""); + Shape* pPresNodeShape(mrDgm.getData()->getOrCreateAssociatedShape(*pPresNode)); + if (nullptr != pPresNodeShape) + rShape->getFillProperties().assignUsed(pPresNodeShape->getFillProperties()); + } + + // TODO(Q1): apply styling & coloring - take presentation + // point's presStyleLbl for both style & color + // if not found use layout node's styleLbl + // however, docs are a bit unclear on this + OUString aStyleLabel = pPresNode->msPresentationLayoutStyleLabel; + if (aStyleLabel.isEmpty()) + aStyleLabel = msStyleLabel; + if( !aStyleLabel.isEmpty() ) + { + const DiagramQStyleMap::const_iterator aStyle = mrDgm.getStyles().find(aStyleLabel); + if( aStyle != mrDgm.getStyles().end() ) + { + const DiagramStyle& rStyle = aStyle->second; + rShape->getShapeStyleRefs()[XML_fillRef] = rStyle.maFillStyle; + rShape->getShapeStyleRefs()[XML_lnRef] = rStyle.maLineStyle; + rShape->getShapeStyleRefs()[XML_effectRef] = rStyle.maEffectStyle; + rShape->getShapeStyleRefs()[XML_fontRef] = rStyle.maTextStyle; + } + else + { + SAL_WARN("oox.drawingml", "Style " << aStyleLabel << " not found"); + } + + const DiagramColorMap::const_iterator aColor = mrDgm.getColors().find(aStyleLabel); + if( aColor != mrDgm.getColors().end() ) + { + // Take the nth color from the color list in case we are the nth shape in a + // <dgm:forEach> loop. + const DiagramColor& rColor=aColor->second; + if( !rColor.maFillColors.empty() ) + rShape->getShapeStyleRefs()[XML_fillRef].maPhClr = DiagramColor::getColorByIndex(rColor.maFillColors, nCurrIdx); + if( !rColor.maLineColors.empty() ) + rShape->getShapeStyleRefs()[XML_lnRef].maPhClr = DiagramColor::getColorByIndex(rColor.maLineColors, nCurrIdx); + if( !rColor.maEffectColors.empty() ) + rShape->getShapeStyleRefs()[XML_effectRef].maPhClr = DiagramColor::getColorByIndex(rColor.maEffectColors, nCurrIdx); + if( !rColor.maTextFillColors.empty() ) + rShape->getShapeStyleRefs()[XML_fontRef].maPhClr = DiagramColor::getColorByIndex(rColor.maTextFillColors, nCurrIdx); + } + } + + // even if no data node found, successful anyway. it's + // contained at the layoutnode + return true; +} + +const LayoutNode* LayoutNode::getParentLayoutNode() const +{ + for (LayoutAtomPtr pAtom = getParent(); pAtom; pAtom = pAtom->getParent()) + { + auto pLayoutNode = dynamic_cast<LayoutNode*>(pAtom.get()); + if (pLayoutNode) + return pLayoutNode; + } + + return nullptr; +} + +void ShapeAtom::accept( LayoutAtomVisitor& rVisitor ) +{ + rVisitor.visit(*this); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/diagramlayoutatoms.hxx b/oox/source/drawingml/diagram/diagramlayoutatoms.hxx new file mode 100644 index 0000000000..5458999029 --- /dev/null +++ b/oox/source/drawingml/diagram/diagramlayoutatoms.hxx @@ -0,0 +1,373 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_DIAGRAMLAYOUTATOMS_HXX +#define INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_DIAGRAMLAYOUTATOMS_HXX + +#include <map> +#include <memory> + +#include <com/sun/star/xml/sax/XFastAttributeList.hpp> +#include <utility> + +#include "diagram.hxx" + +namespace oox::drawingml { + +class DiagramLayout; +typedef std::shared_ptr< DiagramLayout > DiagramLayoutPtr; + +// AG_IteratorAttributes +struct IteratorAttr +{ + IteratorAttr(); + + // not sure this belong here, but wth + void loadFromXAttr( const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttributes ); + + std::vector<sal_Int32> maAxis; + sal_Int32 mnCnt; + bool mbHideLastTrans; + sal_Int32 mnPtType; + sal_Int32 mnSt; + sal_Int32 mnStep; +}; + +struct ConditionAttr +{ + ConditionAttr(); + + // not sure this belong here, but wth + void loadFromXAttr( const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttributes ); + + OUString msVal; + sal_Int32 mnFunc; + sal_Int32 mnArg; + sal_Int32 mnOp; + sal_Int32 mnVal; +}; + +/// Constraints allow you to specify an ideal (or starting point) size for each shape. +struct Constraint +{ + OUString msForName; + OUString msRefForName; + double mfFactor; + double mfValue; + sal_Int32 mnFor; + sal_Int32 mnPointType; + sal_Int32 mnType; + sal_Int32 mnRefFor; + sal_Int32 mnRefType; + sal_Int32 mnRefPointType; + sal_Int32 mnOperator; +}; + +/// Rules allow you to specify what to do when constraints can't be fully satisfied. +struct Rule +{ + OUString msForName; +}; + +typedef std::map<sal_Int32, sal_Int32> LayoutProperty; +typedef std::map<OUString, LayoutProperty> LayoutPropertyMap; + +struct LayoutAtomVisitor; +class LayoutAtom; +class LayoutNode; + +typedef std::shared_ptr< LayoutAtom > LayoutAtomPtr; + +/** abstract Atom for the layout */ +class LayoutAtom +{ +public: + LayoutAtom(LayoutNode& rLayoutNode) : mrLayoutNode(rLayoutNode) {} + virtual ~LayoutAtom() { } + + LayoutNode& getLayoutNode() + { return mrLayoutNode; } + + /** visitor acceptance + */ + virtual void accept( LayoutAtomVisitor& ) = 0; + + void setName( const OUString& sName ) + { msName = sName; } + const OUString& getName() const + { return msName; } + +private: + void addChild( const LayoutAtomPtr & pNode ) + { mpChildNodes.push_back( pNode ); } + void setParent(const LayoutAtomPtr& pParent) { mpParent = pParent; } + +public: + const std::vector<LayoutAtomPtr>& getChildren() const + { return mpChildNodes; } + + LayoutAtomPtr getParent() const { return mpParent.lock(); } + + static void connect(const LayoutAtomPtr& pParent, const LayoutAtomPtr& pChild) + { + pParent->addChild(pChild); + pChild->setParent(pParent); + } + + // dump for debug + void dump(int level = 0); + +protected: + LayoutNode& mrLayoutNode; + std::vector< LayoutAtomPtr > mpChildNodes; + std::weak_ptr<LayoutAtom> mpParent; + OUString msName; +}; + +class ConstraintAtom + : public LayoutAtom +{ +public: + ConstraintAtom(LayoutNode& rLayoutNode) : LayoutAtom(rLayoutNode) {} + virtual void accept( LayoutAtomVisitor& ) override; + Constraint& getConstraint() + { return maConstraint; } + void parseConstraint(std::vector<Constraint>& rConstraints, bool bRequireForName) const; +private: + Constraint maConstraint; +}; + +/// Represents one <dgm:rule> element. +class RuleAtom + : public LayoutAtom +{ +public: + RuleAtom(LayoutNode& rLayoutNode) : LayoutAtom(rLayoutNode) {} + virtual void accept( LayoutAtomVisitor& ) override; + Rule& getRule() + { return maRule; } + void parseRule(std::vector<Rule>& rRules) const; +private: + Rule maRule; +}; + +class AlgAtom + : public LayoutAtom +{ +public: + AlgAtom(LayoutNode& rLayoutNode) : LayoutAtom(rLayoutNode), mnType(0), maMap() {} + + typedef std::map<sal_Int32,sal_Int32> ParamMap; + + virtual void accept( LayoutAtomVisitor& ) override; + + void setType( sal_Int32 nToken ) + { mnType = nToken; } + const ParamMap& getMap() const { return maMap; } + void addParam( sal_Int32 nType, sal_Int32 nVal ) + { maMap[nType]=nVal; } + sal_Int32 getVerticalShapesCount(const ShapePtr& rShape); + void layoutShape( const ShapePtr& rShape, + const std::vector<Constraint>& rConstraints, + const std::vector<Rule>& rRules ); + + void setAspectRatio(double fAspectRatio) { mfAspectRatio = fAspectRatio; } + + double getAspectRatio() const { return mfAspectRatio; } + +private: + sal_Int32 mnType; + ParamMap maMap; + /// Aspect ratio is not integer, so not part of maMap. + double mfAspectRatio = 0; + + /// Determines the connector shape type for conn algorithm + sal_Int32 getConnectorType(); +}; + +typedef std::shared_ptr< AlgAtom > AlgAtomPtr; + +/// Finds optimal grid to layout children that have fixed aspect ratio. +class SnakeAlg +{ +public: + static void layoutShapeChildren(const AlgAtom& rAlg, const ShapePtr& rShape, + const std::vector<Constraint>& rConstraints); +}; + +/** + * Lays out child layout nodes along a vertical path and works with the trapezoid shape to create a + * pyramid. + */ +class PyraAlg +{ +public: + static void layoutShapeChildren(const ShapePtr& rShape); +}; + +/** + * Specifies the size and position for all child layout nodes. + */ +class CompositeAlg +{ +public: + static void layoutShapeChildren(AlgAtom& rAlg, const ShapePtr& rShape, + const std::vector<Constraint>& rConstraints); + +private: + /** + * Apply rConstraint to the rProperties shared layout state. + * + * Note that the order in which constraints are applied matters, given that constraints can refer to + * each other, and in case A depends on B and A is applied before B, the effect of A won't be + * updated when B is applied. + */ + static void applyConstraintToLayout(const Constraint& rConstraint, + LayoutPropertyMap& rProperties); + + /** + * Decides if a certain reference type (e.g. "right") can be inferred from the available properties + * in rMap (e.g. left and width). Returns true if rValue is written to. + */ + static bool inferFromLayoutProperty(const LayoutProperty& rMap, sal_Int32 nRefType, + sal_Int32& rValue); +}; + +class ForEachAtom + : public LayoutAtom +{ +public: + explicit ForEachAtom(LayoutNode& rLayoutNode, const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttributes); + + IteratorAttr & iterator() + { return maIter; } + void setRef(const OUString& rsRef) + { msRef = rsRef; } + const OUString& getRef() const + { return msRef; } + virtual void accept( LayoutAtomVisitor& ) override; + LayoutAtomPtr getRefAtom(); + +private: + IteratorAttr maIter; + OUString msRef; +}; + +typedef std::shared_ptr< ForEachAtom > ForEachAtomPtr; + +class ConditionAtom + : public LayoutAtom +{ +public: + explicit ConditionAtom(LayoutNode& rLayoutNode, bool isElse, const css::uno::Reference< css::xml::sax::XFastAttributeList >& xAttributes); + virtual void accept( LayoutAtomVisitor& ) override; + bool getDecision(const svx::diagram::Point* pPresPoint) const; + +private: + static bool compareResult(sal_Int32 nOperator, sal_Int32 nFirst, sal_Int32 nSecond); + sal_Int32 getNodeCount(const svx::diagram::Point* pPresPoint) const; + + bool mIsElse; + IteratorAttr maIter; + ConditionAttr maCond; +}; + +typedef std::shared_ptr< ConditionAtom > ConditionAtomPtr; + +/** "choose" statements. Atoms will be tested in order. */ +class ChooseAtom + : public LayoutAtom +{ +public: + ChooseAtom(LayoutNode& rLayoutNode) + : LayoutAtom(rLayoutNode) + {} + virtual void accept( LayoutAtomVisitor& ) override; +}; + +class LayoutNode + : public LayoutAtom +{ +public: + typedef std::map<sal_Int32, OUString> VarMap; + + LayoutNode(Diagram& rDgm) + : LayoutAtom(*this) + , mrDgm(rDgm) + , mnChildOrder(0) + { + } + Diagram& getDiagram() { return mrDgm; } + virtual void accept( LayoutAtomVisitor& ) override; + VarMap & variables() + { return mVariables; } + void setMoveWith( const OUString & sName ) + { msMoveWith = sName; } + void setStyleLabel( const OUString & sLabel ) + { msStyleLabel = sLabel; } + void setChildOrder( sal_Int32 nOrder ) + { mnChildOrder = nOrder; } + sal_Int32 getChildOrder() const { return mnChildOrder; } + void setExistingShape( const ShapePtr& pShape ) + { mpExistingShape = pShape; } + const ShapePtr& getExistingShape() const + { return mpExistingShape; } + const std::vector<ShapePtr> & getNodeShapes() const + { return mpNodeShapes; } + void addNodeShape(const ShapePtr& pShape) + { mpNodeShapes.push_back(pShape); } + + bool setupShape( const ShapePtr& rShape, + const svx::diagram::Point* pPresNode, + sal_Int32 nCurrIdx ) const; + + const LayoutNode* getParentLayoutNode() const; + +private: + Diagram& mrDgm; + VarMap mVariables; + OUString msMoveWith; + OUString msStyleLabel; + ShapePtr mpExistingShape; + std::vector<ShapePtr> mpNodeShapes; + sal_Int32 mnChildOrder; +}; + +typedef std::shared_ptr< LayoutNode > LayoutNodePtr; + +class ShapeAtom + : public LayoutAtom +{ +public: + ShapeAtom(LayoutNode& rLayoutNode, ShapePtr pShape) : LayoutAtom(rLayoutNode), mpShapeTemplate(std::move(pShape)) {} + virtual void accept( LayoutAtomVisitor& ) override; + const ShapePtr& getShapeTemplate() const + { return mpShapeTemplate; } + +private: + ShapePtr mpShapeTemplate; +}; + +typedef std::shared_ptr< ShapeAtom > ShapeAtomPtr; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/layoutatomvisitorbase.cxx b/oox/source/drawingml/diagram/layoutatomvisitorbase.cxx new file mode 100644 index 0000000000..b7f5a59630 --- /dev/null +++ b/oox/source/drawingml/diagram/layoutatomvisitorbase.cxx @@ -0,0 +1,176 @@ +/* -*- 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 "layoutatomvisitorbase.hxx" + +#include <sal/log.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace oox::drawingml { + +void LayoutAtomVisitorBase::defaultVisit(LayoutAtom const& rAtom) +{ + for (const auto& pAtom : rAtom.getChildren()) + pAtom->accept(*this); +} + +void LayoutAtomVisitorBase::visit(ChooseAtom& rAtom) +{ + for (const auto& pChild : rAtom.getChildren()) + { + const ConditionAtomPtr pCond = std::dynamic_pointer_cast<ConditionAtom>(pChild); + if (pCond && pCond->getDecision(mpCurrentNode)) + { + SAL_INFO("oox.drawingml", "Entering if node: " << pCond->getName()); + pCond->accept(*this); + break; + } + } +} + +void LayoutAtomVisitorBase::visit(ConditionAtom& rAtom) +{ + defaultVisit(rAtom); +} + +void LayoutAtomVisitorBase::visit(ForEachAtom& rAtom) +{ + if (!rAtom.getRef().isEmpty()) + { + if (LayoutAtomPtr pRefAtom = rAtom.getRefAtom()) + pRefAtom->accept(*this); + return; + } + + if (rAtom.iterator().mbHideLastTrans && !rAtom.iterator().maAxis.empty() && rAtom.iterator().maAxis[0] == XML_followSib) + { + // If last transition is hidden and the axis is the follow sibling, + // then the last atom should not be visited. + if (mnCurrIdx + mnCurrStep >= mnCurrCnt) + return; + } + + sal_Int32 nChildren = 1; + // Approximate the non-assistant type with the node type. + if (rAtom.iterator().mnPtType == XML_node || rAtom.iterator().mnPtType == XML_nonAsst) + { + // count child data nodes - check all child Atoms for "name" + // attribute that is contained in diagram's + // getPointsPresNameMap() + ShallowPresNameVisitor aVisitor(mrDgm, mpCurrentNode); + for (const auto& pAtom : rAtom.getChildren()) + pAtom->accept(aVisitor); + nChildren = aVisitor.getCount(); + } + + const sal_Int32 nCnt = std::min( + nChildren, + rAtom.iterator().mnCnt==-1 ? nChildren : rAtom.iterator().mnCnt); + + const sal_Int32 nOldIdx = mnCurrIdx; + const sal_Int32 nOldStep = mnCurrStep; + const sal_Int32 nOldCnt = mnCurrCnt; + const sal_Int32 nStep = rAtom.iterator().mnStep; + mnCurrStep = nStep; + mnCurrCnt = nCnt; + for( mnCurrIdx=0; mnCurrIdx<nCnt && nStep>0; mnCurrIdx+=nStep ) + { + // TODO there is likely some conditions + for (const auto& pAtom : rAtom.getChildren()) + pAtom->accept(*this); + } + + // and restore idx + mnCurrIdx = nOldIdx; + mnCurrStep = nOldStep; + mnCurrCnt = nOldCnt; +} + +void LayoutAtomVisitorBase::visit(LayoutNode& rAtom) +{ + // TODO: deduplicate code in descendants + + // stop processing if it's not a child of previous LayoutNode + + const DiagramData::PointsNameMap::const_iterator aDataNode + = mrDgm.getData()->getPointsPresNameMap().find(rAtom.getName()); + if (aDataNode == mrDgm.getData()->getPointsPresNameMap().end() + || mnCurrIdx >= static_cast<sal_Int32>(aDataNode->second.size())) + return; + + const svx::diagram::Point* pNewNode = aDataNode->second.at(mnCurrIdx); + if (!mpCurrentNode || !pNewNode) + return; + + bool bIsChild = false; + for (const auto& aConnection : mrDgm.getData()->getConnections()) + if (aConnection.msSourceId == mpCurrentNode->msModelId + && aConnection.msDestId == pNewNode->msModelId) + bIsChild = true; + + if (!bIsChild) + return; + + const svx::diagram::Point* pPreviousNode = mpCurrentNode; + mpCurrentNode = pNewNode; + + defaultVisit(rAtom); + + mpCurrentNode = pPreviousNode; +} + +void ShallowPresNameVisitor::visit(ConstraintAtom& /*rAtom*/) +{ + // stop processing +} + +void ShallowPresNameVisitor::visit(RuleAtom& /*rAtom*/) +{ + // stop processing +} + +void ShallowPresNameVisitor::visit(AlgAtom& /*rAtom*/) +{ + // stop processing +} + +void ShallowPresNameVisitor::visit(ForEachAtom& rAtom) +{ + defaultVisit(rAtom); +} + +void ShallowPresNameVisitor::visit(LayoutNode& rAtom) +{ + DiagramData::PointsNameMap::const_iterator aDataNode = + mrDgm.getData()->getPointsPresNameMap().find(rAtom.getName()); + if( aDataNode != mrDgm.getData()->getPointsPresNameMap().end() ) + mnCnt = std::max(mnCnt, + aDataNode->second.size()); +} + +void ShallowPresNameVisitor::visit(ShapeAtom& /*rAtom*/) +{ + // stop processing +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/layoutatomvisitorbase.hxx b/oox/source/drawingml/diagram/layoutatomvisitorbase.hxx new file mode 100644 index 0000000000..49c83f6745 --- /dev/null +++ b/oox/source/drawingml/diagram/layoutatomvisitorbase.hxx @@ -0,0 +1,98 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_LAYOUTATOMVISITORBASE_HXX +#define INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_LAYOUTATOMVISITORBASE_HXX + +#include "diagram.hxx" +#include "diagramlayoutatoms.hxx" + +namespace oox::drawingml { + +struct LayoutAtomVisitor +{ + virtual ~LayoutAtomVisitor() {} + virtual void visit(ConstraintAtom& rAtom) = 0; + virtual void visit(RuleAtom& rAtom) = 0; + virtual void visit(AlgAtom& rAtom) = 0; + virtual void visit(ForEachAtom& rAtom) = 0; + virtual void visit(ConditionAtom& rAtom) = 0; + virtual void visit(ChooseAtom& rAtom) = 0; + virtual void visit(LayoutNode& rAtom) = 0; + virtual void visit(ShapeAtom& rAtom) = 0; +}; + +// basic visitor implementation that follows if/else and for-each nodes +// and keeps track of current position in data tree +class LayoutAtomVisitorBase : public LayoutAtomVisitor +{ +public: + LayoutAtomVisitorBase(const Diagram& rDgm, const svx::diagram::Point* pRootPoint) : + mrDgm(rDgm), + mpCurrentNode(pRootPoint), + mnCurrIdx(0), + mnCurrStep(0), + mnCurrCnt(0), + meLookFor(LAYOUT_NODE) + {} + + void defaultVisit(LayoutAtom const& rAtom); + + using LayoutAtomVisitor::visit; + virtual void visit(ForEachAtom& rAtom) override; + virtual void visit(ConditionAtom& rAtom) override; + virtual void visit(ChooseAtom& rAtom) override; + virtual void visit(LayoutNode& rAtom) override; + +protected: + const Diagram& mrDgm; + const svx::diagram::Point* mpCurrentNode; + sal_Int32 mnCurrIdx; + sal_Int32 mnCurrStep; + sal_Int32 mnCurrCnt; + enum {LAYOUT_NODE, CONSTRAINT, ALGORITHM, RULE} meLookFor; +}; + +class ShallowPresNameVisitor : public LayoutAtomVisitorBase +{ +public: + explicit ShallowPresNameVisitor(const Diagram& rDgm, const svx::diagram::Point* pRootPoint) : + LayoutAtomVisitorBase(rDgm, pRootPoint), + mnCnt(0) + {} + + using LayoutAtomVisitorBase::visit; + virtual void visit(ConstraintAtom& rAtom) override; + virtual void visit(RuleAtom& rAtom) override; + virtual void visit(AlgAtom& rAtom) override; + virtual void visit(ForEachAtom& rAtom) override; + virtual void visit(LayoutNode& rAtom) override; + virtual void visit(ShapeAtom& rAtom) override; + + size_t getCount() const { return mnCnt; } + +private: + size_t mnCnt; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/layoutatomvisitors.cxx b/oox/source/drawingml/diagram/layoutatomvisitors.cxx new file mode 100644 index 0000000000..f34d93d98f --- /dev/null +++ b/oox/source/drawingml/diagram/layoutatomvisitors.cxx @@ -0,0 +1,269 @@ +/* -*- 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 "layoutatomvisitors.hxx" + +#include <drawingml/customshapeproperties.hxx> + +#include <sal/log.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; +using namespace ::oox::core; + +namespace oox::drawingml +{ +void ShapeCreationVisitor::visit(ConstraintAtom& /*rAtom*/) +{ + // stop processing +} + +void ShapeCreationVisitor::visit(RuleAtom& /*rAtom*/) +{ + // stop processing +} + +void ShapeCreationVisitor::visit(AlgAtom& rAtom) +{ + if (meLookFor == ALGORITHM) + { + mpParentShape->setAspectRatio(rAtom.getAspectRatio()); + mpParentShape->setVerticalShapesCount(rAtom.getVerticalShapesCount(mpParentShape)); + } +} + +void ShapeCreationVisitor::visit(LayoutNode& rAtom) +{ + if (meLookFor != LAYOUT_NODE) + return; + + // stop processing if it's not a child of previous LayoutNode + + const DiagramData::PointsNameMap::const_iterator aDataNode + = mrDgm.getData()->getPointsPresNameMap().find(rAtom.getName()); + if (aDataNode == mrDgm.getData()->getPointsPresNameMap().end() + || mnCurrIdx >= static_cast<sal_Int32>(aDataNode->second.size())) + return; + + const svx::diagram::Point* pNewNode = aDataNode->second.at(mnCurrIdx); + if (!mpCurrentNode || !pNewNode) + return; + + bool bIsChild = false; + for (const auto& aConnection : mrDgm.getData()->getConnections()) + if (aConnection.msSourceId == mpCurrentNode->msModelId + && aConnection.msDestId == pNewNode->msModelId) + bIsChild = true; + + if (!bIsChild) + return; + + ShapePtr pCurrParent(mpParentShape); + + if (rAtom.getExistingShape()) + { + // reuse existing shape + ShapePtr pShape = rAtom.getExistingShape(); + if (rAtom.setupShape(pShape, pNewNode, mnCurrIdx)) + { + pShape->setInternalName(rAtom.getName()); + rAtom.addNodeShape(pShape); + mrDgm.getLayout()->getPresPointShapeMap()[pNewNode] = pShape; + } + } + else + { + ShapeTemplateVisitor aTemplateVisitor(mrDgm, pNewNode); + aTemplateVisitor.defaultVisit(rAtom); + ShapePtr pShape = aTemplateVisitor.getShapeCopy(); + + if (pShape) + { + SAL_INFO("oox.drawingml", + "processing shape type " + << (pShape->getCustomShapeProperties()->getShapePresetType())); + + if (rAtom.setupShape(pShape, pNewNode, mnCurrIdx)) + { + pShape->setInternalName(rAtom.getName()); + pCurrParent->addChild(pShape); + pCurrParent = pShape; + rAtom.addNodeShape(pShape); + mrDgm.getLayout()->getPresPointShapeMap()[pNewNode] = pShape; + } + } + else + { + SAL_WARN("oox.drawingml", + "ShapeCreationVisitor::visit: no shape set while processing layoutnode named " + << rAtom.getName()); + } + } + + const svx::diagram::Point* pPreviousNode = mpCurrentNode; + mpCurrentNode = pNewNode; + + // set new parent for children + ShapePtr pPreviousParent(mpParentShape); + mpParentShape = pCurrParent; + + // process children + meLookFor = LAYOUT_NODE; + defaultVisit(rAtom); + + meLookFor = ALGORITHM; + defaultVisit(rAtom); + meLookFor = LAYOUT_NODE; + + // restore parent + mpParentShape = pPreviousParent; + mpCurrentNode = pPreviousNode; +} + +void ShapeCreationVisitor::visit(ShapeAtom& /*rAtom*/) +{ + // stop processing +} + +void ShapeTemplateVisitor::visit(ConstraintAtom& /*rAtom*/) +{ + // stop processing +} + +void ShapeTemplateVisitor::visit(RuleAtom& /*rAtom*/) +{ + // stop processing +} + +void ShapeTemplateVisitor::visit(AlgAtom& /*rAtom*/) +{ + // stop processing +} + +void ShapeTemplateVisitor::visit(ForEachAtom& /*rAtom*/) +{ + // stop processing +} + +void ShapeTemplateVisitor::visit(LayoutNode& /*rAtom*/) +{ + // stop processing - only traverse Condition/Choose atoms +} + +void ShapeTemplateVisitor::visit(ShapeAtom& rAtom) +{ + if (mpShape) + { + SAL_WARN("oox.drawingml", "multiple shapes encountered inside LayoutNode"); + return; + } + + const ShapePtr& pCurrShape(rAtom.getShapeTemplate()); + + // TODO(F3): cloned shape shares all properties by reference, + // don't change them! + mpShape = std::make_shared<Shape>(pCurrShape); + // Fill properties have to be changed as sometimes only the presentation node contains the blip + // fill, unshare those. + mpShape->cloneFillProperties(); + + // add/set ModelID from current node to allow later association + if (mpCurrentNode) + mpShape->setDiagramDataModelID(mpCurrentNode->msModelId); +} + +void ShapeLayoutingVisitor::visit(ConstraintAtom& rAtom) +{ + if (meLookFor == CONSTRAINT) + rAtom.parseConstraint(maConstraints, /*bRequireForName=*/true); +} + +void ShapeLayoutingVisitor::visit(RuleAtom& rAtom) +{ + if (meLookFor == RULE) + rAtom.parseRule(maRules); +} + +void ShapeLayoutingVisitor::visit(AlgAtom& rAtom) +{ + if (meLookFor == ALGORITHM) + { + const PresPointShapeMap aMap + = rAtom.getLayoutNode().getDiagram().getLayout()->getPresPointShapeMap(); + auto pShape = aMap.find(mpCurrentNode); + if (pShape != aMap.end()) + rAtom.layoutShape(pShape->second, maConstraints, maRules); + } +} + +void ShapeLayoutingVisitor::visit(LayoutNode& rAtom) +{ + if (meLookFor != LAYOUT_NODE) + return; + + // stop processing if it's not a child of previous LayoutNode + + const DiagramData::PointsNameMap::const_iterator aDataNode + = mrDgm.getData()->getPointsPresNameMap().find(rAtom.getName()); + if (aDataNode == mrDgm.getData()->getPointsPresNameMap().end() + || mnCurrIdx >= static_cast<sal_Int32>(aDataNode->second.size())) + return; + + const svx::diagram::Point* pNewNode = aDataNode->second.at(mnCurrIdx); + if (!mpCurrentNode || !pNewNode) + return; + + bool bIsChild = false; + for (const auto& aConnection : mrDgm.getData()->getConnections()) + if (aConnection.msSourceId == mpCurrentNode->msModelId + && aConnection.msDestId == pNewNode->msModelId) + bIsChild = true; + + if (!bIsChild) + return; + + size_t nParentConstraintsNumber = maConstraints.size(); + + const svx::diagram::Point* pPreviousNode = mpCurrentNode; + mpCurrentNode = pNewNode; + + // process alg atoms first, nested layout nodes afterwards + meLookFor = CONSTRAINT; + defaultVisit(rAtom); + meLookFor = RULE; + defaultVisit(rAtom); + meLookFor = ALGORITHM; + defaultVisit(rAtom); + meLookFor = LAYOUT_NODE; + defaultVisit(rAtom); + + mpCurrentNode = pPreviousNode; + + // delete added constraints, keep parent constraints + maConstraints.erase(maConstraints.begin() + nParentConstraintsNumber, maConstraints.end()); +} + +void ShapeLayoutingVisitor::visit(ShapeAtom& /*rAtom*/) +{ + // stop processing +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/layoutatomvisitors.hxx b/oox/source/drawingml/diagram/layoutatomvisitors.hxx new file mode 100644 index 0000000000..7c10fc436d --- /dev/null +++ b/oox/source/drawingml/diagram/layoutatomvisitors.hxx @@ -0,0 +1,97 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_LAYOUTATOMVISITORS_HXX +#define INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_LAYOUTATOMVISITORS_HXX + +#include <oox/drawingml/drawingmltypes.hxx> +#include <utility> +#include "diagram.hxx" +#include "diagramlayoutatoms.hxx" +#include "layoutatomvisitorbase.hxx" + +namespace oox::drawingml { + +class ShapeCreationVisitor : public LayoutAtomVisitorBase +{ +public: + ShapeCreationVisitor(const Diagram& rDgm, + const svx::diagram::Point* pRootPoint, + ShapePtr xParentShape) : + LayoutAtomVisitorBase(rDgm, pRootPoint), + mpParentShape(std::move(xParentShape)) + {} + + using LayoutAtomVisitorBase::visit; + virtual void visit(ConstraintAtom& rAtom) override; + virtual void visit(RuleAtom& rAtom) override; + virtual void visit(AlgAtom& rAtom) override; + virtual void visit(LayoutNode& rAtom) override; + virtual void visit(ShapeAtom& rAtom) override; + +private: + ShapePtr mpParentShape; +}; + +class ShapeTemplateVisitor : public LayoutAtomVisitorBase +{ +public: + ShapeTemplateVisitor(const Diagram& rDgm, const svx::diagram::Point* pRootPoint) + : LayoutAtomVisitorBase(rDgm, pRootPoint) + {} + + using LayoutAtomVisitorBase::visit; + virtual void visit(ConstraintAtom& rAtom) override; + virtual void visit(RuleAtom& rAtom) override; + virtual void visit(AlgAtom& rAtom) override; + virtual void visit(ForEachAtom& rAtom) override; + virtual void visit(LayoutNode& rAtom) override; + virtual void visit(ShapeAtom& rAtom) override; + + ShapePtr const & getShapeCopy() const + { return mpShape; } + +private: + ShapePtr mpShape; +}; + +class ShapeLayoutingVisitor : public LayoutAtomVisitorBase +{ +public: + ShapeLayoutingVisitor(const Diagram& rDgm, const svx::diagram::Point* pRootPoint) : + LayoutAtomVisitorBase(rDgm, pRootPoint) + {} + + using LayoutAtomVisitorBase::visit; + virtual void visit(ConstraintAtom& rAtom) override; + virtual void visit(RuleAtom& rAtom) override; + virtual void visit(AlgAtom& rAtom) override; + virtual void visit(LayoutNode& rAtom) override; + virtual void visit(ShapeAtom& rAtom) override; + +private: + std::vector<Constraint> maConstraints; + std::vector<Rule> maRules; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/layoutnodecontext.cxx b/oox/source/drawingml/diagram/layoutnodecontext.cxx new file mode 100644 index 0000000000..45756e20bd --- /dev/null +++ b/oox/source/drawingml/diagram/layoutnodecontext.cxx @@ -0,0 +1,310 @@ +/* -*- 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 "layoutnodecontext.hxx" + +#include <oox/helper/attributelist.hxx> +#include <oox/drawingml/shapecontext.hxx> +#include <drawingml/customshapeproperties.hxx> +#include "constraintlistcontext.hxx" +#include "rulelistcontext.hxx" +#include <oox/token/namespaces.hxx> +#include <oox/token/tokens.hxx> +#include <sal/log.hxx> +#include <utility> + +using namespace ::oox::core; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::xml::sax; + +namespace oox::drawingml { + +namespace { + +class IfContext + : public LayoutNodeContext +{ +public: + IfContext( ContextHandler2Helper const & rParent, + const AttributeList& rAttribs, + const ConditionAtomPtr& pAtom ) + : LayoutNodeContext( rParent, rAttribs, pAtom ) + {} +}; + +class AlgorithmContext + : public ContextHandler2 +{ +public: + AlgorithmContext( ContextHandler2Helper const & rParent, const AttributeList& rAttribs, const AlgAtomPtr & pNode ) + : ContextHandler2( rParent ) + , mnRevision( 0 ) + , mpNode( pNode ) + { + mnRevision = rAttribs.getInteger( XML_rev, 0 ); + pNode->setType(rAttribs.getToken(XML_type, 0)); + } + + virtual ContextHandlerRef + onCreateContext( ::sal_Int32 aElement, + const AttributeList& rAttribs ) override + { + switch( aElement ) + { + case DGM_TOKEN( param ): + { + sal_Int32 nType = rAttribs.getToken(XML_type, 0); + switch (nType) + { + case XML_ar: + mpNode->setAspectRatio(rAttribs.getDouble(XML_val, 0)); + break; + default: + const sal_Int32 nValTok = rAttribs.getToken(XML_val, 0); + mpNode->addParam(nType, nValTok > 0 ? nValTok + : rAttribs.getInteger(XML_val, 0)); + break; + } + break; + } + default: + break; + } + + return this; + } + +private: + sal_Int32 mnRevision; + AlgAtomPtr mpNode; +}; + +class ChooseContext + : public ContextHandler2 +{ +public: + ChooseContext( ContextHandler2Helper const & rParent, const AttributeList& rAttribs, LayoutAtomPtr pNode ) + : ContextHandler2( rParent ) + , mpNode(std::move( pNode )) + { + msName = rAttribs.getStringDefaulted( XML_name ); + } + + virtual ContextHandlerRef + onCreateContext( ::sal_Int32 aElement, + const AttributeList& rAttribs ) override + { + switch( aElement ) + { + case DGM_TOKEN( if ): + { + // CT_When + ConditionAtomPtr pNode = std::make_shared<ConditionAtom>(mpNode->getLayoutNode(), false, rAttribs.getFastAttributeList()); + LayoutAtom::connect(mpNode, pNode); + return new IfContext( *this, rAttribs, pNode ); + } + case DGM_TOKEN( else ): + { + // CT_Otherwise + ConditionAtomPtr pNode = std::make_shared<ConditionAtom>(mpNode->getLayoutNode(), true, rAttribs.getFastAttributeList()); + LayoutAtom::connect(mpNode, pNode); + return new IfContext( *this, rAttribs, pNode ); + } + default: + break; + } + + return this; + } +private: + OUString msName; + LayoutAtomPtr mpNode; +}; + +class ForEachContext + : public LayoutNodeContext +{ +public: + ForEachContext( ContextHandler2Helper const & rParent, const AttributeList& rAttribs, const ForEachAtomPtr& pAtom ) + : LayoutNodeContext( rParent, rAttribs, pAtom ) + { + pAtom->setRef(rAttribs.getStringDefaulted(XML_ref)); + pAtom->iterator().loadFromXAttr( rAttribs.getFastAttributeList() ); + + LayoutAtomMap& rLayoutAtomMap = pAtom->getLayoutNode().getDiagram().getLayout()->getLayoutAtomMap(); + rLayoutAtomMap[pAtom->getName()] = pAtom; + } +}; + +// CT_LayoutVariablePropertySet +class LayoutVariablePropertySetContext + : public ContextHandler2 +{ +public: + LayoutVariablePropertySetContext( ContextHandler2Helper const & rParent, LayoutNode::VarMap & aVar ) + : ContextHandler2( rParent ) + , mVariables( aVar ) + { + } + + virtual ContextHandlerRef onCreateContext( ::sal_Int32 aElement, const AttributeList& rAttribs ) override + { + mVariables[ getBaseToken(aElement) ] = rAttribs.getStringDefaulted( XML_val ); + return this; + } +private: + LayoutNode::VarMap & mVariables; +}; + +} + +// CT_LayoutNode +LayoutNodeContext::LayoutNodeContext( ContextHandler2Helper const & rParent, + const AttributeList& rAttribs, + const LayoutAtomPtr& pAtom ) + : ContextHandler2( rParent ) + , mpNode( pAtom ) +{ + assert( pAtom && "Node must NOT be NULL" ); + mpNode->setName( rAttribs.getStringDefaulted( XML_name ) ); +} + +LayoutNodeContext::~LayoutNodeContext() +{ +} + +ContextHandlerRef +LayoutNodeContext::onCreateContext( ::sal_Int32 aElement, + const AttributeList& rAttribs ) +{ + switch( aElement ) + { + case DGM_TOKEN( layoutNode ): + { + LayoutNodePtr pNode = std::make_shared<LayoutNode>(mpNode->getLayoutNode().getDiagram()); + LayoutAtom::connect(mpNode, pNode); + + if (rAttribs.hasAttribute(XML_chOrder)) + { + pNode->setChildOrder(rAttribs.getToken(XML_chOrder, XML_b)); + } + else + { + for (LayoutAtomPtr pAtom = mpNode; pAtom; pAtom = pAtom->getParent()) + { + auto pLayoutNode = dynamic_cast<LayoutNode*>(pAtom.get()); + if (pLayoutNode) + { + pNode->setChildOrder(pLayoutNode->getChildOrder()); + break; + } + } + } + + pNode->setMoveWith( rAttribs.getStringDefaulted( XML_moveWith ) ); + pNode->setStyleLabel( rAttribs.getStringDefaulted( XML_styleLbl ) ); + return new LayoutNodeContext( *this, rAttribs, pNode ); + } + case DGM_TOKEN( shape ): + { + ShapePtr pShape; + + if( rAttribs.hasAttribute( XML_type ) ) + { + pShape = std::make_shared<Shape>("com.sun.star.drawing.CustomShape"); + if (!rAttribs.getBool(XML_hideGeom, false)) + { + const sal_Int32 nType(rAttribs.getToken( XML_type, XML_obj )); + pShape->setSubType( nType ); + pShape->getCustomShapeProperties()->setShapePresetType( nType ); + } + } + else + { + pShape = std::make_shared<Shape>("com.sun.star.drawing.GroupShape"); + } + + pShape->setDiagramRotation(rAttribs.getInteger(XML_rot, 0) * PER_DEGREE); + + pShape->setZOrderOff(rAttribs.getInteger(XML_zOrderOff, 0)); + + ShapeAtomPtr pAtom = std::make_shared<ShapeAtom>(mpNode->getLayoutNode(), pShape); + LayoutAtom::connect(mpNode, pAtom); + return new ShapeContext( *this, ShapePtr(), pShape ); + } + case DGM_TOKEN( extLst ): + return nullptr; + case DGM_TOKEN( alg ): + { + // CT_Algorithm + AlgAtomPtr pAtom = std::make_shared<AlgAtom>(mpNode->getLayoutNode()); + LayoutAtom::connect(mpNode, pAtom); + return new AlgorithmContext( *this, rAttribs, pAtom ); + } + case DGM_TOKEN( choose ): + { + // CT_Choose + LayoutAtomPtr pAtom = std::make_shared<ChooseAtom>(mpNode->getLayoutNode()); + LayoutAtom::connect(mpNode, pAtom); + return new ChooseContext( *this, rAttribs, pAtom ); + } + case DGM_TOKEN( forEach ): + { + // CT_ForEach + ForEachAtomPtr pAtom = std::make_shared<ForEachAtom>(mpNode->getLayoutNode(), rAttribs.getFastAttributeList()); + LayoutAtom::connect(mpNode, pAtom); + return new ForEachContext( *this, rAttribs, pAtom ); + } + case DGM_TOKEN( constrLst ): + // CT_Constraints + return new ConstraintListContext( *this, mpNode ); + case DGM_TOKEN( presOf ): + { + // CT_PresentationOf + // TODO + IteratorAttr aIterator; + aIterator.loadFromXAttr(rAttribs.getFastAttributeList()); + break; + } + case DGM_TOKEN( ruleLst ): + // CT_Rules + return new RuleListContext( *this, mpNode ); + case DGM_TOKEN( varLst ): + { + LayoutNodePtr pNode(std::dynamic_pointer_cast<LayoutNode>(mpNode)); + if( pNode ) + { + return new LayoutVariablePropertySetContext( *this, pNode->variables() ); + } + else + { + SAL_WARN("oox", "OOX: encountered a varLst in a non layoutNode context" ); + } + break; + } + default: + break; + } + + return this; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/layoutnodecontext.hxx b/oox/source/drawingml/diagram/layoutnodecontext.hxx new file mode 100644 index 0000000000..3499f57045 --- /dev/null +++ b/oox/source/drawingml/diagram/layoutnodecontext.hxx @@ -0,0 +1,43 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_LAYOUTNODECONTEXT_HXX +#define INCLUDED_OOX_SOURCE_DRAWINGML_DIAGRAM_LAYOUTNODECONTEXT_HXX + +#include <oox/core/contexthandler2.hxx> +#include "diagramlayoutatoms.hxx" + +namespace oox::drawingml { + +class LayoutNodeContext : public ::oox::core::ContextHandler2 +{ +public: + LayoutNodeContext( ::oox::core::ContextHandler2Helper const & rParent, const ::oox::AttributeList& rAttributes, const LayoutAtomPtr &pNode ); + virtual ~LayoutNodeContext() override; + + virtual ::oox::core::ContextHandlerRef onCreateContext( ::sal_Int32 Element, const ::oox::AttributeList& rAttribs ) override; +private: + LayoutAtomPtr mpNode; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/rulelistcontext.cxx b/oox/source/drawingml/diagram/rulelistcontext.cxx new file mode 100644 index 0000000000..2a1c450d01 --- /dev/null +++ b/oox/source/drawingml/diagram/rulelistcontext.cxx @@ -0,0 +1,58 @@ +/* -*- 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 "rulelistcontext.hxx" +#include <oox/helper/attributelist.hxx> +#include <oox/token/namespaces.hxx> +#include <oox/token/tokens.hxx> + +namespace oox::drawingml +{ +RuleListContext::RuleListContext(ContextHandler2Helper const& rParent, const LayoutAtomPtr& pNode) + : ContextHandler2(rParent) + , mpNode(pNode) +{ + assert(pNode); +} + +RuleListContext::~RuleListContext() = default; + +core::ContextHandlerRef RuleListContext::onCreateContext(sal_Int32 nElement, + const AttributeList& rAttribs) +{ + switch (nElement) + { + case DGM_TOKEN(rule): + { + auto pNode = std::make_shared<RuleAtom>(mpNode->getLayoutNode()); + LayoutAtom::connect(mpNode, pNode); + + Rule& rRule = pNode->getRule(); + rRule.msForName = rAttribs.getStringDefaulted(XML_forName); + break; + } + default: + break; + } + + return this; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/oox/source/drawingml/diagram/rulelistcontext.hxx b/oox/source/drawingml/diagram/rulelistcontext.hxx new file mode 100644 index 0000000000..83a86c49e0 --- /dev/null +++ b/oox/source/drawingml/diagram/rulelistcontext.hxx @@ -0,0 +1,42 @@ +/* -*- 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 . + */ + +#pragma once + +#include <oox/core/contexthandler2.hxx> +#include "diagramlayoutatoms.hxx" + +namespace oox::drawingml +{ +/// Handles one <dgm:ruleLst> element. +class RuleListContext : public oox::core::ContextHandler2 +{ +public: + RuleListContext(ContextHandler2Helper const& rParent, const LayoutAtomPtr& pNode); + ~RuleListContext() override; + + oox::core::ContextHandlerRef onCreateContext(sal_Int32 nElement, + const AttributeList& rAttribs) override; + +private: + LayoutAtomPtr mpNode; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |