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