Index: uspace/app/fontedit/fontedit.c
===================================================================
--- uspace/app/fontedit/fontedit.c	(revision 25f2983b59bde7e27803c53ca062db876372b260)
+++ uspace/app/fontedit/fontedit.c	(revision 8fa65af0b685d658d7be9bc6199ea0d89579704b)
@@ -41,4 +41,5 @@
 #include <gfx/glyph.h>
 #include <gfx/render.h>
+#include <gfx/text.h>
 #include <gfx/typeface.h>
 #include <stdbool.h>
@@ -422,33 +423,13 @@
     gfx_coord_t x, gfx_coord_t y, const char *str)
 {
-	gfx_glyph_metrics_t gmetrics;
-	size_t stradv;
-	const char *cp;
+	gfx_text_fmt_t fmt;
 	gfx_coord2_t pos;
-	gfx_glyph_t *glyph;
-	errno_t rc;
+
+	gfx_text_fmt_init(&fmt);
 
 	pos.x = x;
 	pos.y = y;
-	cp = str;
-
-	while (*cp != '\0') {
-		rc = gfx_font_search_glyph(fedit->font, cp, &glyph, &stradv);
-		if (rc != EOK) {
-			++cp;
-			continue;
-		}
-
-		gfx_glyph_get_metrics(glyph, &gmetrics);
-
-		rc = gfx_glyph_render(glyph, &pos);
-		if (rc != EOK)
-			return rc;
-
-		cp += stradv;
-		pos.x += gmetrics.advance;
-	}
-
-	return EOK;
+
+	return gfx_puttext(fedit->font, &pos, &fmt, str);
 }
 
Index: uspace/app/gfxdemo/gfxdemo.c
===================================================================
--- uspace/app/gfxdemo/gfxdemo.c	(revision 25f2983b59bde7e27803c53ca062db876372b260)
+++ uspace/app/gfxdemo/gfxdemo.c	(revision 8fa65af0b685d658d7be9bc6199ea0d89579704b)
@@ -42,4 +42,7 @@
 #include <gfx/color.h>
 #include <gfx/render.h>
+#include <gfx/font.h>
+#include <gfx/text.h>
+#include <gfx/typeface.h>
 #include <io/console.h>
 #include <io/pixelmap.h>
@@ -366,4 +369,5 @@
 	return rc;
 }
+
 /** Run bitmap color key demo on a graphic context.
  *
@@ -424,5 +428,5 @@
 }
 
-/** Run demo loop on a graphic context.
+/** Run text demo on a graphic context.
  *
  * @param gc Graphic context
@@ -430,4 +434,195 @@
  * @param h Height
  */
+static errno_t demo_text(gfx_context_t *gc, gfx_coord_t w, gfx_coord_t h)
+{
+	gfx_color_t *color = NULL;
+	gfx_rect_t rect;
+	gfx_typeface_t *tface = NULL;
+	gfx_font_info_t *finfo;
+	gfx_font_t *font = NULL;
+	gfx_coord2_t pos;
+	gfx_text_fmt_t fmt;
+	int i;
+	errno_t rc;
+
+	rc = gfx_typeface_open(gc, "/data/font/helena.tpf", &tface);
+	if (rc != EOK) {
+		printf("Error opening typeface\n");
+		goto error;
+	}
+
+	finfo = gfx_typeface_first_font(tface);
+	if (finfo == NULL) {
+		printf("Typeface contains no font.\n");
+		rc = ENOENT;
+		goto error;
+	}
+
+	rc = gfx_font_open(finfo, &font);
+	if (rc != EOK) {
+		printf("Error opening font.\n");
+		goto error;
+	}
+
+	rc = clear_scr(gc, w, h);
+	if (rc != EOK)
+		goto error;
+
+	/* Vertical bars */
+
+	for (i = 0; i < 20; i++) {
+		rc = gfx_color_new_rgb_i16(0, 0x8000 * i / 20,
+		    0x8000 * i / 20, &color);
+		if (rc != EOK)
+			goto error;
+
+		rc = gfx_set_color(gc, color);
+		if (rc != EOK)
+			goto error;
+
+		rect.p0.x = w * i / 20;
+		rect.p0.y = 0;
+		rect.p1.x = w * (i + 1) / 20;
+		rect.p1.y = h;
+
+		rc = gfx_fill_rect(gc, &rect);
+		if (rc != EOK)
+			goto error;
+
+		gfx_color_delete(color);
+	}
+
+	rc = gfx_color_new_rgb_i16(0, 0, 0x8000, &color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_set_color(gc, color);
+	if (rc != EOK)
+		goto error;
+
+	rect.p0.x = w / 20;
+	rect.p0.y = 3 * h / 15;
+	rect.p1.x = w - w / 20;
+	rect.p1.y = 5 * h / 15;
+
+	rc = gfx_fill_rect(gc, &rect);
+	if (rc != EOK)
+		goto error;
+
+	gfx_color_delete(color);
+
+	rc = gfx_color_new_rgb_i16(0xffff, 0xffff, 0xffff, &color);
+	if (rc != EOK)
+		goto error;
+
+	rc = gfx_set_color(gc, color);
+	if (rc != EOK)
+		goto error;
+
+	gfx_text_fmt_init(&fmt);
+
+	pos.x = rect.p0.x;
+	pos.y = rect.p0.y;
+	rc = gfx_puttext(font, &pos, &fmt, "Top left");
+	if (rc != EOK) {
+		printf("Error rendering text.\n");
+		goto error;
+	}
+
+	pos.x = (rect.p0.x + rect.p1.x) / 2;
+	pos.y = rect.p0.y;
+	fmt.halign = gfx_halign_center;
+	rc = gfx_puttext(font, &pos, &fmt, "Top center");
+	if (rc != EOK)
+		goto error;
+
+	pos.x = rect.p1.x;
+	pos.y = rect.p0.y;
+	fmt.halign = gfx_halign_right;
+	rc = gfx_puttext(font, &pos, &fmt, "Top right");
+	if (rc != EOK)
+		goto error;
+
+	fmt.valign = gfx_valign_center;
+
+	pos.x = rect.p0.x;
+	pos.y = (rect.p0.y + rect.p1.y) / 2;
+	fmt.halign = gfx_halign_left;
+	rc = gfx_puttext(font, &pos, &fmt, "Center left");
+	if (rc != EOK)
+		goto error;
+
+	pos.x = (rect.p0.x + rect.p1.x) / 2;
+	pos.y = (rect.p0.y + rect.p1.y) / 2;
+	fmt.halign = gfx_halign_center;
+	rc = gfx_puttext(font, &pos, &fmt, "Center");
+	if (rc != EOK)
+		goto error;
+
+	pos.x = rect.p1.x;
+	pos.y = (rect.p0.y + rect.p1.y) / 2;
+	fmt.halign = gfx_halign_right;
+	rc = gfx_puttext(font, &pos, &fmt, "Center right");
+	if (rc != EOK)
+		goto error;
+
+	fmt.valign = gfx_valign_bottom;
+
+	pos.x = rect.p0.x;
+	pos.y = rect.p1.y;
+	fmt.halign = gfx_halign_left;
+	rc = gfx_puttext(font, &pos, &fmt, "Bottom left");
+	if (rc != EOK)
+		goto error;
+
+	pos.x = (rect.p0.x + rect.p1.x) / 2;
+	pos.y = rect.p1.y;
+	fmt.halign = gfx_halign_center;
+	rc = gfx_puttext(font, &pos, &fmt, "Bottom center");
+	if (rc != EOK)
+		goto error;
+
+	pos.x = rect.p1.x;
+	pos.y = rect.p1.y;
+	fmt.halign = gfx_halign_right;
+	rc = gfx_puttext(font, &pos, &fmt, "Bottom right");
+	if (rc != EOK)
+		goto error;
+
+	gfx_text_fmt_init(&fmt);
+
+	for (i = 0; i < 8; i++) {
+		pos.x = w / 20;
+		pos.y = (7 + i) * h / 15;
+		rc = gfx_puttext(font, &pos, &fmt, "The quick brown fox jumps over the lazy dog.");
+		if (rc != EOK)
+			goto error;
+	}
+
+	for (i = 0; i < 10; i++) {
+		fibril_usleep(500 * 1000);
+		if (quit)
+			break;
+	}
+
+	gfx_color_delete(color);
+
+	gfx_font_close(font);
+	gfx_typeface_destroy(tface);
+	return EOK;
+error:
+	if (font != NULL)
+		gfx_font_close(font);
+	if (tface != NULL)
+		gfx_typeface_destroy(tface);
+	return rc;
+}
+
+/** Run demo loop on a graphic context.
+ *
+ * @param gc Graphic context
+ * @param w Width
+ * @param h Height
+ */
 static errno_t demo_loop(gfx_context_t *gc, gfx_coord_t w, gfx_coord_t h)
 {
@@ -448,4 +643,8 @@
 
 		rc = demo_bitmap_kc(gc, w, h);
+		if (rc != EOK)
+			return rc;
+
+		rc = demo_text(gc, w, h);
 		if (rc != EOK)
 			return rc;
Index: uspace/app/gfxdemo/meson.build
===================================================================
--- uspace/app/gfxdemo/meson.build	(revision 25f2983b59bde7e27803c53ca062db876372b260)
+++ uspace/app/gfxdemo/meson.build	(revision 8fa65af0b685d658d7be9bc6199ea0d89579704b)
@@ -27,5 +27,5 @@
 #
 
-deps = [ 'gfx', 'guigfx', 'congfx', 'ipcgfx', 'display' ]
+deps = [ 'gfx', 'gfxfont', 'guigfx', 'congfx', 'ipcgfx', 'display' ]
 src = files(
 	'gfxdemo.c',
Index: uspace/lib/gfxfont/include/gfx/font.h
===================================================================
--- uspace/lib/gfxfont/include/gfx/font.h	(revision 25f2983b59bde7e27803c53ca062db876372b260)
+++ uspace/lib/gfxfont/include/gfx/font.h	(revision 8fa65af0b685d658d7be9bc6199ea0d89579704b)
@@ -42,4 +42,5 @@
 #include <types/gfx/font.h>
 #include <types/gfx/glyph.h>
+#include <types/gfx/text.h>
 #include <types/gfx/typeface.h>
 
Index: uspace/lib/gfxfont/include/gfx/text.h
===================================================================
--- uspace/lib/gfxfont/include/gfx/text.h	(revision 8fa65af0b685d658d7be9bc6199ea0d89579704b)
+++ uspace/lib/gfxfont/include/gfx/text.h	(revision 8fa65af0b685d658d7be9bc6199ea0d89579704b)
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libgfxfont
+ * @{
+ */
+/**
+ * @file Text formatting
+ */
+
+#ifndef _GFX_TEXT_H
+#define _GFX_TEXT_H
+
+#include <errno.h>
+#include <types/gfx/coord.h>
+#include <types/gfx/font.h>
+#include <types/gfx/text.h>
+
+extern void gfx_text_fmt_init(gfx_text_fmt_t *);
+extern gfx_coord_t gfx_text_width(gfx_font_t *, const char *);
+extern errno_t gfx_puttext(gfx_font_t *, gfx_coord2_t *, gfx_text_fmt_t *,
+    const char *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/gfxfont/include/types/gfx/text.h
===================================================================
--- uspace/lib/gfxfont/include/types/gfx/text.h	(revision 8fa65af0b685d658d7be9bc6199ea0d89579704b)
+++ uspace/lib/gfxfont/include/types/gfx/text.h	(revision 8fa65af0b685d658d7be9bc6199ea0d89579704b)
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2020 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libgfxfont
+ * @{
+ */
+/**
+ * @file Text types
+ */
+
+#ifndef _TYPES_GFX_TEXT_H
+#define _TYPES_GFX_TEXT_H
+
+#include <types/gfx/coord.h>
+
+/** Text horizontal alignment */
+typedef enum {
+	/** Align text left */
+	gfx_halign_left,
+	/** Align text on the center */
+	gfx_halign_center,
+	/** Align text right */
+	gfx_halign_right,
+	/** Justify text on both left and right edge */
+	gfx_halign_justify
+} gfx_halign_t;
+
+/** Text vertical alignment */
+typedef enum {
+	/** Align top */
+	gfx_valign_top,
+	/** Align center */
+	gfx_valign_center,
+	/** Align bottom */
+	gfx_valign_bottom
+} gfx_valign_t;
+
+/** Text formatting */
+typedef struct {
+	/** Horizontal alignment */
+	gfx_halign_t halign;
+	/** Justification width (for gfx_halign_justify) */
+	gfx_coord_t justify_width;
+	/** Vertical alignment */
+	gfx_valign_t valign;
+} gfx_text_fmt_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/gfxfont/meson.build
===================================================================
--- uspace/lib/gfxfont/meson.build	(revision 25f2983b59bde7e27803c53ca062db876372b260)
+++ uspace/lib/gfxfont/meson.build	(revision 8fa65af0b685d658d7be9bc6199ea0d89579704b)
@@ -32,4 +32,5 @@
 	'src/glyph.c',
 	'src/glyph_bmp.c',
+	'src/text.c',
 	'src/typeface.c',
 )
@@ -40,4 +41,5 @@
 	'test/glyph_bmp.c',
 	'test/main.c',
+	'test/text.c',
 	'test/tpf.c',
 	'test/typeface.c',
Index: uspace/lib/gfxfont/src/text.c
===================================================================
--- uspace/lib/gfxfont/src/text.c	(revision 8fa65af0b685d658d7be9bc6199ea0d89579704b)
+++ uspace/lib/gfxfont/src/text.c	(revision 8fa65af0b685d658d7be9bc6199ea0d89579704b)
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2020 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libgfxfont
+ * @{
+ */
+/**
+ * @file Text rendering
+ */
+
+#include <errno.h>
+#include <gfx/font.h>
+#include <gfx/glyph.h>
+#include <gfx/text.h>
+#include <mem.h>
+
+/** Initialize text formatting structure.
+ *
+ * Text formatting structure must always be initialized using this function
+ * first.
+ *
+ * @param fmt Text formatting structure
+ */
+void gfx_text_fmt_init(gfx_text_fmt_t *fmt)
+{
+	memset(fmt, 0, sizeof(gfx_text_fmt_t));
+}
+
+/** Compute text width.
+ *
+ * @param font Font
+ * @param str String
+ * @return Text width
+ */
+gfx_coord_t gfx_text_width(gfx_font_t *font, const char *str)
+{
+	gfx_glyph_metrics_t gmetrics;
+	size_t stradv;
+	const char *cp;
+	gfx_glyph_t *glyph;
+	gfx_coord_t width;
+	errno_t rc;
+
+	width = 0;
+	cp = str;
+	while (*cp != '\0') {
+		rc = gfx_font_search_glyph(font, cp, &glyph, &stradv);
+		if (rc != EOK) {
+			++cp;
+			continue;
+		}
+
+		gfx_glyph_get_metrics(glyph, &gmetrics);
+
+		cp += stradv;
+		width += gmetrics.advance;
+	}
+
+	return width;
+}
+
+/** Render text.
+ *
+ * @param font Font
+ * @param pos Anchor position
+ * @param fmt Text formatting
+ * @param str String
+ * @return EOK on success or an error code
+ */
+errno_t gfx_puttext(gfx_font_t *font, gfx_coord2_t *pos,
+    gfx_text_fmt_t *fmt, const char *str)
+{
+	gfx_font_metrics_t fmetrics;
+	gfx_glyph_metrics_t gmetrics;
+	size_t stradv;
+	const char *cp;
+	gfx_glyph_t *glyph;
+	gfx_coord2_t cpos;
+	gfx_coord_t width;
+	errno_t rc;
+
+	cpos = *pos;
+
+	/* Adjust position for horizontal alignment */
+	if (fmt->halign != gfx_halign_left) {
+		width = gfx_text_width(font, str);
+		switch (fmt->halign) {
+		case gfx_halign_center:
+			cpos.x -= width / 2;
+			break;
+		case gfx_halign_right:
+			cpos.x -= width;
+			break;
+		default:
+			break;
+		}
+	}
+
+	/* Adjust position for vertical alignment */
+	if (fmt->valign != gfx_valign_bottom) {
+		gfx_font_get_metrics(font, &fmetrics);
+
+		switch (fmt->valign) {
+		case gfx_valign_top:
+			cpos.y += fmetrics.ascent;
+			break;
+		case gfx_valign_center:
+			cpos.y += fmetrics.ascent / 2;
+			break;
+		default:
+			break;
+		}
+	}
+
+	cp = str;
+	while (*cp != '\0') {
+		rc = gfx_font_search_glyph(font, cp, &glyph, &stradv);
+		if (rc != EOK) {
+			++cp;
+			continue;
+		}
+
+		gfx_glyph_get_metrics(glyph, &gmetrics);
+
+		rc = gfx_glyph_render(glyph, &cpos);
+		if (rc != EOK)
+			return rc;
+
+		cp += stradv;
+		cpos.x += gmetrics.advance;
+	}
+
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/lib/gfxfont/test/main.c
===================================================================
--- uspace/lib/gfxfont/test/main.c	(revision 25f2983b59bde7e27803c53ca062db876372b260)
+++ uspace/lib/gfxfont/test/main.c	(revision 8fa65af0b685d658d7be9bc6199ea0d89579704b)
@@ -34,4 +34,5 @@
 PCUT_IMPORT(glyph);
 PCUT_IMPORT(glyph_bmp);
+PCUT_IMPORT(text);
 PCUT_IMPORT(tpf);
 PCUT_IMPORT(typeface);
Index: uspace/lib/gfxfont/test/text.c
===================================================================
--- uspace/lib/gfxfont/test/text.c	(revision 8fa65af0b685d658d7be9bc6199ea0d89579704b)
+++ uspace/lib/gfxfont/test/text.c	(revision 8fa65af0b685d658d7be9bc6199ea0d89579704b)
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2020 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <gfx/context.h>
+#include <gfx/font.h>
+#include <gfx/text.h>
+#include <gfx/typeface.h>
+#include <pcut/pcut.h>
+#include "../private/font.h"
+#include "../private/typeface.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(text);
+
+static errno_t testgc_set_color(void *, gfx_color_t *);
+static errno_t testgc_fill_rect(void *, gfx_rect_t *);
+static errno_t testgc_bitmap_create(void *, gfx_bitmap_params_t *,
+    gfx_bitmap_alloc_t *, void **);
+static errno_t testgc_bitmap_destroy(void *);
+static errno_t testgc_bitmap_render(void *, gfx_rect_t *, gfx_coord2_t *);
+static errno_t testgc_bitmap_get_alloc(void *, gfx_bitmap_alloc_t *);
+
+static gfx_context_ops_t test_ops = {
+	.set_color = testgc_set_color,
+	.fill_rect = testgc_fill_rect,
+	.bitmap_create = testgc_bitmap_create,
+	.bitmap_destroy = testgc_bitmap_destroy,
+	.bitmap_render = testgc_bitmap_render,
+	.bitmap_get_alloc = testgc_bitmap_get_alloc
+};
+
+typedef struct {
+	gfx_bitmap_params_t bm_params;
+	void *bm_pixels;
+	gfx_rect_t bm_srect;
+	gfx_coord2_t bm_offs;
+} test_gc_t;
+
+typedef struct {
+	test_gc_t *tgc;
+	gfx_bitmap_alloc_t alloc;
+	bool myalloc;
+} testgc_bitmap_t;
+
+/** Test text width computation with a dummy font */
+PCUT_TEST(dummy_text_width)
+{
+	gfx_font_props_t props;
+	gfx_font_metrics_t metrics;
+	gfx_typeface_t *tface;
+	gfx_font_t *font;
+	gfx_context_t *gc;
+	gfx_coord_t width;
+	test_gc_t tgc;
+	errno_t rc;
+
+	rc = gfx_context_new(&test_ops, (void *)&tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = gfx_typeface_create(gc, &tface);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	gfx_font_props_init(&props);
+	gfx_font_metrics_init(&metrics);
+	rc = gfx_font_create(tface, &props, &metrics, &font);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	width = gfx_text_width(font, "Hello world!");
+	PCUT_ASSERT_INT_EQUALS(0, width);
+
+	gfx_font_close(font);
+	gfx_typeface_destroy(tface);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+/** Test text rendering with a dummy font */
+PCUT_TEST(dummy_puttext)
+{
+	gfx_font_props_t props;
+	gfx_font_metrics_t metrics;
+	gfx_typeface_t *tface;
+	gfx_font_t *font;
+	gfx_context_t *gc;
+	gfx_text_fmt_t fmt;
+	gfx_coord2_t pos;
+	test_gc_t tgc;
+	errno_t rc;
+
+	rc = gfx_context_new(&test_ops, (void *)&tgc, &gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = gfx_typeface_create(gc, &tface);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	gfx_font_props_init(&props);
+	gfx_font_metrics_init(&metrics);
+	rc = gfx_font_create(tface, &props, &metrics, &font);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	gfx_text_fmt_init(&fmt);
+	pos.x = 0;
+	pos.y = 0;
+
+	rc = gfx_puttext(font, &pos, &fmt, "Hello world!");
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	gfx_font_close(font);
+	gfx_typeface_destroy(tface);
+
+	rc = gfx_context_delete(gc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+}
+
+static errno_t testgc_set_color(void *arg, gfx_color_t *color)
+{
+	return EOK;
+}
+
+static errno_t testgc_fill_rect(void *arg, gfx_rect_t *rect)
+{
+	return EOK;
+}
+
+static errno_t testgc_bitmap_create(void *arg, gfx_bitmap_params_t *params,
+    gfx_bitmap_alloc_t *alloc, void **rbm)
+{
+	test_gc_t *tgc = (test_gc_t *) arg;
+	testgc_bitmap_t *tbm;
+
+	tbm = calloc(1, sizeof(testgc_bitmap_t));
+	if (tbm == NULL)
+		return ENOMEM;
+
+	if (alloc == NULL) {
+		tbm->alloc.pitch = (params->rect.p1.x - params->rect.p0.x) *
+		    sizeof(uint32_t);
+		tbm->alloc.off0 = 0;
+		tbm->alloc.pixels = calloc(sizeof(uint32_t),
+		    tbm->alloc.pitch * (params->rect.p1.y - params->rect.p0.y));
+		tbm->myalloc = true;
+		if (tbm->alloc.pixels == NULL) {
+			free(tbm);
+			return ENOMEM;
+		}
+	} else {
+		tbm->alloc = *alloc;
+	}
+
+	tbm->tgc = tgc;
+	tgc->bm_params = *params;
+	tgc->bm_pixels = tbm->alloc.pixels;
+	*rbm = (void *)tbm;
+	return EOK;
+}
+
+static errno_t testgc_bitmap_destroy(void *bm)
+{
+	testgc_bitmap_t *tbm = (testgc_bitmap_t *)bm;
+	if (tbm->myalloc)
+		free(tbm->alloc.pixels);
+	free(tbm);
+	return EOK;
+}
+
+static errno_t testgc_bitmap_render(void *bm, gfx_rect_t *srect,
+    gfx_coord2_t *offs)
+{
+	testgc_bitmap_t *tbm = (testgc_bitmap_t *)bm;
+	tbm->tgc->bm_srect = *srect;
+	tbm->tgc->bm_offs = *offs;
+	return EOK;
+}
+
+static errno_t testgc_bitmap_get_alloc(void *bm, gfx_bitmap_alloc_t *alloc)
+{
+	testgc_bitmap_t *tbm = (testgc_bitmap_t *)bm;
+	*alloc = tbm->alloc;
+	return EOK;
+}
+
+PCUT_EXPORT(text);
