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                 static bool bInterpretAbsolute(true);
406 
407                 if(getUseUnitCoordinates())
408                 {
409                     // interpret in unit coordinate system -> object aspect ratio will scale result
410                     // create unit transform from unit vector [0.0 .. 1.0] along the X-Axis to given
411                     // gradient vector defined by Start,End
412                     const basegfx::B2DVector aVector(getEnd() - getStart());
413                     const double fVectorLength(aVector.getLength());
414                     basegfx::B2DHomMatrix aUnitGradientToGradient;
415 
416                     aUnitGradientToGradient.scale(fVectorLength, 1.0);
417                     aUnitGradientToGradient.rotate(atan2(aVector.getY(), aVector.getX()));
418                     aUnitGradientToGradient.translate(getStart().getX(), getStart().getY());
419 
420                     // create full transform from unit gradient coordinates to object coordinates
421                     // including the SvgGradient transformation
422                     aUnitGradientToObject = aObjectTransform * aUnitGradientToGradient;
423                 }
424                 else
425                 {
426                     // interpret in object coordinate system -> object aspect ratio will not scale result
427                     const basegfx::B2DPoint aStart(aObjectTransform * getStart());
428                     const basegfx::B2DPoint aEnd(aObjectTransform * getEnd());
429                     const basegfx::B2DVector aVector(aEnd - aStart);
430 
431                     aUnitGradientToObject.scale(aVector.getLength(), 1.0);
432                     aUnitGradientToObject.rotate(atan2(aVector.getY(), aVector.getX()));
433                     aUnitGradientToObject.translate(aStart.getX(), aStart.getY());
434                 }
435 
436                 // create inverse from it
437                 basegfx::B2DHomMatrix aObjectToUnitGradient(aUnitGradientToObject);
438                 aObjectToUnitGradient.invert();
439 
440                 // back-transform polygon to unit gradient coordinates and get
441                 // UnitRage. This is the range the gradient has to cover
442                 basegfx::B2DPolyPolygon aUnitPoly(getPolyPolygon());
443                 aUnitPoly.transform(aObjectToUnitGradient);
444                 const basegfx::B2DRange aUnitRange(aUnitPoly.getB2DRange());
445 
446                 // prepare result vectors
447                 Primitive2DVector aTargetColor;
448                 Primitive2DVector aTargetOpacity;
449 
450                 if(basegfx::fTools::more(aUnitRange.getWidth(), 0.0))
451                 {
452                     // add a pre-multiply to aUnitGradientToObject to allow
453                     // multiplication of the polygon(xl, 0.0, xr, 1.0)
454                     const basegfx::B2DHomMatrix aPreMultiply(
455                         basegfx::tools::createScaleTranslateB2DHomMatrix(
456                             1.0, aUnitRange.getHeight(), 0.0, aUnitRange.getMinY()));
457                     aUnitGradientToObject = aUnitGradientToObject * aPreMultiply;
458 
459                     // create central run, may also already do all necessary when
460                     // Spread_pad is set as SpreadMethod and/or the range is smaller
461                     double fPos(createRun(aTargetColor, aTargetOpacity, aUnitRange.getMinX(), aUnitRange.getMaxX(), getGradientEntries(), 0));
462 
463                     if(fPos < aUnitRange.getMaxX())
464                     {
465                         // can only happen when SpreadMethod is Spread_reflect or Spread_repeat,
466                         // else the start and end pads are already created and fPos == aUnitRange.getMaxX().
467                         // Its possible to express the repeated linear gradient by adding the
468                         // transformed central run. Crete it this way
469                         Primitive2DSequence aTargetColorEntries(Primitive2DVectorToPrimitive2DSequence(aTargetColor));
470                         Primitive2DSequence aTargetOpacityEntries(Primitive2DVectorToPrimitive2DSequence(aTargetOpacity));
471                         aTargetColor.clear();
472                         aTargetOpacity.clear();
473 
474                         if(aTargetColorEntries.hasElements())
475                         {
476                             // add original central run as group primitive
477                             aTargetColor.push_back(new GroupPrimitive2D(aTargetColorEntries));
478 
479                             if(aTargetOpacityEntries.hasElements())
480                             {
481                                 aTargetOpacity.push_back(new GroupPrimitive2D(aTargetOpacityEntries));
482                             }
483 
484                             // add negative runs
485                             fPos = 0.0;
486                             sal_Int32 nOffset(0);
487 
488                             while(fPos > aUnitRange.getMinX())
489                             {
490                                 fPos -= 1.0;
491                                 nOffset++;
492 
493                                 basegfx::B2DHomMatrix aTransform;
494                                 const bool bMirror(Spread_reflect == getSpreadMethod() && (nOffset % 2));
495 
496                                 if(bMirror)
497                                 {
498                                     aTransform.scale(-1.0, 1.0);
499                                     aTransform.translate(fPos + 1.0, 0.0);
500                                 }
501                                 else
502                                 {
503                                     aTransform.translate(fPos, 0.0);
504                                 }
505 
506                                 aTargetColor.push_back(new TransformPrimitive2D(aTransform, aTargetColorEntries));
507 
508                                 if(aTargetOpacityEntries.hasElements())
509                                 {
510                                     aTargetOpacity.push_back(new TransformPrimitive2D(aTransform, aTargetOpacityEntries));
511                                 }
512                             }
513 
514                             // add positive runs
515                             fPos = 1.0;
516                             nOffset = 1;
517 
518                             while(fPos < aUnitRange.getMaxX())
519                             {
520                                 basegfx::B2DHomMatrix aTransform;
521                                 const bool bMirror(Spread_reflect == getSpreadMethod() && (nOffset % 2));
522 
523                                 if(bMirror)
524                                 {
525                                     aTransform.scale(-1.0, 1.0);
526                                     aTransform.translate(fPos + 1.0, 0.0);
527                                 }
528                                 else
529                                 {
530                                     aTransform.translate(fPos, 0.0);
531                                 }
532 
533                                 aTargetColor.push_back(new TransformPrimitive2D(aTransform, aTargetColorEntries));
534 
535                                 if(aTargetOpacityEntries.hasElements())
536                                 {
537                                     aTargetOpacity.push_back(new TransformPrimitive2D(aTransform, aTargetOpacityEntries));
538                                 }
539 
540                                 fPos += 1.0;
541                                 nOffset++;
542                             }
543                         }
544                     }
545                 }
546 
547                 xRetval = createResult(aTargetColor, aTargetOpacity, aUnitGradientToObject);
548             }
549 
550             return xRetval;
551         }
552 
553         SvgLinearGradientPrimitive2D::SvgLinearGradientPrimitive2D(
554             const basegfx::B2DPolyPolygon& rPolyPolygon,
555             const SvgGradientEntryVector& rGradientEntries,
556             const basegfx::B2DPoint& rStart,
557             const basegfx::B2DPoint& rEnd,
558             bool bUseUnitCoordinates,
559             SpreadMethod aSpreadMethod)
560         :   BufferedDecompositionPrimitive2D(),
561             SvgGradientHelper(rPolyPolygon, rGradientEntries, rStart, bUseUnitCoordinates, aSpreadMethod),
562             maEnd(rEnd)
563         {
564         }
565 
566         SvgLinearGradientPrimitive2D::~SvgLinearGradientPrimitive2D()
567         {
568         }
569 
570         bool SvgLinearGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
571         {
572             const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive);
573 
574             if(pSvgGradientHelper && SvgGradientHelper::operator==(*pSvgGradientHelper))
575             {
576                 const SvgLinearGradientPrimitive2D& rCompare = static_cast< const SvgLinearGradientPrimitive2D& >(rPrimitive);
577 
578                 return (getEnd() == rCompare.getEnd());
579             }
580 
581             return false;
582         }
583 
584         basegfx::B2DRange SvgLinearGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
585         {
586             // return ObjectRange
587             return getPolyPolygon().getB2DRange();
588         }
589 
590         // provide unique ID
591         ImplPrimitrive2DIDBlock(SvgLinearGradientPrimitive2D, PRIMITIVE2D_ID_SVGLINEARGRADIENTPRIMITIVE2D)
592 
593     } // end of namespace primitive2d
594 } // end of namespace drawinglayer
595 
596 //////////////////////////////////////////////////////////////////////////////
597 
598 namespace drawinglayer
599 {
600     namespace primitive2d
601     {
602         void SvgRadialGradientPrimitive2D::checkPreconditions()
603         {
604             // call parent
605             SvgGradientHelper::checkPreconditions();
606 
607             if(getCreatesContent())
608             {
609                 // Check Radius
610                 if(basegfx::fTools::equalZero(getRadius()))
611                 {
612                     // fill with single color using last stop color
613                     setSingleEntry();
614                 }
615             }
616         }
617 
618         void SvgRadialGradientPrimitive2D::createAtom(
619             Primitive2DVector& rTargetColor,
620             Primitive2DVector& rTargetOpacity,
621             const SvgGradientEntry& rFrom,
622             const SvgGradientEntry& rTo,
623             sal_Int32 nOffset) const
624         {
625             // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset())
626             if(rFrom.getOffset() == rTo.getOffset())
627             {
628                 OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)");
629             }
630             else
631             {
632                 const double fScaleFrom(rFrom.getOffset() + nOffset);
633                 const double fScaleTo(rTo.getOffset() + nOffset);
634 
635                 if(isFocalSet())
636                 {
637                     const basegfx::B2DVector aTranslateFrom(maFocalVector * (maFocalLength - fScaleFrom));
638                     const basegfx::B2DVector aTranslateTo(maFocalVector * (maFocalLength - fScaleTo));
639 
640                     rTargetColor.push_back(
641                         new SvgRadialAtomPrimitive2D(
642                             rFrom.getColor(), fScaleFrom, aTranslateFrom,
643                             rTo.getColor(), fScaleTo, aTranslateTo));
644                 }
645                 else
646                 {
647                     rTargetColor.push_back(
648                         new SvgRadialAtomPrimitive2D(
649                             rFrom.getColor(), fScaleFrom,
650                             rTo.getColor(), fScaleTo));
651                 }
652 
653                 if(!getFullyOpaque())
654                 {
655                     const double fTransFrom(1.0 - rFrom.getOpacity());
656                     const double fTransTo(1.0 - rTo.getOpacity());
657                     const basegfx::BColor aColorFrom(fTransFrom, fTransFrom, fTransFrom);
658                     const basegfx::BColor aColorTo(fTransTo, fTransTo, fTransTo);
659 
660                     if(isFocalSet())
661                     {
662                         const basegfx::B2DVector aTranslateFrom(maFocalVector * (maFocalLength - fScaleFrom));
663                         const basegfx::B2DVector aTranslateTo(maFocalVector * (maFocalLength - fScaleTo));
664 
665                         rTargetOpacity.push_back(
666                             new SvgRadialAtomPrimitive2D(
667                                 aColorFrom, fScaleFrom, aTranslateFrom,
668                                 aColorTo, fScaleTo, aTranslateTo));
669                     }
670                     else
671                     {
672                         rTargetOpacity.push_back(
673                             new SvgRadialAtomPrimitive2D(
674                                 aColorFrom, fScaleFrom,
675                                 aColorTo, fScaleTo));
676                     }
677                 }
678             }
679         }
680 
681         const SvgGradientEntryVector& SvgRadialGradientPrimitive2D::getMirroredGradientEntries() const
682         {
683             if(maMirroredGradientEntries.empty() && !getGradientEntries().empty())
684             {
685                 const_cast< SvgRadialGradientPrimitive2D* >(this)->createMirroredGradientEntries();
686             }
687 
688             return maMirroredGradientEntries;
689         }
690 
691         void SvgRadialGradientPrimitive2D::createMirroredGradientEntries()
692         {
693             if(maMirroredGradientEntries.empty() && !getGradientEntries().empty())
694             {
695                 const sal_uInt32 nCount(getGradientEntries().size());
696                 maMirroredGradientEntries.clear();
697                 maMirroredGradientEntries.reserve(nCount);
698 
699                 for(sal_uInt32 a(0); a < nCount; a++)
700                 {
701                     const SvgGradientEntry& rCandidate = getGradientEntries()[nCount - 1 - a];
702 
703                     maMirroredGradientEntries.push_back(
704                         SvgGradientEntry(
705                             1.0 - rCandidate.getOffset(),
706                             rCandidate.getColor(),
707                             rCandidate.getOpacity()));
708                 }
709             }
710         }
711 
712         Primitive2DSequence SvgRadialGradientPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
713         {
714             Primitive2DSequence xRetval;
715 
716             if(!getPreconditionsChecked())
717             {
718                 const_cast< SvgRadialGradientPrimitive2D* >(this)->checkPreconditions();
719             }
720 
721             if(getSingleEntry())
722             {
723                 // fill with last existing color
724                 xRetval = createSingleGradientEntryFill();
725             }
726             else if(getCreatesContent())
727             {
728                 // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely
729                 // invisible, width and height to fill are not empty
730                 const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange());
731                 const double fPolyWidth(aPolyRange.getWidth());
732                 const double fPolyHeight(aPolyRange.getHeight());
733 
734                 // create ObjectTransform based on polygon range
735                 const basegfx::B2DHomMatrix aObjectTransform(
736                     basegfx::tools::createScaleTranslateB2DHomMatrix(
737                         fPolyWidth, fPolyHeight,
738                         aPolyRange.getMinX(), aPolyRange.getMinY()));
739                 basegfx::B2DHomMatrix aUnitGradientToObject;
740                 static bool bInterpretAbsolute(true);
741 
742                 if(getUseUnitCoordinates())
743                 {
744                     // interpret in unit coordinate system -> object aspect ratio will scale result
745                     // create unit transform from unit vector to given linear gradient vector
746                     basegfx::B2DHomMatrix aUnitGradientToGradient;
747 
748                     aUnitGradientToGradient.scale(getRadius(), getRadius());
749                     aUnitGradientToGradient.translate(getStart().getX(), getStart().getY());
750 
751                     // create full transform from unit gradient coordinates to object coordinates
752                     // including the SvgGradient transformation
753                     aUnitGradientToObject = aObjectTransform * aUnitGradientToGradient;
754                 }
755                 else
756                 {
757                     // interpret in object coordinate system -> object aspect ratio will not scale result
758                     const double fRadius((aObjectTransform * basegfx::B2DVector(getRadius(), 0.0)).getLength());
759                     const basegfx::B2DPoint aStart(aObjectTransform * getStart());
760 
761                     aUnitGradientToObject.scale(fRadius, fRadius);
762                     aUnitGradientToObject.translate(aStart.getX(), aStart.getY());
763                 }
764 
765                 // create inverse from it
766                 basegfx::B2DHomMatrix aObjectToUnitGradient(aUnitGradientToObject);
767                 aObjectToUnitGradient.invert();
768 
769                 // back-transform polygon to unit gradient coordinates and get
770                 // UnitRage. This is the range the gradient has to cover
771                 basegfx::B2DPolyPolygon aUnitPoly(getPolyPolygon());
772                 aUnitPoly.transform(aObjectToUnitGradient);
773                 const basegfx::B2DRange aUnitRange(aUnitPoly.getB2DRange());
774 
775                 // create range which the gradient has to cover to cover the whole given geometry.
776                 // For circle, go from 0.0 to max radius in all directions (the corners)
777                 double fMax(basegfx::B2DVector(aUnitRange.getMinimum()).getLength());
778                 fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMaximum()).getLength());
779                 fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMinX(), aUnitRange.getMaxY()).getLength());
780                 fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMaxX(), aUnitRange.getMinY()).getLength());
781 
782                 // prepare result vectors
783                 Primitive2DVector aTargetColor;
784                 Primitive2DVector aTargetOpacity;
785 
786                 if(0.0 < fMax)
787                 {
788                     // prepare maFocalVector
789                     if(isFocalSet())
790                     {
791                         const_cast< SvgRadialGradientPrimitive2D* >(this)->maFocalLength = fMax;
792                     }
793 
794                     // create central run, may also already do all necessary when
795                     // Spread_pad is set as SpreadMethod and/or the range is smaller
796                     double fPos(createRun(aTargetColor, aTargetOpacity, 0.0, fMax, getGradientEntries(), 0));
797 
798                     if(fPos < fMax)
799                     {
800                         // can only happen when SpreadMethod is Spread_reflect or Spread_repeat,
801                         // else the start and end pads are already created and fPos == fMax.
802                         // For radial there is no way to transform the already created
803                         // central run, it needs to be created from 1.0 to fMax
804                         sal_Int32 nOffset(1);
805 
806                         while(fPos < fMax)
807                         {
808                             const bool bMirror(Spread_reflect == getSpreadMethod() && (nOffset % 2));
809 
810                             if(bMirror)
811                             {
812                                 createRun(aTargetColor, aTargetOpacity, 0.0, fMax, getMirroredGradientEntries(), nOffset);
813                             }
814                             else
815                             {
816                                 createRun(aTargetColor, aTargetOpacity, 0.0, fMax, getGradientEntries(), nOffset);
817                             }
818 
819                             nOffset++;
820                             fPos += 1.0;
821                         }
822                     }
823                 }
824 
825                 xRetval = createResult(aTargetColor, aTargetOpacity, aUnitGradientToObject, true);
826             }
827 
828             return xRetval;
829         }
830 
831         SvgRadialGradientPrimitive2D::SvgRadialGradientPrimitive2D(
832             const basegfx::B2DPolyPolygon& rPolyPolygon,
833             const SvgGradientEntryVector& rGradientEntries,
834             const basegfx::B2DPoint& rStart,
835             double fRadius,
836             bool bUseUnitCoordinates,
837             SpreadMethod aSpreadMethod,
838             const basegfx::B2DPoint* pFocal)
839         :   BufferedDecompositionPrimitive2D(),
840             SvgGradientHelper(rPolyPolygon, rGradientEntries, rStart, bUseUnitCoordinates, aSpreadMethod),
841             mfRadius(fRadius),
842             maFocal(rStart),
843             maFocalVector(0.0, 0.0),
844             maFocalLength(0.0),
845             maMirroredGradientEntries(),
846             mbFocalSet(false)
847         {
848             if(pFocal && !pFocal->equal(getStart()))
849             {
850                 maFocal = *pFocal;
851                 maFocalVector = maFocal - getStart();
852                 mbFocalSet = true;
853             }
854         }
855 
856         SvgRadialGradientPrimitive2D::~SvgRadialGradientPrimitive2D()
857         {
858         }
859 
860         bool SvgRadialGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
861         {
862             const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive);
863 
864             if(pSvgGradientHelper && SvgGradientHelper::operator==(*pSvgGradientHelper))
865             {
866                 const SvgRadialGradientPrimitive2D& rCompare = static_cast< const SvgRadialGradientPrimitive2D& >(rPrimitive);
867 
868                 if(getRadius() == rCompare.getRadius())
869                 {
870                     if(isFocalSet() == rCompare.isFocalSet())
871                     {
872                         if(isFocalSet())
873                         {
874                             return getFocal() == rCompare.getFocal();
875                         }
876                         else
877                         {
878                             return true;
879                         }
880                     }
881                 }
882             }
883 
884             return false;
885         }
886 
887         basegfx::B2DRange SvgRadialGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
888         {
889             // return ObjectRange
890             return getPolyPolygon().getB2DRange();
891         }
892 
893         // provide unique ID
894         ImplPrimitrive2DIDBlock(SvgRadialGradientPrimitive2D, PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D)
895 
896     } // end of namespace primitive2d
897 } // end of namespace drawinglayer
898 
899 //////////////////////////////////////////////////////////////////////////////
900 // SvgLinearAtomPrimitive2D class
901 
902 namespace drawinglayer
903 {
904     namespace primitive2d
905     {
906         Primitive2DSequence SvgLinearAtomPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
907         {
908             Primitive2DSequence xRetval;
909             const double fDelta(getOffsetB() - getOffsetA());
910 
911             if(!basegfx::fTools::equalZero(fDelta))
912             {
913                 // use one discrete unit for overlap (one pixel)
914                 const double fDiscreteUnit(getDiscreteUnit());
915 
916                 // use color distance and discrete lengths to calculate step count
917                 const sal_uInt32 nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDelta, fDiscreteUnit));
918 
919                 // prepare polygon in needed width at start position (with discrete overlap)
920                 const basegfx::B2DPolygon aPolygon(
921                     basegfx::tools::createPolygonFromRect(
922                         basegfx::B2DRange(
923                             getOffsetA() - fDiscreteUnit,
924                             0.0,
925                             getOffsetA() + (fDelta / nSteps) + fDiscreteUnit,
926                             1.0)));
927 
928                 // prepare loop (inside to outside, [0.0 .. 1.0[)
929                 double fUnitScale(0.0);
930                 const double fUnitStep(1.0 / nSteps);
931 
932                 // prepare result set (known size)
933                 xRetval.realloc(nSteps);
934 
935                 for(sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep)
936                 {
937                     basegfx::B2DPolygon aNew(aPolygon);
938 
939                     aNew.transform(basegfx::tools::createTranslateB2DHomMatrix(fDelta * fUnitScale, 0.0));
940                     xRetval[a] = new PolyPolygonColorPrimitive2D(
941                         basegfx::B2DPolyPolygon(aNew),
942                         basegfx::interpolate(getColorA(), getColorB(), fUnitScale));
943                 }
944             }
945 
946             return xRetval;
947         }
948 
949         SvgLinearAtomPrimitive2D::SvgLinearAtomPrimitive2D(
950             const basegfx::BColor& aColorA, double fOffsetA,
951             const basegfx::BColor& aColorB, double fOffsetB)
952         :   DiscreteMetricDependentPrimitive2D(),
953             maColorA(aColorA),
954             maColorB(aColorB),
955             mfOffsetA(fOffsetA),
956             mfOffsetB(fOffsetB)
957         {
958             if(mfOffsetA > mfOffsetB)
959             {
960                 OSL_ENSURE(false, "Wrong offset order (!)");
961                 ::std::swap(mfOffsetA, mfOffsetB);
962             }
963         }
964 
965         bool SvgLinearAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
966         {
967             if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive))
968             {
969                 const SvgLinearAtomPrimitive2D& rCompare = static_cast< const SvgLinearAtomPrimitive2D& >(rPrimitive);
970 
971                 return (getColorA() == rCompare.getColorA()
972                     && getColorB() == rCompare.getColorB()
973                     && getOffsetA() == rCompare.getOffsetA()
974                     && getOffsetB() == rCompare.getOffsetB());
975             }
976 
977             return false;
978         }
979 
980         // provide unique ID
981         ImplPrimitrive2DIDBlock(SvgLinearAtomPrimitive2D, PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D)
982 
983     } // end of namespace primitive2d
984 } // end of namespace drawinglayer
985 
986 //////////////////////////////////////////////////////////////////////////////
987 // SvgRadialAtomPrimitive2D class
988 
989 namespace drawinglayer
990 {
991     namespace primitive2d
992     {
993         Primitive2DSequence SvgRadialAtomPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
994         {
995             Primitive2DSequence xRetval;
996             const double fDeltaScale(getScaleB() - getScaleA());
997 
998             if(!basegfx::fTools::equalZero(fDeltaScale))
999             {
1000                 // use one discrete unit for overlap (one pixel)
1001                 const double fDiscreteUnit(getDiscreteUnit());
1002 
1003                 // use color distance and discrete lengths to calculate step count
1004                 const sal_uInt32 nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDeltaScale, fDiscreteUnit));
1005 
1006                 // prepare loop ([0.0 .. 1.0[, full polygons, no polypolygons with holes)
1007                 double fUnitScale(0.0);
1008                 const double fUnitStep(1.0 / nSteps);
1009 
1010                 // prepare result set (known size)
1011                 xRetval.realloc(nSteps);
1012 
1013                 for(sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep)
1014                 {
1015                     basegfx::B2DHomMatrix aTransform;
1016                     const double fEndScale(getScaleB() - (fDeltaScale * fUnitScale));
1017 
1018                     if(isTranslateSet())
1019                     {
1020                         const basegfx::B2DVector aTranslate(
1021                             basegfx::interpolate(
1022                                 getTranslateB(),
1023                                 getTranslateA(),
1024                                 fUnitScale));
1025 
1026                         aTransform = basegfx::tools::createScaleTranslateB2DHomMatrix(
1027                             fEndScale,
1028                             fEndScale,
1029                             aTranslate.getX(),
1030                             aTranslate.getY());
1031                     }
1032                     else
1033                     {
1034                         aTransform = basegfx::tools::createScaleB2DHomMatrix(
1035                             fEndScale,
1036                             fEndScale);
1037                     }
1038 
1039                     basegfx::B2DPolygon aNew(basegfx::tools::createPolygonFromUnitCircle());
1040 
1041                     aNew.transform(aTransform);
1042                     xRetval[a] = new PolyPolygonColorPrimitive2D(
1043                         basegfx::B2DPolyPolygon(aNew),
1044                         basegfx::interpolate(getColorB(), getColorA(), fUnitScale));
1045                 }
1046             }
1047 
1048             return xRetval;
1049         }
1050 
1051         SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D(
1052             const basegfx::BColor& aColorA, double fScaleA, const basegfx::B2DVector& rTranslateA,
1053             const basegfx::BColor& aColorB, double fScaleB, const basegfx::B2DVector& rTranslateB)
1054         :   DiscreteMetricDependentPrimitive2D(),
1055             maColorA(aColorA),
1056             maColorB(aColorB),
1057             mfScaleA(fScaleA),
1058             mfScaleB(fScaleB),
1059             mpTranslate(0)
1060         {
1061             // check and evtl. set translations
1062             if(!rTranslateA.equal(rTranslateB))
1063             {
1064                 mpTranslate = new VectorPair(rTranslateA, rTranslateB);
1065             }
1066 
1067             // scale A and B have to be positive
1068             mfScaleA = ::std::max(mfScaleA, 0.0);
1069             mfScaleB = ::std::max(mfScaleB, 0.0);
1070 
1071             // scale B has to be bigger than scale A; swap if different
1072             if(mfScaleA > mfScaleB)
1073             {
1074                 OSL_ENSURE(false, "Wrong offset order (!)");
1075                 ::std::swap(mfScaleA, mfScaleB);
1076 
1077                 if(mpTranslate)
1078                 {
1079                     ::std::swap(mpTranslate->maTranslateA, mpTranslate->maTranslateB);
1080                 }
1081             }
1082         }
1083 
1084         SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D(
1085             const basegfx::BColor& aColorA, double fScaleA,
1086             const basegfx::BColor& aColorB, double fScaleB)
1087         :   DiscreteMetricDependentPrimitive2D(),
1088             maColorA(aColorA),
1089             maColorB(aColorB),
1090             mfScaleA(fScaleA),
1091             mfScaleB(fScaleB),
1092             mpTranslate(0)
1093         {
1094             // scale A and B have to be positive
1095             mfScaleA = ::std::max(mfScaleA, 0.0);
1096             mfScaleB = ::std::max(mfScaleB, 0.0);
1097 
1098             // scale B has to be bigger than scale A; swap if different
1099             if(mfScaleA > mfScaleB)
1100             {
1101                 OSL_ENSURE(false, "Wrong offset order (!)");
1102                 ::std::swap(mfScaleA, mfScaleB);
1103             }
1104         }
1105 
1106         SvgRadialAtomPrimitive2D::~SvgRadialAtomPrimitive2D()
1107         {
1108             if(mpTranslate)
1109             {
1110                 delete mpTranslate;
1111                 mpTranslate = 0;
1112             }
1113         }
1114 
1115         bool SvgRadialAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
1116         {
1117             if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive))
1118             {
1119                 const SvgRadialAtomPrimitive2D& rCompare = static_cast< const SvgRadialAtomPrimitive2D& >(rPrimitive);
1120 
1121                 if(getColorA() == rCompare.getColorA()
1122                     && getColorB() == rCompare.getColorB()
1123                     && getScaleA() == rCompare.getScaleA()
1124                     && getScaleB() == rCompare.getScaleB())
1125                 {
1126                     if(isTranslateSet() && rCompare.isTranslateSet())
1127                     {
1128                         return (getTranslateA() == rCompare.getTranslateA()
1129                             && getTranslateB() == rCompare.getTranslateB());
1130                     }
1131                     else if(!isTranslateSet() && !rCompare.isTranslateSet())
1132                     {
1133                         return true;
1134                     }
1135                 }
1136             }
1137 
1138             return false;
1139         }
1140 
1141         // provide unique ID
1142         ImplPrimitrive2DIDBlock(SvgRadialAtomPrimitive2D, PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D)
1143 
1144     } // end of namespace primitive2d
1145 } // end of namespace drawinglayer
1146 
1147 //////////////////////////////////////////////////////////////////////////////
1148 // eof
1149