Blog of roxlu, co-founder of Apollo Media. Contact info[shift+2]apollomedia.nl.

Rendering text with Pango, Cairo and Freetype

Pango is a great open source text layouting library which I've been using on several projects. The only problem is that the build process has quite some peculiarities that you need to know about. My goal was to create a build script that compiles all the dependencies for Pango. This script compiles all the necessary libraries and is tested on Mac OS 10.9. and Arch Linux

Compilation challenges

There are some things you need to know when compiling Pango on Mac. If you want to use Freetype as a rasterization backend you need to compile harfbuzz first, because the configure script won't use freetype without harfbuzz. Harfbuzz has dependencies with ragel and colm which at the time of writing couldn't be configured because the build script tests for the colm version doesn't work on Mac. To make ragel compile I changed its configure.ac file and commented the check for the colm version as I was compiling colm 0.13 which is needs (the build script does this for you). The build script also compiles gtkdoc because some of libraries (including pango) are compiled from the the latest git version that don't contain a ready to use configure script. Instead they have a ./autogen.sh script that uses autoconfig to generate these scripts and the ./autogen.sh need gtkdocs in some cases. Note that the configure.ac of the current pango git version doesn't work on Linux so we're using a diffrent version there. The build script manipulates the catalog.xml for the gtkdoc compilation. I'm also compiling pango with --with-included-modules so the modules are linked statically. After compiling you need to have the libpango-1.0.a and libpangoft2-1.0.a files. When you have these you can use freetype with pango.

Finding fonts

Pango uses FontConfig to find fonts and before FontConfig can find fonts you need to create a fonts.conf. FontConfig uses a environment variable called FONTCONFIG_PATH where is looks for this fonts.conf file. For convenience purposes you can create a fonts.conf file in the same directory as you executable with the following information and use a shell script to set a temporary FONTCONFIG_PATH variable. Place your font files in this ./fonts/ directory and fontconfig will be able to find your fonts.

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
  <dir>./fonts/</dir>
  <cachedir>./fonts/cache/</cachedir>
  <config></config>
</fontconfig>

Make sure to set the FONTCONFIG_PATH variable before you start the application:

#!/bin/sh
export FONTCONFIG_PATH=.
./your_application

Pango, Cairo and Freetype code example

The code snippet below shows an example how you can use Pango with Cairo and Freetype font rasterization. The code has been tested on Mac 10.9 and Arch Linux and linked against the libraries that are compiled by the build script.

/*
 
  Text layouting and rendering with Pango
  ---------------------------------------
 
  This code snippet shows how to create a cairo surface and 
  render some text into it using Pango. We store the generated
  pixels in a png file.
 
  n.b. this file was created for testing not production.
 
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <cairo.h>
#include <freetype2/ftbitmap.h>
#include <pango/pangocairo.h>
#include <pango/pangoft2.h>
 
#define USE_FREETYPE 1
#define USE_RGBA 0
 
int main() {
 
  cairo_surface_t* surf = NULL;
  cairo_t* cr = NULL;
  cairo_status_t status;
  PangoContext* context = NULL;
  PangoLayout* layout = NULL;
  PangoFontDescription* font_desc = NULL;
  PangoFontMap* font_map = NULL;
  FT_Bitmap bmp = {0};
 
  int stride = 0;
  int width = 640;
  int height = 480;
 
  /* ------------------------------------------------------------ */
  /*                   I N I T I A L I Z E                        */
  /* ------------------------------------------------------------ */
 
  /* FT buffer */
  FT_Bitmap_New(&bmp);
  bmp.rows = height;
  bmp.width = width;
 
  bmp.buffer = (unsigned char*)malloc(bmp.rows * bmp.width);
  if (NULL == bmp.buffer) {
    printf("+ error: cannot allocate the buffer for the output bitmap.\n");
    exit(EXIT_FAILURE);
  }
 
  /* create our "canvas" */
  bmp.pitch = (width + 3) & -4;
  bmp.pixel_mode = FT_PIXEL_MODE_GRAY; /*< Grayscale*/
  bmp.num_grays = 256;
  stride = cairo_format_stride_for_width(CAIRO_FORMAT_A8, width);
  surf = cairo_image_surface_create_for_data(bmp.buffer, CAIRO_FORMAT_A8, width, height, stride);
 
  if (CAIRO_STATUS_SUCCESS != cairo_surface_status(surf)) {
    printf("+ error: couldn't create the surface.\n");
    exit(EXIT_FAILURE);
  }
 
  /* create our cairo context object that tracks state. */
  cr = cairo_create(surf);
  if (CAIRO_STATUS_NO_MEMORY == cairo_status(cr)) {
    printf("+ error: out of memory, cannot create cairo_t*\n");
    exit(EXIT_FAILURE);
  }
 
  /* ------------------------------------------------------------ */
  /*               D R A W   I N T O  C A N V A S                 */
  /* ------------------------------------------------------------ */
 
  font_map = pango_ft2_font_map_new();
  if (NULL == font_map) {
    printf("+ error: cannot create the pango font map.\n");
    exit(EXIT_FAILURE);
  }
 
  context = pango_font_map_create_context(font_map);
  if (NULL == context) {
    printf("+ error: cannot create pango font context.\n");
    exit(EXIT_FAILURE);
  }
 
  /* create layout object. */
  layout = pango_layout_new(context);
  if (NULL == layout) {
    printf("+ error: cannot create the pango layout.\n");
    exit(EXIT_FAILURE);
  }
 
  /* create the font description @todo the reference does not tell how/when to free this */
  font_desc = pango_font_description_from_string("Station 35");
  pango_layout_set_font_description(layout, font_desc);
  pango_font_map_load_font(font_map, context, font_desc);
  pango_font_description_free(font_desc);
 
  /* set the width around which pango will wrap */
  pango_layout_set_width(layout, 150 * PANGO_SCALE);
 
  /* write using the markup feature */
  const gchar* text = ""
    "<span foreground=\"blue\" font_family=\"Station\">"
    "   <b> bold </b>"
    "   <u> is </u>"
    "   <i> nice </i>"
    "</span>"
    "<tt> hello </tt>"
    "<span font_family=\"sans\" font_stretch=\"ultracondensed\" letter_spacing=\"500\" font_weight=\"light\"> SANS</span>"
    "<span foreground=\"#FFCC00\"> colored</span>"
    "";
 
  gchar* plaintext ;
  PangoAttrList* attr_list;
  pango_layout_set_markup(layout, text, -1);
 
  /* render */
  pango_ft2_render_layout(&bmp, layout, 30, 100);
  pango_cairo_update_layout(cr, layout);
 
  /* ------------------------------------------------------------ */
  /*               O U T P U T  A N D  C L E A N U P              */
  /* ------------------------------------------------------------ */
 
  /* write to png */
  status = cairo_surface_write_to_png(surf, "test_font.png");
  if (CAIRO_STATUS_SUCCESS != status) {
    printf("+ error: couldn't write to png\n");
    exit(EXIT_FAILURE);
  }
 
  cairo_surface_destroy(surf);
  cairo_destroy(cr);
 
  g_object_unref(layout);
  g_object_unref(font_map);
  g_object_unref(context);
 
  return 0;
}

To compile and run the above example I used the following script. I'm also settings the FONTCONFIG_PATH in this scripts so it can find the font files. Also, this example shows what include paths you need to set and with what libraries you need to link (this example is created for Mac 10.9).

#!/bin/sh
is_mac=n
is_linux=n
 
if [ "$(uname)" == "Darwin" ]; then
    is_mac=y
elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
    is_linux=y
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then
    echo "Windows not yet supported."
    exit
fi
 
if [ "${is_mac}" = "y" ] ; then
    clang -o test_pango \
        -I./install/include/cairo \
        -I./install/include \
        -I./install/include/freetype2/ \
        -I./install/include/pango-1.0/ \
        -I./install/include/glib-2.0 \
        -I./install/lib/glib-2.0/include/ \
        test_pango.cpp \
        -L./install/lib \
        -lpango-1.0 \
        -lglib-2.0 \
        -lgobject-2.0 \
        -lgmodule-2.0 \
        -lffi \
        -lintl \
        -lcairo \
        -lpixman-1 \
        -lpng \
        -lfreetype \
        -lpangocairo-1.0 \
        -lpangoft2-1.0 \
        -lharfbuzz \
        -lfontconfig \
        -lexpat \
        -liconv \
        -framework CoreFoundation \
        -framework Cocoa
 
    if [ -f ./test_pango ] ; then 
        export FONTCONFIG_PATH=.
        ./test_pango
    fi
 
else
    g++ -o test_pango \
        -I./install/include/cairo \
        -I./install/include \
        -I./install/include/freetype2/ \
        -I./install/include/pango-1.0/ \
        -I./install/include/glib-2.0 \
        -I./install/lib/glib-2.0/include/ \
        test_pango.cpp \
        -L./install/lib \
        ./install/lib/libpangocairo-1.0.a \
        ./install/lib/libpangoft2-1.0.a \
        ./install/lib/libpango-1.0.a \
        ./install/lib/libfontconfig.a \
        ./install/lib/libgobject-2.0.a \
        ./install/lib/libgmodule-2.0.a \
        ./install/lib/libglib-2.0.a \
        ./install/lib/libffi.a \
        ./install/lib/libcairo.a \
        ./install/lib/libpixman-1.a \
        ./install/lib/libpng12.a \
        ./install/lib/libfreetype.a \
        ./install/lib/libharfbuzz.a \
        -lexpat \
        -lpthread \
        -ldl \
        -lz \
        -lbz2
 
    if [ -f ./test_pango ] ; then 
        export FONTCONFIG_PATH=.
        ./test_pango
    fi
fi

Dependencies

This post uses the following libraries in the build script. On linux we used gtk-doc from a package manager (pacman -S gtk-doc).

Library/util Version or git commit
autoconf 2.69
libtool 2.4.2
automake 1.14
pkgconfig 0.28
gtkdoc (only on mac) 3576d64d4a45f5ccba1f6304a3476771f0c188ab
pixman 0.32.6
gettext 0.19.2
libxml2 2.9.1
fontconfig 2.11.1
libpng 1.2.51
libjpg v9a
colm ca708c6655243d93017c4b21dd5b36e0d1dfd431
ragel 246236afbcb5b8cd53fa1f7d1e23fbb1447cebfc
harfbuzz 0.9.35
freetype bc12d9e9ac7c4960ad4539a48b261193a06fad88
glib 27405ae878ebabb4361d38ed22be11b6891fd866
cairo 0aa43ed886c0f8468a21a470f2f024bd4d8a4513
pango 0d1945ed2c602e295e433ce7e9e1ecbc6600c76a