Complete sample: Rendering text with FreeType and OpenGL ES

This code sample uses Screen with the FreeType library and OpenGL ES for text rendering.



Figure 1. Hello World Application
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <ctype.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/keycodes.h>
#include <screen/screen.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <math.h>
#include <EGL/egl.h>
#include <GLES/gl.h>
#include "png.h"

static float width, height;
static GLuint background;
static GLfloat vertices[8];
static GLfloat tex_coord[8];
static float pos_x, pos_y;

struct font_t {
	unsigned int font_texture;
	float pt;
	float advance[128];
	float width[128];
	float height[128];
	float tex_x1[128];
	float tex_x2[128];
	float tex_y1[128];
	float tex_y2[128];
	float offset_x[128];
	float offset_y[128];
	int initialized;
};

typedef struct font_t font_t;

EGLDisplay egl_disp;
EGLSurface egl_surf;

static EGLConfig egl_conf;
static EGLContext egl_ctx;
static screen_window_t screen_win = NULL;
static screen_display_t screen_disp;
static int nbuffers = 2;
static int initialized = 0;
static font_t* font;

/* Utility function to calculate the dpi based on the display size */
int calculate_dpi() {
    int screen_phys_size[2] = { 0, 0 };

    screen_get_display_property_iv(screen_disp, SCREEN_PROPERTY_PHYSICAL_SIZE, screen_phys_size);

    /* If using a simulator, {0,0} is returned for physical size of the screen,
     so use 170 as the default dpi when this is the case. */
    if ((screen_phys_size[0] == 0) && (screen_phys_size[1] == 0)) {
        return 170;
    } else{
        int screen_resolution[2] = { 0, 0 };
        screen_get_display_property_iv(screen_disp, SCREEN_PROPERTY_SIZE, screen_resolution);

        int diagonal_pixels = sqrt(screen_resolution[0] * screen_resolution[0]
                                   + screen_resolution[1] * screen_resolution[1]);
        int diagonal_inches = 0.0393700787 * sqrt(screen_phys_size[0] * screen_phys_size[0]
                                                   + screen_phys_size[1] * screen_phys_size[1]);
        return (int)(diagonal_pixels / diagonal_inches);
    }
}

static inline int
nextp2(int x)
{
    int val = 1;
    while(val < x) val <<= 1;
    return val;
}


/* Utility function to load and ready the font for use by OpenGL */
font_t* load_font(const char* path, int point_size, int dpi) {
    FT_Library library;
    FT_Face face;
    int c;
    int i, j;
    font_t* font;

    if (!initialized) {
        fprintf(stderr, "EGL has not been initialized\n");
        return NULL;
    }

    if (!path){
        fprintf(stderr, "Invalid path to font file\n");
        return NULL;
    }

    if(FT_Init_FreeType(&library)) {
        fprintf(stderr, "Error loading Freetype library\n");
        return NULL;
    }
    if (FT_New_Face(library, path,0,&face)) {
        fprintf(stderr, "Error loading font %s\n", path);
        return NULL;
    }

    if(FT_Set_Char_Size ( face, point_size * 64, point_size * 64, dpi, dpi)) {
        fprintf(stderr, "Error initializing character parameters\n");
        return NULL;
    }

    font = (font_t*) malloc(sizeof(font_t));
    font->initialized = 0;

    glGenTextures(1, &(font->font_texture));

    /*Let each glyph reside in 32x32 section of the font texture */
    int segment_size_x = 0, segment_size_y = 0;
    int num_segments_x = 16;
    int num_segments_y = 8;

    FT_GlyphSlot slot;
    FT_Bitmap bmp;
    int glyph_width, glyph_height;

    /*First calculate the max width and height of a character in a passed font*/
    for(c = 0; c < 128; c++) {
        if(FT_Load_Char(face, c, FT_LOAD_RENDER)) {
            fprintf(stderr, "FT_Load_Char failed\n");
            free(font);
            return NULL;
        }

        slot = face->glyph;
        bmp = slot->bitmap;

        glyph_width = bmp.width;
        glyph_height = bmp.rows;

        if (glyph_width > segment_size_x) {
            segment_size_x = glyph_width;
        }

        if (glyph_height > segment_size_y) {
            segment_size_y = glyph_height;
        }
    }

    int font_tex_width = nextp2(num_segments_x * segment_size_x);
    int font_tex_height = nextp2(num_segments_y * segment_size_y);

    int bitmap_offset_x = 0, bitmap_offset_y = 0;

    GLubyte* font_texture_data = (GLubyte*) malloc(sizeof(GLubyte) * 2 * font_tex_width * font_tex_height);
    memset((void*)font_texture_data, 0, sizeof(GLubyte) * 2 * font_tex_width * font_tex_height);

    if (!font_texture_data) {
        fprintf(stderr, "Failed to allocate memory for font texture\n");
        free(font);
        return NULL;
    }

    /* Fill font texture bitmap with individual bmp data and record appropriate size,
     texture coordinates and offsets for every glyph */
    for(c = 0; c < 128; c++) {
        if(FT_Load_Char(face, c, FT_LOAD_RENDER)) {
            fprintf(stderr, "FT_Load_Char failed\n");
            free(font);
            return NULL;
        }

        slot = face->glyph;
        bmp = slot->bitmap;

        glyph_width = nextp2(bmp.width);
        glyph_height = nextp2(bmp.rows);

        div_t temp = div(c, num_segments_x);

        bitmap_offset_x = segment_size_x * temp.rem;
        bitmap_offset_y = segment_size_y * temp.quot;

        for (j = 0; j < glyph_height; j++) {
            for (i = 0; i < glyph_width; i++) {
                font_texture_data[2 * ((bitmap_offset_x + i) + (j + bitmap_offset_y) * font_tex_width) + 0] =
                font_texture_data[2 * ((bitmap_offset_x + i) + (j + bitmap_offset_y) * font_tex_width) + 1] =
                    (i >= bmp.width || j >= bmp.rows)? 0 : bmp.buffer[i + bmp.width * j];
            }
        }

        font->advance[c] = (float)(slot->advance.x >> 6);
        font->tex_x1[c] = (float)bitmap_offset_x / (float) font_tex_width;
        font->tex_x2[c] = (float)(bitmap_offset_x + bmp.width) / (float)font_tex_width;
        font->tex_y1[c] = (float)bitmap_offset_y / (float) font_tex_height;
        font->tex_y2[c] = (float)(bitmap_offset_y + bmp.rows) / (float)font_tex_height;
        font->width[c] = bmp.width;
        font->height[c] = bmp.rows;
        font->offset_x[c] = (float)slot->bitmap_left;
        font->offset_y[c] =  (float)((slot->metrics.horiBearingY-face->glyph->metrics.height) >> 6);
    }

    glBindTexture(GL_TEXTURE_2D, font->font_texture);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, font_tex_width, font_tex_height, 0, GL_LUMINANCE_ALPHA , GL_UNSIGNED_BYTE, font_texture_data);

    int err = glGetError();

    free(font_texture_data);

    FT_Done_Face(face);
    FT_Done_FreeType(library);

    if (err != 0) {
        fprintf(stderr, "GL Error 0x%x", err);
        free(font);
        return NULL;
    }

    font->initialized = 1;
    return font;
}

/* Utility function to load background texture */
int load_texture(const char* filename, int* width, int* height, float* tex_x, float* tex_y, unsigned int *tex) {
    int i;
    GLuint format;
    png_byte header[8];	/* header for testing if it is a png */

    if (!tex) {
        return EXIT_FAILURE;
    }

    /* Open file as binary */
    FILE *fp = fopen(filename, "rb");
    if (!fp) {
        return EXIT_FAILURE;
    }

    /* Read the header */
    fread(header, 1, 8, fp);

    /* Test if png */
    int is_png = !png_sig_cmp(header, 0, 8);
    if (!is_png) {
        fclose(fp);
        return EXIT_FAILURE;
    }

    /* Create png struct */
    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr) {
        fclose(fp);
        return EXIT_FAILURE;
    }

    /* Create png info struct */
    png_infop info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
        png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL);
        fclose(fp);
        return EXIT_FAILURE;
    }

    /* Create png info struct */
    png_infop end_info = png_create_info_struct(png_ptr);
    if (!end_info) {
        png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
        fclose(fp);
        return EXIT_FAILURE;
    }

    /* Set up error handling (required without using custom error handlers above) */
    if (setjmp(png_jmpbuf(png_ptr))) {
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        fclose(fp);
        return EXIT_FAILURE;
    }

    /* Initialize png reading */
    png_init_io(png_ptr, fp);

    /* Let libpng know you already read the first 8 bytes */
    png_set_sig_bytes(png_ptr, 8);

    /* Read all the info up to the image data */
    png_read_info(png_ptr, info_ptr);

    /* Variables to pass to get info */
    int bit_depth, color_type;
    png_uint_32 image_width, image_height;

    /* Get info about png */
    png_get_IHDR(png_ptr, info_ptr, &image_width, &image_height, &bit_depth, &color_type, NULL, NULL, NULL);

    switch (color_type)
    {
        case PNG_COLOR_TYPE_RGBA:
            format = GL_RGBA;
            break;
        case PNG_COLOR_TYPE_RGB:
            format = GL_RGB;
            break;
        default:
            fprintf(stderr,"Unsupported PNG color type (%d) for texture: %s", (int)color_type, filename);
            fclose(fp);
            png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
            return NULL;
    }

    /* Update the png info struct. */
    png_read_update_info(png_ptr, info_ptr);

    /* Row size in bytes. */
    int rowbytes = png_get_rowbytes(png_ptr, info_ptr);

    /* Allocate the image_data as a big block, to be given to opengl */
    png_byte *image_data = (png_byte*) malloc(sizeof(png_byte) * rowbytes * image_height);

    if (!image_data) {
        /* clean up memory and close file */
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        fclose(fp);
        return EXIT_FAILURE;
    }

    /* Row_pointers is for pointing to image_data for reading the png with libpng */
    png_bytep *row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * image_height);
    if (!row_pointers) {
        /* clean up memory and close stuff */
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        free(image_data);
        fclose(fp);
        return EXIT_FAILURE;
    }

    /* Set the individual row_pointers to point at the correct offsets of image_data */
    for (i = 0; i < image_height; i++) {
        row_pointers[image_height - 1 - i] = image_data + i * rowbytes;
    }

    /* Read the png into image_data through row_pointers */
    png_read_image(png_ptr, row_pointers);

    int tex_width, tex_height;

    tex_width = nextp2(image_width);
    tex_height = nextp2(image_height);

    glGenTextures(1, tex);
    glBindTexture(GL_TEXTURE_2D, (*tex));
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    glTexImage2D(GL_TEXTURE_2D, 0, format, tex_width, tex_height, 0, format, GL_UNSIGNED_BYTE, NULL);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image_width, image_height, format, GL_UNSIGNED_BYTE, image_data);

    GLint err = glGetError();

    /* Clean up memory and close file */
    png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
    free(image_data);
    free(row_pointers);
    fclose(fp);

    if (err == 0) {
        /* Return physical with and height of texture if pointers are not null */
        if(width) {
            *width = image_width;
        }
        if (height) {
            *height = image_height;
        }
        /* Return modified texture coordinates if pointers are not null */
        if(tex_x) {
            *tex_x = ((float) image_width - 0.5f) / ((float)tex_width);
        }
        if(tex_y) {
            *tex_y = ((float) image_height - 0.5f) / ((float)tex_height);
        }
        return EXIT_SUCCESS;
    } else {
        fprintf(stderr, "GL error %i \n", err);
        return EXIT_FAILURE;
    }
}

/* Utility function to perform EGL cleanup */
void egl_cleanup() {
    /* Typical EGL cleanup */
    if (egl_disp != EGL_NO_DISPLAY) {
        eglMakeCurrent(egl_disp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        if (egl_surf != EGL_NO_SURFACE) {
            eglDestroySurface(egl_disp, egl_surf);
            egl_surf = EGL_NO_SURFACE;
        }
        if (egl_ctx != EGL_NO_CONTEXT) {
            eglDestroyContext(egl_disp, egl_ctx);
            egl_ctx = EGL_NO_CONTEXT;
        }
        if (screen_win != NULL) {
            screen_destroy_window(screen_win);
            screen_win = NULL;
        }
        eglTerminate(egl_disp);
        egl_disp = EGL_NO_DISPLAY;
    }
    eglReleaseThread();

    initialized = 0;
}

/* Utility function to initialize and configure a EGL rendering surface */
int init_egl(screen_context_t screen_ctx) {
    int usage;
    int format = SCREEN_FORMAT_RGBX8888;
    EGLint interval = 1;
    int rc, num_configs;

    EGLint attrib_list[]= { EGL_RED_SIZE,        8,
                            EGL_GREEN_SIZE,      8,
                            EGL_BLUE_SIZE,       8,
                            EGL_BLUE_SIZE,       8,
                            EGL_SURFACE_TYPE,    EGL_WINDOW_BIT,
                            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT,
                            EGL_NONE};

    /* Assuming GL_ES_1 */
    usage = SCREEN_USAGE_OPENGL_ES1 | SCREEN_USAGE_ROTATION;

    /* Establish a connection to the default display */
    egl_disp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    if (egl_disp == EGL_NO_DISPLAY) {
    	egl_cleanup();
        return EXIT_FAILURE;
    }

    /* Initialize EGL on the display */
    rc = eglInitialize(egl_disp, NULL, NULL);
    if (rc != EGL_TRUE) {
    	egl_cleanup();
        return EXIT_FAILURE;
    }

    /* Calling eglBindAPI() to specify the current rendering API is not necessary
     * because OpenGL ES is the default rendering API.
    rc = eglBindAPI(EGL_OPENGL_ES_API);

    if (rc != EGL_TRUE) {
    	egl_cleanup();
        return EXIT_FAILURE;
    }*/

    if(!eglChooseConfig(egl_disp, attrib_list, &egl_conf, 1, &num_configs)) {
    	egl_cleanup();
        return EXIT_FAILURE;
    }

    egl_ctx = eglCreateContext(egl_disp, egl_conf, EGL_NO_CONTEXT, NULL);

    if (egl_ctx == EGL_NO_CONTEXT) {
    	egl_cleanup();
        return EXIT_FAILURE;
    }

    rc = screen_create_window(&screen_win, screen_ctx);
    if (rc) {
        perror("screen_create_window");
        egl_cleanup();
        return EXIT_FAILURE;
    }

    rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_FORMAT, &format);
    if (rc) {
        perror("screen_set_window_property_iv(SCREEN_PROPERTY_FORMAT)");
        egl_cleanup();
        return EXIT_FAILURE;
    }

    rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_USAGE, &usage);
    if (rc) {
        perror("screen_set_window_property_iv(SCREEN_PROPERTY_USAGE)");
        egl_cleanup();
        return EXIT_FAILURE;
    }

    rc = screen_get_window_property_pv(screen_win, SCREEN_PROPERTY_DISPLAY, (void **)&screen_disp);
    if (rc) {
        perror("screen_get_window_property_pv");
        egl_cleanup();
        return EXIT_FAILURE;
    }

    int angle = atoi(getenv("ORIENTATION"));

	screen_display_mode_t screen_mode;
	rc = screen_get_display_property_pv(screen_disp, SCREEN_PROPERTY_MODE, (void**)&screen_mode);
	if (rc) {
		perror("screen_get_display_property_pv");
		egl_cleanup();
		return EXIT_FAILURE;
	}

	int size[2];
	rc = screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, size);
	if (rc) {
		perror("screen_get_window_property_iv");
		egl_cleanup();
		return EXIT_FAILURE;
	}

	int buffer_size[2] = {size[0], size[1]};

	if ((angle == 0) || (angle == 180)) {
		if (((screen_mode.width > screen_mode.height) && (size[0] < size[1])) ||
			((screen_mode.width < screen_mode.height) && (size[0] > size[1]))) {
				buffer_size[1] = size[0];
				buffer_size[0] = size[1];
		}
	} else if ((angle == 90) || (angle == 270)){
		if (((screen_mode.width > screen_mode.height) && (size[0] > size[1])) ||
			((screen_mode.width < screen_mode.height && size[0] < size[1]))) {
				buffer_size[1] = size[0];
				buffer_size[0] = size[1];
		}
	} else {
		 fprintf(stderr, "Navigator returned an unexpected orientation angle.\n");
		 egl_cleanup();
		 return EXIT_FAILURE;
	}

	rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_BUFFER_SIZE, buffer_size);
    if (rc) {
        perror("screen_set_window_property_iv");
        egl_cleanup();
        return EXIT_FAILURE;
    }

    rc = screen_set_window_property_iv(screen_win, SCREEN_PROPERTY_ROTATION, &angle);
    if (rc) {
        perror("screen_set_window_property_iv");
        egl_cleanup();
        return EXIT_FAILURE;
    }

    rc = screen_create_window_buffers(screen_win, nbuffers);
    if (rc) {
        perror("screen_create_window_buffers");
        egl_cleanup();
        return EXIT_FAILURE;
    }

    egl_surf = eglCreateWindowSurface(egl_disp, egl_conf, screen_win, NULL);
    if (egl_surf == EGL_NO_SURFACE) {
        egl_cleanup();
        return EXIT_FAILURE;
    }

    rc = eglMakeCurrent(egl_disp, egl_surf, egl_surf, egl_ctx);
    if (rc != EGL_TRUE) {
        egl_cleanup();
        return EXIT_FAILURE;
    }

    rc = eglSwapInterval(egl_disp, interval);
    if (rc != EGL_TRUE) {
        egl_cleanup();
        return EXIT_FAILURE;
    }

    initialized = 1;

    return EXIT_SUCCESS;
}

void measure_text(font_t* font, const char* msg, float* width, float* height) {
    int i, c;

    if (!msg) {
        return;
    }

    if (width) {
        /* Width of a text rectangle is a sum advances for every glyph in a string */
        *width = 0.0f;

        for(i = 0; i < strlen(msg); ++i) {
            c = msg[i];
            *width += font->advance[c];
        }
    }

    if (height) {
        /* Height of a text rectangle is a high of a tallest glyph in a string */
        *height = 0.0f;

        for(i = 0; i < strlen(msg); ++i) {
            c = msg[i];

            if (*height < font->height[c]) {
                *height = font->height[c];
            }
        }
    }
}

int init() {
	EGLint surface_width, surface_height;

	/* We are going to load MyriadPro-Bold as it looks a little better and scale it to
	   fit out bubble nicely. */
	int dpi = calculate_dpi();

	/*font = load_font(
			"/usr/fonts/font_repository/adobe/MyriadPro-Bold.otf", 15, dpi); */
	font = load_font(
			"/usr/fonts/font_repository/monotype/georgiab.ttf", 8, dpi);
	if (!font) {
		return EXIT_FAILURE;
	}

	/* Load background texture */
	float tex_x, tex_y;
	if (EXIT_SUCCESS
			!= load_texture("app/native/HelloWorld_smaller_bubble.png",
					NULL, NULL, &tex_x, &tex_y, &background)) {
		fprintf(stderr, "Unable to load background texture\n");
	}

	/* Query width and height of the window surface created by utility code */
	eglQuerySurface(egl_disp, egl_surf, EGL_WIDTH, &surface_width);
	eglQuerySurface(egl_disp, egl_surf, EGL_HEIGHT, &surface_height);

	EGLint err = eglGetError();
	if (err != 0x3000) {
		fprintf(stderr, "Unable to query EGL surface dimensions\n");
		return EXIT_FAILURE;
	}

	width = (float) surface_width;
	height = (float) surface_height;

	/*	Initialize GL for 2D rendering */
	glViewport(0, 0, (int) width, (int) height);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	glOrthof(0.0f, width / height, 0.0f, 1.0f, -1.0f, 1.0f);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	/* Set world coordinates to coincide with screen pixels */
	glScalef(1.0f / height, 1.0f / height, 1.0f);

	float text_width, text_height;
	measure_text(font, "Hello world", &text_width, &text_height);
	pos_x = (width - text_width) / 2;
	pos_y = height / 2;

	/* Setup background polygon */
	vertices[0] = 0.0f;
	vertices[1] = 0.0f;
	vertices[2] = width;
	vertices[3] = 0.0f;
	vertices[4] = 0.0f;
	vertices[5] = height;
	vertices[6] = width;
	vertices[7] = height;

	tex_coord[0] = 0.0f;
	tex_coord[1] = 0.0f;
	tex_coord[2] = tex_x;
	tex_coord[3] = 0.0f;
	tex_coord[4] = 0.0f;
	tex_coord[5] = tex_y;
	tex_coord[6] = tex_x;
	tex_coord[7] = tex_y;

	return EXIT_SUCCESS;
}

void render_text(font_t* font, const char* msg, float x, float y) {
    int i, c;
    GLfloat *vertices;
    GLfloat *texture_coords;
    GLshort* indices;

    float pen_x = 0.0f;

    if (!font) {
        fprintf(stderr, "Font must not be null\n");
        return;
    }

    if (!font->initialized) {
        fprintf(stderr, "Font has not been loaded\n");
        return;
    }

    if (!msg) {
        return;
    }

    int texture_enabled;
    glGetIntegerv(GL_TEXTURE_2D, &texture_enabled);
    if (!texture_enabled) {
        glEnable(GL_TEXTURE_2D);
    }

    int blend_enabled;
    glGetIntegerv(GL_BLEND, &blend_enabled);
    if (!blend_enabled) {
        glEnable(GL_BLEND);
    }

    int gl_blend_src, gl_blend_dst;
    glGetIntegerv(GL_BLEND_SRC, &gl_blend_src);
    glGetIntegerv(GL_BLEND_DST, &gl_blend_dst);

    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

    int vertex_array_enabled;
    glGetIntegerv(GL_VERTEX_ARRAY, &vertex_array_enabled);
    if (!vertex_array_enabled) {
        glEnableClientState(GL_VERTEX_ARRAY);
    }

    int texture_array_enabled;
    glGetIntegerv(GL_TEXTURE_COORD_ARRAY, &texture_array_enabled);
    if (!texture_array_enabled) {
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    }

    vertices = (GLfloat*) malloc(sizeof(GLfloat) * 8 * strlen(msg));
    texture_coords = (GLfloat*) malloc(sizeof(GLfloat) * 8 * strlen(msg));

    indices = (GLshort*) malloc(sizeof(GLfloat) * 5 * strlen(msg));

    for(i = 0; i < strlen(msg); ++i) {
        c = msg[i];

        vertices[8 * i + 0] = x + pen_x + font->offset_x[c];
        vertices[8 * i + 1] = y + font->offset_y[c];
        vertices[8 * i + 2] = vertices[8 * i + 0] + font->width[c];
        vertices[8 * i + 3] = vertices[8 * i + 1];
        vertices[8 * i + 4] = vertices[8 * i + 0];
        vertices[8 * i + 5] = vertices[8 * i + 1] + font->height[c];
        vertices[8 * i + 6] = vertices[8 * i + 2];
        vertices[8 * i + 7] = vertices[8 * i + 5];

        texture_coords[8 * i + 0] = font->tex_x1[c];
        texture_coords[8 * i + 1] = font->tex_y2[c];
        texture_coords[8 * i + 2] = font->tex_x2[c];
        texture_coords[8 * i + 3] = font->tex_y2[c];
        texture_coords[8 * i + 4] = font->tex_x1[c];
        texture_coords[8 * i + 5] = font->tex_y1[c];
        texture_coords[8 * i + 6] = font->tex_x2[c];
        texture_coords[8 * i + 7] = font->tex_y1[c];

        indices[i * 6 + 0] = 4 * i + 0;
        indices[i * 6 + 1] = 4 * i + 1;
        indices[i * 6 + 2] = 4 * i + 2;
        indices[i * 6 + 3] = 4 * i + 2;
        indices[i * 6 + 4] = 4 * i + 1;
        indices[i * 6 + 5] = 4 * i + 3;

        /* Assume we are only working with typewriter fonts */
        pen_x += font->advance[c];
    }

    glVertexPointer(2, GL_FLOAT, 0, vertices);
    glTexCoordPointer(2, GL_FLOAT, 0, texture_coords);
    glBindTexture(GL_TEXTURE_2D, font->font_texture);

    glDrawElements(GL_TRIANGLES, 6 * strlen(msg), GL_UNSIGNED_SHORT, indices);

    if (!texture_array_enabled) {
        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    }

    if (!vertex_array_enabled) {
        glDisableClientState(GL_VERTEX_ARRAY);
    }

    if (!texture_enabled) {
        glDisable(GL_TEXTURE_2D);
    }

    glBlendFunc(gl_blend_src, gl_blend_dst);

    if (!blend_enabled) {
        glDisable(GL_BLEND);
    }

    free(vertices);
    free(texture_coords);
    free(indices);
}

int render() {
	/* Typical rendering pass */
	glClear(GL_COLOR_BUFFER_BIT);

	/* Render background quad first */
	glEnable(GL_TEXTURE_2D);

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	glVertexPointer(2, GL_FLOAT, 0, vertices);
	glTexCoordPointer(2, GL_FLOAT, 0, tex_coord);
	glBindTexture(GL_TEXTURE_2D, background);

	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisable(GL_TEXTURE_2D);

	/* Set color to use for text rendering */
	glColor4f(0.35f, 0.35f, 0.35f, 1.0f);

	/* Render the text onto the screen */
	render_text(font, "Hello world", pos_x, pos_y);

	/* Update the screen; Posting of the new frame requires a call to eglSwapBuffers.
	 * For now, this is true even when using single buffering. If an
	 * event has occured that invalidates the surface we are currently
	 * using, eglSwapBuffers() will return EGL_FALSE and set the error
	 * code to EGL_BAD_NATIVE_WINDOW.
	 */
	int rc = eglSwapBuffers(egl_disp, egl_surf);

	return rc;

}

int main(int argc, char **argv) {
	int rc;							/* a return code */
    screen_event_t screen_ev;		/* a screen event to handle */
    screen_context_t screen_ctx;	/* a connection to the screen windowing system */
	int vis = 1;					/* an indicator if our window is visible */
	int pause = 0;					/* an indicator if rendering is frozen */

	const int exit_area_size = 20;	/* a size of area on the window where a user can
									 * contact (using a pointer) to exit this application */

	int size[2] = { 0, 0 };       	/* the width and height of your window; will default to
									 * the size of display since the window property wasn't
								     * explicitly set */

	int val;						/* a variable used to set/get window properties */
	int pos[2] = { 0, 0 };          /* the x,y position of your pointer */

	/*Create a screen context that will be the connection to the windowing system;
	 *this is used to create an EGL surface to receive libscreen events */
	screen_create_context(&screen_ctx, 0);

	/* Initialize EGL for rendering with GL ES 1.1;
	 * this initialization includes initializing and configuring EGL as well as creating
	 * a native window with the appropriate properties to be used as the EGL rendering
	 * surface. */
	if (EXIT_SUCCESS != init_egl(screen_ctx)) {
		fprintf(stderr, "Unable to initialize EGL\n");
		screen_destroy_context(screen_ctx);
		return 0;
	}

	/* Initialize application data;
	 * this initialization includes loading the font and background and initializing
	 * the viewport and geometry for your application. */
	if (EXIT_SUCCESS != init()) {
		fprintf(stderr, "Unable to initialize app logic\n");
		egl_cleanup();
		screen_destroy_context(screen_ctx);
		return 0;
	}

	/* Create a screen event that will be used to retrieve events into so that these
	 * events can be handled.*/
	rc = screen_create_event(&screen_ev);
	if (rc) {
		fprintf(stderr, "screen_create_event\n");
		egl_cleanup();
		screen_destroy_context(screen_ctx);
		return 0;
	}

	/* This is your main application loop. It keeps on running unless a close event is
	 * received from the windowing system or an error occurs. The application loop consists
	 * of two parts. The first part processes any events that have been put in your queue.
	 * The second part does the rendering. When the window is visible, you don't wait if
	 * the event queue is empty; you move on to the rendering part immediately.
	 * When the window is not visible we skip the rendering part. */
	while (1){
		/* The first part of the loop is to handle screen events.
		 * We start the loop by processing any events that might be in our
		 * queue. The only event that is of interest to us are the resize
		 * and close events. The timeout variable is set to 0 (no wait) or
		 * forever depending if the window is visible or not. */
		while (!screen_get_event(screen_ctx, screen_ev, vis ? 0 : ~0))
		{
			rc = screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_TYPE, &val);
			if (rc || val == SCREEN_EVENT_NONE)
			{
				break;
			}
			switch (val) {
				case SCREEN_EVENT_CLOSE:
					 /* All we have to do when we receive the close event is
					  * to exit the application loop. Because we have a loop
					  * within a loop, a simple break won't work. We'll just
					  * use a goto to take us out of here.*/
					goto end;
				case SCREEN_EVENT_PROPERTY:
					 /* We are interested in visibility changes so we can pause
					  * or unpause the rendering. */
					screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_NAME, &val);
					switch (val) {
						case SCREEN_PROPERTY_VISIBLE:
							 /* The new visibility status is not included in the
							  * event, so we must get it ourselves. */
							screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_VISIBLE, &vis);
							break;
					}
					break;
				case SCREEN_EVENT_POINTER:
					 /* To provide a way of gracefully terminating our application,
					  * we will exit if there is a pointer select event in the upper
					  * right corner of our window. This should happen if the mouse's
					  * left button is clicked or if a touch screen display is pressed.
					  * The event will come as a screen pointer event, with an (x,y)
					  * coordinate relative to the window's upper left corner and a
					  * select value. We have to verify ourselves that the coordinates
					  * of the pointer are in the upper right hand area. */
					screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_BUTTONS, &val);
					if (val) {
						screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_POSITION, pos);
						screen_get_window_property_iv(screen_win, SCREEN_PROPERTY_SIZE, size);
						fprintf(stderr, "window width: %d, window height: %d\n", size[0], size[1]);
						fprintf(stderr, "pointer x: %d, pointer y: %d\n", pos[0], pos[1]);
						if (pos[0] >= size[0] - exit_area_size &&
							pos[1] < exit_area_size) {
							goto end;
						}
					}
					break;
			}
		}

		/* The second part of the application loop is the rendering. You want
		 * to skip the rendering part if your window is not visible. This will
		 * leave the CPU and GPU to other applications and make the system a
		 * little bit more responsive while you are invisible. */
		if (vis && !pause)
		{
			rc = render();
			if (rc != EGL_TRUE) break;
		}
	}

    end:
	/* Destroy the font */
	if (font) {
		glDeleteTextures(1, &(font->font_texture));
	    free(font);
	}

	/* Terminate EGL setup */
	egl_cleanup();

	/* Clean up screen */
	screen_destroy_event(screen_ev);
	screen_destroy_context(screen_ctx);

	return EXIT_SUCCESS;
}