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>
34#include <oox/token/namespaces.hxx>
36#include <svx/svdpage.hxx>
37#include <oox/ppt/pptimport.hxx>
39
43
44using namespace ::com::sun::star;
45
46namespace oox::drawingml {
47
48static 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)
86}
87
89static 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
108void 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
152uno::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
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 double nMinScale = 100.0;
190 for (const auto& rShapePair : rShapePairs)
191 {
192 uno::Reference<beans::XPropertySet> xPropertySet(rShapePair.second, uno::UNO_QUERY);
193 if (xPropertySet.is())
194 {
195 double nTextFitToSizeScale = 0.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.0)
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
222static 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
231static uno::Reference<xml::dom::XDocument> loadFragment(
232 core::XmlFilterBase& rFilter,
234{
235 return loadFragment( rFilter, rxHandler->getFragmentPath() );
236}
237
239 const uno::Reference<xml::dom::XDocument>& rXDom,
240 const OUString& rDocName,
241 const DiagramPtr& pDiagram,
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
254namespace
255{
260class DiagramShapeCounter : public oox::core::FragmentHandler2
261{
262public:
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
268private:
269 sal_Int32& m_nCounter;
270};
271
272DiagramShapeCounter::DiagramShapeCounter(oox::core::XmlFilterBase& rFilter,
273 const OUString& rFragmentPath, sal_Int32& nCounter)
274 : FragmentHandler2(rFilter, rFragmentPath)
275 , m_nCounter(nCounter)
276{
277}
278
279oox::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
298void 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
432DiagramColor::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: */
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:133
void setDiagramFontHeights(NamedShapePairs *pDiagramFontHeights)
virtual std::shared_ptr<::oox::drawingml::Theme > getCurrentThemePtr() const
May be implemented by filters which handle Diagrams, default returns empty ptr.
bool importFragment(const rtl::Reference< FragmentHandler > &rxHandler)
Imports a fragment using the passed fragment handler, which contains the full path to the fragment st...
css::uno::Sequence< css::uno::Sequence< css::uno::Any > > maDataRelsMap
void addTo(const ShapePtr &pShape)
Definition: diagram.cxx:108
const OoxDiagramDataPtr & getData() const
css::uno::Sequence< css::beans::PropertyValue > getDomsAsPropertyValues() const
Definition: diagram.cxx:152
sal_Int32 & m_nCounter
Definition: diagram.cxx:269
float u
sal_Int32 nIndex
uno_Any a
#define SAL_WARN(area, stream)
std::unique_ptr< sal_Int32[]> pData
OString generateGUIDString()
static uno::Reference< xml::dom::XDocument > loadFragment(core::XmlFilterBase &rFilter, const rtl::Reference< core::FragmentHandler > &rxHandler)
Definition: diagram.cxx:231
std::shared_ptr< Diagram > DiagramPtr
std::shared_ptr< DiagramData > OoxDiagramDataPtr
Definition: datamodel.hxx:75
std::shared_ptr< Shape > ShapePtr
static void removeUnneededGroupShapes(const ShapePtr &pShape)
Removes empty group shapes, now that their spacing influenced the layout.
Definition: diagram.cxx:89
static void sortChildrenByZOrder(const ShapePtr &pShape)
Definition: diagram.cxx:48
std::map< std::shared_ptr< drawingml::Shape >, css::uno::Reference< css::drawing::XShape > > ShapePairs
Definition: diagram.cxx:179
std::map< OUString, css::uno::Reference< css::xml::dom::XDocument > > DiagramDomMap
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::shared_ptr< DiagramLayout > DiagramLayoutPtr
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
static uno::Reference< xml::dom::XDocument > loadFragment(core::XmlFilterBase &rFilter, const OUString &rFragmentPath)
Definition: diagram.cxx:222