1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <oox/drawingml/diagram/diagram.hxx>
21 #include "diagram.hxx"
22 #include <com/sun/star/awt/Point.hpp>
23 #include <com/sun/star/awt/Size.hpp>
24 #include <com/sun/star/beans/XPropertySet.hpp>
25 #include <com/sun/star/drawing/XShape.hpp>
26 #include <com/sun/star/drawing/XShapes.hpp>
27 #include <com/sun/star/xml/dom/XDocument.hpp>
28 #include <com/sun/star/xml/sax/XFastSAXSerializable.hpp>
29 #include <sal/log.hxx>
30 #include <editeng/unoprnms.hxx>
31 #include <drawingml/fillproperties.hxx>
32 #include <drawingml/customshapeproperties.hxx>
33 #include <o3tl/unit_conversion.hxx>
34 #include <oox/token/namespaces.hxx>
35 #include <basegfx/matrix/b2dhommatrix.hxx>
36 #include <svx/svdpage.hxx>
37 #include <oox/ppt/pptimport.hxx>
38 #include <comphelper/xmltools.hxx>
40 #include "diagramlayoutatoms.hxx"
41 #include "layoutatomvisitors.hxx"
42 #include "diagramfragmenthandler.hxx"
44 using namespace ::com::sun::star
;
46 namespace oox::drawingml
{
48 static void sortChildrenByZOrder(const ShapePtr
& pShape
)
50 std::vector
<ShapePtr
>& rChildren
= pShape
->getChildren();
52 // Offset the children from their default z-order stacking, if necessary.
53 for (size_t i
= 0; i
< rChildren
.size(); ++i
)
54 rChildren
[i
]->setZOrder(i
);
56 for (size_t i
= 0; i
< rChildren
.size(); ++i
)
58 const ShapePtr
& pChild
= rChildren
[i
];
59 sal_Int32 nZOrderOff
= pChild
->getZOrderOff();
63 // Increase my ZOrder by nZOrderOff.
64 pChild
->setZOrder(pChild
->getZOrder() + nZOrderOff
);
65 pChild
->setZOrderOff(0);
67 for (sal_Int32 j
= 0; j
< nZOrderOff
; ++j
)
69 size_t nIndex
= i
+ j
+ 1;
70 if (nIndex
>= rChildren
.size())
73 // Decrease the ZOrder of the next nZOrderOff elements by one.
74 const ShapePtr
& pNext
= rChildren
[nIndex
];
75 pNext
->setZOrder(pNext
->getZOrder() - 1);
79 // Now that the ZOrders are adjusted, sort the children.
80 std::sort(rChildren
.begin(), rChildren
.end(),
81 [](const ShapePtr
& a
, const ShapePtr
& b
) { return a
->getZOrder() < b
->getZOrder(); });
83 // Apply also for children.
84 for (const auto& rChild
: rChildren
)
85 sortChildrenByZOrder(rChild
);
88 /// Removes empty group shapes, now that their spacing influenced the layout.
89 static void removeUnneededGroupShapes(const ShapePtr
& pShape
)
91 std::vector
<ShapePtr
>& rChildren
= pShape
->getChildren();
93 std::erase_if(rChildren
,
94 [](const ShapePtr
& aChild
) {
95 return aChild
->getServiceName()
96 == "com.sun.star.drawing.GroupShape"
97 && aChild
->getChildren().empty();
100 for (const auto& pChild
: rChildren
)
102 removeUnneededGroupShapes(pChild
);
107 void Diagram::addTo( const ShapePtr
& pParentShape
, bool bCreate
)
109 if (pParentShape
->getSize().Width
== 0 || pParentShape
->getSize().Height
== 0)
110 SAL_WARN("oox.drawingml", "Diagram cannot be correctly laid out. Size: "
111 << pParentShape
->getSize().Width
<< "x" << pParentShape
->getSize().Height
);
113 pParentShape
->setChildSize(pParentShape
->getSize());
115 const svx::diagram::Point
* pRootPoint
= mpData
->getRootPoint();
116 if (bCreate
&& mpLayout
->getNode() && pRootPoint
)
118 // create Shape hierarchy
119 ShapeCreationVisitor
aCreationVisitor(*this, pRootPoint
, pParentShape
);
120 mpLayout
->getNode()->setExistingShape(pParentShape
);
121 mpLayout
->getNode()->accept(aCreationVisitor
);
123 // layout shapes - now all shapes are created
124 ShapeLayoutingVisitor
aLayoutingVisitor(*this, pRootPoint
);
125 mpLayout
->getNode()->accept(aLayoutingVisitor
);
127 sortChildrenByZOrder(pParentShape
);
128 removeUnneededGroupShapes(pParentShape
);
131 ShapePtr pBackground
= std::make_shared
<Shape
>("com.sun.star.drawing.CustomShape");
132 pBackground
->setSubType(XML_rect
);
133 pBackground
->getCustomShapeProperties()->setShapePresetType(XML_rect
);
134 pBackground
->setSize(pParentShape
->getSize());
135 pBackground
->getFillProperties() = *mpData
->getBackgroundShapeFillProperties();
136 pBackground
->setLocked(true);
138 // create and set ModelID for BackgroundShape to allow later association
139 getData()->setBackgroundShapeModelID(OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8
));
140 pBackground
->setDiagramDataModelID(getData()->getBackgroundShapeModelID());
142 auto& aChildren
= pParentShape
->getChildren();
143 aChildren
.insert(aChildren
.begin(), pBackground
);
147 : maDiagramFontHeights()
151 uno::Sequence
<beans::PropertyValue
> Diagram::getDomsAsPropertyValues() const
153 sal_Int32 length
= maMainDomMap
.size();
155 if (maDataRelsMap
.hasElements())
158 uno::Sequence
<beans::PropertyValue
> aValue(length
);
159 beans::PropertyValue
* pValue
= aValue
.getArray();
160 for (auto const& mainDom
: maMainDomMap
)
162 pValue
->Name
= mainDom
.first
;
163 pValue
->Value
<<= mainDom
.second
;
167 if (maDataRelsMap
.hasElements())
169 pValue
->Name
= "OOXDiagramDataRels";
170 pValue
->Value
<<= maDataRelsMap
;
178 = std::map
<std::shared_ptr
<drawingml::Shape
>, css::uno::Reference
<css::drawing::XShape
>>;
180 void Diagram::syncDiagramFontHeights()
182 // Each name represents a group of shapes, for which the font height should have the same
184 for (const auto& rNameAndPairs
: maDiagramFontHeights
)
186 // Find out the minimum scale within this group.
187 const ShapePairs
& rShapePairs
= rNameAndPairs
.second
;
188 double fMinFontScale
= 100.0;
189 double fMinSpacingScale
= 100.0;
190 for (const auto& rShapePair
: rShapePairs
)
192 uno::Reference
<beans::XPropertySet
> xPropertySet(rShapePair
.second
, uno::UNO_QUERY
);
193 if (xPropertySet
.is())
195 double fFontScale
= 0.0;
196 double fSpacingScale
= 0.0;
197 xPropertySet
->getPropertyValue(u
"TextFitToSizeFontScale"_ustr
) >>= fFontScale
;
198 xPropertySet
->getPropertyValue(u
"TextFitToSizeSpacingScale"_ustr
) >>= fSpacingScale
;
200 if (fFontScale
> 0 && fSpacingScale
> 0
201 && (fFontScale
< fMinFontScale
|| (fFontScale
== fMinFontScale
&& fSpacingScale
< fMinSpacingScale
)))
203 fMinFontScale
= fFontScale
;
204 fMinSpacingScale
= fSpacingScale
;
209 // Set that minimum scale for all members of the group.
210 if (fMinFontScale
< 100.0 || fMinSpacingScale
< 100.0)
212 for (const auto& rShapePair
: rShapePairs
)
214 uno::Reference
<beans::XPropertySet
> xPropertySet(rShapePair
.second
, uno::UNO_QUERY
);
215 if (xPropertySet
.is())
217 xPropertySet
->setPropertyValue(u
"TextFitToSizeFontScale"_ustr
, uno::Any(fMinFontScale
));
218 xPropertySet
->setPropertyValue(u
"TextFitToSizeSpacingScale"_ustr
, uno::Any(fMinSpacingScale
));
224 // no longer needed after processing
225 maDiagramFontHeights
.clear();
228 static uno::Reference
<xml::dom::XDocument
> loadFragment(
229 core::XmlFilterBase
& rFilter
,
230 const OUString
& rFragmentPath
)
232 // load diagramming fragments into DOM representation, that later
233 // gets serialized back to SAX events and parsed
234 return rFilter
.importFragment( rFragmentPath
);
237 static uno::Reference
<xml::dom::XDocument
> loadFragment(
238 core::XmlFilterBase
& rFilter
,
239 const rtl::Reference
< core::FragmentHandler
>& rxHandler
)
241 return loadFragment( rFilter
, rxHandler
->getFragmentPath() );
244 static void importFragment( core::XmlFilterBase
& rFilter
,
245 const uno::Reference
<xml::dom::XDocument
>& rXDom
,
246 const OUString
& rDocName
,
247 const DiagramPtr
& pDiagram
,
248 const rtl::Reference
< core::FragmentHandler
>& rxHandler
)
250 DiagramDomMap
& rMainDomMap
= pDiagram
->getDomMap();
251 rMainDomMap
[rDocName
] = rXDom
;
253 uno::Reference
<xml::sax::XFastSAXSerializable
> xSerializer(
254 rXDom
, uno::UNO_QUERY_THROW
);
256 // now serialize DOM tree into internal data structures
257 rFilter
.importFragment( rxHandler
, xSerializer
);
263 * A fragment handler that just counts the number of <dsp:sp> elements in a
266 class DiagramShapeCounter
: public oox::core::FragmentHandler2
269 DiagramShapeCounter(oox::core::XmlFilterBase
& rFilter
, const OUString
& rFragmentPath
,
270 sal_Int32
& nCounter
);
271 oox::core::ContextHandlerRef
onCreateContext(sal_Int32 nElement
,
272 const AttributeList
& rAttribs
) override
;
275 sal_Int32
& m_nCounter
;
278 DiagramShapeCounter::DiagramShapeCounter(oox::core::XmlFilterBase
& rFilter
,
279 const OUString
& rFragmentPath
, sal_Int32
& nCounter
)
280 : FragmentHandler2(rFilter
, rFragmentPath
)
281 , m_nCounter(nCounter
)
285 oox::core::ContextHandlerRef
DiagramShapeCounter::onCreateContext(sal_Int32 nElement
,
286 const AttributeList
& /*rAttribs*/)
290 case DSP_TOKEN(drawing
):
291 case DSP_TOKEN(spTree
):
304 void loadDiagram( ShapePtr
const & pShape
,
305 core::XmlFilterBase
& rFilter
,
306 const OUString
& rDataModelPath
,
307 const OUString
& rLayoutPath
,
308 const OUString
& rQStylePath
,
309 const OUString
& rColorStylePath
,
310 const oox::core::Relations
& rRelations
)
312 DiagramPtr pDiagram
= std::make_shared
<Diagram
>();
314 OoxDiagramDataPtr pData
= std::make_shared
<DiagramData
>();
315 pDiagram
->setData( pData
);
317 DiagramLayoutPtr pLayout
= std::make_shared
<DiagramLayout
>(*pDiagram
);
318 pDiagram
->setLayout( pLayout
);
322 // set DiagramFontHeights at filter
323 rFilter
.setDiagramFontHeights(&pDiagram
->getDiagramFontHeights());
326 if( !rDataModelPath
.isEmpty() )
328 rtl::Reference
< core::FragmentHandler
> xRefDataModel(
329 new DiagramDataFragmentHandler( rFilter
, rDataModelPath
, pData
));
331 importFragment(rFilter
,
332 loadFragment(rFilter
,xRefDataModel
),
337 pDiagram
->getDataRelsMap() = pShape
->resolveRelationshipsOfTypeFromOfficeDoc( rFilter
,
338 xRefDataModel
->getFragmentPath(), u
"image" );
340 // Pass the info to pShape
341 for (auto const& extDrawing
: pData
->getExtDrawings())
343 OUString aFragmentPath
= rRelations
.getFragmentPathFromRelId(extDrawing
);
344 // Ignore RelIds which don't resolve to a fragment path.
345 if (aFragmentPath
.isEmpty())
348 sal_Int32 nCounter
= 0;
349 rtl::Reference
<core::FragmentHandler
> xCounter(
350 new DiagramShapeCounter(rFilter
, aFragmentPath
, nCounter
));
351 rFilter
.importFragment(xCounter
);
352 // Ignore ext drawings which don't actually have any shapes.
356 pShape
->addExtDrawingRelId(extDrawing
);
360 // Layout: always import to allow editing in the future. It's needed for
361 // AdvancedDiagramHelper::reLayout to re-create the oox::Shape(s) for the
362 // model. Without importing these the diagram model will be not complete.
363 // NOTE: This also adds the DomMaps to rMainDomMap, so the lines
364 // DiagramDomMap& rMainDomMap = pDiagram->getDomMap();
365 // rMainDomMap[u"OOXLayout"_ustr] = loadFragment(rFilter,rLayoutPath);
366 // rMainDomMap[u"OOXStyle"_ustr] = loadFragment(rFilter,rQStylePath);
367 // which were used before if !pShape->getExtDrawings().empty() are not
369 if (!rLayoutPath
.isEmpty())
371 rtl::Reference
< core::FragmentHandler
> xRefLayout(
372 new DiagramLayoutFragmentHandler( rFilter
, rLayoutPath
,
373 std::move(pLayout
) ));
375 importFragment(rFilter
,
376 loadFragment(rFilter
,xRefLayout
),
382 // Style: same as for Layout (above)
383 if( !rQStylePath
.isEmpty() )
385 rtl::Reference
< core::FragmentHandler
> xRefQStyle(
386 new DiagramQStylesFragmentHandler( rFilter
, rQStylePath
, pDiagram
->getStyles() ));
388 importFragment(rFilter
,
389 loadFragment(rFilter
,xRefQStyle
),
396 if( !rColorStylePath
.isEmpty() )
398 rtl::Reference
< core::FragmentHandler
> xRefColorStyle(
399 new ColorFragmentHandler( rFilter
, rColorStylePath
, pDiagram
->getColors() ));
401 importFragment(rFilter
,
402 loadFragment(rFilter
,xRefColorStyle
),
408 if( !pData
->getExtDrawings().empty() )
410 const DiagramColorMap::const_iterator aColor
= pDiagram
->getColors().find(u
"node0"_ustr
);
411 if( aColor
!= pDiagram
->getColors().end() && !aColor
->second
.maTextFillColors
.empty())
413 // TODO(F1): well, actually, there might be *several* color
414 // definitions in it, after all it's called list.
415 pShape
->setFontRefColorForNodes(DiagramColor::getColorByIndex(aColor
->second
.maTextFillColors
, -1));
419 // collect data, init maps
420 // for Diagram import, do - for now - NOT clear all oox::drawingml::Shape
421 pData
->buildDiagramDataModel(false);
423 // diagram loaded. now lump together & attach to shape
424 // create own geometry if extLst is not present (no geometric
425 // representation is available in file). This will - if false -
426 // just create the BackgroundShape.
427 // NOTE: Need to use pShape->getExtDrawings() here, this is the
428 // already *filtered* version, see usage of DiagramShapeCounter
429 // above. Moving to local bool, there might more conditions show
431 const bool bCreate(pShape
->getExtDrawings().empty());
432 pDiagram
->addTo(pShape
, bCreate
);
433 pShape
->setDiagramDoms(pDiagram
->getDomsAsPropertyValues());
435 // Get the oox::Theme definition and - if available - move/secure the
436 // original ImportData directly to the Diagram ModelData
437 std::shared_ptr
<::oox::drawingml::Theme
> aTheme(rFilter
.getCurrentThemePtr());
439 pData
->setThemeDocument(aTheme
->getFragment()); //getTempFile());
441 // Prepare support for the advanced DiagramHelper using Diagram & Theme data
442 pShape
->prepareDiagramHelper(pDiagram
, rFilter
.getCurrentThemePtr(), bCreate
);
446 // unset DiagramFontHeights at filter if there was a failure
447 // to avoid dangling pointer
448 rFilter
.setDiagramFontHeights(nullptr);
453 const oox::drawingml::Color
&
454 DiagramColor::getColorByIndex(const std::vector
<oox::drawingml::Color
>& rColors
, sal_Int32 nIndex
)
456 assert(!rColors
.empty());
459 return rColors
[rColors
.size() - 1];
462 return rColors
[nIndex
% rColors
.size()];
466 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */