1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
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
38 #include "diagramlayoutatoms.hxx"
39 #include "layoutatomvisitors.hxx"
40 #include "diagramfragmenthandler.hxx"
41
42 using namespace ::com::sun::star;
43
44 namespace oox::drawingml {
45
sortChildrenByZOrder(const ShapePtr & pShape)46 static void sortChildrenByZOrder(const ShapePtr& pShape)
47 {
48 std::vector<ShapePtr>& rChildren = pShape->getChildren();
49
50 // Offset the children from their default z-order stacking, if necessary.
51 for (size_t i = 0; i < rChildren.size(); ++i)
52 rChildren[i]->setZOrder(i);
53
54 for (size_t i = 0; i < rChildren.size(); ++i)
55 {
56 const ShapePtr& pChild = rChildren[i];
57 sal_Int32 nZOrderOff = pChild->getZOrderOff();
58 if (nZOrderOff <= 0)
59 continue;
60
61 // Increase my ZOrder by nZOrderOff.
62 pChild->setZOrder(pChild->getZOrder() + nZOrderOff);
63 pChild->setZOrderOff(0);
64
65 for (sal_Int32 j = 0; j < nZOrderOff; ++j)
66 {
67 size_t nIndex = i + j + 1;
68 if (nIndex >= rChildren.size())
69 break;
70
71 // Decrease the ZOrder of the next nZOrderOff elements by one.
72 const ShapePtr& pNext = rChildren[nIndex];
73 pNext->setZOrder(pNext->getZOrder() - 1);
74 }
75 }
76
77 // Now that the ZOrders are adjusted, sort the children.
78 std::sort(rChildren.begin(), rChildren.end(),
79 [](const ShapePtr& a, const ShapePtr& b) { return a->getZOrder() < b->getZOrder(); });
80
81 // Apply also for children.
82 for (const auto& rChild : rChildren)
83 sortChildrenByZOrder(rChild);
84 }
85
86 /// Removes empty group shapes, now that their spacing influenced the layout.
removeUnneededGroupShapes(const ShapePtr & pShape)87 static void removeUnneededGroupShapes(const ShapePtr& pShape)
88 {
89 std::vector<ShapePtr>& rChildren = pShape->getChildren();
90
91 rChildren.erase(std::remove_if(rChildren.begin(), rChildren.end(),
92 [](const ShapePtr& aChild) {
93 return aChild->getServiceName()
94 == "com.sun.star.drawing.GroupShape"
95 && aChild->getChildren().empty();
96 }),
97 rChildren.end());
98
99 for (const auto& pChild : rChildren)
100 {
101 removeUnneededGroupShapes(pChild);
102 }
103 }
104
addTo(const ShapePtr & pParentShape)105 void Diagram::addTo( const ShapePtr & pParentShape )
106 {
107 if (pParentShape->getSize().Width == 0 || pParentShape->getSize().Height == 0)
108 SAL_WARN("oox.drawingml", "Diagram cannot be correctly laid out. Size: "
109 << pParentShape->getSize().Width << "x" << pParentShape->getSize().Height);
110
111 pParentShape->setChildSize(pParentShape->getSize());
112
113 const dgm::Point* pRootPoint = mpData->getRootPoint();
114 if (mpLayout->getNode() && pRootPoint)
115 {
116 // create Shape hierarchy
117 ShapeCreationVisitor aCreationVisitor(*this, pRootPoint, pParentShape);
118 mpLayout->getNode()->setExistingShape(pParentShape);
119 mpLayout->getNode()->accept(aCreationVisitor);
120
121 // layout shapes - now all shapes are created
122 ShapeLayoutingVisitor aLayoutingVisitor(*this, pRootPoint);
123 mpLayout->getNode()->accept(aLayoutingVisitor);
124
125 sortChildrenByZOrder(pParentShape);
126 removeUnneededGroupShapes(pParentShape);
127 }
128
129 ShapePtr pBackground = std::make_shared<Shape>("com.sun.star.drawing.CustomShape");
130 pBackground->setSubType(XML_rect);
131 pBackground->getCustomShapeProperties()->setShapePresetType(XML_rect);
132 pBackground->setSize(pParentShape->getSize());
133 pBackground->getFillProperties() = *mpData->getFillProperties();
134 pBackground->setLocked(true);
135 auto& aChildren = pParentShape->getChildren();
136 aChildren.insert(aChildren.begin(), pBackground);
137 }
138
Diagram(const ShapePtr & pShape)139 Diagram::Diagram(const ShapePtr& pShape)
140 : mpShape(pShape)
141 {
142 }
143
getDomsAsPropertyValues() const144 uno::Sequence<beans::PropertyValue> Diagram::getDomsAsPropertyValues() const
145 {
146 sal_Int32 length = maMainDomMap.size();
147
148 if (maDataRelsMap.hasElements())
149 ++length;
150
151 uno::Sequence<beans::PropertyValue> aValue(length);
152 beans::PropertyValue* pValue = aValue.getArray();
153 for (auto const& mainDom : maMainDomMap)
154 {
155 pValue->Name = mainDom.first;
156 pValue->Value <<= mainDom.second;
157 ++pValue;
158 }
159
160 if (maDataRelsMap.hasElements())
161 {
162 pValue->Name = "OOXDiagramDataRels";
163 pValue->Value <<= maDataRelsMap;
164 ++pValue;
165 }
166
167 return aValue;
168 }
169
loadFragment(core::XmlFilterBase & rFilter,const OUString & rFragmentPath)170 static uno::Reference<xml::dom::XDocument> loadFragment(
171 core::XmlFilterBase& rFilter,
172 const OUString& rFragmentPath )
173 {
174 // load diagramming fragments into DOM representation, that later
175 // gets serialized back to SAX events and parsed
176 return rFilter.importFragment( rFragmentPath );
177 }
178
loadFragment(core::XmlFilterBase & rFilter,const rtl::Reference<core::FragmentHandler> & rxHandler)179 static uno::Reference<xml::dom::XDocument> loadFragment(
180 core::XmlFilterBase& rFilter,
181 const rtl::Reference< core::FragmentHandler >& rxHandler )
182 {
183 return loadFragment( rFilter, rxHandler->getFragmentPath() );
184 }
185
importFragment(core::XmlFilterBase & rFilter,const uno::Reference<xml::dom::XDocument> & rXDom,const OUString & rDocName,const DiagramPtr & pDiagram,const rtl::Reference<core::FragmentHandler> & rxHandler)186 static void importFragment( core::XmlFilterBase& rFilter,
187 const uno::Reference<xml::dom::XDocument>& rXDom,
188 const OUString& rDocName,
189 const DiagramPtr& pDiagram,
190 const rtl::Reference< core::FragmentHandler >& rxHandler )
191 {
192 DiagramDomMap& rMainDomMap = pDiagram->getDomMap();
193 rMainDomMap[rDocName] = rXDom;
194
195 uno::Reference<xml::sax::XFastSAXSerializable> xSerializer(
196 rXDom, uno::UNO_QUERY_THROW);
197
198 // now serialize DOM tree into internal data structures
199 rFilter.importFragment( rxHandler, xSerializer );
200 }
201
202 namespace
203 {
204 /**
205 * A fragment handler that just counts the number of <dsp:sp> elements in a
206 * fragment.
207 */
208 class DiagramShapeCounter : public oox::core::FragmentHandler2
209 {
210 public:
211 DiagramShapeCounter(oox::core::XmlFilterBase& rFilter, const OUString& rFragmentPath,
212 sal_Int32& nCounter);
213 oox::core::ContextHandlerRef onCreateContext(sal_Int32 nElement,
214 const AttributeList& rAttribs) override;
215
216 private:
217 sal_Int32& m_nCounter;
218 };
219
DiagramShapeCounter(oox::core::XmlFilterBase & rFilter,const OUString & rFragmentPath,sal_Int32 & nCounter)220 DiagramShapeCounter::DiagramShapeCounter(oox::core::XmlFilterBase& rFilter,
221 const OUString& rFragmentPath, sal_Int32& nCounter)
222 : FragmentHandler2(rFilter, rFragmentPath)
223 , m_nCounter(nCounter)
224 {
225 }
226
onCreateContext(sal_Int32 nElement,const AttributeList &)227 oox::core::ContextHandlerRef DiagramShapeCounter::onCreateContext(sal_Int32 nElement,
228 const AttributeList& /*rAttribs*/)
229 {
230 switch (nElement)
231 {
232 case DSP_TOKEN(drawing):
233 case DSP_TOKEN(spTree):
234 return this;
235 case DSP_TOKEN(sp):
236 ++m_nCounter;
237 break;
238 default:
239 break;
240 }
241
242 return nullptr;
243 }
244 }
245
loadDiagram(ShapePtr const & pShape,core::XmlFilterBase & rFilter,const OUString & rDataModelPath,const OUString & rLayoutPath,const OUString & rQStylePath,const OUString & rColorStylePath,const oox::core::Relations & rRelations)246 void loadDiagram( ShapePtr const & pShape,
247 core::XmlFilterBase& rFilter,
248 const OUString& rDataModelPath,
249 const OUString& rLayoutPath,
250 const OUString& rQStylePath,
251 const OUString& rColorStylePath,
252 const oox::core::Relations& rRelations )
253 {
254 DiagramPtr pDiagram = std::make_shared<Diagram>(pShape);
255
256 DiagramDataPtr pData = std::make_shared<DiagramData>();
257 pDiagram->setData( pData );
258
259 DiagramLayoutPtr pLayout = std::make_shared<DiagramLayout>(*pDiagram);
260 pDiagram->setLayout( pLayout );
261
262 // data
263 if( !rDataModelPath.isEmpty() )
264 {
265 rtl::Reference< core::FragmentHandler > xRefDataModel(
266 new DiagramDataFragmentHandler( rFilter, rDataModelPath, pData ));
267
268 importFragment(rFilter,
269 loadFragment(rFilter,xRefDataModel),
270 "OOXData",
271 pDiagram,
272 xRefDataModel);
273
274 pDiagram->getDataRelsMap() = pShape->resolveRelationshipsOfTypeFromOfficeDoc( rFilter,
275 xRefDataModel->getFragmentPath(), u"image" );
276
277 // Pass the info to pShape
278 for (auto const& extDrawing : pData->getExtDrawings())
279 {
280 OUString aFragmentPath = rRelations.getFragmentPathFromRelId(extDrawing);
281 // Ignore RelIds which don't resolve to a fragment path.
282 if (aFragmentPath.isEmpty())
283 continue;
284
285 sal_Int32 nCounter = 0;
286 rtl::Reference<core::FragmentHandler> xCounter(
287 new DiagramShapeCounter(rFilter, aFragmentPath, nCounter));
288 rFilter.importFragment(xCounter);
289 // Ignore ext drawings which don't actually have any shapes.
290 if (nCounter == 0)
291 continue;
292
293 pShape->addExtDrawingRelId(extDrawing);
294 }
295 }
296
297 // extLst is present, lets bet on that and ignore the rest of the data from here
298 if( pShape->getExtDrawings().empty() )
299 {
300 // layout
301 if( !rLayoutPath.isEmpty() )
302 {
303 rtl::Reference< core::FragmentHandler > xRefLayout(
304 new DiagramLayoutFragmentHandler( rFilter, rLayoutPath, pLayout ));
305
306 importFragment(rFilter,
307 loadFragment(rFilter,xRefLayout),
308 "OOXLayout",
309 pDiagram,
310 xRefLayout);
311 }
312
313 // style
314 if( !rQStylePath.isEmpty() )
315 {
316 rtl::Reference< core::FragmentHandler > xRefQStyle(
317 new DiagramQStylesFragmentHandler( rFilter, rQStylePath, pDiagram->getStyles() ));
318
319 importFragment(rFilter,
320 loadFragment(rFilter,xRefQStyle),
321 "OOXStyle",
322 pDiagram,
323 xRefQStyle);
324 }
325 }
326 else
327 {
328 // We still want to add the XDocuments to the DiagramDomMap
329 DiagramDomMap& rMainDomMap = pDiagram->getDomMap();
330 rMainDomMap[OUString("OOXLayout")] = loadFragment(rFilter,rLayoutPath);
331 rMainDomMap[OUString("OOXStyle")] = loadFragment(rFilter,rQStylePath);
332 }
333
334 // colors
335 if( !rColorStylePath.isEmpty() )
336 {
337 rtl::Reference< core::FragmentHandler > xRefColorStyle(
338 new ColorFragmentHandler( rFilter, rColorStylePath, pDiagram->getColors() ));
339
340 importFragment(rFilter,
341 loadFragment(rFilter,xRefColorStyle),
342 "OOXColor",
343 pDiagram,
344 xRefColorStyle);
345 }
346
347 if( !pData->getExtDrawings().empty() )
348 {
349 const DiagramColorMap::const_iterator aColor = pDiagram->getColors().find("node0");
350 if( aColor != pDiagram->getColors().end() && !aColor->second.maTextFillColors.empty())
351 {
352 // TODO(F1): well, actually, there might be *several* color
353 // definitions in it, after all it's called list.
354 pShape->setFontRefColorForNodes(DiagramColor::getColorByIndex(aColor->second.maTextFillColors, -1));
355 }
356 }
357
358 // collect data, init maps
359 pData->build();
360
361 // diagram loaded. now lump together & attach to shape
362 pDiagram->addTo(pShape);
363 pShape->setDiagramData(pData);
364 pShape->setDiagramDoms(pDiagram->getDomsAsPropertyValues());
365 }
366
loadDiagram(ShapePtr const & pShape,DiagramDataPtr pDiagramData,const uno::Reference<xml::dom::XDocument> & layoutDom,const uno::Reference<xml::dom::XDocument> & styleDom,const uno::Reference<xml::dom::XDocument> & colorDom,core::XmlFilterBase & rFilter)367 void loadDiagram(ShapePtr const& pShape,
368 DiagramDataPtr pDiagramData,
369 const uno::Reference<xml::dom::XDocument>& layoutDom,
370 const uno::Reference<xml::dom::XDocument>& styleDom,
371 const uno::Reference<xml::dom::XDocument>& colorDom,
372 core::XmlFilterBase& rFilter)
373 {
374 DiagramPtr pDiagram = std::make_shared<Diagram>(pShape);
375
376 pDiagram->setData(pDiagramData);
377
378 DiagramLayoutPtr pLayout = std::make_shared<DiagramLayout>(*pDiagram);
379 pDiagram->setLayout(pLayout);
380
381 // layout
382 if (layoutDom.is())
383 {
384 rtl::Reference<core::FragmentHandler> xRefLayout(
385 new DiagramLayoutFragmentHandler(rFilter, OUString(), pLayout));
386
387 importFragment(rFilter, layoutDom, "OOXLayout", pDiagram, xRefLayout);
388 }
389
390 // style
391 if (styleDom.is())
392 {
393 rtl::Reference<core::FragmentHandler> xRefQStyle(
394 new DiagramQStylesFragmentHandler(rFilter, OUString(), pDiagram->getStyles()));
395
396 importFragment(rFilter, styleDom, "OOXStyle", pDiagram, xRefQStyle);
397 }
398
399 // colors
400 if (colorDom.is())
401 {
402 rtl::Reference<core::FragmentHandler> xRefColorStyle(
403 new ColorFragmentHandler(rFilter, OUString(), pDiagram->getColors()));
404
405 importFragment(rFilter, colorDom, "OOXColor", pDiagram, xRefColorStyle);
406 }
407
408 // diagram loaded. now lump together & attach to shape
409 pDiagram->addTo(pShape);
410 }
411
reloadDiagram(SdrObject * pObj,core::XmlFilterBase & rFilter)412 void reloadDiagram(SdrObject* pObj, core::XmlFilterBase& rFilter)
413 {
414 DiagramDataPtr pDiagramData = std::dynamic_pointer_cast<DiagramData>(pObj->GetDiagramData());
415 if (!pDiagramData)
416 return;
417
418 pObj->getChildrenOfSdrObject()->ClearSdrObjList();
419
420 uno::Reference<css::drawing::XShape> xShape(pObj->getUnoShape(), uno::UNO_QUERY_THROW);
421 uno::Reference<beans::XPropertySet> xPropSet(xShape, uno::UNO_QUERY_THROW);
422
423 uno::Reference<xml::dom::XDocument> layoutDom;
424 uno::Reference<xml::dom::XDocument> styleDom;
425 uno::Reference<xml::dom::XDocument> colorDom;
426
427 // retrieve the doms from the GrabBag
428 uno::Sequence<beans::PropertyValue> propList;
429 xPropSet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= propList;
430 for (const auto& rProp : std::as_const(propList))
431 {
432 OUString propName = rProp.Name;
433 if (propName == "OOXLayout")
434 rProp.Value >>= layoutDom;
435 else if (propName == "OOXStyle")
436 rProp.Value >>= styleDom;
437 else if (propName == "OOXColor")
438 rProp.Value >>= colorDom;
439 }
440
441 ShapePtr pShape = std::make_shared<Shape>();
442 pShape->setDiagramType();
443 pShape->setSize(
444 awt::Size(o3tl::convert(xShape->getSize().Width, o3tl::Length::mm100, o3tl::Length::emu),
445 o3tl::convert(xShape->getSize().Height, o3tl::Length::mm100, o3tl::Length::emu)));
446
447 loadDiagram(pShape, pDiagramData, layoutDom, styleDom, colorDom, rFilter);
448
449 uno::Reference<drawing::XShapes> xShapes(xShape, uno::UNO_QUERY_THROW);
450 basegfx::B2DHomMatrix aTransformation;
451 aTransformation.translate(
452 o3tl::convert(xShape->getPosition().X, o3tl::Length::mm100, o3tl::Length::emu),
453 o3tl::convert(xShape->getPosition().Y, o3tl::Length::mm100, o3tl::Length::emu));
454 for (auto const& child : pShape->getChildren())
455 child->addShape(rFilter, rFilter.getCurrentTheme(), xShapes, aTransformation, pShape->getFillProperties());
456 }
457
458 const oox::drawingml::Color&
getColorByIndex(const std::vector<oox::drawingml::Color> & rColors,sal_Int32 nIndex)459 DiagramColor::getColorByIndex(const std::vector<oox::drawingml::Color>& rColors, sal_Int32 nIndex)
460 {
461 assert(!rColors.empty());
462 if (nIndex == -1)
463 {
464 return rColors[rColors.size() - 1];
465 }
466
467 return rColors[nIndex % rColors.size()];
468 }
469 }
470
471 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
472