From 7ed47817ba6a9a703dd5932fdb4dd2f2db9ed703 Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Tue, 19 Jul 2016 17:23:37 -0700 Subject: [PATCH] * Refactor wx.lib.wxcairo to a subpackage. * Add support for using cairocffi instead of PyCairo. --- demo/Cairo.py | 17 ++- demo/Cairo_Snippets.py | 7 +- wx/lib/wxcairo/__init__.py | 134 +++++++++++++++++++ wx/lib/wxcairo/wx_cairocffi.py | 129 ++++++++++++++++++ wx/lib/{wxcairo.py => wxcairo/wx_pycairo.py} | 98 +------------- 5 files changed, 282 insertions(+), 103 deletions(-) create mode 100644 wx/lib/wxcairo/__init__.py create mode 100644 wx/lib/wxcairo/wx_cairocffi.py rename wx/lib/{wxcairo.py => wxcairo/wx_pycairo.py} (83%) diff --git a/demo/Cairo.py b/demo/Cairo.py index a4a1de6f..99dba034 100644 --- a/demo/Cairo.py +++ b/demo/Cairo.py @@ -4,7 +4,7 @@ import wx import math try: - import wx.lib.wxcairo + import wx.lib.wxcairo as wxcairo import cairo haveCairo = True except ImportError: @@ -45,7 +45,7 @@ class TestPanel(wx.Panel): dc.DrawLine(x, 0, 0, y) # now draw something with cairo - ctx = wx.lib.wxcairo.ContextFromDC(dc) + ctx = wxcairo.ContextFromDC(dc) ctx.set_line_width(15) ctx.move_to(125, 25) ctx.line_to(225, 225) @@ -71,7 +71,7 @@ class TestPanel(wx.Panel): ctx.fill() # Draw some text - face = wx.lib.wxcairo.FontFaceFromFont( + face = wxcairo.FontFaceFromFont( wx.FFont(10, wx.FONTFAMILY_SWISS, wx.FONTFLAG_BOLD)) ctx.set_font_face(face) ctx.set_font_size(60) @@ -113,13 +113,13 @@ class TestPanel(wx.Panel): # the expected result. The other platforms are okay. bmp = wx.Bitmap(opj('bitmaps/toucan.png')) #bmp = wx.Bitmap(opj('bitmaps/splash.png')) - img = wx.lib.wxcairo.ImageSurfaceFromBitmap(bmp) + img = wxcairo.ImageSurfaceFromBitmap(bmp) ctx.set_source_surface(img, 70, 230) ctx.paint() # this is how to convert an image surface to a wx.Bitmap - bmp2 = wx.lib.wxcairo.BitmapFromImageSurface(img) + bmp2 = wxcairo.BitmapFromImageSurface(img) dc.DrawBitmap(bmp2, 280, 300) @@ -155,7 +155,7 @@ class TestPanel(wx.Panel): if not haveCairo: from wx.lib.msgpanel import MessagePanel def runTest(frame, nb, log): - win = MessagePanel(nb, 'This demo requires the Pycairo package,\n' + win = MessagePanel(nb, 'This demo requires the PyCairo package,\n' 'or there is some other unmet dependency.', 'Sorry', wx.ICON_WARNING) return win @@ -169,17 +169,16 @@ else: if haveCairo: extra = "\n

wx.lib.wxcairo

\n%s" % ( - wx.lib.wxcairo.__doc__.replace('\n\n', '\n

')) + wxcairo.__doc__.replace('\n\n', '\n

')) else: extra = '\n

See the docstring in the wx.lib.wxcairo module for details about installing dependencies.' - overview = """

Cairo Integration

This sample shows how to draw on a DC using the cairo 2D graphics -library and the pycairo Python extension module that wraps the cairo +library and either the PyCairo or cairocffi package which wrap the cairo API. For more information about cairo please see http://cairographics.org/. diff --git a/demo/Cairo_Snippets.py b/demo/Cairo_Snippets.py index d6e41d49..53672433 100644 --- a/demo/Cairo_Snippets.py +++ b/demo/Cairo_Snippets.py @@ -66,7 +66,8 @@ class TestPanel(wx.Panel): def OnListBoxSelect(self, evt): snippet_file = opj('snippets/%s.py' % evt.GetString()) - text = file(snippet_file).read() + with open(snippet_file) as f: + text = f.read() self.canvas.SetSnippet(text) self.editor.SetValue(text) @@ -99,10 +100,10 @@ overview = """

Cairo Integration

The wx.lib.wxcairo module provides a bit of glue that will allow you to -use the Pycairo package drawing directly on wx.DC's. +use the PyCairo or cairocffi packages to do Cairo drawing directly on wx.DC's.

This sample draws the standard 'snippet' examples that come with -the Pycairo pacakge, and a few others. The C version of the samples +the PyCairo package, and a few others. The C version of the samples can be seen at http://cairographics.org/samples/

In most snippets you'll see a call to a snippet_normalize() diff --git a/wx/lib/wxcairo/__init__.py b/wx/lib/wxcairo/__init__.py new file mode 100644 index 00000000..f51193af --- /dev/null +++ b/wx/lib/wxcairo/__init__.py @@ -0,0 +1,134 @@ +#---------------------------------------------------------------------- +# Name: wx.lib.wxcairo +# Purpose: Glue code to allow either the PyCairo package or the +# cairocffi package to be used with a wx.DC as the cairo +# surface. +# +# Author: Robin Dunn +# +# Created: 3-Sept-2008 +# Copyright: (c) 2008-2016 by Total Control Software +# Licence: wxWindows license +# +# Tags: phoenix-port, py3-port +#---------------------------------------------------------------------- + +""" +This package provides some glue code that allows the Cairo library to draw +directly on wx.DCs, convert to/from wx.Bitmaps, etc. using either the PyCairo +or the newer cairocffi Cairo wrappers. In Cairo terms, the DC is the +drawing surface. The `CairoContextFromDC` function in this module +will return an instance of the Cairo Context class that is ready for +drawing, using the native cairo surface type for the current platform. + +Be sure to import wx.lib.wxcairo before importing the cairo module. + +To use Cairo with wxPython you will need to have a few dependencies +installed. On Linux and other unix-like systems you may already have +them, or can easily get them with your system's package manager. Just +check if libcairo and either cairocffi or pycairo are installed. + +On Mac you can get Cairo from MacPorts or similar services. Make sure that +the quartz option is turned on so those Mac-specific APIs will be included in +the Cairo library when it is built. You can then use ``pip install cairocffi`` +to get the Python wrappers. + +On Windows you can get a Cairo DLL from here: + + http://www.gtk.org/download/windows.php + +You'll also want to get the zlib and libpng binaries from the same place. Once +you get those files extract the DLLs from each of the zip files and copy them +to some place on your PATH. Finally, install the cairocffi package with pip +to get the Python wrappers for the Cairo library. +""" + +#---------------------------------------------------------------------------- + +import wx + +# Import our glue functions for either cairocffi or pycairo, depending on +# which is installed. +try: + # Use cairocffi first if it is available + from .wx_cairocffi import _ContextFromDC, _FontFaceFromFont + import cairo +except ImportError: + try: + # otherwise use pycairo + from .wx_pycairo import _ContextFromDC, _FontFaceFromFont + import cairo + except ImportError: + # or provide some exception raising stubs instead + def _ContextFromDC(dc): + raise NotImplementedError("Cairo wrappers not found") + + def _FontFaceFromFont(font): + raise NotImplementedError("Cairo wrappers not found") + +#---------------------------------------------------------------------------- + +def ContextFromDC(dc): + """ + Creates and returns a Cairo context object using the :class:`wx.DC` as the + surface. (Only window, client, paint and memory DC's are allowed at this + time.) + """ + return _ContextFromDC(dc) + + +def FontFaceFromFont(font): + """ + Creates and returns a cairo.FontFace object from the native + information in a :class:`wx.Font`. + """ + return _FontFaceFromFont(font) + +#---------------------------------------------------------------------------- +# wxBitmap <--> ImageSurface + +def BitmapFromImageSurface(surface): + """ + Create a wx.Bitmap from a Cairo ImageSurface. + """ + format = surface.get_format() + if format not in [cairo.FORMAT_ARGB32, cairo.FORMAT_RGB24]: + raise TypeError("Unsupported format") + + width = surface.get_width() + height = surface.get_height() + stride = surface.get_stride() + data = surface.get_data() + if format == cairo.FORMAT_ARGB32: + fmt = wx.BitmapBufferFormat_ARGB32 + else: + fmt = wx.BitmapBufferFormat_RGB32 + + bmp = wx.Bitmap(width, height, 32) + bmp.CopyFromBuffer(data, fmt, stride) + return bmp + + +def ImageSurfaceFromBitmap(bitmap): + """ + Create an ImageSurface from a wx.Bitmap + """ + width, height = bitmap.GetSize() + if bitmap.ConvertToImage().HasAlpha(): + format = cairo.FORMAT_ARGB32 + fmt = wx.BitmapBufferFormat_ARGB32 + else: + format = cairo.FORMAT_RGB24 + fmt = wx.BitmapBufferFormat_RGB32 + + try: + stride = cairo.ImageSurface.format_stride_for_width(format, width) + except AttributeError: + stride = width * 4 + + surface = cairo.ImageSurface(format, width, height) + bitmap.CopyToBuffer(surface.get_data(), fmt, stride) + surface.mark_dirty() + return surface + +#---------------------------------------------------------------------------- diff --git a/wx/lib/wxcairo/wx_cairocffi.py b/wx/lib/wxcairo/wx_cairocffi.py new file mode 100644 index 00000000..36a40d96 --- /dev/null +++ b/wx/lib/wxcairo/wx_cairocffi.py @@ -0,0 +1,129 @@ +#---------------------------------------------------------------------- +# Name: wx_cairocffi +# Purpose: wx.lib.wxcairo implementation functions for cairocffi +# +# Author: Robin Dunn +# +# Created: 19-July-2016 +# Copyright: (c) 2016 by Total Control Software +# Licence: wxWindows license +# +# Tags: phoenix-port, py3-port +#---------------------------------------------------------------------- + +""" +wx.lib.wxcairo implementation functions using cairocffi. +""" + +import wx + +import cairocffi +from cairocffi import cairo as cairo_c +from cairocffi import ffi + +# Make it so subsequent `import cairo` statements still work as if it +# was really PyCairo +cairocffi.install_as_pycairo() + + +#---------------------------------------------------------------------------- + +# a convenience function, just to save a bit of typing below +def voidp(ptr): + """Convert a SIP void* type to a ffi cdata""" + return ffi.cast('void *', int(ptr)) + +#---------------------------------------------------------------------------- + +def _ContextFromDC(dc): + if not isinstance(dc, wx.WindowDC) and not isinstance(dc, wx.MemoryDC): + raise TypeError("Only window and memory DC's are supported at this time.") + + if 'wxMac' in wx.PlatformInfo: + width, height = dc.GetSize() + + # use the CGContextRef of the DC to make the cairo surface + cgc = dc.GetHandle() + assert cgc is not None and int(cgc) != 0, "Unable to get CGContext from DC." + ptr = voidp(cgc) + surfaceptr = cairo_c.cairo_quartz_surface_create_for_cg_context( + ptr, width, height) + surface = cairocffi.Surface._from_pointer(surfaceptr, False) + + # Now create a cairo context for that surface + ctx = cairocffi.Context(surface) + + + elif 'wxMSW' in wx.PlatformInfo: + # Similarly, get the HDC and create a surface from it + hdc = dc.GetHandle() + surfaceptr = cairo_c.cairo_win32_surface_create(hdc) + surface = cairocffi.Surface._from_pointer(surfaceptr, False) + + # Now create a cairo context for that surface + ctx = cairocffi.Context(surface) + + + elif 'wxGTK' in wx.PlatformInfo: + if 'gtk3' in wx.PlatformInfo: + # With wxGTK3, GetHandle() returns a cairo context directly + ctxptr = voidp( dc.GetHandle() ) + + # pyCairo will try to destroy it so we need to increase ref count + cairoLib.cairo_reference(ctxptr) + else: + # Get the GdkDrawable from the dc + drawable = voidp( dc.GetHandle() ) + + # Call a GDK API to create a cairo context + ctxptr = gdkLib.gdk_cairo_create(drawable) + + # Turn it into a pycairo context object + ctx = pycairoAPI.Context_FromContext(ctxptr, pycairoAPI.Context_Type, None) + + else: + raise NotImplementedError("Help me, I'm lost...") + + return ctx + + +#---------------------------------------------------------------------------- + +def _FontFaceFromFont(font): + if 'wxMac' in wx.PlatformInfo: + cgFont = voidp(font.OSXGetCGFont()) + fontfaceptr = cairo_c.cairo_quartz_font_face_create_for_cgfont(cgFont) + fontface = cairocffi.FontFace._from_pointer(fontfaceptr, False) + + elif 'wxMSW' in wx.PlatformInfo: + hfont = voidp(font.GetHFONT()) + fontfaceptr = cairo_c.cairo_win32_font_face_create_for_hfont(hfont) + fontface = cairocffi.FontFace._from_pointer(fontfaceptr, False) + + elif 'wxGTK' in wx.PlatformInfo: + # wow, this is a hell of a lot of steps... + desc = voidp( font.GetPangoFontDescription() ) + + pcfm = voidp(pcLib.pango_cairo_font_map_get_default()) + + pctx = voidp(gdkLib.gdk_pango_context_get()) + + pfnt = voidp( pcLib.pango_font_map_load_font(pcfm, pctx, desc) ) + + scaledfontptr = voidp( pcLib.pango_cairo_font_get_scaled_font(pfnt) ) + + fontfaceptr = voidp(cairoLib.cairo_scaled_font_get_font_face(scaledfontptr)) + cairoLib.cairo_font_face_reference(fontfaceptr) + + fontface = pycairoAPI.FontFace_FromFontFace(fontfaceptr) + + gdkLib.g_object_unref(pctx) + + else: + raise NotImplementedError("Help me, I'm lost...") + + return fontface + + +#---------------------------------------------------------------------------- + diff --git a/wx/lib/wxcairo.py b/wx/lib/wxcairo/wx_pycairo.py similarity index 83% rename from wx/lib/wxcairo.py rename to wx/lib/wxcairo/wx_pycairo.py index 3a4984ae..c7d87d98 100644 --- a/wx/lib/wxcairo.py +++ b/wx/lib/wxcairo/wx_pycairo.py @@ -1,56 +1,20 @@ #---------------------------------------------------------------------- -# Name: wx.lib.wxcairo -# Purpose: Glue code to allow the pycairo package to be used -# with a wx.DC as the cairo surface. +# Name: wx_pycairo +# Purpose: wx.lib.wxcairo implementation functions for PyCairo # # Author: Robin Dunn # # Created: 3-Sept-2008 -# Copyright: (c) 2008 by Total Control Software +# Copyright: (c) 2008-2016 by Total Control Software # Licence: wxWindows license # # Tags: phoenix-port, py3-port #---------------------------------------------------------------------- """ -This module provides some glue code that allows the pycairo package to -be used for drawing direclty on wx.DCs. In cairo terms, the DC is the -drawing surface. The `CairoContextFromDC` function in this module -will return an instance of the pycairo Context class that is ready for -drawing, using the native cairo surface type for the current platform. - -This module requires the pycairo package, and makes use of ctypes for -fetching the pycairo C API and also for digging into the cairo library -itself. - -To use Cairo with wxPython you will need to have a few dependencies -installed. On Linux and other unix-like systems you may already have -them, or can easily get them with your system's package manager. Just -check if libcairo and pycairo are installed. - -On Mac you can get Cairo from MacPorts or Fink. If you are also using -MacPorts or Fink for your Python installation then you should be able -to get pycairo the same way. Otherwise it's real easy to build and -install pycairo for the Python framework installation. Just get the -source tarball from http://pypi.python.org/pypi/pycairo and do the -normal 'python setup.py install' dance. - -On Windows you can get a Cairo DLL from here: - - http://www.gtk.org/download/win32.php - -You'll also want to get the zlib and libpng binaries from the same -page. Once you get those files extract the DLLs from each of the zip -files and copy them to some place on your PATH. Finally, there is an -installer for the pycairo pacakge here: - - http://wxpython.org/cairo/ - +wx.lib.wxcairo implementation functions using PyCairo. """ -# TODO: Support printer surfaces? - - import wx from six import PY3 @@ -75,7 +39,7 @@ def voidp(ptr): #---------------------------------------------------------------------------- -def ContextFromDC(dc): +def _ContextFromDC(dc): """ Creates and returns a Cairo context object using the wxDC as the surface. (Only window, client, paint and memory DC's are allowed @@ -138,7 +102,7 @@ def ContextFromDC(dc): #---------------------------------------------------------------------------- -def FontFaceFromFont(font): +def _FontFaceFromFont(font): """ Creates and returns a cairo.FontFace object from the native information in a wx.Font. @@ -180,54 +144,6 @@ def FontFaceFromFont(font): return fontface -#---------------------------------------------------------------------------- -# wxBitmap <--> ImageSurface - -def BitmapFromImageSurface(surface): - """ - Create a wx.Bitmap from a Cairo ImageSurface. - """ - format = surface.get_format() - if format not in [cairo.FORMAT_ARGB32, cairo.FORMAT_RGB24]: - raise TypeError("Unsupported format") - - width = surface.get_width() - height = surface.get_height() - stride = surface.get_stride() - data = surface.get_data() - if format == cairo.FORMAT_ARGB32: - fmt = wx.BitmapBufferFormat_ARGB32 - else: - fmt = wx.BitmapBufferFormat_RGB32 - - bmp = wx.Bitmap(width, height, 32) - bmp.CopyFromBuffer(data, fmt, stride) - return bmp - - -def ImageSurfaceFromBitmap(bitmap): - """ - Create an ImageSurface from a wx.Bitmap - """ - width, height = bitmap.GetSize() - if bitmap.ConvertToImage().HasAlpha(): - format = cairo.FORMAT_ARGB32 - fmt = wx.BitmapBufferFormat_ARGB32 - else: - format = cairo.FORMAT_RGB24 - fmt = wx.BitmapBufferFormat_RGB32 - - try: - stride = cairo.ImageSurface.format_stride_for_width(format, width) - except AttributeError: - stride = width * 4 - - surface = cairo.ImageSurface(format, width, height) - bitmap.CopyToBuffer(surface.get_data(), fmt, stride) - surface.mark_dirty() - return surface - - #---------------------------------------------------------------------------- # Only implementation helpers after this point #---------------------------------------------------------------------------- @@ -321,7 +237,7 @@ def _findAppSvcLib(): #---------------------------------------------------------------------------- -# Pycairo exports a C API in a structure via a PyCObject. Using +# PyCairo exports a C API in a structure via a PyCObject. Using # ctypes will let us use that API from Python too. We'll use it to # convert a C pointer value to pycairo objects. The information about # this API structure is gleaned from pycairo.h.