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