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 double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : getViewBox()->getWidth()); 186 const double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : getViewBox()->getHeight()); 187 const basegfx::B2DRange aTarget(fX, fY, fX + fW, fY + fH); 188 189 if(aTarget.equal(*getViewBox())) 190 { 191 // no mapping needed, append 192 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence); 193 } 194 else 195 { 196 // create mapping 197 const SvgAspectRatio& rRatio = getSvgAspectRatio(); 198 199 if(rRatio.isSet()) 200 { 201 // let mapping be created from SvgAspectRatio 202 const basegfx::B2DHomMatrix aEmbeddingTransform( 203 rRatio.createMapping(aTarget, *getViewBox())); 204 205 // prepare embedding in transformation 206 const drawinglayer::primitive2d::Primitive2DReference xRef( 207 new drawinglayer::primitive2d::TransformPrimitive2D( 208 aEmbeddingTransform, 209 aSequence)); 210 211 if(rRatio.isMeetOrSlice()) 212 { 213 // embed in transformation 214 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xRef); 215 } 216 else 217 { 218 // need to embed in MaskPrimitive2D, too 219 const drawinglayer::primitive2d::Primitive2DReference xMask( 220 new drawinglayer::primitive2d::MaskPrimitive2D( 221 basegfx::B2DPolyPolygon(basegfx::tools::createPolygonFromRect(aTarget)), 222 drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1))); 223 224 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask); 225 } 226 } 227 else 228 { 229 // choose default mapping 230 const basegfx::B2DHomMatrix aEmbeddingTransform( 231 rRatio.createLinearMapping( 232 aTarget, *getViewBox())); 233 234 // embed in transformation 235 const drawinglayer::primitive2d::Primitive2DReference xTransform( 236 new drawinglayer::primitive2d::TransformPrimitive2D( 237 aEmbeddingTransform, 238 aSequence)); 239 240 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xTransform); 241 } 242 } 243 } 244 } 245 else 246 { 247 // check if we have a size 248 const double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : 0.0); 249 const double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : 0.0); 250 251 // Svg defines that a negative value is an error and that 0.0 disables rendering 252 if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0)) 253 { 254 // check if we have a x,y position 255 const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0); 256 const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0); 257 258 if(!basegfx::fTools::equalZero(fX) || !basegfx::fTools::equalZero(fY)) 259 { 260 // embed in transform 261 const drawinglayer::primitive2d::Primitive2DReference xRef( 262 new drawinglayer::primitive2d::TransformPrimitive2D( 263 basegfx::tools::createTranslateB2DHomMatrix(fX, fY), 264 aSequence)); 265 266 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1); 267 } 268 269 // embed in MaskPrimitive2D to clip 270 const drawinglayer::primitive2d::Primitive2DReference xMask( 271 new drawinglayer::primitive2d::MaskPrimitive2D( 272 basegfx::B2DPolyPolygon( 273 basegfx::tools::createPolygonFromRect( 274 basegfx::B2DRange(fX, fY, fX + fW, fY + fH))), 275 aSequence)); 276 277 // append 278 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xMask); 279 } 280 } 281 } 282 else 283 { 284 // Outermost SVG element; create target range homing width and height as given. 285 // SVG defines that x,y has no meanig for the outermost SVG element. Use a fallback 286 // width and height of din A 4 (21 x 29,7 cm) 287 double fW(getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : (210.0 * 3.543307)); 288 double fH(getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : (297.0 * 3.543307)); 289 290 // Svg defines that a negative value is an error and that 0.0 disables rendering 291 if(basegfx::fTools::more(fW, 0.0) && basegfx::fTools::more(fH, 0.0)) 292 { 293 const basegfx::B2DRange aSvgCanvasRange(0.0, 0.0, fW, fH); 294 295 if(getViewBox()) 296 { 297 if(!basegfx::fTools::equalZero(getViewBox()->getWidth()) && !basegfx::fTools::equalZero(getViewBox()->getHeight())) 298 { 299 // create mapping 300 const SvgAspectRatio& rRatio = getSvgAspectRatio(); 301 basegfx::B2DHomMatrix aViewBoxMapping; 302 303 if(rRatio.isSet()) 304 { 305 // let mapping be created from SvgAspectRatio 306 aViewBoxMapping = rRatio.createMapping(aSvgCanvasRange, *getViewBox()); 307 308 // no need to check ratio here for slice, the outermost Svg will 309 // be clipped anyways (see below) 310 } 311 else 312 { 313 // choose default mapping 314 aViewBoxMapping = rRatio.createLinearMapping(aSvgCanvasRange, *getViewBox()); 315 } 316 317 // scale content to viewBox definitions 318 const drawinglayer::primitive2d::Primitive2DReference xTransform( 319 new drawinglayer::primitive2d::TransformPrimitive2D( 320 aViewBoxMapping, 321 aSequence)); 322 323 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1); 324 } 325 } 326 327 // to be completely correct in Svg sense it is necessary to clip 328 // the whole content to the given canvas. I choose here to do this 329 // initially despite I found various examples of Svg files out there 330 // which have no correct values for this clipping. It's correct 331 // due to the Svg spec. 332 bool bDoCorrectCanvasClipping(true); 333 334 if(bDoCorrectCanvasClipping) 335 { 336 // different from Svg we have the possibility with primitives to get 337 // a correct bounding box for the geometry. Get it for evtl. taking action 338 const basegfx::B2DRange aContentRange( 339 drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence( 340 aSequence, 341 drawinglayer::geometry::ViewInformation2D())); 342 343 if(aSvgCanvasRange.isInside(aContentRange)) 344 { 345 // no clip needed, but an invisible HiddenGeometryPrimitive2D 346 // to allow getting the full Svg range using the primitive mechanisms. 347 // This is needed since e.g. an SdrObject using this as graphic will 348 // create a mapping transformation to exactly map the content to it's 349 // real life size 350 const drawinglayer::primitive2d::Primitive2DReference xLine( 351 new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( 352 basegfx::tools::createPolygonFromRect( 353 aSvgCanvasRange), 354 basegfx::BColor(0.0, 0.0, 0.0))); 355 const drawinglayer::primitive2d::Primitive2DReference xHidden( 356 new drawinglayer::primitive2d::HiddenGeometryPrimitive2D( 357 drawinglayer::primitive2d::Primitive2DSequence(&xLine, 1))); 358 359 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(aSequence, xHidden); 360 } 361 else if(aSvgCanvasRange.overlaps(aContentRange)) 362 { 363 // Clip is necessary. This will make Svg images evtl. smaller 364 // than wanted from Svg (the free space which may be around it is 365 // conform to the Svg spec), but avoids an expensive and unneccessary 366 // clip. Keep the full Svg range here to get the correct mappings 367 // to objects using this. Optimizations can be done in the processors 368 const drawinglayer::primitive2d::Primitive2DReference xMask( 369 new drawinglayer::primitive2d::MaskPrimitive2D( 370 basegfx::B2DPolyPolygon( 371 basegfx::tools::createPolygonFromRect( 372 aSvgCanvasRange)), 373 aSequence)); 374 375 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xMask, 1); 376 } 377 else 378 { 379 // not inside, no overlap. Empty Svg 380 aSequence.realloc(0); 381 } 382 } 383 384 if(aSequence.hasElements()) 385 { 386 // embed in transform primitive to scale to 1/100th mm 387 // where 1 mm == 3.543307 px to get from Svg coordinates to 388 // drawinglayer ones 389 const double fScaleTo100thmm(100.0 / 3.543307); 390 const basegfx::B2DHomMatrix aTransform( 391 basegfx::tools::createScaleB2DHomMatrix( 392 fScaleTo100thmm, 393 fScaleTo100thmm)); 394 395 const drawinglayer::primitive2d::Primitive2DReference xTransform( 396 new drawinglayer::primitive2d::TransformPrimitive2D( 397 aTransform, 398 aSequence)); 399 400 aSequence = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1); 401 402 // append to result 403 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aSequence); 404 } 405 } 406 } 407 } 408 } 409 410 const basegfx::B2DRange* SvgSvgNode::getCurrentViewPort() const 411 { 412 if(getViewBox()) 413 { 414 return getViewBox(); 415 } 416 else 417 { 418 return SvgNode::getCurrentViewPort(); 419 } 420 } 421 422 } // end of namespace svgreader 423 } // end of namespace svgio 424 425 ////////////////////////////////////////////////////////////////////////////// 426 // eof 427