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())
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