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/svgclippathnode.hxx> 26 #include <drawinglayer/primitive2d/transformprimitive2d.hxx> 27 #include <drawinglayer/primitive2d/maskprimitive2d.hxx> 28 #include <basegfx/matrix/b2dhommatrixtools.hxx> 29 #include <drawinglayer/geometry/viewinformation2d.hxx> 30 #include <drawinglayer/processor2d/contourextractor2d.hxx> 31 #include <basegfx/polygon/b2dpolypolygoncutter.hxx> 32 #include <basegfx/polygon/b2dpolygontools.hxx> 33 34 ////////////////////////////////////////////////////////////////////////////// 35 36 namespace svgio 37 { 38 namespace svgreader 39 { 40 SvgClipPathNode::SvgClipPathNode( 41 SvgDocument& rDocument, 42 SvgNode* pParent) 43 : SvgNode(SVGTokenClipPathNode, rDocument, pParent), 44 maSvgStyleAttributes(*this), 45 mpaTransform(0), 46 maClipPathUnits(userSpaceOnUse) 47 { 48 } 49 50 SvgClipPathNode::~SvgClipPathNode() 51 { 52 if(mpaTransform) delete mpaTransform; 53 } 54 55 const SvgStyleAttributes* SvgClipPathNode::getSvgStyleAttributes() const 56 { 57 return &maSvgStyleAttributes; 58 } 59 60 void SvgClipPathNode::parseAttribute(const rtl::OUString& rTokenName, SVGToken aSVGToken, const rtl::OUString& aContent) 61 { 62 // call parent 63 SvgNode::parseAttribute(rTokenName, aSVGToken, aContent); 64 65 // read style attributes 66 maSvgStyleAttributes.parseStyleAttribute(rTokenName, aSVGToken, aContent); 67 68 // parse own 69 switch(aSVGToken) 70 { 71 case SVGTokenStyle: 72 { 73 readLocalCssStyle(aContent); 74 break; 75 } 76 case SVGTokenTransform: 77 { 78 const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this)); 79 80 if(!aMatrix.isIdentity()) 81 { 82 setTransform(&aMatrix); 83 } 84 break; 85 } 86 case SVGTokenClipPathUnits: 87 { 88 if(aContent.getLength()) 89 { 90 if(aContent.match(commonStrings::aStrUserSpaceOnUse, 0)) 91 { 92 setClipPathUnits(userSpaceOnUse); 93 } 94 else if(aContent.match(commonStrings::aStrObjectBoundingBox, 0)) 95 { 96 setClipPathUnits(objectBoundingBox); 97 } 98 } 99 break; 100 } 101 default: 102 { 103 break; 104 } 105 } 106 } 107 108 void SvgClipPathNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool bReferenced) const 109 { 110 drawinglayer::primitive2d::Primitive2DSequence aNewTarget; 111 112 // decompose childs 113 SvgNode::decomposeSvgNode(aNewTarget, bReferenced); 114 115 if(aNewTarget.hasElements()) 116 { 117 if(getTransform()) 118 { 119 // create embedding group element with transformation 120 const drawinglayer::primitive2d::Primitive2DReference xRef( 121 new drawinglayer::primitive2d::TransformPrimitive2D( 122 *getTransform(), 123 aNewTarget)); 124 125 drawinglayer::primitive2d::appendPrimitive2DReferenceToPrimitive2DSequence(rTarget, xRef); 126 } 127 else 128 { 129 // append to current target 130 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aNewTarget); 131 } 132 } 133 } 134 135 void SvgClipPathNode::apply( 136 drawinglayer::primitive2d::Primitive2DSequence& rContent, 137 const basegfx::B2DHomMatrix* pTransform) const 138 { 139 if(rContent.hasElements() && Display_none != getDisplay()) 140 { 141 const drawinglayer::geometry::ViewInformation2D aViewInformation2D; 142 drawinglayer::primitive2d::Primitive2DSequence aClipTarget; 143 basegfx::B2DPolyPolygon aClipPolyPolygon; 144 145 // get clipPath definition as primitives 146 decomposeSvgNode(aClipTarget, true); 147 148 if(aClipTarget.hasElements()) 149 { 150 // extract filled plygons as base for a mask PolyPolygon 151 drawinglayer::processor2d::ContourExtractor2D aExtractor(aViewInformation2D, true); 152 153 aExtractor.process(aClipTarget); 154 155 const basegfx::B2DPolyPolygonVector& rResult(aExtractor.getExtractedContour()); 156 const sal_uInt32 nSize(rResult.size()); 157 158 if(nSize > 1) 159 { 160 // merge to single clipPolyPolygon 161 aClipPolyPolygon = basegfx::tools::mergeToSinglePolyPolygon(rResult); 162 } 163 else 164 { 165 aClipPolyPolygon = rResult[0]; 166 } 167 } 168 169 if(aClipPolyPolygon.count()) 170 { 171 if(objectBoundingBox == getClipPathUnits()) 172 { 173 // clip is object-relative, transform using content transformation 174 const basegfx::B2DRange aContentRange( 175 drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence( 176 rContent, 177 aViewInformation2D)); 178 179 aClipPolyPolygon.transform( 180 basegfx::tools::createScaleTranslateB2DHomMatrix( 181 aContentRange.getRange(), 182 aContentRange.getMinimum())); 183 } 184 else // userSpaceOnUse 185 { 186 // #i124852# 187 if(pTransform) 188 { 189 aClipPolyPolygon.transform(*pTransform); 190 } 191 } 192 193 // #124313# try to avoid creating an embedding to a MaskPrimitive2D if 194 // possible; MaskPrimitive2D processing is potentially expensive 195 bool bCreateEmbedding(true); 196 bool bAddContent(true); 197 198 if(basegfx::tools::isRectangle(aClipPolyPolygon)) 199 { 200 // ClipRegion is a rectangle, thus it is not expensive to tell 201 // if the content is completely inside or outside of it; get ranges 202 const basegfx::B2DRange aClipRange(aClipPolyPolygon.getB2DRange()); 203 const basegfx::B2DRange aContentRange( 204 drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence( 205 rContent, 206 aViewInformation2D)); 207 208 if(aClipRange.isInside(aContentRange)) 209 { 210 // completely contained, no need to clip at all, so no need for embedding 211 bCreateEmbedding = false; 212 } 213 else if(aClipRange.overlaps(aContentRange)) 214 { 215 // overlap; embedding needed. ClipRegion can be minimized by using 216 // the intersection of the ClipRange and the ContentRange. Minimizing 217 // the ClipRegion potentially enhances further processing since 218 // usually clip operations are expensive. 219 basegfx::B2DRange aCommonRange(aContentRange); 220 221 aCommonRange.intersect(aClipRange); 222 aClipPolyPolygon = basegfx::B2DPolyPolygon(basegfx::tools::createPolygonFromRect(aCommonRange)); 223 } 224 else 225 { 226 // not inside and no overlap -> completely outside 227 // no need for embedding, no need for content at all 228 bCreateEmbedding = false; 229 bAddContent = false; 230 } 231 } 232 else 233 { 234 // ClipRegion is not a simple rectangle, it would be possible but expensive to 235 // tell if the content needs clipping or not. It is also dependent of 236 // the content's decomposition. To do this, a processor would be needed that 237 // is capable if processing the given sequence of primitives and decide 238 // if all is inside or all is outside. Such a ClipProcessor could be written, 239 // but for now just create the embedding 240 } 241 242 if(bCreateEmbedding) 243 { 244 // redefine target. Use MaskPrimitive2D with created clip 245 // geometry. Using the automatically set mbIsClipPathContent at 246 // SvgStyleAttributes the clip definition is without fill, stroke, 247 // and strokeWidth and forced to black 248 const drawinglayer::primitive2d::Primitive2DReference xEmbedTransparence( 249 new drawinglayer::primitive2d::MaskPrimitive2D( 250 aClipPolyPolygon, 251 rContent)); 252 253 rContent = drawinglayer::primitive2d::Primitive2DSequence(&xEmbedTransparence, 1); 254 } 255 else 256 { 257 if(!bAddContent) 258 { 259 rContent.realloc(0); 260 } 261 } 262 } 263 else 264 { 265 // An empty clipping path will completely clip away the element that had 266 // the �clip-path� property applied. (Svg spec) 267 rContent.realloc(0); 268 } 269 } 270 } 271 272 } // end of namespace svgreader 273 } // end of namespace svgio 274 275 ////////////////////////////////////////////////////////////////////////////// 276 // eof 277