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 rChildren
.erase(std::remove_if(rChildren
.begin(), rChildren
.end(),
94 [](const ShapePtr
& aChild
) {
95 return aChild
->getServiceName()
96 == "com.sun.star.drawing.GroupShape"
97 && aChild
->getChildren().empty();
101 for (const auto& pChild
: rChildren
)
103 removeUnneededGroupShapes(pChild
);
108 void Diagram::addTo( const ShapePtr
& pParentShape
)
110 if (pParentShape
->getSize().Width
== 0 || pParentShape
->getSize().Height
== 0)
111 SAL_WARN("oox.drawingml", "Diagram cannot be correctly laid out. Size: "
112 << pParentShape
->getSize().Width
<< "x" << pParentShape
->getSize().Height
);
114 pParentShape
->setChildSize(pParentShape
->getSize());
116 const svx::diagram::Point
* pRootPoint
= mpData
->getRootPoint();
117 if (mpLayout
->getNode() && pRootPoint
)
119 // create Shape hierarchy
120 ShapeCreationVisitor
aCreationVisitor(*this, pRootPoint
, pParentShape
);
121 mpLayout
->getNode()->setExistingShape(pParentShape
);
122 mpLayout
->getNode()->accept(aCreationVisitor
);
124 // layout shapes - now all shapes are created
125 ShapeLayoutingVisitor
aLayoutingVisitor(*this, pRootPoint
);
126 mpLayout
->getNode()->accept(aLayoutingVisitor
);
128 sortChildrenByZOrder(pParentShape
);
129 removeUnneededGroupShapes(pParentShape
);
132 ShapePtr pBackground
= std::make_shared
<Shape
>("com.sun.star.drawing.CustomShape");
133 pBackground
->setSubType(XML_rect
);
134 pBackground
->getCustomShapeProperties()->setShapePresetType(XML_rect
);
135 pBackground
->setSize(pParentShape
->getSize());
136 pBackground
->getFillProperties() = *mpData
->getBackgroundShapeFillProperties();
137 pBackground
->setLocked(true);
139 // create and set ModelID for BackgroundShape to allow later association
140 getData()->setBackgroundShapeModelID(OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8
));
141 pBackground
->setDiagramDataModelID(getData()->getBackgroundShapeModelID());
143 auto& aChildren
= pParentShape
->getChildren();
144 aChildren
.insert(aChildren
.begin(), pBackground
);
148 : maDiagramFontHeights()
152 uno::Sequence
<beans::PropertyValue
> Diagram::getDomsAsPropertyValues() const
154 sal_Int32 length
= maMainDomMap
.size();
156 if (maDataRelsMap
.hasElements())
159 uno::Sequence
<beans::PropertyValue
> aValue(length
);
160 beans::PropertyValue
* pValue
= aValue
.getArray();
161 for (auto const& mainDom
: maMainDomMap
)
163 pValue
->Name
= mainDom
.first
;
164 pValue
->Value
<<= mainDom
.second
;
168 if (maDataRelsMap
.hasElements())
170 pValue
->Name
= "OOXDiagramDataRels";
171 pValue
->Value
<<= maDataRelsMap
;
179 = std::map
<std::shared_ptr
<drawingml::Shape
>, css::uno::Reference
<css::drawing::XShape
>>;
181 void Diagram::syncDiagramFontHeights()
183 // Each name represents a group of shapes, for which the font height should have the same
185 for (const auto& rNameAndPairs
: maDiagramFontHeights
)
187 // Find out the minimum scale within this group.
188 const ShapePairs
& rShapePairs
= rNameAndPairs
.second
;
189 double nMinScale
= 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 nTextFitToSizeScale
= 0.0;
196 xPropertySet
->getPropertyValue("TextFitToSizeScale") >>= nTextFitToSizeScale
;
197 if (nTextFitToSizeScale
> 0 && nTextFitToSizeScale
< nMinScale
)
199 nMinScale
= nTextFitToSizeScale
;
204 // Set that minimum scale for all members of the group.
205 if (nMinScale
< 100.0)
207 for (const auto& rShapePair
: rShapePairs
)
209 uno::Reference
<beans::XPropertySet
> xPropertySet(rShapePair
.second
, uno::UNO_QUERY
);
210 if (xPropertySet
.is())
212 xPropertySet
->setPropertyValue("TextFitToSizeScale", uno::Any(nMinScale
));
218 // no longer needed after processing
219 maDiagramFontHeights
.clear();
222 static uno::Reference
<xml::dom::XDocument
> loadFragment(
223 core::XmlFilterBase
& rFilter
,
224 const OUString
& rFragmentPath
)
226 // load diagramming fragments into DOM representation, that later
227 // gets serialized back to SAX events and parsed
228 return rFilter
.importFragment( rFragmentPath
);
231 static uno::Reference
<xml::dom::XDocument
> loadFragment(
232 core::XmlFilterBase
& rFilter
,
233 const rtl::Reference
< core::FragmentHandler
>& rxHandler
)
235 return loadFragment( rFilter
, rxHandler
->getFragmentPath() );
238 static void importFragment( core::XmlFilterBase
& rFilter
,
239 const uno::Reference
<xml::dom::XDocument
>& rXDom
,
240 const OUString
& rDocName
,
241 const DiagramPtr
& pDiagram
,
242 const rtl::Reference
< core::FragmentHandler
>& rxHandler
)
244 DiagramDomMap
& rMainDomMap
= pDiagram
->getDomMap();
245 rMainDomMap
[rDocName
] = rXDom
;
247 uno::Reference
<xml::sax::XFastSAXSerializable
> xSerializer(
248 rXDom
, uno::UNO_QUERY_THROW
);
250 // now serialize DOM tree into internal data structures
251 rFilter
.importFragment( rxHandler
, xSerializer
);
257 * A fragment handler that just counts the number of <dsp:sp> elements in a
260 class DiagramShapeCounter
: public oox::core::FragmentHandler2
263 DiagramShapeCounter(oox::core::XmlFilterBase
& rFilter
, const OUString
& rFragmentPath
,
264 sal_Int32
& nCounter
);
265 oox::core::ContextHandlerRef
onCreateContext(sal_Int32 nElement
,
266 const AttributeList
& rAttribs
) override
;
269 sal_Int32
& m_nCounter
;
272 DiagramShapeCounter::DiagramShapeCounter(oox::core::XmlFilterBase
& rFilter
,
273 const OUString
& rFragmentPath
, sal_Int32
& nCounter
)
274 : FragmentHandler2(rFilter
, rFragmentPath
)
275 , m_nCounter(nCounter
)
279 oox::core::ContextHandlerRef
DiagramShapeCounter::onCreateContext(sal_Int32 nElement
,
280 const AttributeList
& /*rAttribs*/)
284 case DSP_TOKEN(drawing
):
285 case DSP_TOKEN(spTree
):
298 void loadDiagram( ShapePtr
const & pShape
,
299 core::XmlFilterBase
& rFilter
,
300 const OUString
& rDataModelPath
,
301 const OUString
& rLayoutPath
,
302 const OUString
& rQStylePath
,
303 const OUString
& rColorStylePath
,
304 const oox::core::Relations
& rRelations
)
306 DiagramPtr pDiagram
= std::make_shared
<Diagram
>();
308 OoxDiagramDataPtr pData
= std::make_shared
<DiagramData
>();
309 pDiagram
->setData( pData
);
311 DiagramLayoutPtr pLayout
= std::make_shared
<DiagramLayout
>(*pDiagram
);
312 pDiagram
->setLayout( pLayout
);
314 // set DiagramFontHeights at filter
315 rFilter
.setDiagramFontHeights(&pDiagram
->getDiagramFontHeights());
318 if( !rDataModelPath
.isEmpty() )
320 rtl::Reference
< core::FragmentHandler
> xRefDataModel(
321 new DiagramDataFragmentHandler( rFilter
, rDataModelPath
, pData
));
323 importFragment(rFilter
,
324 loadFragment(rFilter
,xRefDataModel
),
329 pDiagram
->getDataRelsMap() = pShape
->resolveRelationshipsOfTypeFromOfficeDoc( rFilter
,
330 xRefDataModel
->getFragmentPath(), u
"image" );
332 // Pass the info to pShape
333 for (auto const& extDrawing
: pData
->getExtDrawings())
335 OUString aFragmentPath
= rRelations
.getFragmentPathFromRelId(extDrawing
);
336 // Ignore RelIds which don't resolve to a fragment path.
337 if (aFragmentPath
.isEmpty())
340 sal_Int32 nCounter
= 0;
341 rtl::Reference
<core::FragmentHandler
> xCounter(
342 new DiagramShapeCounter(rFilter
, aFragmentPath
, nCounter
));
343 rFilter
.importFragment(xCounter
);
344 // Ignore ext drawings which don't actually have any shapes.
348 pShape
->addExtDrawingRelId(extDrawing
);
352 // extLst is present, lets bet on that and ignore the rest of the data from here
353 if( pShape
->getExtDrawings().empty() )
356 if( !rLayoutPath
.isEmpty() )
358 rtl::Reference
< core::FragmentHandler
> xRefLayout(
359 new DiagramLayoutFragmentHandler( rFilter
, rLayoutPath
, pLayout
));
361 importFragment(rFilter
,
362 loadFragment(rFilter
,xRefLayout
),
369 if( !rQStylePath
.isEmpty() )
371 rtl::Reference
< core::FragmentHandler
> xRefQStyle(
372 new DiagramQStylesFragmentHandler( rFilter
, rQStylePath
, pDiagram
->getStyles() ));
374 importFragment(rFilter
,
375 loadFragment(rFilter
,xRefQStyle
),
383 // We still want to add the XDocuments to the DiagramDomMap
384 DiagramDomMap
& rMainDomMap
= pDiagram
->getDomMap();
385 rMainDomMap
[OUString("OOXLayout")] = loadFragment(rFilter
,rLayoutPath
);
386 rMainDomMap
[OUString("OOXStyle")] = loadFragment(rFilter
,rQStylePath
);
390 if( !rColorStylePath
.isEmpty() )
392 rtl::Reference
< core::FragmentHandler
> xRefColorStyle(
393 new ColorFragmentHandler( rFilter
, rColorStylePath
, pDiagram
->getColors() ));
395 importFragment(rFilter
,
396 loadFragment(rFilter
,xRefColorStyle
),
402 if( !pData
->getExtDrawings().empty() )
404 const DiagramColorMap::const_iterator aColor
= pDiagram
->getColors().find("node0");
405 if( aColor
!= pDiagram
->getColors().end() && !aColor
->second
.maTextFillColors
.empty())
407 // TODO(F1): well, actually, there might be *several* color
408 // definitions in it, after all it's called list.
409 pShape
->setFontRefColorForNodes(DiagramColor::getColorByIndex(aColor
->second
.maTextFillColors
, -1));
413 // collect data, init maps
414 // for Diagram import, do - for now - NOT clear all oox::drawingml::Shape
415 pData
->buildDiagramDataModel(false);
417 // diagram loaded. now lump together & attach to shape
418 pDiagram
->addTo(pShape
);
419 pShape
->setDiagramDoms(pDiagram
->getDomsAsPropertyValues());
421 // Get the oox::Theme definition and - if available - move/secure the
422 // original ImportData directly to the Diagram ModelData
423 std::shared_ptr
<::oox::drawingml::Theme
> aTheme(rFilter
.getCurrentThemePtr());
425 pData
->setThemeDocument(aTheme
->getFragment()); //getTempFile());
427 // Prepare support for the advanced DiagramHelper using Diagram & Theme data
428 pShape
->prepareDiagramHelper(pDiagram
, rFilter
.getCurrentThemePtr());
431 const oox::drawingml::Color
&
432 DiagramColor::getColorByIndex(const std::vector
<oox::drawingml::Color
>& rColors
, sal_Int32 nIndex
)
434 assert(!rColors
.empty());
437 return rColors
[rColors
.size() - 1];
440 return rColors
[nIndex
% rColors
.size()];
444 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */