summaryrefslogtreecommitdiff
path: root/posts/2013-08-17-font-rendering-pango-cairo.skr
diff options
context:
space:
mode:
authorDavid Thompson <dthompson2@worcester.edu>2016-03-27 11:59:14 -0400
committerDavid Thompson <dthompson2@worcester.edu>2016-03-27 11:59:14 -0400
commit9934cc80b087ce9b71a87baaa77068fbd23445ce (patch)
tree0ad991cec55a7fe492f07e22c03d411fd7229a18 /posts/2013-08-17-font-rendering-pango-cairo.skr
First commit!
The wonderful beginnings of a new blog powered by Haunt!
Diffstat (limited to 'posts/2013-08-17-font-rendering-pango-cairo.skr')
-rw-r--r--posts/2013-08-17-font-rendering-pango-cairo.skr328
1 files changed, 328 insertions, 0 deletions
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 <pango/pangocairo.h>
+#include <SDL.h>
+#include <SDL_opengl.h>
+
+#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).]))