Files
Phoenix/wx/lib/wxcairo/wx_pycairo.py
2025-02-02 20:17:45 -05:00

456 lines
19 KiB
Python

#----------------------------------------------------------------------
# Name: wx_pycairo
# Purpose: wx.lib.wxcairo implementation functions for PyCairo
#
# Author: Robin Dunn
#
# Created: 3-Sept-2008
# Copyright: (c) 2008-2020 by Total Control Software
# Licence: wxWindows license
#
# Tags: phoenix-port, py3-port
#----------------------------------------------------------------------
"""
wx.lib.wxcairo implementation functions using PyCairo.
"""
import wx
import cairo
import ctypes
import ctypes.util
#----------------------------------------------------------------------------
# A reference to the cairo shared lib via ctypes.CDLL
cairoLib = None
# A reference to the pycairo C API structure
pycairoAPI = None
# a convenience function, just to save a bit of typing below
def voidp(ptr):
"""Convert a SIP void* type to a ctypes c_void_p"""
return ctypes.c_void_p(int(ptr))
#----------------------------------------------------------------------------
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
at this time.)
"""
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, "Unable to get CGContext from DC."
cgref = voidp( cgc )
surfaceptr = voidp(surface_create(cgref, width, height))
# create a cairo context for that surface
ctxptr = voidp(cairo_create(surfaceptr))
# Turn it into a pycairo context object
ctx = pycairoAPI.Context_FromContext(ctxptr, pycairoAPI.Context_Type, None)
# The context keeps its own reference to the surface
cairoLib.cairo_surface_destroy(surfaceptr)
elif 'wxMSW' in wx.PlatformInfo:
# This one is easy, just fetch the HDC and use PyCairo to make
# the surface and context.
hdc = dc.GetHandle()
# Ensure the pointer value is clampped into the range of a C signed long
hdc = ctypes.c_long(hdc)
surface = cairo.Win32Surface(hdc.value)
ctx = cairo.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):
"""
Creates and returns a cairo.FontFace object from the native
information in a wx.Font.
"""
if 'wxMac' in wx.PlatformInfo:
fontfaceptr = font_face_create(voidp(font.OSXGetCGFont()))
fontface = pycairoAPI.FontFace_FromFontFace(fontfaceptr)
elif 'wxMSW' in wx.PlatformInfo:
fontfaceptr = voidp( cairoLib.cairo_win32_font_face_create_for_hfont(
ctypes.c_ulong(int(font.GetHFONT()))) )
fontface = pycairoAPI.FontFace_FromFontFace(fontfaceptr)
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
#----------------------------------------------------------------------------
# Only implementation helpers after this point
#----------------------------------------------------------------------------
def _findCairoLib():
"""
Try to locate the Cairo shared library and make a CDLL for it.
"""
global cairoLib
if cairoLib is not None:
return
names = ['cairo', 'cairo-2', 'libcairo', 'libcairo-2']
# first look using just the base name
for name in names:
try:
cairoLib = ctypes.CDLL(name)
return
except:
pass
# if that didn't work then use the ctypes util to search the paths
# appropriate for the system
for name in names:
location = ctypes.util.find_library(name)
if location:
try:
cairoLib = ctypes.CDLL(location)
return
except:
pass
# If the above didn't find it on OS X then we still have a
# trick up our sleeve...
if 'wxMac' in wx.PlatformInfo:
# look at the libs linked to by the pycairo extension module
import macholib.MachO
m = macholib.MachO.MachO(cairo._cairo.__file__)
for h in m.headers:
for idx, name, path in h.walkRelocatables():
if 'libcairo' in path:
try:
cairoLib = ctypes.CDLL(path)
return
except:
pass
if not cairoLib:
raise RuntimeError("Unable to find the Cairo shared library")
#----------------------------------------------------------------------------
# For other DLLs we'll just use a dictionary to track them, as there
# probably isn't any need to use them outside of this module.
_dlls = dict()
def _findHelper(names, key, msg):
dll = _dlls.get(key, None)
if dll is not None:
return dll
location = None
for name in names:
location = ctypes.util.find_library(name)
if location:
break
if not location:
raise RuntimeError(msg)
dll = ctypes.CDLL(location)
_dlls[key] = dll
return dll
def _findGDKLib():
if 'gtk3' in wx.PlatformInfo:
libname = 'gdk-3'
else:
libname = 'gdk-x11-2.0'
return _findHelper([libname], 'gdk',
"Unable to find the GDK shared library")
def _findPangoCairoLib():
return _findHelper(['pangocairo-1.0'], 'pangocairo',
"Unable to find the pangocairo shared library")
def _findAppSvcLib():
return _findHelper(['ApplicationServices'], 'appsvc',
"Unable to find the ApplicationServices Framework")
#----------------------------------------------------------------------------
# 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.
class Pycairo_CAPI(ctypes.Structure):
if cairo.version_info < (1,8): # This structure is known good with pycairo 1.6.4
_fields_ = [
('Context_Type', ctypes.py_object),
('Context_FromContext', ctypes.PYFUNCTYPE(ctypes.py_object,
ctypes.c_void_p,
ctypes.py_object,
ctypes.py_object)),
('FontFace_Type', ctypes.py_object),
('FontFace_FromFontFace', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('FontOptions_Type', ctypes.py_object),
('FontOptions_FromFontOptions', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Matrix_Type', ctypes.py_object),
('Matrix_FromMatrix', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Path_Type', ctypes.py_object),
('Path_FromPath', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Pattern_Type', ctypes.py_object),
('SolidPattern_Type', ctypes.py_object),
('SurfacePattern_Type', ctypes.py_object),
('Gradient_Type', ctypes.py_object),
('LinearGradient_Type', ctypes.py_object),
('RadialGradient_Type', ctypes.py_object),
('Pattern_FromPattern', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('ScaledFont_Type', ctypes.py_object),
('ScaledFont_FromScaledFont', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Surface_Type', ctypes.py_object),
('ImageSurface_Type', ctypes.py_object),
('PDFSurface_Type', ctypes.py_object),
('PSSurface_Type', ctypes.py_object),
('SVGSurface_Type', ctypes.py_object),
('Win32Surface_Type', ctypes.py_object),
('XlibSurface_Type', ctypes.py_object),
('Surface_FromSurface', ctypes.PYFUNCTYPE(ctypes.py_object,
ctypes.c_void_p,
ctypes.py_object)),
('Check_Status', ctypes.PYFUNCTYPE(ctypes.c_int, ctypes.c_int))]
# This structure is known good with pycairo 1.8.4.
# We have to also test for (1,10,8) because pycairo 1.8.10 has an
# incorrect version_info value
elif cairo.version_info < (1,9) or cairo.version_info == (1,10,8):
_fields_ = [
('Context_Type', ctypes.py_object),
('Context_FromContext', ctypes.PYFUNCTYPE(ctypes.py_object,
ctypes.c_void_p,
ctypes.py_object,
ctypes.py_object)),
('FontFace_Type', ctypes.py_object),
('ToyFontFace_Type', ctypes.py_object), #** new in 1.8.4
('FontFace_FromFontFace', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('FontOptions_Type', ctypes.py_object),
('FontOptions_FromFontOptions', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Matrix_Type', ctypes.py_object),
('Matrix_FromMatrix', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Path_Type', ctypes.py_object),
('Path_FromPath', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Pattern_Type', ctypes.py_object),
('SolidPattern_Type', ctypes.py_object),
('SurfacePattern_Type', ctypes.py_object),
('Gradient_Type', ctypes.py_object),
('LinearGradient_Type', ctypes.py_object),
('RadialGradient_Type', ctypes.py_object),
('Pattern_FromPattern', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p,
ctypes.py_object)), #** changed in 1.8.4
('ScaledFont_Type', ctypes.py_object),
('ScaledFont_FromScaledFont', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Surface_Type', ctypes.py_object),
('ImageSurface_Type', ctypes.py_object),
('PDFSurface_Type', ctypes.py_object),
('PSSurface_Type', ctypes.py_object),
('SVGSurface_Type', ctypes.py_object),
('Win32Surface_Type', ctypes.py_object),
('XlibSurface_Type', ctypes.py_object),
('Surface_FromSurface', ctypes.PYFUNCTYPE(ctypes.py_object,
ctypes.c_void_p,
ctypes.py_object)),
('Check_Status', ctypes.PYFUNCTYPE(ctypes.c_int, ctypes.c_int))]
# This structure is known good with pycairo 1.10.0. They keep adding stuff
# to the middle of the structure instead of only adding to the end!
elif cairo.version_info < (1,11):
_fields_ = [
('Context_Type', ctypes.py_object),
('Context_FromContext', ctypes.PYFUNCTYPE(ctypes.py_object,
ctypes.c_void_p,
ctypes.py_object,
ctypes.py_object)),
('FontFace_Type', ctypes.py_object),
('ToyFontFace_Type', ctypes.py_object),
('FontFace_FromFontFace', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('FontOptions_Type', ctypes.py_object),
('FontOptions_FromFontOptions', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Matrix_Type', ctypes.py_object),
('Matrix_FromMatrix', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Path_Type', ctypes.py_object),
('Path_FromPath', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Pattern_Type', ctypes.py_object),
('SolidPattern_Type', ctypes.py_object),
('SurfacePattern_Type', ctypes.py_object),
('Gradient_Type', ctypes.py_object),
('LinearGradient_Type', ctypes.py_object),
('RadialGradient_Type', ctypes.py_object),
('Pattern_FromPattern', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p,
ctypes.py_object)), #** changed in 1.8.4
('ScaledFont_Type', ctypes.py_object),
('ScaledFont_FromScaledFont', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Surface_Type', ctypes.py_object),
('ImageSurface_Type', ctypes.py_object),
('PDFSurface_Type', ctypes.py_object),
('PSSurface_Type', ctypes.py_object),
('SVGSurface_Type', ctypes.py_object),
('Win32Surface_Type', ctypes.py_object),
('Win32PrintingSurface_Type', ctypes.py_object), #** new
('XCBSurface_Type', ctypes.py_object), #** new
('XlibSurface_Type', ctypes.py_object),
('Surface_FromSurface', ctypes.PYFUNCTYPE(ctypes.py_object,
ctypes.c_void_p,
ctypes.py_object)),
('Check_Status', ctypes.PYFUNCTYPE(ctypes.c_int, ctypes.c_int))]
# This structure is known good with pycairo 1.11.1+.
else:
_fields_ = [
('Context_Type', ctypes.py_object),
('Context_FromContext', ctypes.PYFUNCTYPE(ctypes.py_object,
ctypes.c_void_p,
ctypes.py_object,
ctypes.py_object)),
('FontFace_Type', ctypes.py_object),
('ToyFontFace_Type', ctypes.py_object),
('FontFace_FromFontFace', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('FontOptions_Type', ctypes.py_object),
('FontOptions_FromFontOptions', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Matrix_Type', ctypes.py_object),
('Matrix_FromMatrix', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Path_Type', ctypes.py_object),
('Path_FromPath', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Pattern_Type', ctypes.py_object),
('SolidPattern_Type', ctypes.py_object),
('SurfacePattern_Type', ctypes.py_object),
('Gradient_Type', ctypes.py_object),
('LinearGradient_Type', ctypes.py_object),
('RadialGradient_Type', ctypes.py_object),
('Pattern_FromPattern', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p,
ctypes.py_object)), #** changed in 1.8.4
('ScaledFont_Type', ctypes.py_object),
('ScaledFont_FromScaledFont', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Surface_Type', ctypes.py_object),
('ImageSurface_Type', ctypes.py_object),
('PDFSurface_Type', ctypes.py_object),
('PSSurface_Type', ctypes.py_object),
('SVGSurface_Type', ctypes.py_object),
('Win32Surface_Type', ctypes.py_object),
('Win32PrintingSurface_Type', ctypes.py_object), #** new
('XCBSurface_Type', ctypes.py_object), #** new
('XlibSurface_Type', ctypes.py_object),
('Surface_FromSurface', ctypes.PYFUNCTYPE(ctypes.py_object,
ctypes.c_void_p,
ctypes.py_object)),
('Check_Status', ctypes.PYFUNCTYPE(ctypes.c_int, ctypes.c_int)),
('RectangleInt_Type', ctypes.py_object),
('RectangleInt_FromRectangleInt', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('Region_Type', ctypes.py_object),
('Region_FromRegion', ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
('RecordingSurface_Type', ctypes.py_object)]
def _loadPycairoAPI():
global pycairoAPI
if pycairoAPI is not None:
return
if not hasattr(cairo, 'CAPI'):
return
PyCapsule_GetPointer = ctypes.pythonapi.PyCapsule_GetPointer
PyCapsule_GetPointer.argtypes = [ctypes.py_object, ctypes.c_char_p]
PyCapsule_GetPointer.restype = ctypes.c_void_p
ptr = PyCapsule_GetPointer(cairo.CAPI, b'cairo.CAPI')
pycairoAPI = ctypes.cast(ptr, ctypes.POINTER(Pycairo_CAPI)).contents
#----------------------------------------------------------------------------
# Load these at import time. That seems a bit better than doing it at
# first use...
_findCairoLib()
_loadPycairoAPI()
if 'wxMac' in wx.PlatformInfo:
surface_create = cairoLib.cairo_quartz_surface_create_for_cg_context
surface_create.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int]
surface_create.restype = ctypes.c_void_p
cairo_create = cairoLib.cairo_create
cairo_create.argtypes = [ctypes.c_void_p]
cairo_create.restype = ctypes.c_void_p
font_face_create = cairoLib.cairo_quartz_font_face_create_for_cgfont
font_face_create.argtypes = [ctypes.c_void_p]
font_face_create.restype = ctypes.c_void_p
elif 'wxMSW' in wx.PlatformInfo:
cairoLib.cairo_win32_font_face_create_for_hfont.restype = ctypes.c_void_p
elif 'wxGTK' in wx.PlatformInfo:
gdkLib = _findGDKLib()
pcLib = _findPangoCairoLib()
gdkLib.gdk_cairo_create.restype = ctypes.c_void_p
pcLib.pango_cairo_font_map_get_default.restype = ctypes.c_void_p
gdkLib.gdk_pango_context_get.restype = ctypes.c_void_p
pcLib.pango_font_map_load_font.restype = ctypes.c_void_p
pcLib.pango_cairo_font_get_scaled_font.restype = ctypes.c_void_p
cairoLib.cairo_scaled_font_get_font_face.restype = ctypes.c_void_p
#----------------------------------------------------------------------------