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