© Jan Newmarch 2017

Jan Newmarch, Raspberry Pi GPU Audio Video Programming , 10.1007/978-1-4842-2472-4_18

18. Text Processing in OpenVG on the Raspberry Pi

Jan Newmarch

(1)Oakleigh, Victoria, Australia

Displaying text is an important function of any application. Drawing the text should be a low-level operation that the application should not be concerned with. It has to be done explicitly in OpenVG, and this chapter discusses various techniques of doing that.

Building Programs

The programs in this chapter require extra libraries in addition to the OpenVG libraries. These need to be installed with the following:

sudo apt-get install libcairo2-dev libpango1.0-dev

The FreeType 2 packages should already be installed, but if they are not, you will need to get the package freetype2.

You can then build the programs in this chapter using the following Makefile:

DMX_INC =  -I/opt/vc/include/ -I /opt/vc/include/interface/vmcs_host/ -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux
EGL_INC =
OPENVG_INC =
PANGO_CAIRO_INC = $(shell pkg-config --cflags pangocairo)
FREETYPE_INC = $(shell pkg-config --cflags freetype2)
INCLUDES = $(DMX_INC) $(EGL_INC) $(OPENVG_INC) $(PANGO_CAIRO_INC) $(FREETYPE_INC)


CFLAGS= $(INCLUDES)
CPPFLAGS =


DMX_LIBS =  -L/opt/vc/lib/ -lbcm_host -lvcos -lvchiq_arm -lpthread
EGL_LIBS = -L/opt/vc/lib/ -lEGL -lGLESv2
OPENVG_LIBS = -lOpenVG
PANGO_CAIRO_LIBS = $(shell pkg-config --libs pangocairo)
FREETYPE_LIBS = $(shell pkg-config --libs freetype2)
LDFLAGS =  $(DMX_LIBS) $(EGL_LIBS) $(OPENVG_LIBS)  $(PANGO_CAIRO_LIBS)
   $(FREETYPE_LIBS)


all: cairo  pango  text-bitmap  text-font  text-outline             

Drawing Text

Drawing text is an immensely difficult area. Even in English there are the following issues :

  • Letters such as g reach below the baseline and need to have space allocated for this.

  • Letter combinations such as ff are often rendered with a different (ligature) format such as ff.

  • Letter pairs such as AW are often rendered with less space between the characters to avoid an ugly space between the letters.

  • In cursive writing, the shapes of the letters may change to join up with the following or preceding letters.

Once you switch to other languages, the complexities multiply .

  • Some languages have right-to-left writing such as Arabic or (often) top to bottom in Chinese.

  • Languages such as Chinese have hieroglyphs typically taking twice the width of Latin languages.

  • Joining letters in Arabic is an extremely complex issue.

Displaying text involves a number of issues .

  • For each character, a glyph has to be chosen. A glyph is the visual representation of a character and can vary. In English, varieties can include bold, italic, cursive, and so on, and are often associated with fonts.

  • Glyphs have to be laid out on the screen using complex rules for spacing, direction, and so on.

OpenVG has the capability of finding fonts and laying them out at locations on the rendering surface. The rules for layout are not part of OpenVG. The simplest rule is just to lay them out one after another, but this will often get them wrong or not visually satisfying.

In this chapter, I will look at a variety of ways of drawing text using OpenVG, trading off ease of use versus complexity of programming and CPU usage on the RPi. Of necessity, this will involve using other libraries such as Cairo, Pango, and the freetype libraries.

Drawing Text Using Cairo

Cairo is a popular 2D graphics library that includes support for “toy” text and complex text processing. The toy library is suitable for simple text layout rules, particularly for Latin languages.

Cairo draws 2D shapes onto surfaces of various kinds. A good tutorial is at http://zetcode.com/gfx/cairo/ . Drawing involves creating a surface, drawing into it, and then getting the data out of the surface to display it or otherwise process it. The link with OpenVG from Cairo is provided by a Cairo function and an OpenVG function.

    unsigned char* data = cairo_image_surface_get_data(surface);

    vgWritePixels(data, tex_s,
                  VG_lBGRA_8888, left, 300, tex_w, tex_h );

The text parameters are from Cairo.

    int tex_w = cairo_image_surface_get_width(surface);
    int tex_h = cairo_image_surface_get_height(surface);
    int tex_s = cairo_image_surface_get_stride(surface);
    int left = 1920/2 - extents.width/2;

A program to display a set of lines progressively using the Cairo “toy” text interface is cairo.c.

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>


#include <assert.h>

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <VG/openvg.h>
#include <VG/vgu.h>


#include <bcm_host.h>

#include <cairo/cairo.h>

char *lines[] = {"hello", "world", "AWAKE"};
int num_lines = 3;


typedef struct
{
    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;
    EGLConfig config;
} EGL_STATE_T;


EGL_STATE_T state, *p_state = &state;

void init_egl(EGL_STATE_T *state)
{
    EGLint num_configs;
    EGLBoolean result;


    //bcm_host_init();

    static const EGLint attribute_list[] =
        {
            EGL_RED_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            EGL_ALPHA_SIZE, 8,
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            EGL_NONE
        };


    // get an EGL display connection
    state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);


    // initialize the EGL display connection
    result = eglInitialize(state->display, NULL, NULL);


    // get an appropriate EGL frame buffer configuration
    result = eglChooseConfig(state->display, attribute_list, &state->config, 1, &num_configs);
    assert(EGL_FALSE != result);


    // Choose the OpenVG API
    result = eglBindAPI(EGL_OPENVG_API);
    assert(EGL_FALSE != result);


    // create an EGL rendering context
    state->context = eglCreateContext(state->display,
                                      state->config, EGL_NO_CONTEXT,
                                      NULL);


    assert(state->context!=EGL_NO_CONTEXT);
}


void init_dispmanx(EGL_DISPMANX_WINDOW_T *nativewindow) {  
    int32_t success = 0;  
    uint32_t screen_width;
    uint32_t screen_height;


    DISPMANX_ELEMENT_HANDLE_T dispman_element; // | FT_LOAD_NO_SCALE);
    DISPMANX_DISPLAY_HANDLE_T dispman_display;
    DISPMANX_UPDATE_HANDLE_T dispman_update;
    VC_RECT_T dst_rect;
    VC_RECT_T src_rect;


    bcm_host_init();

    // create an EGL window surface
    success = graphics_get_display_size(0 /* LCD */,
                                        &screen_width,
                                        &screen_height);
    assert( success >= 0 );


    dst_rect.x = 0;
    dst_rect.y = 0;
    dst_rect.width = screen_width;
    dst_rect.height = screen_height;


    src_rect.x = 0;
    src_rect.y = 0;
    src_rect.width = screen_width << 16;
    src_rect.height = screen_height << 16;        


    dispman_display = vc_dispmanx_display_open( 0 /* LCD */);
    dispman_update = vc_dispmanx_update_start( 0 );


    dispman_element =
        vc_dispmanx_element_add(dispman_update, dispman_display,
                                0 /*layer*/, &dst_rect, 0 /*src*/,
                                &src_rect, DISPMANX_PROTECTION_NONE,
                                0 /*alpha*/, 0 /*clamp*/, 0 /*transform*/);


    // Build an EGL_DISPMANX_WINDOW_T from the Dispmanx window
    nativewindow->element = dispman_element;
    nativewindow->width = screen_width;
    nativewindow->height = screen_height;
    vc_dispmanx_update_submit_sync(dispman_update);


    printf("Got a Dispmanx window ");
}


void egl_from_dispmanx(EGL_STATE_T *state,
                       EGL_DISPMANX_WINDOW_T *nativewindow) {
    EGLBoolean result;


    state->surface = eglCreateWindowSurface(state->display,
                                            state->config,
                                            nativewindow, NULL );
    assert(state->surface != EGL_NO_SURFACE);


    // connect the context to the surface
    result = eglMakeCurrent(state->display, state->surface, state->surface, state->context);
    assert(EGL_FALSE != result);
}


void cairo(char *text) {
    int width = 500;
    int height = 200;


    cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
                                                           width, height);
    cairo_t *cr = cairo_create(surface);


    cairo_rectangle(cr, 0, 0, width, height);
    cairo_set_source_rgb(cr, 0.0, 0.0, 0.5);
    cairo_fill(cr);


    // draw some white text on top
    cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
    // this is a standard font for Cairo
    cairo_select_font_face (cr, "cairo:serif",
                            CAIRO_FONT_SLANT_NORMAL,
                            CAIRO_FONT_WEIGHT_BOLD);
    cairo_set_font_size (cr, 36);
    cairo_move_to(cr, 10.0, 50.0);


    cairo_scale(cr, 1.0f, -1.0f);
    cairo_translate(cr, 0.0f, -50);


    cairo_text_extents_t extents;
    cairo_text_extents(cr, text, &extents);


    cairo_show_text (cr, text);

    int tex_w = cairo_image_surface_get_width(surface);
    int tex_h = cairo_image_surface_get_height(surface);
    int tex_s = cairo_image_surface_get_stride(surface);
    unsigned char* data = cairo_image_surface_get_data(surface);


    int left = 1920/2 - extents.width/2;
    vgWritePixels(data, tex_s,
                  VG_lBGRA_8888, left, 300, tex_w, tex_h );
}


void draw() {
    EGL_DISPMANX_WINDOW_T nativewindow;


    init_egl(p_state);
    init_dispmanx(&nativewindow);


    egl_from_dispmanx(p_state, &nativewindow);

    int n = 0;
    while (1) {
        cairo(lines[n++]);
        n %= num_lines;


        eglSwapBuffers(p_state->display, p_state->surface);
        sleep(3);      
    }
}


int main(int argc, char** argv) {

    draw();

    exit(0);
}

Run the program with cairo.

This draws each line of text in white on a blue background, as shown here:

A435266_1_En_18_Figa_HTML.jpg
Figure a.

Drawing Text Using Pango

While Cairo can draw any form of text, the functions such as cairo_show_text do not have much flexibility. Drawing in, say, multiple colors will involve a lot of work. Pango is a library for handling all aspects of text. There is a Pango Reference Manual ( https://developer.gnome.org/pango/stable/ ), and you can find a good tutorial at www.ibm.com/developerworks/library/l-u-pango2/ .

The simplest way of coloring text (and some other effects) is to create the text marked up with HTML, as follows:

gchar *markup_text = "<span foreground="red">hello </span><span foreground="black">world</span>";

This has hello in red and world in black. This is then parsed into the text itself, red black, and a set of attribute markups.

gchar *markup_text = "<span foreground="red">hello </span><span foreground="black">world</span>";
PangoAttrList *attrs;
gchar *text;


pango_parse_markup (markup_text, -1,0, &attrs, &text, NULL, NULL);

This can be rendered into a Cairo context by creating a PangoLayout from the Cairo context, laying out the text with its attributes in the Pango layout, and then showing this layout in the Cairo context.

PangoLayout *layout;
PangoFontDescription *desc;
cairo_move_to(cr, 300.0, 50.0);
layout = pango_cairo_create_layout (cr);
pango_layout_set_text (layout, text, -1);
pango_layout_set_attributes(layout, attrs);
pango_cairo_update_layout (cr, layout);
pango_cairo_show_layout (cr, layout);

(Yes, there is a lot of jumping around between libraries in all of this!)

Once the Cairo surface has been drawn, the data can be extracted as in the Cairo example and written into the OpenVG surface. The program is pango.c and is as follows:

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>


#include <assert.h>

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <VG/openvg.h>
#include <VG/vgu.h>


#include <bcm_host.h>

#include <pango/pangocairo.h>

typedef struct
{
    EGLDisplay display;
    EGLSurface surface;
    EGLContext context;
    EGLConfig config;
} EGL_STATE_T;


EGL_STATE_T state, *p_state = &state;

void init_egl(EGL_STATE_T *state)
{
    EGLint num_configs;
    EGLBoolean result;


    //bcm_host_init();

    static const EGLint attribute_list[] =
        {
            EGL_RED_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            EGL_ALPHA_SIZE, 8,
            EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
            EGL_NONE
        };


    // get an EGL display connection
    state->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);


    // initialize the EGL display connection
    result = eglInitialize(state->display, NULL, NULL);


    // get an appropriate EGL frame buffer configuration
    result = eglChooseConfig(state->display, attribute_list, &state->config, 1, &num_configs);
    assert(EGL_FALSE != result);


    // Choose the OpenVG API
    result = eglBindAPI(EGL_OPENVG_API);
    assert(EGL_FALSE != result);


    // create an EGL rendering context
    state->context = eglCreateContext(state->display,
                                      state->config, EGL_NO_CONTEXT,
                                      NULL);


    assert(state->context!=EGL_NO_CONTEXT);
}


void init_dispmanx(EGL_DISPMANX_WINDOW_T *nativewindow) {  
    int32_t success = 0;  
    uint32_t screen_width;
    uint32_t screen_height;


    DISPMANX_ELEMENT_HANDLE_T dispman_element;
    DISPMANX_DISPLAY_HANDLE_T dispman_display;
    DISPMANX_UPDATE_HANDLE_T dispman_update;
    VC_RECT_T dst_rect;
    VC_RECT_T src_rect;


    bcm_host_init();

    // create an EGL window surface
    success = graphics_get_display_size(0 /* LCD */,
                                        &screen_width,
                                        &screen_height);
    assert( success >= 0 );


    dst_rect.x = 0;
    dst_rect.y = 0;
    dst_rect.width = screen_width;
    dst_rect.height = screen_height;


    src_rect.x = 0;
    src_rect.y = 0;
    src_rect.width = screen_width << 16;
    src_rect.height = screen_height << 16;        


    dispman_display = vc_dispmanx_display_open( 0 /* LCD */);
    dispman_update = vc_dispmanx_update_start( 0 );


    dispman_element =
        vc_dispmanx_element_add(dispman_update, dispman_display,
                                0/*layer*/, &dst_rect, 0/*src*/,
                                &src_rect, DISPMANX_PROTECTION_NONE,
                                0 /*alpha*/, 0/*clamp*/, 0/*transform*/);


    // Build an EGL_DISPMANX_WINDOW_T from the Dispmanx window
    nativewindow->element = dispman_element;
    nativewindow->width = screen_width;
    nativewindow->height = screen_height;
    vc_dispmanx_update_submit_sync(dispman_update);


    printf("Got a Dispmanx window ");
}


void egl_from_dispmanx(EGL_STATE_T *state,
                       EGL_DISPMANX_WINDOW_T *nativewindow) {
    EGLBoolean result;


    state->surface = eglCreateWindowSurface(state->display,
                                            state->config,
                                            nativewindow, NULL );
    assert(state->surface != EGL_NO_SURFACE);


    // connect the context to the surface
    result = eglMakeCurrent(state->display, state->surface, state->surface, state->context);
    assert(EGL_FALSE != result);
}


// setfill sets the fill color
void setfill(float color[4]) {
    VGPaint fillPaint = vgCreatePaint();
    vgSetParameteri(fillPaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
    vgSetParameterfv(fillPaint, VG_PAINT_COLOR, 4, color);
    vgSetPaint(fillPaint, VG_FILL_PATH);
    vgDestroyPaint(fillPaint);
}


// setstroke sets the stroke color and width
void setstroke(float color[4], float width) {
    VGPaint strokePaint = vgCreatePaint();
    vgSetParameteri(strokePaint, VG_PAINT_TYPE, VG_PAINT_TYPE_COLOR);
    vgSetParameterfv(strokePaint, VG_PAINT_COLOR, 4, color);
    vgSetPaint(strokePaint, VG_STROKE_PATH);
    vgSetf(VG_STROKE_LINE_WIDTH, width);
    vgSeti(VG_STROKE_CAP_STYLE, VG_CAP_BUTT);
    vgSeti(VG_STROKE_JOIN_STYLE, VG_JOIN_MITER);
    vgDestroyPaint(strokePaint);
}


// Ellipse makes an ellipse at the specified location and dimensions, applying style
void Ellipse(float x, float y, float w, float h, float sw, float fill[4], float stroke[4]) {
    VGPath path = vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);
    vguEllipse(path, x, y, w, h);
    setfill(fill);
    setstroke(stroke, sw);
    vgDrawPath(path, VG_FILL_PATH | VG_STROKE_PATH);
    vgDestroyPath(path);
}


void pango() {
    int width = 500;
    int height = 200;


    cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
                                                           width, height);
    cairo_t *cr = cairo_create(surface);


    cairo_rectangle(cr, 0, 0, width, height);
    cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
    cairo_fill(cr);


    // Pango marked up text, half red, half black
    gchar *markup_text = "<span foreground="red" font=’48’>hello</span>
<span foreground="black" font='48'>AWA犬犬</span>";
    PangoAttrList *attrs;
    gchar *text;


    pango_parse_markup (markup_text, -1, 0, &attrs, &text, NULL, NULL);

    // draw Pango text
    PangoLayout *layout;
    PangoFontDescription *desc;


    cairo_move_to(cr, 30.0, 150.0);
    cairo_scale(cr, 1.0f, -1.0f);
    layout = pango_cairo_create_layout (cr);
    pango_layout_set_text (layout, text, -1);
    pango_layout_set_attributes(layout, attrs);
    pango_cairo_update_layout (cr, layout);
    pango_cairo_show_layout (cr, layout);


    int tex_w = cairo_image_surface_get_width(surface);
    int tex_h = cairo_image_surface_get_height(surface);
    int tex_s = cairo_image_surface_get_stride(surface);
    cairo_surface_flush(surface);
    cairo_format_t type =
        cairo_image_surface_get_format (surface);
    printf("Format is (0 is ARGB32) %d ", type);
    unsigned char* data = cairo_image_surface_get_data(surface);


    PangoLayoutLine *layout_line =
        pango_layout_get_line(layout, 0);
    PangoRectangle ink_rect;
    PangoRectangle logical_rect;
    pango_layout_line_get_pixel_extents (layout_line,
                                         &ink_rect,
                                         &logical_rect);
    printf("Layout line rectangle is %d, %d, %d, %d ",
           ink_rect.x, ink_rect.y, ink_rect.width, ink_rect.height);


    int left = 1920/2 - ink_rect.width/2;
    vgWritePixels(data, tex_s,
                  //VG_lBGRA_8888,
                  VG_sARGB_8888 ,
                  left, 300, tex_w, tex_h );    
}


void draw()
{
    EGL_DISPMANX_WINDOW_T nativewindow;


    init_egl(p_state);
    init_dispmanx(&nativewindow);


    egl_from_dispmanx(p_state, &nativewindow);
    //draw();
    pango();


    eglSwapBuffers(p_state->display, p_state->surface);

    sleep(30);
    eglTerminate(p_state->display);


    exit(0);
}


int main(int argc, char** argv) {

    draw();

    exit(0);
}

The program is run with pango.

This draws some text in red and black onto a gray rectangle against a white backdrop, as shown here:

A435266_1_En_18_Figb_HTML.jpg
Figure b.

FreeType

The previous programs basically did all the rendering calculations using the RPi’s CPU and used the GPU only to render the resultant images. This is not using the GPU as you want: to manage as much as possible of the rendering calculations.

It is not possible to do everything on the GPU . It doesn’t know about Linux file systems, for example. But more important, it doesn’t know how or where information about text fonts and glyphs is stored. This information has to be built from an external library and massaged into a form suitable for the RPi’s GPU.

The most common library in use on Linux systems now seems to be the FreeType library ( www.freetype.org/ ). Here is the API reference : www.freetype.org/freetype2/docs/reference/ft2-basic_types.html#FT_Glyph_Format . There is also a tutorial at www.freetype.org/freetype2/docs/tutorial/index.html .

Font Faces

The highest-level object in FreeType is an FT_Library. There may be more than one of these, say, one for each thread in a multithreaded application. You will use only one. An FT_Face represents a single typeface in a particular style, such as Palatino Regular or Palatino Italic. An FT_Face is extracted from a font file that may contain many faces. Typically, face 0 is chosen. Once selected, a face may have characteristics such as the pixel size set.

The typical code to set up a face with a particular size is as follows:

void ft_init() {
    int err;


    err = FT_Init_FreeType(&ft_library);
    assert( !err);


    err = FT_New_Face(ft_library,
                      "/usr/share/ghostscript/9.05/Resource/CIDFSubst/DroidSansFallback.ttf",
                     0,
                     &ft_face );
    assert( !err);


    int font_size = 128;
    err = FT_Set_Pixel_Sizes(ft_face, 0, font_size);
    assert( !err);
}

Where do you get the font files from? The file type required is usually TrueType with the extension .ttf, and many of these are found in the directory /usr/share/fonts/truetype/. These all contain the ASCII character set, and their appearance can be seen using gnome-font-viewer. For example, the font /usr/share/fonts/truetype/freefont/FreeMonoBold.ttf looks like this:

A435266_1_En_18_Figc_HTML.jpg
Figure c.

I want to be able to see Chinese characters as well as ASCII, and I know that with all the packages I have installed, Pango is able to find a font file containing them. Repolho shows at https://repolinux.wordpress.com/2013/03/10/find-out-fallback-font-used-by-fontconfig-for-a-certain-character/ how to find a font file for any particular character. For example, for the Chinese character 好 (meaning “good”), the command is as follows:

FC_DEBUG=4 pango-view -t ’好’ 2>&1 |    
 grep -o ’family: "[^"]+’ |
 cut -c 10- | tail -n 1

This will display the character in a small box, and when you quit (q) the box, it will print the name of a font file. On my system, it printed Droid Sans Japanese. This is the name of the font but not of the file. By using the following, the file /usr/share/fonts/truetype/droid/DroidSansJapanese.ttf was identified:

locate DroidSansJapanese

(I had installed the package locate and ran updatedb to build the database for the locate command.)

This need not be the only containing this character but is the one used by Pango . (That directory also contains DroidSansFallbackFull.ttf, which would probably be better if I wanted to draw more characters.) It may be that there is no such font file on your system. In that case, download the files zysong.ttf or Cyberbit.ttf.

Paths and Glyphs

Once a face has been set up, you can hand characters to it and get an index for each character in the face using FT_Get_Char_Index(FT_Face face, FT_ULong charcode). Although the function name says Char, with Unicode characters these won’t be 8-bit C chars except for the ASCII subset. You will need the full 16- or 32-bit Unicode character, which is an FT_ULong to the FreeType system. So, for example, the Chinese character 好 needs to be represented by its Unicode value of 0x597d. Unicode values can be found in many places, such as scarfboy.com.

Once an index is found, the character can be loaded into the face by FT_Load_Glyph. There are two things you can do with a loaded glyph: get it back as an outline or convert it to a bitmap. In each case there are different things that can done with OpenVG, which are discussed in the following sections.

Extracting the outline of a glyph from a face is easy, as shown here:

 FT_Outline *outline = &ft_face->glyph->outline;

Getting a bitmap involves more steps: first you get the glyph as an FT_Glyph with FT_Get_Glyph. This is then converted to a bitmap with FT_Glyph_To_Bitmap. But a glyph has no field of a bitmap: it needs to be coerced into type FT_BitmapGlyph, and then the bitmap can be found.

    FT_Glyph glyph;
    FT_Get_Glyph(ft_face->glyph, &glyph);
    FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, NULL, 1);
    FT_BitmapGlyph bit_glyph = (FT_BitmapGlyph) glyph;
    FT_Bitmap bitmap = bit_glyph->bitmap;

There are operations that can be performed on a glyph before extracting the bitmap. For example, using an FT_Stroker, the corners can be smoothed out. The code to do that is as follows:

    FT_Glyph glyph;
    FT_Get_Glyph(ft_face->glyph, &glyph);


    // smooth the border
    FT_Stroker ft_stroker;
    FT_Stroker_New(ft_library, &ft_stroker);
    FT_Stroker_Set(ft_stroker,
                   200, // line_height_*border_thickness*64.0f,
                   FT_STROKER_LINECAP_ROUND,
                   FT_STROKER_LINEJOIN_ROUND,
                   0);
    FT_Glyph_StrokeBorder(&glyph, ft_stroker, 0, 1);


    // now get the smoothed bitmap
    FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, NULL, 1);
    FT_BitmapGlyph bit_glyph = (FT_BitmapGlyph) glyph;
    FT_Bitmap bitmap = bit_glyph->bitmap;

Drawing a FreeType Outline

The FT_Outline type from FreeType contains information about the outline of a glyph, but this is not the same form as used in an OpenVG path. It has to be converted from FreeType to OpenVG.

Functions to do this are in /opt/vc/src/hello_pi/libs/vgfont/vgft.c.

#define SEGMENTS_COUNT_MAX 256
#define COORDS_COUNT_MAX 1024


static VGuint segments_count;
static VGubyte segments[SEGMENTS_COUNT_MAX];
static VGuint coords_count;
static VGfloat coords[COORDS_COUNT_MAX];


static VGfloat float_from_26_6(FT_Pos x)
{
   return (VGfloat)x / 64.0f;
}


static void convert_contour(const FT_Vector *points,
                            const char *tags, short points_count)
{
   int first_coords = coords_count;


   int first = 1;
   char last_tag = 0;
   int c = 0;


   for (; points_count != 0; ++points, ++tags, --points_count) {
      ++c;


      char tag = *tags;
      if (first) {
         assert(tag & 0x1);
         assert(c==1); c=0;
         segments[segments_count++] = VG_MOVE_TO;
         first = 0;
      } else if (tag & 0x1) {
         /* on curve */


         if (last_tag & 0x1) {
            /* last point was also on -- line */
            assert(c==1); c=0;
            segments[segments_count++] = VG_LINE_TO;
         } else {
            /* last point was off -- quad or cubic */
            if (last_tag & 0x2) {
               /* cubic */
               assert(c==3); c=0;
               segments[segments_count++] = VG_CUBIC_TO;
            } else {
               /* quad */
               assert(c==2); c=0;
               segments[segments_count++] = VG_QUAD_TO;
            }
         }
      } else {
         /* off curve */


         if (tag & 0x2) {
            /* cubic */


            assert((last_tag & 0x1) || (last_tag & 0x2)); /* last either on or off and cubic */
         } else {
            /* quad */


            if (!(last_tag & 0x1)) {
               /* last was also off curve */


               assert(!(last_tag & 0x2)); /* must be quad */

               /* add on point half-way between */
               assert(c==2); c=1;
               segments[segments_count++] = VG_QUAD_TO;
               VGfloat x = (coords[coords_count - 2] + float_from_26_6(points->x)) * 0.5f;
               VGfloat y = (coords[coords_count - 1] + float_from_26_6(points->y)) * 0.5f;
               coords[coords_count++] = x;
               coords[coords_count++] = y;
            }
         }
      }
      last_tag = tag;


      coords[coords_count++] = float_from_26_6(points->x);
      coords[coords_count++] = float_from_26_6(points->y);
   }


   if (last_tag & 0x1) {
      /* last point was also on -- line (implicit with close path) */
      assert(c==0);
   } else {
      ++c;


      /* last point was off -- quad or cubic */
      if (last_tag & 0x2) {
         /* cubic */
         assert(c==3); c=0;
         segments[segments_count++] = VG_CUBIC_TO;
      } else {
         /* quad */
         assert(c==2); c=0;
         segments[segments_count++] = VG_QUAD_TO;
      }
      coords[coords_count++] = coords[first_coords + 0];
      coords[coords_count++] = coords[first_coords + 1];
   }


   segments[segments_count++] = VG_CLOSE_PATH;
}


static void convert_outline(const FT_Vector *points,
                            const char *tags, const short *contours,
                            short contours_count, short points_count)
{
   segments_count = 0;
   coords_count = 0;


   short last_contour = 0;
   for (; contours_count != 0; ++contours, --contours_count) {
      short contour = *contours + 1;
      convert_contour(points + last_contour, tags + last_contour, contour - last_contour);
      last_contour = contour;
   }
   assert(last_contour == points_count);


   assert(segments_count <= SEGMENTS_COUNT_MAX); /* oops... we overwrote some me
mory */
   assert(coords_count <= COORDS_COUNT_MAX);
}

These functions are used to set segments and coordinates suitable for adding to an OpenVG path created using vgCreatePath with vgAppendPath. The resultant path can then be drawn with vgDrawpath.

      FT_Outline *outline = &ft_face->glyph->outline;
      if (outline->n_contours != 0) {
         vg_path = vgCreatePath(VG_PATH_FORMAT_STANDARD,
                                VG_PATH_DATATYPE_F, 1.0f,
                                0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);
         assert(vg_path != VG_INVALID_HANDLE);


         convert_outline(outline->points, outline->tags,
                         outline->contours, outline->n_contours,
                         outline->n_points);
         vgAppendPathData(vg_path, segments_count, segments, coords);
      } else {
         vg_path = VG_INVALID_HANDLE;
      }


      VGfloat strokeColor[4] = {0.4, 0.1, 1.0, 1.0}; // purple
      VGfloat fillColor[4] = {1.0, 1.0, 0.0, 1.0}; // yellow


      setfill(fillColor);
      setstroke(strokeColor, 5);


      vgDrawPath(vg_path, VG_FILL_PATH | VG_STROKE_PATH);  

It looks like this:

A435266_1_En_18_Figd_HTML.jpg
Figure d.

The complete program is text-outline.c.

Drawing a FreeType Bitmap

In an earlier section, you derived an FT_Bitmap for a glyph. The bitmap contains fields for the stride (which it calls pitch), width, and height, plus a buffer of data. The format is grayscale with 256 levels. What this becomes in OpenVG I’m not sure. VG_sL_8 seems to work OK for me, while omxplayer uses VG_A_8.

The only real wrinkle is that the bitmap image is upside down in OpenVG coordinates. So, you can’t just use vgCreateImage because that draws the bitmap upside down. Instead, you have to add the data to a VGImage using vgImageSubData. The program omxplayer uses a neat trick to insert the data upside down. While vgImageSubData expects to load data from bottom to top, increasing the stride by one each time, you hand the data in from the top, decreasing the stride each time.

The image is drawn using vgSetPixels to set the location on the screen. The relevant code is as follows:

    FT_Glyph glyph;    
    FT_Get_Glyph(ft_face->glyph, &glyph);


    FT_Stroker ft_stroker;
    FT_Stroker_New(ft_library, &ft_stroker);
    FT_Stroker_Set(ft_stroker,
                   200, // line_height_*border_thickness*64.0f,
                   FT_STROKER_LINECAP_ROUND,
                   FT_STROKER_LINEJOIN_ROUND,
                   0);
    FT_Glyph_StrokeBorder(&glyph, ft_stroker, 0, 1);


    FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, NULL, 1);
    FT_BitmapGlyph bit_glyph = (FT_BitmapGlyph) glyph;
    FT_Bitmap bitmap = bit_glyph->bitmap;
    printf("Bitmap mode is %d (2 is gray) with %d levels ",
           bitmap.pixel_mode, bitmap.num_grays);


    int tex_s = bitmap.pitch;
    int tex_w = bitmap.width;
    int tex_h = bitmap.rows;


    VGImage image = vgCreateImage(VG_sL_8, tex_w, tex_h,
                                  VG_IMAGE_QUALITY_NONANTIALIASED);


    // invert bitmap image
    vgImageSubData(image,
                   bitmap.buffer + tex_s*(tex_h-1),
                   -tex_s,
                   VG_sL_8, //VG_A_8, VG_sL_8 seem to be okay too
                   0, 0, tex_w, tex_h);


    vgSetPixels(600, 600, image, 0, 0, tex_w, tex_h);

The complete program is text-bitmap.c, and it looks like this:

A435266_1_En_18_Fige_HTML.jpg
Figure e.

There is no border or color control.

VGFonts and Multiple Characters

If you want to draw multiple characters, you need to get multiple outline paths or bitmaps from FreeType and convert them. If characters are repeated or drawn multiple times, then this can lead to redundant processing. OpenVG has a type called VGFont, which can essentially act as a cache for the paths and bitmaps, so that you load each one into the font and then can draw them multiple times without having to repeat the loading process.

A font is created using vgCreateFont, which takes a parameter for the expected size or 0. Paths are added to a font with vgSetGlyphToPath, while bitmaps are added using vgSetGlyphToImage. These take as parameters the font, the index used for the glyph, the path or image, and two other parameters describing the geometry of the path/image. These are discussed next.

VGDrawGlyph

There is then a bonus function: instead of extracting the path or image from the font and then drawing it, the function vgDrawGlyph will perform the drawing for you, stroking or filling the path as required, as follows:

    vgDrawGlyph(font, ch,
                VG_STROKE_PATH | VG_FILL_PATH,
                VG_FALSE);

Where it draws the path/image depends on the value of the parameter VG_GLYPH_ORIGIN. This is initially set to an (x, y) location, as follows:

    VGfloat origin[2] = {300.0f, 300.0f};
    vgSetfv(VG_GLYPH_ORIGIN, 2, origin);

Each time a glyph is drawn, this location is updated by the width and height of the glyph so that successive glyphs are drawn next to each other.

A pseudo-code algorithm for drawing a string is as follows:

for each ch in the string
    if ch is not in the font
    then
        add the glyph to the font, using ch as index
    draw the glyph using ch as index

There is only one glitch in this: there is no way of telling whether a glyph is in the font! If you are just adding a small set of glyphs such as the ASCII characters, this is not a problem: just loop through them all adding each one. But if it is a large set, such as the 120,000+ Unicode characters, then you won’t want to do that. Instead, you would like to add them on an as-needed basis, say, as you loop through a piece of text.

To avoid repeated additions of a character, you need to keep a list outside of OpenVG. I used the hash table from GLib: add it to the hash table when first seen and add it the font, but do not add it if it’s already there. The code to add an outline glyph is as follows:

void add_glyph(int *ch) {
    if (g_hash_table_lookup(glyphs_seen, GINT_TO_POINTER(ch)) != NULL) {
        printf("Glyph %d already seen ", *ch);
        return;
    }
    g_hash_table_insert(glyphs_seen,
                        GINT_TO_POINTER(ch),
                        GINT_TO_POINTER(ch));
    printf("Inserting %d ", *ch);


    int glyph_index = FT_Get_Char_Index(ft_face, *ch);
    if (glyph_index == 0) printf("No glyph found ");
    FT_Load_Glyph(ft_face, glyph_index,
                  FT_LOAD_NO_HINTING| FT_LOAD_LINEAR_DESIGN);


    VGPath vg_path;

    FT_Outline *outline = &ft_face->glyph->outline;
    if (outline->n_contours != 0) {
        vg_path = vgCreatePath(VG_PATH_FORMAT_STANDARD,
                               VG_PATH_DATATYPE_F, 1.0f,
                               0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);
        assert(vg_path != VG_INVALID_HANDLE);


        convert_outline(outline->points, outline->tags,
                        outline->contours, outline->n_contours,
                        outline->n_points);
        vgAppendPathData(vg_path, segments_count, segments, coords);
    } else {
        vg_path = VG_INVALID_HANDLE;
    }


    VGfloat glyphOrigin[2] = {0.0f, 0.0f};
    printf("Width %d, height %d advance %d ",
           ft_face->glyph->metrics.width,
           ft_face->glyph->metrics.height,
           ft_face->glyph->linearHoriAdvance);
    VGfloat escapement[2] = {ft_face->glyph->linearHoriAdvance,
                             0.0f};


    vgSetGlyphToPath(font, *ch,
                     vg_path, 0,
                     glyphOrigin,
                     escapement);
}

You can then draw a set of glyphs as follows:

void draw() {
    EGL_DISPMANX_WINDOW_T nativewindow;
    glyphs_seen = g_hash_table_new(g_int_hash, g_int_equal);


    init_egl(p_state);
    init_dispmanx(&nativewindow);
 "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf",
    egl_from_dispmanx(p_state, &nativewindow);


    vgClear(0, 0, 1920, 1080);

    ft_init();

    font = vgCreateFont(0);
    //font_border = vgCreateFont(0);


    VGfloat strokeColor[4] = {0.4, 0.1, 1.0, 1.0}; // purple
    VGfloat fillColor[4] = {1.0, 1.0, 0.0, 1.0}; // yellowish


    setfill(fillColor);
    setstroke(strokeColor, 5);


    VGfloat origin[2] = {300.0f, 300.0f};
    vgSetfv(VG_GLYPH_ORIGIN, 2, origin);


    int ch;

    ch = 0x597d;
    add_glyph(&ch);
    vgDrawGlyph(font, ch,
                VG_STROKE_PATH,
                VG_FALSE);


    ch = ’A’;
    add_glyph(&ch);
    vgDrawGlyph(font, ch,
                VG_STROKE_PATH,
                VG_FALSE);


    ch = ’b’;
    add_glyph(&ch);
    vgDrawGlyph(font, ch,
                VG_STROKE_PATH | VG_FILL_PATH,
                VG_FALSE);
    ch = ’g’;
    add_glyph(&ch);
    vgDrawGlyph(font, ch,
                VG_STROKE_PATH | VG_FILL_PATH,
                VG_FALSE);
    ch = ’b’;    
    add_glyph(&ch);
    vgDrawGlyph(font, ch,
                VG_STROKE_PATH,
                VG_FALSE);


    vgFlush();

    eglSwapBuffers(p_state->display, p_state->surface);
}

with appearance

A435266_1_En_18_Figf_HTML.jpg
Figure f.

The complete program is text-font.c.

vgDrawGlyphs

In the previous example, you drew some glyphs filled and some in outline only. You could have made other changes too, such as different color fills. However, if all the characters are to be drawn using the same parameters, then the convenience function vgDrawGlyphs can be used.

Glyph Metrics

The geometry of a glyph is complex, and I have ignored it so far. For FreeType, the geometry is discussed in the tutorial “Managing Glyphs” at www.freetype.org/freetype2/docs/tutorial/step2.html . The metrics are accessible from the structure face->glyph->metrics and are as follows:

  • width: This is the width of the glyph image’s bounding box. It is independent of the layout direction.

  • height: This is the height of the glyph image’s bounding box. It is independent of the layout direction. Be careful not to confuse it with the height field in the FT_Size_Metrics structure.

  • horiBearingX: For horizontal text layouts, this is the horizontal distance from the current cursor position to the leftmost border of the glyph image’s bounding box.

  • horiBearingY: For horizontal text layouts, this is the vertical distance from the current cursor position (on the baseline) to the topmost border of the glyph image’s bounding box.

  • horiAdvance: For horizontal text layouts, this is the horizontal distance to increment the pen position when the glyph is drawn as part of a string of text.

  • vertBearingX: For vertical text layouts, this is the horizontal distance from the current cursor position to the leftmost border of the glyph image’s bounding box.

  • vertBearingY: For vertical text layouts, this is the vertical distance from the current cursor position (on the baseline) to the topmost border of the glyph image’s bounding box.

  • vertAdvance: For vertical text layouts, this is the vertical distance used to increment the pen position when the glyph is drawn as part of a string of text.

For horizontal layout, OpenVG does not seem to go into so much detail . The only diagram that is shown is as follows:

A435266_1_En_18_Figg_HTML.jpg
Figure g.
A435266_1_En_18_Figh_HTML.jpg
Figure h.

When adding a glyph to a font, only the glyph origin and the escapement are set, each as an (x, y) vector. The best values seem to be as follows:

    VGfloat glyphOrigin[2] = {0.0f, 0.0f};
    VGfloat escapement[2] = {ft_face->glyph->linearHoriAdvance,
                             0.0f};

Conclusion

This chapter has looked at a number of different ways of drawing text using OpenVG. There are trade-offs between ease of use and utilizing the GPU.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset