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