Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
5 : * This Source Code Form is subject to the terms of the Mozilla Public
6 : * License, v. 2.0. If a copy of the MPL was not distributed with this
7 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
11 : * Licensed to the Apache Software Foundation (ASF) under one or more
12 : * contributor license agreements. See the NOTICE file distributed
13 : * with this work for additional information regarding copyright
14 : * ownership. The ASF licenses this file to you under the Apache
15 : * License, Version 2.0 (the "License"); you may not use this file
16 : * except in compliance with the License. You may obtain a copy of
17 : * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 : */
19 :
20 : #include <svgio/svgreader/svgmasknode.hxx>
21 : #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
22 : #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
23 : #include <basegfx/matrix/b2dhommatrixtools.hxx>
24 : #include <drawinglayer/geometry/viewinformation2d.hxx>
25 : #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
26 : #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
27 : #include <basegfx/polygon/b2dpolygontools.hxx>
28 : #include <basegfx/polygon/b2dpolygon.hxx>
29 :
30 : namespace svgio
31 : {
32 : namespace svgreader
33 : {
34 189 : SvgMaskNode::SvgMaskNode(
35 : SvgDocument& rDocument,
36 : SvgNode* pParent)
37 : : SvgNode(SVGTokenMask, rDocument, pParent),
38 : maSvgStyleAttributes(*this),
39 : maX(SvgNumber(-10.0, Unit_percent, true)),
40 : maY(SvgNumber(-10.0, Unit_percent, true)),
41 : maWidth(SvgNumber(120.0, Unit_percent, true)),
42 : maHeight(SvgNumber(120.0, Unit_percent, true)),
43 : mpaTransform(0),
44 : maMaskUnits(objectBoundingBox),
45 189 : maMaskContentUnits(userSpaceOnUse)
46 : {
47 189 : }
48 :
49 567 : SvgMaskNode::~SvgMaskNode()
50 : {
51 189 : if(mpaTransform) delete mpaTransform;
52 378 : }
53 :
54 2053 : const SvgStyleAttributes* SvgMaskNode::getSvgStyleAttributes() const
55 : {
56 2053 : return &maSvgStyleAttributes;
57 : }
58 :
59 1134 : void SvgMaskNode::parseAttribute(const OUString& rTokenName, SVGToken aSVGToken, const OUString& aContent)
60 : {
61 : // call parent
62 1134 : SvgNode::parseAttribute(rTokenName, aSVGToken, aContent);
63 :
64 : // read style attributes
65 1134 : maSvgStyleAttributes.parseStyleAttribute(rTokenName, aSVGToken, aContent, false);
66 :
67 : // parse own
68 1134 : switch(aSVGToken)
69 : {
70 : case SVGTokenStyle:
71 : {
72 0 : readLocalCssStyle(aContent);
73 0 : break;
74 : }
75 : case SVGTokenX:
76 : {
77 189 : SvgNumber aNum;
78 :
79 189 : if(readSingleNumber(aContent, aNum))
80 : {
81 189 : setX(aNum);
82 : }
83 189 : break;
84 : }
85 : case SVGTokenY:
86 : {
87 189 : SvgNumber aNum;
88 :
89 189 : if(readSingleNumber(aContent, aNum))
90 : {
91 189 : setY(aNum);
92 : }
93 189 : break;
94 : }
95 : case SVGTokenWidth:
96 : {
97 189 : SvgNumber aNum;
98 :
99 189 : if(readSingleNumber(aContent, aNum))
100 : {
101 189 : if(aNum.isPositive())
102 : {
103 189 : setWidth(aNum);
104 : }
105 : }
106 189 : break;
107 : }
108 : case SVGTokenHeight:
109 : {
110 189 : SvgNumber aNum;
111 :
112 189 : if(readSingleNumber(aContent, aNum))
113 : {
114 189 : if(aNum.isPositive())
115 : {
116 189 : setHeight(aNum);
117 : }
118 : }
119 189 : break;
120 : }
121 : case SVGTokenTransform:
122 : {
123 0 : const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
124 :
125 0 : if(!aMatrix.isIdentity())
126 : {
127 0 : setTransform(&aMatrix);
128 : }
129 0 : break;
130 : }
131 : case SVGTokenMaskUnits:
132 : {
133 189 : if(!aContent.isEmpty())
134 : {
135 189 : if(aContent.match(commonStrings::aStrUserSpaceOnUse, 0))
136 : {
137 189 : setMaskUnits(userSpaceOnUse);
138 : }
139 0 : else if(aContent.match(commonStrings::aStrObjectBoundingBox, 0))
140 : {
141 0 : setMaskUnits(objectBoundingBox);
142 : }
143 : }
144 189 : break;
145 : }
146 : case SVGTokenMaskContentUnits:
147 : {
148 0 : if(!aContent.isEmpty())
149 : {
150 0 : if(aContent.match(commonStrings::aStrUserSpaceOnUse, 0))
151 : {
152 0 : setMaskContentUnits(userSpaceOnUse);
153 : }
154 0 : else if(aContent.match(commonStrings::aStrObjectBoundingBox, 0))
155 : {
156 0 : setMaskContentUnits(objectBoundingBox);
157 : }
158 : }
159 0 : break;
160 : }
161 : default:
162 : {
163 189 : break;
164 : }
165 : }
166 1134 : }
167 :
168 342 : void SvgMaskNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DSequence& rTarget, bool bReferenced) const
169 : {
170 342 : drawinglayer::primitive2d::Primitive2DSequence aNewTarget;
171 :
172 : // decompose children
173 342 : SvgNode::decomposeSvgNode(aNewTarget, bReferenced);
174 :
175 342 : if(aNewTarget.hasElements())
176 : {
177 151 : if(getTransform())
178 : {
179 : // create embedding group element with transformation
180 : const drawinglayer::primitive2d::Primitive2DReference xRef(
181 : new drawinglayer::primitive2d::TransformPrimitive2D(
182 : *getTransform(),
183 0 : aNewTarget));
184 :
185 0 : aNewTarget = drawinglayer::primitive2d::Primitive2DSequence(&xRef, 1);
186 : }
187 :
188 : // append to current target
189 151 : drawinglayer::primitive2d::appendPrimitive2DSequenceToPrimitive2DSequence(rTarget, aNewTarget);
190 342 : }
191 342 : }
192 :
193 153 : void SvgMaskNode::apply(
194 : drawinglayer::primitive2d::Primitive2DSequence& rTarget,
195 : const basegfx::B2DHomMatrix* pTransform) const
196 : {
197 153 : if(rTarget.hasElements() && Display_none != getDisplay())
198 : {
199 153 : drawinglayer::primitive2d::Primitive2DSequence aMaskTarget;
200 :
201 : // get mask definition as primitives
202 153 : decomposeSvgNode(aMaskTarget, true);
203 :
204 153 : if(aMaskTarget.hasElements())
205 : {
206 : // get range of content to be masked
207 : const basegfx::B2DRange aContentRange(
208 : drawinglayer::primitive2d::getB2DRangeFromPrimitive2DSequence(
209 : rTarget,
210 151 : drawinglayer::geometry::ViewInformation2D()));
211 151 : const double fContentWidth(aContentRange.getWidth());
212 151 : const double fContentHeight(aContentRange.getHeight());
213 :
214 151 : if(fContentWidth > 0.0 && fContentHeight > 0.0)
215 : {
216 : // create OffscreenBufferRange
217 151 : basegfx::B2DRange aOffscreenBufferRange;
218 :
219 151 : if(objectBoundingBox == getMaskUnits())
220 : {
221 : // fractions or percentages of the bounding box of the element to which the mask is applied
222 0 : const double fX(Unit_percent == getX().getUnit() ? getX().getNumber() * 0.01 : getX().getNumber());
223 0 : const double fY(Unit_percent == getY().getUnit() ? getY().getNumber() * 0.01 : getY().getNumber());
224 0 : const double fW(Unit_percent == getWidth().getUnit() ? getWidth().getNumber() * 0.01 : getWidth().getNumber());
225 0 : const double fH(Unit_percent == getHeight().getUnit() ? getHeight().getNumber() * 0.01 : getHeight().getNumber());
226 :
227 : aOffscreenBufferRange = basegfx::B2DRange(
228 0 : aContentRange.getMinX() + (fX * fContentWidth),
229 0 : aContentRange.getMinY() + (fY * fContentHeight),
230 0 : aContentRange.getMinX() + ((fX + fW) * fContentWidth),
231 0 : aContentRange.getMinY() + ((fY + fH) * fContentHeight));
232 : }
233 : else
234 : {
235 151 : const double fX(getX().isSet() ? getX().solve(*this, xcoordinate) : 0.0);
236 151 : const double fY(getY().isSet() ? getY().solve(*this, ycoordinate) : 0.0);
237 :
238 : aOffscreenBufferRange = basegfx::B2DRange(
239 : fX,
240 : fY,
241 302 : fX + (getWidth().isSet() ? getWidth().solve(*this, xcoordinate) : 0.0),
242 453 : fY + (getHeight().isSet() ? getHeight().solve(*this, ycoordinate) : 0.0));
243 : }
244 :
245 151 : if(objectBoundingBox == getMaskContentUnits())
246 : {
247 : // mask is object-relative, embed in content transformation
248 : const drawinglayer::primitive2d::Primitive2DReference xTransform(
249 : new drawinglayer::primitive2d::TransformPrimitive2D(
250 : basegfx::tools::createScaleTranslateB2DHomMatrix(
251 : aContentRange.getRange(),
252 : aContentRange.getMinimum()),
253 0 : aMaskTarget));
254 :
255 0 : aMaskTarget = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1);
256 : }
257 : else // userSpaceOnUse
258 : {
259 : // #i124852#
260 151 : if(pTransform)
261 : {
262 : const drawinglayer::primitive2d::Primitive2DReference xTransform(
263 : new drawinglayer::primitive2d::TransformPrimitive2D(
264 : *pTransform,
265 25 : aMaskTarget));
266 :
267 25 : aMaskTarget = drawinglayer::primitive2d::Primitive2DSequence(&xTransform, 1);
268 : }
269 : }
270 :
271 : // embed content to a ModifiedColorPrimitive2D since the definitions
272 : // how content is used as alpha is special for Svg
273 : {
274 : const drawinglayer::primitive2d::Primitive2DReference xInverseMask(
275 : new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
276 : aMaskTarget,
277 : basegfx::BColorModifierSharedPtr(
278 151 : new basegfx::BColorModifier_luminance_to_alpha())));
279 :
280 151 : aMaskTarget = drawinglayer::primitive2d::Primitive2DSequence(&xInverseMask, 1);
281 : }
282 :
283 : // prepare new content
284 : drawinglayer::primitive2d::Primitive2DReference xNewContent(
285 : new drawinglayer::primitive2d::TransparencePrimitive2D(
286 : rTarget,
287 151 : aMaskTarget));
288 :
289 : // output up to now is defined by aContentRange and mask is oriented
290 : // relative to it. It is possible that aOffscreenBufferRange defines
291 : // a smaller area. In that case, embed to a mask primitive
292 151 : if(!aOffscreenBufferRange.isInside(aContentRange))
293 : {
294 408 : xNewContent = new drawinglayer::primitive2d::MaskPrimitive2D(
295 : basegfx::B2DPolyPolygon(
296 : basegfx::tools::createPolygonFromRect(
297 : aOffscreenBufferRange)),
298 272 : drawinglayer::primitive2d::Primitive2DSequence(&xNewContent, 1));
299 : }
300 :
301 : // redefine target. Use TransparencePrimitive2D with created mask
302 : // geometry
303 151 : rTarget = drawinglayer::primitive2d::Primitive2DSequence(&xNewContent, 1);
304 : }
305 : else
306 : {
307 : // content is geometrically empty
308 0 : rTarget.realloc(0);
309 : }
310 : }
311 : else
312 : {
313 : // An empty clipping path will completely clip away the element that had
314 : // the clip-path property applied. (Svg spec)
315 2 : rTarget.realloc(0);
316 153 : }
317 : }
318 153 : }
319 :
320 : } // end of namespace svgreader
321 : } // end of namespace svgio
322 :
323 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|