Coverage Report

Created: 2026-11-06 11:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/drawinglayer/source/primitive2d/svggradientprimitive2d.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 <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
21
#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
22
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
23
#include <basegfx/matrix/b2dhommatrixtools.hxx>
24
#include <basegfx/polygon/b2dpolygontools.hxx>
25
#include <basegfx/polygon/b2dpolygon.hxx>
26
#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
27
#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
28
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
29
#include <drawinglayer/primitive2d/PolyPolygonRGBAPrimitive2D.hxx>
30
#include <osl/diagnose.h>
31
#include <sal/log.hxx>
32
#include <cmath>
33
#include <utility>
34
#include <vcl/skia/SkiaHelper.hxx>
35
#include <ranges>
36
37
using namespace com::sun::star;
38
39
40
namespace
41
{
42
    sal_uInt32 calculateStepsForSvgGradient(const basegfx::BColor& rColorA, const basegfx::BColor& rColorB, double fDelta, double fDiscreteUnit)
43
0
    {
44
        // use color distance, assume to do every color step (full quality)
45
0
        sal_uInt32 nSteps(basegfx::fround(rColorA.getDistance(rColorB) * 255.0));
46
47
0
        if(nSteps)
48
0
        {
49
            // calc discrete length to change color all 1.5 discrete units (pixels)
50
0
            const sal_uInt32 nDistSteps(basegfx::fround(fDelta / (fDiscreteUnit * 1.5)));
51
52
0
            nSteps = std::min(nSteps, nDistSteps);
53
0
        }
54
55
        // roughly cut when too big or too small
56
0
        nSteps = std::min(nSteps, sal_uInt32(255));
57
0
        nSteps = std::max(nSteps, sal_uInt32(1));
58
59
0
        return nSteps;
60
0
    }
61
} // end of anonymous namespace
62
63
64
namespace drawinglayer::primitive2d
65
{
66
        Primitive2DReference SvgGradientHelper::createSingleGradientEntryFill() const
67
0
        {
68
0
            const SvgGradientEntryVector& rEntries(getGradientEntries());
69
0
            const sal_uInt32 nCount(rEntries.size());
70
71
0
            if(0 == nCount)
72
0
            {
73
                // no entries, done
74
0
                OSL_ENSURE(false, "Single gradient entry construction without entry (!)");
75
0
                return nullptr;
76
0
            }
77
78
0
            const SvgGradientEntry& rSingleEntry(rEntries[nCount - 1]);
79
0
            const double fOpacity(rSingleEntry.getOpacity());
80
81
0
            if (fOpacity <= 0.0 || basegfx::fTools::equalZero(fOpacity))
82
0
            {
83
                // completely opaque, done
84
0
                return nullptr;
85
0
            }
86
87
0
            if (basegfx::fTools::moreOrEqual(fOpacity, 1.0))
88
0
            {
89
                // no opacity
90
0
                return Primitive2DReference {
91
0
                    new PolyPolygonColorPrimitive2D(
92
0
                        getPolyPolygon(),
93
0
                        rSingleEntry.getColor()) };
94
0
            }
95
96
            // if transparent, use PolyPolygonRGBAPrimitive2D
97
0
            return Primitive2DReference {
98
0
                new PolyPolygonRGBAPrimitive2D(
99
0
                    getPolyPolygon(),
100
0
                    rSingleEntry.getColor(),
101
0
                    1.0 - fOpacity) };
102
0
        }
103
104
        void SvgGradientHelper::checkPreconditions()
105
53
        {
106
53
            const SvgGradientEntryVector& rEntries = getGradientEntries();
107
108
53
            if(rEntries.empty())
109
0
            {
110
                // no fill at all, done
111
0
                return;
112
0
            }
113
114
            // sort maGradientEntries by offset, small to big
115
53
            std::sort(maGradientEntries.begin(), maGradientEntries.end());
116
117
            // gradient with at least two colors
118
53
            bool bAllInvisible(true);
119
53
            bool bInvalidEntries(false);
120
121
53
            for(const SvgGradientEntry& rCandidate : rEntries)
122
106
            {
123
106
                if(basegfx::fTools::equalZero(rCandidate.getOpacity()))
124
52
                {
125
                    // invisible
126
52
                    mbFullyOpaque = false;
127
52
                }
128
54
                else if(basegfx::fTools::equal(rCandidate.getOpacity(), 1.0))
129
1
                {
130
                    // completely opaque
131
1
                    bAllInvisible = false;
132
1
                }
133
53
                else
134
53
                {
135
                    // opacity
136
53
                    bAllInvisible = false;
137
53
                    mbFullyOpaque = false;
138
53
                }
139
140
106
                if(!basegfx::fTools::betweenOrEqualEither(rCandidate.getOffset(), 0.0, 1.0))
141
0
                {
142
0
                    bInvalidEntries = true;
143
0
                }
144
106
            }
145
146
53
            if(bAllInvisible)
147
1
            {
148
                // all invisible, nothing to do
149
1
                return;
150
1
            }
151
152
52
            if(bInvalidEntries)
153
0
            {
154
                // invalid entries, do nothing
155
0
                SAL_WARN("drawinglayer", "SvgGradientHelper got invalid SvgGradientEntries outside [0.0 .. 1.0]");
156
0
                return;
157
0
            }
158
159
52
            const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange());
160
161
52
            if(aPolyRange.isEmpty())
162
0
            {
163
                // no range to fill, nothing to do
164
0
                return;
165
0
            }
166
167
52
            const double fPolyWidth(aPolyRange.getWidth());
168
52
            const double fPolyHeight(aPolyRange.getHeight());
169
170
52
            if(basegfx::fTools::equalZero(fPolyWidth) || basegfx::fTools::equalZero(fPolyHeight))
171
52
            {
172
                // no width/height to fill, nothing to do
173
52
                return;
174
52
            }
175
176
0
            mbCreatesContent = true;
177
178
0
            if(1 == rEntries.size())
179
0
            {
180
                // fill with single existing color
181
0
                setSingleEntry();
182
0
            }
183
0
        }
184
185
        const SvgGradientEntry& SvgGradientHelper::FindEntryLessOrEqual(
186
            sal_Int32& rInt,
187
            const double fFrac) const
188
0
        {
189
0
            const bool bMirror(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2);
190
0
            const SvgGradientEntryVector& rCurrent(bMirror ? getMirroredGradientEntries() : getGradientEntries());
191
192
0
            for(SvgGradientEntryVector::const_reverse_iterator aIter(rCurrent.rbegin()); aIter != rCurrent.rend(); ++aIter)
193
0
            {
194
0
               if(basegfx::fTools::lessOrEqual(aIter->getOffset(), fFrac))
195
0
               {
196
0
                   return *aIter;
197
0
               }
198
0
            }
199
200
            // walk over gap to the left, be prepared for missing 0.0/1.0 entries
201
0
            rInt--;
202
0
            const bool bMirror2(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2);
203
0
            const SvgGradientEntryVector& rCurrent2(bMirror2 ? getMirroredGradientEntries() : getGradientEntries());
204
0
            return rCurrent2.back();
205
0
        }
206
207
        const SvgGradientEntry& SvgGradientHelper::FindEntryMore(
208
            sal_Int32& rInt,
209
            const double fFrac) const
210
0
        {
211
0
            const bool bMirror(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2);
212
0
            const SvgGradientEntryVector& rCurrent(bMirror ? getMirroredGradientEntries() : getGradientEntries());
213
214
0
            for(SvgGradientEntryVector::const_iterator aIter(rCurrent.begin()); aIter != rCurrent.end(); ++aIter)
215
0
            {
216
0
               if(basegfx::fTools::more(aIter->getOffset(), fFrac))
217
0
               {
218
0
                   return *aIter;
219
0
               }
220
0
            }
221
222
            // walk over gap to the right, be prepared for missing 0.0/1.0 entries
223
0
            rInt++;
224
0
            const bool bMirror2(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2);
225
0
            const SvgGradientEntryVector& rCurrent2(bMirror2 ? getMirroredGradientEntries() : getGradientEntries());
226
0
            return rCurrent2.front();
227
0
        }
228
229
        // tdf#124424 Adapted creation of color runs to do in a single effort. Previous
230
        // version tried to do this from [0.0 .. 1.0] and to re-use transformed versions
231
        // in the caller if SpreadMethod was on some repeat mode, but had problems when
232
        // e.g. like in the bugdoc from the task a negative-only fStart/fEnd run was
233
        // requested in which case it did nothing. Even when reusing the spread might
234
        // not have been a full one from [0.0 .. 1.0].
235
        // This gets complicated due to mirrored runs, but also for gradient definitions
236
        // with missing entries for 0.0 and 1.0 in which case these have to be guessed
237
        // to be there with same parametrisation as their nearest existing entries. These
238
        // *could* have been added at checkPreconditions() but would then create unnecessary
239
        // spreads on zone overlaps.
240
        void SvgGradientHelper::createRun(
241
            Primitive2DContainer& rTargetColor,
242
            Primitive2DContainer& rTargetOpacity,
243
            double fStart,
244
            double fEnd) const
245
0
        {
246
0
            double fInt(0.0);
247
0
            double fFrac(0.0);
248
0
            double fEnd2(0.0);
249
250
0
            if(SpreadMethod::Pad == getSpreadMethod())
251
0
            {
252
0
                if(fStart < 0.0)
253
0
                {
254
0
                    fFrac = std::modf(fStart, &fInt);
255
0
                    const SvgGradientEntry& rFront(getGradientEntries().front());
256
0
                    const SvgGradientEntry aTemp(1.0 + fFrac, rFront.getColor(), rFront.getOpacity());
257
0
                    createAtom(rTargetColor, rTargetOpacity, aTemp, rFront, static_cast<sal_Int32>(fInt - 1), 0);
258
0
                    fStart = rFront.getOffset();
259
0
                }
260
261
0
                if(fEnd > 1.0)
262
0
                {
263
                    // change fEnd early, but create geometry later (after range below)
264
0
                    fEnd2 = fEnd;
265
0
                    fEnd = getGradientEntries().back().getOffset();
266
0
                }
267
0
            }
268
269
0
            while(fStart < fEnd)
270
0
            {
271
0
                fFrac = std::modf(fStart, &fInt);
272
273
0
                if(fFrac < 0.0)
274
0
                {
275
0
                    fInt -= 1;
276
0
                    fFrac = 1.0 + fFrac;
277
0
                }
278
279
0
                sal_Int32 nIntLeft(static_cast<sal_Int32>(fInt));
280
0
                sal_Int32 nIntRight(nIntLeft);
281
282
0
                const SvgGradientEntry& rLeft(FindEntryLessOrEqual(nIntLeft, fFrac));
283
0
                const SvgGradientEntry& rRight(FindEntryMore(nIntRight, fFrac));
284
0
                createAtom(rTargetColor, rTargetOpacity, rLeft, rRight, nIntLeft, nIntRight);
285
286
0
                const double fNextfStart(static_cast<double>(nIntRight) + rRight.getOffset());
287
288
0
                if(basegfx::fTools::more(fNextfStart, fStart))
289
0
                {
290
0
                    fStart = fNextfStart;
291
0
                }
292
0
                else
293
0
                {
294
0
                    SAL_WARN("drawinglayer", "SvgGradientHelper spread error");
295
0
                    fStart += 1.0;
296
0
                }
297
0
            }
298
299
0
            if(fEnd2 > 1.0)
300
0
            {
301
                // create end run for SpreadMethod::Pad late to keep correct creation order
302
0
                fFrac = std::modf(fEnd2, &fInt);
303
0
                const SvgGradientEntry& rBack(getGradientEntries().back());
304
0
                const SvgGradientEntry aTemp(fFrac, rBack.getColor(), rBack.getOpacity());
305
0
                createAtom(rTargetColor, rTargetOpacity, rBack, aTemp, 0, static_cast<sal_Int32>(fInt));
306
0
            }
307
0
        }
308
309
        Primitive2DReference SvgGradientHelper::createResult(
310
            Primitive2DContainer aTargetColor,
311
            Primitive2DContainer aTargetOpacity,
312
            const basegfx::B2DHomMatrix& rUnitGradientToObject,
313
            bool bInvert) const
314
0
        {
315
0
            Primitive2DContainer aTargetColorEntries(aTargetColor.maybeInvert(bInvert));
316
0
            Primitive2DContainer aTargetOpacityEntries(aTargetOpacity.maybeInvert(bInvert));
317
318
0
            if(aTargetColorEntries.empty())
319
0
                return nullptr;
320
321
0
            Primitive2DReference xRefContent;
322
323
0
            if(!aTargetOpacityEntries.empty())
324
0
            {
325
0
                const Primitive2DReference xRefOpacity = new TransparencePrimitive2D(
326
0
                    std::move(aTargetColorEntries),
327
0
                    std::move(aTargetOpacityEntries));
328
329
0
                xRefContent = new TransformPrimitive2D(
330
0
                    rUnitGradientToObject,
331
0
                    Primitive2DContainer { xRefOpacity });
332
0
            }
333
0
            else
334
0
            {
335
0
                xRefContent = new TransformPrimitive2D(
336
0
                    rUnitGradientToObject,
337
0
                    std::move(aTargetColorEntries));
338
0
            }
339
340
0
            return new MaskPrimitive2D(
341
0
                getPolyPolygon(),
342
0
                Primitive2DContainer { xRefContent });
343
0
        }
344
345
        SvgGradientHelper::SvgGradientHelper(
346
            basegfx::B2DHomMatrix aGradientTransform,
347
            basegfx::B2DPolyPolygon aPolyPolygon,
348
            SvgGradientEntryVector&& rGradientEntries,
349
            const basegfx::B2DPoint& rStart,
350
            bool bUseUnitCoordinates,
351
            SpreadMethod aSpreadMethod)
352
53
        :   maGradientTransform(std::move(aGradientTransform)),
353
53
            maPolyPolygon(std::move(aPolyPolygon)),
354
53
            maGradientEntries(std::move(rGradientEntries)),
355
53
            maStart(rStart),
356
53
            maSpreadMethod(aSpreadMethod),
357
53
            mbCreatesContent(false),
358
53
            mbSingleEntry(false),
359
53
            mbFullyOpaque(true),
360
53
            mbUseUnitCoordinates(bUseUnitCoordinates)
361
53
        {
362
53
        }
363
364
        SvgGradientHelper::~SvgGradientHelper()
365
53
        {
366
53
        }
367
368
        const SvgGradientEntryVector& SvgGradientHelper::getMirroredGradientEntries() const
369
0
        {
370
0
            if(maMirroredGradientEntries.empty() && !getGradientEntries().empty())
371
0
            {
372
0
                const_cast< SvgGradientHelper* >(this)->createMirroredGradientEntries();
373
0
            }
374
375
0
            return maMirroredGradientEntries;
376
0
        }
377
378
        void SvgGradientHelper::createMirroredGradientEntries()
379
0
        {
380
0
            if(!maMirroredGradientEntries.empty() || getGradientEntries().empty())
381
0
                return;
382
383
0
            maMirroredGradientEntries.clear();
384
0
            maMirroredGradientEntries.reserve(getGradientEntries().size());
385
386
0
            const SvgGradientEntryVector& rEntries(getGradientEntries());
387
0
            for(const SvgGradientEntry& rCandidate : std::views::reverse(rEntries))
388
0
            {
389
0
                maMirroredGradientEntries.emplace_back(
390
0
                        1.0 - rCandidate.getOffset(),
391
0
                        rCandidate.getColor(),
392
0
                        rCandidate.getOpacity());
393
0
            }
394
0
        }
395
396
        bool SvgGradientHelper::operator==(const SvgGradientHelper& rSvgGradientHelper) const
397
0
        {
398
0
            const SvgGradientHelper& rCompare = rSvgGradientHelper;
399
400
0
            return (getGradientTransform() == rCompare.getGradientTransform()
401
0
                && getPolyPolygon() == rCompare.getPolyPolygon()
402
0
                && getGradientEntries() == rCompare.getGradientEntries()
403
0
                && getStart() == rCompare.getStart()
404
0
                && getUseUnitCoordinates() == rCompare.getUseUnitCoordinates()
405
0
                && getSpreadMethod() == rCompare.getSpreadMethod());
406
0
        }
407
408
} // end of namespace drawinglayer::primitive2d
409
410
411
namespace drawinglayer::primitive2d
412
{
413
        void SvgLinearGradientPrimitive2D::checkPreconditions()
414
53
        {
415
            // call parent
416
53
            SvgGradientHelper::checkPreconditions();
417
418
53
            if(getCreatesContent())
419
0
            {
420
                // Check Vector
421
0
                const basegfx::B2DVector aVector(getEnd() - getStart());
422
423
0
                if(basegfx::fTools::equalZero(aVector.getX()) && basegfx::fTools::equalZero(aVector.getY()))
424
0
                {
425
                    // fill with single color using last stop color
426
0
                    setSingleEntry();
427
0
                }
428
0
            }
429
53
        }
430
431
        void SvgLinearGradientPrimitive2D::createAtom(
432
            Primitive2DContainer& rTargetColor,
433
            Primitive2DContainer& rTargetOpacity,
434
            const SvgGradientEntry& rFrom,
435
            const SvgGradientEntry& rTo,
436
            sal_Int32 nOffsetFrom,
437
            sal_Int32 nOffsetTo) const
438
0
        {
439
            // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset())
440
0
            if(rFrom.getOffset() == rTo.getOffset())
441
0
            {
442
0
                OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)");
443
0
            }
444
0
            else
445
0
            {
446
0
                rTargetColor.push_back(
447
0
                    new SvgLinearAtomPrimitive2D(
448
0
                        rFrom.getColor(), rFrom.getOffset() + nOffsetFrom,
449
0
                        rTo.getColor(), rTo.getOffset() + nOffsetTo));
450
451
0
                if(!getFullyOpaque())
452
0
                {
453
0
                    const double fTransFrom(1.0 - rFrom.getOpacity());
454
0
                    const double fTransTo(1.0 - rTo.getOpacity());
455
0
                    const basegfx::BColor aColorFrom(fTransFrom, fTransFrom, fTransFrom);
456
0
                    const basegfx::BColor aColorTo(fTransTo, fTransTo, fTransTo);
457
458
0
                    rTargetOpacity.push_back(
459
0
                        new SvgLinearAtomPrimitive2D(
460
0
                            aColorFrom, rFrom.getOffset() + nOffsetFrom,
461
0
                            aColorTo, rTo.getOffset() + nOffsetTo));
462
0
                }
463
0
            }
464
0
        }
465
466
        basegfx::B2DHomMatrix SvgLinearGradientPrimitive2D::createUnitGradientToObjectTransformation() const
467
0
        {
468
0
            const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange());
469
0
            const double fPolyWidth(aPolyRange.getWidth());
470
0
            const double fPolyHeight(aPolyRange.getHeight());
471
472
            // create ObjectTransform based on polygon range
473
0
            const basegfx::B2DHomMatrix aObjectTransform(
474
0
                basegfx::utils::createScaleTranslateB2DHomMatrix(
475
0
                    fPolyWidth, fPolyHeight,
476
0
                    aPolyRange.getMinX(), aPolyRange.getMinY()));
477
0
            basegfx::B2DHomMatrix aUnitGradientToObject;
478
479
0
            if(getUseUnitCoordinates())
480
0
            {
481
                // interpret in unit coordinate system -> object aspect ratio will scale result
482
                // create unit transform from unit vector [0.0 .. 1.0] along the X-Axis to given
483
                // gradient vector defined by Start,End
484
0
                const basegfx::B2DVector aVector(getEnd() - getStart());
485
0
                const double fVectorLength(aVector.getLength());
486
487
0
                aUnitGradientToObject.scale(fVectorLength, 1.0);
488
0
                aUnitGradientToObject.rotate(atan2(aVector.getY(), aVector.getX()));
489
0
                aUnitGradientToObject.translate(getStart().getX(), getStart().getY());
490
491
0
                aUnitGradientToObject *= getGradientTransform();
492
493
                // create full transform from unit gradient coordinates to object coordinates
494
                // including the SvgGradient transformation
495
0
                aUnitGradientToObject *= aObjectTransform;
496
0
            }
497
0
            else
498
0
            {
499
                // interpret in object coordinate system -> object aspect ratio will not scale result
500
0
                const basegfx::B2DPoint aStart(aObjectTransform * getStart());
501
0
                const basegfx::B2DPoint aEnd(aObjectTransform * getEnd());
502
0
                const basegfx::B2DVector aVector(aEnd - aStart);
503
504
0
                aUnitGradientToObject.scale(aVector.getLength(), 1.0);
505
0
                aUnitGradientToObject.rotate(atan2(aVector.getY(), aVector.getX()));
506
0
                aUnitGradientToObject.translate(aStart.getX(), aStart.getY());
507
508
0
                aUnitGradientToObject *= getGradientTransform();
509
0
            }
510
511
0
            return aUnitGradientToObject;
512
0
        }
513
514
        Primitive2DReference SvgLinearGradientPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
515
0
        {
516
0
            if(getSingleEntry())
517
0
            {
518
                // fill with last existing color
519
0
                return createSingleGradientEntryFill();
520
0
            }
521
0
            else if(getCreatesContent())
522
0
            {
523
                // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely
524
                // invisible, width and height to fill are not empty
525
0
                basegfx::B2DHomMatrix aUnitGradientToObject(createUnitGradientToObjectTransformation());
526
527
                // create inverse from it
528
0
                basegfx::B2DHomMatrix aObjectToUnitGradient(aUnitGradientToObject);
529
0
                aObjectToUnitGradient.invert();
530
531
                // back-transform polygon to unit gradient coordinates and get
532
                // UnitRage. This is the range the gradient has to cover
533
0
                basegfx::B2DPolyPolygon aUnitPoly(getPolyPolygon());
534
0
                aUnitPoly.transform(aObjectToUnitGradient);
535
0
                const basegfx::B2DRange aUnitRange(aUnitPoly.getB2DRange());
536
537
                // prepare result vectors
538
0
                Primitive2DContainer aTargetColor;
539
0
                Primitive2DContainer aTargetOpacity;
540
541
0
                if(aUnitRange.getWidth() > 0.0)
542
0
                {
543
                    // add a pre-multiply to aUnitGradientToObject to allow
544
                    // multiplication of the polygon(xl, 0.0, xr, 1.0)
545
0
                    const basegfx::B2DHomMatrix aPreMultiply(
546
0
                        basegfx::utils::createScaleTranslateB2DHomMatrix(
547
0
                            1.0, aUnitRange.getHeight(), 0.0, aUnitRange.getMinY()));
548
0
                    aUnitGradientToObject = aUnitGradientToObject * aPreMultiply;
549
550
                    // create full color run, including all SpreadMethod variants
551
0
                    createRun(
552
0
                        aTargetColor,
553
0
                        aTargetOpacity,
554
0
                        aUnitRange.getMinX(),
555
0
                        aUnitRange.getMaxX());
556
0
                }
557
558
0
                return createResult(std::move(aTargetColor), std::move(aTargetOpacity), aUnitGradientToObject);
559
0
            }
560
0
            return nullptr;
561
0
        }
562
563
        SvgLinearGradientPrimitive2D::SvgLinearGradientPrimitive2D(
564
            const basegfx::B2DHomMatrix& rGradientTransform,
565
            const basegfx::B2DPolyPolygon& rPolyPolygon,
566
            SvgGradientEntryVector&& rGradientEntries,
567
            const basegfx::B2DPoint& rStart,
568
            const basegfx::B2DPoint& rEnd,
569
            bool bUseUnitCoordinates,
570
            SpreadMethod aSpreadMethod)
571
53
        :   SvgGradientHelper(rGradientTransform, rPolyPolygon, std::move(rGradientEntries), rStart, bUseUnitCoordinates, aSpreadMethod),
572
53
            maEnd(rEnd)
573
53
        {
574
            // ensure Preconditions are checked
575
53
            checkPreconditions();
576
53
        }
577
578
        SvgLinearGradientPrimitive2D::~SvgLinearGradientPrimitive2D()
579
53
        {
580
53
        }
581
582
        bool SvgLinearGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
583
0
        {
584
0
            const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive);
585
586
0
            if(pSvgGradientHelper && SvgGradientHelper::operator==(*pSvgGradientHelper))
587
0
            {
588
0
                const SvgLinearGradientPrimitive2D& rCompare = static_cast< const SvgLinearGradientPrimitive2D& >(rPrimitive);
589
590
0
                return (getEnd() == rCompare.getEnd());
591
0
            }
592
593
0
            return false;
594
0
        }
595
596
        basegfx::B2DRange SvgLinearGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
597
53
        {
598
            // return ObjectRange
599
53
            return getPolyPolygon().getB2DRange();
600
53
        }
601
602
        // provide unique ID
603
        sal_uInt32 SvgLinearGradientPrimitive2D::getPrimitive2DID() const
604
0
        {
605
0
            return PRIMITIVE2D_ID_SVGLINEARGRADIENTPRIMITIVE2D;
606
0
        }
607
608
} // end of namespace drawinglayer::primitive2d
609
610
611
namespace drawinglayer::primitive2d
612
{
613
        void SvgRadialGradientPrimitive2D::checkPreconditions()
614
0
        {
615
            // call parent
616
0
            SvgGradientHelper::checkPreconditions();
617
618
0
            if(getCreatesContent())
619
0
            {
620
                // Check Radius
621
0
                if(basegfx::fTools::equalZero(getRadius()))
622
0
                {
623
                    // fill with single color using last stop color
624
0
                    setSingleEntry();
625
0
                }
626
0
            }
627
0
        }
628
629
        void SvgRadialGradientPrimitive2D::createAtom(
630
            Primitive2DContainer& rTargetColor,
631
            Primitive2DContainer& rTargetOpacity,
632
            const SvgGradientEntry& rFrom,
633
            const SvgGradientEntry& rTo,
634
            sal_Int32 nOffsetFrom,
635
            sal_Int32 nOffsetTo) const
636
0
        {
637
            // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset())
638
0
            if(rFrom.getOffset() == rTo.getOffset())
639
0
            {
640
0
                OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)");
641
0
            }
642
0
            else
643
0
            {
644
0
                const double fScaleFrom(rFrom.getOffset() + nOffsetFrom);
645
0
                const double fScaleTo(rTo.getOffset() + nOffsetTo);
646
647
0
                if(isFocalSet())
648
0
                {
649
0
                    const basegfx::B2DVector aFocalVector(getFocal() - getStart());
650
0
                    const basegfx::B2DVector aTranslateFrom(aFocalVector * (maFocalLength - fScaleFrom));
651
0
                    const basegfx::B2DVector aTranslateTo(aFocalVector * (maFocalLength - fScaleTo));
652
653
0
                    rTargetColor.push_back(
654
0
                        new SvgRadialAtomPrimitive2D(
655
0
                            rFrom.getColor(), fScaleFrom, aTranslateFrom,
656
0
                            rTo.getColor(), fScaleTo, aTranslateTo));
657
0
                }
658
0
                else
659
0
                {
660
0
                    rTargetColor.push_back(
661
0
                        new SvgRadialAtomPrimitive2D(
662
0
                            rFrom.getColor(), fScaleFrom,
663
0
                            rTo.getColor(), fScaleTo));
664
0
                }
665
666
0
                if(!getFullyOpaque())
667
0
                {
668
0
                    const double fTransFrom(1.0 - rFrom.getOpacity());
669
0
                    const double fTransTo(1.0 - rTo.getOpacity());
670
0
                    const basegfx::BColor aColorFrom(fTransFrom, fTransFrom, fTransFrom);
671
0
                    const basegfx::BColor aColorTo(fTransTo, fTransTo, fTransTo);
672
673
0
                    if(isFocalSet())
674
0
                    {
675
0
                        const basegfx::B2DVector aFocalVector(getFocal() - getStart());
676
0
                        const basegfx::B2DVector aTranslateFrom(aFocalVector * (maFocalLength - fScaleFrom));
677
0
                        const basegfx::B2DVector aTranslateTo(aFocalVector * (maFocalLength - fScaleTo));
678
679
0
                        rTargetOpacity.push_back(
680
0
                            new SvgRadialAtomPrimitive2D(
681
0
                                aColorFrom, fScaleFrom, aTranslateFrom,
682
0
                                aColorTo, fScaleTo, aTranslateTo));
683
0
                    }
684
0
                    else
685
0
                    {
686
0
                        rTargetOpacity.push_back(
687
0
                            new SvgRadialAtomPrimitive2D(
688
0
                                aColorFrom, fScaleFrom,
689
0
                                aColorTo, fScaleTo));
690
0
                    }
691
0
                }
692
0
            }
693
0
        }
694
695
        basegfx::B2DHomMatrix SvgRadialGradientPrimitive2D::createUnitGradientToObjectTransformation() const
696
0
        {
697
0
            const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange());
698
0
            const double fPolyWidth(aPolyRange.getWidth());
699
0
            const double fPolyHeight(aPolyRange.getHeight());
700
701
            // create ObjectTransform based on polygon range
702
0
            const basegfx::B2DHomMatrix aObjectTransform(
703
0
                basegfx::utils::createScaleTranslateB2DHomMatrix(
704
0
                    fPolyWidth, fPolyHeight,
705
0
                    aPolyRange.getMinX(), aPolyRange.getMinY()));
706
0
            basegfx::B2DHomMatrix aUnitGradientToObject;
707
708
0
            if(getUseUnitCoordinates())
709
0
            {
710
                // interpret in unit coordinate system -> object aspect ratio will scale result
711
                // create unit transform from unit vector to given linear gradient vector
712
0
                aUnitGradientToObject.scale(getRadius(), getRadius());
713
0
                aUnitGradientToObject.translate(getStart().getX(), getStart().getY());
714
715
0
                if(!getGradientTransform().isIdentity())
716
0
                {
717
0
                    aUnitGradientToObject = getGradientTransform() * aUnitGradientToObject;
718
0
                }
719
720
                // create full transform from unit gradient coordinates to object coordinates
721
                // including the SvgGradient transformation
722
0
                aUnitGradientToObject = aObjectTransform * aUnitGradientToObject;
723
0
            }
724
0
            else
725
0
            {
726
                // interpret in object coordinate system -> object aspect ratio will not scale result
727
                // use X-Axis with radius, it was already made relative to object width when coming from
728
                // SVG import
729
0
                const double fRadius((aObjectTransform * basegfx::B2DVector(getRadius(), 0.0)).getLength());
730
0
                const basegfx::B2DPoint aStart(aObjectTransform * getStart());
731
732
0
                aUnitGradientToObject.scale(fRadius, fRadius);
733
0
                aUnitGradientToObject.translate(aStart.getX(), aStart.getY());
734
735
0
                aUnitGradientToObject *= getGradientTransform();
736
0
            }
737
738
0
            return aUnitGradientToObject;
739
0
        }
740
741
        Primitive2DReference SvgRadialGradientPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
742
0
        {
743
0
            if(getSingleEntry())
744
0
            {
745
                // fill with last existing color
746
0
                return createSingleGradientEntryFill();
747
0
            }
748
0
            else if(getCreatesContent())
749
0
            {
750
                // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely
751
                // invisible, width and height to fill are not empty
752
0
                basegfx::B2DHomMatrix aUnitGradientToObject(createUnitGradientToObjectTransformation());
753
754
                // create inverse from it
755
0
                basegfx::B2DHomMatrix aObjectToUnitGradient(aUnitGradientToObject);
756
0
                aObjectToUnitGradient.invert();
757
758
                // back-transform polygon to unit gradient coordinates and get
759
                // UnitRage. This is the range the gradient has to cover
760
0
                basegfx::B2DPolyPolygon aUnitPoly(getPolyPolygon());
761
0
                aUnitPoly.transform(aObjectToUnitGradient);
762
0
                const basegfx::B2DRange aUnitRange(aUnitPoly.getB2DRange());
763
764
                // create range which the gradient has to cover to cover the whole given geometry.
765
                // For circle, go from 0.0 to max radius in all directions (the corners)
766
0
                double fMax(basegfx::B2DVector(aUnitRange.getMinimum()).getLength());
767
0
                fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMaximum()).getLength());
768
0
                fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMinX(), aUnitRange.getMaxY()).getLength());
769
0
                fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMaxX(), aUnitRange.getMinY()).getLength());
770
771
                // prepare result vectors
772
0
                Primitive2DContainer aTargetColor;
773
0
                Primitive2DContainer aTargetOpacity;
774
775
0
                if(0.0 < fMax)
776
0
                {
777
                    // prepare maFocalVector
778
0
                    if(isFocalSet())
779
0
                    {
780
0
                        const_cast< SvgRadialGradientPrimitive2D* >(this)->maFocalLength = fMax;
781
0
                    }
782
783
                    // create full color run, including all SpreadMethod variants
784
0
                    createRun(
785
0
                        aTargetColor,
786
0
                        aTargetOpacity,
787
0
                        0.0,
788
0
                        fMax);
789
0
                }
790
791
0
                return createResult(std::move(aTargetColor), std::move(aTargetOpacity), aUnitGradientToObject, true);
792
0
            }
793
0
            return nullptr;
794
0
        }
795
796
        SvgRadialGradientPrimitive2D::SvgRadialGradientPrimitive2D(
797
            const basegfx::B2DHomMatrix& rGradientTransform,
798
            const basegfx::B2DPolyPolygon& rPolyPolygon,
799
            SvgGradientEntryVector&& rGradientEntries,
800
            const basegfx::B2DPoint& rStart,
801
            double fRadius,
802
            bool bUseUnitCoordinates,
803
            SpreadMethod aSpreadMethod,
804
            const basegfx::B2DPoint* pFocal)
805
0
        :   SvgGradientHelper(rGradientTransform, rPolyPolygon, std::move(rGradientEntries), rStart, bUseUnitCoordinates, aSpreadMethod),
806
0
            mfRadius(fRadius),
807
0
            maFocal(rStart),
808
0
            maFocalLength(0.0)
809
0
        {
810
0
            if(pFocal && !pFocal->equal(getStart()))
811
0
            {
812
0
                maFocal = *pFocal;
813
0
            }
814
815
            // ensure Preconditions are checked
816
0
            checkPreconditions();
817
0
        }
818
819
        SvgRadialGradientPrimitive2D::~SvgRadialGradientPrimitive2D()
820
0
        {
821
0
        }
822
823
        bool SvgRadialGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
824
0
        {
825
0
            const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive);
826
827
0
            if(!pSvgGradientHelper || !SvgGradientHelper::operator==(*pSvgGradientHelper))
828
0
                return false;
829
830
0
            const SvgRadialGradientPrimitive2D& rCompare = static_cast< const SvgRadialGradientPrimitive2D& >(rPrimitive);
831
832
0
            if(getRadius() == rCompare.getRadius())
833
0
            {
834
0
                if(isFocalSet() == rCompare.isFocalSet())
835
0
                {
836
0
                    if(isFocalSet())
837
0
                    {
838
0
                        return getFocal() == rCompare.getFocal();
839
0
                    }
840
0
                    else
841
0
                    {
842
0
                        return true;
843
0
                    }
844
0
                }
845
0
            }
846
847
0
            return false;
848
0
        }
849
850
        basegfx::B2DRange SvgRadialGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
851
0
        {
852
            // return ObjectRange
853
0
            return getPolyPolygon().getB2DRange();
854
0
        }
855
856
        // provide unique ID
857
        sal_uInt32 SvgRadialGradientPrimitive2D::getPrimitive2DID() const
858
0
        {
859
0
            return PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D;
860
0
        }
861
862
} // end of namespace drawinglayer::primitive2d
863
864
865
// SvgLinearAtomPrimitive2D class
866
867
namespace drawinglayer::primitive2d
868
{
869
        Primitive2DReference SvgLinearAtomPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
870
0
        {
871
0
            const double fDelta(getOffsetB() - getOffsetA());
872
873
0
            if(basegfx::fTools::equalZero(fDelta))
874
0
                return nullptr;
875
876
            // use one discrete unit for overlap (one pixel)
877
0
            const double fDiscreteUnit(getDiscreteUnit());
878
879
            // use color distance and discrete lengths to calculate step count
880
0
            const sal_uInt32 nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDelta, fDiscreteUnit));
881
882
            // HACK: Splitting a gradient into adjacent polygons with gradually changing color is silly.
883
            // If antialiasing is used to draw them, the AA-ed adjacent edges won't line up perfectly
884
            // because of the AA (see SkiaSalGraphicsImpl::mergePolyPolygonToPrevious()).
885
            // Make the polygons a bit wider, so they the partial overlap "fixes" this.
886
0
            const double fixup = SkiaHelper::isVCLSkiaEnabled() ? fDiscreteUnit / 2 : 0;
887
888
            // tdf#117949 Use a small amount of discrete overlap at the edges. Usually this
889
            // should be exactly 0.0 and 1.0, but there were cases when this gets clipped
890
            // against the mask polygon which got numerically problematic.
891
            // This change is unnecessary in that respect, but avoids that numerical havoc
892
            // by at the same time doing no real harm AFAIK
893
            // TTTT: Remove again when clipping is fixed (!)
894
895
            // prepare polygon in needed width at start position (with discrete overlap)
896
0
            const basegfx::B2DPolygon aPolygon(
897
0
                basegfx::utils::createPolygonFromRect(
898
0
                    basegfx::B2DRange(
899
0
                        getOffsetA() - fDiscreteUnit,
900
0
                        -0.0001, // TTTT -> should be 0.0, see comment above
901
0
                        getOffsetA() + (fDelta / nSteps) + fDiscreteUnit + fixup,
902
0
                        1.0001))); // TTTT -> should be 1.0, see comment above
903
904
            // prepare loop (inside to outside, [0.0 .. 1.0[)
905
0
            double fUnitScale(0.0);
906
0
            const double fUnitStep(1.0 / nSteps);
907
908
0
            Primitive2DContainer aContainer;
909
0
            aContainer.resize(nSteps);
910
0
            for(sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep)
911
0
            {
912
0
                basegfx::B2DPolygon aNew(aPolygon);
913
914
0
                aNew.translate(fDelta * fUnitScale, 0.0);
915
0
                aContainer[a] = new PolyPolygonColorPrimitive2D(
916
0
                    basegfx::B2DPolyPolygon(aNew),
917
0
                    basegfx::interpolate(getColorA(), getColorB(), fUnitScale));
918
0
            }
919
0
            return new GroupPrimitive2D(std::move(aContainer));
920
0
        }
921
922
        SvgLinearAtomPrimitive2D::SvgLinearAtomPrimitive2D(
923
            const basegfx::BColor& aColorA, double fOffsetA,
924
            const basegfx::BColor& aColorB, double fOffsetB)
925
0
        :   maColorA(aColorA),
926
0
            maColorB(aColorB),
927
0
            mfOffsetA(fOffsetA),
928
0
            mfOffsetB(fOffsetB)
929
0
        {
930
0
            if(mfOffsetA > mfOffsetB)
931
0
            {
932
0
                OSL_ENSURE(false, "Wrong offset order (!)");
933
0
                std::swap(mfOffsetA, mfOffsetB);
934
0
            }
935
0
        }
936
937
        bool SvgLinearAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
938
0
        {
939
0
            if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive))
940
0
            {
941
0
                const SvgLinearAtomPrimitive2D& rCompare = static_cast< const SvgLinearAtomPrimitive2D& >(rPrimitive);
942
943
0
                return (getColorA() == rCompare.getColorA()
944
0
                    && getColorB() == rCompare.getColorB()
945
0
                    && getOffsetA() == rCompare.getOffsetA()
946
0
                    && getOffsetB() == rCompare.getOffsetB());
947
0
            }
948
949
0
            return false;
950
0
        }
951
952
        // provide unique ID
953
        sal_uInt32 SvgLinearAtomPrimitive2D::getPrimitive2DID() const
954
0
        {
955
0
            return PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D;
956
0
        }
957
958
} // end of namespace drawinglayer::primitive2d
959
960
961
// SvgRadialAtomPrimitive2D class
962
963
namespace drawinglayer::primitive2d
964
{
965
        Primitive2DReference SvgRadialAtomPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
966
0
        {
967
0
            const double fDeltaScale(getScaleB() - getScaleA());
968
969
0
            if(basegfx::fTools::equalZero(fDeltaScale))
970
0
                return nullptr;
971
972
            // use one discrete unit for overlap (one pixel)
973
0
            const double fDiscreteUnit(getDiscreteUnit());
974
975
            // use color distance and discrete lengths to calculate step count
976
0
            const sal_uInt32 nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDeltaScale, fDiscreteUnit));
977
978
            // prepare loop ([0.0 .. 1.0[, full polygons, no polypolygons with holes)
979
0
            double fUnitScale(0.0);
980
0
            const double fUnitStep(1.0 / nSteps);
981
982
0
            Primitive2DContainer aContainer;
983
0
            aContainer.resize(nSteps);
984
0
            for(sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep)
985
0
            {
986
0
                basegfx::B2DHomMatrix aTransform;
987
0
                const double fEndScale(getScaleB() - (fDeltaScale * fUnitScale));
988
989
0
                if(isTranslateSet())
990
0
                {
991
0
                    const basegfx::B2DVector aTranslate(
992
0
                        basegfx::interpolate(
993
0
                            getTranslateB(),
994
0
                            getTranslateA(),
995
0
                            fUnitScale));
996
997
0
                    aTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(
998
0
                        fEndScale,
999
0
                        fEndScale,
1000
0
                        aTranslate.getX(),
1001
0
                        aTranslate.getY());
1002
0
                }
1003
0
                else
1004
0
                {
1005
0
                    aTransform = basegfx::utils::createScaleB2DHomMatrix(
1006
0
                        fEndScale,
1007
0
                        fEndScale);
1008
0
                }
1009
1010
0
                basegfx::B2DPolygon aNew(basegfx::utils::createPolygonFromUnitCircle());
1011
1012
0
                aNew.transform(aTransform);
1013
0
                aContainer[a] = new PolyPolygonColorPrimitive2D(
1014
0
                    basegfx::B2DPolyPolygon(aNew),
1015
0
                    basegfx::interpolate(getColorB(), getColorA(), fUnitScale));
1016
0
            }
1017
0
            return new GroupPrimitive2D(std::move(aContainer));
1018
0
        }
1019
1020
        SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D(
1021
            const basegfx::BColor& aColorA, double fScaleA, const basegfx::B2DVector& rTranslateA,
1022
            const basegfx::BColor& aColorB, double fScaleB, const basegfx::B2DVector& rTranslateB)
1023
0
        :   maColorA(aColorA),
1024
0
            maColorB(aColorB),
1025
0
            mfScaleA(fScaleA),
1026
0
            mfScaleB(fScaleB)
1027
0
        {
1028
            // check and evtl. set translations
1029
0
            if(!rTranslateA.equal(rTranslateB))
1030
0
            {
1031
0
                mpTranslate.reset( new VectorPair(rTranslateA, rTranslateB) );
1032
0
            }
1033
1034
            // scale A and B have to be positive
1035
0
            mfScaleA = std::max(mfScaleA, 0.0);
1036
0
            mfScaleB = std::max(mfScaleB, 0.0);
1037
1038
            // scale B has to be bigger than scale A; swap if different
1039
0
            if(mfScaleA > mfScaleB)
1040
0
            {
1041
0
                OSL_ENSURE(false, "Wrong offset order (!)");
1042
0
                std::swap(mfScaleA, mfScaleB);
1043
1044
0
                if(mpTranslate)
1045
0
                {
1046
0
                    std::swap(mpTranslate->maTranslateA, mpTranslate->maTranslateB);
1047
0
                }
1048
0
            }
1049
0
        }
1050
1051
        SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D(
1052
            const basegfx::BColor& aColorA, double fScaleA,
1053
            const basegfx::BColor& aColorB, double fScaleB)
1054
0
        :   maColorA(aColorA),
1055
0
            maColorB(aColorB),
1056
0
            mfScaleA(fScaleA),
1057
0
            mfScaleB(fScaleB)
1058
0
        {
1059
            // scale A and B have to be positive
1060
0
            mfScaleA = std::max(mfScaleA, 0.0);
1061
0
            mfScaleB = std::max(mfScaleB, 0.0);
1062
1063
            // scale B has to be bigger than scale A; swap if different
1064
0
            if(mfScaleA > mfScaleB)
1065
0
            {
1066
0
                OSL_ENSURE(false, "Wrong offset order (!)");
1067
0
                std::swap(mfScaleA, mfScaleB);
1068
0
            }
1069
0
        }
1070
1071
        SvgRadialAtomPrimitive2D::~SvgRadialAtomPrimitive2D()
1072
0
        {
1073
0
        }
1074
1075
        bool SvgRadialAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
1076
0
        {
1077
0
            if(!DiscreteMetricDependentPrimitive2D::operator==(rPrimitive))
1078
0
                return false;
1079
1080
0
            const SvgRadialAtomPrimitive2D& rCompare = static_cast< const SvgRadialAtomPrimitive2D& >(rPrimitive);
1081
1082
0
            if(getColorA() == rCompare.getColorA()
1083
0
                && getColorB() == rCompare.getColorB()
1084
0
                && getScaleA() == rCompare.getScaleA()
1085
0
                && getScaleB() == rCompare.getScaleB())
1086
0
            {
1087
0
                if(isTranslateSet() && rCompare.isTranslateSet())
1088
0
                {
1089
0
                    return (getTranslateA() == rCompare.getTranslateA()
1090
0
                        && getTranslateB() == rCompare.getTranslateB());
1091
0
                }
1092
0
                else if(!isTranslateSet() && !rCompare.isTranslateSet())
1093
0
                {
1094
0
                    return true;
1095
0
                }
1096
0
            }
1097
1098
0
            return false;
1099
0
        }
1100
1101
        // provide unique ID
1102
        sal_uInt32 SvgRadialAtomPrimitive2D::getPrimitive2DID() const
1103
0
        {
1104
0
            return PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D;
1105
0
        }
1106
1107
} // end of namespace
1108
1109
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */