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