#!/usr/bin/env python # -*- coding: utf-8 -*- # ----------------------------------------------------------------------------- # # pycairo/cairocffi-based glyph-vector-2 example - Copyright 2017 Hin-Tak Leung # Distributed under the terms of the new BSD license. # # rewrite of the numply,matplotlib-based example from Nicolas P. Rougier # - The code is incomplete and over-simplified, as it ignores the 3rd order # bezier curve bit when intepolating between off-curve points. # This is only correct for truetype fonts (which only use 2nd order bezier curves). # - Also it seems to assume the first point is always on curve; this is # unusual but legal. # # Can cope with well-behaved Postscript/CFF fonts too. # # ----------------------------------------------------------------------------- ''' Show how to access glyph outline description. ''' from freetype import * # using Matrix class from Cairo, instead of FreeType's from cairo import Context, SVGSurface, Matrix, SurfacePattern, FILTER_BEST from bitmap_to_surface import make_image_surface if __name__ == '__main__': import numpy from PIL import Image # Replacement for Path enums: STOP, MOVETO, LINETO, CURVE3, CURVE4 = 0, 1, 2, 3, 4 face = Face('./Vera.ttf') face.set_char_size( 32*64 ) face.load_char('g') slot = face.glyph bitmap = face.glyph.bitmap width = face.glyph.bitmap.width rows = face.glyph.bitmap.rows pitch = face.glyph.bitmap.pitch Z = make_image_surface(bitmap) outline = slot.outline points = numpy.array(outline.points, dtype=[('x',float), ('y',float)]) x, y = points['x'], points['y'] bbox = outline.get_cbox() MARGIN = 10 scale = 3 def Floor64(x): return (x//64) * 64 def Ceil64(x): return ((x+63)//64) * 64 width_s = (width * 64)//scale + 2 * MARGIN height_s = (rows * 64)//scale + 2 * MARGIN surface = SVGSurface('glyph-vector-2-cairo.svg', width_s, height_s) ctx = Context(surface) ctx.set_source_rgb(1,1,1) ctx.paint() ctx.save() ctx.scale(1.0/scale,1.0/scale) ctx.translate(-Floor64(bbox.xMin) + MARGIN * scale,-Floor64(bbox.yMin) + MARGIN * scale) ctx.transform(Matrix(1,0,0,-1)) ctx.translate(0, -(Ceil64(bbox.yMax) + Floor64(bbox.yMin))) # difference! start, end = 0, 0 VERTS, CODES = [], [] # Iterate over each contour for i in range(len(outline.contours)): end = outline.contours[i] points = outline.points[start:end+1] points.append(points[0]) tags = outline.tags[start:end+1] tags.append(tags[0]) segments = [ [points[0],], ] for j in range(1, len(points) ): segments[-1].append(points[j]) if ( FT_Curve_Tag( tags[j] ) == FT_Curve_Tag_On ) and j < (len(points)-1): segments.append( [points[j],] ) verts = [points[0], ] codes = [MOVETO,] tags.pop() for segment in segments: if len(segment) == 2: verts.extend(segment[1:]) codes.extend([LINETO]) elif len(segment) == 3: verts.extend(segment[1:]) codes.extend([CURVE3, CURVE3]) elif ( len(segment) == 4 ) \ and ( FT_Curve_Tag(tags[1]) == FT_Curve_Tag_Cubic ) \ and ( FT_Curve_Tag(tags[2]) == FT_Curve_Tag_Cubic ): verts.extend(segment[1:]) codes.extend([CURVE4, CURVE4, CURVE4]) else: # Interpolating verts.append(segment[1]) codes.append(CURVE3) for i in range(1,len(segment)-2): A,B = segment[i], segment[i+1] C = ((A[0]+B[0])/2.0, (A[1]+B[1])/2.0) verts.extend([ C, B ]) codes.extend([ CURVE3, CURVE3]) verts.append(segment[-1]) codes.append(CURVE3) [tags.pop() for x in range(len(segment) - 1)] VERTS.extend(verts) CODES.extend(codes) start = end+1 # Draw glyph ctx.new_path() ctx.set_source_rgba(0.8,0.5,0.8, 1) i = 0 while (i < len(CODES)): if (CODES[i] == MOVETO): ctx.move_to(VERTS[i][0],VERTS[i][1]) i += 1 elif (CODES[i] == LINETO): ctx.line_to(VERTS[i][0],VERTS[i][1]) i += 1 elif (CODES[i] == CURVE3): ctx.curve_to(VERTS[i][0],VERTS[i][1], VERTS[i+1][0],VERTS[i+1][1], # undocumented VERTS[i+1][0],VERTS[i+1][1]) i += 2 elif (CODES[i] == CURVE4): ctx.curve_to(VERTS[i][0],VERTS[i][1], VERTS[i+1][0],VERTS[i+1][1], VERTS[i+2][0],VERTS[i+2][1]) i += 3 ctx.fill_preserve() ctx.set_source_rgb(0,0,0) ctx.set_line_width(6) ctx.stroke() ctx.restore() scale2 = (height_s - 2.0 * MARGIN)/rows ctx.set_source_surface(Z, 0, 0) pattern = ctx.get_source() SurfacePattern.set_filter(pattern, FILTER_BEST) scalematrix = Matrix() scalematrix.scale(1.0/scale2, 1.0/scale2) scalematrix.translate(-( width_s/2.0 - width *scale2 /2.0 ), -MARGIN) pattern.set_matrix(scalematrix) ctx.set_source_rgba (0, 0, 0, 0.7) ctx.mask(pattern) ctx.fill() surface.flush() surface.write_to_png("glyph-vector-2-cairo.png") surface.finish() Image.open("glyph-vector-2-cairo.png").show()