LibreOffice Module oox (master)  1
diagram.cxx
Go to the documentation of this file.
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 
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>
33 #include <o3tl/unit_conversion.hxx>
34 #include <oox/token/namespaces.hxx>
36 #include <svx/svdpage.hxx>
37 
38 #include "diagramlayoutatoms.hxx"
39 #include "layoutatomvisitors.hxx"
41 
42 using namespace ::com::sun::star;
43 
44 namespace oox::drawingml {
45 
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 
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  {
102  }
103 }
104 
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 
140  : mpShape(pShape)
141 {
142 }
143 
144 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 
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 
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 
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 {
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 
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 
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 
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  {
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;
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  {
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  {
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  {
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 
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  {
385  new DiagramLayoutFragmentHandler(rFilter, OUString(), pLayout));
386 
387  importFragment(rFilter, layoutDom, "OOXLayout", pDiagram, xRefLayout);
388  }
389 
390  // style
391  if (styleDom.is())
392  {
394  new DiagramQStylesFragmentHandler(rFilter, OUString(), pDiagram->getStyles()));
395 
396  importFragment(rFilter, styleDom, "OOXStyle", pDiagram, xRefQStyle);
397  }
398 
399  // colors
400  if (colorDom.is())
401  {
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 
413 {
414  DiagramDataPtr pDiagramData = std::dynamic_pointer_cast<DiagramData>(pObj->GetDiagramData());
415  if (!pDiagramData)
416  return;
417 
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 
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: */
const std::shared_ptr< DiagramDataInterface > & GetDiagramData() const
std::shared_ptr< Diagram > DiagramPtr
sal_Int32 nIndex
void reloadDiagram(SdrObject *pObj, core::XmlFilterBase &rFilter)
Definition: diagram.cxx:412
std::shared_ptr< DiagramData > DiagramDataPtr
Definition: datamodel.hxx:199
std::unique_ptr< ContentProperties > pData
virtual SdrObjList * getChildrenOfSdrObject() const
static void removeUnneededGroupShapes(const ShapePtr &pShape)
Removes empty group shapes, now that their spacing influenced the layout.
Definition: diagram.cxx:87
constexpr Point convert(const Point &rPoint, o3tl::Length eFrom, o3tl::Length eTo)
css::uno::Sequence< css::uno::Sequence< css::uno::Any > > maDataRelsMap
bool importFragment(const rtl::Reference< FragmentHandler > &rxHandler)
Imports a fragment using the passed fragment handler, which contains the full path to the fragment st...
virtual css::uno::Reference< css::uno::XInterface > getUnoShape()
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)
Definition: diagram.cxx:186
virtual const ::oox::drawingml::Theme * getCurrentTheme() const =0
Has to be implemented by each filter, returns the current theme.
#define UNO_NAME_MISC_OBJ_INTEROPGRABBAG
std::map< OUString, css::uno::Reference< css::xml::dom::XDocument > > DiagramDomMap
static uno::Reference< xml::dom::XDocument > loadFragment(core::XmlFilterBase &rFilter, const rtl::Reference< core::FragmentHandler > &rxHandler)
Definition: diagram.cxx:179
uno_Any a
static uno::Reference< xml::dom::XDocument > loadFragment(core::XmlFilterBase &rFilter, const OUString &rFragmentPath)
Definition: diagram.cxx:170
sal_Int32 & m_nCounter
Definition: diagram.cxx:217
float u
std::shared_ptr< DiagramLayout > DiagramLayoutPtr
void 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)
Definition: diagram.cxx:367
static void sortChildrenByZOrder(const ShapePtr &pShape)
Definition: diagram.cxx:46
css::uno::Sequence< css::beans::PropertyValue > getDomsAsPropertyValues() const
Definition: diagram.cxx:144
Provides access to attribute values of an element.
OUString getFragmentPathFromRelId(const OUString &rRelId) const
Returns the full fragment path for the passed relation identifier.
Definition: relations.cxx:129
void ClearSdrObjList()
void addTo(const ShapePtr &pShape)
Definition: diagram.cxx:105
Diagram(const ShapePtr &pShape)
Definition: diagram.cxx:139
void translate(double fX, double fY)
#define SAL_WARN(area, stream)
std::shared_ptr< Shape > ShapePtr
AnimatableShapeSharedPtr mpShape