c++textcairopangopangocairo

Create Pango Layout Before Cairo Surface


In my application, I am using Pango and Cairo to create text textures. These textures have their width fixed, but should scale their height to fit text contents. The parent objects involved in this situation will then scale their heights to match the text.

The problem is, the way I have been initializing Pango and Cairo does not allow for this. Currently, the system is set up by:

cairo_surface_t* cairoSurface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, sizeX, sizeY );
cairo_t* cairoContext = cairo_create( cairoSurface );
PangoLayout* pangoLayout = pango_cairo_create_layout( cairoContext );

Which fixes the height, at least of the surface - something I do not want to do, at least not all the time.

My understanding is that if the layout height is not specified, it will automatically scale the height, which can then be found via pango_layout_get_size(). I would like to create the layout first and then use the output of this function to create the surface.

However, pango_cairo_create_layout() requires the surface to already be created, and I have been unable to find a way to render a layout from pango_layout_new() via Cairo. The API docs one of the render functions, pango_cairo_update_layout(), specify that pango_cairo_create_layout() had to be used to create the layout; however, the more important function, pango_cairo_show_layout(), notes no such requirement, and I am not sure if that means that any Pango layout is allowed or not. While I could test if it works, I'm afraid that trial and error could lead me to undefined behavior.

I feel like I'm stuck in a chicken and egg situation, and the documentation for Pango is mostly an API reference with little explanation of how the library is intended to be used. Is there a way to do this properly?


Solution

  • I've figured this process out. Hopefully the information is helpful - although I make no claims to this being the "correct" way to do any of this, just that this works.

    First, set up FontConfig. This might not be necessary on some systems - it is probably OK to leave it automatic on Linux. On Windows, though, FontConfig is problematic. The easiest way to handle it is to create a config in memory and point it to where you want it to look for fonts. I pointed it to the resource directory of my program. You could use "C:\Windows\Fonts", but note this takes forever to load. Loading a proper font.conf file is probably the best approach, but I had little luck myself.

    gchar* workingDir = g_get_current_dir();
    gchar* resourceDir = g_strjoin( NULL, workingDir, "/Resources", (char*)0 );
    FcConfigAppFontAddDir( fontConfig, (const FcChar8*)resourceDir );
    g_free(workingDir);
    g_free(resourceDir);
    FcConfigBuildFonts( fontConfig );
    FcConfigSetCurrent( fontConfig );
    

    Then, you have to create a font map, Pango Context, and Pango Layout:

    PangoFontMap* fontMap = pango_cairo_font_map_new();
    PangoContext* pangoContext = pango_font_map_create_context( fontMap );
    PangoLayout* pangoLayout = pango_layout_new( pangoContext );
    

    Now, using a manually created (not from pango_cairo_create_layout()) layout doesn't seem to automatically load fonts. Trying to use a font that is in the font map but not loaded causes Pango-Cairo to crash (using one that is flat out not listed just goes to the default). As such, load all fonts listed in the font map:

    FcPattern *p = FcPatternCreate();
    FcObjectSet *os = FcObjectSetBuild(FC_FAMILY,NULL);
    FcFontSet *fs = FcFontList(fontConfig, p, os);
    FcPatternDestroy( p );
    FcObjectSetDestroy( os );
    for( int i = 0; i < fs->nfont; ++i )
    {
        guchar* fontName = FcNameUnparse( fs->fonts[i] );
        PangoFontDescription* fontDesc = pango_font_description_from_string( (gchar*)fontName );
        pango_font_map_load_font( fontMap, pangoContext, fontDesc );
        pango_font_description_free( fontDesc );
        g_free(fontName);
    }
    

    To specify the width:

    pango_layout_set_width( pangoLayout, sizeX * PANGO_SCALE );
    

    This is also around the point you should set justification/alignment/etc..

    Then you can insert your text:

    pango_layout_set_markup( pangoLayout, text.c_str( ), -1 );
    

    After this, you can get the layout height via pango_layout_get_pixel_size() and use that to create Cairo's objects. Then you can render it via:

    cairo_move_to(cairoContext, 0, 0);
    pango_cairo_update_layout( cairoContext, pangoLayout );
    pango_cairo_show_layout( cairoContext, pangoLayout );