1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 // MARKER(update_precomp.py): autogen include statement, do not remove 23 #include "precompiled_svgio.hxx" 24 25 #include <svgio/svgreader/svgsvgnode.hxx> 26 #include <drawinglayer/geometry/viewinformation2d.hxx> 27 #include <drawinglayer/primitive2d/transformprimitive2d.hxx> 28 #include <drawinglayer/primitive2d/maskprimitive2d.hxx> 29 #include <basegfx/polygon/b2dpolygontools.hxx> 30 #include <basegfx/polygon/b2dpolygon.hxx> 31 #include <basegfx/matrix/b2dhommatrixtools.hxx> 32 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx> 33 #include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx> 34 35 ////////////////////////////////////////////////////////////////////////////// 36 37 namespace svgio 38 { 39 namespace svgreader 40 { 41 SvgSvgNode::SvgSvgNode( 42 SvgDocument& rDocument, 43 SvgNode* pParent) 44 : SvgNode(SVGTokenSvg, rDocument, pParent), 45 maSvgStyleAttributes(*this), 46 mpViewBox(0), 47 maSvgAspectRatio(), 48 maX(), 49 maY(), 50 maWidth(), 51 maHeight(), 52 maVersion() 53 { 54 if(!getParent()) 55 { 56 // initial fill is black 57 maSvgStyleAttributes.setFill(SvgPaint(basegfx::BColor(0.0, 0.0, 0.0), true, true)); 58 } 59 } 60 61 SvgSvgNode::~SvgSvgNode() 62 { 63 if(mpViewBox) delete mpViewBox; 64 } 65 66 const SvgStyleAttributes* SvgSvgNode::getSvgStyleAttributes() const 67 { 68 return &maSvgStyleAttributes; 69 } 70 71 void SvgSvgNode::parseAttribute(const rtl::OUString& rTokenName, SVGToken aSVGToken, const rtl::OUString& aContent) 72 { 73 // call parent 74 SvgNode::parseAttribute(rTokenName, aSVGToken, aContent); 75 76 // read style attributes 77 maSvgStyleAttributes.parseStyleAttribute(rTokenName, aSVGToken, aContent); 78 79 // parse own 80 switch(aSVGToken) 81 { 82 case SVGTokenStyle: 83 { 84 maSvgStyleAttributes.readStyle(aContent); 85 break; 86 } 87 case SVGTokenViewBox: 88 { 89 const basegfx::B2DRange aRange(readViewBox(aContent, *this)); 90 91 if(!aRange.isEmpty()) 92 { 93 setViewBox(&aRange); 94 } 95 break; 96 } 97 case SVGTokenPreserveAspectRatio: 98 { 99 setSvgAspectRatio(readSvgAspectRatio(aContent)); 100 break; 101 } 102 case SVGTokenX: 103 { 104 SvgNumber aNum; 105 106 if(readSingleNumber(aContent, aNum)) 107 { 108 setX(aNum); 109 } 110 break; 111 } 112 case SVGTokenY: 113 { 114 SvgNumber aNum; 115 116 if(readSingleNumber(aContent, aNum)) 117 { 118 setY(aNum); 119 } 120 break; 121 } 122 case SVGTokenWidth: 123 { 124 SvgNumber aNum; 125 126 if(readSingleNumber(aContent, aNum)) 127 { 128 if(aNum.isPositive()) 129 { 130 setWidth(aNum); 131 } 132 } 133 break; 134 } 135 case SVGTokenHeight: 136 { 137 SvgNumber aNum; 138 139 if(readSingleNumber(aContent, aNum)) 140 { 141 if(aNum.isPositive()) 142 { 143 setHeight(aNum); 144 } 145 } 146 break; 147 } 148 case SVGTokenVersion: 149 { 150 SvgNumber aNum; 151 152 if(readSingleNumber(aContent, aNum)) 153 { 154 setVersion(aNum); 155 } 156 break; 157 } 158 default: 159 { 160 break; 161 } 162 } 163 } 164 165 void SvgSvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool bReferenced) const 166 { 167 drawinglayer::primitive2d::Primitive2DSequence aSequence; 168 169 // decompose childs 170 SvgNode::decomposeSvgNode(aSequence, bReferenced); 171 172 if(aSequence.hasElements()) 173 { 174 if(getParent()) 175 { 176 if(getViewBox()) 177 { 178 // Svg defines that with no width or no height the viewBox content is empty, 179 // so both need to exist 180 if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight())) 181 { 182 // create target range homing x,y, width and height as given 183 const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0); 184 const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0); 185 const bool bWidthIsRelative(!getWidth().isSet() || Unit_percent == getWidth().getUnit()); 186 const bool bHeightIsRelative(!getWidth().isSet() || Unit_percent == getWidth().getUnit()); 187 const SvgSvgNode* pParentSvgSvgNode = 0; 188 double fW(0.0); 189 double fH(0.0); 190 191 // #122594# if width/height is not given, it's 100% (see 5.1.2 The �svg� element in SVG1.1 spec). 192 // If it is relative, the question is to what. The previous implementatin assumed relative to the 193 // local ViewBox which is implied by (4.2 Basic data types): 194 // 195 // "Note that the non-property <length> definition also allows a percentage unit identifier. 196 // The meaning of a percentage length value depends on the attribute for which the percentage 197 // length value has been specified. Two common cases are: (a) when a percentage length value 198 // represents a percentage of the viewport width or height (refer to the section that discusses 199 // units in general), and (b) when a percentage length value represents a percentage of the 200 // bounding box width or height on a given object (refer to the section that describes object 201 // bounding box units)." 202 // 203 // This is not closer specified for the SVG element itself as non-outmost element, but comparisons 204 // with common browsers shows that it's mostly interpreted relative to the viewBox of the parent. 205 // Adding code to search the parent SVG element and calculating width/height relative to it's 206 // viewBox width/height (and no longer to the local viewBox). 207 if(bWidthIsRelative || bHeightIsRelative) 208 { 209 for(const SvgNode* pParent = getParent(); pParent && !pParentSvgSvgNode; pParent = pParent->getParent()) 210 { 211 pParentSvgSvgNode = dynamic_cast< const SvgSvgNode* >(pParent); 212 } 213 } 214 215 if(bWidthIsRelative) 216 { 217 fW = getWidth().isSet() ? getWidth().getNumber() * 0.01 : 1.0; 218 219 if(pParentSvgSvgNode) 220 { 221 fW *= pParentSvgSvgNode->getViewBox()->getWidth(); 222 } 223 } 224 else 225 { 226 fW = getWidth().solve(*this, xcoordinate); 227 } 228 229 if(bHeightIsRelative) 230 { 231 fH = getHeight().isSet() ? getHeight().getNumber() * 0.01 : 1.0; 232 233 if(pParentSvgSvgNode) 234 { 235 fH *= pParentSvgSvgNode->getViewBox()->getHeight(); 236 } 237 } 238 else 239 { 240 fH = getHeight().solve(*this, ycoordinate); 241 } 242 243 const basegfx::B2DRange aTarget(fX, fY, fX + fW, fY + fH); 244 245 if(aTarget.equal(*getViewBox())) 246 { 247 // no mapping needed, append 248 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence); 249 } 250 else 251 { 252 // create mapping 253 const SvgAspectRatio& rRatio = getSvgAspectRatio(); 254 255 if(rRatio.isSet()) 256 { 257 // let mapping be created from SvgAspectRatio 258 const basegfx::B2DHomMatrix aEmbeddingTransform( 259 rRatio.createMapping(aTarget, *getViewBox())); 260 261 // prepare embedding in transformation 262 const drawinglayer::primitive2d::Primitive2DReference xRef( 263 new drawinglayer::primitive2d::TransformPrimitive2D( 264 aEmbeddingTransform, 265 aSequence)); 266 267 if(rRatio.isMeetOrSlice()) 268 { 269 // embed in transformation 270 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xRef); 271 } 272 else 273 { 274 // need to embed in MaskPrimitive2D, too 275 const drawinglayer::primitive2d::Primitive2DReference xMask( 276 new drawinglayer::primitive2d::MaskPrimitive2D( 277 basegfx::B2DPolyPolygon(basegfx::tools::createPolygonFromRect(aTarget)), 278 drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1))); 279 280 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask); 281 } 282 } 283 else 284 { 285 // choose default mapping 286 const basegfx::B2DHomMatrix aEmbeddingTransform( 287 rRatio.createLinearMapping( 288 aTarget, *getViewBox())); 289 290 // embed in transformation 291 const drawinglayer::primitive2d::Primitive2DReference xTransform( 292 new drawinglayer::primitive2d::TransformPrimitive2D( 293 aEmbeddingTransform, 294 aSequence)); 295 296 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xTransform); 297 } 298 } 299 } 300 } 301 else 302 { 303 // check if we have a size 304 const double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : 0.0); 305 const double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : 0.0); 306 307 // Svg defines that a negative value is an error and that 0.0 disables rendering 308 if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0)) 309 { 310 // check if we have a x,y position 311 const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0); 312 const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0); 313 314 if(!basegfx::fTools::equalZero(fX) || !basegfx::fTools::equalZero(fY)) 315 { 316 // embed in transform 317 const drawinglayer::primitive2d::Primitive2DReference xRef( 318 new drawinglayer::primitive2d::TransformPrimitive2D( 319 basegfx::tools::createTranslateB2DHomMatrix(fX, fY), 320 aSequence)); 321 322 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1); 323 } 324 325 // embed in MaskPrimitive2D to clip 326 const drawinglayer::primitive2d::Primitive2DReference xMask( 327 new drawinglayer::primitive2d::MaskPrimitive2D( 328 basegfx::B2DPolyPolygon( 329 basegfx::tools::createPolygonFromRect( 330 basegfx::B2DRange(fX, fY, fX + fW, fY + fH))), 331 aSequence)); 332 333 // append 334 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask); 335 } 336 } 337 } 338 else 339 { 340 // Outermost SVG element; create target range homing width and height as given. 341 // SVG defines that x,y has no meanig for the outermost SVG element. Use a fallback 342 // width and height of din A 4 (21 x 29,7 cm) 343 double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : (210.0 * 3.543307)); 344 double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : (297.0 * 3.543307)); 345 346 // Svg defines that a negative value is an error and that 0.0 disables rendering 347 if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0)) 348 { 349 const basegfx::B2DRange aSvgCanvasRange(0.0, 0.0, fW, fH); 350 351 if(getViewBox()) 352 { 353 if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight())) 354 { 355 // create mapping 356 const SvgAspectRatio& rRatio = getSvgAspectRatio(); 357 basegfx::B2DHomMatrix aViewBoxMapping; 358 359 if(rRatio.isSet()) 360 { 361 // let mapping be created from SvgAspectRatio 362 aViewBoxMapping = rRatio.createMapping(aSvgCanvasRange, *getViewBox()); 363 364 // no need to check ratio here for slice, the outermost Svg will 365 // be clipped anyways (see below) 366 } 367 else 368 { 369 // choose default mapping 370 aViewBoxMapping = rRatio.createLinearMapping(aSvgCanvasRange, *getViewBox()); 371 } 372 373 // scale content to viewBox definitions 374 const drawinglayer::primitive2d::Primitive2DReference xTransform( 375 new drawinglayer::primitive2d::TransformPrimitive2D( 376 aViewBoxMapping, 377 aSequence)); 378 379 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1); 380 } 381 } 382 383 // to be completely correct in Svg sense it is necessary to clip 384 // the whole content to the given canvas. I choose here to do this 385 // initially despite I found various examples of Svg files out there 386 // which have no correct values for this clipping. It's correct 387 // due to the Svg spec. 388 bool bDoCorrectCanvasClipping(true); 389 390 if(bDoCorrectCanvasClipping) 391 { 392 // different from Svg we have the possibility with primitives to get 393 // a correct bounding box for the geometry. Get it for evtl. taking action 394 const basegfx::B2DRange aContentRange( 395 drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence( 396 aSequence, 397 drawinglayer::geometry::ViewInformation2D())); 398 399 if(aSvgCanvasRange.isInside(aContentRange)) 400 { 401 // no clip needed, but an invisible HiddenGeometryPrimitive2D 402 // to allow getting the full Svg range using the primitive mechanisms. 403 // This is needed since e.g. an SdrObject using this as graphic will 404 // create a mapping transformation to exactly map the content to it's 405 // real life size 406 const drawinglayer::primitive2d::Primitive2DReference xLine( 407 new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( 408 basegfx::tools::createPolygonFromRect( 409 aSvgCanvasRange), 410 basegfx::BColor(0.0, 0.0, 0.0))); 411 const drawinglayer::primitive2d::Primitive2DReference xHidden( 412 new drawinglayer::primitive2d::HiddenGeometryPrimitive2D( 413 drawinglayer::primitive2d::Primitive2DSequence(&xLine, 1))); 414 415 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(aSequence, xHidden); 416 } 417 else if(aSvgCanvasRange.overlaps(aContentRange)) 418 { 419 // Clip is necessary. This will make Svg images evtl. smaller 420 // than wanted from Svg (the free space which may be around it is 421 // conform to the Svg spec), but avoids an expensive and unneccessary 422 // clip. Keep the full Svg range here to get the correct mappings 423 // to objects using this. Optimizations can be done in the processors 424 const drawinglayer::primitive2d::Primitive2DReference xMask( 425 new drawinglayer::primitive2d::MaskPrimitive2D( 426 basegfx::B2DPolyPolygon( 427 basegfx::tools::createPolygonFromRect( 428 aSvgCanvasRange)), 429 aSequence)); 430 431 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xMask, 1); 432 } 433 else 434 { 435 // not inside, no overlap. Empty Svg 436 aSequence.realloc(0); 437 } 438 } 439 440 if(aSequence.hasElements()) 441 { 442 // embed in transform primitive to scale to 1/100th mm 443 // where 1 mm == 3.543307 px to get from Svg coordinates to 444 // drawinglayer ones 445 const double fScaleTo100thmm(100.0 / 3.543307); 446 const basegfx::B2DHomMatrix aTransform( 447 basegfx::tools::createScaleB2DHomMatrix( 448 fScaleTo100thmm, 449 fScaleTo100thmm)); 450 451 const drawinglayer::primitive2d::Primitive2DReference xTransform( 452 new drawinglayer::primitive2d::TransformPrimitive2D( 453 aTransform, 454 aSequence)); 455 456 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1); 457 458 // append to result 459 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence); 460 } 461 } 462 } 463 } 464 } 465 466 const basegfx::B2DRange* SvgSvgNode::getCurrentViewPort() const 467 { 468 if(getViewBox()) 469 { 470 return getViewBox(); 471 } 472 else 473 { 474 return SvgNode::getCurrentViewPort(); 475 } 476 } 477 478 } // end of namespace svgreader 479 } // end of namespace svgio 480 481 ////////////////////////////////////////////////////////////////////////////// 482 // eof 483