1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
21 #include <X11/Xutil.h>
22 #include <X11/extensions/Xrender.h>
25 #include <basegfx/polygon/b2dpolygon.hxx>
26 #include <basegfx/polygon/b2dpolypolygon.hxx>
27 #include <basegfx/polygon/b2dpolypolygontools.hxx>
28 #include <basegfx/matrix/b2dhommatrix.hxx>
29 #include <basegfx/curve/b2dcubicbezier.hxx>
31 #include <headless/svpgdi.hxx>
33 #include <vcl/sysdata.hxx>
34 #include <vcl/virdev.hxx>
35 #include <sal/log.hxx>
37 #include <unx/salunx.h>
38 #include <unx/saldisp.hxx>
39 #include <unx/salgdi.h>
40 #include <unx/x11/xlimits.hxx>
42 #include <salframe.hxx>
43 #include <salgdiimpl.hxx>
44 #include <textrender.hxx>
46 #include "gdiimpl.hxx"
47 #include <opengl/x11/gdiimpl.hxx>
48 #include <unx/x11/x11cairotextrender.hxx>
49 #include <opengl/x11/cairotextrender.hxx>
51 #include <unx/x11/xrender_peer.hxx>
52 #include "cairo_xlib_cairo.hxx"
53 #include <cairo-xlib.h>
55 #include <vcl/opengl/OpenGLHelper.hxx>
57 #include <config_features.h>
58 #include <vcl/skia/SkiaHelper.hxx>
60 #include <skia/x11/gdiimpl.hxx>
61 #include <skia/x11/textrender.hxx>
64 X11SalGraphics::X11SalGraphics():
69 m_pExternalSurface(nullptr),
71 m_pXRenderFormat(nullptr),
73 mpClipRegion(nullptr),
74 #if ENABLE_CAIRO_CANVAS
76 mnPenColor(SALCOLOR_NONE
),
77 mnFillColor(SALCOLOR_NONE
),
78 #endif // ENABLE_CAIRO_CANVAS
82 m_bOpenGL(OpenGLHelper::isVCLOpenGLEnabled()),
83 m_bSkia(SkiaHelper::isVCLSkiaEnabled())
88 mxImpl
.reset(new X11SkiaSalGraphicsImpl(*this));
89 mxTextRenderImpl
.reset(new SkiaTextRender
);
95 mxImpl
.reset(new X11OpenGLSalGraphicsImpl(*this));
96 mxTextRenderImpl
.reset(new OpenGLX11CairoTextRender(*this));
100 mxTextRenderImpl
.reset(new X11CairoTextRender(*this));
101 mxImpl
.reset(new X11SalGraphicsImpl(*this));
106 X11SalGraphics::~X11SalGraphics() COVERITY_NOEXCEPT_FALSE
113 void X11SalGraphics::freeResources()
115 Display
*pDisplay
= GetXDisplay();
119 XDestroyRegion( mpClipRegion
);
123 mxImpl
->freeResources();
127 XFreePixmap( pDisplay
, hBrush_
);
130 if( m_pDeleteColormap
)
132 m_pDeleteColormap
.reset();
133 m_pColormap
= nullptr;
135 if( m_aXRenderPicture
)
137 XRenderPeer::GetInstance().FreePicture( m_aXRenderPicture
);
138 m_aXRenderPicture
= 0;
142 SalGraphicsImpl
* X11SalGraphics::GetImpl() const
147 void X11SalGraphics::SetDrawable(Drawable aDrawable
, cairo_surface_t
* pExternalSurface
, SalX11Screen nXScreen
)
149 m_pExternalSurface
= pExternalSurface
;
151 // shortcut if nothing changed
152 if( hDrawable_
== aDrawable
)
155 // free screen specific resources if needed
156 if( nXScreen
!= m_nXScreen
)
159 m_pColormap
= &vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetColormap( nXScreen
);
160 m_nXScreen
= nXScreen
;
163 hDrawable_
= aDrawable
;
164 SetXRenderFormat( nullptr );
165 if( m_aXRenderPicture
)
167 XRenderPeer::GetInstance().FreePicture( m_aXRenderPicture
);
168 m_aXRenderPicture
= 0;
172 void X11SalGraphics::Init( SalFrame
*pFrame
, Drawable aTarget
,
173 SalX11Screen nXScreen
)
175 m_pColormap
= &vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetColormap(nXScreen
);
176 m_nXScreen
= nXScreen
;
184 SetDrawable(aTarget
, nullptr, nXScreen
);
188 void X11SalGraphics::DeInit()
191 SetDrawable(None
, nullptr, m_nXScreen
);
194 void X11SalGraphics::SetClipRegion( GC pGC
, Region pXReg
) const
196 Display
*pDisplay
= GetXDisplay();
202 Regions
[n
++] = mpClipRegion
;
204 if( pXReg
&& !XEmptyRegion( pXReg
) )
205 Regions
[n
++] = pXReg
;
208 XSetClipMask( pDisplay
, pGC
, None
);
210 XSetRegion( pDisplay
, pGC
, Regions
[0] );
213 Region pTmpRegion
= XCreateRegion();
214 XIntersectRegion( Regions
[0], Regions
[1], pTmpRegion
);
216 XSetRegion( pDisplay
, pGC
, pTmpRegion
);
217 XDestroyRegion( pTmpRegion
);
221 // Calculate a dither-pixmap and make a brush of it
223 #define DMAP( v, m ) ((v % P_DELTA) > m ? (v / P_DELTA) + 1 : (v / P_DELTA))
225 bool X11SalGraphics::GetDitherPixmap( Color nColor
)
227 static const short nOrdDither8Bit
[ 8 ][ 8 ] =
229 { 0, 38, 9, 48, 2, 40, 12, 50},
230 {25, 12, 35, 22, 28, 15, 37, 24},
231 { 6, 44, 3, 41, 8, 47, 5, 44},
232 {32, 19, 28, 16, 34, 21, 31, 18},
233 { 1, 40, 11, 49, 0, 39, 10, 48},
234 {27, 14, 36, 24, 26, 13, 36, 23},
235 { 8, 46, 4, 43, 7, 45, 4, 42},
236 {33, 20, 30, 17, 32, 20, 29, 16}
239 // test for correct depth (8bit)
240 if( GetColormap().GetVisual().GetDepth() != 8 )
244 char *pBitsPtr
= pBits
;
246 // Set the palette-entries for the dithering tile
247 sal_uInt8 nColorRed
= nColor
.GetRed();
248 sal_uInt8 nColorGreen
= nColor
.GetGreen();
249 sal_uInt8 nColorBlue
= nColor
.GetBlue();
251 for(auto & nY
: nOrdDither8Bit
)
253 for( int nX
= 0; nX
< 8; nX
++ )
255 short nMagic
= nY
[nX
];
256 sal_uInt8 nR
= P_DELTA
* DMAP( nColorRed
, nMagic
);
257 sal_uInt8 nG
= P_DELTA
* DMAP( nColorGreen
, nMagic
);
258 sal_uInt8 nB
= P_DELTA
* DMAP( nColorBlue
, nMagic
);
260 *pBitsPtr
++ = GetColormap().GetPixel( Color( nR
, nG
, nB
) );
264 // create the tile as ximage and an according pixmap -> caching
265 XImage
*pImage
= XCreateImage( GetXDisplay(),
266 GetColormap().GetXVisual(),
271 8, 8, // width & height
273 0 ); // (default) bytes_per_line
276 hBrush_
= limitXCreatePixmap( GetXDisplay(), GetDrawable(), 8, 8, 8 );
278 // put the ximage to the pixmap
279 XPutImage( GetXDisplay(),
281 GetDisplay()->GetCopyGC( m_nXScreen
),
285 8, 8 ); // width & height
287 // destroy image-frame but not palette-data
288 pImage
->data
= nullptr;
289 XDestroyImage( pImage
);
294 void X11SalGraphics::GetResolution( sal_Int32
&rDPIX
, sal_Int32
&rDPIY
) // const
297 if ((pForceDpi
= getenv("SAL_FORCEDPI")))
299 OString
sForceDPI(pForceDpi
);
300 rDPIX
= rDPIY
= sForceDPI
.toInt32();
304 const SalDisplay
*pDisplay
= GetDisplay();
307 SAL_WARN( "vcl", "Null display");
312 Pair dpi
= pDisplay
->GetResolution();
318 rDPIX
= Divide( rDPIX
* 200, rDPIY
);
322 // #i12705# equalize x- and y-resolution if they are close enough
326 // different x- and y- resolutions are usually artifacts of
327 // a wrongly calculated screen size.
329 SAL_INFO("vcl.gdi", "Forcing Resolution from "
334 << std::dec
<< rDPIY
);
336 rDPIX
= rDPIY
; // y-resolution is more trustworthy
339 sal_uInt16
X11SalGraphics::GetBitCount() const
341 return mxImpl
->GetBitCount();
344 tools::Long
X11SalGraphics::GetGraphicsWidth() const
346 return mxImpl
->GetGraphicsWidth();
349 void X11SalGraphics::ResetClipRegion()
351 #if ENABLE_CAIRO_CANVAS
352 maClipRegion
.SetNull();
354 mxImpl
->ResetClipRegion();
357 bool X11SalGraphics::setClipRegion( const vcl::Region
& i_rClip
)
359 #if ENABLE_CAIRO_CANVAS
360 maClipRegion
= i_rClip
;
362 return mxImpl
->setClipRegion( i_rClip
);
365 void X11SalGraphics::SetLineColor()
367 #if ENABLE_CAIRO_CANVAS
368 mnPenColor
= SALCOLOR_NONE
;
369 #endif // ENABLE_CAIRO_CANVAS
371 mxImpl
->SetLineColor();
374 void X11SalGraphics::SetLineColor( Color nColor
)
376 #if ENABLE_CAIRO_CANVAS
378 #endif // ENABLE_CAIRO_CANVAS
380 mxImpl
->SetLineColor( nColor
);
383 void X11SalGraphics::SetFillColor()
385 #if ENABLE_CAIRO_CANVAS
386 mnFillColor
= SALCOLOR_NONE
;
387 #endif // ENABLE_CAIRO_CANVAS
389 mxImpl
->SetFillColor();
392 void X11SalGraphics::SetFillColor( Color nColor
)
394 #if ENABLE_CAIRO_CANVAS
395 mnFillColor
= nColor
;
396 #endif // ENABLE_CAIRO_CANVAS
398 mxImpl
->SetFillColor( nColor
);
401 void X11SalGraphics::SetROPLineColor( SalROPColor nROPColor
)
403 mxImpl
->SetROPLineColor( nROPColor
);
406 void X11SalGraphics::SetROPFillColor( SalROPColor nROPColor
)
408 mxImpl
->SetROPFillColor( nROPColor
);
411 void X11SalGraphics::SetXORMode( bool bSet
, bool bInvertOnly
)
413 mxImpl
->SetXORMode( bSet
, bInvertOnly
);
416 void X11SalGraphics::drawPixel( tools::Long nX
, tools::Long nY
)
418 mxImpl
->drawPixel( nX
, nY
);
421 void X11SalGraphics::drawPixel( tools::Long nX
, tools::Long nY
, Color nColor
)
423 mxImpl
->drawPixel( nX
, nY
, nColor
);
426 void X11SalGraphics::drawLine( tools::Long nX1
, tools::Long nY1
, tools::Long nX2
, tools::Long nY2
)
428 mxImpl
->drawLine( nX1
, nY1
, nX2
, nY2
);
431 void X11SalGraphics::drawRect( tools::Long nX
, tools::Long nY
, tools::Long nDX
, tools::Long nDY
)
433 mxImpl
->drawRect( nX
, nY
, nDX
, nDY
);
436 void X11SalGraphics::drawPolyLine( sal_uInt32 nPoints
, const Point
*pPtAry
)
438 mxImpl
->drawPolyLine( nPoints
, pPtAry
);
441 void X11SalGraphics::drawPolygon( sal_uInt32 nPoints
, const Point
* pPtAry
)
443 mxImpl
->drawPolygon( nPoints
, pPtAry
);
446 void X11SalGraphics::drawPolyPolygon( sal_uInt32 nPoly
,
447 const sal_uInt32
*pPoints
,
448 const Point
* *pPtAry
)
450 mxImpl
->drawPolyPolygon( nPoly
, pPoints
, pPtAry
);
453 bool X11SalGraphics::drawPolyLineBezier( sal_uInt32 nPoints
, const Point
* pPtAry
, const PolyFlags
* pFlgAry
)
455 return mxImpl
->drawPolyLineBezier( nPoints
, pPtAry
, pFlgAry
);
458 bool X11SalGraphics::drawPolygonBezier( sal_uInt32 nPoints
, const Point
* pPtAry
, const PolyFlags
* pFlgAry
)
460 return mxImpl
->drawPolygonBezier( nPoints
, pPtAry
, pFlgAry
);
463 bool X11SalGraphics::drawPolyPolygonBezier( sal_uInt32 nPoints
, const sal_uInt32
* pPoints
,
464 const Point
* const* pPtAry
, const PolyFlags
* const* pFlgAry
)
466 return mxImpl
->drawPolyPolygonBezier( nPoints
, pPoints
, pPtAry
, pFlgAry
);
469 void X11SalGraphics::invert( sal_uInt32 nPoints
,
473 mxImpl
->invert( nPoints
, pPtAry
, nFlags
);
476 bool X11SalGraphics::drawEPS( tools::Long nX
, tools::Long nY
, tools::Long nWidth
,
477 tools::Long nHeight
, void* pPtr
, sal_uInt32 nSize
)
479 return mxImpl
->drawEPS( nX
, nY
, nWidth
, nHeight
, pPtr
, nSize
);
482 XRenderPictFormat
* X11SalGraphics::GetXRenderFormat() const
484 if( m_pXRenderFormat
== nullptr )
485 m_pXRenderFormat
= XRenderPeer::GetInstance().FindVisualFormat( GetVisual().visual
);
486 return m_pXRenderFormat
;
489 SystemGraphicsData
X11SalGraphics::GetGraphicsData() const
491 SystemGraphicsData aRes
;
493 aRes
.nSize
= sizeof(aRes
);
494 aRes
.pDisplay
= GetXDisplay();
495 aRes
.hDrawable
= hDrawable_
;
496 aRes
.pVisual
= GetVisual().visual
;
497 aRes
.nScreen
= m_nXScreen
.getXScreen();
498 aRes
.pXRenderFormat
= m_pXRenderFormat
;
502 void X11SalGraphics::Flush()
504 if( X11GraphicsImpl
* x11Impl
= dynamic_cast< X11GraphicsImpl
* >( mxImpl
.get()))
508 #if ENABLE_CAIRO_CANVAS
510 bool X11SalGraphics::SupportsCairo() const
512 static bool bSupportsCairo
= [this] {
513 Display
*pDisplay
= GetXDisplay();
515 return XQueryExtension(pDisplay
, "RENDER", &nDummy
, &nDummy
, &nDummy
);
517 return bSupportsCairo
;
520 cairo::SurfaceSharedPtr
X11SalGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr
& rSurface
) const
522 return std::make_shared
<cairo::X11Surface
>(rSurface
);
527 cairo::X11SysData
getSysData( const vcl::Window
& rWindow
)
529 const SystemEnvData
* pSysData
= rWindow
.GetSystemData();
532 return cairo::X11SysData();
534 return cairo::X11SysData(*pSysData
, rWindow
.ImplGetFrame());
537 cairo::X11SysData
getSysData( const VirtualDevice
& rVirDev
)
539 return cairo::X11SysData( rVirDev
.GetSystemGfxData() );
543 cairo::SurfaceSharedPtr
X11SalGraphics::CreateSurface( const OutputDevice
& rRefDevice
,
544 int x
, int y
, int width
, int height
) const
546 if( rRefDevice
.GetOutDevType() == OUTDEV_WINDOW
)
547 return std::make_shared
<cairo::X11Surface
>(getSysData(static_cast<const vcl::Window
&>(rRefDevice
)),
549 if( rRefDevice
.IsVirtual() )
550 return std::make_shared
<cairo::X11Surface
>(getSysData(static_cast<const VirtualDevice
&>(rRefDevice
)),
552 return cairo::SurfaceSharedPtr();
555 cairo::SurfaceSharedPtr
X11SalGraphics::CreateBitmapSurface( const OutputDevice
& rRefDevice
,
556 const BitmapSystemData
& rData
,
557 const Size
& rSize
) const
559 SAL_INFO("vcl", "requested size: " << rSize
.Width() << " x " << rSize
.Height()
560 << " available size: " << rData
.mnWidth
<< " x "
562 if ( rData
.mnWidth
== rSize
.Width() && rData
.mnHeight
== rSize
.Height() )
564 if( rRefDevice
.GetOutDevType() == OUTDEV_WINDOW
)
565 return std::make_shared
<cairo::X11Surface
>(getSysData(static_cast<const vcl::Window
&>(rRefDevice
)), rData
);
566 else if( rRefDevice
.IsVirtual() )
567 return std::make_shared
<cairo::X11Surface
>(getSysData(static_cast<const VirtualDevice
&>(rRefDevice
)), rData
);
570 return cairo::SurfaceSharedPtr();
573 css::uno::Any
X11SalGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr
& rSurface
, const basegfx::B2ISize
& /*rSize*/) const
575 cairo::X11Surface
& rXlibSurface
=dynamic_cast<cairo::X11Surface
&>(*rSurface
);
576 css::uno::Sequence
< css::uno::Any
> args( 3 );
577 args
[0] <<= false; // do not call XFreePixmap on it
578 args
[1] <<= sal_Int64(rXlibSurface
.getPixmap()->mhDrawable
);
579 args
[2] <<= sal_Int32( rXlibSurface
.getDepth() );
580 return css::uno::Any(args
);
583 #endif // ENABLE_CAIRO_CANVAS
585 // draw a poly-polygon
586 bool X11SalGraphics::drawPolyPolygon(
587 const basegfx::B2DHomMatrix
& rObjectToDevice
,
588 const basegfx::B2DPolyPolygon
& rPolyPolygon
,
589 double fTransparency
)
591 if(fTransparency
>= 1.0)
596 if(rPolyPolygon
.count() == 0)
601 #if ENABLE_CAIRO_CANVAS
602 // Fallback: Transform to DeviceCoordinates
603 basegfx::B2DPolyPolygon
aPolyPolygon(rPolyPolygon
);
604 aPolyPolygon
.transform(rObjectToDevice
);
606 if(SALCOLOR_NONE
== mnFillColor
&& SALCOLOR_NONE
== mnPenColor
)
611 // enable by setting to something
612 static const char* pUseCairoForPolygons(getenv("SAL_ENABLE_USE_CAIRO_FOR_POLYGONS"));
614 if (!m_bOpenGL
&& !m_bSkia
&& nullptr != pUseCairoForPolygons
&& SupportsCairo())
616 // snap to raster if requested
617 const bool bSnapPoints(!getAntiAlias());
621 aPolyPolygon
= basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygon
);
624 cairo_t
* cr
= getCairoContext();
627 for(auto const& rPolygon
: aPolyPolygon
)
629 const sal_uInt32
nPointCount(rPolygon
.count());
633 const sal_uInt32
nEdgeCount(rPolygon
.isClosed() ? nPointCount
: nPointCount
- 1);
637 basegfx::B2DCubicBezier aEdge
;
639 for(sal_uInt32 b
= 0; b
< nEdgeCount
; ++b
)
641 rPolygon
.getBezierSegment(b
, aEdge
);
645 const basegfx::B2DPoint
aStart(aEdge
.getStartPoint());
646 cairo_move_to(cr
, aStart
.getX(), aStart
.getY());
649 const basegfx::B2DPoint
aEnd(aEdge
.getEndPoint());
653 const basegfx::B2DPoint
aCP1(aEdge
.getControlPointA());
654 const basegfx::B2DPoint
aCP2(aEdge
.getControlPointB());
656 aCP1
.getX(), aCP1
.getY(),
657 aCP2
.getX(), aCP2
.getY(),
658 aEnd
.getX(), aEnd
.getY());
662 cairo_line_to(cr
, aEnd
.getX(), aEnd
.getY());
666 cairo_close_path(cr
);
671 if(SALCOLOR_NONE
!= mnFillColor
)
673 cairo_set_source_rgba(cr
,
674 mnFillColor
.GetRed()/255.0,
675 mnFillColor
.GetGreen()/255.0,
676 mnFillColor
.GetBlue()/255.0,
677 1.0 - fTransparency
);
678 cairo_set_fill_rule(cr
, CAIRO_FILL_RULE_EVEN_ODD
);
679 cairo_fill_preserve(cr
);
682 if(SALCOLOR_NONE
!= mnPenColor
)
684 cairo_set_source_rgba(cr
,
685 mnPenColor
.GetRed()/255.0,
686 mnPenColor
.GetGreen()/255.0,
687 mnPenColor
.GetBlue()/255.0,
688 1.0 - fTransparency
);
689 cairo_stroke_preserve(cr
);
692 releaseCairoContext(cr
);
695 #endif // ENABLE_CAIRO_CANVAS
697 return mxImpl
->drawPolyPolygon(
703 #if ENABLE_CAIRO_CANVAS
704 void X11SalGraphics::clipRegion(cairo_t
* cr
)
706 SvpSalGraphics::clipRegion(cr
, maClipRegion
);
708 #endif // ENABLE_CAIRO_CANVAS
710 bool X11SalGraphics::drawPolyLine(
711 const basegfx::B2DHomMatrix
& rObjectToDevice
,
712 const basegfx::B2DPolygon
& rPolygon
,
713 double fTransparency
,
715 const std::vector
< double >* pStroke
, // MM01
716 basegfx::B2DLineJoin eLineJoin
,
717 css::drawing::LineCap eLineCap
,
718 double fMiterMinimumAngle
,
719 bool bPixelSnapHairline
)
721 if(0 == rPolygon
.count())
726 if(fTransparency
>= 1.0)
731 #if ENABLE_CAIRO_CANVAS
732 // disable by setting to something
733 static const char* pUseCairoForFatLines(getenv("SAL_DISABLE_USE_CAIRO_FOR_FATLINES"));
735 if (!m_bOpenGL
&& !m_bSkia
&& nullptr == pUseCairoForFatLines
&& SupportsCairo())
737 cairo_t
* cr
= getCairoContext();
740 // Use the now available static drawPolyLine from the Cairo-Headless-Fallback
741 // that will take care of all needed stuff
743 SvpSalGraphics::drawPolyLine(
756 bPixelSnapHairline
));
758 releaseCairoContext(cr
);
765 #endif // ENABLE_CAIRO_CANVAS
767 return mxImpl
->drawPolyLine(
779 bool X11SalGraphics::drawGradient(const tools::PolyPolygon
& rPoly
, const Gradient
& rGradient
)
781 return mxImpl
->drawGradient(rPoly
, rGradient
);
784 bool X11SalGraphics::implDrawGradient(basegfx::B2DPolyPolygon
const & rPolyPolygon
, SalGradient
const & rGradient
)
786 return mxImpl
->implDrawGradient(rPolyPolygon
, rGradient
);
789 SalGeometryProvider
*X11SalGraphics::GetGeometryProvider() const
792 return static_cast< SalGeometryProvider
* >(m_pFrame
);
794 return static_cast< SalGeometryProvider
* >(m_pVDev
);
797 cairo_t
* X11SalGraphics::getCairoContext()
799 if (m_pExternalSurface
)
800 return cairo_create(m_pExternalSurface
);
802 cairo_surface_t
* surface
= cairo_xlib_surface_create(GetXDisplay(), hDrawable_
,
803 GetVisual().visual
, SAL_MAX_INT16
, SAL_MAX_INT16
);
805 cairo_t
*cr
= cairo_create(surface
);
806 cairo_surface_destroy(surface
);
811 void X11SalGraphics::releaseCairoContext(cairo_t
* cr
)
816 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */