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/svgmasknode.hxx>
26 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
27 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
28 #include <basegfx/matrix/b2dhommatrixtools.hxx>
29 #include <drawinglayer/geometry/viewinformation2d.hxx>
30 #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
31 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
32 #include <basegfx/polygon/b2dpolygontools.hxx>
33 #include <basegfx/polygon/b2dpolygon.hxx>
34 
35 //////////////////////////////////////////////////////////////////////////////
36 
37 namespace svgio
38 {
39     namespace svgreader
40     {
SvgMaskNode(SvgDocument & rDocument,SvgNode * pParent)41         SvgMaskNode::SvgMaskNode(
42             SvgDocument& rDocument,
43             SvgNode* pParent)
44         :   SvgNode(SVGTokenMask, rDocument, pParent),
45             maSvgStyleAttributes(*this),
46             maX(SvgNumber(-10.0, Unit_percent, true)),
47             maY(SvgNumber(-10.0, Unit_percent, true)),
48             maWidth(SvgNumber(120.0, Unit_percent, true)),
49             maHeight(SvgNumber(120.0, Unit_percent, true)),
50             mpaTransform(0),
51             maMaskUnits(objectBoundingBox),
52             maMaskContentUnits(userSpaceOnUse)
53         {
54         }
55 
~SvgMaskNode()56         SvgMaskNode::~SvgMaskNode()
57         {
58             if(mpaTransform) delete mpaTransform;
59         }
60 
getSvgStyleAttributes() const61         const SvgStyleAttributes* SvgMaskNode::getSvgStyleAttributes() const
62         {
63             return &maSvgStyleAttributes;
64         }
65 
parseAttribute(const rtl::OUString & rTokenName,SVGToken aSVGToken,const rtl::OUString & aContent)66         void SvgMaskNode::parseAttribute(const rtl::OUString& rTokenName, SVGToken aSVGToken, const rtl::OUString& aContent)
67         {
68             // call parent
69             SvgNode::parseAttribute(rTokenName, aSVGToken, aContent);
70 
71             // read style attributes
72             maSvgStyleAttributes.parseStyleAttribute(rTokenName, aSVGToken, aContent);
73 
74             // parse own
75             switch(aSVGToken)
76             {
77                 case SVGTokenStyle:
78                 {
79                     maSvgStyleAttributes.readStyle(aContent);
80                     break;
81                 }
82                 case SVGTokenX:
83                 {
84                     SvgNumber aNum;
85 
86                     if(readSingleNumber(aContent, aNum))
87                     {
88                         setX(aNum);
89                     }
90                     break;
91                 }
92                 case SVGTokenY:
93                 {
94                     SvgNumber aNum;
95 
96                     if(readSingleNumber(aContent, aNum))
97                     {
98                         setY(aNum);
99                     }
100                     break;
101                 }
102                 case SVGTokenWidth:
103                 {
104                     SvgNumber aNum;
105 
106                     if(readSingleNumber(aContent, aNum))
107                     {
108                         if(aNum.isPositive())
109                         {
110                             setWidth(aNum);
111                         }
112                     }
113                     break;
114                 }
115                 case SVGTokenHeight:
116                 {
117                     SvgNumber aNum;
118 
119                     if(readSingleNumber(aContent, aNum))
120                     {
121                         if(aNum.isPositive())
122                         {
123                             setHeight(aNum);
124                         }
125                     }
126                     break;
127                 }
128                 case SVGTokenTransform:
129                 {
130                     const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
131 
132                     if(!aMatrix.isIdentity())
133                     {
134                         setTransform(&aMatrix);
135                     }
136                     break;
137                 }
138                 case SVGTokenMaskUnits:
139                 {
140                     if(aContent.getLength())
141                     {
142                         if(aContent.match(commonStrings::aStrUserSpaceOnUse, 0))
143                         {
144                             setMaskUnits(userSpaceOnUse);
145                         }
146                         else if(aContent.match(commonStrings::aStrObjectBoundingBox, 0))
147                         {
148                             setMaskUnits(objectBoundingBox);
149                         }
150                     }
151                     break;
152                 }
153                 case SVGTokenMaskContentUnits:
154                 {
155                     if(aContent.getLength())
156                     {
157                         if(aContent.match(commonStrings::aStrUserSpaceOnUse, 0))
158                         {
159                             setMaskContentUnits(userSpaceOnUse);
160                         }
161                         else if(aContent.match(commonStrings::aStrObjectBoundingBox, 0))
162                         {
163                             setMaskContentUnits(objectBoundingBox);
164                         }
165                     }
166                     break;
167                 }
168                 default:
169                 {
170                     break;
171                 }
172             }
173         }
174 
decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence & rTarget,bool bReferenced) const175         void SvgMaskNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool bReferenced) const
176         {
177             drawinglayer::primitive2d::Primitive2DSequence aNewTarget;
178 
179             // decompose childs
180             SvgNode::decomposeSvgNode(aNewTarget, bReferenced);
181 
182             if(aNewTarget.hasElements())
183             {
184                 if(getTransform())
185                 {
186                     // create embedding group element with transformation
187                     const drawinglayer::primitive2d::Primitive2DReference xRef(
188                         new drawinglayer::primitive2d::TransformPrimitive2D(
189                             *getTransform(),
190                             aNewTarget));
191 
192                     aNewTarget = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1);
193                 }
194 
195                 // append to current target
196                 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aNewTarget);
197             }
198         }
199 
apply(drawinglayer::primitive2d::Primitive2DSequence & rTarget,const basegfx::B2DHomMatrix * pTransform) const200         void SvgMaskNode::apply(
201             drawinglayer::primitive2d::Primitive2DSequence& rTarget,
202             const basegfx::B2DHomMatrix* pTransform) const
203         {
204             if(rTarget.hasElements() && Display_none != getDisplay())
205             {
206                 drawinglayer::primitive2d::Primitive2DSequence aMaskTarget;
207 
208                 // get mask definition as primitives
209                 decomposeSvgNode(aMaskTarget, true);
210 
211                 if(aMaskTarget.hasElements())
212                 {
213                     // get range of content to be masked
214                     const basegfx::B2DRange aContentRange(
215                         drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence(
216                             rTarget,
217                             drawinglayer::geometry::ViewInformation2D()));
218                     const double fContentWidth(aContentRange.getWidth());
219                     const double fContentHeight(aContentRange.getHeight());
220 
221                     if(fContentWidth > 0.0 && fContentHeight > 0.0)
222                     {
223                         // create OffscreenBufferRange
224                         basegfx::B2DRange aOffscreenBufferRange;
225 
226                         if(objectBoundingBox == getMaskUnits())
227                         {
228                             // fractions or percentages of the bounding box of the element to which the mask is applied
229                             const double fX(Unit_percent == getX().getUnit() ? getX().getNumber() * 0.01 : getX().getNumber());
230                             const double fY(Unit_percent == getY().getUnit() ? getY().getNumber() * 0.01 : getY().getNumber());
231                             const double fW(Unit_percent == getWidth().getUnit() ? getWidth().getNumber() * 0.01 : getWidth().getNumber());
232                             const double fH(Unit_percent == getHeight().getUnit() ? getHeight().getNumber() * 0.01 : getHeight().getNumber());
233 
234                             aOffscreenBufferRange = basegfx::B2DRange(
235                                 aContentRange.getMinX() + (fX * fContentWidth),
236                                 aContentRange.getMinY() + (fY * fContentHeight),
237                                 aContentRange.getMinX() + ((fX + fW) * fContentWidth),
238                                 aContentRange.getMinY() + ((fY + fH) * fContentHeight));
239                         }
240                         else
241                         {
242                             const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0);
243                             const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0);
244 
245                             aOffscreenBufferRange = basegfx::B2DRange(
246                                 fX,
247                                 fY,
248                                 fX + (getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : 0.0),
249                                 fY + (getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : 0.0));
250                         }
251 
252                         if(objectBoundingBox == getMaskContentUnits())
253                         {
254                             // mask is object-relative, embed in content transformation
255                             const drawinglayer::primitive2d::Primitive2DReference xTransform(
256                                 new drawinglayer::primitive2d::TransformPrimitive2D(
257                                     basegfx::tools::createScaleTranslateB2DHomMatrix(
258                                         aContentRange.getRange(),
259                                         aContentRange.getMinimum()),
260                                     aMaskTarget));
261 
262                             aMaskTarget = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1);
263                         }
264                         else // userSpaceOnUse
265                         {
266                             // #i124852#
267                             if(pTransform)
268                             {
269                                 const drawinglayer::primitive2d::Primitive2DReference xTransform(
270                                     new drawinglayer::primitive2d::TransformPrimitive2D(
271                                         *pTransform,
272                                         aMaskTarget));
273 
274                                 aMaskTarget = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1);
275                             }
276                         }
277 
278                         // embed content to a ModifiedColorPrimitive2D since the definitions
279                         // how content is used as alpha is special for Svg
280                         {
281                             const drawinglayer::primitive2d::Primitive2DReference xInverseMask(
282                                 new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
283                                     aMaskTarget,
284                                     basegfx::BColorModifierSharedPtr(
285                                         new basegfx::BColorModifier_luminance_to_alpha())));
286 
287                             aMaskTarget = drawinglayer::primitive2d::Primitive2DSequence(&xInverseMask, 1);
288                         }
289 
290                         // prepare new content
291                         drawinglayer::primitive2d::Primitive2DReference xNewContent(
292                             new drawinglayer::primitive2d::TransparencePrimitive2D(
293                                 rTarget,
294                                 aMaskTarget));
295 
296                         // output up to now is defined by aContentRange and mask is oriented
297                         // relative to it. It is possible that aOffscreenBufferRange defines
298                         // a smaller area. In that case, embed to a mask primitive
299                         if(!aOffscreenBufferRange.isInside(aContentRange))
300                         {
301                             xNewContent = new drawinglayer::primitive2d::MaskPrimitive2D(
302                                 basegfx::B2DPolyPolygon(
303                                     basegfx::tools::createPolygonFromRect(
304                                         aOffscreenBufferRange)),
305                                 drawinglayer::primitive2d::Primitive2DSequence(&xNewContent, 1));
306                         }
307 
308                         // redefine target. Use TransparencePrimitive2D with created mask
309                         // geometry
310                         rTarget = drawinglayer::primitive2d::Primitive2DSequence(&xNewContent, 1);
311                     }
312                     else
313                     {
314                         // content is geometrically empty
315                         rTarget.realloc(0);
316                     }
317                 }
318                 else
319                 {
320                     // An empty clipping path will completely clip away the element that had
321                     // the �clip-path� property applied. (Svg spec)
322                     rTarget.realloc(0);
323                 }
324             }
325         }
326 
327     } // end of namespace svgreader
328 } // end of namespace svgio
329 
330 //////////////////////////////////////////////////////////////////////////////
331 // eof
332