xref: /trunk/main/drawinglayer/source/processor2d/vclprocessor2d.cxx (revision cdf0e10c4e3984b49a9502b011690b615761d4a3)
1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_drawinglayer.hxx"
30 
31 #include <drawinglayer/processor2d/vclprocessor2d.hxx>
32 #include <drawinglayer/primitive2d/textprimitive2d.hxx>
33 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
34 #include <tools/debug.hxx>
35 #include <vcl/outdev.hxx>
36 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
37 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
38 #include <drawinglayer/primitive2d/rendergraphicprimitive2d.hxx>
39 #include <vclhelperbitmaptransform.hxx>
40 #include <basegfx/polygon/b2dpolygontools.hxx>
41 #include <vclhelperbitmaprender.hxx>
42 #include <drawinglayer/attribute/sdrfillbitmapattribute.hxx>
43 #include <drawinglayer/primitive2d/fillbitmapprimitive2d.hxx>
44 #include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
45 #include <vclhelpergradient.hxx>
46 #include <drawinglayer/primitive2d/metafileprimitive2d.hxx>
47 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
48 #include <basegfx/polygon/b2dpolypolygontools.hxx>
49 #include <vclhelperbufferdevice.hxx>
50 #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
51 #include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
52 #include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
53 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
54 #include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
55 #include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
56 #include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx>
57 #include <svl/ctloptions.hxx>
58 #include <vcl/svapp.hxx>
59 #include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx>
60 #include <tools/diagnose_ex.h>
61 #include <vcl/metric.hxx>
62 #include <drawinglayer/primitive2d/textenumsprimitive2d.hxx>
63 #include <drawinglayer/primitive2d/epsprimitive2d.hxx>
64 #include <vcl/rendergraphicrasterizer.hxx>
65 
66 //////////////////////////////////////////////////////////////////////////////
67 // control support
68 
69 #include <com/sun/star/awt/XWindow2.hpp>
70 #include <com/sun/star/awt/PosSize.hpp>
71 #include <com/sun/star/awt/XView.hpp>
72 #include <drawinglayer/primitive2d/controlprimitive2d.hxx>
73 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
74 
75 //////////////////////////////////////////////////////////////////////////////
76 // for test, can be removed again
77 
78 #include <basegfx/polygon/b2dpolygonclipper.hxx>
79 #include <basegfx/polygon/b2dtrapezoid.hxx>
80 
81 //////////////////////////////////////////////////////////////////////////////
82 
83 using namespace com::sun::star;
84 
85 //////////////////////////////////////////////////////////////////////////////
86 
87 namespace drawinglayer
88 {
89     namespace processor2d
90     {
91         //////////////////////////////////////////////////////////////////////////////
92         // UNO class usages
93         using ::com::sun::star::uno::Reference;
94         using ::com::sun::star::uno::UNO_QUERY;
95         using ::com::sun::star::uno::UNO_QUERY_THROW;
96         using ::com::sun::star::uno::Exception;
97         using ::com::sun::star::awt::XView;
98         using ::com::sun::star::awt::XGraphics;
99         using ::com::sun::star::awt::XWindow;
100         using ::com::sun::star::awt::PosSize::POSSIZE;
101 
102         //////////////////////////////////////////////////////////////////////////////
103         // rendering support
104 
105         // directdraw of text simple portion or decorated portion primitive. When decorated, all the extra
106         // information is translated to VCL parameters and set at the font.
107         // Acceptance is restricted to no shearing and positive scaling in X and Y (no font mirroring
108         // for VCL)
109         void VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D(const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate)
110         {
111             // decompose matrix to have position and size of text
112             basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation * rTextCandidate.getTextTransform());
113             basegfx::B2DVector aFontScaling, aTranslate;
114             double fRotate, fShearX;
115             aLocalTransform.decompose(aFontScaling, aTranslate, fRotate, fShearX);
116             bool bPrimitiveAccepted(false);
117 
118             if(basegfx::fTools::equalZero(fShearX))
119             {
120                 if(basegfx::fTools::less(aFontScaling.getX(), 0.0) && basegfx::fTools::less(aFontScaling.getY(), 0.0))
121                 {
122                     // handle special case: If scale is negative in (x,y) (3rd quadrant), it can
123                     // be expressed as rotation by PI. Use this since the Font rendering will not
124                     // apply the negative scales in any form
125                     aFontScaling = basegfx::absolute(aFontScaling);
126                     fRotate += F_PI;
127                 }
128 
129                 if(basegfx::fTools::more(aFontScaling.getX(), 0.0) && basegfx::fTools::more(aFontScaling.getY(), 0.0))
130                 {
131                     // Get the VCL font (use FontHeight as FontWidth)
132                     Font aFont(primitive2d::getVclFontFromFontAttribute(
133                         rTextCandidate.getFontAttribute(),
134                         aFontScaling.getX(),
135                         aFontScaling.getY(),
136                         fRotate,
137                         rTextCandidate.getLocale()));
138 
139                     // handle additional font attributes
140                     const primitive2d::TextDecoratedPortionPrimitive2D* pTCPP =
141                         dynamic_cast<const primitive2d::TextDecoratedPortionPrimitive2D*>( &rTextCandidate );
142 
143                     if( pTCPP != NULL )
144                     {
145 
146                         // set the color of text decorations
147                         const basegfx::BColor aTextlineColor = maBColorModifierStack.getModifiedColor(pTCPP->getTextlineColor());
148                         mpOutputDevice->SetTextLineColor( Color(aTextlineColor) );
149 
150                         // set Overline attribute
151                         const FontUnderline eFontOverline(primitive2d::mapTextLineToFontUnderline( pTCPP->getFontOverline() ));
152                         if( eFontOverline != UNDERLINE_NONE )
153                         {
154                             aFont.SetOverline( eFontOverline );
155                             const basegfx::BColor aOverlineColor = maBColorModifierStack.getModifiedColor(pTCPP->getOverlineColor());
156                             mpOutputDevice->SetOverlineColor( Color(aOverlineColor) );
157                             if( pTCPP->getWordLineMode() )
158                                 aFont.SetWordLineMode( true );
159                         }
160 
161                         // set Underline attribute
162                         const FontUnderline eFontUnderline(primitive2d::mapTextLineToFontUnderline( pTCPP->getFontUnderline() ));
163                         if( eFontUnderline != UNDERLINE_NONE )
164                         {
165                             aFont.SetUnderline( eFontUnderline );
166                             if( pTCPP->getWordLineMode() )
167                                 aFont.SetWordLineMode( true );
168 //TODO: ???                 if( pTCPP->getUnderlineAbove() )
169 //                              aFont.SetUnderlineAbove( true );
170                         }
171 
172                         // set Strikeout attribute
173                         const FontStrikeout eFontStrikeout(primitive2d::mapTextStrikeoutToFontStrikeout(pTCPP->getTextStrikeout()));
174 
175                         if( eFontStrikeout != STRIKEOUT_NONE )
176                             aFont.SetStrikeout( eFontStrikeout );
177 
178                         // set EmphasisMark attribute
179                         FontEmphasisMark eFontEmphasisMark = EMPHASISMARK_NONE;
180                         switch( pTCPP->getTextEmphasisMark() )
181                         {
182                             default:
183                                 DBG_WARNING1( "DrawingLayer: Unknown EmphasisMark style (%d)!", pTCPP->getTextEmphasisMark() );
184                                 // fall through
185                             case primitive2d::TEXT_EMPHASISMARK_NONE:   eFontEmphasisMark = EMPHASISMARK_NONE; break;
186                             case primitive2d::TEXT_EMPHASISMARK_DOT:    eFontEmphasisMark = EMPHASISMARK_DOT; break;
187                             case primitive2d::TEXT_EMPHASISMARK_CIRCLE: eFontEmphasisMark = EMPHASISMARK_CIRCLE; break;
188                             case primitive2d::TEXT_EMPHASISMARK_DISC:   eFontEmphasisMark = EMPHASISMARK_DISC; break;
189                             case primitive2d::TEXT_EMPHASISMARK_ACCENT: eFontEmphasisMark = EMPHASISMARK_ACCENT; break;
190                         }
191 
192                         if( eFontEmphasisMark != EMPHASISMARK_NONE )
193                         {
194                             DBG_ASSERT( (pTCPP->getEmphasisMarkAbove() != pTCPP->getEmphasisMarkBelow()),
195                                 "DrawingLayer: Bad EmphasisMark position!" );
196                             if( pTCPP->getEmphasisMarkAbove() )
197                                 eFontEmphasisMark |= EMPHASISMARK_POS_ABOVE;
198                             else
199                                 eFontEmphasisMark |= EMPHASISMARK_POS_BELOW;
200                             aFont.SetEmphasisMark( eFontEmphasisMark );
201                         }
202 
203                         // set Relief attribute
204                         FontRelief eFontRelief = RELIEF_NONE;
205                         switch( pTCPP->getTextRelief() )
206                         {
207                             default:
208                                 DBG_WARNING1( "DrawingLayer: Unknown Relief style (%d)!", pTCPP->getTextRelief() );
209                                 // fall through
210                             case primitive2d::TEXT_RELIEF_NONE:     eFontRelief = RELIEF_NONE; break;
211                             case primitive2d::TEXT_RELIEF_EMBOSSED: eFontRelief = RELIEF_EMBOSSED; break;
212                             case primitive2d::TEXT_RELIEF_ENGRAVED: eFontRelief = RELIEF_ENGRAVED; break;
213                         }
214 
215                         if( eFontRelief != RELIEF_NONE )
216                             aFont.SetRelief( eFontRelief );
217 
218                         // set Shadow attribute
219                         if( pTCPP->getShadow() )
220                             aFont.SetShadow( true );
221                     }
222 
223                     // create transformed integer DXArray in view coordinate system
224                     ::std::vector< sal_Int32 > aTransformedDXArray;
225 
226                     if(rTextCandidate.getDXArray().size())
227                     {
228                         aTransformedDXArray.reserve(rTextCandidate.getDXArray().size());
229                         const basegfx::B2DVector aPixelVector(maCurrentTransformation * basegfx::B2DVector(1.0, 0.0));
230                         const double fPixelVectorFactor(aPixelVector.getLength());
231 
232                         for(::std::vector< double >::const_iterator aStart(rTextCandidate.getDXArray().begin());
233                             aStart != rTextCandidate.getDXArray().end(); aStart++)
234                         {
235                             aTransformedDXArray.push_back(basegfx::fround((*aStart) * fPixelVectorFactor));
236                         }
237                     }
238 
239                     // set parameters and paint text snippet
240                     const basegfx::BColor aRGBFontColor(maBColorModifierStack.getModifiedColor(rTextCandidate.getFontColor()));
241                     const basegfx::B2DPoint aPoint(aLocalTransform * basegfx::B2DPoint(0.0, 0.0));
242                     const Point aStartPoint(basegfx::fround(aPoint.getX()), basegfx::fround(aPoint.getY()));
243                     const sal_uInt32 nOldLayoutMode(mpOutputDevice->GetLayoutMode());
244 
245                     if(rTextCandidate.getFontAttribute().getRTL())
246                     {
247                         sal_uInt32 nRTLLayoutMode(nOldLayoutMode & ~(TEXT_LAYOUT_COMPLEX_DISABLED|TEXT_LAYOUT_BIDI_STRONG));
248                         nRTLLayoutMode |= TEXT_LAYOUT_BIDI_RTL|TEXT_LAYOUT_TEXTORIGIN_LEFT;
249                         mpOutputDevice->SetLayoutMode(nRTLLayoutMode);
250                     }
251 
252                     mpOutputDevice->SetFont(aFont);
253                     mpOutputDevice->SetTextColor(Color(aRGBFontColor));
254 
255                     if(aTransformedDXArray.size())
256                     {
257                         mpOutputDevice->DrawTextArray(
258                             aStartPoint,
259                             rTextCandidate.getText(),
260                             &(aTransformedDXArray[0]),
261                             rTextCandidate.getTextPosition(),
262                             rTextCandidate.getTextLength());
263                     }
264                     else
265                     {
266                         mpOutputDevice->DrawText(
267                             aStartPoint,
268                             rTextCandidate.getText(),
269                             rTextCandidate.getTextPosition(),
270                             rTextCandidate.getTextLength());
271                     }
272 
273                     if(rTextCandidate.getFontAttribute().getRTL())
274                     {
275                         mpOutputDevice->SetLayoutMode(nOldLayoutMode);
276                     }
277 
278                     bPrimitiveAccepted = true;
279                 }
280             }
281 
282             if(!bPrimitiveAccepted)
283             {
284                 // let break down
285                 process(rTextCandidate.get2DDecomposition(getViewInformation2D()));
286             }
287         }
288 
289         // direct draw of hairline
290         void VclProcessor2D::RenderPolygonHairlinePrimitive2D(const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate, bool bPixelBased)
291         {
292             const basegfx::BColor aHairlineColor(maBColorModifierStack.getModifiedColor(rPolygonCandidate.getBColor()));
293             mpOutputDevice->SetLineColor(Color(aHairlineColor));
294             mpOutputDevice->SetFillColor();
295 
296             basegfx::B2DPolygon aLocalPolygon(rPolygonCandidate.getB2DPolygon());
297             aLocalPolygon.transform(maCurrentTransformation);
298 
299             static bool bCheckTrapezoidDecomposition(false);
300             static bool bShowOutlinesThere(false);
301             if(bCheckTrapezoidDecomposition)
302             {
303                 // clip against discrete ViewPort
304                 const basegfx::B2DRange& rDiscreteViewport = getViewInformation2D().getDiscreteViewport();
305                 basegfx::B2DPolyPolygon aLocalPolyPolygon(basegfx::tools::clipPolygonOnRange(
306                     aLocalPolygon, rDiscreteViewport, true, false));
307 
308                 if(aLocalPolyPolygon.count())
309                 {
310                     // subdivide
311                     aLocalPolyPolygon = basegfx::tools::adaptiveSubdivideByDistance(
312                         aLocalPolyPolygon, 0.5);
313 
314                     // trapezoidize
315                     static double fLineWidth(2.0);
316                     basegfx::B2DTrapezoidVector aB2DTrapezoidVector;
317                     basegfx::tools::createLineTrapezoidFromB2DPolyPolygon(aB2DTrapezoidVector, aLocalPolyPolygon, fLineWidth);
318 
319                     const sal_uInt32 nCount(aB2DTrapezoidVector.size());
320 
321                     if(nCount)
322                     {
323                         basegfx::BColor aInvPolygonColor(aHairlineColor);
324                         aInvPolygonColor.invert();
325 
326                         for(sal_uInt32 a(0); a < nCount; a++)
327                         {
328                             const basegfx::B2DPolygon aTempPolygon(aB2DTrapezoidVector[a].getB2DPolygon());
329 
330                             if(bShowOutlinesThere)
331                             {
332                                 mpOutputDevice->SetFillColor(Color(aHairlineColor));
333                                 mpOutputDevice->SetLineColor();
334                             }
335 
336                             mpOutputDevice->DrawPolygon(aTempPolygon);
337 
338                             if(bShowOutlinesThere)
339                             {
340                                 mpOutputDevice->SetFillColor();
341                                 mpOutputDevice->SetLineColor(Color(aInvPolygonColor));
342                                 mpOutputDevice->DrawPolyLine(aTempPolygon, 0.0);
343                             }
344                         }
345                     }
346                 }
347             }
348             else
349             {
350                 if(bPixelBased && getOptionsDrawinglayer().IsAntiAliasing() && getOptionsDrawinglayer().IsSnapHorVerLinesToDiscrete())
351                 {
352                     // #i98289#
353                     // when a Hairline is painted and AntiAliasing is on the option SnapHorVerLinesToDiscrete
354                     // allows to suppress AntiAliasing for pure horizontal or vertical lines. This is done since
355                     // not-AntiAliased such lines look more pleasing to the eye (e.g. 2D chart content). This
356                     // NEEDS to be done in discrete coordinates, so only useful for pixel based rendering.
357                     aLocalPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aLocalPolygon);
358                 }
359 
360                 mpOutputDevice->DrawPolyLine(aLocalPolygon, 0.0);
361             }
362         }
363 
364         // direct draw of transformed BitmapEx primitive
365         void VclProcessor2D::RenderBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate)
366         {
367             // create local transform
368             basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation * rBitmapCandidate.getTransform());
369             BitmapEx aBitmapEx(rBitmapCandidate.getBitmapEx());
370             bool bPainted(false);
371 
372             if(maBColorModifierStack.count())
373             {
374                 aBitmapEx = impModifyBitmapEx(maBColorModifierStack, aBitmapEx);
375 
376                 if(aBitmapEx.IsEmpty())
377                 {
378                     // color gets completely replaced, get it
379                     const basegfx::BColor aModifiedColor(maBColorModifierStack.getModifiedColor(basegfx::BColor()));
380                     basegfx::B2DPolygon aPolygon(basegfx::tools::createUnitPolygon());
381                     aPolygon.transform(aLocalTransform);
382 
383                     mpOutputDevice->SetFillColor(Color(aModifiedColor));
384                     mpOutputDevice->SetLineColor();
385                     mpOutputDevice->DrawPolygon(aPolygon);
386 
387                     bPainted = true;
388                 }
389             }
390 
391             if(!bPainted)
392             {
393                 static bool bForceUseOfOwnTransformer(false);
394                 static bool bUseGraphicManager(true);
395 
396                 // decompose matrix to check for shear, rotate and mirroring
397                 basegfx::B2DVector aScale, aTranslate;
398                 double fRotate, fShearX;
399                 aLocalTransform.decompose(aScale, aTranslate, fRotate, fShearX);
400 
401                 if(!bForceUseOfOwnTransformer && basegfx::fTools::equalZero(fShearX))
402                 {
403                     if(!bUseGraphicManager && basegfx::fTools::equalZero(fRotate))
404                     {
405                         RenderBitmapPrimitive2D_BitmapEx(*mpOutputDevice, aBitmapEx, aLocalTransform);
406                     }
407                     else
408                     {
409                         RenderBitmapPrimitive2D_GraphicManager(*mpOutputDevice, aBitmapEx, aLocalTransform);
410                     }
411                 }
412                 else
413                 {
414                     if(!aBitmapEx.IsTransparent() && (!basegfx::fTools::equalZero(fShearX) || !basegfx::fTools::equalZero(fRotate)))
415                     {
416                         // parts will be uncovered, extend aBitmapEx with a mask bitmap
417                         const Bitmap aContent(aBitmapEx.GetBitmap());
418                         aBitmapEx = BitmapEx(aContent, Bitmap(aContent.GetSizePixel(), 1));
419                     }
420 
421                     RenderBitmapPrimitive2D_self(*mpOutputDevice, aBitmapEx, aLocalTransform);
422                 }
423             }
424         }
425 
426         void VclProcessor2D::RenderRenderGraphicPrimitive2D(const primitive2d::RenderGraphicPrimitive2D& rRenderGraphicCandidate)
427         {
428             // create local transform
429             basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation * rRenderGraphicCandidate.getTransform());
430             vcl::RenderGraphic aRenderGraphic(rRenderGraphicCandidate.getRenderGraphic());
431             bool bPainted(false);
432 
433             if(maBColorModifierStack.count())
434             {
435                 // !!! TODO
436                 // aRenderGraphic = impModifyRenderGraphic(maBColorModifierStack, aRenderGraphic);
437 
438                 if(aRenderGraphic.IsEmpty())
439                 {
440                     // color gets completely replaced, get it
441                     const basegfx::BColor aModifiedColor(maBColorModifierStack.getModifiedColor(basegfx::BColor()));
442                     basegfx::B2DPolygon aPolygon(basegfx::tools::createUnitPolygon());
443                     aPolygon.transform(aLocalTransform);
444 
445                     mpOutputDevice->SetFillColor(Color(aModifiedColor));
446                     mpOutputDevice->SetLineColor();
447                     mpOutputDevice->DrawPolygon(aPolygon);
448 
449                     bPainted = true;
450                 }
451             }
452 
453             if(!bPainted)
454             {
455                 // decompose matrix to check for shear, rotate and mirroring
456                 basegfx::B2DVector aScale, aTranslate;
457                 double fRotate, fShearX;
458                 aLocalTransform.decompose(aScale, aTranslate, fRotate, fShearX);
459 
460                 basegfx::B2DRange aOutlineRange(0.0, 0.0, 1.0, 1.0);
461 
462                 if( basegfx::fTools::equalZero( fRotate ) )
463                 {
464                     aOutlineRange.transform( aLocalTransform );
465                 }
466                 else
467                 {
468                     // !!! TODO
469                     // if rotated, create the unrotated output rectangle for the GraphicManager paint
470                     /*
471                     const basegfx::B2DHomMatrix aSimpleObjectMatrix(basegfx::tools::createScaleTranslateB2DHomMatrix(
472                         fabs(aScale.getX()), fabs(aScale.getY()),
473                         aTranslate.getX(), aTranslate.getY()));
474 
475                     aOutlineRange.transform(aSimpleObjectMatrix);
476                     */
477                 }
478 
479                 // prepare dest coordinates
480                 const Point                         aPoint( basegfx::fround(aOutlineRange.getMinX() ),
481                                                             basegfx::fround(aOutlineRange.getMinY() ) );
482                 const Size                          aSize( basegfx::fround(aOutlineRange.getWidth() ),
483                                                            basegfx::fround(aOutlineRange.getHeight() ) );
484                 const Size                          aSizePixel( mpOutputDevice->LogicToPixel( aSize ) );
485                 const vcl::RenderGraphicRasterizer  aRasterizer( aRenderGraphic );
486                 const BitmapEx                      aBitmapEx( aRasterizer.Rasterize( aSizePixel, fRotate, fShearX ) );
487 
488                 if( !aBitmapEx.IsEmpty() )
489                 {
490                     mpOutputDevice->DrawBitmapEx( aPoint, aSize, aBitmapEx );
491                 }
492             }
493         }
494 
495         void VclProcessor2D::RenderFillBitmapPrimitive2D(const primitive2d::FillBitmapPrimitive2D& rFillBitmapCandidate)
496         {
497             const attribute::FillBitmapAttribute& rFillBitmapAttribute(rFillBitmapCandidate.getFillBitmap());
498             bool bPrimitiveAccepted(false);
499 
500             if(rFillBitmapAttribute.getTiling())
501             {
502                 // decompose matrix to check for shear, rotate and mirroring
503                 basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation * rFillBitmapCandidate.getTransformation());
504                 basegfx::B2DVector aScale, aTranslate;
505                 double fRotate, fShearX;
506                 aLocalTransform.decompose(aScale, aTranslate, fRotate, fShearX);
507 
508                 if(basegfx::fTools::equalZero(fRotate) && basegfx::fTools::equalZero(fShearX))
509                 {
510                     // no shear or rotate, draw direct in pixel coordinates
511                     bPrimitiveAccepted = true;
512                     BitmapEx aBitmapEx(rFillBitmapAttribute.getBitmapEx());
513                     bool bPainted(false);
514 
515                     if(maBColorModifierStack.count())
516                     {
517                         aBitmapEx = impModifyBitmapEx(maBColorModifierStack, aBitmapEx);
518 
519                         if(aBitmapEx.IsEmpty())
520                         {
521                             // color gets completely replaced, get it
522                             const basegfx::BColor aModifiedColor(maBColorModifierStack.getModifiedColor(basegfx::BColor()));
523                             basegfx::B2DPolygon aPolygon(basegfx::tools::createUnitPolygon());
524                             aPolygon.transform(aLocalTransform);
525 
526                             mpOutputDevice->SetFillColor(Color(aModifiedColor));
527                             mpOutputDevice->SetLineColor();
528                             mpOutputDevice->DrawPolygon(aPolygon);
529 
530                             bPainted = true;
531                         }
532                     }
533 
534                     if(!bPainted)
535                     {
536                         const basegfx::B2DPoint aObjTopLeft(aTranslate.getX(), aTranslate.getY());
537                         const basegfx::B2DPoint aObjBottomRight(aTranslate.getX() + aScale.getX(), aTranslate.getY() + aScale.getY());
538                         const Point aObjTL(mpOutputDevice->LogicToPixel(Point((sal_Int32)aObjTopLeft.getX(), (sal_Int32)aObjTopLeft.getY())));
539                         const Point aObjBR(mpOutputDevice->LogicToPixel(Point((sal_Int32)aObjBottomRight.getX(), (sal_Int32)aObjBottomRight.getY())));
540 
541                         const basegfx::B2DPoint aBmpTopLeft(aLocalTransform * rFillBitmapAttribute.getTopLeft());
542                         const basegfx::B2DPoint aBmpBottomRight(aLocalTransform * basegfx::B2DPoint(rFillBitmapAttribute.getTopLeft() + rFillBitmapAttribute.getSize()));
543                         const Point aBmpTL(mpOutputDevice->LogicToPixel(Point((sal_Int32)aBmpTopLeft.getX(), (sal_Int32)aBmpTopLeft.getY())));
544                         const Point aBmpBR(mpOutputDevice->LogicToPixel(Point((sal_Int32)aBmpBottomRight.getX(), (sal_Int32)aBmpBottomRight.getY())));
545 
546                         sal_Int32 nOWidth(aObjBR.X() - aObjTL.X());
547                         sal_Int32 nOHeight(aObjBR.Y() - aObjTL.Y());
548 
549                         // only do something when object has a size in discrete units
550                         if(nOWidth > 0 && nOHeight > 0)
551                         {
552                             sal_Int32 nBWidth(aBmpBR.X() - aBmpTL.X());
553                             sal_Int32 nBHeight(aBmpBR.Y() - aBmpTL.Y());
554 
555                             // only do something when bitmap fill has a size in discrete units
556                             if(nBWidth > 0 && nBHeight > 0)
557                             {
558                                 sal_Int32 nBLeft(aBmpTL.X());
559                                 sal_Int32 nBTop(aBmpTL.Y());
560 
561                                 if(nBLeft > aObjTL.X())
562                                 {
563                                     nBLeft -= ((nBLeft / nBWidth) + 1L) * nBWidth;
564                                 }
565 
566                                 if(nBLeft + nBWidth <= aObjTL.X())
567                                 {
568                                     nBLeft -= (nBLeft / nBWidth) * nBWidth;
569                                 }
570 
571                                 if(nBTop > aObjTL.Y())
572                                 {
573                                     nBTop -= ((nBTop / nBHeight) + 1L) * nBHeight;
574                                 }
575 
576                                 if(nBTop + nBHeight <= aObjTL.Y())
577                                 {
578                                     nBTop -= (nBTop / nBHeight) * nBHeight;
579                                 }
580 
581                                 // nBWidth, nBHeight is the pixel size of the neede bitmap. To not need to scale it
582                                 // in vcl many times, create a size-optimized version
583                                 const Size aNeededBitmapSizePixel(nBWidth, nBHeight);
584 
585                                 if(aNeededBitmapSizePixel != aBitmapEx.GetSizePixel())
586                                 {
587                                     aBitmapEx.Scale(aNeededBitmapSizePixel);
588                                 }
589 
590                                 // prepare OutDev
591                                 const Point aEmptyPoint(0, 0);
592                                 const Rectangle aVisiblePixel(aEmptyPoint, mpOutputDevice->GetOutputSizePixel());
593                                 const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled());
594                                 mpOutputDevice->EnableMapMode(false);
595 
596                                 for(sal_Int32 nXPos(nBLeft); nXPos < aObjTL.X() + nOWidth; nXPos += nBWidth)
597                                 {
598                                     for(sal_Int32 nYPos(nBTop); nYPos < aObjTL.Y() + nOHeight; nYPos += nBHeight)
599                                     {
600                                         const Rectangle aOutRectPixel(Point(nXPos, nYPos), aNeededBitmapSizePixel);
601 
602                                         if(aOutRectPixel.IsOver(aVisiblePixel))
603                                         {
604                                             mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(), aBitmapEx);
605                                         }
606                                     }
607                                 }
608 
609                                 // restore OutDev
610                                 mpOutputDevice->EnableMapMode(bWasEnabled);
611                             }
612                         }
613                     }
614                 }
615             }
616 
617             if(!bPrimitiveAccepted)
618             {
619                 // do not accept, use decomposition
620                 process(rFillBitmapCandidate.get2DDecomposition(getViewInformation2D()));
621             }
622         }
623 
624         // direct draw of gradient
625         void VclProcessor2D::RenderPolyPolygonGradientPrimitive2D(const primitive2d::PolyPolygonGradientPrimitive2D& rPolygonCandidate)
626         {
627             const attribute::FillGradientAttribute& rGradient(rPolygonCandidate.getFillGradient());
628             basegfx::BColor aStartColor(maBColorModifierStack.getModifiedColor(rGradient.getStartColor()));
629             basegfx::BColor aEndColor(maBColorModifierStack.getModifiedColor(rGradient.getEndColor()));
630             basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon());
631 
632             if(aLocalPolyPolygon.count())
633             {
634                 aLocalPolyPolygon.transform(maCurrentTransformation);
635 
636                 if(aStartColor == aEndColor)
637                 {
638                     // no gradient at all, draw as polygon in AA and non-AA case
639                     mpOutputDevice->SetLineColor();
640                     mpOutputDevice->SetFillColor(Color(aStartColor));
641                     mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon);
642                 }
643                 else if(getOptionsDrawinglayer().IsAntiAliasing())
644                 {
645                     // For AA, direct render has to be avoided since it uses XOR maskings which will not
646                     // work with AA. Instead, the decompose which uses MaskPrimitive2D with fillings is
647                     // used
648                     process(rPolygonCandidate.get2DDecomposition(getViewInformation2D()));
649                 }
650                 else
651                 {
652                     impDrawGradientToOutDev(
653                         *mpOutputDevice, aLocalPolyPolygon, rGradient.getStyle(), rGradient.getSteps(),
654                         aStartColor, aEndColor, rGradient.getBorder(),
655                         rGradient.getAngle(), rGradient.getOffsetX(), rGradient.getOffsetY(), false);
656                 }
657             }
658         }
659 
660         // direct draw of bitmap
661         void VclProcessor2D::RenderPolyPolygonBitmapPrimitive2D(const primitive2d::PolyPolygonBitmapPrimitive2D& rPolygonCandidate)
662         {
663             bool bDone(false);
664             const basegfx::B2DPolyPolygon& rPolyPolygon = rPolygonCandidate.getB2DPolyPolygon();
665 
666             if(rPolyPolygon.count())
667             {
668                 const attribute::FillBitmapAttribute& rFillBitmapAttribute = rPolygonCandidate.getFillBitmap();
669                 const BitmapEx& rBitmapEx = rFillBitmapAttribute.getBitmapEx();
670 
671                 if(rBitmapEx.IsEmpty())
672                 {
673                     // empty bitmap, done
674                     bDone = true;
675                 }
676                 else
677                 {
678                     // try to catch cases where the bitmap will be color-modified to a single
679                     // color (e.g. shadow). This would NOT be optimizable with an transparence channel
680                     // at the Bitmap which we do not have here. When this should change, this
681                     // optimization has to be reworked accordingly.
682                     const sal_uInt32 nBColorModifierStackCount(maBColorModifierStack.count());
683 
684                     if(nBColorModifierStackCount)
685                     {
686                         const basegfx::BColorModifier& rTopmostModifier = maBColorModifierStack.getBColorModifier(nBColorModifierStackCount - 1);
687 
688                         if(basegfx::BCOLORMODIFYMODE_REPLACE == rTopmostModifier.getMode())
689                         {
690                             // the bitmap fill is in unified color, so we can replace it with
691                             // a single polygon fill. The form of the fill depends on tiling
692                             if(rFillBitmapAttribute.getTiling())
693                             {
694                                 // with tiling, fill the whole PolyPolygon with the modifier color
695                                 basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon);
696 
697                                 aLocalPolyPolygon.transform(maCurrentTransformation);
698                                 mpOutputDevice->SetLineColor();
699                                 mpOutputDevice->SetFillColor(Color(rTopmostModifier.getBColor()));
700                                 mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon);
701                             }
702                             else
703                             {
704                                 // without tiling, only the area common to the bitmap tile and the
705                                 // PolyPolygon is filled. Create the bitmap tile area in object
706                                 // coordinates. For this, the object transformation needs to be created
707                                 // from the already scaled PolyPolygon. The tile area in object
708                                 // coordinates wil always be non-rotated, so it's not necessary to
709                                 // work with a polygon here
710                                 basegfx::B2DRange aTileRange(rFillBitmapAttribute.getTopLeft(),
711                                     rFillBitmapAttribute.getTopLeft() + rFillBitmapAttribute.getSize());
712                                 const basegfx::B2DRange aPolyPolygonRange(rPolyPolygon.getB2DRange());
713                                 basegfx::B2DHomMatrix aNewObjectTransform;
714 
715                                 aNewObjectTransform.set(0, 0, aPolyPolygonRange.getWidth());
716                                 aNewObjectTransform.set(1, 1, aPolyPolygonRange.getHeight());
717                                 aNewObjectTransform.set(0, 2, aPolyPolygonRange.getMinX());
718                                 aNewObjectTransform.set(1, 2, aPolyPolygonRange.getMinY());
719                                 aTileRange.transform(aNewObjectTransform);
720 
721                                 // now clip the object polyPolygon against the tile range
722                                 // to get the common area (OR)
723                                 basegfx::B2DPolyPolygon aTarget = basegfx::tools::clipPolyPolygonOnRange(rPolyPolygon, aTileRange, true, false);
724 
725                                 if(aTarget.count())
726                                 {
727                                     aTarget.transform(maCurrentTransformation);
728                                     mpOutputDevice->SetLineColor();
729                                     mpOutputDevice->SetFillColor(Color(rTopmostModifier.getBColor()));
730                                     mpOutputDevice->DrawPolyPolygon(aTarget);
731                                 }
732                             }
733 
734                             bDone = true;
735                         }
736                     }
737                 }
738             }
739             else
740             {
741                 // empty polyPolygon, done
742                 bDone = true;
743             }
744 
745             if(!bDone)
746             {
747                 // use default decomposition
748                 process(rPolygonCandidate.get2DDecomposition(getViewInformation2D()));
749             }
750         }
751 
752         // direct draw of PolyPolygon with color
753         void VclProcessor2D::RenderPolyPolygonColorPrimitive2D(const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate)
754         {
755             const basegfx::BColor aPolygonColor(maBColorModifierStack.getModifiedColor(rPolygonCandidate.getBColor()));
756             mpOutputDevice->SetFillColor(Color(aPolygonColor));
757             mpOutputDevice->SetLineColor();
758 
759             basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon());
760             aLocalPolyPolygon.transform(maCurrentTransformation);
761 
762             static bool bCheckTrapezoidDecomposition(false);
763             static bool bShowOutlinesThere(false);
764             if(bCheckTrapezoidDecomposition)
765             {
766                 // clip against discrete ViewPort
767                 const basegfx::B2DRange& rDiscreteViewport = getViewInformation2D().getDiscreteViewport();
768                 aLocalPolyPolygon = basegfx::tools::clipPolyPolygonOnRange(
769                     aLocalPolyPolygon, rDiscreteViewport, true, false);
770 
771                 if(aLocalPolyPolygon.count())
772                 {
773                     // subdivide
774                     aLocalPolyPolygon = basegfx::tools::adaptiveSubdivideByDistance(
775                         aLocalPolyPolygon, 0.5);
776 
777                     // trapezoidize
778                     basegfx::B2DTrapezoidVector aB2DTrapezoidVector;
779                     basegfx::tools::trapezoidSubdivide(aB2DTrapezoidVector, aLocalPolyPolygon);
780 
781                     const sal_uInt32 nCount(aB2DTrapezoidVector.size());
782 
783                     if(nCount)
784                     {
785                         basegfx::BColor aInvPolygonColor(aPolygonColor);
786                         aInvPolygonColor.invert();
787 
788                         for(sal_uInt32 a(0); a < nCount; a++)
789                         {
790                             const basegfx::B2DPolygon aTempPolygon(aB2DTrapezoidVector[a].getB2DPolygon());
791 
792                             if(bShowOutlinesThere)
793                             {
794                                 mpOutputDevice->SetFillColor(Color(aPolygonColor));
795                                 mpOutputDevice->SetLineColor();
796                             }
797 
798                             mpOutputDevice->DrawPolygon(aTempPolygon);
799 
800                             if(bShowOutlinesThere)
801                             {
802                                 mpOutputDevice->SetFillColor();
803                                 mpOutputDevice->SetLineColor(Color(aInvPolygonColor));
804                                 mpOutputDevice->DrawPolyLine(aTempPolygon, 0.0);
805                             }
806                         }
807                     }
808                 }
809             }
810             else
811             {
812                 mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon);
813 
814                 if(mnPolygonStrokePrimitive2D
815                     && getOptionsDrawinglayer().IsAntiAliasing()
816                     && (mpOutputDevice->GetAntialiasing() & ANTIALIASING_ENABLE_B2DDRAW))
817                 {
818                     // when AA is on and this filled polygons are the result of stroked line geometry,
819                     // draw the geometry once extra as lines to avoid AA 'gaps' between partial polygons
820                     mpOutputDevice->SetFillColor();
821                     mpOutputDevice->SetLineColor(Color(aPolygonColor));
822                     const sal_uInt32 nCount(aLocalPolyPolygon.count());
823 
824                     for(sal_uInt32 a(0); a < nCount; a++)
825                     {
826                         mpOutputDevice->DrawPolyLine(aLocalPolyPolygon.getB2DPolygon(a), 0.0);
827                     }
828                 }
829             }
830         }
831 
832         // direct draw of MetaFile
833         void VclProcessor2D::RenderMetafilePrimitive2D(const primitive2d::MetafilePrimitive2D& rMetaCandidate)
834         {
835             // decompose matrix to check for shear, rotate and mirroring
836             basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation * rMetaCandidate.getTransform());
837             basegfx::B2DVector aScale, aTranslate;
838             double fRotate, fShearX;
839             aLocalTransform.decompose(aScale, aTranslate, fRotate, fShearX);
840 
841             if(basegfx::fTools::less(aScale.getX(), 0.0) && basegfx::fTools::less(aScale.getY(), 0.0))
842             {
843                 // #i102175# handle special case: If scale is negative in (x,y) (3rd quadrant), it can
844                 // be expressed as rotation by PI. This needs to be done for Metafiles since
845                 // these can be rotated, but not really mirrored
846                 aScale = basegfx::absolute(aScale);
847                 fRotate += F_PI;
848             }
849 
850             // get BoundRect
851             basegfx::B2DRange aOutlineRange(rMetaCandidate.getB2DRange(getViewInformation2D()));
852             aOutlineRange.transform(maCurrentTransformation);
853 
854             // Due to the integer MapModes used from VCL aind inside MetaFiles errors of up to three
855             // pixels in size may happen. As long as there is no better way (e.g. convert the MetaFile
856             // to primitives) it is necessary to reduce maximum pixel size by 1 in X and Y and to use
857             // the inner pixel bounds accordingly (ceil resp. floor). This will also be done for logic
858             // units e.g. when creating a new MetaFile, but since much huger value ranges are used
859             // there typically will be okay for this compromize.
860             Rectangle aDestRectView(
861                 // !!CAUTION!! Here, ceil and floor are exchanged BY PURPOSE, do NOT copy when
862                 // looking for a standard conversion to rectangle (!)
863                 (sal_Int32)ceil(aOutlineRange.getMinX()), (sal_Int32)ceil(aOutlineRange.getMinY()),
864                 (sal_Int32)floor(aOutlineRange.getMaxX()), (sal_Int32)floor(aOutlineRange.getMaxY()));
865 
866             // get metafile (copy it)
867             GDIMetaFile aMetaFile;
868 
869             if(maBColorModifierStack.count())
870             {
871                 const basegfx::BColor aRGBBaseColor(0, 0, 0);
872                 const basegfx::BColor aRGBColor(maBColorModifierStack.getModifiedColor(aRGBBaseColor));
873                 aMetaFile = rMetaCandidate.getMetaFile().GetMonochromeMtf(Color(aRGBColor));
874             }
875             else
876             {
877                 aMetaFile = rMetaCandidate.getMetaFile();
878             }
879 
880             // rotation
881             if(!basegfx::fTools::equalZero(fRotate))
882             {
883                 // #i103530#
884                 // MetaFile::Rotate has no input parameter check, so the parameter needs to be
885                 // well-aligned to the old range [0..3600] 10th degrees with inverse orientation
886                 sal_Int16 nRotation((sal_Int16)((fRotate / F_PI180) * -10.0));
887 
888                 while(nRotation < 0)
889                     nRotation += 3600;
890 
891                 while(nRotation >= 3600)
892                     nRotation -= 3600;
893 
894                 aMetaFile.Rotate(nRotation);
895             }
896 
897             // Prepare target output size
898             Size aDestSize(aDestRectView.GetSize());
899 
900             if(aDestSize.getWidth() && aDestSize.getHeight())
901             {
902                 // Get preferred Metafile output size. When it's very equal to the output size, it's probably
903                 // a rounding error somewhere, so correct it to get a 1:1 output without single pixel scalings
904                 // of the Metafile (esp. for contaned Bitmaps, e.g 3D charts)
905                 const Size aPrefSize(mpOutputDevice->LogicToPixel(aMetaFile.GetPrefSize(), aMetaFile.GetPrefMapMode()));
906 
907                 if(aPrefSize.getWidth() && (aPrefSize.getWidth() - 1 == aDestSize.getWidth() || aPrefSize.getWidth() + 1 == aDestSize.getWidth()))
908                 {
909                     aDestSize.setWidth(aPrefSize.getWidth());
910                 }
911 
912                 if(aPrefSize.getHeight() && (aPrefSize.getHeight() - 1 == aDestSize.getHeight() || aPrefSize.getHeight() + 1 == aDestSize.getHeight()))
913                 {
914                     aDestSize.setHeight(aPrefSize.getHeight());
915                 }
916 
917                 // paint it
918                 aMetaFile.WindStart();
919                 aMetaFile.Play(mpOutputDevice, aDestRectView.TopLeft(), aDestSize);
920             }
921         }
922 
923         // mask group. Force output to VDev and create mask from given mask
924         void VclProcessor2D::RenderMaskPrimitive2DPixel(const primitive2d::MaskPrimitive2D& rMaskCandidate)
925         {
926             if(rMaskCandidate.getChildren().hasElements())
927             {
928                 basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask());
929 
930                 if(aMask.count())
931                 {
932                     aMask.transform(maCurrentTransformation);
933                     const basegfx::B2DRange aRange(basegfx::tools::getRange(aMask));
934                     impBufferDevice aBufferDevice(*mpOutputDevice, aRange, true);
935 
936                     if(aBufferDevice.isVisible())
937                     {
938                         // remember last OutDev and set to content
939                         OutputDevice* pLastOutputDevice = mpOutputDevice;
940                         mpOutputDevice = &aBufferDevice.getContent();
941 
942                         // paint to it
943                         process(rMaskCandidate.getChildren());
944 
945                         // back to old OutDev
946                         mpOutputDevice = pLastOutputDevice;
947 
948                         // draw mask
949                         if(getOptionsDrawinglayer().IsAntiAliasing())
950                         {
951                             // with AA, use 8bit AlphaMask to get nice borders
952                             VirtualDevice& rTransparence = aBufferDevice.getTransparence();
953                             rTransparence.SetLineColor();
954                             rTransparence.SetFillColor(COL_BLACK);
955                             rTransparence.DrawPolyPolygon(aMask);
956 
957                             // dump buffer to outdev
958                             aBufferDevice.paint();
959                         }
960                         else
961                         {
962                             // No AA, use 1bit mask
963                             VirtualDevice& rMask = aBufferDevice.getMask();
964                             rMask.SetLineColor();
965                             rMask.SetFillColor(COL_BLACK);
966                             rMask.DrawPolyPolygon(aMask);
967 
968                             // dump buffer to outdev
969                             aBufferDevice.paint();
970                         }
971                     }
972                 }
973             }
974         }
975 
976         // modified color group. Force output to unified color.
977         void VclProcessor2D::RenderModifiedColorPrimitive2D(const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate)
978         {
979             if(rModifiedCandidate.getChildren().hasElements())
980             {
981                 maBColorModifierStack.push(rModifiedCandidate.getColorModifier());
982                 process(rModifiedCandidate.getChildren());
983                 maBColorModifierStack.pop();
984             }
985         }
986 
987         // unified sub-transparence. Draw to VDev first.
988         void VclProcessor2D::RenderUnifiedTransparencePrimitive2D(const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate)
989         {
990             static bool bForceToDecomposition(false);
991 
992             if(rTransCandidate.getChildren().hasElements())
993             {
994                 if(bForceToDecomposition)
995                 {
996                     // use decomposition
997                     process(rTransCandidate.get2DDecomposition(getViewInformation2D()));
998                 }
999                 else
1000                 {
1001                     if(0.0 == rTransCandidate.getTransparence())
1002                     {
1003                         // no transparence used, so just use the content
1004                         process(rTransCandidate.getChildren());
1005                     }
1006                     else if(rTransCandidate.getTransparence() > 0.0 && rTransCandidate.getTransparence() < 1.0)
1007                     {
1008                         // transparence is in visible range
1009                         basegfx::B2DRange aRange(primitive2d::getB2DRangeFromPrimitive2DSequence(rTransCandidate.getChildren(), getViewInformation2D()));
1010                         aRange.transform(maCurrentTransformation);
1011                         impBufferDevice aBufferDevice(*mpOutputDevice, aRange, true);
1012 
1013                         if(aBufferDevice.isVisible())
1014                         {
1015                             // remember last OutDev and set to content
1016                             OutputDevice* pLastOutputDevice = mpOutputDevice;
1017                             mpOutputDevice = &aBufferDevice.getContent();
1018 
1019                             // paint content to it
1020                             process(rTransCandidate.getChildren());
1021 
1022                             // back to old OutDev
1023                             mpOutputDevice = pLastOutputDevice;
1024 
1025                             // dump buffer to outdev using given transparence
1026                             aBufferDevice.paint(rTransCandidate.getTransparence());
1027                         }
1028                     }
1029                 }
1030             }
1031         }
1032 
1033         // sub-transparence group. Draw to VDev first.
1034         void VclProcessor2D::RenderTransparencePrimitive2D(const primitive2d::TransparencePrimitive2D& rTransCandidate)
1035         {
1036             if(rTransCandidate.getChildren().hasElements())
1037             {
1038                 basegfx::B2DRange aRange(primitive2d::getB2DRangeFromPrimitive2DSequence(rTransCandidate.getChildren(), getViewInformation2D()));
1039                 aRange.transform(maCurrentTransformation);
1040                 impBufferDevice aBufferDevice(*mpOutputDevice, aRange, true);
1041 
1042                 if(aBufferDevice.isVisible())
1043                 {
1044                     // remember last OutDev and set to content
1045                     OutputDevice* pLastOutputDevice = mpOutputDevice;
1046                     mpOutputDevice = &aBufferDevice.getContent();
1047 
1048                     // paint content to it
1049                     process(rTransCandidate.getChildren());
1050 
1051                     // set to mask
1052                     mpOutputDevice = &aBufferDevice.getTransparence();
1053 
1054                     // when painting transparence masks, reset the color stack
1055                     basegfx::BColorModifierStack aLastBColorModifierStack(maBColorModifierStack);
1056                     maBColorModifierStack = basegfx::BColorModifierStack();
1057 
1058                     // paint mask to it (always with transparence intensities, evtl. with AA)
1059                     process(rTransCandidate.getTransparence());
1060 
1061                     // back to old color stack
1062                     maBColorModifierStack = aLastBColorModifierStack;
1063 
1064                     // back to old OutDev
1065                     mpOutputDevice = pLastOutputDevice;
1066 
1067                     // dump buffer to outdev
1068                     aBufferDevice.paint();
1069                 }
1070             }
1071         }
1072 
1073         // transform group.
1074         void VclProcessor2D::RenderTransformPrimitive2D(const primitive2d::TransformPrimitive2D& rTransformCandidate)
1075         {
1076             // remember current transformation and ViewInformation
1077             const basegfx::B2DHomMatrix aLastCurrentTransformation(maCurrentTransformation);
1078             const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
1079 
1080             // create new transformations for CurrentTransformation
1081             // and for local ViewInformation2D
1082             maCurrentTransformation = maCurrentTransformation * rTransformCandidate.getTransformation();
1083             const geometry::ViewInformation2D aViewInformation2D(
1084                 getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(),
1085                 getViewInformation2D().getViewTransformation(),
1086                 getViewInformation2D().getViewport(),
1087                 getViewInformation2D().getVisualizedPage(),
1088                 getViewInformation2D().getViewTime(),
1089                 getViewInformation2D().getExtendedInformationSequence());
1090             updateViewInformation(aViewInformation2D);
1091 
1092             // proccess content
1093             process(rTransformCandidate.getChildren());
1094 
1095             // restore transformations
1096             maCurrentTransformation = aLastCurrentTransformation;
1097             updateViewInformation(aLastViewInformation2D);
1098         }
1099 
1100         // new XDrawPage for ViewInformation2D
1101         void VclProcessor2D::RenderPagePreviewPrimitive2D(const primitive2d::PagePreviewPrimitive2D& rPagePreviewCandidate)
1102         {
1103             // remember current transformation and ViewInformation
1104             const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
1105 
1106             // create new local ViewInformation2D
1107             const geometry::ViewInformation2D aViewInformation2D(
1108                 getViewInformation2D().getObjectTransformation(),
1109                 getViewInformation2D().getViewTransformation(),
1110                 getViewInformation2D().getViewport(),
1111                 rPagePreviewCandidate.getXDrawPage(),
1112                 getViewInformation2D().getViewTime(),
1113                 getViewInformation2D().getExtendedInformationSequence());
1114             updateViewInformation(aViewInformation2D);
1115 
1116             // proccess decomposed content
1117             process(rPagePreviewCandidate.get2DDecomposition(getViewInformation2D()));
1118 
1119             // restore transformations
1120             updateViewInformation(aLastViewInformation2D);
1121         }
1122 
1123         // marker
1124         void VclProcessor2D::RenderMarkerArrayPrimitive2D(const primitive2d::MarkerArrayPrimitive2D& rMarkArrayCandidate)
1125         {
1126             static bool bCheckCompleteMarkerDecompose(false);
1127             if(bCheckCompleteMarkerDecompose)
1128             {
1129                 process(rMarkArrayCandidate.get2DDecomposition(getViewInformation2D()));
1130                 return;
1131             }
1132 
1133             // get data
1134             const std::vector< basegfx::B2DPoint >& rPositions = rMarkArrayCandidate.getPositions();
1135             const sal_uInt32 nCount(rPositions.size());
1136 
1137             if(nCount && !rMarkArrayCandidate.getMarker().IsEmpty())
1138             {
1139                 // get pixel size
1140                 const BitmapEx& rMarker(rMarkArrayCandidate.getMarker());
1141                 const Size aBitmapSize(rMarker.GetSizePixel());
1142 
1143                 if(aBitmapSize.Width() && aBitmapSize.Height())
1144                 {
1145                     // get discrete half size
1146                     const basegfx::B2DVector aDiscreteHalfSize(
1147                         (aBitmapSize.getWidth() - 1.0) * 0.5,
1148                         (aBitmapSize.getHeight() - 1.0) * 0.5);
1149                     const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled());
1150 
1151                     // do not forget evtl. moved origin in target device MapMode when
1152                     // switching it off; it would be missing and lead to wrong positions.
1153                     // All his could be done using logic sizes and coordinates, too, but
1154                     // we want a 1:1 bitmap rendering here, so it's more safe and faster
1155                     // to work with switching off MapMode usage completely.
1156                     const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin());
1157 
1158                     mpOutputDevice->EnableMapMode(false);
1159 
1160                     for(std::vector< basegfx::B2DPoint >::const_iterator aIter(rPositions.begin()); aIter != rPositions.end(); aIter++)
1161                     {
1162                         const basegfx::B2DPoint aDiscreteTopLeft((maCurrentTransformation * (*aIter)) - aDiscreteHalfSize);
1163                         const Point aDiscretePoint(basegfx::fround(aDiscreteTopLeft.getX()), basegfx::fround(aDiscreteTopLeft.getY()));
1164 
1165                         mpOutputDevice->DrawBitmapEx(aDiscretePoint + aOrigin, rMarker);
1166                     }
1167 
1168                     mpOutputDevice->EnableMapMode(bWasEnabled);
1169                 }
1170             }
1171         }
1172 
1173         // point
1174         void VclProcessor2D::RenderPointArrayPrimitive2D(const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate)
1175         {
1176             const std::vector< basegfx::B2DPoint >& rPositions = rPointArrayCandidate.getPositions();
1177             const basegfx::BColor aRGBColor(maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor()));
1178             const Color aVCLColor(aRGBColor);
1179 
1180             for(std::vector< basegfx::B2DPoint >::const_iterator aIter(rPositions.begin()); aIter != rPositions.end(); aIter++)
1181             {
1182                 const basegfx::B2DPoint aViewPosition(maCurrentTransformation * (*aIter));
1183                 const Point aPos(basegfx::fround(aViewPosition.getX()), basegfx::fround(aViewPosition.getY()));
1184 
1185                 mpOutputDevice->DrawPixel(aPos, aVCLColor);
1186             }
1187         }
1188 
1189         void VclProcessor2D::RenderPolygonStrokePrimitive2D(const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate)
1190         {
1191             // #i101491# method restructured to clearly use the DrawPolyLine
1192             // calls starting from a deined line width
1193             const attribute::LineAttribute& rLineAttribute = rPolygonStrokeCandidate.getLineAttribute();
1194             const double fLineWidth(rLineAttribute.getWidth());
1195             bool bDone(false);
1196 
1197             if(basegfx::fTools::more(fLineWidth, 0.0))
1198             {
1199                 const basegfx::B2DVector aDiscreteUnit(maCurrentTransformation * basegfx::B2DVector(fLineWidth, 0.0));
1200                 const double fDiscreteLineWidth(aDiscreteUnit.getLength());
1201                 const attribute::StrokeAttribute& rStrokeAttribute = rPolygonStrokeCandidate.getStrokeAttribute();
1202                 const basegfx::BColor aHairlineColor(maBColorModifierStack.getModifiedColor(rLineAttribute.getColor()));
1203                 basegfx::B2DPolyPolygon aHairlinePolyPolygon;
1204 
1205                 mpOutputDevice->SetLineColor(Color(aHairlineColor));
1206                 mpOutputDevice->SetFillColor();
1207 
1208                 if(0.0 == rStrokeAttribute.getFullDotDashLen())
1209                 {
1210                     // no line dashing, just copy
1211                     aHairlinePolyPolygon.append(rPolygonStrokeCandidate.getB2DPolygon());
1212                 }
1213                 else
1214                 {
1215                     // else apply LineStyle
1216                     basegfx::tools::applyLineDashing(rPolygonStrokeCandidate.getB2DPolygon(),
1217                         rStrokeAttribute.getDotDashArray(),
1218                         &aHairlinePolyPolygon, 0, rStrokeAttribute.getFullDotDashLen());
1219                 }
1220 
1221                 const sal_uInt32 nCount(aHairlinePolyPolygon.count());
1222 
1223                 if(nCount)
1224                 {
1225                     const bool bAntiAliased(getOptionsDrawinglayer().IsAntiAliasing());
1226                     aHairlinePolyPolygon.transform(maCurrentTransformation);
1227 
1228                     for(sal_uInt32 a(0); a < nCount; a++)
1229                     {
1230                         basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a));
1231 
1232                         if(bAntiAliased)
1233                         {
1234                             if(basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 1.0))
1235                             {
1236                                 // line in range ]0.0 .. 1.0[
1237                                 // paint as simple hairline
1238                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1239                                 bDone = true;
1240                             }
1241                             else if(basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 2.0))
1242                             {
1243                                 // line in range [1.0 .. 2.0[
1244                                 // paint as 2x2 with dynamic line distance
1245                                 basegfx::B2DHomMatrix aMat;
1246                                 const double fDistance(fDiscreteLineWidth - 1.0);
1247                                 const double fHalfDistance(fDistance * 0.5);
1248 
1249                                 aMat.set(0, 2, -fHalfDistance);
1250                                 aMat.set(1, 2, -fHalfDistance);
1251                                 aCandidate.transform(aMat);
1252                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1253 
1254                                 aMat.set(0, 2, fDistance);
1255                                 aMat.set(1, 2, 0.0);
1256                                 aCandidate.transform(aMat);
1257                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1258 
1259                                 aMat.set(0, 2, 0.0);
1260                                 aMat.set(1, 2, fDistance);
1261                                 aCandidate.transform(aMat);
1262                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1263 
1264                                 aMat.set(0, 2, -fDistance);
1265                                 aMat.set(1, 2, 0.0);
1266                                 aCandidate.transform(aMat);
1267                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1268                                 bDone = true;
1269                             }
1270                             else if(basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 3.0))
1271                             {
1272                                 // line in range [2.0 .. 3.0]
1273                                 // paint as cross in a 3x3  with dynamic line distance
1274                                 basegfx::B2DHomMatrix aMat;
1275                                 const double fDistance((fDiscreteLineWidth - 1.0) * 0.5);
1276 
1277                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1278 
1279                                 aMat.set(0, 2, -fDistance);
1280                                 aMat.set(1, 2, 0.0);
1281                                 aCandidate.transform(aMat);
1282                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1283 
1284                                 aMat.set(0, 2, fDistance);
1285                                 aMat.set(1, 2, -fDistance);
1286                                 aCandidate.transform(aMat);
1287                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1288 
1289                                 aMat.set(0, 2, fDistance);
1290                                 aMat.set(1, 2, fDistance);
1291                                 aCandidate.transform(aMat);
1292                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1293 
1294                                 aMat.set(0, 2, -fDistance);
1295                                 aMat.set(1, 2, fDistance);
1296                                 aCandidate.transform(aMat);
1297                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1298                                 bDone = true;
1299                             }
1300                             else
1301                             {
1302                                 // #i101491# line width above 3.0
1303                             }
1304                         }
1305                         else
1306                         {
1307                             if(basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 1.5))
1308                             {
1309                                 // line width below 1.5, draw the basic hairline polygon
1310                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1311                                 bDone = true;
1312                             }
1313                             else if(basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 2.5))
1314                             {
1315                                 // line width is in range ]1.5 .. 2.5], use four hairlines
1316                                 // drawn in a square
1317                                 basegfx::B2DHomMatrix aMat;
1318                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1319 
1320                                 aMat.set(0, 2, 1.0);
1321                                 aMat.set(1, 2, 0.0);
1322                                 aCandidate.transform(aMat);
1323 
1324                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1325 
1326                                 aMat.set(0, 2, 0.0);
1327                                 aMat.set(1, 2, 1.0);
1328                                 aCandidate.transform(aMat);
1329 
1330                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1331 
1332                                 aMat.set(0, 2, -1.0);
1333                                 aMat.set(1, 2, 0.0);
1334                                 aCandidate.transform(aMat);
1335 
1336                                 mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
1337                                 bDone = true;
1338                             }
1339                             else
1340                             {
1341                                 // #i101491# line width is above 2.5
1342                             }
1343                         }
1344 
1345                         if(!bDone && rPolygonStrokeCandidate.getB2DPolygon().count() > 1000)
1346                         {
1347                             // #i101491# If the polygon complexity uses more than a given amount, do
1348                             // use OuputDevice::DrawPolyLine directly; this will avoid buffering all
1349                             // decompositions in primtives (memory) and fallback to old line painting
1350                             // for very complex polygons, too
1351                             mpOutputDevice->DrawPolyLine(aCandidate, fDiscreteLineWidth, rLineAttribute.getLineJoin());
1352                             bDone = true;
1353                         }
1354                     }
1355                 }
1356             }
1357 
1358             if(!bDone)
1359             {
1360                 // remeber that we enter a PolygonStrokePrimitive2D decomposition,
1361                 // used for AA thick line drawing
1362                 mnPolygonStrokePrimitive2D++;
1363 
1364                 // line width is big enough for standard filled polygon visualisation or zero
1365                 process(rPolygonStrokeCandidate.get2DDecomposition(getViewInformation2D()));
1366 
1367                 // leave PolygonStrokePrimitive2D
1368                 mnPolygonStrokePrimitive2D--;
1369             }
1370         }
1371 
1372         void VclProcessor2D::RenderEpsPrimitive2D(const primitive2d::EpsPrimitive2D& rEpsPrimitive2D)
1373         {
1374             // The new decomposition of Metafiles made it necessary to add an Eps
1375             // primitive to handle embedded Eps data. On some devices, this can be
1376             // painted directly (mac, printer).
1377             // To be able to handle the replacement correctly, i need to handle it myself
1378             // since DrawEPS will not be able e.g. to rotate the replacement. To be able
1379             // to do that, i added a boolean return to OutputDevice::DrawEPS(..)
1380             // to know when EPS was handled directly already.
1381             basegfx::B2DRange aRange(0.0, 0.0, 1.0, 1.0);
1382             aRange.transform(maCurrentTransformation * rEpsPrimitive2D.getEpsTransform());
1383 
1384             if(!aRange.isEmpty())
1385             {
1386                 const Rectangle aRectangle(
1387                     (sal_Int32)floor(aRange.getMinX()), (sal_Int32)floor(aRange.getMinY()),
1388                     (sal_Int32)ceil(aRange.getMaxX()), (sal_Int32)ceil(aRange.getMaxY()));
1389 
1390                 if(!aRectangle.IsEmpty())
1391                 {
1392                     // try to paint EPS directly without fallback visualisation
1393                     const bool bEPSPaintedDirectly(mpOutputDevice->DrawEPS(
1394                         aRectangle.TopLeft(),
1395                         aRectangle.GetSize(),
1396                         rEpsPrimitive2D.getGfxLink(),
1397                         0));
1398 
1399                     if(!bEPSPaintedDirectly)
1400                     {
1401                         // use the decomposition which will correctly handle the
1402                         // fallback visualisation using full transformation (e.g. rotation)
1403                         process(rEpsPrimitive2D.get2DDecomposition(getViewInformation2D()));
1404                     }
1405                 }
1406             }
1407         }
1408 
1409         void VclProcessor2D::adaptLineToFillDrawMode() const
1410         {
1411             const sal_uInt32 nOriginalDrawMode(mpOutputDevice->GetDrawMode());
1412 
1413             if(nOriginalDrawMode & (DRAWMODE_BLACKLINE|DRAWMODE_GRAYLINE|DRAWMODE_GHOSTEDLINE|DRAWMODE_WHITELINE|DRAWMODE_SETTINGSLINE))
1414             {
1415                 sal_uInt32 nAdaptedDrawMode(nOriginalDrawMode);
1416 
1417                 if(nOriginalDrawMode & DRAWMODE_BLACKLINE)
1418                 {
1419                     nAdaptedDrawMode |= DRAWMODE_BLACKFILL;
1420                 }
1421                 else
1422                 {
1423                     nAdaptedDrawMode &= ~DRAWMODE_BLACKFILL;
1424                 }
1425 
1426                 if(nOriginalDrawMode & DRAWMODE_GRAYLINE)
1427                 {
1428                     nAdaptedDrawMode |= DRAWMODE_GRAYFILL;
1429                 }
1430                 else
1431                 {
1432                     nAdaptedDrawMode &= ~DRAWMODE_GRAYFILL;
1433                 }
1434 
1435                 if(nOriginalDrawMode & DRAWMODE_GHOSTEDLINE)
1436                 {
1437                     nAdaptedDrawMode |= DRAWMODE_GHOSTEDFILL;
1438                 }
1439                 else
1440                 {
1441                     nAdaptedDrawMode &= ~DRAWMODE_GHOSTEDFILL;
1442                 }
1443 
1444                 if(nOriginalDrawMode & DRAWMODE_WHITELINE)
1445                 {
1446                     nAdaptedDrawMode |= DRAWMODE_WHITEFILL;
1447                 }
1448                 else
1449                 {
1450                     nAdaptedDrawMode &= ~DRAWMODE_WHITEFILL;
1451                 }
1452 
1453                 if(nOriginalDrawMode & DRAWMODE_SETTINGSLINE)
1454                 {
1455                     nAdaptedDrawMode |= DRAWMODE_SETTINGSFILL;
1456                 }
1457                 else
1458                 {
1459                     nAdaptedDrawMode &= ~DRAWMODE_SETTINGSFILL;
1460                 }
1461 
1462                 mpOutputDevice->SetDrawMode(nAdaptedDrawMode);
1463             }
1464         }
1465 
1466         void VclProcessor2D::adaptTextToFillDrawMode() const
1467         {
1468             const sal_uInt32 nOriginalDrawMode(mpOutputDevice->GetDrawMode());
1469             if(nOriginalDrawMode & (DRAWMODE_BLACKTEXT|DRAWMODE_GRAYTEXT|DRAWMODE_GHOSTEDTEXT|DRAWMODE_WHITETEXT|DRAWMODE_SETTINGSTEXT))
1470             {
1471                 sal_uInt32 nAdaptedDrawMode(nOriginalDrawMode);
1472 
1473                 if(nOriginalDrawMode & DRAWMODE_BLACKTEXT)
1474                 {
1475                     nAdaptedDrawMode |= DRAWMODE_BLACKFILL;
1476                 }
1477                 else
1478                 {
1479                     nAdaptedDrawMode &= ~DRAWMODE_BLACKFILL;
1480                 }
1481 
1482                 if(nOriginalDrawMode & DRAWMODE_GRAYTEXT)
1483                 {
1484                     nAdaptedDrawMode |= DRAWMODE_GRAYFILL;
1485                 }
1486                 else
1487                 {
1488                     nAdaptedDrawMode &= ~DRAWMODE_GRAYFILL;
1489                 }
1490 
1491                 if(nOriginalDrawMode & DRAWMODE_GHOSTEDTEXT)
1492                 {
1493                     nAdaptedDrawMode |= DRAWMODE_GHOSTEDFILL;
1494                 }
1495                 else
1496                 {
1497                     nAdaptedDrawMode &= ~DRAWMODE_GHOSTEDFILL;
1498                 }
1499 
1500                 if(nOriginalDrawMode & DRAWMODE_WHITETEXT)
1501                 {
1502                     nAdaptedDrawMode |= DRAWMODE_WHITEFILL;
1503                 }
1504                 else
1505                 {
1506                     nAdaptedDrawMode &= ~DRAWMODE_WHITEFILL;
1507                 }
1508 
1509                 if(nOriginalDrawMode & DRAWMODE_SETTINGSTEXT)
1510                 {
1511                     nAdaptedDrawMode |= DRAWMODE_SETTINGSFILL;
1512                 }
1513                 else
1514                 {
1515                     nAdaptedDrawMode &= ~DRAWMODE_SETTINGSFILL;
1516                 }
1517 
1518                 mpOutputDevice->SetDrawMode(nAdaptedDrawMode);
1519             }
1520         }
1521 
1522         //////////////////////////////////////////////////////////////////////////////
1523         // process support
1524 
1525         VclProcessor2D::VclProcessor2D(
1526             const geometry::ViewInformation2D& rViewInformation,
1527             OutputDevice& rOutDev)
1528         :   BaseProcessor2D(rViewInformation),
1529             mpOutputDevice(&rOutDev),
1530             maBColorModifierStack(),
1531             maCurrentTransformation(),
1532             maDrawinglayerOpt(),
1533             mnPolygonStrokePrimitive2D(0)
1534         {
1535             // set digit language, derived from SvtCTLOptions to have the correct
1536             // number display for arabic/hindi numerals
1537             const SvtCTLOptions aSvtCTLOptions;
1538             LanguageType eLang(LANGUAGE_SYSTEM);
1539 
1540             if(SvtCTLOptions::NUMERALS_HINDI == aSvtCTLOptions.GetCTLTextNumerals())
1541             {
1542                 eLang = LANGUAGE_ARABIC_SAUDI_ARABIA;
1543             }
1544             else if(SvtCTLOptions::NUMERALS_ARABIC == aSvtCTLOptions.GetCTLTextNumerals())
1545             {
1546                 eLang = LANGUAGE_ENGLISH;
1547             }
1548             else
1549             {
1550                 eLang = (LanguageType)Application::GetSettings().GetLanguage();
1551             }
1552 
1553             rOutDev.SetDigitLanguage(eLang);
1554         }
1555 
1556         VclProcessor2D::~VclProcessor2D()
1557         {
1558         }
1559     } // end of namespace processor2d
1560 } // end of namespace drawinglayer
1561 
1562 //////////////////////////////////////////////////////////////////////////////
1563 // eof
1564