From 9934cc80b087ce9b71a87baaa77068fbd23445ce Mon Sep 17 00:00:00 2001 From: David Thompson Date: Sun, 27 Mar 2016 11:59:14 -0400 Subject: First commit! The wonderful beginnings of a new blog powered by Haunt! --- posts/2013-08-17-font-rendering-pango-cairo.skr | 328 ++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 posts/2013-08-17-font-rendering-pango-cairo.skr (limited to 'posts/2013-08-17-font-rendering-pango-cairo.skr') diff --git a/posts/2013-08-17-font-rendering-pango-cairo.skr b/posts/2013-08-17-font-rendering-pango-cairo.skr new file mode 100644 index 0000000..59e0f96 --- /dev/null +++ b/posts/2013-08-17-font-rendering-pango-cairo.skr @@ -0,0 +1,328 @@ +(define guile-2d-url "https://github.com/davexunit/guile-2d") +(define madsy9-url "http://www.reddit.com/user/Madsy9") +(define madsy9-comment-url + "http://www.reddit.com/r/scheme/comments/1k739l/guile_2d_game_programming_lib_for_scheme/cbmnyuk") +(define pango-url "http://www.pango.org/") +(define cairo-url "http://cairographics.org/") +;; TODO: Move to files.dthompson.us +(define tarball-url "/src/pangocairo.tar.gz") + +(post + :title "Font Rendering in OpenGL with Pango and Cairo" + :date (make-date* 2013 08 17) + :tags '("opengl" "pango" "cairo" "font" "wsu") + :summary "A brief tutorial for rendering fonts in OpenGL with libpangocairo" + + (p [I am working towards a 0.1 release of my game development + framework for GNU Guile, ,(anchor "guile-2d" guile-2d-url). One of + the few remaining blockers on my to-do list is font rendering. A + reddit user, ,(anchor "Madsy9" madsy9-url), pointed me in the right + direction with this ,(anchor "comment" madsy9-comment-url). There are + two libraries needed to perform nice font rendering with proper + internationalization support: ,(anchor "Pango" pango-url), “a library + for laying out and rendering of text, with an emphasis on + internationalization,” and ,(anchor "Cairo" cairo-url), “a 2D graphics + library with support for multiple output devices.”]) + + (p [It took me awhile to put together all of the pieces and build a + working sample program. The goal of this post is to help others that + may be trying to accomplish a similar task that have no prior + knowledge of Pango and Cairo. I will assume basic knowledge of C, + SDL, and OpenGL throughout this post.]) + + (p [Let's get the basic SDL and OpenGL initialization out of the way:]) + + (source-code + (c-source + "#include +#include +#include + +#define WINDOW_WIDTH 800 +#define WINDOW_HEIGHT 600 +#define FONT \"Sans Bold 18\" +#define TEXT \"The quick brown fox is so かわいい!\" + +void +init_sdl () +{ + SDL_Init (SDL_INIT_EVERYTHING); + SDL_SetVideoMode (WINDOW_WIDTH, WINDOW_HEIGHT, 0, SDL_OPENGL); +} + +void +init_gl () +{ + glClearColor (0.0f, 0.0f, 0.0f, 0.0f); + glDisable (GL_DEPTH_TEST); + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable (GL_TEXTURE_2D); + glViewport (0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); + glMatrixMode (GL_PROJECTION); + glLoadIdentity (); + glOrtho (0, WINDOW_WIDTH, WINDOW_HEIGHT, 0, -1, 1); + glMatrixMode (GL_MODELVIEW); + glLoadIdentity (); +}")) + + (p [,(code "create_texture") simply creates an OpenGL texture given an array of + pixel data and the texture dimensions. Our Cairo surface will use BGRA + color.]) + + (source-code + (c-source + "unsigned int +create_texture (unsigned int width, + unsigned int height, + unsigned char *pixels) +{ + unsigned int texture_id; + + glGenTextures (1, &texture_id); + glBindTexture (GL_TEXTURE_2D, texture_id); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D (GL_TEXTURE_2D, + 0, + GL_RGBA, + width, + height, + 0, + GL_BGRA, + GL_UNSIGNED_BYTE, + pixels); + + return texture_id; +}")) + + (p [,(code "draw_texture") clears the screen, renders a simple textured quad + using OpenGL’s immediate mode, and then swaps buffers.]) + + (source-code + (c-source + "void +draw_texture (int width, + int height, + unsigned int texture_id) +{ + /* Render a texture in immediate mode. */ + glMatrixMode (GL_MODELVIEW); + glLoadIdentity (); + glClear (GL_COLOR_BUFFER_BIT); + glPushMatrix (); + glBindTexture (GL_TEXTURE_2D, texture_id); + glColor3f (1.f, 1.0f, 1.0f); + + glBegin (GL_QUADS); + glTexCoord2f (0.0f, 0.0f); + glVertex2f (0.0f, 0.0f); + glTexCoord2f (1.0f, 0.0f); + glVertex2f (width, 0.0f); + glTexCoord2f (1.0f, 1.0f); + glVertex2f (width , height); + glTexCoord2f (0.0f, 1.0f); + glVertex2f (0.0f, height); + glEnd (); + + glPopMatrix (); + SDL_GL_SwapBuffers(); +}")) + + (p [,(code [create_cairo_context]) is used to make a new Cairo context that +draws to a raw data surface. The return value, a ,(code [cairo_t]), is the +main object in Cairo. All drawing is done via a ,(code [cairo_t]) object. A +context needs a surface to draw on. +,(code [cairo_image_surface_create_for_data]) creates a raw data surface for +us. We will be translating the surface into a texture later on.]) + + (source-code + (c-source + "cairo_t* +create_cairo_context (int width, + int height, + int channels, + cairo_surface_t** surf, + unsigned char** buffer) +{ + *buffer = calloc (channels * width * height, sizeof (unsigned char)); + *surf = cairo_image_surface_create_for_data (*buffer, + CAIRO_FORMAT_ARGB32, + width, + height, + channels * width); + return cairo_create (*surf); +}")) + + (p [,(code [create_layout_context]) also makes a new Cairo context, but this +context is for PangoLayout objects. In Pango, a layout describes the +style of a paragraph of text. The layout needs a context in order to +function. We use ,(code [cairo_image_surface_create]) with dimensions of 0x0 +because we won’t actually be rendering to this surface. Instead, we +will layout our text and use ,(code [create_cairo_context]) to build a +context with a surface that is the size of the rendered text. Cairo +uses reference counting for dynamically allocated objects, so we need +to call ,(code [cairo_surface_destroy]) when we’re done with the temporary +surface. The context still maintains a reference to the surface, so +the memory for the surface will not be freed until the context is.]) + + (source-code + (c-source + "cairo_t* +create_layout_context () +{ + cairo_surface_t *temp_surface; + cairo_t *context; + + temp_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 0, 0); + context = cairo_create (temp_surface); + cairo_surface_destroy (temp_surface); + + return context; +}")) + + (p [,(code [get_text_size]) tells us the size of the text that’s in the layout, +in pixels. Pango’s units are not in pixels, so we must divide by +,(code [PANGO_SCALE]) in order to get pixel units.]) + + (source-code + (c-source + "void +get_text_size (PangoLayout *layout, + unsigned int *width, + unsigned int *height) +{ + pango_layout_get_size (layout, width, height); + /* Divide by pango scale to get dimensions in pixels. */ + *width /= PANGO_SCALE; + *height /= PANGO_SCALE; +}")) + + (p [,(code [render_text]) is where all of the magic happens. First, we create a +layout with a layout context and set the text that we will render with +this layout. ,(code [TEXT]) is defined earlier in the program as “The quick +brown fox is so かわいい!”]) + + (p [Then we create a ,(code [PangoFontDescription]) object. This object +represents the font that we want to render. Earlier in the program, +,(code [FONT]) is defined as “Sans Bold 18”. Pango is able to figure out +how to load a font from a string in this format. Your system must be +able to recognize the font family and font face, though. I haven’t +yet figured out how to have Pango render an arbitrary font from a +,(code [*.ttf]) file.]) + + (p [Next, we create a rendering context by getting the layout’s size and +creating a context with a surface big enough to show all of the +rendered text.]) + + (p [Finally, we set the font color to white, render the text to the +surface with ,(code [pango_cairo_show_layout]), and create an OpenGL texture +from the surface. We also clean up all the objects that we no longer +need before returning.]) + + (source-code + (c-source + "unsigned int +render_text (const char *text, + unsigned int *text_width, + unsigned int *text_height, + unsigned int *texture_id) +{ + cairo_t *layout_context; + cairo_t *render_context; + cairo_surface_t *temp_surface; + cairo_surface_t *surface; + unsigned char* surface_data = NULL; + PangoFontDescription *desc; + PangoLayout *layout; + + layout_context = create_layout_context (); + + /* Create a PangoLayout, set the font and text */ + layout = pango_cairo_create_layout (layout_context); + pango_layout_set_text (layout, text, -1); + + /* Load the font */ + desc = pango_font_description_from_string (FONT); + pango_layout_set_font_description (layout, desc); + pango_font_description_free (desc); + + /* Get text dimensions and create a context to render to */ + get_text_size (layout, text_width, text_height); + render_context = create_cairo_context (*text_width, + *text_height, + 4, + &surface, + &surface_data); + + /* Render */ + cairo_set_source_rgba (render_context, 1, 1, 1, 1); + pango_cairo_show_layout (render_context, layout); + *texture_id = create_texture(*text_width, *text_height, surface_data); + + /* Clean up */ + free (surface_data); + g_object_unref (layout); + cairo_destroy (layout_context); + cairo_destroy (render_context); + cairo_surface_destroy (surface); +}")) + + (p [,(code [main]) is pretty simple. We initialize SDL and OpenGL, render text +to a texture, and enter the rendering loop. The program will run +until you click the close button, press “enter”, or press “q”.]) + + (source-code + (c-source + "int main (int argc, char **argv) +{ + SDL_Event event; + int keep_running = 1; + unsigned int texture_id; + unsigned int text_width = 0; + unsigned int text_height = 0; + + init_sdl (); + init_gl (); + render_text(TEXT, + &texture_id, + &text_width, + &text_height); + + /* Update/render loop */ + while (keep_running) { + SDL_PollEvent (&event); + + switch (event.type) { + case SDL_QUIT : + keep_running = 0; + break; + + case SDL_KEYDOWN : + if (event.key.keysym.sym == SDLK_ESCAPE) + keep_running = 0; + if (event.key.keysym.sym == SDLK_q) + keep_running = 0; + break; + } + + draw_texture (texture_id, text_width, text_height); + SDL_Delay (16); + } + + /* Clean up */ + glDeleteTextures (1, &texture_id); + + SDL_Quit(); + + return 0; +}")) + + (p [And we’re done! You should now be able to render some text in an +OpenGL context. I hope this brief tutorial was helpful. Font rendering +isn’t easy, and it’s not really my area of interest. I’m glad that +Pango exists to do all of the real work for me so that I can more +quickly move on to the parts of graphics programming that I actually +enjoy.]) + + (p [You can download the full source code ,(anchor [here] tarball-url).])) -- cgit v1.2.3