* Refactor wx.lib.wxcairo to a subpackage.

* Add support for using cairocffi instead of PyCairo.
This commit is contained in:
Robin Dunn
2016-07-19 17:23:37 -07:00
parent d775f28143
commit 7ed47817ba
5 changed files with 282 additions and 103 deletions

View File

@@ -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<h3>wx.lib.wxcairo</h3>\n%s" % (
wx.lib.wxcairo.__doc__.replace('\n\n', '\n<p>'))
wxcairo.__doc__.replace('\n\n', '\n<p>'))
else:
extra = '\n<p>See the docstring in the wx.lib.wxcairo module for details about installing dependencies.'
overview = """<html><body>
<h2><center>Cairo Integration</center></h2>
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/.

View File

@@ -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 = """<html><body>
<h2><center>Cairo Integration</center></h2>
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.
<p> 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/
<p> In most snippets you'll see a call to a snippet_normalize()

134
wx/lib/wxcairo/__init__.py Normal file
View File

@@ -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
#----------------------------------------------------------------------------

View File

@@ -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
#----------------------------------------------------------------------------

View File

@@ -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.