use insert function instead of for loop
[LibreOffice.git] / oox / source / drawingml / diagram / diagram.cxx
blob3928e82767ededfe05511d71ff9736d054518d8d
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
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 #include <oox/ppt/pptimport.hxx>
38 #include <comphelper/xmltools.hxx>
40 #include "diagramlayoutatoms.hxx"
41 #include "layoutatomvisitors.hxx"
42 #include "diagramfragmenthandler.hxx"
44 using namespace ::com::sun::star;
46 namespace oox::drawingml {
48 static void sortChildrenByZOrder(const ShapePtr& pShape)
50 std::vector<ShapePtr>& rChildren = pShape->getChildren();
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);
56 for (size_t i = 0; i < rChildren.size(); ++i)
58 const ShapePtr& pChild = rChildren[i];
59 sal_Int32 nZOrderOff = pChild->getZOrderOff();
60 if (nZOrderOff <= 0)
61 continue;
63 // Increase my ZOrder by nZOrderOff.
64 pChild->setZOrder(pChild->getZOrder() + nZOrderOff);
65 pChild->setZOrderOff(0);
67 for (sal_Int32 j = 0; j < nZOrderOff; ++j)
69 size_t nIndex = i + j + 1;
70 if (nIndex >= rChildren.size())
71 break;
73 // Decrease the ZOrder of the next nZOrderOff elements by one.
74 const ShapePtr& pNext = rChildren[nIndex];
75 pNext->setZOrder(pNext->getZOrder() - 1);
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(); });
83 // Apply also for children.
84 for (const auto& rChild : rChildren)
85 sortChildrenByZOrder(rChild);
88 /// Removes empty group shapes, now that their spacing influenced the layout.
89 static void removeUnneededGroupShapes(const ShapePtr& pShape)
91 std::vector<ShapePtr>& rChildren = pShape->getChildren();
93 std::erase_if(rChildren,
94 [](const ShapePtr& aChild) {
95 return aChild->getServiceName()
96 == "com.sun.star.drawing.GroupShape"
97 && aChild->getChildren().empty();
98 });
100 for (const auto& pChild : rChildren)
102 removeUnneededGroupShapes(pChild);
107 void Diagram::addTo( const ShapePtr & pParentShape, bool bCreate )
109 if (pParentShape->getSize().Width == 0 || pParentShape->getSize().Height == 0)
110 SAL_WARN("oox.drawingml", "Diagram cannot be correctly laid out. Size: "
111 << pParentShape->getSize().Width << "x" << pParentShape->getSize().Height);
113 pParentShape->setChildSize(pParentShape->getSize());
115 const svx::diagram::Point* pRootPoint = mpData->getRootPoint();
116 if (bCreate && mpLayout->getNode() && pRootPoint)
118 // create Shape hierarchy
119 ShapeCreationVisitor aCreationVisitor(*this, pRootPoint, pParentShape);
120 mpLayout->getNode()->setExistingShape(pParentShape);
121 mpLayout->getNode()->accept(aCreationVisitor);
123 // layout shapes - now all shapes are created
124 ShapeLayoutingVisitor aLayoutingVisitor(*this, pRootPoint);
125 mpLayout->getNode()->accept(aLayoutingVisitor);
127 sortChildrenByZOrder(pParentShape);
128 removeUnneededGroupShapes(pParentShape);
131 ShapePtr pBackground = std::make_shared<Shape>("com.sun.star.drawing.CustomShape");
132 pBackground->setSubType(XML_rect);
133 pBackground->getCustomShapeProperties()->setShapePresetType(XML_rect);
134 pBackground->setSize(pParentShape->getSize());
135 pBackground->getFillProperties() = *mpData->getBackgroundShapeFillProperties();
136 pBackground->setLocked(true);
138 // create and set ModelID for BackgroundShape to allow later association
139 getData()->setBackgroundShapeModelID(OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8));
140 pBackground->setDiagramDataModelID(getData()->getBackgroundShapeModelID());
142 auto& aChildren = pParentShape->getChildren();
143 aChildren.insert(aChildren.begin(), pBackground);
146 Diagram::Diagram()
147 : maDiagramFontHeights()
151 uno::Sequence<beans::PropertyValue> Diagram::getDomsAsPropertyValues() const
153 sal_Int32 length = maMainDomMap.size();
155 if (maDataRelsMap.hasElements())
156 ++length;
158 uno::Sequence<beans::PropertyValue> aValue(length);
159 beans::PropertyValue* pValue = aValue.getArray();
160 for (auto const& mainDom : maMainDomMap)
162 pValue->Name = mainDom.first;
163 pValue->Value <<= mainDom.second;
164 ++pValue;
167 if (maDataRelsMap.hasElements())
169 pValue->Name = "OOXDiagramDataRels";
170 pValue->Value <<= maDataRelsMap;
171 ++pValue;
174 return aValue;
177 using ShapePairs
178 = std::map<std::shared_ptr<drawingml::Shape>, css::uno::Reference<css::drawing::XShape>>;
180 void Diagram::syncDiagramFontHeights()
182 // Each name represents a group of shapes, for which the font height should have the same
183 // scaling.
184 for (const auto& rNameAndPairs : maDiagramFontHeights)
186 // Find out the minimum scale within this group.
187 const ShapePairs& rShapePairs = rNameAndPairs.second;
188 double fMinFontScale = 100.0;
189 double fMinSpacingScale = 100.0;
190 for (const auto& rShapePair : rShapePairs)
192 uno::Reference<beans::XPropertySet> xPropertySet(rShapePair.second, uno::UNO_QUERY);
193 if (xPropertySet.is())
195 double fFontScale = 0.0;
196 double fSpacingScale = 0.0;
197 xPropertySet->getPropertyValue(u"TextFitToSizeFontScale"_ustr) >>= fFontScale;
198 xPropertySet->getPropertyValue(u"TextFitToSizeSpacingScale"_ustr) >>= fSpacingScale;
200 if (fFontScale > 0 && fSpacingScale > 0
201 && (fFontScale < fMinFontScale || (fFontScale == fMinFontScale && fSpacingScale < fMinSpacingScale)))
203 fMinFontScale = fFontScale;
204 fMinSpacingScale = fSpacingScale;
209 // Set that minimum scale for all members of the group.
210 if (fMinFontScale < 100.0 || fMinSpacingScale < 100.0)
212 for (const auto& rShapePair : rShapePairs)
214 uno::Reference<beans::XPropertySet> xPropertySet(rShapePair.second, uno::UNO_QUERY);
215 if (xPropertySet.is())
217 xPropertySet->setPropertyValue(u"TextFitToSizeFontScale"_ustr, uno::Any(fMinFontScale));
218 xPropertySet->setPropertyValue(u"TextFitToSizeSpacingScale"_ustr, uno::Any(fMinSpacingScale));
224 // no longer needed after processing
225 maDiagramFontHeights.clear();
228 static uno::Reference<xml::dom::XDocument> loadFragment(
229 core::XmlFilterBase& rFilter,
230 const OUString& rFragmentPath )
232 // load diagramming fragments into DOM representation, that later
233 // gets serialized back to SAX events and parsed
234 return rFilter.importFragment( rFragmentPath );
237 static uno::Reference<xml::dom::XDocument> loadFragment(
238 core::XmlFilterBase& rFilter,
239 const rtl::Reference< core::FragmentHandler >& rxHandler )
241 return loadFragment( rFilter, rxHandler->getFragmentPath() );
244 static void importFragment( core::XmlFilterBase& rFilter,
245 const uno::Reference<xml::dom::XDocument>& rXDom,
246 const OUString& rDocName,
247 const DiagramPtr& pDiagram,
248 const rtl::Reference< core::FragmentHandler >& rxHandler )
250 DiagramDomMap& rMainDomMap = pDiagram->getDomMap();
251 rMainDomMap[rDocName] = rXDom;
253 uno::Reference<xml::sax::XFastSAXSerializable> xSerializer(
254 rXDom, uno::UNO_QUERY_THROW);
256 // now serialize DOM tree into internal data structures
257 rFilter.importFragment( rxHandler, xSerializer );
260 namespace
263 * A fragment handler that just counts the number of <dsp:sp> elements in a
264 * fragment.
266 class DiagramShapeCounter : public oox::core::FragmentHandler2
268 public:
269 DiagramShapeCounter(oox::core::XmlFilterBase& rFilter, const OUString& rFragmentPath,
270 sal_Int32& nCounter);
271 oox::core::ContextHandlerRef onCreateContext(sal_Int32 nElement,
272 const AttributeList& rAttribs) override;
274 private:
275 sal_Int32& m_nCounter;
278 DiagramShapeCounter::DiagramShapeCounter(oox::core::XmlFilterBase& rFilter,
279 const OUString& rFragmentPath, sal_Int32& nCounter)
280 : FragmentHandler2(rFilter, rFragmentPath)
281 , m_nCounter(nCounter)
285 oox::core::ContextHandlerRef DiagramShapeCounter::onCreateContext(sal_Int32 nElement,
286 const AttributeList& /*rAttribs*/)
288 switch (nElement)
290 case DSP_TOKEN(drawing):
291 case DSP_TOKEN(spTree):
292 return this;
293 case DSP_TOKEN(sp):
294 ++m_nCounter;
295 break;
296 default:
297 break;
300 return nullptr;
304 void loadDiagram( ShapePtr const & pShape,
305 core::XmlFilterBase& rFilter,
306 const OUString& rDataModelPath,
307 const OUString& rLayoutPath,
308 const OUString& rQStylePath,
309 const OUString& rColorStylePath,
310 const oox::core::Relations& rRelations )
312 DiagramPtr pDiagram = std::make_shared<Diagram>();
314 OoxDiagramDataPtr pData = std::make_shared<DiagramData>();
315 pDiagram->setData( pData );
317 DiagramLayoutPtr pLayout = std::make_shared<DiagramLayout>(*pDiagram);
318 pDiagram->setLayout( pLayout );
322 // set DiagramFontHeights at filter
323 rFilter.setDiagramFontHeights(&pDiagram->getDiagramFontHeights());
325 // data
326 if( !rDataModelPath.isEmpty() )
328 rtl::Reference< core::FragmentHandler > xRefDataModel(
329 new DiagramDataFragmentHandler( rFilter, rDataModelPath, pData ));
331 importFragment(rFilter,
332 loadFragment(rFilter,xRefDataModel),
333 u"OOXData"_ustr,
334 pDiagram,
335 xRefDataModel);
337 pDiagram->getDataRelsMap() = pShape->resolveRelationshipsOfTypeFromOfficeDoc( rFilter,
338 xRefDataModel->getFragmentPath(), u"image" );
340 // Pass the info to pShape
341 for (auto const& extDrawing : pData->getExtDrawings())
343 OUString aFragmentPath = rRelations.getFragmentPathFromRelId(extDrawing);
344 // Ignore RelIds which don't resolve to a fragment path.
345 if (aFragmentPath.isEmpty())
346 continue;
348 sal_Int32 nCounter = 0;
349 rtl::Reference<core::FragmentHandler> xCounter(
350 new DiagramShapeCounter(rFilter, aFragmentPath, nCounter));
351 rFilter.importFragment(xCounter);
352 // Ignore ext drawings which don't actually have any shapes.
353 if (nCounter == 0)
354 continue;
356 pShape->addExtDrawingRelId(extDrawing);
360 // Layout: always import to allow editing in the future. It's needed for
361 // AdvancedDiagramHelper::reLayout to re-create the oox::Shape(s) for the
362 // model. Without importing these the diagram model will be not complete.
363 // NOTE: This also adds the DomMaps to rMainDomMap, so the lines
364 // DiagramDomMap& rMainDomMap = pDiagram->getDomMap();
365 // rMainDomMap[u"OOXLayout"_ustr] = loadFragment(rFilter,rLayoutPath);
366 // rMainDomMap[u"OOXStyle"_ustr] = loadFragment(rFilter,rQStylePath);
367 // which were used before if !pShape->getExtDrawings().empty() are not
368 // needed
369 if (!rLayoutPath.isEmpty())
371 rtl::Reference< core::FragmentHandler > xRefLayout(
372 new DiagramLayoutFragmentHandler( rFilter, rLayoutPath,
373 std::move(pLayout) ));
375 importFragment(rFilter,
376 loadFragment(rFilter,xRefLayout),
377 u"OOXLayout"_ustr,
378 pDiagram,
379 xRefLayout);
382 // Style: same as for Layout (above)
383 if( !rQStylePath.isEmpty() )
385 rtl::Reference< core::FragmentHandler > xRefQStyle(
386 new DiagramQStylesFragmentHandler( rFilter, rQStylePath, pDiagram->getStyles() ));
388 importFragment(rFilter,
389 loadFragment(rFilter,xRefQStyle),
390 u"OOXStyle"_ustr,
391 pDiagram,
392 xRefQStyle);
395 // colors
396 if( !rColorStylePath.isEmpty() )
398 rtl::Reference< core::FragmentHandler > xRefColorStyle(
399 new ColorFragmentHandler( rFilter, rColorStylePath, pDiagram->getColors() ));
401 importFragment(rFilter,
402 loadFragment(rFilter,xRefColorStyle),
403 u"OOXColor"_ustr,
404 pDiagram,
405 xRefColorStyle);
408 if( !pData->getExtDrawings().empty() )
410 const DiagramColorMap::const_iterator aColor = pDiagram->getColors().find(u"node0"_ustr);
411 if( aColor != pDiagram->getColors().end() && !aColor->second.maTextFillColors.empty())
413 // TODO(F1): well, actually, there might be *several* color
414 // definitions in it, after all it's called list.
415 pShape->setFontRefColorForNodes(DiagramColor::getColorByIndex(aColor->second.maTextFillColors, -1));
419 // collect data, init maps
420 // for Diagram import, do - for now - NOT clear all oox::drawingml::Shape
421 pData->buildDiagramDataModel(false);
423 // diagram loaded. now lump together & attach to shape
424 // create own geometry if extLst is not present (no geometric
425 // representation is available in file). This will - if false -
426 // just create the BackgroundShape.
427 // NOTE: Need to use pShape->getExtDrawings() here, this is the
428 // already *filtered* version, see usage of DiagramShapeCounter
429 // above. Moving to local bool, there might more conditions show
430 // up
431 const bool bCreate(pShape->getExtDrawings().empty());
432 pDiagram->addTo(pShape, bCreate);
433 pShape->setDiagramDoms(pDiagram->getDomsAsPropertyValues());
435 // Get the oox::Theme definition and - if available - move/secure the
436 // original ImportData directly to the Diagram ModelData
437 std::shared_ptr<::oox::drawingml::Theme> aTheme(rFilter.getCurrentThemePtr());
438 if(aTheme)
439 pData->setThemeDocument(aTheme->getFragment()); //getTempFile());
441 // Prepare support for the advanced DiagramHelper using Diagram & Theme data
442 pShape->prepareDiagramHelper(pDiagram, rFilter.getCurrentThemePtr(), bCreate);
444 catch (...)
446 // unset DiagramFontHeights at filter if there was a failure
447 // to avoid dangling pointer
448 rFilter.setDiagramFontHeights(nullptr);
449 throw;
453 const oox::drawingml::Color&
454 DiagramColor::getColorByIndex(const std::vector<oox::drawingml::Color>& rColors, sal_Int32 nIndex)
456 assert(!rColors.empty());
457 if (nIndex == -1)
459 return rColors[rColors.size() - 1];
462 return rColors[nIndex % rColors.size()];
466 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */