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