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 33 ////////////////////////////////////////////////////////////////////////////// 34 35 namespace svgio 36 { 37 namespace svgreader 38 { 39 SvgSvgNode::SvgSvgNode( 40 SvgDocument& rDocument, 41 SvgNode* pParent) 42 : SvgNode(SVGTokenSvg, rDocument, pParent), 43 maSvgStyleAttributes(*this), 44 mpViewBox(0), 45 maSvgAspectRatio(), 46 maX(), 47 maY(), 48 maWidth(), 49 maHeight(), 50 maVersion() 51 { 52 if(!getParent()) 53 { 54 // initial fill is black 55 maSvgStyleAttributes.setFill(SvgPaint(basegfx::BColor(0.0, 0.0, 0.0), true, true)); 56 } 57 } 58 59 SvgSvgNode::~SvgSvgNode() 60 { 61 if(mpViewBox) delete mpViewBox; 62 } 63 64 const SvgStyleAttributes* SvgSvgNode::getSvgStyleAttributes() const 65 { 66 return &maSvgStyleAttributes; 67 } 68 69 void SvgSvgNode::parseAttribute(const rtl::OUString& rTokenName, SVGToken aSVGToken, const rtl::OUString& aContent) 70 { 71 // call parent 72 SvgNode::parseAttribute(rTokenName, aSVGToken, aContent); 73 74 // read style attributes 75 maSvgStyleAttributes.parseStyleAttribute(rTokenName, aSVGToken, aContent); 76 77 // parse own 78 switch(aSVGToken) 79 { 80 case SVGTokenStyle: 81 { 82 maSvgStyleAttributes.readStyle(aContent); 83 break; 84 } 85 case SVGTokenViewBox: 86 { 87 const basegfx::B2DRange aRange(readViewBox(aContent, *this)); 88 89 if(!aRange.isEmpty()) 90 { 91 setViewBox(&aRange); 92 } 93 break; 94 } 95 case SVGTokenPreserveAspectRatio: 96 { 97 setSvgAspectRatio(readSvgAspectRatio(aContent)); 98 break; 99 } 100 case SVGTokenX: 101 { 102 SvgNumber aNum; 103 104 if(readSingleNumber(aContent, aNum)) 105 { 106 setX(aNum); 107 } 108 break; 109 } 110 case SVGTokenY: 111 { 112 SvgNumber aNum; 113 114 if(readSingleNumber(aContent, aNum)) 115 { 116 setY(aNum); 117 } 118 break; 119 } 120 case SVGTokenWidth: 121 { 122 SvgNumber aNum; 123 124 if(readSingleNumber(aContent, aNum)) 125 { 126 if(aNum.isPositive()) 127 { 128 setWidth(aNum); 129 } 130 } 131 break; 132 } 133 case SVGTokenHeight: 134 { 135 SvgNumber aNum; 136 137 if(readSingleNumber(aContent, aNum)) 138 { 139 if(aNum.isPositive()) 140 { 141 setHeight(aNum); 142 } 143 } 144 break; 145 } 146 case SVGTokenVersion: 147 { 148 SvgNumber aNum; 149 150 if(readSingleNumber(aContent, aNum)) 151 { 152 setVersion(aNum); 153 } 154 break; 155 } 156 default: 157 { 158 break; 159 } 160 } 161 } 162 163 void SvgSvgNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool bReferenced) const 164 { 165 drawinglayer::primitive2d::Primitive2DSequence aSequence; 166 167 // decompose childs 168 SvgNode::decomposeSvgNode(aSequence, bReferenced); 169 170 if(aSequence.hasElements()) 171 { 172 if(getParent()) 173 { 174 if(getViewBox()) 175 { 176 // Svg defines that with no width or no height the viewBox content is empty, 177 // so both need to exist 178 if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight())) 179 { 180 // create target range homing x,y, width and height as given 181 const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0); 182 const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0); 183 const double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : getViewBox()->getWidth()); 184 const double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : getViewBox()->getHeight()); 185 const basegfx::B2DRange aTarget(fX, fY, fX + fW, fY + fH); 186 187 if(aTarget.equal(*getViewBox())) 188 { 189 // no mapping needed, append 190 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence); 191 } 192 else 193 { 194 // create mapping 195 const SvgAspectRatio& rRatio = getSvgAspectRatio(); 196 197 if(rRatio.isSet()) 198 { 199 // let mapping be created from SvgAspectRatio 200 const basegfx::B2DHomMatrix aEmbeddingTransform( 201 rRatio.createMapping(aTarget, *getViewBox())); 202 203 // prepare embedding in transformation 204 const drawinglayer::primitive2d::Primitive2DReference xRef( 205 new drawinglayer::primitive2d::TransformPrimitive2D( 206 aEmbeddingTransform, 207 aSequence)); 208 209 if(rRatio.isMeetOrSlice()) 210 { 211 // embed in transformation 212 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xRef); 213 } 214 else 215 { 216 // need to embed in MaskPrimitive2D, too 217 const drawinglayer::primitive2d::Primitive2DReference xMask( 218 new drawinglayer::primitive2d::MaskPrimitive2D( 219 basegfx::B2DPolyPolygon(basegfx::tools::createPolygonFromRect(aTarget)), 220 drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1))); 221 222 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask); 223 } 224 } 225 else 226 { 227 // choose default mapping 228 const basegfx::B2DHomMatrix aEmbeddingTransform( 229 rRatio.createLinearMapping( 230 aTarget, *getViewBox())); 231 232 // embed in transformation 233 const drawinglayer::primitive2d::Primitive2DReference xTransform( 234 new drawinglayer::primitive2d::TransformPrimitive2D( 235 aEmbeddingTransform, 236 aSequence)); 237 238 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xTransform); 239 } 240 } 241 } 242 } 243 else 244 { 245 // check if we have a size 246 const double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : 0.0); 247 const double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : 0.0); 248 249 // Svg defines that a negative value is an error and that 0.0 disables rendering 250 if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0)) 251 { 252 // check if we have a x,y position 253 const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0); 254 const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0); 255 256 if(!basegfx::fTools::equalZero(fX) || !basegfx::fTools::equalZero(fY)) 257 { 258 // embed in transform 259 const drawinglayer::primitive2d::Primitive2DReference xRef( 260 new drawinglayer::primitive2d::TransformPrimitive2D( 261 basegfx::tools::createTranslateB2DHomMatrix(fX, fY), 262 aSequence)); 263 264 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1); 265 } 266 267 // embed in MaskPrimitive2D to clip 268 const drawinglayer::primitive2d::Primitive2DReference xMask( 269 new drawinglayer::primitive2d::MaskPrimitive2D( 270 basegfx::B2DPolyPolygon( 271 basegfx::tools::createPolygonFromRect( 272 basegfx::B2DRange(fX, fY, fX + fW, fY + fH))), 273 aSequence)); 274 275 // append 276 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask); 277 } 278 } 279 } 280 else 281 { 282 // Outermost SVG element; create target range homing width and height as given. 283 // SVG defines that x,y has no meanig for the outermost SVG element. Use a fallback 284 // width and height of din A 4 (21 x 29,7 cm) 285 double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : (210.0 * 3.543307)); 286 double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : (297.0 * 3.543307)); 287 288 // Svg defines that a negative value is an error and that 0.0 disables rendering 289 if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0)) 290 { 291 const basegfx::B2DRange aSvgCanvasRange(0.0, 0.0, fW, fH); 292 293 if(getViewBox()) 294 { 295 if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight())) 296 { 297 // create mapping 298 const SvgAspectRatio& rRatio = getSvgAspectRatio(); 299 basegfx::B2DHomMatrix aViewBoxMapping; 300 301 if(rRatio.isSet()) 302 { 303 // let mapping be created from SvgAspectRatio 304 aViewBoxMapping = rRatio.createMapping(aSvgCanvasRange, *getViewBox()); 305 306 // no need to check ratio here for slice, the outermost Svg will 307 // be clipped anyways (see below) 308 } 309 else 310 { 311 // choose default mapping 312 aViewBoxMapping = rRatio.createLinearMapping(aSvgCanvasRange, *getViewBox()); 313 } 314 315 // scale content to viewBox definitions 316 const drawinglayer::primitive2d::Primitive2DReference xTransform( 317 new drawinglayer::primitive2d::TransformPrimitive2D( 318 aViewBoxMapping, 319 aSequence)); 320 321 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1); 322 } 323 } 324 325 // to be completely correct in Svg sense it is necessary to clip 326 // the whole content to the given canvas. I choose here to do this 327 // initially despite I found various examples of Svg files out there 328 // which have no correct values for this clipping. It's correct 329 // due to the Svg spec. 330 bool bDoCorrectCanvasClipping(true); 331 332 if(bDoCorrectCanvasClipping) 333 { 334 // different from Svg we have the possibility with primitives to get 335 // a correct bounding box for the geometry, thhus I will allow to 336 // only clip if necessary. This will make Svg images evtl. smaller 337 // than wanted from Svg (the free space which may be around it is 338 // conform to the Svg spec), but avoids an expensive and unneccessary 339 // clip. 340 const basegfx::B2DRange aContentRange( 341 drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence( 342 aSequence, 343 drawinglayer::geometry::ViewInformation2D())); 344 345 if(!aSvgCanvasRange.isInside(aContentRange)) 346 { 347 const drawinglayer::primitive2d::Primitive2DReference xMask( 348 new drawinglayer::primitive2d::MaskPrimitive2D( 349 basegfx::B2DPolyPolygon( 350 basegfx::tools::createPolygonFromRect( 351 aSvgCanvasRange)), 352 aSequence)); 353 354 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xMask, 1); 355 } 356 } 357 358 { 359 // embed in transform primitive to scale to 1/100th mm 360 // where 1 mm == 3.543307 px to get from Svg coordinates to 361 // drawinglayer ones 362 const double fScaleTo100thmm(100.0 / 3.543307); 363 const basegfx::B2DHomMatrix aTransform( 364 basegfx::tools::createScaleB2DHomMatrix( 365 fScaleTo100thmm, 366 fScaleTo100thmm)); 367 368 const drawinglayer::primitive2d::Primitive2DReference xTransform( 369 new drawinglayer::primitive2d::TransformPrimitive2D( 370 aTransform, 371 aSequence)); 372 373 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1); 374 } 375 376 // append 377 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence); 378 } 379 } 380 } 381 } 382 383 const basegfx::B2DRange* SvgSvgNode::getCurrentViewPort() const 384 { 385 if(getViewBox()) 386 { 387 return getViewBox(); 388 } 389 else 390 { 391 return SvgNode::getCurrentViewPort(); 392 } 393 } 394 395 } // end of namespace svgreader 396 } // end of namespace svgio 397 398 ////////////////////////////////////////////////////////////////////////////// 399 // eof 400