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     {
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 
56         SvgMaskNode::~SvgMaskNode()
57         {
58             if(mpaTransform) delete mpaTransform;
59         }
60 
61         const SvgStyleAttributes* SvgMaskNode::getSvgStyleAttributes() const
62         {
63             static rtl::OUString aClassStr(rtl::OUString::createFromAscii("mask"));
64             maSvgStyleAttributes.checkForCssStyle(aClassStr);
65 
66             return &maSvgStyleAttributes;
67         }
68 
69         void SvgMaskNode::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 SVGTokenX:
86                 {
87                     SvgNumber aNum;
88 
89                     if(readSingleNumber(aContent, aNum))
90                     {
91                         setX(aNum);
92                     }
93                     break;
94                 }
95                 case SVGTokenY:
96                 {
97                     SvgNumber aNum;
98 
99                     if(readSingleNumber(aContent, aNum))
100                     {
101                         setY(aNum);
102                     }
103                     break;
104                 }
105                 case SVGTokenWidth:
106                 {
107                     SvgNumber aNum;
108 
109                     if(readSingleNumber(aContent, aNum))
110                     {
111                         if(aNum.isPositive())
112                         {
113                             setWidth(aNum);
114                         }
115                     }
116                     break;
117                 }
118                 case SVGTokenHeight:
119                 {
120                     SvgNumber aNum;
121 
122                     if(readSingleNumber(aContent, aNum))
123                     {
124                         if(aNum.isPositive())
125                         {
126                             setHeight(aNum);
127                         }
128                     }
129                     break;
130                 }
131                 case SVGTokenTransform:
132                 {
133                     const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
134 
135                     if(!aMatrix.isIdentity())
136                     {
137                         setTransform(&aMatrix);
138                     }
139                     break;
140                 }
141                 case SVGTokenMaskUnits:
142                 {
143                     if(aContent.getLength())
144                     {
145                         if(aContent.match(commonStrings::aStrUserSpaceOnUse, 0))
146                         {
147                             setMaskUnits(userSpaceOnUse);
148                         }
149                         else if(aContent.match(commonStrings::aStrObjectBoundingBox, 0))
150                         {
151                             setMaskUnits(objectBoundingBox);
152                         }
153                     }
154                     break;
155                 }
156                 case SVGTokenMaskContentUnits:
157                 {
158                     if(aContent.getLength())
159                     {
160                         if(aContent.match(commonStrings::aStrUserSpaceOnUse, 0))
161                         {
162                             setMaskContentUnits(userSpaceOnUse);
163                         }
164                         else if(aContent.match(commonStrings::aStrObjectBoundingBox, 0))
165                         {
166                             setMaskContentUnits(objectBoundingBox);
167                         }
168                     }
169                     break;
170                 }
171             }
172         }
173 
174         void SvgMaskNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool bReferenced) const
175         {
176             drawinglayer::primitive2d::Primitive2DSequence aNewTarget;
177 
178             // decompose childs
179             SvgNode::decomposeSvgNode(aNewTarget, bReferenced);
180 
181             if(aNewTarget.hasElements())
182             {
183                 if(getTransform())
184                 {
185                     // create embedding group element with transformation
186                     const drawinglayer::primitive2d::Primitive2DReference xRef(
187                         new drawinglayer::primitive2d::TransformPrimitive2D(
188                             *getTransform(),
189                             aNewTarget));
190 
191                     aNewTarget = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1);
192                 }
193 
194                 // append to current target
195                 drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aNewTarget);
196             }
197         }
198 
199         void SvgMaskNode::apply(drawinglayer::primitive2d::Primitive2DSequence& rTarget) const
200         {
201             if(rTarget.hasElements())
202             {
203                 drawinglayer::primitive2d::Primitive2DSequence aMaskTarget;
204 
205                 // get mask definition as primitives
206                 decomposeSvgNode(aMaskTarget, true);
207 
208                 if(aMaskTarget.hasElements())
209                 {
210                     // get range of content to be masked
211                     const basegfx::B2DRange aContentRange(
212                         drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence(
213                             rTarget,
214                             drawinglayer::geometry::ViewInformation2D()));
215                     const double fContentWidth(aContentRange.getWidth());
216                     const double fContentHeight(aContentRange.getHeight());
217 
218                     if(fContentWidth > 0.0 && fContentHeight > 0.0)
219                     {
220                         // create OffscreenBufferRange
221                         basegfx::B2DRange aOffscreenBufferRange;
222 
223                         if(objectBoundingBox == getMaskUnits())
224                         {
225                             // fractions or percentages of the bounding box of the element to which the mask is applied
226                             const double fX(Unit_percent == getX().getUnit() ? getX().getNumber() * 0.01 : getX().getNumber());
227                             const double fY(Unit_percent == getY().getUnit() ? getY().getNumber() * 0.01 : getY().getNumber());
228                             const double fW(Unit_percent == getWidth().getUnit() ? getWidth().getNumber() * 0.01 : getWidth().getNumber());
229                             const double fH(Unit_percent == getHeight().getUnit() ? getHeight().getNumber() * 0.01 : getHeight().getNumber());
230 
231                             aOffscreenBufferRange = basegfx::B2DRange(
232                                 aContentRange.getMinX() + (fX * fContentWidth),
233                                 aContentRange.getMinY() + (fY * fContentHeight),
234                                 aContentRange.getMinX() + ((fX + fW) * fContentWidth),
235                                 aContentRange.getMinY() + ((fY + fH) * fContentHeight));
236                         }
237                         else
238                         {
239                             const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0);
240                             const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0);
241 
242                             aOffscreenBufferRange = basegfx::B2DRange(
243                                 fX,
244                                 fY,
245                                 fX + (getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : 0.0),
246                                 fY + (getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : 0.0));
247                         }
248 
249                         if(objectBoundingBox == getMaskContentUnits())
250                         {
251                             // mask is object-relative, embed in content transformation
252                             const drawinglayer::primitive2d::Primitive2DReference xTransform(
253                                 new drawinglayer::primitive2d::TransformPrimitive2D(
254                                     basegfx::tools::createScaleTranslateB2DHomMatrix(
255                                         aContentRange.getRange(),
256                                         aContentRange.getMinimum()),
257                                     aMaskTarget));
258 
259                             aMaskTarget = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1);
260                         }
261 
262                         // embed content to a ModifiedColorPrimitive2D since the definitions
263                         // how content is used as alpha is special for Svg
264                         {
265                             const drawinglayer::primitive2d::Primitive2DReference xInverseMask(
266                                 new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
267                                     aMaskTarget,
268                                     basegfx::BColorModifier(
269                                         basegfx::BColor(0.0, 0.0, 0.0),
270                                         0.5,
271                                         basegfx::BCOLORMODIFYMODE_LUMINANCE_TO_ALPHA)));
272 
273                             aMaskTarget = drawinglayer::primitive2d::Primitive2DSequence(&xInverseMask, 1);
274                         }
275 
276                         // prepare new content
277                         drawinglayer::primitive2d::Primitive2DReference xNewContent(
278                             new drawinglayer::primitive2d::TransparencePrimitive2D(
279                                 rTarget,
280                                 aMaskTarget));
281 
282                         // output up to now is defined by aContentRange and mask is oriented
283                         // relative to it. It is possible that aOffscreenBufferRange defines
284                         // a smaller area. In that case, embed to a mask primitive
285                         if(!aOffscreenBufferRange.isInside(aContentRange))
286                         {
287                             xNewContent = new drawinglayer::primitive2d::MaskPrimitive2D(
288                                 basegfx::B2DPolyPolygon(
289                                     basegfx::tools::createPolygonFromRect(
290                                         aOffscreenBufferRange)),
291                                 drawinglayer::primitive2d::Primitive2DSequence(&xNewContent, 1));
292                         }
293 
294                         // redefine target. Use TransparencePrimitive2D with created mask
295                         // geometry
296                         rTarget = drawinglayer::primitive2d::Primitive2DSequence(&xNewContent, 1);
297                     }
298                     else
299                     {
300                         // content is geometrically empty
301                         rTarget.realloc(0);
302                     }
303                 }
304                 else
305                 {
306                     // An empty clipping path will completely clip away the element that had
307                     // the �clip-path� property applied. (Svg spec)
308                     rTarget.realloc(0);
309                 }
310             }
311         }
312 
313     } // end of namespace svgreader
314 } // end of namespace svgio
315 
316 //////////////////////////////////////////////////////////////////////////////
317 // eof
318