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             return &maSvgStyleAttributes;
64         }
65 
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 
175         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 
200         void SvgMaskNode::apply(drawinglayer::primitive2d::Primitive2DSequence& rTarget) const
201         {
202             if(rTarget.hasElements() && Display_none != getDisplay())
203             {
204                 drawinglayer::primitive2d::Primitive2DSequence aMaskTarget;
205 
206                 // get mask definition as primitives
207                 decomposeSvgNode(aMaskTarget, true);
208 
209                 if(aMaskTarget.hasElements())
210                 {
211                     // get range of content to be masked
212                     const basegfx::B2DRange aContentRange(
213                         drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence(
214                             rTarget,
215                             drawinglayer::geometry::ViewInformation2D()));
216                     const double fContentWidth(aContentRange.getWidth());
217                     const double fContentHeight(aContentRange.getHeight());
218 
219                     if(fContentWidth > 0.0 && fContentHeight > 0.0)
220                     {
221                         // create OffscreenBufferRange
222                         basegfx::B2DRange aOffscreenBufferRange;
223 
224                         if(objectBoundingBox == getMaskUnits())
225                         {
226                             // fractions or percentages of the bounding box of the element to which the mask is applied
227                             const double fX(Unit_percent == getX().getUnit() ? getX().getNumber() * 0.01 : getX().getNumber());
228                             const double fY(Unit_percent == getY().getUnit() ? getY().getNumber() * 0.01 : getY().getNumber());
229                             const double fW(Unit_percent == getWidth().getUnit() ? getWidth().getNumber() * 0.01 : getWidth().getNumber());
230                             const double fH(Unit_percent == getHeight().getUnit() ? getHeight().getNumber() * 0.01 : getHeight().getNumber());
231 
232                             aOffscreenBufferRange = basegfx::B2DRange(
233                                 aContentRange.getMinX() + (fX * fContentWidth),
234                                 aContentRange.getMinY() + (fY * fContentHeight),
235                                 aContentRange.getMinX() + ((fX + fW) * fContentWidth),
236                                 aContentRange.getMinY() + ((fY + fH) * fContentHeight));
237                         }
238                         else
239                         {
240                             const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0);
241                             const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0);
242 
243                             aOffscreenBufferRange = basegfx::B2DRange(
244                                 fX,
245                                 fY,
246                                 fX + (getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : 0.0),
247                                 fY + (getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : 0.0));
248                         }
249 
250                         if(objectBoundingBox == getMaskContentUnits())
251                         {
252                             // mask is object-relative, embed in content transformation
253                             const drawinglayer::primitive2d::Primitive2DReference xTransform(
254                                 new drawinglayer::primitive2d::TransformPrimitive2D(
255                                     basegfx::tools::createScaleTranslateB2DHomMatrix(
256                                         aContentRange.getRange(),
257                                         aContentRange.getMinimum()),
258                                     aMaskTarget));
259 
260                             aMaskTarget = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1);
261                         }
262 
263                         // embed content to a ModifiedColorPrimitive2D since the definitions
264                         // how content is used as alpha is special for Svg
265                         {
266                             const drawinglayer::primitive2d::Primitive2DReference xInverseMask(
267                                 new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
268                                     aMaskTarget,
269                                     basegfx::BColorModifier(
270                                         basegfx::BColor(0.0, 0.0, 0.0),
271                                         0.5,
272                                         basegfx::BCOLORMODIFYMODE_LUMINANCE_TO_ALPHA)));
273 
274                             aMaskTarget = drawinglayer::primitive2d::Primitive2DSequence(&xInverseMask, 1);
275                         }
276 
277                         // prepare new content
278                         drawinglayer::primitive2d::Primitive2DReference xNewContent(
279                             new drawinglayer::primitive2d::TransparencePrimitive2D(
280                                 rTarget,
281                                 aMaskTarget));
282 
283                         // output up to now is defined by aContentRange and mask is oriented
284                         // relative to it. It is possible that aOffscreenBufferRange defines
285                         // a smaller area. In that case, embed to a mask primitive
286                         if(!aOffscreenBufferRange.isInside(aContentRange))
287                         {
288                             xNewContent = new drawinglayer::primitive2d::MaskPrimitive2D(
289                                 basegfx::B2DPolyPolygon(
290                                     basegfx::tools::createPolygonFromRect(
291                                         aOffscreenBufferRange)),
292                                 drawinglayer::primitive2d::Primitive2DSequence(&xNewContent, 1));
293                         }
294 
295                         // redefine target. Use TransparencePrimitive2D with created mask
296                         // geometry
297                         rTarget = drawinglayer::primitive2d::Primitive2DSequence(&xNewContent, 1);
298                     }
299                     else
300                     {
301                         // content is geometrically empty
302                         rTarget.realloc(0);
303                     }
304                 }
305                 else
306                 {
307                     // An empty clipping path will completely clip away the element that had
308                     // the �clip-path� property applied. (Svg spec)
309                     rTarget.realloc(0);
310                 }
311             }
312         }
313 
314     } // end of namespace svgreader
315 } // end of namespace svgio
316 
317 //////////////////////////////////////////////////////////////////////////////
318 // eof
319