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_canvas.hxx"
30 
31 #include <canvas/debug.hxx>
32 #include <tools/diagnose_ex.h>
33 
34 #include <rtl/logfile.hxx>
35 #include <rtl/math.hxx>
36 
37 #include <com/sun/star/rendering/TexturingMode.hpp>
38 #include <com/sun/star/rendering/CompositeOperation.hpp>
39 #include <com/sun/star/rendering/RepaintResult.hpp>
40 #include <com/sun/star/rendering/PathCapType.hpp>
41 #include <com/sun/star/rendering/PathJoinType.hpp>
42 
43 #include <basegfx/matrix/b2dhommatrix.hxx>
44 #include <basegfx/point/b2dpoint.hxx>
45 #include <basegfx/tools/canvastools.hxx>
46 #include <basegfx/matrix/b2dhommatrixtools.hxx>
47 
48 #include <comphelper/sequence.hxx>
49 #include <canvas/canvastools.hxx>
50 
51 #include "dx_spritecanvas.hxx"
52 #include "dx_impltools.hxx"
53 #include "dx_vcltools.hxx"
54 #include "dx_canvasfont.hxx"
55 #include "dx_textlayout.hxx"
56 #include "dx_canvashelper.hxx"
57 
58 #include <algorithm>
59 
60 
61 using namespace ::com::sun::star;
62 
63 namespace dxcanvas
64 {
65     namespace
66     {
67 		Gdiplus::LineCap gdiCapFromCap( sal_Int8 nCapType )
68         {
69             switch( nCapType )
70             {
71                 case rendering::PathCapType::BUTT:
72                     return Gdiplus::LineCapFlat;
73 
74                 case rendering::PathCapType::ROUND:
75                     return Gdiplus::LineCapRound;
76 
77                 case rendering::PathCapType::SQUARE:
78                     return Gdiplus::LineCapSquare;
79 
80                 default:
81                     ENSURE_OR_THROW( false,
82                                       "gdiCapFromCap(): Unexpected cap type" );
83             }
84 
85             return Gdiplus::LineCapFlat;
86         }
87 
88         Gdiplus::LineJoin gdiJoinFromJoin( sal_Int8 nJoinType )
89         {
90             switch( nJoinType )
91             {
92                 case rendering::PathJoinType::NONE:
93                     OSL_ENSURE( false,
94                                 "gdiJoinFromJoin(): Join NONE not possible, mapping to MITER" );
95                     // FALLTHROUGH intended
96                 case rendering::PathJoinType::MITER:
97                     return Gdiplus::LineJoinMiter;
98 
99                 case rendering::PathJoinType::ROUND:
100                     return Gdiplus::LineJoinRound;
101 
102                 case rendering::PathJoinType::BEVEL:
103                     return Gdiplus::LineJoinBevel;
104 
105                 default:
106                     ENSURE_OR_THROW( false,
107                                       "gdiJoinFromJoin(): Unexpected join type" );
108             }
109 
110             return Gdiplus::LineJoinMiter;
111         }
112     }
113 
114     CanvasHelper::CanvasHelper() :
115         mpGdiPlusUser( GDIPlusUser::createInstance() ),
116         mpDevice( NULL ),
117         mpGraphicsProvider(),
118         maOutputOffset()
119     {
120     }
121 
122     void CanvasHelper::disposing()
123     {
124         mpGraphicsProvider.reset();
125         mpDevice = NULL;
126         mpGdiPlusUser.reset();
127     }
128 
129     void CanvasHelper::setDevice( rendering::XGraphicDevice& rDevice )
130     {
131         mpDevice = &rDevice;
132     }
133 
134     void CanvasHelper::setTarget( const GraphicsProviderSharedPtr& rTarget )
135     {
136         ENSURE_OR_THROW( rTarget,
137                           "CanvasHelper::setTarget(): Invalid target" );
138         ENSURE_OR_THROW( !mpGraphicsProvider.get(),
139                           "CanvasHelper::setTarget(): target set, old target would be overwritten" );
140 
141         mpGraphicsProvider = rTarget;
142     }
143 
144     void CanvasHelper::setTarget( const GraphicsProviderSharedPtr& rTarget,
145                                   const ::basegfx::B2ISize& 	   rOutputOffset )
146     {
147         ENSURE_OR_THROW( rTarget,
148                          "CanvasHelper::setTarget(): invalid target" );
149         ENSURE_OR_THROW( !mpGraphicsProvider.get(),
150                          "CanvasHelper::setTarget(): target set, old target would be overwritten" );
151 
152         mpGraphicsProvider = rTarget;
153         maOutputOffset = rOutputOffset;
154     }
155 
156     void CanvasHelper::clear()
157     {
158         if( needOutput() )
159         {
160             GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
161             Gdiplus::Color aClearColor = Gdiplus::Color((Gdiplus::ARGB)Gdiplus::Color::White);
162 
163             ENSURE_OR_THROW(
164                 Gdiplus::Ok == pGraphics->SetCompositingMode(
165                     Gdiplus::CompositingModeSourceCopy ), // force set, don't blend
166                 "CanvasHelper::clear(): GDI+ SetCompositingMode call failed" );
167             ENSURE_OR_THROW(
168                 Gdiplus::Ok == pGraphics->Clear( aClearColor ),
169                 "CanvasHelper::clear(): GDI+ Clear call failed" );
170         }
171     }
172 
173     void CanvasHelper::drawPoint( const rendering::XCanvas* 	/*pCanvas*/,
174                                   const geometry::RealPoint2D& 	aPoint,
175                                   const rendering::ViewState& 	viewState,
176                                   const rendering::RenderState&	renderState )
177     {
178         if( needOutput() )
179         {
180             GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
181 
182             setupGraphicsState( pGraphics, viewState, renderState );
183 
184             Gdiplus::SolidBrush aBrush(
185                 Gdiplus::Color(
186                     tools::sequenceToArgb(renderState.DeviceColor)) );
187 
188             // determine size of one-by-one device pixel ellipse
189             Gdiplus::Matrix aMatrix;
190             pGraphics->GetTransform(&aMatrix);
191             aMatrix.Invert();
192             Gdiplus::PointF vector(1, 1);
193             aMatrix.TransformVectors(&vector);
194 
195             // paint a one-by-one circle, with the given point
196             // in the middle (rounded to float)
197             ENSURE_OR_THROW(
198                 Gdiplus::Ok == pGraphics->FillEllipse( &aBrush,
199                                                        // disambiguate call
200                                                        Gdiplus::REAL(aPoint.X),
201                                                        Gdiplus::REAL(aPoint.Y),
202                                                        Gdiplus::REAL(vector.X),
203                                                        Gdiplus::REAL(vector.Y) ),
204                 "CanvasHelper::drawPoint(): GDI+ call failed" );
205         }
206     }
207 
208     void CanvasHelper::drawLine( const rendering::XCanvas* 		/*pCanvas*/,
209                                  const geometry::RealPoint2D& 	aStartPoint,
210                                  const geometry::RealPoint2D& 	aEndPoint,
211                                  const rendering::ViewState& 	viewState,
212                                  const rendering::RenderState& 	renderState )
213     {
214         if( needOutput() )
215         {
216             GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
217 
218             setupGraphicsState( pGraphics, viewState, renderState );
219 
220             Gdiplus::Pen aPen(
221                 Gdiplus::Color(
222                     tools::sequenceToArgb(renderState.DeviceColor)),
223                 Gdiplus::REAL(0.0) );
224 
225             // #122683# Switched precedence of pixel offset
226             // mode. Seemingly, polygon stroking needs
227             // PixelOffsetModeNone to achieve visually pleasing
228             // results, whereas all other operations (e.g. polygon
229             // fills, bitmaps) look better with PixelOffsetModeHalf.
230             const Gdiplus::PixelOffsetMode aOldMode(
231                 pGraphics->GetPixelOffsetMode() );
232 			pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone );
233 
234             Gdiplus::Status hr = pGraphics->DrawLine( &aPen,
235                                                       Gdiplus::REAL(aStartPoint.X), // disambiguate call
236                                                       Gdiplus::REAL(aStartPoint.Y),
237                                                       Gdiplus::REAL(aEndPoint.X),
238                                                       Gdiplus::REAL(aEndPoint.Y) );
239 			pGraphics->SetPixelOffsetMode( aOldMode );
240 
241             ENSURE_OR_THROW(
242                 Gdiplus::Ok == hr,
243                 "CanvasHelper::drawLine(): GDI+ call failed" );
244         }
245     }
246 
247     void CanvasHelper::drawBezier( const rendering::XCanvas* 			/*pCanvas*/,
248                                    const geometry::RealBezierSegment2D&	aBezierSegment,
249                                    const geometry::RealPoint2D& 		aEndPoint,
250                                    const rendering::ViewState& 			viewState,
251                                    const rendering::RenderState& 		renderState )
252     {
253         if( needOutput() )
254         {
255             GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
256 
257             setupGraphicsState( pGraphics, viewState, renderState );
258 
259             Gdiplus::Pen aPen(
260                 Gdiplus::Color(
261                     tools::sequenceToArgb(renderState.DeviceColor)),
262                 Gdiplus::REAL(0.0) );
263 
264             // #122683# Switched precedence of pixel offset
265             // mode. Seemingly, polygon stroking needs
266             // PixelOffsetModeNone to achieve visually pleasing
267             // results, whereas all other operations (e.g. polygon
268             // fills, bitmaps) look better with PixelOffsetModeHalf.
269             const Gdiplus::PixelOffsetMode aOldMode(
270                 pGraphics->GetPixelOffsetMode() );
271 			pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone );
272 
273             Gdiplus::Status hr = pGraphics->DrawBezier( &aPen,
274                                                         Gdiplus::REAL(aBezierSegment.Px), // disambiguate call
275                                                         Gdiplus::REAL(aBezierSegment.Py),
276                                                         Gdiplus::REAL(aBezierSegment.C1x),
277                                                         Gdiplus::REAL(aBezierSegment.C1y),
278                                                         Gdiplus::REAL(aEndPoint.X),
279                                                         Gdiplus::REAL(aEndPoint.Y),
280                                                         Gdiplus::REAL(aBezierSegment.C2x),
281                                                         Gdiplus::REAL(aBezierSegment.C2y) );
282 
283 			pGraphics->SetPixelOffsetMode( aOldMode );
284 
285             ENSURE_OR_THROW(
286                 Gdiplus::Ok == hr,
287                 "CanvasHelper::drawBezier(): GDI+ call failed" );
288         }
289     }
290 
291     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawPolyPolygon( const rendering::XCanvas* 							/*pCanvas*/,
292                                                                                  const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
293                                                                                  const rendering::ViewState& 						viewState,
294                                                                                  const rendering::RenderState& 						renderState )
295     {
296         ENSURE_OR_THROW( xPolyPolygon.is(),
297                           "CanvasHelper::drawPolyPolygon: polygon is NULL");
298 
299         if( needOutput() )
300         {
301             GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
302 
303             setupGraphicsState( pGraphics, viewState, renderState );
304 
305             Gdiplus::Pen aPen(
306                 Gdiplus::Color(
307                     tools::sequenceToArgb(renderState.DeviceColor)),
308                 Gdiplus::REAL(0.0) );
309 
310             // #122683# Switched precedence of pixel offset
311             // mode. Seemingly, polygon stroking needs
312             // PixelOffsetModeNone to achieve visually pleasing
313             // results, whereas all other operations (e.g. polygon
314             // fills, bitmaps) look better with PixelOffsetModeHalf.
315             const Gdiplus::PixelOffsetMode aOldMode(
316                 pGraphics->GetPixelOffsetMode() );
317 			pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone );
318 
319             GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ) );
320 
321             // TODO(E1): Return value
322             Gdiplus::Status hr = pGraphics->DrawPath( &aPen, pPath.get() );
323 
324 			pGraphics->SetPixelOffsetMode( aOldMode );
325 
326             ENSURE_OR_THROW(
327                 Gdiplus::Ok == hr,
328                 "CanvasHelper::drawPolyPolygon(): GDI+ call failed" );
329         }
330 
331         // TODO(P1): Provide caching here.
332         return uno::Reference< rendering::XCachedPrimitive >(NULL);
333     }
334 
335     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokePolyPolygon( const rendering::XCanvas* 							/*pCanvas*/,
336                                                                                    const uno::Reference< rendering::XPolyPolygon2D >& 	xPolyPolygon,
337                                                                                    const rendering::ViewState& 							viewState,
338                                                                                    const rendering::RenderState& 						renderState,
339                                                                                    const rendering::StrokeAttributes& 					strokeAttributes )
340     {
341         ENSURE_OR_THROW( xPolyPolygon.is(),
342                           "CanvasHelper::drawPolyPolygon: polygon is NULL");
343 
344         if( needOutput() )
345         {
346             GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
347 
348             setupGraphicsState( pGraphics, viewState, renderState );
349 
350 
351             // Setup stroke pen
352             // ----------------
353 
354             Gdiplus::Pen aPen(
355                 Gdiplus::Color(
356                     tools::sequenceToArgb(renderState.DeviceColor)),
357                 static_cast< Gdiplus::REAL >(strokeAttributes.StrokeWidth) );
358 
359             // #122683# Switched precedence of pixel offset
360             // mode. Seemingly, polygon stroking needs
361             // PixelOffsetModeNone to achieve visually pleasing
362             // results, whereas all other operations (e.g. polygon
363             // fills, bitmaps) look better with PixelOffsetModeHalf.
364             const Gdiplus::PixelOffsetMode aOldMode(
365                 pGraphics->GetPixelOffsetMode() );
366 			pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone );
367 
368             const bool bIsMiter(rendering::PathJoinType::MITER == strokeAttributes.JoinType);
369             const bool bIsNone(rendering::PathJoinType::NONE == strokeAttributes.JoinType);
370 
371             if(bIsMiter)
372                 aPen.SetMiterLimit( static_cast< Gdiplus::REAL >(strokeAttributes.MiterLimit) );
373 
374             const ::std::vector< Gdiplus::REAL >& rDashArray(
375                 ::comphelper::sequenceToContainer< ::std::vector< Gdiplus::REAL > >(
376                     strokeAttributes.DashArray ) );
377             if( !rDashArray.empty() )
378             {
379                 aPen.SetDashPattern( &rDashArray[0],
380                                      rDashArray.size() );
381             }
382             aPen.SetLineCap( gdiCapFromCap(strokeAttributes.StartCapType),
383                              gdiCapFromCap(strokeAttributes.EndCapType),
384                              Gdiplus::DashCapFlat );
385             if(!bIsNone)
386                 aPen.SetLineJoin( gdiJoinFromJoin(strokeAttributes.JoinType) );
387 
388             GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon, bIsNone ) );
389 
390             // TODO(E1): Return value
391             Gdiplus::Status hr = pGraphics->DrawPath( &aPen, pPath.get() );
392 
393 			pGraphics->SetPixelOffsetMode( aOldMode );
394 
395             ENSURE_OR_THROW(
396                 Gdiplus::Ok == hr,
397                 "CanvasHelper::strokePolyPolygon(): GDI+ call failed" );
398         }
399 
400         // TODO(P1): Provide caching here.
401         return uno::Reference< rendering::XCachedPrimitive >(NULL);
402     }
403 
404     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygon( const rendering::XCanvas* 							/*pCanvas*/,
405                                                                                            const uno::Reference< rendering::XPolyPolygon2D >& 	/*xPolyPolygon*/,
406                                                                                            const rendering::ViewState& 							/*viewState*/,
407                                                                                            const rendering::RenderState& 						/*renderState*/,
408                                                                                            const uno::Sequence< rendering::Texture >& 			/*textures*/,
409                                                                                            const rendering::StrokeAttributes& 					/*strokeAttributes*/ )
410     {
411         // TODO
412         return uno::Reference< rendering::XCachedPrimitive >(NULL);
413     }
414 
415     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTextureMappedPolyPolygon( const rendering::XCanvas* 							/*pCanvas*/,
416                                                                                                 const uno::Reference< rendering::XPolyPolygon2D >&	/*xPolyPolygon*/,
417                                                                                                 const rendering::ViewState& 						/*viewState*/,
418                                                                                                 const rendering::RenderState& 						/*renderState*/,
419                                                                                                 const uno::Sequence< rendering::Texture >& 			/*textures*/,
420                                                                                                 const uno::Reference< geometry::XMapping2D >& 		/*xMapping*/,
421                                                                                                 const rendering::StrokeAttributes& 					/*strokeAttributes*/ )
422     {
423         // TODO
424         return uno::Reference< rendering::XCachedPrimitive >(NULL);
425     }
426 
427     uno::Reference< rendering::XPolyPolygon2D >   CanvasHelper::queryStrokeShapes( const rendering::XCanvas* 							/*pCanvas*/,
428                                                                                    const uno::Reference< rendering::XPolyPolygon2D >& 	/*xPolyPolygon*/,
429                                                                                    const rendering::ViewState& 							/*viewState*/,
430                                                                                    const rendering::RenderState& 						/*renderState*/,
431                                                                                    const rendering::StrokeAttributes& 					/*strokeAttributes*/ )
432     {
433         // TODO
434         return uno::Reference< rendering::XPolyPolygon2D >(NULL);
435     }
436 
437     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillPolyPolygon( const rendering::XCanvas* 							/*pCanvas*/,
438                                                                                  const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
439                                                                                  const rendering::ViewState& 						viewState,
440                                                                                  const rendering::RenderState& 						renderState )
441     {
442         ENSURE_OR_THROW( xPolyPolygon.is(),
443                           "CanvasHelper::fillPolyPolygon: polygon is NULL");
444 
445         if( needOutput() )
446         {
447             GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
448 
449 			setupGraphicsState( pGraphics, viewState, renderState );
450 
451             Gdiplus::SolidBrush aBrush(
452                 tools::sequenceToArgb(renderState.DeviceColor));
453 
454             GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ) );
455 
456             // TODO(F1): FillRule
457             ENSURE_OR_THROW( Gdiplus::Ok == pGraphics->FillPath( &aBrush, pPath.get() ),
458                              "CanvasHelper::fillPolyPolygon(): GDI+ call failed  " );
459         }
460 
461         // TODO(P1): Provide caching here.
462         return uno::Reference< rendering::XCachedPrimitive >(NULL);
463     }
464 
465     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas* 							/*pCanvas*/,
466                                                                                               const uno::Reference< rendering::XPolyPolygon2D >& 	/*xPolyPolygon*/,
467                                                                                               const rendering::ViewState& 							/*viewState*/,
468                                                                                               const rendering::RenderState& 						/*renderState*/,
469                                                                                               const uno::Sequence< rendering::Texture >& 			/*textures*/,
470                                                                                               const uno::Reference< geometry::XMapping2D >& 		/*xMapping*/ )
471     {
472         // TODO
473         return uno::Reference< rendering::XCachedPrimitive >(NULL);
474     }
475 
476     uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas* 					/*pCanvas*/,
477                                                                        const rendering::FontRequest& 				fontRequest,
478                                                                        const uno::Sequence< beans::PropertyValue >& extraFontProperties,
479                                                                        const geometry::Matrix2D& 					fontMatrix )
480     {
481         if( needOutput() )
482         {
483             return uno::Reference< rendering::XCanvasFont >(
484                     new CanvasFont(fontRequest, extraFontProperties, fontMatrix ) );
485         }
486 
487         return uno::Reference< rendering::XCanvasFont >();
488     }
489 
490     uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas* 						/*pCanvas*/,
491                                                                             const rendering::FontInfo& 						/*aFilter*/,
492                                                                             const uno::Sequence< beans::PropertyValue >& 	/*aFontProperties*/ )
493     {
494         // TODO
495         return uno::Sequence< rendering::FontInfo >();
496     }
497 
498     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas* 						/*pCanvas*/,
499                                                                           const rendering::StringContext& 					text,
500                                                                           const uno::Reference< rendering::XCanvasFont >& 	xFont,
501                                                                           const rendering::ViewState& 						viewState,
502                                                                           const rendering::RenderState& 					renderState,
503                                                                           sal_Int8				 							/*textDirection*/ )
504     {
505         ENSURE_OR_THROW( xFont.is(),
506                           "CanvasHelper::drawText: font is NULL");
507 
508         if( needOutput() )
509         {
510             GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
511 
512             setupGraphicsState( pGraphics, viewState, renderState );
513 
514             Gdiplus::SolidBrush aBrush(
515                 Gdiplus::Color(
516                     tools::sequenceToArgb(renderState.DeviceColor)));
517 
518             CanvasFont::ImplRef pFont(
519                 tools::canvasFontFromXFont(xFont) );
520 
521             // Move glyphs up, such that output happens at the font
522             // baseline.
523             Gdiplus::PointF aPoint( 0.0,
524                                     static_cast<Gdiplus::REAL>(-(pFont->getFont()->GetSize()*
525                                                                  pFont->getCellAscent() /
526                                                                  pFont->getEmHeight())) );
527 
528             // TODO(F1): According to
529             // http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q307208,
530             // we might have to revert to GDI and ExTextOut here,
531             // since GDI+ takes the scalability a little bit too
532             // far...
533 
534             // TODO(F2): Proper layout (BiDi, CTL)! IMHO must use
535             // DrawDriverString here, and perform layouting myself...
536             ENSURE_OR_THROW(
537                 Gdiplus::Ok == pGraphics->DrawString( reinterpret_cast<LPCWSTR>(
538                                                           text.Text.copy( text.StartPosition,
539                                                                           text.Length ).getStr()),
540                                                       text.Length,
541                                                       pFont->getFont().get(),
542                                                       aPoint,
543                                                       &aBrush ),
544                 "CanvasHelper::drawText(): GDI+ call failed" );
545         }
546 
547         return uno::Reference< rendering::XCachedPrimitive >(NULL);
548     }
549 
550     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas* 						/*pCanvas*/,
551                                                                                 const uno::Reference< rendering::XTextLayout >& xLayoutetText,
552                                                                                 const rendering::ViewState& 					viewState,
553                                                                                 const rendering::RenderState& 					renderState )
554     {
555         ENSURE_OR_THROW( xLayoutetText.is(),
556                           "CanvasHelper::drawTextLayout: layout is NULL");
557 
558         if( needOutput() )
559         {
560 			TextLayout* pTextLayout =
561                 dynamic_cast< TextLayout* >( xLayoutetText.get() );
562 
563             ENSURE_OR_THROW( pTextLayout,
564                                 "CanvasHelper::drawTextLayout(): TextLayout not compatible with this canvas" );
565 
566 			pTextLayout->draw( mpGraphicsProvider->getGraphics(),
567                                viewState,
568                                renderState,
569                                maOutputOffset,
570                                mpDevice,
571                                false );
572         }
573 
574         return uno::Reference< rendering::XCachedPrimitive >(NULL);
575     }
576 
577     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmap( const rendering::XCanvas* 					/*pCanvas*/,
578                                                                             const uno::Reference< rendering::XBitmap >& xBitmap,
579                                                                             const rendering::ViewState& 				viewState,
580                                                                             const rendering::RenderState& 				renderState )
581     {
582         ENSURE_OR_THROW( xBitmap.is(),
583                           "CanvasHelper::drawBitmap: bitmap is NULL");
584 
585         if( needOutput() )
586         {
587             // check whether one of our own objects - need to retrieve
588             // bitmap _before_ calling
589             // GraphicsProvider::getGraphics(), to avoid locking our
590             // own surface.
591             BitmapSharedPtr pGdiBitmap;
592             BitmapProvider* pBitmap = dynamic_cast< BitmapProvider* >(xBitmap.get());
593             if( pBitmap )
594             {
595                 IBitmapSharedPtr pDXBitmap( pBitmap->getBitmap() );
596                 if( pDXBitmap )
597                     pGdiBitmap = pDXBitmap->getBitmap();
598             }
599 
600 			GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
601 			setupGraphicsState( pGraphics, viewState, renderState );
602 
603             if( pGdiBitmap )
604                 tools::drawGdiPlusBitmap(pGraphics,pGdiBitmap);
605             else
606                 tools::drawVCLBitmapFromXBitmap(pGraphics,
607                                                 xBitmap);
608         }
609 
610         // TODO(P1): Provide caching here.
611         return uno::Reference< rendering::XCachedPrimitive >(NULL);
612     }
613 
614     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas* 						pCanvas,
615                                                                                      const uno::Reference< rendering::XBitmap >& 	xBitmap,
616                                                                                      const rendering::ViewState& 					viewState,
617                                                                                      const rendering::RenderState& 					renderState )
618     {
619         ENSURE_OR_THROW( xBitmap.is(),
620                           "CanvasHelper::drawBitmap: bitmap is NULL");
621 
622         // no color set -> this is equivalent to a plain drawBitmap(), then
623         if( renderState.DeviceColor.getLength() < 3 )
624             return drawBitmap( pCanvas, xBitmap, viewState, renderState );
625 
626         if( needOutput() )
627         {
628             GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
629 
630             setupGraphicsState( pGraphics, viewState, renderState );
631 
632             BitmapSharedPtr pBitmap( tools::bitmapFromXBitmap( xBitmap ) );
633             Gdiplus::Rect aRect( 0, 0,
634                                  pBitmap->GetWidth(),
635                                  pBitmap->GetHeight() );
636 
637             // Setup an ImageAttributes with an alpha-modulating
638             // color matrix.
639             const rendering::ARGBColor& rARGBColor(
640                 mpDevice->getDeviceColorSpace()->convertToARGB(renderState.DeviceColor)[0]);
641 
642             Gdiplus::ImageAttributes aImgAttr;
643             tools::setModulateImageAttributes( aImgAttr,
644                                                rARGBColor.Red,
645                                                rARGBColor.Green,
646                                                rARGBColor.Blue,
647                                                rARGBColor.Alpha );
648 
649             ENSURE_OR_THROW(
650                 Gdiplus::Ok == pGraphics->DrawImage( pBitmap.get(),
651                                                      aRect,
652                                                      0, 0,
653                                                      pBitmap->GetWidth(),
654                                                      pBitmap->GetHeight(),
655                                                      Gdiplus::UnitPixel,
656                                                      &aImgAttr,
657                                                      NULL,
658                                                      NULL ),
659                 "CanvasHelper::drawBitmapModulated(): GDI+ call failed" );
660         }
661 
662         // TODO(P1): Provide caching here.
663         return uno::Reference< rendering::XCachedPrimitive >(NULL);
664     }
665 
666     uno::Reference< rendering::XGraphicDevice > CanvasHelper::getDevice()
667     {
668         return uno::Reference< rendering::XGraphicDevice >(mpDevice);
669     }
670 
671     // private helper
672     // --------------------------------------------------
673 
674     Gdiplus::CompositingMode CanvasHelper::calcCompositingMode( sal_Int8 nMode )
675     {
676         Gdiplus::CompositingMode aRet( Gdiplus::CompositingModeSourceOver );
677 
678         switch( nMode )
679         {
680             case rendering::CompositeOperation::OVER:
681                 // FALLTHROUGH intended
682             case rendering::CompositeOperation::CLEAR:
683                 aRet = Gdiplus::CompositingModeSourceOver;
684                 break;
685 
686             case rendering::CompositeOperation::SOURCE:
687                 aRet = Gdiplus::CompositingModeSourceCopy;
688                 break;
689 
690             case rendering::CompositeOperation::DESTINATION:
691                 // FALLTHROUGH intended
692             case rendering::CompositeOperation::UNDER:
693                 // FALLTHROUGH intended
694             case rendering::CompositeOperation::INSIDE:
695                 // FALLTHROUGH intended
696             case rendering::CompositeOperation::INSIDE_REVERSE:
697                 // FALLTHROUGH intended
698             case rendering::CompositeOperation::OUTSIDE:
699                 // FALLTHROUGH intended
700             case rendering::CompositeOperation::OUTSIDE_REVERSE:
701                 // FALLTHROUGH intended
702             case rendering::CompositeOperation::ATOP:
703                 // FALLTHROUGH intended
704             case rendering::CompositeOperation::ATOP_REVERSE:
705                 // FALLTHROUGH intended
706             case rendering::CompositeOperation::XOR:
707                 // FALLTHROUGH intended
708             case rendering::CompositeOperation::ADD:
709                 // FALLTHROUGH intended
710             case rendering::CompositeOperation::SATURATE:
711                 // TODO(F2): Problem, because GDI+ only knows about two compositing modes
712                 aRet = Gdiplus::CompositingModeSourceOver;
713                 break;
714 
715             default:
716                 ENSURE_OR_THROW( false, "CanvasHelper::calcCompositingMode: unexpected mode" );
717                 break;
718         }
719 
720         return aRet;
721     }
722 
723     void CanvasHelper::setupGraphicsState( GraphicsSharedPtr&            rGraphics,
724                                            const rendering::ViewState& 	 viewState,
725                                            const rendering::RenderState& renderState )
726     {
727         ENSURE_OR_THROW( needOutput(),
728                           "CanvasHelper::setupGraphicsState: primary graphics invalid" );
729         ENSURE_OR_THROW( mpDevice,
730                           "CanvasHelper::setupGraphicsState: reference device invalid" );
731 
732         // setup view transform first. Clipping e.g. depends on it
733         ::basegfx::B2DHomMatrix aTransform;
734         ::canvas::tools::getViewStateTransform(aTransform, viewState);
735 
736         // add output offset
737         if( !maOutputOffset.equalZero() )
738         {
739             const basegfx::B2DHomMatrix aOutputOffset(basegfx::tools::createTranslateB2DHomMatrix(
740                 maOutputOffset.getX(), maOutputOffset.getY()));
741             aTransform = aOutputOffset * aTransform;
742         }
743 
744         Gdiplus::Matrix aMatrix;
745         tools::gdiPlusMatrixFromB2DHomMatrix( aMatrix, aTransform );
746 
747 		ENSURE_OR_THROW(
748             Gdiplus::Ok == rGraphics->SetTransform( &aMatrix ),
749             "CanvasHelper::setupGraphicsState(): Failed to set GDI+ transformation" );
750 
751         // setup view and render state clipping
752         ENSURE_OR_THROW(
753             Gdiplus::Ok == rGraphics->ResetClip(),
754             "CanvasHelper::setupGraphicsState(): Failed to reset GDI+ clip" );
755 
756         if( viewState.Clip.is() )
757         {
758             GraphicsPathSharedPtr aClipPath( tools::graphicsPathFromXPolyPolygon2D( viewState.Clip ) );
759 
760             // TODO(P3): Cache clip. SetClip( GraphicsPath ) performs abyssmally on GDI+.
761             // Try SetClip( Rect ) or similar for simple clip paths (need some support in
762             // LinePolyPolygon, then)
763             ENSURE_OR_THROW(
764                 Gdiplus::Ok == rGraphics->SetClip( aClipPath.get(),
765                                                    Gdiplus::CombineModeIntersect ),
766                 "CanvasHelper::setupGraphicsState(): Cannot set GDI+ clip" );
767         }
768 
769         // setup overall transform only now. View clip above was relative to
770         // view transform
771         ::canvas::tools::mergeViewAndRenderTransform(aTransform,
772                                                      viewState,
773                                                      renderState);
774 
775         // add output offset
776         if( !maOutputOffset.equalZero() )
777         {
778             const basegfx::B2DHomMatrix aOutputOffset(basegfx::tools::createTranslateB2DHomMatrix(
779                 maOutputOffset.getX(), maOutputOffset.getY()));
780             aTransform = aOutputOffset * aTransform;
781         }
782 
783         tools::gdiPlusMatrixFromB2DHomMatrix( aMatrix, aTransform );
784 
785         ENSURE_OR_THROW(
786             Gdiplus::Ok == rGraphics->SetTransform( &aMatrix ),
787             "CanvasHelper::setupGraphicsState(): Cannot set GDI+ transformation" );
788 
789         if( renderState.Clip.is() )
790         {
791             GraphicsPathSharedPtr aClipPath( tools::graphicsPathFromXPolyPolygon2D( renderState.Clip ) );
792 
793             // TODO(P3): Cache clip. SetClip( GraphicsPath ) performs abyssmally on GDI+.
794             // Try SetClip( Rect ) or similar for simple clip paths (need some support in
795             // LinePolyPolygon, then)
796             ENSURE_OR_THROW(
797                 Gdiplus::Ok == rGraphics->SetClip( aClipPath.get(),
798                                                    Gdiplus::CombineModeIntersect ),
799                 "CanvasHelper::setupGraphicsState(): Cannot set GDI+ clip" );
800         }
801 
802         // setup compositing
803         const Gdiplus::CompositingMode eCompositing( calcCompositingMode( renderState.CompositeOperation ) );
804         ENSURE_OR_THROW(
805             Gdiplus::Ok == rGraphics->SetCompositingMode( eCompositing ),
806             "CanvasHelper::setupGraphicsState(): Cannot set GDI* compositing mode)" );
807     }
808 
809     void CanvasHelper::flush() const
810     {
811         if( needOutput() )
812             mpGraphicsProvider->getGraphics()->Flush( Gdiplus::FlushIntentionSync );
813     }
814 }
815