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