[cairo-commit] [cairo-www] src/cookbook

Bryce Harrington bryce at freedesktop.org
Fri Jan 15 13:28:09 PST 2016


 src/cookbook/freetypepython.mdwn |  140 +++++++++++++++++++++++++--------------
 1 file changed, 92 insertions(+), 48 deletions(-)

New commits:
commit 3a0314a1c426636f2db817f597565c1be0f7e2d8
Author: Lawrence D'Oliveiro <ldo at geek-central.gen.nz>
Date:   Wed Dec 23 01:23:19 2015 +0000

    fix memory leaks and other cleanup in Python FreeType example

diff --git a/src/cookbook/freetypepython.mdwn b/src/cookbook/freetypepython.mdwn
index a1a48de..5aea77f 100644
--- a/src/cookbook/freetypepython.mdwn
+++ b/src/cookbook/freetypepython.mdwn
@@ -5,85 +5,129 @@ The following snippet uses Python's ctypes module to load a font file using Free
 and create a cairo font face from it, using the cairo-ft API that is not part of pycairo yet.
 The resulting cairo font face however can be used normally with pycairo.
 
+	#!/usr/bin/python3
 
-	import ctypes
+	import ctypes as ct
 	import cairo
 
-
 	_initialized = False
 	def create_cairo_font_face_for_file (filename, faceindex=0, loadoptions=0):
+		"given the name of a font file, and optional faceindex to pass to FT_New_Face" \
+		" and loadoptions to pass to cairo_ft_font_face_create_for_ft_face, creates" \
+		" a cairo.FontFace object that may be used to render text with that font."
 		global _initialized
 		global _freetype_so
 		global _cairo_so
 		global _ft_lib
+		global _ft_destroy_key
 		global _surface
 
 		CAIRO_STATUS_SUCCESS = 0
 		FT_Err_Ok = 0
 
 		if not _initialized:
-
 			# find shared objects
-			_freetype_so = ctypes.CDLL ("libfreetype.so.6")
-			_cairo_so = ctypes.CDLL ("libcairo.so.2")
-
-			_cairo_so.cairo_ft_font_face_create_for_ft_face.restype = ctypes.c_void_p
-			_cairo_so.cairo_ft_font_face_create_for_ft_face.argtypes = [ ctypes.c_void_p, ctypes.c_int ]
-			_cairo_so.cairo_set_font_face.argtypes = [ ctypes.c_void_p, ctypes.c_void_p ]
-			_cairo_so.cairo_font_face_status.argtypes = [ ctypes.c_void_p ]
-			_cairo_so.cairo_status.argtypes = [ ctypes.c_void_p ]
-
+			_freetype_so = ct.CDLL("libfreetype.so.6")
+			_cairo_so = ct.CDLL("libcairo.so.2")
+			_cairo_so.cairo_ft_font_face_create_for_ft_face.restype = ct.c_void_p
+			_cairo_so.cairo_ft_font_face_create_for_ft_face.argtypes = [ ct.c_void_p, ct.c_int ]
+			_cairo_so.cairo_font_face_get_user_data.restype = ct.c_void_p
+			_cairo_so.cairo_font_face_set_user_data.argtypes = (ct.c_void_p, ct.c_void_p, ct.c_void_p, ct.c_void_p)
+			_cairo_so.cairo_set_font_face.argtypes = [ ct.c_void_p, ct.c_void_p ]
+			_cairo_so.cairo_font_face_status.argtypes = [ ct.c_void_p ]
+			_cairo_so.cairo_font_face_destroy.argtypes = (ct.c_void_p,)
+			_cairo_so.cairo_status.argtypes = [ ct.c_void_p ]
 			# initialize freetype
-			_ft_lib = ctypes.c_void_p ()
-			if FT_Err_Ok != _freetype_so.FT_Init_FreeType (ctypes.byref (_ft_lib)):
-			  raise "Error initialising FreeType library."
-
-			class PycairoContext(ctypes.Structure):
-			    _fields_ = [("PyObject_HEAD", ctypes.c_byte * object.__basicsize__),
-					("ctx", ctypes.c_void_p),
-					("base", ctypes.c_void_p)]
-
-			_surface = cairo.ImageSurface (cairo.FORMAT_A8, 0, 0)
-
+			_ft_lib = ct.c_void_p()
+			status = _freetype_so.FT_Init_FreeType(ct.byref(_ft_lib))
+			if  status != FT_Err_Ok :
+				raise RuntimeError("Error %d initializing FreeType library." % status)
+			#end if
+
+			class PycairoContext(ct.Structure):
+				_fields_ = \
+					[
+						("PyObject_HEAD", ct.c_byte * object.__basicsize__),
+						("ctx", ct.c_void_p),
+						("base", ct.c_void_p),
+					]
+			#end PycairoContext
+
+			_surface = cairo.ImageSurface(cairo.FORMAT_A8, 0, 0)
+			_ft_destroy_key = ct.c_int() # dummy address
 			_initialized = True
-
-		# create freetype face
-		ft_face = ctypes.c_void_p()
-		cairo_ctx = cairo.Context (_surface)
-		cairo_t = PycairoContext.from_address(id(cairo_ctx)).ctx
-
-		if FT_Err_Ok != _freetype_so.FT_New_Face (_ft_lib, filename, faceindex, ctypes.byref(ft_face)):
-			raise Exception("Error creating FreeType font face for " + filename)
-
-		# create cairo font face for freetype face
-		cr_face = _cairo_so.cairo_ft_font_face_create_for_ft_face (ft_face, loadoptions)
-		if CAIRO_STATUS_SUCCESS != _cairo_so.cairo_font_face_status (cr_face):
-			raise Exception("Error creating cairo font face for " + filename)
-
-		_cairo_so.cairo_set_font_face (cairo_t, cr_face)
-		if CAIRO_STATUS_SUCCESS != _cairo_so.cairo_status (cairo_t):
-			raise Exception("Error creating cairo font face for " + filename)
-
-		face = cairo_ctx.get_font_face ()
-
+		#end if
+
+		ft_face = ct.c_void_p()
+		cr_face = None
+		try :
+			# load FreeType face
+			status = _freetype_so.FT_New_Face(_ft_lib, filename.encode("utf-8"), faceindex, ct.byref(ft_face))
+			if status != FT_Err_Ok :
+				raise RuntimeError("Error %d creating FreeType font face for %s" % (status, filename))
+			#end if
+
+			# create Cairo font face for freetype face
+			cr_face = _cairo_so.cairo_ft_font_face_create_for_ft_face(ft_face, loadoptions)
+			status = _cairo_so.cairo_font_face_status(cr_face)
+			if status != CAIRO_STATUS_SUCCESS :
+				raise RuntimeError("Error %d creating cairo font face for %s" % (status, filename))
+			#end if
+			# Problem: Cairo doesn't know to call FT_Done_Face when its font_face object is
+			# destroyed, so we have to do that for it, by attaching a cleanup callback to
+			# the font_face. This only needs to be done once for each font face, while
+			# cairo_ft_font_face_create_for_ft_face will return the same font_face if called
+			# twice with the same FT Face.
+			# The following check for whether the cleanup has been attached or not is
+			# actually unnecessary in our situation, because each call to FT_New_Face
+			# will return a new FT Face, but we include it here to show how to handle the
+			# general case.
+			if _cairo_so.cairo_font_face_get_user_data(cr_face, ct.byref(_ft_destroy_key)) == None :
+				status = _cairo_so.cairo_font_face_set_user_data \
+				  (
+					cr_face,
+					ct.byref(_ft_destroy_key),
+					ft_face,
+					_freetype_so.FT_Done_Face
+				  )
+				if status != CAIRO_STATUS_SUCCESS :
+					raise RuntimeError("Error %d doing user_data dance for %s" % (status, filename))
+				#end if
+				ft_face = None # Cairo has stolen my reference
+			#end if
+
+			# set Cairo font face into Cairo context
+			cairo_ctx = cairo.Context(_surface)
+			cairo_t = PycairoContext.from_address(id(cairo_ctx)).ctx
+			_cairo_so.cairo_set_font_face(cairo_t, cr_face)
+			status = _cairo_so.cairo_font_face_status(cairo_t)
+			if status != CAIRO_STATUS_SUCCESS :
+				raise RuntimeError("Error %d creating cairo font face for %s" % (status, filename))
+			#end if
+
+		finally :
+			_cairo_so.cairo_font_face_destroy(cr_face)
+			_freetype_so.FT_Done_Face(ft_face)
+		#end try
+
+		# get back Cairo font face as a Python object
+		face = cairo_ctx.get_font_face()
 		return face
+	#end create_cairo_font_face_for_file
 
 	if __name__ == '__main__':
-
-		face = create_cairo_font_face_for_file ("/usr/share/fonts/dejavu-lgc/DejaVuLGCSerif.ttf", 0)
-
+		face = create_cairo_font_face_for_file("/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf", 0)
 		surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 128, 128)
-
 		ctx = cairo.Context(surface)
 
 		ctx.set_font_face(face)
 		ctx.set_font_size(30)
 		ctx.move_to(0, 44)
 		ctx.show_text("Hello,")
-
 		ctx.move_to(30, 74)
 		ctx.show_text("world!")
 
 		del ctx
 
 		surface.write_to_png("hello.png")
+	#end if


More information about the cairo-commit mailing list