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 #include <oox/ppt/pptimport.hxx>
38 #include <comphelper/xmltools.hxx>
39 
40 #include "diagramlayoutatoms.hxx"
41 #include "layoutatomvisitors.hxx"
43 
44 using namespace ::com::sun::star;
45 
46 namespace oox::drawingml {
47 
48 static void sortChildrenByZOrder(const ShapePtr& pShape)
49 {
50  std::vector<ShapePtr>& rChildren = pShape->getChildren();
51 
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);
55 
56  for (size_t i = 0; i < rChildren.size(); ++i)
57  {
58  const ShapePtr& pChild = rChildren[i];
59  sal_Int32 nZOrderOff = pChild->getZOrderOff();
60  if (nZOrderOff <= 0)
61  continue;
62 
63  // Increase my ZOrder by nZOrderOff.
64  pChild->setZOrder(pChild->getZOrder() + nZOrderOff);
65  pChild->setZOrderOff(0);
66 
67  for (sal_Int32 j = 0; j < nZOrderOff; ++j)
68  {
69  size_t nIndex = i + j + 1;
70  if (nIndex >= rChildren.size())
71  break;
72 
73  // Decrease the ZOrder of the next nZOrderOff elements by one.
74  const ShapePtr& pNext = rChildren[nIndex];
75  pNext->setZOrder(pNext->getZOrder() - 1);
76  }
77  }
78 
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(); });
82 
83  // Apply also for children.
84  for (const auto& rChild : rChildren)
85  sortChildrenByZOrder(rChild);
86 }
87 
89 static void removeUnneededGroupShapes(const ShapePtr& pShape)
90 {
91  std::vector<ShapePtr>& rChildren = pShape->getChildren();
92 
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();
98  }),
99  rChildren.end());
100 
101  for (const auto& pChild : rChildren)
102  {
104  }
105 }
106 
107 
108 void Diagram::addTo( const ShapePtr & pParentShape )
109 {
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);
113 
114  pParentShape->setChildSize(pParentShape->getSize());
115 
116  const svx::diagram::Point* pRootPoint = mpData->getRootPoint();
117  if (mpLayout->getNode() && pRootPoint)
118  {
119  // create Shape hierarchy
120  ShapeCreationVisitor aCreationVisitor(*this, pRootPoint, pParentShape);
121  mpLayout->getNode()->setExistingShape(pParentShape);
122  mpLayout->getNode()->accept(aCreationVisitor);
123 
124  // layout shapes - now all shapes are created
125  ShapeLayoutingVisitor aLayoutingVisitor(*this, pRootPoint);
126  mpLayout->getNode()->accept(aLayoutingVisitor);
127 
128  sortChildrenByZOrder(pParentShape);
129  removeUnneededGroupShapes(pParentShape);
130  }
131 
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);
138 
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());
142 
143  auto& aChildren = pParentShape->getChildren();
144  aChildren.insert(aChildren.begin(), pBackground);
145 }
146 
148 : maDiagramFontHeights()
149 {
150 }
151 
152 uno::Sequence<beans::PropertyValue> Diagram::getDomsAsPropertyValues() const
153 {
154  sal_Int32 length = maMainDomMap.size();
155 
156  if (maDataRelsMap.hasElements())
157  ++length;
158 
159  uno::Sequence<beans::PropertyValue> aValue(length);
160  beans::PropertyValue* pValue = aValue.getArray();
161  for (auto const& mainDom : maMainDomMap)
162  {
163  pValue->Name = mainDom.first;
164  pValue->Value <<= mainDom.second;
165  ++pValue;
166  }
167 
168  if (maDataRelsMap.hasElements())
169  {
170  pValue->Name = "OOXDiagramDataRels";
171  pValue->Value <<= maDataRelsMap;
172  ++pValue;
173  }
174 
175  return aValue;
176 }
177 
178 using ShapePairs
179  = std::map<std::shared_ptr<drawingml::Shape>, css::uno::Reference<css::drawing::XShape>>;
180 
182 {
183  // Each name represents a group of shapes, for which the font height should have the same
184  // scaling.
185  for (const auto& rNameAndPairs : maDiagramFontHeights)
186  {
187  // Find out the minimum scale within this group.
188  const ShapePairs& rShapePairs = rNameAndPairs.second;
189  sal_Int16 nMinScale = 100;
190  for (const auto& rShapePair : rShapePairs)
191  {
192  uno::Reference<beans::XPropertySet> xPropertySet(rShapePair.second, uno::UNO_QUERY);
193  if (xPropertySet.is())
194  {
195  sal_Int16 nTextFitToSizeScale = 0;
196  xPropertySet->getPropertyValue("TextFitToSizeScale") >>= nTextFitToSizeScale;
197  if (nTextFitToSizeScale > 0 && nTextFitToSizeScale < nMinScale)
198  {
199  nMinScale = nTextFitToSizeScale;
200  }
201  }
202  }
203 
204  // Set that minimum scale for all members of the group.
205  if (nMinScale < 100)
206  {
207  for (const auto& rShapePair : rShapePairs)
208  {
209  uno::Reference<beans::XPropertySet> xPropertySet(rShapePair.second, uno::UNO_QUERY);
210  if (xPropertySet.is())
211  {
212  xPropertySet->setPropertyValue("TextFitToSizeScale", uno::Any(nMinScale));
213  }
214  }
215  }
216  }
217 
218  // no longer needed after processing
219  maDiagramFontHeights.clear();
220 }
221 
222 static uno::Reference<xml::dom::XDocument> loadFragment(
223  core::XmlFilterBase& rFilter,
224  const OUString& rFragmentPath )
225 {
226  // load diagramming fragments into DOM representation, that later
227  // gets serialized back to SAX events and parsed
228  return rFilter.importFragment( rFragmentPath );
229 }
230 
231 static uno::Reference<xml::dom::XDocument> loadFragment(
232  core::XmlFilterBase& rFilter,
233  const rtl::Reference< core::FragmentHandler >& rxHandler )
234 {
235  return loadFragment( rFilter, rxHandler->getFragmentPath() );
236 }
237 
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 )
243 {
244  DiagramDomMap& rMainDomMap = pDiagram->getDomMap();
245  rMainDomMap[rDocName] = rXDom;
246 
247  uno::Reference<xml::sax::XFastSAXSerializable> xSerializer(
248  rXDom, uno::UNO_QUERY_THROW);
249 
250  // now serialize DOM tree into internal data structures
251  rFilter.importFragment( rxHandler, xSerializer );
252 }
253 
254 namespace
255 {
260 class DiagramShapeCounter : public oox::core::FragmentHandler2
261 {
262 public:
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;
267 
268 private:
269  sal_Int32& m_nCounter;
270 };
271 
272 DiagramShapeCounter::DiagramShapeCounter(oox::core::XmlFilterBase& rFilter,
273  const OUString& rFragmentPath, sal_Int32& nCounter)
274  : FragmentHandler2(rFilter, rFragmentPath)
275  , m_nCounter(nCounter)
276 {
277 }
278 
279 oox::core::ContextHandlerRef DiagramShapeCounter::onCreateContext(sal_Int32 nElement,
280  const AttributeList& /*rAttribs*/)
281 {
282  switch (nElement)
283  {
284  case DSP_TOKEN(drawing):
285  case DSP_TOKEN(spTree):
286  return this;
287  case DSP_TOKEN(sp):
288  ++m_nCounter;
289  break;
290  default:
291  break;
292  }
293 
294  return nullptr;
295 }
296 }
297 
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 )
305 {
306  DiagramPtr pDiagram = std::make_shared<Diagram>();
307 
308  OoxDiagramDataPtr pData = std::make_shared<DiagramData>();
309  pDiagram->setData( pData );
310 
311  DiagramLayoutPtr pLayout = std::make_shared<DiagramLayout>(*pDiagram);
312  pDiagram->setLayout( pLayout );
313 
314  // set DiagramFontHeights at filter
315  rFilter.setDiagramFontHeights(&pDiagram->getDiagramFontHeights());
316 
317  // data
318  if( !rDataModelPath.isEmpty() )
319  {
321  new DiagramDataFragmentHandler( rFilter, rDataModelPath, pData ));
322 
323  importFragment(rFilter,
324  loadFragment(rFilter,xRefDataModel),
325  "OOXData",
326  pDiagram,
327  xRefDataModel);
328 
329  pDiagram->getDataRelsMap() = pShape->resolveRelationshipsOfTypeFromOfficeDoc( rFilter,
330  xRefDataModel->getFragmentPath(), u"image" );
331 
332  // Pass the info to pShape
333  for (auto const& extDrawing : pData->getExtDrawings())
334  {
335  OUString aFragmentPath = rRelations.getFragmentPathFromRelId(extDrawing);
336  // Ignore RelIds which don't resolve to a fragment path.
337  if (aFragmentPath.isEmpty())
338  continue;
339 
340  sal_Int32 nCounter = 0;
342  new DiagramShapeCounter(rFilter, aFragmentPath, nCounter));
343  rFilter.importFragment(xCounter);
344  // Ignore ext drawings which don't actually have any shapes.
345  if (nCounter == 0)
346  continue;
347 
348  pShape->addExtDrawingRelId(extDrawing);
349  }
350  }
351 
352  // extLst is present, lets bet on that and ignore the rest of the data from here
353  if( pShape->getExtDrawings().empty() )
354  {
355  // layout
356  if( !rLayoutPath.isEmpty() )
357  {
359  new DiagramLayoutFragmentHandler( rFilter, rLayoutPath, pLayout ));
360 
361  importFragment(rFilter,
362  loadFragment(rFilter,xRefLayout),
363  "OOXLayout",
364  pDiagram,
365  xRefLayout);
366  }
367 
368  // style
369  if( !rQStylePath.isEmpty() )
370  {
372  new DiagramQStylesFragmentHandler( rFilter, rQStylePath, pDiagram->getStyles() ));
373 
374  importFragment(rFilter,
375  loadFragment(rFilter,xRefQStyle),
376  "OOXStyle",
377  pDiagram,
378  xRefQStyle);
379  }
380  }
381  else
382  {
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);
387  }
388 
389  // colors
390  if( !rColorStylePath.isEmpty() )
391  {
393  new ColorFragmentHandler( rFilter, rColorStylePath, pDiagram->getColors() ));
394 
395  importFragment(rFilter,
396  loadFragment(rFilter,xRefColorStyle),
397  "OOXColor",
398  pDiagram,
399  xRefColorStyle);
400  }
401 
402  if( !pData->getExtDrawings().empty() )
403  {
404  const DiagramColorMap::const_iterator aColor = pDiagram->getColors().find("node0");
405  if( aColor != pDiagram->getColors().end() && !aColor->second.maTextFillColors.empty())
406  {
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));
410  }
411  }
412 
413  // collect data, init maps
414  // for Diagram import, do - for now - NOT clear all oox::drawingml::Shape
415  pData->buildDiagramDataModel(false);
416 
417  // diagram loaded. now lump together & attach to shape
418  pDiagram->addTo(pShape);
419  pShape->setDiagramDoms(pDiagram->getDomsAsPropertyValues());
420 
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());
424  if(aTheme)
425  pData->setThemeDocument(aTheme->getFragment()); //getTempFile());
426 
427  // Prepare support for the advanced DiagramHelper using Diagram & Theme data
428  pShape->prepareDiagramHelper(pDiagram, rFilter.getCurrentThemePtr());
429 }
430 
432 DiagramColor::getColorByIndex(const std::vector<oox::drawingml::Color>& rColors, sal_Int32 nIndex)
433 {
434  assert(!rColors.empty());
435  if (nIndex == -1)
436  {
437  return rColors[rColors.size() - 1];
438  }
439 
440  return rColors[nIndex % rColors.size()];
441 }
442 }
443 
444 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
std::shared_ptr< Diagram > DiagramPtr
sal_Int32 nIndex
void setDiagramFontHeights(NamedShapePairs *pDiagramFontHeights)
std::unique_ptr< sal_Int32[]> pData
static void removeUnneededGroupShapes(const ShapePtr &pShape)
Removes empty group shapes, now that their spacing influenced the layout.
Definition: diagram.cxx:89
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...
std::shared_ptr< DiagramData > OoxDiagramDataPtr
Definition: datamodel.hxx:74
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:238
std::map< OUString, css::uno::Reference< css::xml::dom::XDocument > > DiagramDomMap
virtual std::shared_ptr<::oox::drawingml::Theme > getCurrentThemePtr() const
May be implemented by filters which handle Diagrams, default returns empty ptr.
static uno::Reference< xml::dom::XDocument > loadFragment(core::XmlFilterBase &rFilter, const rtl::Reference< core::FragmentHandler > &rxHandler)
Definition: diagram.cxx:231
uno_Any a
static uno::Reference< xml::dom::XDocument > loadFragment(core::XmlFilterBase &rFilter, const OUString &rFragmentPath)
Definition: diagram.cxx:222
sal_Int32 & m_nCounter
Definition: diagram.cxx:269
float u
std::shared_ptr< DiagramLayout > DiagramLayoutPtr
static void sortChildrenByZOrder(const ShapePtr &pShape)
Definition: diagram.cxx:48
css::uno::Sequence< css::beans::PropertyValue > getDomsAsPropertyValues() const
Definition: diagram.cxx:152
OString generateGUIDString()
Provides access to attribute values of an element.
void loadDiagram(ShapePtr const &pShape, core::XmlFilterBase &rFilter, const OUString &rDataModelPath, const OUString &rLayoutPath, const OUString &rQStylePath, const OUString &rColorStylePath, const oox::core::Relations &rRelations)
load diagram data, and put resulting graphic into shape
Definition: diagram.cxx:298
OUString getFragmentPathFromRelId(const OUString &rRelId) const
Returns the full fragment path for the passed relation identifier.
Definition: relations.cxx:132
const OoxDiagramDataPtr & getData() const
void addTo(const ShapePtr &pShape)
Definition: diagram.cxx:108
#define SAL_WARN(area, stream)
std::shared_ptr< Shape > ShapePtr
std::map< std::shared_ptr< drawingml::Shape >, css::uno::Reference< css::drawing::XShape >> ShapePairs
Definition: diagram.cxx:179