--- Title: Rendering with user fonts --- # Rendering with user fonts Most of the time, text is rendered using fonts that are ready-made and provided in formats such as TrueType or OpenType. Pango supports such fonts with [class@PangoHbFace]. But there are fonts in custom formats that HarfBuzz might not support. And sometimes, it is more convenient to use a drawing API to render glyphs on-the-spot, maybe with fancy effects. For these cases, Pango provides the [class@PangoUserFace] implementation of `PangoFontFace` that uses callbacks for its functionality. This lets you embed custom drawing into your text, fully integrated with Pango's text layout capabilities. ## A user font example ``` #include #include #include #define BULLET "•" #define HEART "♥" const char text[] = "The GNOME project provides three things:\n" "\n" " • The GNOME desktop environment\n" " • The GNOME development platform\n" " • Planet GNOME\n" " ♥ Lots of love"; typedef struct { double width, height; const char *path; } MiniSvg; static MiniSvg GnomeFootLogo = { 96.2152, 118.26, "M 86.068,1 C 61.466,0 56.851,35.041 70.691,35.041 C 84.529,35.041 110.671,0 86.068,0 z " "M 45.217,30.699 C 52.586,31.149 60.671,2.577 46.821,4.374 C 32.976,6.171 37.845,30.249 45.217,30.699 z " "M 11.445,48.453 C 16.686,46.146 12.12,23.581 3.208,29.735 C -5.7,35.89 6.204,50.759 11.445,48.453 z " "M 26.212,36.642 C 32.451,35.37 32.793,9.778 21.667,14.369 C 10.539,18.961 19.978,37.916 26.212,36.642 L 26.212,36.642 z " "M 58.791,93.913 C 59.898,102.367 52.589,106.542 45.431,101.092 C 22.644,83.743 83.16,75.088 79.171,51.386 C 75.86,31.712 15.495,37.769 8.621,68.553 C 3.968,89.374 27.774,118.26 52.614,118.26 C 64.834,118.26 78.929,107.226 81.566,93.248 C 83.58,82.589 57.867,86.86 58.791,93.913 L 58.791,93.913 z " "\0" }; static void mini_svg_render (MiniSvg *shape, cairo_t *cr) { double x, y; char op[2]; int len; double x1, y1, x2, y2, x3, y3; const char *p; cairo_get_current_point (cr, &x, &y); cairo_translate (cr, x, y); for (p = shape->path; sscanf (p, "%1s %n", op, &len), p += len, *p;) switch (*op) { case 'M': sscanf (p, "%lf,%lf %n", &x, &y, &len); p += len; cairo_move_to (cr, x, y); break; case 'L': sscanf (p, "%lf,%lf %n", &x, &y, &len); p += len; cairo_line_to (cr, x, y); break; case 'C': sscanf (p, "%lf,%lf %lf,%lf %lf,%lf %n", &x1, &y1, &x2, &y2, &x3, &y3, &len); p += len; cairo_curve_to (cr, x1, y1, x2, y2, x3, y3); break; case 'z': cairo_close_path (cr); break; default: g_warning ("Invalid MiniSvg operation '%c'", *op); break; } cairo_fill (cr); } static PangoLayout * get_layout (cairo_t *cr) { PangoLayout *layout; PangoAttrList *attrs; PangoFontDescription *font_desc; const char *p; /* Create a PangoLayout, set the font and text */ layout = pango_cairo_create_layout (cr); pango_layout_set_text (layout, text, -1); font_desc = pango_font_description_from_string ("Cantarell 12"); pango_layout_set_font_description (layout, font_desc); pango_font_description_free (font_desc); attrs = pango_attr_list_new (); font_desc = pango_font_description_from_string ("Bullets 12"); for (p = text; (p = strstr (p, BULLET)); p += strlen (BULLET)) { PangoAttribute *attr; attr = pango_attr_font_desc_new (font_desc); attr->start_index = p - text; attr->end_index = attr->start_index + strlen (BULLET); pango_attr_list_insert (attrs, attr); } for (p = text; (p = strstr (p, HEART)); p += strlen (HEART)) { PangoAttribute *attr; attr = pango_attr_font_desc_new (font_desc); attr->start_index = p - text; attr->end_index = attr->start_index + strlen (HEART); pango_attr_list_insert (attrs, attr); } pango_font_description_free (font_desc); pango_layout_set_attributes (layout, attrs); pango_attr_list_unref (attrs); return layout; } static void measure_text (cairo_t *cr, int *width, int *height) { PangoLayout *layout = get_layout (cr); PangoLines *lines = pango_layout_get_lines (layout); PangoRectangle ext; pango_lines_get_extents (lines, NULL, &ext); pango_extents_to_pixels (&ext, NULL); *width = ext.width + 20; *height = ext.height + 20; } static void draw_text (cairo_t *cr) { PangoLayout *layout = get_layout (cr); PangoLines *lines = pango_layout_get_lines (layout); cairo_move_to (cr, 10, 10); pango_cairo_show_lines (cr, lines); g_object_unref (layout); } static gboolean glyph_cb (PangoUserFace *face, hb_codepoint_t unicode, hb_codepoint_t *glyph, gpointer data) { if (unicode == 0x2022 || unicode == 0x2665) { *glyph = unicode; return TRUE; } return FALSE; } static gboolean glyph_info_cb (PangoUserFace *face, int size, hb_codepoint_t glyph, hb_glyph_extents_t *extents, hb_position_t *h_advance, hb_position_t *v_advance, gboolean *is_color, gpointer user_data) { if (glyph == 0x2022 || glyph == 0x2665) { extents->x_bearing = 0; extents->y_bearing = - size; extents->width = size; extents->height = size; *h_advance = size; *v_advance = size; *is_color = glyph == 0x2665; return TRUE; } return FALSE; } static gboolean font_info_cb (PangoUserFace *face, int size, hb_font_extents_t *extents, gpointer user_data) { extents->ascender = size; extents->descender = 0; extents->line_gap = 0; return TRUE; } static gboolean render_cb (PangoUserFace *face, int size, hb_codepoint_t glyph, gpointer user_data, const char *backend_id, gpointer backend_data) { cairo_t *cr = backend_data; if (strcmp (backend_id, "cairo") != 0) return FALSE; if (glyph == 0x2022) { MiniSvg *shape = &GnomeFootLogo; cairo_move_to (cr, 0, -1); cairo_scale (cr, 1. / shape->width, 1. / shape->height); mini_svg_render (shape, cr); return TRUE; } else if (glyph == 0x2665) { cairo_set_source_rgb (cr, 1., 0., 0.); cairo_move_to (cr, .5, .0); cairo_line_to (cr, .9, -.4); cairo_curve_to (cr, 1.1, -.8, .5, -.9, .5, -.5); cairo_curve_to (cr, .5, -.9, -.1, -.8, .1, -.4); cairo_close_path (cr); cairo_fill (cr); return TRUE; } return FALSE; } static void setup_fontmap (void) { PangoFontMap *fontmap = pango_font_map_get_default (); PangoFontDescription *desc; PangoUserFace *face; desc = pango_font_description_new (); pango_font_description_set_family (desc, "Bullets"); /* Create our fancy user font, "Bullets Black" */ face = pango_user_face_new (font_info_cb, glyph_cb, glyph_info_cb, NULL, render_cb, NULL, NULL, "Black", desc); /* And add it to the default fontmap */ pango_font_map_add_face (fontmap, PANGO_FONT_FACE (face)); pango_font_description_free (desc); } int main (int argc, char **argv) { cairo_t *cr; char *filename; cairo_status_t status; cairo_surface_t *surface; int width, height; if (argc != 2) { g_printerr ("Usage: cairoshape OUTPUT_FILENAME\n"); return 1; } filename = argv[1]; setup_fontmap (); surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0); cr = cairo_create (surface); measure_text (cr, &width, &height); cairo_destroy (cr); cairo_surface_destroy (surface); /* Now create the final surface and draw to it. */ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); cr = cairo_create (surface); cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); cairo_paint (cr); cairo_set_source_rgb (cr, 0.0, 0.0, 0.5); draw_text (cr); cairo_destroy (cr); /* Write out the surface as PNG */ status = cairo_surface_write_to_png (surface, filename); cairo_surface_destroy (surface); if (status != CAIRO_STATUS_SUCCESS) { g_printerr ("Could not save png to '%s': %s\n", filename, cairo_status_to_string (status)); return 1; } return 0; } ``` Once you build and run the example code above, you should see the following result: ![Output of the example](bullets.png)