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 const bool bWidthIsRelative(!getWidth().isSet() || Unit_percent == getWidth().getUnit()); 177 const bool bHeightIsRelative(!getWidth().isSet() || Unit_percent == getWidth().getUnit()); 178 const SvgSvgNode* pParentSvgSvgNode = 0; 179 double fW(0.0); 180 double fH(0.0); 181 182 // #122594# if width/height is not given, it's 100% (see 5.1.2 The �svg� element in SVG1.1 spec). 183 // If it is relative, the question is to what. The previous implementatin assumed relative to the 184 // local ViewBox which is implied by (4.2 Basic data types): 185 // 186 // "Note that the non-property <length> definition also allows a percentage unit identifier. 187 // The meaning of a percentage length value depends on the attribute for which the percentage 188 // length value has been specified. Two common cases are: (a) when a percentage length value 189 // represents a percentage of the viewport width or height (refer to the section that discusses 190 // units in general), and (b) when a percentage length value represents a percentage of the 191 // bounding box width or height on a given object (refer to the section that describes object 192 // bounding box units)." 193 // 194 // This is not closer specified for the SVG element itself as non-outmost element, but comparisons 195 // with common browsers shows that it's mostly interpreted relative to the viewBox of the parent. 196 // Adding code to search the parent SVG element and calculating width/height relative to it's 197 // viewBox width/height (and no longer to the local viewBox). 198 if(bWidthIsRelative || bHeightIsRelative) 199 { 200 for(const SvgNode* pParent = getParent(); pParent && !pParentSvgSvgNode; pParent = pParent->getParent()) 201 { 202 pParentSvgSvgNode = dynamic_cast< const SvgSvgNode* >(pParent); 203 } 204 } 205 206 if(bWidthIsRelative) 207 { 208 fW = getWidth().isSet() ? getWidth().getNumber() * 0.01 : 1.0; 209 210 if(pParentSvgSvgNode) 211 { 212 fW *= pParentSvgSvgNode->getViewBox()->getWidth(); 213 } 214 } 215 else 216 { 217 fW = getWidth().solve(*this, xcoordinate); 218 } 219 220 if(bHeightIsRelative) 221 { 222 fH = getHeight().isSet() ? getHeight().getNumber() * 0.01 : 1.0; 223 224 if(pParentSvgSvgNode) 225 { 226 fH *= pParentSvgSvgNode->getViewBox()->getHeight(); 227 } 228 } 229 else 230 { 231 fH = getHeight().solve(*this, ycoordinate); 232 } 233 234 if(getViewBox()) 235 { 236 // Svg defines that with no width or no height the viewBox content is empty, 237 // so both need to exist 238 if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight())) 239 { 240 // create target range homing x,y, width and height as given 241 const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0); 242 const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0); 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 // Svg defines that a negative value is an error and that 0.0 disables rendering 304 if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0)) 305 { 306 // check if we have a x,y position 307 const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0); 308 const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0); 309 310 if(!basegfx::fTools::equalZero(fX) || !basegfx::fTools::equalZero(fY)) 311 { 312 // embed in transform 313 const drawinglayer::primitive2d::Primitive2DReference xRef( 314 new drawinglayer::primitive2d::TransformPrimitive2D( 315 basegfx::tools::createTranslateB2DHomMatrix(fX, fY), 316 aSequence)); 317 318 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1); 319 } 320 321 // embed in MaskPrimitive2D to clip 322 const drawinglayer::primitive2d::Primitive2DReference xMask( 323 new drawinglayer::primitive2d::MaskPrimitive2D( 324 basegfx::B2DPolyPolygon( 325 basegfx::tools::createPolygonFromRect( 326 basegfx::B2DRange(fX, fY, fX + fW, fY + fH))), 327 aSequence)); 328 329 // append 330 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask); 331 } 332 } 333 } 334 else 335 { 336 // Outermost SVG element; create target range homing width and height as given. 337 // SVG defines that x,y has no meanig for the outermost SVG element. Use a fallback 338 // width and height of din A 4 (21 x 29,7 cm) 339 double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : (210.0 * 3.543307)); 340 double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : (297.0 * 3.543307)); 341 342 // Svg defines that a negative value is an error and that 0.0 disables rendering 343 if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0)) 344 { 345 const basegfx::B2DRange aSvgCanvasRange(0.0, 0.0, fW, fH); 346 347 if(getViewBox()) 348 { 349 if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight())) 350 { 351 // create mapping 352 const SvgAspectRatio& rRatio = getSvgAspectRatio(); 353 basegfx::B2DHomMatrix aViewBoxMapping; 354 355 if(rRatio.isSet()) 356 { 357 // let mapping be created from SvgAspectRatio 358 aViewBoxMapping = rRatio.createMapping(aSvgCanvasRange, *getViewBox()); 359 360 // no need to check ratio here for slice, the outermost Svg will 361 // be clipped anyways (see below) 362 } 363 else 364 { 365 // choose default mapping 366 aViewBoxMapping = rRatio.createLinearMapping(aSvgCanvasRange, *getViewBox()); 367 } 368 369 // scale content to viewBox definitions 370 const drawinglayer::primitive2d::Primitive2DReference xTransform( 371 new drawinglayer::primitive2d::TransformPrimitive2D( 372 aViewBoxMapping, 373 aSequence)); 374 375 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1); 376 } 377 } 378 379 // to be completely correct in Svg sense it is necessary to clip 380 // the whole content to the given canvas. I choose here to do this 381 // initially despite I found various examples of Svg files out there 382 // which have no correct values for this clipping. It's correct 383 // due to the Svg spec. 384 bool bDoCorrectCanvasClipping(true); 385 386 if(bDoCorrectCanvasClipping) 387 { 388 // different from Svg we have the possibility with primitives to get 389 // a correct bounding box for the geometry. Get it for evtl. taking action 390 const basegfx::B2DRange aContentRange( 391 drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence( 392 aSequence, 393 drawinglayer::geometry::ViewInformation2D())); 394 395 if(aSvgCanvasRange.isInside(aContentRange)) 396 { 397 // no clip needed, but an invisible HiddenGeometryPrimitive2D 398 // to allow getting the full Svg range using the primitive mechanisms. 399 // This is needed since e.g. an SdrObject using this as graphic will 400 // create a mapping transformation to exactly map the content to it's 401 // real life size 402 const drawinglayer::primitive2d::Primitive2DReference xLine( 403 new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( 404 basegfx::tools::createPolygonFromRect( 405 aSvgCanvasRange), 406 basegfx::BColor(0.0, 0.0, 0.0))); 407 const drawinglayer::primitive2d::Primitive2DReference xHidden( 408 new drawinglayer::primitive2d::HiddenGeometryPrimitive2D( 409 drawinglayer::primitive2d::Primitive2DSequence(&xLine, 1))); 410 411 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(aSequence, xHidden); 412 } 413 else if(aSvgCanvasRange.overlaps(aContentRange)) 414 { 415 // Clip is necessary. This will make Svg images evtl. smaller 416 // than wanted from Svg (the free space which may be around it is 417 // conform to the Svg spec), but avoids an expensive and unneccessary 418 // clip. Keep the full Svg range here to get the correct mappings 419 // to objects using this. Optimizations can be done in the processors 420 const drawinglayer::primitive2d::Primitive2DReference xMask( 421 new drawinglayer::primitive2d::MaskPrimitive2D( 422 basegfx::B2DPolyPolygon( 423 basegfx::tools::createPolygonFromRect( 424 aSvgCanvasRange)), 425 aSequence)); 426 427 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xMask, 1); 428 } 429 else 430 { 431 // not inside, no overlap. Empty Svg 432 aSequence.realloc(0); 433 } 434 } 435 436 if(aSequence.hasElements()) 437 { 438 // embed in transform primitive to scale to 1/100th mm 439 // where 1 mm == 3.543307 px to get from Svg coordinates to 440 // drawinglayer ones 441 const double fScaleTo100thmm(100.0 / 3.543307); 442 const basegfx::B2DHomMatrix aTransform( 443 basegfx::tools::createScaleB2DHomMatrix( 444 fScaleTo100thmm, 445 fScaleTo100thmm)); 446 447 const drawinglayer::primitive2d::Primitive2DReference xTransform( 448 new drawinglayer::primitive2d::TransformPrimitive2D( 449 aTransform, 450 aSequence)); 451 452 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1); 453 454 // append to result 455 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence); 456 } 457 } 458 } 459 } 460 } 461 462 const basegfx::B2DRange* SvgSvgNode::getCurrentViewPort() const 463 { 464 if(getViewBox()) 465 { 466 return getViewBox(); 467 } 468 else 469 { 470 return SvgNode::getCurrentViewPort(); 471 } 472 } 473 474 } // end of namespace svgreader 475 } // end of namespace svgio 476 477 ////////////////////////////////////////////////////////////////////////////// 478 // eof 479