Coverage Report

Created: 2026-04-09 11:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svgio/source/svgreader/svgsvgnode.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 <svgsvgnode.hxx>
21
#include <drawinglayer/geometry/viewinformation2d.hxx>
22
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
23
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
24
#include <basegfx/polygon/b2dpolygontools.hxx>
25
#include <basegfx/polygon/b2dpolygon.hxx>
26
#include <basegfx/matrix/b2dhommatrixtools.hxx>
27
#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
28
#include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx>
29
#include <o3tl/unit_conversion.hxx>
30
#include <svgdocument.hxx>
31
32
namespace svgio::svgreader
33
{
34
        SvgSvgNode::SvgSvgNode(
35
            SvgDocument& rDocument,
36
            SvgNode* pParent)
37
11.3k
        :   SvgNode(SVGToken::Svg, rDocument, pParent),
38
11.3k
            maSvgStyleAttributes(*this),
39
11.3k
            mbStyleAttributesInitialized(false) // #i125258#
40
11.3k
        {
41
11.3k
        }
42
43
        // #i125258#
44
        void SvgSvgNode::initializeStyleAttributes()
45
60
        {
46
60
            if(mbStyleAttributesInitialized)
47
0
                return;
48
49
            // #i125258# determine if initial values need to be initialized with hard values
50
            // for the case that this is the outmost SVG statement and it has no parent
51
            // stale (CssStyle for svg may be defined)
52
60
            bool bSetInitialValues(true);
53
54
60
            if(getParent())
55
0
            {
56
                // #i125258# no initial values when it's a SVG element embedded in SVG
57
0
                bSetInitialValues = false;
58
0
            }
59
60
60
            if(bSetInitialValues)
61
60
            {
62
60
                const SvgStyleAttributes* pStyles = getSvgStyleAttributes();
63
64
60
                if(pStyles && pStyles->getCssStyleOrParentStyle())
65
0
                {
66
                    // SVG has a parent style (probably CssStyle), check if fill is set there anywhere
67
                    // already. If yes, do not set the default fill (black)
68
0
                    bool bFillSet(false);
69
0
                    const SvgStyleAttributes* pParentStyle = pStyles->getCssStyleOrParentStyle();
70
71
0
                    while(pParentStyle && !bFillSet)
72
0
                    {
73
0
                        bFillSet = pParentStyle->isFillSet();
74
0
                        pParentStyle = pParentStyle->getCssStyleOrParentStyle();
75
0
                    }
76
77
0
                    if(bFillSet)
78
0
                    {
79
                        // #125258# no initial values when SVG has a parent style at which a fill
80
                        // is already set
81
0
                        bSetInitialValues = false;
82
0
                    }
83
0
                }
84
60
            }
85
86
60
            if(bSetInitialValues)
87
60
            {
88
                // #i125258# only set if not yet initialized (SvgSvgNode::parseAttribute is already done,
89
                // just setting may revert an already set valid value)
90
60
                if(!maSvgStyleAttributes.isFillSet())
91
60
                {
92
                    // #i125258# initial fill is black (see SVG1.1 spec)
93
60
                    maSvgStyleAttributes.setFill(SvgPaint(basegfx::BColor(0.0, 0.0, 0.0), true, true));
94
60
                }
95
60
            }
96
97
60
            mbStyleAttributesInitialized = true;
98
60
        }
99
100
        SvgSvgNode::~SvgSvgNode()
101
11.3k
        {
102
11.3k
        }
103
104
        const SvgStyleAttributes* SvgSvgNode::getSvgStyleAttributes() const
105
60
        {
106
            // #i125258# svg node can have CssStyles, too, so check for it here
107
60
            return checkForCssStyle(maSvgStyleAttributes);
108
60
        }
109
110
        void SvgSvgNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
111
0
        {
112
            // call parent
113
0
            SvgNode::parseAttribute(aSVGToken, aContent);
114
115
            // read style attributes
116
0
            maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
117
118
            // parse own
119
0
            switch(aSVGToken)
120
0
            {
121
0
                case SVGToken::Style:
122
0
                {
123
0
                    readLocalCssStyle(aContent);
124
0
                    break;
125
0
                }
126
0
                case SVGToken::ViewBox:
127
0
                {
128
0
                    const basegfx::B2DRange aRange(readViewBox(aContent, *this));
129
130
0
                    if(!aRange.isEmpty())
131
0
                    {
132
0
                        setViewBox(&aRange);
133
0
                    }
134
0
                    break;
135
0
                }
136
0
                case SVGToken::PreserveAspectRatio:
137
0
                {
138
0
                    maSvgAspectRatio = readSvgAspectRatio(aContent);
139
0
                    break;
140
0
                }
141
0
                case SVGToken::X:
142
0
                {
143
0
                    SvgNumber aNum;
144
145
0
                    if(readSingleNumber(aContent, aNum))
146
0
                    {
147
0
                        maX = aNum;
148
0
                    }
149
0
                    break;
150
0
                }
151
0
                case SVGToken::Y:
152
0
                {
153
0
                    SvgNumber aNum;
154
155
0
                    if(readSingleNumber(aContent, aNum))
156
0
                    {
157
0
                        maY = aNum;
158
0
                    }
159
0
                    break;
160
0
                }
161
0
                case SVGToken::Width:
162
0
                {
163
0
                    SvgNumber aNum;
164
165
0
                    if(readSingleNumber(aContent, aNum))
166
0
                    {
167
0
                        if(aNum.isPositive())
168
0
                        {
169
0
                            maWidth = aNum;
170
0
                        }
171
0
                    }
172
0
                    break;
173
0
                }
174
0
                case SVGToken::Height:
175
0
                {
176
0
                    SvgNumber aNum;
177
178
0
                    if(readSingleNumber(aContent, aNum))
179
0
                    {
180
0
                        if(aNum.isPositive())
181
0
                        {
182
0
                            maHeight = aNum;
183
0
                        }
184
0
                    }
185
0
                    break;
186
0
                }
187
0
                case SVGToken::Version:
188
0
                {
189
0
                    SvgNumber aNum;
190
191
0
                    if(readSingleNumber(aContent, aNum))
192
0
                    {
193
0
                        maVersion = aNum;
194
0
                    }
195
0
                    break;
196
0
                }
197
0
                default:
198
0
                {
199
0
                    break;
200
0
                }
201
0
            }
202
0
        }
203
204
        void SvgSvgNode::seekReferenceWidth(double& fWidth, bool& bHasFound) const
205
0
        {
206
0
            if (!getParent() || bHasFound)
207
0
            {
208
0
                return;
209
0
            }
210
0
            const SvgSvgNode* pParentSvgSvgNode = nullptr;
211
            // enclosing svg might have relative width, need to cumulate them till they are
212
            // resolved somewhere up in the node tree
213
0
            double fPercentage(1.0);
214
0
            for(const SvgNode* pParent = getParent(); pParent && !bHasFound; pParent = pParent->getParent())
215
0
            {
216
                // dynamic_cast results Null-pointer for not SvgSvgNode and so skips them in if condition
217
0
                pParentSvgSvgNode = dynamic_cast< const SvgSvgNode* >(pParent);
218
0
                if (pParentSvgSvgNode)
219
0
                {
220
0
                    if (pParentSvgSvgNode->getViewBox())
221
0
                    {
222
                        // viewbox values are already in 'user unit'.
223
0
                        fWidth = pParentSvgSvgNode->getViewBox()->getWidth() * fPercentage;
224
0
                        bHasFound = true;
225
0
                    }
226
0
                    else
227
0
                    {
228
                        // take absolute value or cumulate percentage
229
0
                        if (pParentSvgSvgNode->getWidth().isSet())
230
0
                        {
231
0
                            if (SvgUnit::percent == pParentSvgSvgNode->getWidth().getUnit())
232
0
                            {
233
0
                                fPercentage *= pParentSvgSvgNode->getWidth().getNumber() * 0.01;
234
0
                            }
235
0
                            else
236
0
                            {
237
0
                                fWidth = pParentSvgSvgNode->getWidth().solveNonPercentage(*pParentSvgSvgNode) * fPercentage;
238
0
                                bHasFound = true;
239
0
                            }
240
0
                        } // not set => width=100% => factor 1, no need for else
241
0
                    }
242
0
                }
243
0
            }
244
0
        }
245
246
        void SvgSvgNode::seekReferenceHeight(double& fHeight, bool& bHasFound) const
247
0
        {
248
0
            if (!getParent() || bHasFound)
249
0
            {
250
0
                return;
251
0
            }
252
0
            const SvgSvgNode* pParentSvgSvgNode = nullptr;
253
            // enclosing svg might have relative width and height, need to cumulate them till they are
254
            // resolved somewhere up in the node tree
255
0
            double fPercentage(1.0);
256
0
            for(const SvgNode* pParent = getParent(); pParent && !bHasFound; pParent = pParent->getParent())
257
0
            {
258
                // dynamic_cast results Null-pointer for not SvgSvgNode and so skips them in if condition
259
0
                pParentSvgSvgNode = dynamic_cast< const SvgSvgNode* >(pParent);
260
0
                if (pParentSvgSvgNode)
261
0
                {
262
0
                    if (pParentSvgSvgNode->getViewBox())
263
0
                    {
264
                        // viewbox values are already in 'user unit'.
265
0
                        fHeight = pParentSvgSvgNode->getViewBox()->getHeight() * fPercentage;
266
0
                        bHasFound = true;
267
0
                    }
268
0
                    else
269
0
                    {
270
                        // take absolute value or cumulate percentage
271
0
                        if (pParentSvgSvgNode->getHeight().isSet())
272
0
                        {
273
0
                            if (SvgUnit::percent == pParentSvgSvgNode->getHeight().getUnit())
274
0
                            {
275
0
                                fPercentage *= pParentSvgSvgNode->getHeight().getNumber() * 0.01;
276
0
                            }
277
0
                            else
278
0
                            {
279
0
                                fHeight = pParentSvgSvgNode->getHeight().solveNonPercentage(*pParentSvgSvgNode) * fPercentage;
280
0
                                bHasFound = true;
281
0
                            }
282
0
                        } // not set => height=100% => factor 1, no need for else
283
0
                    }
284
0
                }
285
0
            }
286
0
        }
287
288
// ToDo: Consider attribute overflow in method decomposeSvgNode
289
        void SvgSvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const
290
60
        {
291
60
            drawinglayer::primitive2d::Primitive2DContainer aSequence;
292
293
            // #i125258# check now if we need to init some style settings locally. Do not do this
294
            // in the constructor, there is not yet information e.g. about existing CssStyles.
295
            // Here all nodes are read and interpreted
296
60
            const_cast< SvgSvgNode* >(this)->initializeStyleAttributes();
297
298
            // decompose children
299
60
            SvgNode::decomposeSvgNode(aSequence, bReferenced);
300
301
60
            if(!aSequence.empty())
302
0
            {
303
0
                if(getParent())
304
0
                {
305
                    // #i122594# if width/height is not given, it's 100% (see 5.1.2 The 'svg' element in SVG1.1 spec).
306
                    // If it is relative, the question is to what. The previous implementation assumed relative to the
307
                    // local ViewBox which is implied by (4.2 Basic data types):
308
309
                    // "Note that the non-property <length> definition also allows a percentage unit identifier.
310
                    // The meaning of a percentage length value depends on the attribute for which the percentage
311
                    // length value has been specified. Two common cases are: (a) when a percentage length value
312
                    // represents a percentage of the viewport width or height (refer to the section that discusses
313
                    // units in general), and (b) when a percentage length value represents a percentage of the
314
                    // bounding box width or height on a given object (refer to the section that describes object
315
                    // bounding box units)."
316
317
                    // Comparisons with common browsers show that it's mostly interpreted relative to the viewport
318
                    // of the parent, and so does the new implementation.
319
320
                    // Extract known viewport data
321
                    // bXXXIsAbsolute tracks whether relative values could be resolved to absolute values
322
323
                    // If width or height is not provided, the default 100% is used, see SVG 1.1 section 5.1.2
324
                    // value 0.0 here is only to initialize variable
325
0
                    bool bWidthIsAbsolute(getWidth().isSet() && SvgUnit::percent != getWidth().getUnit());
326
0
                    double fW( bWidthIsAbsolute ? getWidth().solveNonPercentage(*this) : 0.0);
327
328
0
                    bool bHeightIsAbsolute(getHeight().isSet() && SvgUnit::percent != getHeight().getUnit());
329
0
                    double fH( bHeightIsAbsolute ? getHeight().solveNonPercentage(*this) : 0.0);
330
331
                    // If x or y not provided, then default 0.0 is used, see SVG 1.1 Section 5.1.2
332
0
                    bool bXIsAbsolute((getX().isSet() && SvgUnit::percent != getX().getUnit()) || !getX().isSet());
333
0
                    double fX( bXIsAbsolute && getX().isSet() ? getX().solveNonPercentage(*this) : 0.0);
334
335
0
                    bool bYIsAbsolute((getY().isSet() && SvgUnit::percent != getY().getUnit()) || !getY().isSet());
336
0
                    double fY( bYIsAbsolute && getY().isSet() ? getY().solveNonPercentage(*this) : 0.0);
337
338
0
                    if ( !bXIsAbsolute || !bWidthIsAbsolute)
339
0
                    {
340
                        // get width of enclosing svg and resolve percentage in x and width;
341
0
                        double fWReference(0.0);
342
0
                        bool bHasFoundWidth(false);
343
0
                        seekReferenceWidth(fWReference, bHasFoundWidth);
344
0
                        if (!bHasFoundWidth)
345
0
                        {
346
0
                            if (getViewBox())
347
0
                            {
348
0
                                fWReference = getViewBox()->getWidth();
349
0
                            }
350
0
                            else
351
0
                            {
352
                                // Even outermost svg has not all information to resolve relative values,
353
                                // I use content itself as fallback to set missing values for viewport
354
                                // Any better idea for such ill structured svg documents?
355
0
                                const basegfx::B2DRange aChildRange(
356
0
                                            aSequence.getB2DRange(
357
0
                                                drawinglayer::geometry::ViewInformation2D()));
358
0
                                fWReference = aChildRange.getWidth();
359
0
                            }
360
0
                        }
361
                        // referenced values are already in 'user unit'
362
0
                        if (!bXIsAbsolute)
363
0
                        {
364
0
                            fX = getX().getNumber() * 0.01 * fWReference;
365
0
                        }
366
0
                        if (!bWidthIsAbsolute)
367
0
                        {
368
0
                            fW = (getWidth().isSet() ? getWidth().getNumber() *0.01 : 1.0) * fWReference;
369
0
                        }
370
0
                    }
371
372
0
                    if ( !bYIsAbsolute || !bHeightIsAbsolute)
373
0
                    {
374
                        // get height of enclosing svg and resolve percentage in y and height
375
0
                        double fHReference(0.0);
376
0
                        bool bHasFoundHeight(false);
377
0
                        seekReferenceHeight(fHReference, bHasFoundHeight);
378
0
                        if (!bHasFoundHeight)
379
0
                        {
380
0
                            if (getViewBox())
381
0
                            {
382
0
                                fHReference = getViewBox()->getHeight();
383
0
                            }
384
0
                            else
385
0
                            {
386
                            // Even outermost svg has not all information to resolve relative values,
387
                                // I use content itself as fallback to set missing values for viewport
388
                                // Any better idea for such ill structured svg documents?
389
0
                                const basegfx::B2DRange aChildRange(
390
0
                                        aSequence.getB2DRange(
391
0
                                            drawinglayer::geometry::ViewInformation2D()));
392
0
                                fHReference = aChildRange.getHeight();
393
0
                            }
394
0
                        }
395
396
                        // referenced values are already in 'user unit'
397
0
                        if (!bYIsAbsolute)
398
0
                        {
399
0
                            fY = getY().getNumber() * 0.01 * fHReference;
400
0
                        }
401
0
                        if (!bHeightIsAbsolute)
402
0
                        {
403
0
                            fH = (getHeight().isSet() ? getHeight().getNumber() *0.01 : 1.0) * fHReference;
404
0
                        }
405
0
                    }
406
407
0
                    if(getViewBox())
408
0
                    {
409
                        // SVG 1.1 defines in section 7.7 that a negative value for width or height
410
                        // in viewBox is an error and that 0.0 disables rendering
411
0
                        if (getViewBox()->getWidth() > 0.0 && getViewBox()->getHeight() > 0.0 && !basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight()))
412
0
                        {
413
                            // create target range homing x,y, width and height as calculated above
414
0
                            const basegfx::B2DRange aTarget(fX, fY, fX + fW, fY + fH);
415
416
0
                            if(aTarget.equal(*getViewBox()))
417
0
                            {
418
                                // no mapping needed, append
419
0
                                rTarget.append(aSequence);
420
0
                            }
421
0
                            else
422
0
                            {
423
                                // create mapping
424
0
                                const SvgAspectRatio& rRatio = getSvgAspectRatio();
425
426
                                // let mapping be created from SvgAspectRatio
427
0
                                const basegfx::B2DHomMatrix aEmbeddingTransform(
428
0
                                    rRatio.createMapping(aTarget, *getViewBox()));
429
430
                                // prepare embedding in transformation
431
0
                                const drawinglayer::primitive2d::Primitive2DReference xRef(
432
0
                                    new drawinglayer::primitive2d::TransformPrimitive2D(
433
0
                                        aEmbeddingTransform,
434
0
                                        drawinglayer::primitive2d::Primitive2DContainer(aSequence)));
435
436
0
                                if(rRatio.isMeetOrSlice())
437
0
                                {
438
                                    // embed in transformation
439
0
                                    rTarget.push_back(xRef);
440
0
                                }
441
0
                                else
442
0
                                {
443
                                    // need to embed in MaskPrimitive2D, too
444
0
                                    const drawinglayer::primitive2d::Primitive2DReference xMask(
445
0
                                        new drawinglayer::primitive2d::MaskPrimitive2D(
446
0
                                            basegfx::B2DPolyPolygon(basegfx::utils::createPolygonFromRect(aTarget)),
447
0
                                            drawinglayer::primitive2d::Primitive2DContainer { xRef }));
448
449
0
                                    rTarget.push_back(xMask);
450
0
                                }
451
0
                            }
452
0
                        }
453
0
                    }
454
0
                    else // no viewBox attribute
455
0
                    {
456
                        // Svg defines that a negative value is an error and that 0.0 disables rendering
457
0
                        if (fW > 0.0 && fH > 0.0 && !basegfx::fTools::equalZero(fW) && !basegfx::fTools::equalZero(fH))
458
0
                        {
459
0
                            if(!basegfx::fTools::equalZero(fX) || !basegfx::fTools::equalZero(fY))
460
0
                            {
461
                                // embed in transform
462
0
                                const drawinglayer::primitive2d::Primitive2DReference xRef(
463
0
                                    new drawinglayer::primitive2d::TransformPrimitive2D(
464
0
                                        basegfx::utils::createTranslateB2DHomMatrix(fX, fY),
465
0
                                        std::move(aSequence)));
466
467
0
                                aSequence = drawinglayer::primitive2d::Primitive2DContainer { xRef, };
468
0
                            }
469
470
                            // embed in MaskPrimitive2D to clip
471
0
                            const drawinglayer::primitive2d::Primitive2DReference xMask(
472
0
                                new drawinglayer::primitive2d::MaskPrimitive2D(
473
0
                                    basegfx::B2DPolyPolygon(
474
0
                                        basegfx::utils::createPolygonFromRect(
475
0
                                            basegfx::B2DRange(fX, fY, fX + fW, fY + fH))),
476
0
                                    drawinglayer::primitive2d::Primitive2DContainer(aSequence)));
477
478
                            // append
479
0
                            rTarget.push_back(xMask);
480
0
                        }
481
0
                    }
482
0
                }
483
0
                else // Outermost SVG element
484
0
                {
485
                    // Svg defines that a negative value is an error and that 0.0 disables rendering
486
                    // isPositive() not usable because it allows 0.0 in contrast to mathematical definition of 'positive'
487
0
                    const bool bWidthInvalid(getWidth().isSet() && getWidth().getNumber() <= 0.0);
488
0
                    const bool bHeightInvalid(getHeight().isSet() && getHeight().getNumber() <= 0.0);
489
0
                    if(!bWidthInvalid && !bHeightInvalid)
490
0
                    {
491
0
                        basegfx::B2DRange aSvgCanvasRange; // viewport
492
0
                        if (const basegfx::B2DRange* pBox = getViewBox())
493
0
                        {
494
                            // SVG 1.1 defines in section 7.7 that a negative value for width or height
495
                            // in viewBox is an error and that 0.0 disables rendering
496
0
                            const double fViewBoxWidth = pBox->getWidth();
497
0
                            const double fViewBoxHeight = pBox->getHeight();
498
0
                            if (fViewBoxWidth > 0.0 && fViewBoxHeight > 0.0 && !basegfx::fTools::equalZero(fViewBoxWidth) && !basegfx::fTools::equalZero(fViewBoxHeight))
499
0
                            {
500
                                // The intrinsic aspect ratio of the svg element is given by absolute values of svg width and svg height
501
                                // or by the width and height of the viewBox, if svg width or svg height is relative.
502
                                // see SVG 1.1 section 7.12
503
0
                                bool bNeedsMapping(true);
504
0
                                const bool bWidthIsAbsolute(getWidth().isSet() && SvgUnit::percent != getWidth().getUnit());
505
0
                                const bool bHeightIsAbsolute(getHeight().isSet() && SvgUnit::percent != getHeight().getUnit());
506
0
                                const double fViewBoxRatio(fViewBoxWidth/fViewBoxHeight);
507
0
                                if(bWidthIsAbsolute && bHeightIsAbsolute)
508
0
                                {
509
0
                                    double fW = getWidth().solveNonPercentage(*this);
510
0
                                    double fH = getHeight().solveNonPercentage(*this);
511
0
                                    aSvgCanvasRange = basegfx::B2DRange(0.0, 0.0, fW, fH);
512
0
                                }
513
0
                                else if (bWidthIsAbsolute)
514
0
                                {
515
0
                                    double fW = getWidth().solveNonPercentage(*this);
516
0
                                    double fH = fW / fViewBoxRatio;
517
0
                                    aSvgCanvasRange = basegfx::B2DRange(0.0, 0.0, fW, fH);
518
0
                                }
519
0
                                else if (bHeightIsAbsolute)
520
0
                                {
521
0
                                    double fH = getHeight().solveNonPercentage(*this);
522
0
                                    double fW = fH * fViewBoxRatio;
523
0
                                    aSvgCanvasRange = basegfx::B2DRange(0.0, 0.0, fW, fH);
524
0
                                }
525
0
                                else
526
0
                                {
527
                                    // There exists no parent to resolve relative width or height.
528
                                    // Use child size as fallback and expand to aspect ratio given
529
                                    // by the viewBox. No mapping.
530
                                    // We get viewport >= content, therefore no clipping.
531
0
                                    bNeedsMapping = false;
532
533
0
                                    const double fChildWidth(pBox->getWidth());
534
0
                                    const double fChildHeight(pBox->getHeight());
535
0
                                    const double fLeft(pBox->getMinX());
536
0
                                    const double fTop(pBox->getMinY());
537
0
                                    double fW, fH;
538
0
                                    if ( fChildWidth / fViewBoxWidth > fChildHeight / fViewBoxHeight )
539
0
                                    {  // expand y
540
0
                                        fW = fChildWidth;
541
0
                                        fH = fChildWidth / fViewBoxRatio;
542
0
                                    }
543
0
                                    else
544
0
                                    {  // expand x
545
0
                                        fH = fChildHeight;
546
0
                                        fW = fChildHeight * fViewBoxRatio;
547
0
                                    }
548
0
                                    aSvgCanvasRange = basegfx::B2DRange(fLeft, fTop, fLeft + fW, fTop + fH);
549
0
                                }
550
551
0
                                if (bNeedsMapping)
552
0
                                {
553
                                    // create mapping
554
                                    // SVG 1.1 defines in section 5.1.2 that if the attribute preserveAspectRatio is not specified,
555
                                    // then the effect is as if a value of 'xMidYMid meet' were specified.
556
0
                                    SvgAspectRatio aRatioDefault(SvgAlign::xMidYMid, true);
557
0
                                    const SvgAspectRatio& rRatio = getSvgAspectRatio().isSet()? getSvgAspectRatio() : aRatioDefault;
558
559
0
                                    basegfx::B2DHomMatrix aViewBoxMapping = rRatio.createMapping(aSvgCanvasRange, *pBox);
560
                                    // no need to check ratio here for slice, the outermost Svg will
561
                                    // be clipped anyways (see below)
562
563
                                    // scale content to viewBox definitions
564
0
                                    const drawinglayer::primitive2d::Primitive2DReference xTransform(
565
0
                                        new drawinglayer::primitive2d::TransformPrimitive2D(
566
0
                                            aViewBoxMapping,
567
0
                                            std::move(aSequence)));
568
569
0
                                    aSequence = drawinglayer::primitive2d::Primitive2DContainer { xTransform };
570
0
                                }
571
0
                            }
572
0
                        }
573
0
                        else // no viewbox => no mapping
574
0
                        {
575
0
                            const bool bWidthIsAbsolute(getWidth().isSet() && SvgUnit::percent != getWidth().getUnit());
576
0
                            const bool bHeightIsAbsolute(getHeight().isSet() && SvgUnit::percent != getHeight().getUnit());
577
0
                            if (bWidthIsAbsolute && bHeightIsAbsolute)
578
0
                            {
579
0
                                double fW = getWidth().solveNonPercentage(*this);
580
0
                                double fH = getHeight().solveNonPercentage(*this);
581
0
                                aSvgCanvasRange = basegfx::B2DRange(0.0, 0.0, fW, fH);
582
0
                            }
583
0
                            else
584
0
                            {
585
                                // There exists no parent to resolve relative width or height.
586
                                // Use child size as fallback. We get viewport >= content, therefore no clipping.
587
0
                                const basegfx::B2DRange aChildRange(
588
0
                                     aSequence.getB2DRange(
589
0
                                         drawinglayer::geometry::ViewInformation2D()));
590
0
                                const double fChildWidth(aChildRange.getWidth());
591
0
                                const double fChildHeight(aChildRange.getHeight());
592
0
                                const double fChildLeft(aChildRange.getMinX());
593
0
                                const double fChildTop(aChildRange.getMinY());
594
0
                                double fW = bWidthIsAbsolute ? getWidth().solveNonPercentage(*this) : fChildWidth;
595
0
                                double fH = bHeightIsAbsolute ? getHeight().solveNonPercentage(*this) : fChildHeight;
596
0
                                const double fLeft(bWidthIsAbsolute ? 0.0 : fChildLeft);
597
0
                                const double fTop(bHeightIsAbsolute ? 0.0 : fChildTop);
598
0
                                aSvgCanvasRange = basegfx::B2DRange(fLeft, fTop, fLeft+fW, fTop+fH);
599
0
                            }
600
601
0
                        }
602
603
                        // to be completely correct in Svg sense it is necessary to clip
604
                        // the whole content to the given canvas. I choose here to do this
605
                        // initially despite I found various examples of Svg files out there
606
                        // which have no correct values for this clipping. It's correct
607
                        // due to the Svg spec.
608
609
                        // different from Svg we have the possibility with primitives to get
610
                        // a correct bounding box for the geometry. Get it for evtl. taking action
611
0
                        const basegfx::B2DRange aContentRange(
612
0
                            aSequence.getB2DRange(
613
0
                                drawinglayer::geometry::ViewInformation2D()));
614
615
0
                        if(aSvgCanvasRange.isInside(aContentRange))
616
0
                        {
617
                            // no clip needed, but an invisible HiddenGeometryPrimitive2D
618
                            // to allow getting the full Svg range using the primitive mechanisms.
619
                            // This is needed since e.g. an SdrObject using this as graphic will
620
                            // create a mapping transformation to exactly map the content to its
621
                            // real life size
622
0
                            const drawinglayer::primitive2d::Primitive2DReference xLine(
623
0
                                new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(
624
0
                                    basegfx::utils::createPolygonFromRect(
625
0
                                        aSvgCanvasRange),
626
0
                                    basegfx::BColor(0.0, 0.0, 0.0)));
627
0
                            const drawinglayer::primitive2d::Primitive2DReference xHidden(
628
0
                                new drawinglayer::primitive2d::HiddenGeometryPrimitive2D(
629
0
                                    drawinglayer::primitive2d::Primitive2DContainer { xLine }));
630
631
0
                            aSequence.push_back(xHidden);
632
0
                        }
633
0
                        else if(aSvgCanvasRange.overlaps(aContentRange))
634
0
                        {
635
                            // Clip is necessary. This will make Svg images evtl. smaller
636
                            // than wanted from Svg (the free space which may be around it is
637
                            // conform to the Svg spec), but avoids an expensive and unnecessary
638
                            // clip. Keep the full Svg range here to get the correct mappings
639
                            // to objects using this. Optimizations can be done in the processors
640
0
                            const drawinglayer::primitive2d::Primitive2DReference xMask(
641
0
                                new drawinglayer::primitive2d::MaskPrimitive2D(
642
0
                                    basegfx::B2DPolyPolygon(
643
0
                                        basegfx::utils::createPolygonFromRect(
644
0
                                            aSvgCanvasRange)),
645
0
                                    std::move(aSequence)));
646
647
0
                            aSequence = drawinglayer::primitive2d::Primitive2DContainer { xMask };
648
0
                        }
649
0
                        else
650
0
                        {
651
                            // not inside, no overlap. Empty Svg
652
0
                            aSequence.clear();
653
0
                        }
654
655
0
                        if(!aSequence.empty())
656
0
                        {
657
                            // Another correction:
658
                            // If no Width/Height is set (usually done in
659
                            // <svg ... width="215.9mm" height="279.4mm" >) which
660
                            // is the case for own-Impress-exports, assume that
661
                            // the Units are already 100ThMM.
662
                            // Maybe only for own-Impress-exports, thus may need to be
663
                            // &&ed with getDocument().findSvgNodeById("ooo:meta_slides"),
664
                            // but does not need to be.
665
0
                            bool bEmbedInFinalTransformPxTo100ThMM(true);
666
667
0
                            if(getDocument().findSvgNodeById(u"ooo:meta_slides"_ustr)
668
0
                                && !getWidth().isSet()
669
0
                                && !getHeight().isSet())
670
0
                            {
671
0
                                bEmbedInFinalTransformPxTo100ThMM = false;
672
0
                            }
673
674
0
                            if(bEmbedInFinalTransformPxTo100ThMM)
675
0
                            {
676
                                // embed in transform primitive to scale to 1/100th mm
677
                                // to get from Svg coordinates (px) to drawinglayer coordinates
678
0
                                constexpr double fScaleTo100thmm(o3tl::convert(1.0, o3tl::Length::px, o3tl::Length::mm100));
679
0
                                const basegfx::B2DHomMatrix aTransform(
680
0
                                    basegfx::utils::createScaleB2DHomMatrix(
681
0
                                        fScaleTo100thmm,
682
0
                                        fScaleTo100thmm));
683
684
0
                                const drawinglayer::primitive2d::Primitive2DReference xTransform(
685
0
                                    new drawinglayer::primitive2d::TransformPrimitive2D(
686
0
                                        aTransform,
687
0
                                        std::move(aSequence)));
688
689
0
                                aSequence = drawinglayer::primitive2d::Primitive2DContainer { xTransform };
690
0
                            }
691
692
                            // append to result
693
0
                            rTarget.append(aSequence);
694
0
                        }
695
0
                    }
696
0
                }
697
0
            }
698
699
60
            if(!(aSequence.empty() && !getParent() && getViewBox()))
700
60
                return;
701
702
            // tdf#118232 No geometry, Outermost SVG element and we have a ViewBox.
703
            // Create a HiddenGeometry Primitive containing an expanded
704
            // hairline geometry to have the size contained
705
0
            const drawinglayer::primitive2d::Primitive2DReference xLine(
706
0
                new drawinglayer::primitive2d::PolygonHairlinePrimitive2D(
707
0
                    basegfx::utils::createPolygonFromRect(
708
0
                        *getViewBox()),
709
0
                    basegfx::BColor(0.0, 0.0, 0.0)));
710
0
            const drawinglayer::primitive2d::Primitive2DReference xHidden(
711
0
                new drawinglayer::primitive2d::HiddenGeometryPrimitive2D(
712
0
                    drawinglayer::primitive2d::Primitive2DContainer { xLine }));
713
714
0
            rTarget.push_back(xHidden);
715
0
        }
716
717
        basegfx::B2DRange SvgSvgNode::getCurrentViewPort() const
718
0
        {
719
0
            if(getViewBox())
720
0
            {
721
0
                return *(getViewBox());
722
0
            }
723
0
            else // viewport should be given by x, y, width, and height
724
0
            {
725
                // Extract known viewport data
726
                // bXXXIsAbsolute tracks whether relative values could be resolved to absolute values
727
0
                if (getParent())
728
0
                {
729
                    // If width or height is not provided, the default 100% is used, see SVG 1.1 section 5.1.2
730
                    // value 0.0 here is only to initialize variable
731
0
                    bool bWidthIsAbsolute(getWidth().isSet() && SvgUnit::percent != getWidth().getUnit());
732
0
                    double fW( bWidthIsAbsolute ? getWidth().solveNonPercentage(*this) : 0.0);
733
0
                    bool bHeightIsAbsolute(getHeight().isSet() && SvgUnit::percent != getHeight().getUnit());
734
0
                    double fH( bHeightIsAbsolute ? getHeight().solveNonPercentage(*this) : 0.0);
735
736
                    // If x or y not provided, then default 0.0 is used, see SVG 1.1 Section 5.1.2
737
0
                    bool bXIsAbsolute((getX().isSet() && SvgUnit::percent != getX().getUnit()) || !getX().isSet());
738
0
                    double fX( bXIsAbsolute && getX().isSet() ? getX().solveNonPercentage(*this) : 0.0);
739
740
0
                    bool bYIsAbsolute((getY().isSet() && SvgUnit::percent != getY().getUnit()) || !getY().isSet());
741
0
                    double fY( bYIsAbsolute && getY().isSet() ? getY().solveNonPercentage(*this) : 0.0);
742
743
0
                    if (bXIsAbsolute && bYIsAbsolute && bWidthIsAbsolute && bHeightIsAbsolute)
744
0
                    {
745
0
                        return basegfx::B2DRange(fX, fY, fX+fW, fY+fH);
746
0
                    }
747
0
                    else // try to resolve relative values
748
0
                    {
749
0
                        if (!bXIsAbsolute || !bWidthIsAbsolute)
750
0
                        {
751
                            // get width of enclosing svg and resolve percentage in x and width
752
0
                            double fWReference(0.0);
753
0
                            bool bHasFoundWidth(false);
754
0
                            seekReferenceWidth(fWReference, bHasFoundWidth);
755
                            // referenced values are already in 'user unit'
756
0
                            if (!bXIsAbsolute && bHasFoundWidth)
757
0
                            {
758
0
                                fX = getX().getNumber() * 0.01 * fWReference;
759
0
                                bXIsAbsolute = true;
760
0
                            }
761
0
                            if (!bWidthIsAbsolute && bHasFoundWidth)
762
0
                            {
763
0
                                fW = (getWidth().isSet() ? getWidth().getNumber() *0.01 : 1.0) * fWReference;
764
0
                                bWidthIsAbsolute = true;
765
0
                            }
766
0
                        }
767
0
                        if (!bYIsAbsolute || !bHeightIsAbsolute)
768
0
                        {
769
                            // get height of enclosing svg and resolve percentage in y and height
770
0
                            double fHReference(0.0);
771
0
                            bool bHasFoundHeight(false);
772
0
                            seekReferenceHeight(fHReference, bHasFoundHeight);
773
                            // referenced values are already in 'user unit'
774
0
                            if (!bYIsAbsolute && bHasFoundHeight)
775
0
                            {
776
0
                                fY = getY().getNumber() * 0.01 * fHReference;
777
0
                                bYIsAbsolute = true;
778
0
                            }
779
0
                            if (!bHeightIsAbsolute && bHasFoundHeight)
780
0
                            {
781
0
                                fH = (getHeight().isSet() ? getHeight().getNumber() *0.01 : 1.0) * fHReference;
782
0
                                bHeightIsAbsolute = true;
783
0
                            }
784
0
                        }
785
786
0
                        if (bXIsAbsolute && bYIsAbsolute && bWidthIsAbsolute && bHeightIsAbsolute)
787
0
                        {
788
0
                            return basegfx::B2DRange(fX, fY, fX+fW, fY+fH);
789
0
                        }
790
0
                        else // relative values could not be resolved, there exists no fallback
791
0
                        {
792
0
                            return SvgNode::getCurrentViewPort();
793
0
                        }
794
0
                    }
795
0
                }
796
0
                else //outermost svg
797
0
                {
798
                    // If width or height is not provided, the default would be 100%, see SVG 1.1 section 5.1.2
799
                    // But here it cannot be resolved and no fallback exists.
800
                    // SVG 1.1 defines in section 5.1.2 that x,y has no meaning for the outermost SVG element.
801
0
                    bool bWidthIsAbsolute(getWidth().isSet() && SvgUnit::percent != getWidth().getUnit());
802
0
                    bool bHeightIsAbsolute(getHeight().isSet() && SvgUnit::percent != getHeight().getUnit());
803
0
                    if (bWidthIsAbsolute && bHeightIsAbsolute)
804
0
                    {
805
0
                        double fW( getWidth().solveNonPercentage(*this) );
806
0
                        double fH( getHeight().solveNonPercentage(*this) );
807
0
                        return basegfx::B2DRange(0.0, 0.0, fW, fH);
808
0
                    }
809
0
                    else // no fallback exists
810
0
                    {
811
0
                            return SvgNode::getCurrentViewPort();
812
0
                    }
813
0
                }
814
// TODO: Is it possible to decompose and use the bounding box of the children, if even the
815
//       outermost svg has no information to resolve percentage? Is it worth, how expensive is it?
816
817
0
            }
818
0
        }
819
820
} // end of namespace svgio
821
822
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */