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 SvgGradientEntryVector& rEntries = getGradientEntries();
813                 const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange());
814                 const double fPolyWidth(aPolyRange.getWidth());
815                 const double fPolyHeight(aPolyRange.getHeight());
816 
817                 // create ObjectTransform based on polygon range
818                 const basegfx::B2DHomMatrix aObjectTransform(
819                     basegfx::tools::createScaleTranslateB2DHomMatrix(
820                         fPolyWidth, fPolyHeight,
821                         aPolyRange.getMinX(), aPolyRange.getMinY()));
822 
823                 // create unit transform from unit vector to given linear gradient vector
824                 basegfx::B2DHomMatrix aUnitGradientToGradient;
825 
826                 aUnitGradientToGradient.scale(getRadius(), getRadius());
827                 aUnitGradientToGradient.translate(getStart().getX(), getStart().getY());
828 
829                 // create full transform from unit gradient coordinates to object coordinates
830                 // including the SvgGradient transformation
831                 basegfx::B2DHomMatrix aUnitGradientToObject(aObjectTransform * aUnitGradientToGradient);
832 
833                 // create inverse from it
834                 basegfx::B2DHomMatrix aObjectToUnitGradient(aUnitGradientToObject);
835                 aObjectToUnitGradient.invert();
836 
837                 // back-transform polygon to unit gradient coordinates and get
838                 // UnitRage. This is the range the gradient has to cover
839                 basegfx::B2DPolyPolygon aUnitPoly(getPolyPolygon());
840                 aUnitPoly.transform(aObjectToUnitGradient);
841                 const basegfx::B2DRange aUnitRange(aUnitPoly.getB2DRange());
842 
843                 // create range which the gradient has to cover to cover the whole given geometry.
844                 // For circle, go from 0.0 to max radius in all directions (the corners)
845                 double fMax(basegfx::B2DVector(aUnitRange.getMinimum()).getLength());
846                 fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMaximum()).getLength());
847                 fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMinX(), aUnitRange.getMaxY()).getLength());
848                 fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMaxX(), aUnitRange.getMinY()).getLength());
849 
850                 // prepare result vectors
851                 Primitive2DVector aTargetColor;
852                 Primitive2DVector aTargetOpacity;
853 
854                 if(0.0 < fMax)
855                 {
856                     // prepare maFocalVector
857                     if(isFocalSet())
858                     {
859                         const_cast< SvgRadialGradientPrimitive2D* >(this)->maFocalLength = fMax;
860                     }
861 
862                     // create central run, may also already do all necessary when
863                     // Spread_pad is set as SpreadMethod and/or the range is smaller
864                     double fPos(createRun(aTargetColor, aTargetOpacity, 0.0, fMax, getGradientEntries(), 0));
865 
866                     if(fPos < fMax)
867                     {
868                         // can only happen when SpreadMethod is Spread_reflect or Spread_repeat,
869                         // else the start and end pads are already created and fPos == fMax.
870                         // For radial there is no way to transform the already created
871                         // central run, it needs to be created from 1.0 to fMax
872                         sal_Int32 nOffset(1);
873 
874                         while(fPos < fMax)
875                         {
876                             const bool bMirror(Spread_reflect == getSpreadMethod() && (nOffset % 2));
877 
878                             if(bMirror)
879                             {
880                                 createRun(aTargetColor, aTargetOpacity, 0.0, fMax, getMirroredGradientEntries(), nOffset);
881                             }
882                             else
883                             {
884                                 createRun(aTargetColor, aTargetOpacity, 0.0, fMax, getGradientEntries(), nOffset);
885                             }
886 
887                             nOffset++;
888                             fPos += 1.0;
889                         }
890                     }
891                 }
892 
893                 xRetval = createResult(aTargetColor, aTargetOpacity, aUnitGradientToObject, true);
894             }
895 
896             return xRetval;
897 		}
898 
899 		SvgRadialGradientPrimitive2D::SvgRadialGradientPrimitive2D(
900 			const basegfx::B2DPolyPolygon& rPolyPolygon,
901             const SvgGradientEntryVector& rGradientEntries,
902             const basegfx::B2DPoint& rStart,
903             double fRadius,
904             SpreadMethod aSpreadMethod,
905             const basegfx::B2DPoint* pFocal,
906             double fOverlapping)
907 		:	BufferedDecompositionPrimitive2D(),
908             SvgGradientHelper(rPolyPolygon, rGradientEntries, rStart, aSpreadMethod, fOverlapping),
909             mfRadius(fRadius),
910             maFocal(rStart),
911             maFocalVector(0.0, 0.0),
912             maFocalLength(0.0),
913             maMirroredGradientEntries(),
914             mbFocalSet(false)
915 		{
916             if(pFocal)
917             {
918                 maFocal = *pFocal;
919                 maFocalVector = maFocal - getStart();
920                 mbFocalSet = true;
921             }
922 		}
923 
924 		bool SvgRadialGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
925 		{
926             const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive);
927 
928             if(pSvgGradientHelper && SvgGradientHelper::operator==(*pSvgGradientHelper))
929 			{
930 				const SvgRadialGradientPrimitive2D& rCompare = static_cast< const SvgRadialGradientPrimitive2D& >(rPrimitive);
931 
932 				if(getRadius() == rCompare.getRadius())
933                 {
934                     if(isFocalSet() == rCompare.isFocalSet())
935                     {
936                         if(isFocalSet())
937                         {
938                             return getFocal() == rCompare.getFocal();
939                         }
940                         else
941                         {
942                             return true;
943                         }
944                     }
945                 }
946 			}
947 
948 			return false;
949 		}
950 
951 		basegfx::B2DRange SvgRadialGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
952 		{
953 			// return ObjectRange
954 			return getPolyPolygon().getB2DRange();
955 		}
956 
957 		// provide unique ID
958 		ImplPrimitrive2DIDBlock(SvgRadialGradientPrimitive2D, PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D)
959 
960 	} // end of namespace primitive2d
961 } // end of namespace drawinglayer
962 
963 //////////////////////////////////////////////////////////////////////////////
964 // SvgLinearAtomPrimitive2D class
965 
966 namespace drawinglayer
967 {
968 	namespace primitive2d
969 	{
970         Primitive2DSequence SvgLinearAtomPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& rViewInformation) const
971         {
972             Primitive2DSequence xRetval;
973             const double fDelta(getOffsetB() - getOffsetA());
974 
975             if(!basegfx::fTools::equalZero(fDelta))
976             {
977                 if(getColorA() == getColorB())
978                 {
979                     const basegfx::B2DPolygon aPolygon(
980                         basegfx::tools::createPolygonFromRect(
981                             basegfx::B2DRange(
982                                 getOffsetA() - getOverlapping(),
983                                 0.0,
984                                 getOffsetB() + getOverlapping(),
985                                 1.0)));
986 
987                     xRetval.realloc(1);
988                     xRetval[0] = new PolyPolygonColorPrimitive2D(
989                         basegfx::B2DPolyPolygon(aPolygon),
990                         getColorA());
991                 }
992                 else
993                 {
994                     // calc discrete length to change color all 2.5 pixels
995                     sal_uInt32 nSteps(basegfx::fround(fDelta / (getDiscreteUnit() * 2.5)));
996 
997                     // use color distance, assume to do every 3rd
998                     const double fColorDistance(getColorA().getDistance(getColorB()));
999                     const sal_uInt32 nColorSteps(basegfx::fround(fColorDistance * (255.0 * 0.3)));
1000                     nSteps = std::min(nSteps, nColorSteps);
1001 
1002                     // roughly cut when too big
1003                     nSteps = std::min(nSteps, sal_uInt32(100));
1004                     nSteps = std::max(nSteps, sal_uInt32(1));
1005 
1006                     // preapare iteration
1007                     double fStart(0.0);
1008                     double fStep(fDelta / nSteps);
1009 
1010                     xRetval.realloc(nSteps);
1011 
1012                     for(sal_uInt32 a(0); a < nSteps; a++, fStart += fStep)
1013                     {
1014                         const double fLeft(getOffsetA() + fStart);
1015                         const double fRight(fLeft + fStep);
1016                         const basegfx::B2DPolygon aPolygon(
1017                             basegfx::tools::createPolygonFromRect(
1018                                 basegfx::B2DRange(
1019                                     fLeft - getOverlapping(),
1020                                     0.0,
1021                                     fRight + getOverlapping(),
1022                                     1.0)));
1023 
1024                         xRetval[a] = new PolyPolygonColorPrimitive2D(
1025                             basegfx::B2DPolyPolygon(aPolygon),
1026                             basegfx::interpolate(getColorA(), getColorB(), fStart/fDelta));
1027                     }
1028                 }
1029             }
1030 
1031             return xRetval;
1032         }
1033 
1034         bool SvgLinearAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
1035         {
1036 			if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive))
1037 			{
1038 				const SvgLinearAtomPrimitive2D& rCompare = static_cast< const SvgLinearAtomPrimitive2D& >(rPrimitive);
1039 
1040 				return (getColorA() == rCompare.getColorA()
1041                     && getColorB() == rCompare.getColorB()
1042                     && getOffsetA() == rCompare.getOffsetA()
1043                     && getOffsetB() == rCompare.getOffsetB()
1044                     && getOverlapping() == rCompare.getOverlapping());
1045 			}
1046 
1047 			return false;
1048         }
1049 
1050 		// provide unique ID
1051 		ImplPrimitrive2DIDBlock(SvgLinearAtomPrimitive2D, PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D)
1052 
1053     } // end of namespace primitive2d
1054 } // end of namespace drawinglayer
1055 
1056 //////////////////////////////////////////////////////////////////////////////
1057 // SvgRadialAtomPrimitive2D class
1058 
1059 namespace drawinglayer
1060 {
1061 	namespace primitive2d
1062 	{
1063         Primitive2DSequence SvgRadialAtomPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
1064         {
1065             Primitive2DSequence xRetval;
1066             const double fDeltaScale(getScaleB() - getScaleA());
1067 
1068             if(!basegfx::fTools::equalZero(fDeltaScale))
1069             {
1070                 if(getColorA() == getColorB())
1071                 {
1072                     basegfx::B2DPolygon aPolygonA(basegfx::tools::createPolygonFromUnitCircle());
1073                     basegfx::B2DPolygon aPolygonB(basegfx::tools::createPolygonFromUnitCircle());
1074                     double fScaleA(getScaleA());
1075                     const double fScaleB(getScaleB());
1076 
1077                     if(fScaleA > getOverlapping())
1078                     {
1079                         fScaleA -= getOverlapping();
1080                     }
1081 
1082                     const bool bUseA(basegfx::fTools::equalZero(fScaleA));
1083 
1084                     if(getTranslateSet())
1085                     {
1086                         if(bUseA)
1087                         {
1088                             aPolygonA.transform(
1089                                 basegfx::tools::createScaleTranslateB2DHomMatrix(
1090                                     fScaleA,
1091                                     fScaleA,
1092                                     getTranslateA().getX(),
1093                                     getTranslateA().getY()));
1094                         }
1095 
1096                         aPolygonB.transform(
1097                             basegfx::tools::createScaleTranslateB2DHomMatrix(
1098                                 fScaleB,
1099                                 fScaleB,
1100                                 getTranslateB().getX(),
1101                                 getTranslateB().getY()));
1102                     }
1103                     else
1104                     {
1105                         if(bUseA)
1106                         {
1107                             aPolygonA.transform(
1108                                 basegfx::tools::createScaleB2DHomMatrix(
1109                                     fScaleA,
1110                                     fScaleA));
1111                         }
1112 
1113                         aPolygonB.transform(
1114                             basegfx::tools::createScaleB2DHomMatrix(
1115                                 fScaleB,
1116                                 fScaleB));
1117                     }
1118 
1119                     basegfx::B2DPolyPolygon aPolyPolygon(aPolygonB);
1120 
1121                     if(bUseA)
1122                     {
1123                         aPolyPolygon.append(aPolygonA);
1124                     }
1125 
1126                     xRetval.realloc(1);
1127 
1128                     xRetval[0] = new PolyPolygonColorPrimitive2D(
1129                         aPolyPolygon,
1130                         getColorA());
1131                 }
1132                 else
1133                 {
1134                     // calc discrete length to change color all 2.5 pixels
1135                     sal_uInt32 nSteps(basegfx::fround(fDeltaScale / (getDiscreteUnit() * 2.5)));
1136 
1137                     // use color distance, assume to do every 3rd
1138                     const double fColorDistance(getColorA().getDistance(getColorB()));
1139                     const sal_uInt32 nColorSteps(basegfx::fround(fColorDistance * (255.0 * 0.3)));
1140                     nSteps = std::min(nSteps, nColorSteps);
1141 
1142                     // roughly cut when too big
1143                     nSteps = std::min(nSteps, sal_uInt32(100));
1144                     nSteps = std::max(nSteps, sal_uInt32(1));
1145 
1146                     // preapare iteration
1147                     double fStartScale(0.0);
1148                     double fStepScale(fDeltaScale / nSteps);
1149 
1150                     xRetval.realloc(nSteps);
1151 
1152                     for(sal_uInt32 a(0); a < nSteps; a++, fStartScale += fStepScale)
1153                     {
1154                         double fScaleA(getScaleA() + fStartScale);
1155                         const double fScaleB(fScaleA + fStepScale);
1156                         const double fUnitScale(fStartScale/fDeltaScale);
1157                         basegfx::B2DPolygon aPolygonA(basegfx::tools::createPolygonFromUnitCircle());
1158                         basegfx::B2DPolygon aPolygonB(basegfx::tools::createPolygonFromUnitCircle());
1159 
1160                         if(fScaleA > getOverlapping())
1161                         {
1162                             fScaleA -= getOverlapping();
1163                         }
1164 
1165                         const bool bUseA(basegfx::fTools::equalZero(fScaleA));
1166 
1167                         if(getTranslateSet())
1168                         {
1169                             const double fUnitScaleEnd((fStartScale + fStepScale)/fDeltaScale);
1170                             const basegfx::B2DVector aTranslateB(basegfx::interpolate(getTranslateA(), getTranslateB(), fUnitScaleEnd));
1171 
1172                             if(bUseA)
1173                             {
1174                                 const basegfx::B2DVector aTranslateA(basegfx::interpolate(getTranslateA(), getTranslateB(), fUnitScale));
1175 
1176                                 aPolygonA.transform(
1177                                     basegfx::tools::createScaleTranslateB2DHomMatrix(
1178                                         fScaleA,
1179                                         fScaleA,
1180                                         aTranslateA.getX(),
1181                                         aTranslateA.getY()));
1182                             }
1183 
1184                             aPolygonB.transform(
1185                                 basegfx::tools::createScaleTranslateB2DHomMatrix(
1186                                     fScaleB,
1187                                     fScaleB,
1188                                     aTranslateB.getX(),
1189                                     aTranslateB.getY()));
1190                         }
1191                         else
1192                         {
1193                             if(bUseA)
1194                             {
1195                                 aPolygonA.transform(
1196                                     basegfx::tools::createScaleB2DHomMatrix(
1197                                         fScaleA,
1198                                         fScaleA));
1199                             }
1200 
1201                             aPolygonB.transform(
1202                                 basegfx::tools::createScaleB2DHomMatrix(
1203                                     fScaleB,
1204                                     fScaleB));
1205                         }
1206 
1207                         basegfx::B2DPolyPolygon aPolyPolygon(aPolygonB);
1208 
1209                         if(bUseA)
1210                         {
1211                             aPolyPolygon.append(aPolygonA);
1212                         }
1213 
1214                         xRetval[nSteps - 1 - a] = new PolyPolygonColorPrimitive2D(
1215                             aPolyPolygon,
1216                             basegfx::interpolate(getColorA(), getColorB(), fUnitScale));
1217                     }
1218                 }
1219             }
1220 
1221             return xRetval;
1222         }
1223 
1224         bool SvgRadialAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
1225         {
1226 			if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive))
1227 			{
1228 				const SvgRadialAtomPrimitive2D& rCompare = static_cast< const SvgRadialAtomPrimitive2D& >(rPrimitive);
1229 
1230 				return (getColorA() == rCompare.getColorA()
1231                     && getColorB() == rCompare.getColorB()
1232                     && getScaleA() == rCompare.getScaleA()
1233                     && getScaleB() == rCompare.getScaleB()
1234                     && getTranslateA() == rCompare.getTranslateA()
1235                     && getTranslateB() == rCompare.getTranslateB()
1236                     && getOverlapping() == rCompare.getOverlapping());
1237 			}
1238 
1239 			return false;
1240         }
1241 
1242 		// provide unique ID
1243 		ImplPrimitrive2DIDBlock(SvgRadialAtomPrimitive2D, PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D)
1244 
1245 	} // end of namespace primitive2d
1246 } // end of namespace drawinglayer
1247 
1248 //////////////////////////////////////////////////////////////////////////////
1249 // eof
1250