commit 55009a9b2fc3477235ddf5cae6c45446e0117802
parent 38fe277a39863666574934ce221dcf9bd9ee4cad
Author: lumidify <nobody@lumidify.org>
Date:   Sun, 13 Sep 2020 21:28:30 +0200
Fix basic rendering
Diffstat:
17 files changed, 691 insertions(+), 614 deletions(-)
diff --git a/README.md b/README.md
@@ -3,7 +3,10 @@ Not much to see here.
 WARNING: DON'T TRY TO USE THIS! IT IS ONLY A PLACE FOR ME TO TRY OUT MY
 WILDEST FANTASIES, NOT ACTUAL WORKING CODE.
 
-Also, it currently only works with pango, until I fix the basic text again.
+To build with or without pango: Follow instructions in config.mk and defs.h.
+
+Note: The basic (non-pango) text doesn't work properly on my i386 machine
+because it's a bit of a hack.
 
 To test:
 
diff --git a/color.c b/color.c
@@ -1,5 +1,6 @@
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
+#include "defs.h"
 #include "util.h"
 #include "color.h"
 
diff --git a/config.mk b/config.mk
@@ -1,8 +1,14 @@
 VERSION = -999
 
-CFLAGS = -g -std=c99 -w -fcommon -Wall -Werror -Wextra `pkg-config --cflags x11 fontconfig pangoxft` -pedantic
-LDFLAGS = -lm `pkg-config --libs x11 fontconfig pangoxft`
-OBJ = text_pango.o
+CFLAGS = -g -std=c99 -w -fcommon -Wall -Werror -Wextra `pkg-config --cflags x11 fontconfig` -pedantic
+LDFLAGS = -lm `pkg-config --libs x11 fontconfig`
+# Comment when enabling pango rendering:
+OBJ = stb_truetype.o text_stb.o
+
+# Uncomment to enable pango rendering:
+#OBJ = text_pango.o
+#CFLAGS += `pkg-config --cflags pangoxft`
+#LDFLAGS += `pkg-config --libs pangoxft`
 
 # OpenBSD
 COMPATOBJ = 
diff --git a/defs.h b/defs.h
@@ -1,2 +1,9 @@
+/*
+ Uncomment USE_PANGO and USE_XFT and comment USE_STB to enable pango,
+ and vice versa to enable basic rendering.
+*/
+/*
 #define USE_PANGO
 #define USE_XFT
+*/
+#define USE_STB
diff --git a/draw.c b/draw.c
@@ -28,10 +28,6 @@
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
 #include "util.h"
-#include "khash.h"
-#include "stb_truetype.h"
-#include <fontconfig/fontconfig.h>
-#include "text_common.h"
 #include "ltk.h"
 #include "draw.h"
 
diff --git a/grid.c b/grid.c
@@ -31,7 +31,6 @@
 #include <X11/Xutil.h>
 #include "util.h"
 #include "khash.h"
-#include "text_common.h"
 #include "ltk.h"
 #include "grid.h"
 
diff --git a/ltk.h b/ltk.h
@@ -26,6 +26,8 @@
 
 /* Requires the following includes: <X11/Xlib.h>, <X11/Xutil.h> */
 
+#include "khash.h"
+
 typedef struct {
 	int x;
 	int y;
@@ -128,8 +130,6 @@ typedef struct ltk_window {
 	khash_t(widget) *widget_hash;
 } ltk_window;
 
-char *ltk_read_file(const char *path, unsigned long *len);
-
 void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect);
 
 void ltk_fatal(const char *msg);
diff --git a/socket_format.txt b/socket_format.txt
@@ -0,0 +1,25 @@
+Note: This is not implemented yet; it is just here to collect
+my thoughts while I keep working.
+
+<widget type> <widget id> <command> <args>
+> grid grd1 create 2 2
+
+If the command takes a string, the string may contain newlines:
+> button btn1 create "I'm a
+> button!"
+
+The command line is read until the first newline that is not
+within a string.
+
+Double quotes must be escaped in strings, like so:
+> button btn1 create "Bla\"bla"
+
+When the server sends a reply, the format is the same, but
+there are some special cases, such as "get-text". When the
+client asks to get the text for a widget, only the text is
+sent back, but still inside double quotes, with double quotes
+belonging to the text escaped.
+
+Essentially, the individual messages are separated by line
+breaks (\n), but line breaks within strings don't break the
+message.
diff --git a/text.h b/text.h
@@ -4,13 +4,17 @@
 #include "defs.h"
 #include "color.h"
 
+typedef struct LtkTextLine LtkTextLine;
+
+void ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap cm);
+void ltk_cleanup_text(void);
+LtkTextLine *ltk_text_line_create(Window window, uint16_t font_size, char *text, int width);
+void ltk_text_line_render(LtkTextLine *tl, LtkColor *bg, LtkColor *fg);
+void ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y);
+void ltk_text_line_set_width(LtkTextLine *tl, int width);
+void ltk_text_line_get_size(LtkTextLine *tl, int *w, int *h);
+void ltk_text_line_destroy(LtkTextLine *tl);
+
 #ifdef USE_PANGO
   #include <pango/pangoxft.h>
-  #include "text_pango.h"
-#endif
-
-/* Basic */
-#ifdef USE_BASIC_TEXT
-  #include "text_common.h"
-  #include "text_line.h"
 #endif
diff --git a/text_common.c b/text_common.c
@@ -1,452 +0,0 @@
-/*
- * This file is part of the Lumidify ToolKit (LTK)
- * Copyright (c) 2017, 2018, 2020 lumidify <nobody@lumidify.org>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <limits.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */
-#include "khash.h"
-#include <fontconfig/fontconfig.h>
-#include "text_common.h"
-#include "ltk.h"
-
-typedef struct ltk_font {
-	stbtt_fontinfo info;
-	char *path;
-	int index; /* index in font file */
-	uint16_t id;
-	unsigned int refs;
-} ltk_font;
-
-/* Hash definitions */
-/* glyph id -> glyph info struct */
-KHASH_MAP_INIT_INT(glyphinfo, ltk_glyph_info*)
-/* font path, size -> glyph cache hash */
-KHASH_MAP_INIT_INT(glyphcache, khash_t(glyphinfo)*)
-
-static struct {
-	khash_t(glyphcache) *glyph_cache;
-	ltk_font **fonts;
-	int num_fonts;
-	int fonts_bufsize;
-	FcPattern *fcpattern;
-	ltk_font *default_font;
-	uint16_t font_id_cur;
-} tm = {NULL, NULL, 0, 0, NULL, NULL, 1};
-
-static const char *default_font;
-
-static void err(const char *msg);
-static char *read_file(const char *path, unsigned long *len);
-static ltk_font *ltk_get_font(char *path, int index);
-static void ltk_init_text(void);
-static ltk_glyph_info *ltk_create_glyph_info(ltk_font *font, int id,
-    float scale);
-static voidltk_destroy_glyph_info(ltk_glyph_info *gi);
-static ltk_glyph_info *ltk_get_glyph_info(ltk_font *font, int id,
-    float scale, khash_t(glyphinfo) *cache);
-static khash_t(glyphinfo) *ltk_get_glyph_cache(uint16_t font_id,
-    uint16_t font_size);
-static khint_t ltk_create_glyph_cache(uint16_t font_id, uint16_t font_size);
-static void ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache);
-static void ltk_load_default_font(void);
-static ltk_font *ltk_create_font(char *path, uint16_t id, int index);
-static void ltk_destroy_font(ltk_font *font);
-static ltk_font *ltk_load_font(char *path, int index);
-static ltk_font *ltk_get_font(char *path, int index);
-
-static void
-err(const char *msg) {
-	perror(msg);
-	exit(1);
-}
-
-static char *
-read_file(const char *path, unsigned long *len) {
-	FILE *f;
-	char *file_contents;
-	f = fopen(path, "rb");
-	fseek(f, 0, SEEK_END);
-	*len = ftell(f);
-	fseek(f, 0, SEEK_SET);
-	file_contents = malloc(*len + 1);
-	fread(file_contents, 1, *len, f);
-	file_contents[*len] = '\0';
-	fclose(f);
-
-	return file_contents;
-}
-
-/* These unicode routines are taken from
- * https://github.com/JeffBezanson/cutef8 */
-
-/* is c the start of a utf8 sequence? */
-#define isutf(c) (((c)&0xC0)!=0x80)
-
-static const uint32_t offsetsFromUTF8[6] = {
-    0x00000000UL, 0x00003080UL, 0x000E2080UL,
-    0x03C82080UL, 0xFA082080UL, 0x82082080UL
-};
-
-/* next character without NUL character terminator */
-uint32_t u8_nextmemchar(const char *s, size_t *i) {
-    uint32_t ch = 0;
-    size_t sz = 0;
-    do {
-        ch <<= 6;
-        ch += (unsigned char)s[(*i)++];
-        sz++;
-    } while (!isutf(s[*i]));
-    ch -= offsetsFromUTF8[sz-1];
-
-    return ch;
-}
-
-/* number of characters in NUL-terminated string */
-size_t u8_strlen(const char *s) {
-    size_t count = 0;
-    size_t i = 0, lasti;
-
-    while (1) {
-        lasti = i;
-        while (s[i] > 0)
-            i++;
-        count += (i-lasti);
-        if (s[i++]==0) break;
-        (void)(isutf(s[++i]) || isutf(s[++i]) || ++i);
-        count++;
-    }
-    return count;
-}
-
-size_t u8_wc_toutf8(char *dest, uint32_t ch) {
-    if (ch < 0x80) {
-        dest[0] = (char)ch;
-        return 1;
-    }
-    if (ch < 0x800) {
-        dest[0] = (ch>>6) | 0xC0;
-        dest[1] = (ch & 0x3F) | 0x80;
-        return 2;
-    }
-    if (ch < 0x10000) {
-        dest[0] = (ch>>12) | 0xE0;
-        dest[1] = ((ch>>6) & 0x3F) | 0x80;
-        dest[2] = (ch & 0x3F) | 0x80;
-        return 3;
-    }
-    if (ch < 0x110000) {
-        dest[0] = (ch>>18) | 0xF0;
-        dest[1] = ((ch>>12) & 0x3F) | 0x80;
-        dest[2] = ((ch>>6) & 0x3F) | 0x80;
-        dest[3] = (ch & 0x3F) | 0x80;
-        return 4;
-    }
-    return 0;
-}
-
-void
-ltk_init_default_font(const char *font_name) {
-	default_font = strdup(font_name);
-	if (!default_font) err("ltk_init_default_font");
-}
-
-static void
-ltk_init_text(void) {
-	tm.fonts_bufsize = 1;
-	tm.glyph_cache = kh_init(glyphcache);
-	tm.fonts = malloc(sizeof(ltk_font *));
-	if (!tm.fonts) err("ltk_init_text");
-	ltk_load_default_font();
-}
-
-void
-ltk_cleanup_text(void) {
-	if (default_font) free(default_font);
-	for (int i = 0; i < tm.num_fonts; i++) {
-		ltk_destroy_font(&tm.fonts[i]);
-	}
-	if (!tm.glyph_cache) return;
-	for (int k = kh_begin(tm.glyph_cache); k != kh_end(tm.glyph_cache); k++) {
-		if (kh_exist(tm.glyph_cache, k)) {
-			ltk_destroy_glyph_cache(kh_value(tm.glyph_cache, k));
-		}
-	}
-	kh_destroy(glyphcache, tm.glyph_cache);
-}
-
-static ltk_glyph_info *
-ltk_create_glyph_info(ltk_font *font, int id, float scale) {
-	ltk_glyph_info *glyph = malloc(sizeof(ltk_glyph_info));
-	if (!glyph) err("ltk_create_glyph_info");
-
-	glyph->id = id;
-	glyph->refs = 0;
-	glyph->alphamap = stbtt_GetGlyphBitmap(
-		&font->info, scale, scale, id, &glyph->w,
-		&glyph->h, &glyph->xoff, &glyph->yoff
-	);
-
-	return glyph;
-}
-
-static void
-ltk_destroy_glyph_info(ltk_glyph_info *gi) {
-	free(gi->alphamap);
-	free(gi);
-}
-
-static ltk_glyph_info *
-ltk_get_glyph_info(ltk_font *font, int id, float scale, khash_t(glyphinfo) *cache) {
-	int ret;
-	khint_t k;
-	ltk_glyph_info *glyph;
-	k = kh_get(glyphinfo, cache, id);
-	if (k == kh_end(cache)) {
-		glyph = ltk_create_glyph_info(font, id, scale);
-		/* FIXME: error checking with ret */
-		k = kh_put(glyphinfo, cache, id, &ret);
-		kh_value(cache, k) = glyph;
-	} else {
-		glyph = kh_value(cache, k);
-	}
-
-	return glyph;
-}
-
-static khash_t(glyphinfo) *
-ltk_get_glyph_cache(uint16_t font_id, uint16_t font_size) {
-	if (!tm.glyph_cache) ltk_init_text();
-	khint_t k;
-	uint32_t attr = (uint32_t)font_id << 16 + font_size;
-	k = kh_get(glyphcache, tm.glyph_cache, attr);
-	if (k == kh_end(tm.glyph_cache)) {
-		k = ltk_create_glyph_cache(font_id, font_size);
-	}
-	return kh_value(tm.glyph_cache, k);
-}
-
-static khint_t
-ltk_create_glyph_cache(uint16_t font_id, uint16_t font_size) {
-	if (!tm.glyph_cache) ltk_init_text();
-	khash_t(glyphinfo) *cache = kh_init(glyphinfo);
-	int ret;
-	khint_t k;
-	/* I guess I can just ignore ret for now */
-	k = kh_put(glyphcache, tm.glyph_cache, font_id << 16 + font_size, &ret);
-	kh_value(tm.glyph_cache, k) = cache;
-
-	return k;
-}
-
-static void
-ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache) {
-	int k;
-	for (k = kh_begin(cache); k != kh_end(cache); k++) {
-		if (kh_exist(cache, k)) {
-			ltk_destroy_glyph_info(kh_value(cache, k));
-		}
-	}
-	kh_destroy(glyphinfo, cache);
-}
-
-static void
-ltk_load_default_font(void) {
-	FcPattern *match;
-	FcResult result;
-	char *file;
-	int index;
-	uint16_t font;
-
-	if (default_font)
-		tm.fcpattern = FcNameParse(default_font);
-	else
-		tm.fcpattern = FcPatternCreate();
-	FcPatternAddString(tm.fcpattern, FC_FONTFORMAT, "truetype");
-	FcConfigSubstitute(NULL, tm.fcpattern, FcMatchPattern);
-	FcDefaultSubstitute(tm.fcpattern);
-	match = FcFontMatch(NULL, tm.fcpattern, &result);
-
-	FcPatternGetString(match, FC_FILE, 0, (FcChar8 **) &file);
-	FcPatternGetInteger(match, FC_INDEX, 0, &index);
-
-	tm.default_font = ltk_get_font(file, index);
-
-	FcPatternDestroy(match);
-}
-
-static ltk_font *
-ltk_create_font(char *path, uint16_t id, int index) {
-	long len;
-	ltk_font *font = malloc(sizeof(ltk_font));
-	if (!font) err("ltk_create_font");
-	char *contents = read_file(path, &len);
-	/* FIXME: error checking */
-	int offset = stbtt_GetFontOffsetForIndex(contents, index);
-	if (!stbtt_InitFont(&font->info, contents, offset)) {
-		(void)fprintf(stderr, "Failed to load font %s\n", path);
-		exit(1);
-	}
-	font->id = id;
-	font->refs = 0;
-	font->index = index;
-	font->path = strdup(path);
-	if (!font->path) err("ltk_create_font");
-
-	return font;
-}
-
-static void
-ltk_destroy_font(ltk_font *font) {
-	free(font->info.data);
-	free(font);
-}
-
-static ltk_font *
-ltk_load_font(char *path, int index) {
-	ltk_font *font = ltk_create_font(path, tm.font_id_cur++, index);
-	if (tm.num_fonts == tm.fonts_bufsize) {
-		ltk_font *new = realloc(tm.fonts, tm.fonts_bufsize * 2 * sizeof(ltk_font *));
-		if (!new) err("ltk_load_font");
-		tm.fonts = new;
-		tm.fonts_bufsize *= 2;
-	}
-	tm.fonts[tm.num_fonts] = font;
-	tm.num_fonts++;
-	return font;
-}
-
-static ltk_font *
-ltk_get_font(char *path, int index) {
-	ltk_font *font = NULL;
-	for (int i = 0; i < tm.num_fonts; i++) {
-		if (tm.fonts[i]->index == index &&
-		    strcmp(tm.fonts[i]->path, path) == 0) {
-			font = &tm.fonts[i];
-			break;
-		}
-	}
-	if (!font)
-		font = ltk_load_font(path, index);
-	return font;
-}
-
-void
-ltk_text_to_glyphs(ltk_glyph *glyphs, int num_glyphs, char *text, uint16_t font_size,
-    int *x_min, int *y_min, int *x_max, int *y_max) {
-        uint32_t c1, c2 = 0;
-        int gid;
-        int index;
-        char *file;
-        size_t inc = 0;
-        int x = 0, y, kern_advance, ax;
-        int x1_abs, x2_abs;
-        float scale;
-        int ascent, descent, line_gap;
-        *x_min = INT_MAX, *x_max = INT_MIN, *y_min = INT_MAX, *y_max = INT_MIN;
-        ltk_glyph_info *ginfo;
-	if (!tm.default_font)
-		ltk_init_text();
-
-	ltk_font *font = tm.default_font;
-	khash_t(glyphinfo) *glyph_cache = ltk_get_glyph_cache(font->id, font_size);
-
-        scale = stbtt_ScaleForPixelHeight(&font->info, font_size);
-        stbtt_GetFontVMetrics(&font->info, &ascent, &descent, &line_gap);
-        ascent *= scale;
-        descent *= scale;
-
-	c1 = u8_nextmemchar(text, &inc);
-	for (int i = 0; i < num_glyphs; i++) {
-		gid = stbtt_FindGlyphIndex(&font->info, c1);
-		if (!gid) {
-			/* Question: Why does this not work with FcPatternDuplicate? */
-			FcPattern *pat = FcPatternCreate();
-			FcPattern *match;
-			FcResult result;
-			FcPatternAddBool(pat, FC_SCALABLE, 1);
-			FcConfigSubstitute(NULL, pat, FcMatchPattern);
-			FcDefaultSubstitute(pat);
-			FcCharSet *cs = FcCharSetCreate();
-			FcCharSetAddChar(cs, c1);
-			FcPatternAddCharSet(pat, FC_CHARSET, cs);
-			match = FcFontMatch(NULL, pat, &result);
-			FcPatternGetString(match, FC_FILE, 0, &file);
-			FcPatternGetInteger(match, FC_INDEX, 0, &index);
-			font = ltk_get_font(file, index);
-			glyph_cache = ltk_get_glyph_cache(font->id, font_size);
-			FcPatternDestroy(match);
-			FcPatternDestroy(pat);
-			gid = stbtt_FindGlyphIndex(&font->info, c1);
-			scale = stbtt_ScaleForPixelHeight(&font->info, font_size);
-			stbtt_GetFontVMetrics(&font->info, &ascent, &descent, &line_gap);
-			ascent *= scale;
-			descent *= scale;
-		}
-		ginfo = ltk_get_glyph_info(font, gid, scale, glyph_cache);
-		ginfo->refs++;
-                y = ascent + ginfo->yoff;
-		x1_abs = x + ginfo->xoff;
-
-		glyphs[i].x = x1_abs;
-		glyphs[i].y = y;
-
-                stbtt_GetGlyphHMetrics(&font->info, gid, &ax, 0);
-                x += (int) (ax * scale);
-		x2_abs = x;
-
-		glyphs[i].info = ginfo;
-		if (x1_abs < *x_min) *x_min = x1_abs;
-		if (y < *y_min) *y_min = y;
-		if (x2_abs > *x_max) *x_max = x2_abs;
-		if (y + ginfo->h > *y_max) *y_max = y + ginfo->h;
-
-		if (i != num_glyphs - 1) {
-			c2 = u8_nextmemchar(text, &inc);
-			kern_advance = stbtt_GetCodepointKernAdvance(&font->info, c1, c2);
-			x += (int) (kern_advance * scale);
-		}
-		c1 = c2;
-	}
-}
-
-/*
-void
-ltk_unref_glyph(ltk_glyph *glyph, khash_t(glyphinfo) *cache) {
-	int k;
-	if (--glyph->info->refs < 1) {
-		k = kh_get(glyphinfo, cache, glyph->info->id);
-		kh_del(glyphinfo, cache, k);
-		ltk_destroy_glyph_info(glyph->info);
-	}
-}
-
-void
-ltk_unref_glyphs(ltk_glyph *glyphs, int num_glyphs) {
-	for (int i = 0; i < num_glyphs; i++)
-		ltk_unref_glyph(&glyphs[i]);
-}
-*/
diff --git a/text_common.h b/text_common.h
@@ -1,66 +0,0 @@
-/*
- * This file is part of the Lumidify ToolKit (LTK)
- * Copyright (c) 2017, 2018, 2020 lumidify <nobody@lumidify.org>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#ifndef _TEXT_COMMON_H_
-#define _TEXT_COMMON_H_
-
-/*
-Requires: <stdint.h>
-*/
-
-typedef struct ltk_font ltk_font;
-
-/* Contains general info on glyphs that doesn't change regardless of the context */
-typedef struct {
-	int id;
-	unsigned char *alphamap;
-	int w;
-	int h;
-	int xoff; /* x offset from origin to top left corner of glyph */
-	int yoff; /* y offset from origin to top left corner of glyph */
-	unsigned int refs;
-	/* FIXME: does refs need to be long? It could cause problems if a
-	program tries to cache/"keep alive" a lot of pages of text. */
-} ltk_glyph_info;
-
-/* Contains glyph info specific to one run of text */
-typedef struct {
-	ltk_glyph_info *info;
-	int x;
-	int y;
-} ltk_glyph;
-
-uint32_t u8_nextmemchar(const char *s, size_t *i);
-
-size_t u8_strlen(const char *s);
-
-size_t u8_wc_toutf8(char *dest, uint32_t ch);
-
-void ltk_init_default_font(const char *font_name);
-
-void ltk_cleanup_text(void);
-
-void ltk_text_to_glyphs(ltk_glyph *glyphs, int num_glyphs, char *text,
-    uint16_t font_size, int *x_min, int *y_min, int *x_max, int *y_max);
-
-#endif /* _TEXT_COMMON_H_ */
diff --git a/text_line.c b/text_line.c
@@ -28,20 +28,24 @@
 #include <X11/Xutil.h>
 #include "text_common.h"
 #include "text_line.h"
+#include "util.h"
+
+void ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap cm);
+void ltk_cleanup_text(void);
+
+LtkTextLine *ltk_text_line_create(Window window, uint16_t font_size, char *text, int width);
+void ltk_text_line_render(LtkTextLine *tl, LtkColor *bg, LtkColor *fg);
+void ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y);
+void ltk_text_line_set_width(LtkTextLine *tl, int width);
+void ltk_text_line_get_size(LtkTextLine *tl, int *w, int *h);
+void ltk_text_line_destroy(LtkTextLine *tl);
 
-static void err(const char *msg);
 static void ltk_text_line_create_glyphs(struct ltk_text_line *tl);
 static void ltk_text_line_draw_glyph(ltk_glyph *glyph, int xoff, int yoff,
     XImage *img, XColor fg);
 static XImage *ltk_create_ximage(Display *dpy, int w, int h, int depth,
     XColor bg);
 
-static void
-err(const char *msg) {
-	perror(msg);
-	exit(1);
-}
-
 static XImage *
 ltk_create_ximage(Display *dpy, int w, int h, int depth, XColor bg) {
 	XImage *img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, w, h, 32, 0);
@@ -107,7 +111,7 @@ ltk_text_line_render(
 }
 
 static void
-ltk_text_line_create_glyphs(struct ltk_text_line *tl) {
+ltk_text_line_create_glyphs(LtkTextLine *tl) {
 	int x_min, x_max, y_min, y_max;
 	ltk_text_to_glyphs(tl->glyphs, tl->glyph_len, tl->text, tl->font_size,
 	    &x_min, &y_min, &x_max, &y_max);
@@ -118,10 +122,10 @@ ltk_text_line_create_glyphs(struct ltk_text_line *tl) {
 	tl->h = y_max - y_min;
 }
 
-struct ltk_text_line *
-ltk_text_line_create(uint16_t font_size, char *text) {
-	struct ltk_text_line *line = malloc(sizeof(struct ltk_text_line));
-	if (!line) err("ltk_text_line_create");
+LtkTextLine *
+ltk_text_line_create(uint16_t font_size, char *text, int width) {
+	LtkTextLine *line = malloc(sizeof(LtkTextLine));
+	if (!line) ltk_err("ltk_text_line_create (basic)");
 	line->text = text;
 	line->glyph_len = u8_strlen(text);
 	line->glyphs = malloc(line->glyph_len * sizeof(ltk_glyph));
@@ -131,7 +135,7 @@ ltk_text_line_create(uint16_t font_size, char *text) {
 }
 
 void
-ltk_text_line_destroy(struct ltk_text_line *tl) {
+ltk_text_line_destroy(LtkTextLine *tl) {
 	free(tl->text);
 	/* FIXME: Reference count glyph infos */
 	free(tl->glyphs);
diff --git a/text_line.h b/text_line.h
@@ -29,7 +29,7 @@ Requires the following includes:
 <X11/Xlib.h>, <X11/Xutil.h>, <stdint.h>, "text_common.h",
 */
 
-struct ltk_text_line {
+typedef struct {
 	char *text;
 	ltk_glyph *glyphs;
 	size_t glyph_len;
@@ -38,11 +38,15 @@ struct ltk_text_line {
 	int h;
 	int x_min;
 	int y_min;
-};
+} LtkTextLine;
 
-XImage *ltk_text_line_render(struct ltk_text_line *tl, Display *dpy,
-    Window window, GC gc, Colormap colormap, XColor fg, XColor bg);
-struct ltk_text_line *ltk_text_line_create(uint16_t font_size, char *text);
-void ltk_text_line_destroy(struct ltk_text_line *tl);
+void ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap cm);
+void ltk_cleanup_text(void);
+LtkTextLine *ltk_text_line_create(Window window, uint16_t font_size, char *text, int width);
+void ltk_text_line_render(LtkTextLine *tl, LtkColor *bg, LtkColor *fg);
+void ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y);
+void ltk_text_line_set_width(LtkTextLine *tl, int width);
+void ltk_text_line_get_size(LtkTextLine *tl, int *w, int *h);
+void ltk_text_line_destroy(LtkTextLine *tl);
 
 #endif /* _TEXT_LINE_H_ */
diff --git a/text_pango.c b/text_pango.c
@@ -6,9 +6,19 @@
 #include <X11/Xutil.h>
 #include <X11/Xos.h>
 #include <pango/pangoxft.h>
-#include "color.h"
-#include "text_pango.h"
 #include "util.h"
+#include "text.h"
+
+struct LtkTextLine {
+	char *text;
+	uint16_t font_size;
+	int w;
+	int h;
+	Window window;
+	PangoLayout *layout;
+	XftDraw *draw;
+	Pixmap pixmap;
+};
 
 struct {
 	PangoFontMap *fontmap;
@@ -36,25 +46,31 @@ ltk_cleanup_text(void) {
 	/* FIXME: destroy fontmap and context */
 }
 
+void
+ltk_text_line_set_width(LtkTextLine *tl, int width) {
+	/* TODO: Implement! */
+}
+
 LtkTextLine *
 ltk_text_line_create(Window window, uint16_t font_size, char *text, int width) {
 	if (!tm.context)
 		ltk_err("ltk_text_line_create (pango): text not initialized yet");
-	/* FIXME: respect font size */
-	/*
-	PangoFontDescription *desc = pango_font_description_from_string("Sans Bold 27");
-	pango_layout_set_font_description(layout, desc);
-	pango_font_description_free(desc);
-	*/
 	LtkTextLine *line = malloc(sizeof(LtkTextLine));
 	if (!line) ltk_err("ltk_text_line_create (pango)");
 	line->text = text;
 	line->font_size = font_size;
 	line->layout = pango_layout_new(tm.context);
+
 	if (width > 0) {
 		pango_layout_set_width(line->layout, width * PANGO_SCALE);
 		pango_layout_set_wrap(line->layout, PANGO_WRAP_WORD_CHAR);
 	}
+
+	PangoFontDescription *desc = pango_font_description_from_string(tm.default_font);
+	pango_font_description_set_size(desc, font_size * PANGO_SCALE);
+	pango_layout_set_font_description(line->layout, desc);
+	pango_font_description_free(desc);
+
 	pango_layout_set_text(line->layout, text, -1);
 	pango_layout_get_size(line->layout, &line->w, &line->h);
 	line->w /= PANGO_SCALE;
diff --git a/text_pango.h b/text_pango.h
@@ -1,51 +0,0 @@
-/*
- * This file is part of the Lumidify ToolKit (LTK)
- * Copyright (c) 2020 lumidify <nobody@lumidify.org>
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#ifndef _TEXT_PANGO_H_
-#define _TEXT_PANGO_H_
-
-/*
-Requires the following includes:
-<X11/Xlib.h>, <X11/Xutil.h>, <stdint.h>, <pango/pangoxft.h>
-*/
-
-typedef struct {
-	char *text;
-	uint16_t font_size;
-	int w;
-	int h;
-	Window window;
-	PangoLayout *layout;
-	XftDraw *draw;
-	Pixmap pixmap;
-} LtkTextLine;
-
-void ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap cm);
-void ltk_cleanup_text(void);
-LtkTextLine *ltk_text_line_create(Window window, uint16_t font_size, char *text, int width);
-void ltk_text_line_render(LtkTextLine *tl, LtkColor *bg, LtkColor *fg);
-void ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y);
-void ltk_text_line_get_size(LtkTextLine *tl, int *w, int *h);
-void ltk_text_line_destroy(LtkTextLine *tl);
-
-#endif /* _TEXT_PANGO_H_ */
diff --git a/text_stb.c b/text_stb.c
@@ -0,0 +1,579 @@
+/*
+ * This file is part of the Lumidify ToolKit (LTK)
+ * Copyright (c) 2017, 2018, 2020 lumidify <nobody@lumidify.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */
+#include "khash.h"
+#include <fontconfig/fontconfig.h>
+#include "util.h"
+#include "text.h"
+#include "ltk.h"
+
+typedef struct {
+	stbtt_fontinfo info;
+	char *path;
+	int index; /* index in font file */
+	uint16_t id;
+	unsigned int refs;
+} LtkFont;
+
+/* Contains general info on glyphs that doesn't change regardless of the context */
+typedef struct {
+	int id;
+	unsigned char *alphamap;
+	int w;
+	int h;
+	int xoff; /* x offset from origin to top left corner of glyph */
+	int yoff; /* y offset from origin to top left corner of glyph */
+	unsigned int refs;
+	/* FIXME: does refs need to be long? It could cause problems if a
+	program tries to cache/"keep alive" a lot of pages of text. */
+} LtkGlyphInfo;
+
+/* Contains glyph info specific to one run of text */
+typedef struct {
+	LtkGlyphInfo *info;
+	int x;
+	int y;
+} LtkGlyph;
+
+struct LtkTextLine {
+	Window window;
+	XImage *img;
+	char *text;
+	LtkGlyph *glyphs;
+	size_t glyph_len;
+	uint16_t font_size;
+	int w;
+	int h;
+	int x_min;
+	int y_min;
+};
+
+/* Hash definitions */
+/* glyph id -> glyph info struct */
+KHASH_MAP_INIT_INT(glyphinfo, LtkGlyphInfo*)
+/* font path, size -> glyph cache hash */
+KHASH_MAP_INIT_INT(glyphcache, khash_t(glyphinfo)*)
+
+static struct {
+	khash_t(glyphcache) *glyph_cache;
+	LtkFont **fonts;
+	int num_fonts;
+	int fonts_bufsize;
+	FcPattern *fcpattern;
+	LtkFont *default_font;
+	uint16_t font_id_cur;
+	Display *dpy;
+	int screen;
+	Colormap cm;
+} tm = {NULL, NULL, 0, 0, NULL, NULL, 1, NULL, 0, 0};
+
+
+static LtkFont *ltk_get_font(char *path, int index);
+static LtkGlyphInfo *ltk_create_glyph_info(LtkFont *font, int id,
+    float scale);
+static void ltk_destroy_glyph_info(LtkGlyphInfo *gi);
+static LtkGlyphInfo *ltk_get_glyph_info(LtkFont *font, int id,
+    float scale, khash_t(glyphinfo) *cache);
+static khash_t(glyphinfo) *ltk_get_glyph_cache(uint16_t font_id,
+    uint16_t font_size);
+static khint_t ltk_create_glyph_cache(uint16_t font_id, uint16_t font_size);
+static void ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache);
+static void ltk_load_default_font(char *name);
+static LtkFont *ltk_create_font(char *path, uint16_t id, int index);
+static void ltk_destroy_font(LtkFont *font);
+static LtkFont *ltk_load_font(char *path, int index);
+static LtkFont *ltk_get_font(char *path, int index);
+static void ltk_text_to_glyphs(LtkGlyph *glyphs, int num_glyphs, char *text,
+    uint16_t font_size, int *x_min, int *y_min, int *x_max, int *y_max);
+static void ltk_text_line_create_glyphs(LtkTextLine *tl);
+static void ltk_text_line_draw_glyph(LtkGlyph *glyph, int xoff, int yoff,
+    XImage *img, XColor fg);
+static XImage *ltk_create_ximage(int w, int h, int depth, XColor bg);
+
+
+/* These unicode routines are taken from
+ * https://github.com/JeffBezanson/cutef8 */
+
+static size_t u8_wc_toutf8(char *dest, uint32_t ch);
+static size_t u8_strlen(const char *s);
+static uint32_t u8_nextmemchar(const char *s, size_t *i);
+
+/* is c the start of a utf8 sequence? */
+#define isutf(c) (((c)&0xC0)!=0x80)
+
+static const uint32_t offsetsFromUTF8[6] = {
+    0x00000000UL, 0x00003080UL, 0x000E2080UL,
+    0x03C82080UL, 0xFA082080UL, 0x82082080UL
+};
+
+/* next character without NUL character terminator */
+static uint32_t u8_nextmemchar(const char *s, size_t *i) {
+    uint32_t ch = 0;
+    size_t sz = 0;
+    do {
+        ch <<= 6;
+        ch += (unsigned char)s[(*i)++];
+        sz++;
+    } while (!isutf(s[*i]));
+    ch -= offsetsFromUTF8[sz-1];
+
+    return ch;
+}
+
+/* number of characters in NUL-terminated string */
+static size_t u8_strlen(const char *s) {
+    size_t count = 0;
+    size_t i = 0, lasti;
+
+    while (1) {
+        lasti = i;
+        while (s[i] > 0)
+            i++;
+        count += (i-lasti);
+        if (s[i++]==0) break;
+        (void)(isutf(s[++i]) || isutf(s[++i]) || ++i);
+        count++;
+    }
+    return count;
+}
+
+static size_t u8_wc_toutf8(char *dest, uint32_t ch) {
+    if (ch < 0x80) {
+        dest[0] = (char)ch;
+        return 1;
+    }
+    if (ch < 0x800) {
+        dest[0] = (ch>>6) | 0xC0;
+        dest[1] = (ch & 0x3F) | 0x80;
+        return 2;
+    }
+    if (ch < 0x10000) {
+        dest[0] = (ch>>12) | 0xE0;
+        dest[1] = ((ch>>6) & 0x3F) | 0x80;
+        dest[2] = (ch & 0x3F) | 0x80;
+        return 3;
+    }
+    if (ch < 0x110000) {
+        dest[0] = (ch>>18) | 0xF0;
+        dest[1] = ((ch>>12) & 0x3F) | 0x80;
+        dest[2] = ((ch>>6) & 0x3F) | 0x80;
+        dest[3] = (ch & 0x3F) | 0x80;
+        return 4;
+    }
+    return 0;
+}
+
+void
+ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap cm) {
+	tm.fonts_bufsize = 1;
+	tm.glyph_cache = kh_init(glyphcache);
+	tm.fonts = malloc(sizeof(LtkFont *));
+	if (!tm.fonts) ltk_err("ltk_init_text");
+	ltk_load_default_font(default_font);
+	tm.dpy = dpy;
+	tm.screen = screen;
+	tm.cm = cm;
+}
+
+void
+ltk_cleanup_text(void) {
+	for (int i = 0; i < tm.num_fonts; i++) {
+		ltk_destroy_font(&tm.fonts[i]);
+	}
+	if (!tm.glyph_cache) return;
+	for (int k = kh_begin(tm.glyph_cache); k != kh_end(tm.glyph_cache); k++) {
+		if (kh_exist(tm.glyph_cache, k)) {
+			ltk_destroy_glyph_cache(kh_value(tm.glyph_cache, k));
+		}
+	}
+	kh_destroy(glyphcache, tm.glyph_cache);
+}
+
+static LtkGlyphInfo *
+ltk_create_glyph_info(LtkFont *font, int id, float scale) {
+	LtkGlyphInfo *glyph = malloc(sizeof(LtkGlyphInfo));
+	if (!glyph) ltk_err("ltk_create_glyph_info");
+
+	glyph->id = id;
+	glyph->refs = 0;
+	glyph->alphamap = stbtt_GetGlyphBitmap(
+		&font->info, scale, scale, id, &glyph->w,
+		&glyph->h, &glyph->xoff, &glyph->yoff
+	);
+
+	return glyph;
+}
+
+static void
+ltk_destroy_glyph_info(LtkGlyphInfo *gi) {
+	free(gi->alphamap);
+	free(gi);
+}
+
+static LtkGlyphInfo *
+ltk_get_glyph_info(LtkFont *font, int id, float scale, khash_t(glyphinfo) *cache) {
+	int ret;
+	khint_t k;
+	LtkGlyphInfo *glyph;
+	k = kh_get(glyphinfo, cache, id);
+	if (k == kh_end(cache)) {
+		glyph = ltk_create_glyph_info(font, id, scale);
+		/* FIXME: error checking with ret */
+		k = kh_put(glyphinfo, cache, id, &ret);
+		kh_value(cache, k) = glyph;
+	} else {
+		glyph = kh_value(cache, k);
+	}
+
+	return glyph;
+}
+
+static khash_t(glyphinfo) *
+ltk_get_glyph_cache(uint16_t font_id, uint16_t font_size) {
+	khint_t k;
+	uint32_t attr = (uint32_t)font_id << 16 + font_size;
+	k = kh_get(glyphcache, tm.glyph_cache, attr);
+	if (k == kh_end(tm.glyph_cache)) {
+		k = ltk_create_glyph_cache(font_id, font_size);
+	}
+	return kh_value(tm.glyph_cache, k);
+}
+
+static khint_t
+ltk_create_glyph_cache(uint16_t font_id, uint16_t font_size) {
+	khash_t(glyphinfo) *cache = kh_init(glyphinfo);
+	int ret;
+	khint_t k;
+	/* I guess I can just ignore ret for now */
+	k = kh_put(glyphcache, tm.glyph_cache, font_id << 16 + font_size, &ret);
+	kh_value(tm.glyph_cache, k) = cache;
+
+	return k;
+}
+
+static void
+ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache) {
+	int k;
+	for (k = kh_begin(cache); k != kh_end(cache); k++) {
+		if (kh_exist(cache, k)) {
+			ltk_destroy_glyph_info(kh_value(cache, k));
+		}
+	}
+	kh_destroy(glyphinfo, cache);
+}
+
+static void
+ltk_load_default_font(char *name) {
+	FcPattern *match;
+	FcResult result;
+	char *file;
+	int index;
+	uint16_t font;
+
+	tm.fcpattern = FcNameParse(name);
+	/*tm.fcpattern = FcPatternCreate();*/
+	FcPatternAddString(tm.fcpattern, FC_FONTFORMAT, "truetype");
+	FcConfigSubstitute(NULL, tm.fcpattern, FcMatchPattern);
+	FcDefaultSubstitute(tm.fcpattern);
+	match = FcFontMatch(NULL, tm.fcpattern, &result);
+
+	FcPatternGetString(match, FC_FILE, 0, (FcChar8 **) &file);
+	FcPatternGetInteger(match, FC_INDEX, 0, &index);
+
+	tm.default_font = ltk_get_font(file, index);
+
+	FcPatternDestroy(match);
+}
+
+static LtkFont *
+ltk_create_font(char *path, uint16_t id, int index) {
+	unsigned long len;
+	LtkFont *font = malloc(sizeof(LtkFont));
+	if (!font) ltk_err("ltk_create_font (stb)");
+	char *contents = ltk_read_file(path, &len);
+	/* FIXME: error checking */
+	int offset = stbtt_GetFontOffsetForIndex(contents, index);
+	if (!stbtt_InitFont(&font->info, contents, offset)) {
+		(void)fprintf(stderr, "Failed to load font %s\n", path);
+		exit(1);
+	}
+	font->id = id;
+	font->refs = 0;
+	font->index = index;
+	font->path = strdup(path);
+	if (!font->path) ltk_err("ltk_create_font");
+
+	return font;
+}
+
+static void
+ltk_destroy_font(LtkFont *font) {
+	/* FIXME: why does this cause error? */
+	free(font->info.data);
+	free(font);
+}
+
+static LtkFont *
+ltk_load_font(char *path, int index) {
+	LtkFont *font = ltk_create_font(path, tm.font_id_cur++, index);
+	if (tm.num_fonts == tm.fonts_bufsize) {
+		LtkFont *new = realloc(tm.fonts, tm.fonts_bufsize * 2 * sizeof(LtkFont *));
+		if (!new) ltk_err("ltk_load_font");
+		tm.fonts = new;
+		tm.fonts_bufsize *= 2;
+	}
+	tm.fonts[tm.num_fonts] = font;
+	tm.num_fonts++;
+	return font;
+}
+
+static LtkFont *
+ltk_get_font(char *path, int index) {
+	LtkFont *font = NULL;
+	for (int i = 0; i < tm.num_fonts; i++) {
+		if (tm.fonts[i]->index == index &&
+		    strcmp(tm.fonts[i]->path, path) == 0) {
+			font = &tm.fonts[i];
+			break;
+		}
+	}
+	if (!font)
+		font = ltk_load_font(path, index);
+	return font;
+}
+
+static void
+ltk_text_to_glyphs(LtkGlyph *glyphs, int num_glyphs, char *text, uint16_t font_size,
+    int *x_min, int *y_min, int *x_max, int *y_max) {
+        uint32_t c1, c2 = 0;
+        int gid;
+        int index;
+        char *file;
+        size_t inc = 0;
+        int x = 0, y, kern_advance, ax;
+        int x1_abs, x2_abs;
+        float scale;
+        int ascent, descent, line_gap;
+        *x_min = INT_MAX, *x_max = INT_MIN, *y_min = INT_MAX, *y_max = INT_MIN;
+        LtkGlyphInfo *ginfo;
+
+	LtkFont *font = tm.default_font;
+	khash_t(glyphinfo) *glyph_cache = ltk_get_glyph_cache(font->id, font_size);
+
+        scale = stbtt_ScaleForPixelHeight(&font->info, font_size);
+        stbtt_GetFontVMetrics(&font->info, &ascent, &descent, &line_gap);
+        ascent *= scale;
+        descent *= scale;
+
+	c1 = u8_nextmemchar(text, &inc);
+	for (int i = 0; i < num_glyphs; i++) {
+		gid = stbtt_FindGlyphIndex(&font->info, c1);
+		if (!gid) {
+			/* Question: Why does this not work with FcPatternDuplicate? */
+			FcPattern *pat = FcPatternCreate();
+			FcPattern *match;
+			FcResult result;
+			FcPatternAddBool(pat, FC_SCALABLE, 1);
+			FcConfigSubstitute(NULL, pat, FcMatchPattern);
+			FcDefaultSubstitute(pat);
+			FcCharSet *cs = FcCharSetCreate();
+			FcCharSetAddChar(cs, c1);
+			FcPatternAddCharSet(pat, FC_CHARSET, cs);
+			match = FcFontMatch(NULL, pat, &result);
+			FcPatternGetString(match, FC_FILE, 0, &file);
+			FcPatternGetInteger(match, FC_INDEX, 0, &index);
+			font = ltk_get_font(file, index);
+			glyph_cache = ltk_get_glyph_cache(font->id, font_size);
+			FcPatternDestroy(match);
+			FcPatternDestroy(pat);
+			gid = stbtt_FindGlyphIndex(&font->info, c1);
+			scale = stbtt_ScaleForPixelHeight(&font->info, font_size);
+			stbtt_GetFontVMetrics(&font->info, &ascent, &descent, &line_gap);
+			ascent *= scale;
+			descent *= scale;
+		}
+		ginfo = ltk_get_glyph_info(font, gid, scale, glyph_cache);
+		ginfo->refs++;
+                y = ascent + ginfo->yoff;
+		x1_abs = x + ginfo->xoff;
+
+		glyphs[i].x = x1_abs;
+		glyphs[i].y = y;
+
+                stbtt_GetGlyphHMetrics(&font->info, gid, &ax, 0);
+                x += (int) (ax * scale);
+		x2_abs = x;
+
+		glyphs[i].info = ginfo;
+		if (x1_abs < *x_min) *x_min = x1_abs;
+		if (y < *y_min) *y_min = y;
+		if (x2_abs > *x_max) *x_max = x2_abs;
+		if (y + ginfo->h > *y_max) *y_max = y + ginfo->h;
+
+		if (i != num_glyphs - 1) {
+			c2 = u8_nextmemchar(text, &inc);
+			kern_advance = stbtt_GetCodepointKernAdvance(&font->info, c1, c2);
+			x += (int) (kern_advance * scale);
+		}
+		c1 = c2;
+	}
+}
+
+/*
+void
+ltk_unref_glyph(ltk_glyph *glyph, khash_t(glyphinfo) *cache) {
+	int k;
+	if (--glyph->info->refs < 1) {
+		k = kh_get(glyphinfo, cache, glyph->info->id);
+		kh_del(glyphinfo, cache, k);
+		ltk_destroy_glyph_info(glyph->info);
+	}
+}
+
+void
+ltk_unref_glyphs(ltk_glyph *glyphs, int num_glyphs) {
+	for (int i = 0; i < num_glyphs; i++)
+		ltk_unref_glyph(&glyphs[i]);
+}
+*/
+
+/* FIXME: Error checking that tm has been initialized */
+
+static XImage *
+ltk_create_ximage(int w, int h, int depth, XColor bg) {
+	XImage *img = XCreateImage(tm.dpy, CopyFromParent, depth, ZPixmap, 0, NULL, w, h, 32, 0);
+	img->data = calloc(img->bytes_per_line, img->height);
+	XInitImage(img);
+
+	int b;
+	for (int i = 0; i < h; i++) {
+		b = img->bytes_per_line * i;
+		for (int j = 0; j < w; j++) {
+			img->data[b++] = bg.blue / 257;
+			img->data[b++] = bg.green / 257;
+			img->data[b++] = bg.red / 257;
+			b++;
+		}
+	}
+
+	return img;
+}
+
+/* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */
+static void
+ltk_text_line_draw_glyph(LtkGlyph *glyph, int xoff, int yoff, XImage *img, XColor fg) {
+	int x = glyph->x + xoff;
+	int y = glyph->y + yoff;
+	double a;
+	int b;
+	for (int i = 0; i < glyph->info->h; i++) {
+		for (int j = 0; j < glyph->info->w; j++) {
+			if (y + i >= img->height || x + j >= img->width ||
+			    y + i < 0 || x + i < 0)
+				continue;
+			b = (y + i) * img->bytes_per_line + (x + j) * 4;
+			a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0;
+			img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)img->data[b] * 257) / 257;
+			img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)img->data[b + 1] * 257) / 257;
+			img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)img->data[b + 2] * 257) / 257;
+		}
+	}
+}
+
+void
+ltk_text_line_render(
+	LtkTextLine *tl,
+	LtkColor *bg,
+	LtkColor *fg)
+{
+	LtkGlyph *glyph;
+
+	XWindowAttributes attrs;
+	XGetWindowAttributes(tm.dpy, tl->window, &attrs);
+	int depth = attrs.depth;
+	/* FIXME: pass old image; if it has same dimensions, just clear it */
+	if (tl->img)
+		XDestroyImage(tl->img);
+	tl->img = ltk_create_ximage(tl->w, tl->h, depth, bg->xcolor);
+	for (int i = 0; i < tl->glyph_len; i++) {
+		ltk_text_line_draw_glyph(&tl->glyphs[i], -tl->x_min, -tl->y_min, tl->img, fg->xcolor);
+	}
+}
+
+/* FIXME: error checking if img is rendered yet, tm initialized, etc. */
+void
+ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y) {
+	XPutImage(tm.dpy, tl->window, gc, tl->img, 0, 0, x, y, tl->w, tl->h);
+}
+
+void
+ltk_text_line_set_width(LtkTextLine *tl, int width) {
+	/* FIXME: implement */
+}
+
+void
+ltk_text_line_get_size(LtkTextLine *tl, int *w, int *h) {
+	*w = tl->w;
+	*h = tl->h;
+}
+
+static void
+ltk_text_line_create_glyphs(LtkTextLine *tl) {
+	int x_min, x_max, y_min, y_max;
+	ltk_text_to_glyphs(tl->glyphs, tl->glyph_len, tl->text, tl->font_size,
+	    &x_min, &y_min, &x_max, &y_max);
+	/* for drawing the glyphs at the right position on the image */
+	tl->x_min = x_min;
+	tl->y_min = y_min;
+	tl->w = x_max - x_min;
+	tl->h = y_max - y_min;
+}
+
+LtkTextLine *
+ltk_text_line_create(Window window, uint16_t font_size, char *text, int width) {
+	LtkTextLine *line = malloc(sizeof(LtkTextLine));
+	if (!line) ltk_err("ltk_text_line_create (basic)");
+	line->window = window;
+	line->img = NULL;
+	line->text = text;
+	line->glyph_len = u8_strlen(text);
+	line->glyphs = malloc(line->glyph_len * sizeof(LtkGlyph));
+	line->font_size = font_size;
+	ltk_text_line_create_glyphs(line);
+	return line;
+}
+
+void
+ltk_text_line_destroy(LtkTextLine *tl) {
+	free(tl->text);
+	/* FIXME: Reference count glyph infos */
+	free(tl->glyphs);
+	free(tl);
+}
diff --git a/util.c b/util.c
@@ -22,6 +22,7 @@
  */
 
 #include <stdio.h>
+#include <stdlib.h>
 
 void
 ltk_err(const char *msg) {
@@ -37,6 +38,7 @@ ltk_read_file(const char *path, unsigned long *len) {
 	fseek(f, 0, SEEK_END);
 	*len = ftell(f);
 	fseek(f, 0, SEEK_SET);
+	/* FIXME: error checking */
 	file_contents = malloc(*len + 1);
 	fread(file_contents, 1, *len, f);
 	file_contents[*len] = '\0';