#!/usr/bin/env python3 import math import cairo import gi gi.require_version('Pango', '1.0') gi.require_version('PangoCairo', '1.0') from gi.repository import Pango, PangoCairo surface = cairo.SVGSurface('test.svg', 480, 480) ctx = cairo.Context(surface) ctx.set_source_rgb(1, 1, 1) ctx.rectangle(0, 0, 480, 480) ctx.fill() layout = PangoCairo.create_layout(ctx) # Size is relative to surface units. An ImageSurface will get nominally # 45 pixel tall text. An SVGSurface will get 45 point text. # # The comma is necessary because otherwise Pango will think you mean the # Roman variation of a font named "Times New". # # Linux users without Microsoft fonts installed might want to use # Liberation Serif instead of Times New Roman. font_description = Pango.font_description_from_string('Times New Roman, 45') layout.set_font_description(font_description) # Set the origin a little ways into the canvas to give some margin ctx.translate(16, 16) # Needed whenever the Cairo context's transformation (or target surface) changes. PangoCairo.update_layout(ctx, layout) # Make a function to draw text and the bounds around the text, so we can # do this a few times in a row. The function will assume that the context # has been translated so the text will be placed at the current origin. # The function returns the text's logical bounds so we can place # additional text without overlapping anything. def draw_text_and_bounds(ctx, layout, text): """Returns the text's logical rectangle.""" layout.set_text(text) # The ink_rect gives the bounds of the glyphs in aggregate. The # logical_rect covers the total area of the font; basically the full line. # # As with all Pango measurements, these are scaled integers. You need to # divide them by Pango.SCALE to get Cairo units. ink_rect, logical_rect = layout.get_extents() baseline = layout.get_baseline() ctx.set_line_width(2) # Baseline in orange. Offset to account for stroke thickness. ctx.set_source_rgb(1, 0.5, 0.25) ctx.move_to(-8, baseline / Pango.SCALE + 1) ctx.line_to((logical_rect.x + logical_rect.width) / Pango.SCALE + 8, baseline / Pango.SCALE + 1) ctx.stroke() # logical rect in red ctx.set_source_rgb(0.75, 0, 0) ctx.rectangle(logical_rect.x / Pango.SCALE - 1, logical_rect.y / Pango.SCALE - 1, logical_rect.width / Pango.SCALE + 2, logical_rect.height / Pango.SCALE + 2) ctx.stroke() # ink rect in blue ctx.set_source_rgb(0, 0, 0.75) ctx.rectangle(ink_rect.x / Pango.SCALE - 1, ink_rect.y / Pango.SCALE - 1, ink_rect.width / Pango.SCALE + 2, ink_rect.height / Pango.SCALE + 2) ctx.stroke() # Origin in dark blue ctx.set_source_rgb(0, 0, 0.5) ctx.arc(0, 0, 2 * math.sqrt(2), 0, 2 * math.pi) ctx.fill() # Output the text ctx.set_source_rgb(0, 0, 0) # Since the origin was changed via translation, the text just goes in # at (0, 0). ctx.move_to(0, 0) PangoCairo.show_layout(ctx, layout) return logical_rect logical_rect = draw_text_and_bounds(ctx, layout, 'Aa Ee Rr ') ctx.translate(0, 16 + logical_rect.height / Pango.SCALE) PangoCairo.update_layout(ctx, layout) font_description = Pango.font_description_from_string('Times New Roman, Italic 45') layout.set_font_description(font_description) # Note that the ink rectangle for this text actually has a negative x # value (with Times New Roman as the font). Logically, the text starts at # the origin, but the serif on the "B" sticks out a little to the left. logical_rect = draw_text_and_bounds(ctx, layout, 'Bb Gg Jj') ctx.translate(0, 16 + logical_rect.height / Pango.SCALE) PangoCairo.update_layout(ctx, layout) # Alternate way of accessing fonts. font_map = PangoCairo.font_map_get_default() font_family = font_map.get_family('Times New Roman') if font_family is None: print('No font found. Fonts on the system:') for family in font_map.list_families(): print(family.get_name()) exit(1) font_face = font_family.get_face('Italic') if font_face is None: print('No face found. "{}" faces on the system:'.format(font_family.get_name())) for face in font_family.list_faces(): print(face.get_face_name()) exit(1) font_description = font_face.describe() font_description.set_size(45 * Pango.SCALE) layout.set_font_description(font_description) logical_rect = draw_text_and_bounds(ctx, layout, 'אָלֶף־בֵּית עִבְרִי') surface.finish() surface.flush()