Coverage Report

Created: 2026-05-16 09:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svgio/source/svgreader/svgmasknode.cxx
Line
Count
Source
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 <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
#include <o3tl/string_view.hxx>
30
31
namespace svgio::svgreader
32
{
33
        SvgMaskNode::SvgMaskNode(
34
            SvgDocument& rDocument,
35
            SvgNode* pParent)
36
0
        :   SvgNode(SVGToken::Mask, rDocument, pParent),
37
0
            maSvgStyleAttributes(*this),
38
0
            maX(SvgNumber(-10.0, SvgUnit::percent, true)),
39
0
            maY(SvgNumber(-10.0, SvgUnit::percent, true)),
40
0
            maWidth(SvgNumber(120.0, SvgUnit::percent, true)),
41
0
            maHeight(SvgNumber(120.0, SvgUnit::percent, true)),
42
0
            maMaskUnits(SvgUnits::objectBoundingBox),
43
0
            maMaskContentUnits(SvgUnits::userSpaceOnUse)
44
0
        {
45
0
        }
46
47
        SvgMaskNode::~SvgMaskNode()
48
0
        {
49
0
        }
50
51
        const SvgStyleAttributes* SvgMaskNode::getSvgStyleAttributes() const
52
0
        {
53
0
            return &maSvgStyleAttributes;
54
0
        }
55
56
        void SvgMaskNode::parseAttribute(SVGToken aSVGToken, const OUString& aContent)
57
0
        {
58
            // call parent
59
0
            SvgNode::parseAttribute(aSVGToken, aContent);
60
61
            // read style attributes
62
0
            maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
63
64
            // parse own
65
0
            switch(aSVGToken)
66
0
            {
67
0
                case SVGToken::Style:
68
0
                {
69
0
                    readLocalCssStyle(aContent);
70
0
                    break;
71
0
                }
72
0
                case SVGToken::X:
73
0
                {
74
0
                    SvgNumber aNum;
75
76
0
                    if(readSingleNumber(aContent, aNum))
77
0
                    {
78
0
                        maX = aNum;
79
0
                    }
80
0
                    break;
81
0
                }
82
0
                case SVGToken::Y:
83
0
                {
84
0
                    SvgNumber aNum;
85
86
0
                    if(readSingleNumber(aContent, aNum))
87
0
                    {
88
0
                        maY = aNum;
89
0
                    }
90
0
                    break;
91
0
                }
92
0
                case SVGToken::Width:
93
0
                {
94
0
                    SvgNumber aNum;
95
96
0
                    if(readSingleNumber(aContent, aNum))
97
0
                    {
98
0
                        if(aNum.isPositive())
99
0
                        {
100
0
                            maWidth = aNum;
101
0
                        }
102
0
                    }
103
0
                    break;
104
0
                }
105
0
                case SVGToken::Height:
106
0
                {
107
0
                    SvgNumber aNum;
108
109
0
                    if(readSingleNumber(aContent, aNum))
110
0
                    {
111
0
                        if(aNum.isPositive())
112
0
                        {
113
0
                            maHeight = aNum;
114
0
                        }
115
0
                    }
116
0
                    break;
117
0
                }
118
0
                case SVGToken::Transform:
119
0
                {
120
0
                    const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, *this));
121
122
0
                    if(!aMatrix.isIdentity())
123
0
                    {
124
0
                        setTransform(aMatrix);
125
0
                    }
126
0
                    break;
127
0
                }
128
0
                case SVGToken::MaskUnits:
129
0
                {
130
0
                    if(!aContent.isEmpty())
131
0
                    {
132
0
                        if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrUserSpaceOnUse))
133
0
                        {
134
0
                            setMaskUnits(SvgUnits::userSpaceOnUse);
135
0
                        }
136
0
                        else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrObjectBoundingBox))
137
0
                        {
138
0
                            setMaskUnits(SvgUnits::objectBoundingBox);
139
0
                        }
140
0
                    }
141
0
                    break;
142
0
                }
143
0
                case SVGToken::MaskContentUnits:
144
0
                {
145
0
                    if(!aContent.isEmpty())
146
0
                    {
147
0
                        if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrUserSpaceOnUse))
148
0
                        {
149
0
                            setMaskContentUnits(SvgUnits::userSpaceOnUse);
150
0
                        }
151
0
                        else if(o3tl::equalsIgnoreAsciiCase(o3tl::trim(aContent), commonStrings::aStrObjectBoundingBox))
152
0
                        {
153
0
                            setMaskContentUnits(SvgUnits::objectBoundingBox);
154
0
                        }
155
0
                    }
156
0
                    break;
157
0
                }
158
0
                default:
159
0
                {
160
0
                    break;
161
0
                }
162
0
            }
163
0
        }
164
165
        void SvgMaskNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget, bool bReferenced) const
166
0
        {
167
0
            drawinglayer::primitive2d::Primitive2DContainer aNewTarget;
168
169
            // decompose children
170
0
            SvgNode::decomposeSvgNode(aNewTarget, bReferenced);
171
172
0
            if(aNewTarget.empty())
173
0
                return;
174
175
0
            if(getTransform())
176
0
            {
177
                // create embedding group element with transformation
178
0
                aNewTarget = drawinglayer::primitive2d::Primitive2DContainer {
179
0
                        new drawinglayer::primitive2d::TransformPrimitive2D(
180
0
                            *getTransform(),
181
0
                            std::move(aNewTarget))
182
0
                };
183
0
            }
184
185
            // append to current target
186
0
            rTarget.append(aNewTarget);
187
0
        }
188
189
        void SvgMaskNode::apply(
190
            drawinglayer::primitive2d::Primitive2DContainer& rTarget,
191
            const std::optional<basegfx::B2DHomMatrix>& pTransform) const
192
0
        {
193
0
            if(rTarget.empty() || Display::None == getDisplay())
194
0
                return;
195
196
0
            drawinglayer::primitive2d::Primitive2DContainer aMaskTarget;
197
198
            // get mask definition as primitives
199
0
            decomposeSvgNode(aMaskTarget, true);
200
201
0
            if(!aMaskTarget.empty())
202
0
            {
203
                // get range of content to be masked
204
0
                const basegfx::B2DRange aContentRange(
205
0
                        rTarget.getB2DRange(
206
0
                            drawinglayer::geometry::ViewInformation2D()));
207
0
                const double fContentWidth(aContentRange.getWidth());
208
0
                const double fContentHeight(aContentRange.getHeight());
209
210
0
                if(fContentWidth > 0.0 && fContentHeight > 0.0)
211
0
                {
212
                    // create OffscreenBufferRange
213
0
                    basegfx::B2DRange aOffscreenBufferRange;
214
215
0
                    if (SvgUnits::objectBoundingBox == maMaskUnits)
216
0
                    {
217
                        // fractions or percentages of the bounding box of the element to which the mask is applied
218
0
                        const double fX(SvgUnit::percent == getX().getUnit() ? getX().getNumber() * 0.01 : getX().getNumber());
219
0
                        const double fY(SvgUnit::percent == getY().getUnit() ? getY().getNumber() * 0.01 : getY().getNumber());
220
0
                        const double fW(SvgUnit::percent == getWidth().getUnit() ? getWidth().getNumber() * 0.01 : getWidth().getNumber());
221
0
                        const double fH(SvgUnit::percent == getHeight().getUnit() ? getHeight().getNumber() * 0.01 : getHeight().getNumber());
222
223
0
                        aOffscreenBufferRange = basegfx::B2DRange(
224
0
                            aContentRange.getMinX() + (fX * fContentWidth),
225
0
                            aContentRange.getMinY() + (fY * fContentHeight),
226
0
                            aContentRange.getMinX() + ((fX + fW) * fContentWidth),
227
0
                            aContentRange.getMinY() + ((fY + fH) * fContentHeight));
228
0
                    }
229
0
                    else
230
0
                    {
231
0
                        const double fX(getX().isSet() ? getX().solve(*this, NumberType::xcoordinate) : 0.0);
232
0
                        const double fY(getY().isSet() ? getY().solve(*this, NumberType::ycoordinate) : 0.0);
233
234
0
                        aOffscreenBufferRange = basegfx::B2DRange(
235
0
                            fX,
236
0
                            fY,
237
0
                            fX + (getWidth().isSet() ? getWidth().solve(*this, NumberType::xcoordinate) : 0.0),
238
0
                            fY + (getHeight().isSet() ? getHeight().solve(*this, NumberType::ycoordinate) : 0.0));
239
0
                    }
240
241
0
                    if (SvgUnits::objectBoundingBox == maMaskContentUnits)
242
0
                    {
243
                        // mask is object-relative, embed in content transformation
244
0
                        aMaskTarget = drawinglayer::primitive2d::Primitive2DContainer {
245
0
                                new drawinglayer::primitive2d::TransformPrimitive2D(
246
0
                                    basegfx::utils::createScaleTranslateB2DHomMatrix(
247
0
                                        aContentRange.getRange(),
248
0
                                        aContentRange.getMinimum()),
249
0
                                    std::move(aMaskTarget))
250
0
                        };
251
0
                    }
252
0
                    else // userSpaceOnUse
253
0
                    {
254
                        // #i124852#
255
0
                        if(pTransform)
256
0
                        {
257
0
                            aMaskTarget = drawinglayer::primitive2d::Primitive2DContainer {
258
0
                                    new drawinglayer::primitive2d::TransformPrimitive2D(
259
0
                                        *pTransform,
260
0
                                        std::move(aMaskTarget))
261
0
                            };
262
0
                        }
263
0
                    }
264
265
                    // embed content to a ModifiedColorPrimitive2D since the definitions
266
                    // how content is used as alpha is special for Svg
267
0
                    {
268
0
                        aMaskTarget = drawinglayer::primitive2d::Primitive2DContainer {
269
0
                                new drawinglayer::primitive2d::ModifiedColorPrimitive2D(
270
0
                                    std::move(aMaskTarget),
271
0
                                    std::make_shared<basegfx::BColorModifier_luminance_to_alpha>())
272
0
                        };
273
0
                    }
274
275
                    // prepare new content
276
0
                    drawinglayer::primitive2d::Primitive2DReference xNewContent(
277
0
                        new drawinglayer::primitive2d::TransparencePrimitive2D(
278
0
                            std::move(rTarget),
279
0
                            std::move(aMaskTarget)));
280
281
                    // output up to now is defined by aContentRange and mask is oriented
282
                    // relative to it. It is possible that aOffscreenBufferRange defines
283
                    // a smaller area. In that case, embed to a mask primitive
284
0
                    if(!aOffscreenBufferRange.isInside(aContentRange))
285
0
                    {
286
0
                        xNewContent = new drawinglayer::primitive2d::MaskPrimitive2D(
287
0
                            basegfx::B2DPolyPolygon(
288
0
                                basegfx::utils::createPolygonFromRect(
289
0
                                    aOffscreenBufferRange)),
290
0
                            drawinglayer::primitive2d::Primitive2DContainer { xNewContent });
291
0
                    }
292
293
                    // redefine target. Use TransparencePrimitive2D with created mask
294
                    // geometry
295
0
                    rTarget = drawinglayer::primitive2d::Primitive2DContainer { xNewContent };
296
0
                }
297
0
                else
298
0
                {
299
                    // content is geometrically empty
300
0
                    rTarget.clear();
301
0
                }
302
0
            }
303
0
            else
304
0
            {
305
                // An empty clipping path will completely clip away the element that had
306
                // the clip-path property applied. (Svg spec)
307
0
                rTarget.clear();
308
0
            }
309
0
        }
310
311
} // end of namespace svgio::svgreader
312
313
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */