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