Coverage Report

Created: 2025-11-16 09:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/oox/source/drawingml/diagram/diagram.cxx
Line
Count
Source
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
#include <oox/ppt/pptimport.hxx>
38
#include <comphelper/xmltools.hxx>
39
40
#include "diagramlayoutatoms.hxx"
41
#include "layoutatomvisitors.hxx"
42
#include "diagramfragmenthandler.hxx"
43
44
using namespace ::com::sun::star;
45
46
namespace oox::drawingml {
47
48
static void sortChildrenByZOrder(const ShapePtr& pShape)
49
371
{
50
371
    std::vector<ShapePtr>& rChildren = pShape->getChildren();
51
52
    // Offset the children from their default z-order stacking, if necessary.
53
698
    for (size_t i = 0; i < rChildren.size(); ++i)
54
327
        rChildren[i]->setZOrder(i);
55
56
698
    for (size_t i = 0; i < rChildren.size(); ++i)
57
327
    {
58
327
        const ShapePtr& pChild = rChildren[i];
59
327
        sal_Int32 nZOrderOff = pChild->getZOrderOff();
60
327
        if (nZOrderOff <= 0)
61
327
            continue;
62
63
        // Increase my ZOrder by nZOrderOff.
64
0
        pChild->setZOrder(pChild->getZOrder() + nZOrderOff);
65
0
        pChild->setZOrderOff(0);
66
67
0
        for (sal_Int32 j = 0; j < nZOrderOff; ++j)
68
0
        {
69
0
            size_t nIndex = i + j + 1;
70
0
            if (nIndex >= rChildren.size())
71
0
                break;
72
73
            // Decrease the ZOrder of the next nZOrderOff elements by one.
74
0
            const ShapePtr& pNext = rChildren[nIndex];
75
0
            pNext->setZOrder(pNext->getZOrder() - 1);
76
0
        }
77
0
    }
78
79
    // Now that the ZOrders are adjusted, sort the children.
80
371
    std::sort(rChildren.begin(), rChildren.end(),
81
371
              [](const ShapePtr& a, const ShapePtr& b) { return a->getZOrder() < b->getZOrder(); });
82
83
    // Apply also for children.
84
371
    for (const auto& rChild : rChildren)
85
327
        sortChildrenByZOrder(rChild);
86
371
}
87
88
/// Removes empty group shapes, now that their spacing influenced the layout.
89
static void removeUnneededGroupShapes(const ShapePtr& pShape)
90
371
{
91
371
    std::vector<ShapePtr>& rChildren = pShape->getChildren();
92
93
371
    std::erase_if(rChildren,
94
371
                                   [](const ShapePtr& aChild) {
95
327
                                       return aChild->getServiceName()
96
327
                                                  == "com.sun.star.drawing.GroupShape"
97
45
                                              && aChild->getChildren().empty();
98
327
                                   });
99
100
371
    for (const auto& pChild : rChildren)
101
327
    {
102
327
        removeUnneededGroupShapes(pChild);
103
327
    }
104
371
}
105
106
107
void Diagram::addTo( const ShapePtr & pParentShape, bool bCreate )
108
135
{
109
135
    if (pParentShape->getSize().Width == 0 || pParentShape->getSize().Height == 0)
110
135
        SAL_WARN("oox.drawingml", "Diagram cannot be correctly laid out. Size: "
111
135
            << pParentShape->getSize().Width << "x" << pParentShape->getSize().Height);
112
113
135
    pParentShape->setChildSize(pParentShape->getSize());
114
115
135
    const svx::diagram::Point* pRootPoint = mpData->getRootPoint();
116
135
    if (bCreate && mpLayout->getNode() && pRootPoint)
117
44
    {
118
        // create Shape hierarchy
119
44
        ShapeCreationVisitor aCreationVisitor(*this, pRootPoint, pParentShape);
120
44
        mpLayout->getNode()->setExistingShape(pParentShape);
121
44
        mpLayout->getNode()->accept(aCreationVisitor);
122
123
        // layout shapes - now all shapes are created
124
44
        ShapeLayoutingVisitor aLayoutingVisitor(*this, pRootPoint);
125
44
        mpLayout->getNode()->accept(aLayoutingVisitor);
126
127
44
        sortChildrenByZOrder(pParentShape);
128
44
        removeUnneededGroupShapes(pParentShape);
129
44
    }
130
131
135
    ShapePtr pBackground = std::make_shared<Shape>("com.sun.star.drawing.CustomShape");
132
135
    pBackground->setSubType(XML_rect);
133
135
    pBackground->getCustomShapeProperties()->setShapePresetType(XML_rect);
134
135
    pBackground->setSize(pParentShape->getSize());
135
135
    pBackground->getFillProperties() = *mpData->getBackgroundShapeFillProperties();
136
135
    pBackground->setLocked(true);
137
138
    // create and set ModelID for BackgroundShape to allow later association
139
135
    getData()->setBackgroundShapeModelID(OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8));
140
135
    pBackground->setDiagramDataModelID(getData()->getBackgroundShapeModelID());
141
142
135
    auto& aChildren = pParentShape->getChildren();
143
135
    aChildren.insert(aChildren.begin(), pBackground);
144
135
}
145
146
Diagram::Diagram()
147
380
: maDiagramFontHeights()
148
380
{
149
380
}
150
151
uno::Sequence<beans::PropertyValue> Diagram::getDomsAsPropertyValues() const
152
135
{
153
135
    sal_Int32 length = maMainDomMap.size();
154
155
135
    if (maDataRelsMap.hasElements())
156
0
        ++length;
157
158
135
    uno::Sequence<beans::PropertyValue> aValue(length);
159
135
    beans::PropertyValue* pValue = aValue.getArray();
160
135
    for (auto const& mainDom : maMainDomMap)
161
426
    {
162
426
        pValue->Name = mainDom.first;
163
426
        pValue->Value <<= mainDom.second;
164
426
        ++pValue;
165
426
    }
166
167
135
    if (maDataRelsMap.hasElements())
168
0
    {
169
0
        pValue->Name = "OOXDiagramDataRels";
170
0
        pValue->Value <<= maDataRelsMap;
171
0
        ++pValue;
172
0
    }
173
174
135
    return aValue;
175
135
}
176
177
using ShapePairs
178
    = std::map<std::shared_ptr<drawingml::Shape>, css::uno::Reference<css::drawing::XShape>>;
179
180
void Diagram::syncDiagramFontHeights()
181
135
{
182
    // Each name represents a group of shapes, for which the font height should have the same
183
    // scaling.
184
135
    for (const auto& rNameAndPairs : maDiagramFontHeights)
185
7
    {
186
        // Find out the minimum scale within this group.
187
7
        const ShapePairs& rShapePairs = rNameAndPairs.second;
188
7
        double fMinFontScale = 100.0;
189
7
        double fMinSpacingScale = 100.0;
190
7
        for (const auto& rShapePair : rShapePairs)
191
0
        {
192
0
            uno::Reference<beans::XPropertySet> xPropertySet(rShapePair.second, uno::UNO_QUERY);
193
0
            if (xPropertySet.is())
194
0
            {
195
0
                double fFontScale = 0.0;
196
0
                double fSpacingScale = 0.0;
197
0
                xPropertySet->getPropertyValue(u"TextFitToSizeFontScale"_ustr) >>= fFontScale;
198
0
                xPropertySet->getPropertyValue(u"TextFitToSizeSpacingScale"_ustr) >>= fSpacingScale;
199
200
0
                if (fFontScale > 0 && fSpacingScale > 0
201
0
                    && (fFontScale < fMinFontScale || (fFontScale == fMinFontScale && fSpacingScale < fMinSpacingScale)))
202
0
                {
203
0
                    fMinFontScale = fFontScale;
204
0
                    fMinSpacingScale = fSpacingScale;
205
0
                }
206
0
            }
207
0
        }
208
209
        // Set that minimum scale for all members of the group.
210
7
        if (fMinFontScale < 100.0 || fMinSpacingScale < 100.0)
211
0
        {
212
0
            for (const auto& rShapePair : rShapePairs)
213
0
            {
214
0
                uno::Reference<beans::XPropertySet> xPropertySet(rShapePair.second, uno::UNO_QUERY);
215
0
                if (xPropertySet.is())
216
0
                {
217
0
                    xPropertySet->setPropertyValue(u"TextFitToSizeFontScale"_ustr, uno::Any(fMinFontScale));
218
0
                    xPropertySet->setPropertyValue(u"TextFitToSizeSpacingScale"_ustr, uno::Any(fMinSpacingScale));
219
0
                }
220
0
            }
221
0
        }
222
7
    }
223
224
    // no longer needed after processing
225
135
    maDiagramFontHeights.clear();
226
135
}
227
228
static uno::Reference<xml::dom::XDocument> loadFragment(
229
    core::XmlFilterBase& rFilter,
230
    const OUString& rFragmentPath )
231
1.08k
{
232
    // load diagramming fragments into DOM representation, that later
233
    // gets serialized back to SAX events and parsed
234
1.08k
    return rFilter.importFragment( rFragmentPath );
235
1.08k
}
236
237
static uno::Reference<xml::dom::XDocument> loadFragment(
238
    core::XmlFilterBase& rFilter,
239
    const rtl::Reference< core::FragmentHandler >& rxHandler )
240
1.08k
{
241
1.08k
    return loadFragment( rFilter, rxHandler->getFragmentPath() );
242
1.08k
}
243
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 )
249
1.08k
{
250
1.08k
    DiagramDomMap& rMainDomMap = pDiagram->getDomMap();
251
1.08k
    rMainDomMap[rDocName] = rXDom;
252
253
1.08k
    uno::Reference<xml::sax::XFastSAXSerializable> xSerializer(
254
1.08k
        rXDom, uno::UNO_QUERY_THROW);
255
256
    // now serialize DOM tree into internal data structures
257
1.08k
    rFilter.importFragment( rxHandler, xSerializer );
258
1.08k
}
259
260
namespace
261
{
262
/**
263
 * A fragment handler that just counts the number of <dsp:sp> elements in a
264
 * fragment.
265
 */
266
class DiagramShapeCounter : public oox::core::FragmentHandler2
267
{
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;
273
274
private:
275
    sal_Int32& m_nCounter;
276
};
277
278
DiagramShapeCounter::DiagramShapeCounter(oox::core::XmlFilterBase& rFilter,
279
                                         const OUString& rFragmentPath, sal_Int32& nCounter)
280
305
    : FragmentHandler2(rFilter, rFragmentPath)
281
305
    , m_nCounter(nCounter)
282
305
{
283
305
}
284
285
oox::core::ContextHandlerRef DiagramShapeCounter::onCreateContext(sal_Int32 nElement,
286
                                                                  const AttributeList& /*rAttribs*/)
287
1.18k
{
288
1.18k
    switch (nElement)
289
1.18k
    {
290
153
        case DSP_TOKEN(drawing):
291
306
        case DSP_TOKEN(spTree):
292
306
            return this;
293
637
        case DSP_TOKEN(sp):
294
637
            ++m_nCounter;
295
637
            break;
296
242
        default:
297
242
            break;
298
1.18k
    }
299
300
879
    return nullptr;
301
1.18k
}
302
}
303
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 )
311
380
{
312
380
    DiagramPtr pDiagram = std::make_shared<Diagram>();
313
314
380
    OoxDiagramDataPtr pData = std::make_shared<DiagramData>();
315
380
    pDiagram->setData( pData );
316
317
380
    DiagramLayoutPtr pLayout = std::make_shared<DiagramLayout>(*pDiagram);
318
380
    pDiagram->setLayout( pLayout );
319
320
380
    try
321
380
    {
322
        // set DiagramFontHeights at filter
323
380
        rFilter.setDiagramFontHeights(&pDiagram->getDiagramFontHeights());
324
325
        // data
326
380
        if( !rDataModelPath.isEmpty() )
327
377
        {
328
377
            rtl::Reference< core::FragmentHandler > xRefDataModel(
329
377
                    new DiagramDataFragmentHandler( rFilter, rDataModelPath, pData ));
330
331
377
            importFragment(rFilter,
332
377
                           loadFragment(rFilter,xRefDataModel),
333
377
                           u"OOXData"_ustr,
334
377
                           pDiagram,
335
377
                           xRefDataModel);
336
337
377
            pDiagram->getDataRelsMap() = pShape->resolveRelationshipsOfTypeFromOfficeDoc( rFilter,
338
377
                    xRefDataModel->getFragmentPath(), u"image" );
339
340
            // Pass the info to pShape
341
377
            for (auto const& extDrawing : pData->getExtDrawings())
342
305
            {
343
305
                OUString aFragmentPath = rRelations.getFragmentPathFromRelId(extDrawing);
344
                // Ignore RelIds which don't resolve to a fragment path.
345
305
                if (aFragmentPath.isEmpty())
346
0
                    continue;
347
348
305
                sal_Int32 nCounter = 0;
349
305
                rtl::Reference<core::FragmentHandler> xCounter(
350
305
                    new DiagramShapeCounter(rFilter, aFragmentPath, nCounter));
351
305
                rFilter.importFragment(xCounter);
352
                // Ignore ext drawings which don't actually have any shapes.
353
305
                if (nCounter == 0)
354
152
                    continue;
355
356
153
                pShape->addExtDrawingRelId(extDrawing);
357
153
            }
358
377
        }
359
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
380
        if (!rLayoutPath.isEmpty())
370
315
        {
371
315
            rtl::Reference< core::FragmentHandler > xRefLayout(
372
315
                    new DiagramLayoutFragmentHandler( rFilter, rLayoutPath,
373
315
                                                      std::move(pLayout) ));
374
375
315
            importFragment(rFilter,
376
315
                    loadFragment(rFilter,xRefLayout),
377
315
                    u"OOXLayout"_ustr,
378
315
                    pDiagram,
379
315
                    xRefLayout);
380
315
        }
381
382
        // Style: same as for Layout (above)
383
380
        if( !rQStylePath.isEmpty() )
384
238
        {
385
238
            rtl::Reference< core::FragmentHandler > xRefQStyle(
386
238
                    new DiagramQStylesFragmentHandler( rFilter, rQStylePath, pDiagram->getStyles() ));
387
388
238
            importFragment(rFilter,
389
238
                    loadFragment(rFilter,xRefQStyle),
390
238
                    u"OOXStyle"_ustr,
391
238
                    pDiagram,
392
238
                    xRefQStyle);
393
238
        }
394
395
        // colors
396
380
        if( !rColorStylePath.isEmpty() )
397
151
        {
398
151
            rtl::Reference< core::FragmentHandler > xRefColorStyle(
399
151
                new ColorFragmentHandler( rFilter, rColorStylePath, pDiagram->getColors() ));
400
401
151
            importFragment(rFilter,
402
151
                loadFragment(rFilter,xRefColorStyle),
403
151
                u"OOXColor"_ustr,
404
151
                pDiagram,
405
151
                xRefColorStyle);
406
151
        }
407
408
380
        if( !pData->getExtDrawings().empty() )
409
125
        {
410
125
            const DiagramColorMap::const_iterator aColor = pDiagram->getColors().find(u"node0"_ustr);
411
125
            if( aColor != pDiagram->getColors().end() && !aColor->second.maTextFillColors.empty())
412
0
            {
413
                // TODO(F1): well, actually, there might be *several* color
414
                // definitions in it, after all it's called list.
415
0
                pShape->setFontRefColorForNodes(DiagramColor::getColorByIndex(aColor->second.maTextFillColors, -1));
416
0
            }
417
125
        }
418
419
        // collect data, init maps
420
        // for Diagram import, do - for now - NOT clear all oox::drawingml::Shape
421
380
        pData->buildDiagramDataModel(false);
422
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
380
        const bool bCreate(pShape->getExtDrawings().empty());
432
380
        pDiagram->addTo(pShape, bCreate);
433
380
        pShape->setDiagramDoms(pDiagram->getDomsAsPropertyValues());
434
435
        // Get the oox::Theme definition and - if available - move/secure the
436
        // original ImportData directly to the Diagram ModelData
437
380
        std::shared_ptr<::oox::drawingml::Theme> aTheme(rFilter.getCurrentThemePtr());
438
380
        if(aTheme)
439
11
            pData->setThemeDocument(aTheme->getFragment()); //getTempFile());
440
441
        // Prepare support for the advanced DiagramHelper using Diagram & Theme data
442
380
        pShape->prepareDiagramHelper(pDiagram, rFilter.getCurrentThemePtr(), bCreate);
443
380
    }
444
380
    catch (...)
445
380
    {
446
        // unset DiagramFontHeights at filter if there was a failure
447
        // to avoid dangling pointer
448
245
        rFilter.setDiagramFontHeights(nullptr);
449
245
        throw;
450
245
    }
451
380
}
452
453
const oox::drawingml::Color&
454
DiagramColor::getColorByIndex(const std::vector<oox::drawingml::Color>& rColors, sal_Int32 nIndex)
455
618
{
456
618
    assert(!rColors.empty());
457
618
    if (nIndex == -1)
458
0
    {
459
0
        return rColors[rColors.size() - 1];
460
0
    }
461
462
618
    return rColors[nIndex % rColors.size()];
463
618
}
464
}
465
466
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */