Search code examples
cfontscairofreetypetext-rendering

Font layouting & rendering with cairo and freetype


I have a system that has only the freetype2 and cairo libraries available. What I want to achieve is:

  • getting the glyphs for a UTF-8 text
  • layouting the text, storing position information (by myself)
  • getting cairo paths for each glyph for rendering

Unfortunately the documentation doesn't really explain how it should be done, as they expect one to use a higher level library like Pango.

What I think could be right is: Create a scaled font with cairo_scaled_font_create and then retrieve the glyphs for the text using cairo_scaled_font_text_to_glyphs. cairo_glyph_extents then gives the extents for each glyph. But how can I then get things like kerning and the advance? Also, how can I then get paths for each font?

Are there some more resources on this topic? Are these functions the expected way to go?


Solution

  • Okay, so I found what's needed.

    You first need to create a cairo_scaled_font_t which represents a font in a specific size. To do so, one can simply use cairo_get_scaled_font after setting a font, it creates a scaled font for the current settings in the context.

    Next, you convert the input text using cairo_scaled_font_text_to_glyphs, this gives an array of glyphs and also clusters as output. The cluster mappings represent which part of the UTF-8 string belong to the corresponding glyphs in the glyph array.

    To get the extents of glyphs, cairo_scaled_font_glyph_extents is used. It gives dimensions, advances and bearings of each glyph/set of glyphs.

    Finally, the paths for glyphs can be put in the context using cairo_glyph_path. These paths can then be drawn as wished.

    The following example converts an input string to glyphs, retrieves their extents and renders them:

    const char* text = "Hello world";
    int fontSize = 14;
    cairo_font_face_t* fontFace = ...;
    
    // get the scaled font object
    cairo_set_font_face(cr, fontFace);
    cairo_set_font_size(cr, fontSize);
    auto scaled_face = cairo_get_scaled_font(cr);
    
    // get glyphs for the text
    cairo_glyph_t* glyphs = NULL;
    int glyph_count;
    cairo_text_cluster_t* clusters = NULL;
    int cluster_count;
    cairo_text_cluster_flags_t clusterflags;
    
    auto stat = cairo_scaled_font_text_to_glyphs(scaled_face, 0, 0, text, strlen(text), &glyphs, &glyph_count, &clusters, &cluster_count,
            &clusterflags);
    
    // check if conversion was successful
    if (stat == CAIRO_STATUS_SUCCESS) {
    
        // text paints on bottom line
        cairo_translate(cr, 0, fontSize);
    
        // draw each cluster
        int glyph_index = 0;
        int byte_index = 0;
    
        for (int i = 0; i < cluster_count; i++) {
            cairo_text_cluster_t* cluster = &clusters[i];
            cairo_glyph_t* clusterglyphs = &glyphs[glyph_index];
    
            // get extents for the glyphs in the cluster
            cairo_text_extents_t extents;
            cairo_scaled_font_glyph_extents(scaled_face, clusterglyphs, cluster->num_glyphs, &extents);
            // ... for later use
    
            // put paths for current cluster to context
            cairo_glyph_path(cr, clusterglyphs, cluster->num_glyphs);
    
            // draw black text with green stroke
            cairo_set_source_rgba(cr, 0.2, 0.2, 0.2, 1.0);
            cairo_fill_preserve(cr);
            cairo_set_source_rgba(cr, 0, 1, 0, 1.0);
            cairo_set_line_width(cr, 0.5);
            cairo_stroke(cr);
    
            // glyph/byte position
            glyph_index += cluster->num_glyphs;
            byte_index += cluster->num_bytes;
        }
    }