commit 38fe277a39863666574934ce221dcf9bd9ee4cad
parent 5aff106f8dd7610fe450e26880243949481ba5af
Author: lumidify <nobody@lumidify.org>
Date:   Thu, 10 Sep 2020 22:25:49 +0200
Add pango text support; break other text support :(
Diffstat:
| M | Makefile |  |  | 2 | +- | 
| M | README.md |  |  | 5 | +++++ | 
| M | button.c |  |  | 140 | ++++++++++++++++++++++++++++++++++++++++++------------------------------------- | 
| M | button.h |  |  | 31 | ++++++++++++++----------------- | 
| A | color.c |  |  | 17 | +++++++++++++++++ | 
| A | color.h |  |  | 11 | +++++++++++ | 
| M | config.mk |  |  | 5 | +++-- | 
| A | defs.h |  |  | 2 | ++ | 
| M | draw.c |  |  | 2 | +- | 
| M | grid.c |  |  | 2 | +- | 
| M | ltk.c |  |  | 34 | ++++++++++++++-------------------- | 
| M | ltk.h |  |  | 4 | +++- | 
| A | text.h |  |  | 16 | ++++++++++++++++ | 
| A | text_pango.c |  |  | 95 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | text_pango.h |  |  | 51 | +++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | util.c |  |  | 46 | ++++++++++++++++++++++++++++++++++++++++++++++ | 
| M | util.h |  |  | 26 | ++++++++++++++++++++++++++ | 
17 files changed, 380 insertions(+), 109 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,6 +1,6 @@
 include config.mk
 
-OBJ = text_line.o text_common.o stb_truetype.o ltk.o ini.o grid.o button.o draw.o
+OBJ += color.o util.o ltk.o ini.o grid.o button.o draw.o
 
 ltk: $(OBJ) $(COMPATOBJ)
 	$(CC) -o $@ $(OBJ) $(COMPATOBJ) $(LDFLAGS)
diff --git a/README.md b/README.md
@@ -1,5 +1,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 test:
 
 make
diff --git a/button.c b/button.c
@@ -29,9 +29,8 @@
 #include <X11/Xutil.h>
 #include "util.h"
 #include "khash.h"
-#include "text_common.h"
 #include "ltk.h"
-#include "text_line.h"
+#include "text.h"
 #include "button.h"
 
 static void ltk_button_draw(ltk_button *button);
@@ -40,11 +39,6 @@ static ltk_button *ltk_button_create(ltk_window *window,
     const char *id, const char *text);
 static void ltk_button_destroy(ltk_button *button, int shallow);
 
-static void ltk_grid_cmd_create(
-    ltk_window *window,
-    char **tokens,
-    size_t num_tokens);
-
 void
 ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value) {
 	ltk_theme *theme = window->theme;
@@ -57,23 +51,32 @@ ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value) 
 	} else if (strcmp(prop, "pad") == 0) {
 		theme->button->pad = atoi(value);
 	} else if (strcmp(prop, "border") == 0) {
-		ltk_create_xcolor(window, value, &theme->button->border);
+		ltk_color_create(window->dpy, window->screen, window->cm,
+		    value, &theme->button->border);
 	} else if (strcmp(prop, "fill") == 0) {
-		ltk_create_xcolor(window, value, &theme->button->fill);
+		ltk_color_create(window->dpy, window->screen, window->cm,
+		    value, &theme->button->fill);
 	} else if (strcmp(prop, "border_pressed") == 0) {
-		ltk_create_xcolor(window, value, &theme->button->border_pressed);
+		ltk_color_create(window->dpy, window->screen, window->cm,
+		    value, &theme->button->border_pressed);
 	} else if (strcmp(prop, "fill_pressed") == 0) {
-		ltk_create_xcolor(window, value, &theme->button->fill_pressed);
+		ltk_color_create(window->dpy, window->screen, window->cm,
+		    value, &theme->button->fill_pressed);
 	} else if (strcmp(prop, "border_active") == 0) {
-		ltk_create_xcolor(window, value, &theme->button->border_active);
+		ltk_color_create(window->dpy, window->screen, window->cm,
+		    value, &theme->button->border_active);
 	} else if (strcmp(prop, "fill_active") == 0) {
-		ltk_create_xcolor(window, value, &theme->button->fill_active);
+		ltk_color_create(window->dpy, window->screen, window->cm,
+		    value, &theme->button->fill_active);
 	} else if (strcmp(prop, "border_disabled") == 0) {
-		ltk_create_xcolor(window, value, &theme->button->border_disabled);
+		ltk_color_create(window->dpy, window->screen, window->cm,
+		    value, &theme->button->border_disabled);
 	} else if (strcmp(prop, "fill_disabled") == 0) {
-		ltk_create_xcolor(window, value, &theme->button->fill_disabled);
+		ltk_color_create(window->dpy, window->screen, window->cm,
+		    value, &theme->button->fill_disabled);
 	} else if (strcmp(prop, "text_color") == 0) {
-		ltk_create_xcolor(window, value, &theme->button->text_color);
+		ltk_color_create(window->dpy, window->screen, window->cm,
+		    value, &theme->button->text_color);
 	} else {
 		(void)printf("WARNING: Unknown property \"%s\" for button style.\n", prop);
 	}
@@ -85,64 +88,69 @@ ltk_button_draw(ltk_button *button) {
 	ltk_button_theme *theme = window->theme->button;
 	ltk_rect rect = button->widget.rect;
 	int bw = theme->border_width;
-	XColor border;
-	XColor fill;
-	XImage *img = NULL;
+	LtkColor *border;
+	LtkColor *fill;
 	switch (button->widget.state) {
 	case LTK_NORMAL:
-		img = button->text_normal;
-		border = theme->border;
-		fill = theme->fill;
+		border = &theme->border;
+		fill = &theme->fill;
 		break;
 	case LTK_PRESSED:
-		img = button->text_pressed;
-		border = theme->border_pressed;
-		fill = theme->fill_pressed;
+		border = &theme->border_pressed;
+		fill = &theme->fill_pressed;
 		break;
 	case LTK_ACTIVE:
-		img = button->text_active;
-		border = theme->border_active;
-		fill = theme->fill_active;
+		border = &theme->border_active;
+		fill = &theme->fill_active;
 		break;
 	case LTK_DISABLED:
-		img = button->text_disabled;
-		border = theme->border_disabled;
-		fill = theme->fill_disabled;
+		border = &theme->border_disabled;
+		fill = &theme->fill_disabled;
 		break;
 	default:
 		ltk_fatal("No style found for button!\n");
 	}
-	XSetForeground(window->dpy, window->gc, fill.pixel);
-	XFillRectangle(window->dpy, window->xwindow, window->gc, rect.x, rect.y, rect.w, rect.h);
+	XSetForeground(window->dpy, window->gc, fill->xcolor.pixel);
+	XFillRectangle(window->dpy, window->xwindow, window->gc, rect.x,
+	    rect.y, rect.w, rect.h);
 	/* FIXME: Why did I do this? */
 	if (bw < 1) return;
-	XSetForeground(window->dpy, window->gc, border.pixel);
-	XSetLineAttributes(window->dpy, window->gc, bw, LineSolid, CapButt, JoinMiter);
-	XDrawRectangle(window->dpy, window->xwindow, window->gc, rect.x + bw / 2, rect.y + bw / 2, rect.w - bw, rect.h - bw);
-	if (!img) {
-		img = ltk_text_line_render(button->tl, window->dpy,
-		    window->xwindow, window->gc, window->cm,
-		    theme->text_color, fill);
-		switch (button->widget.state) {
-			case LTK_NORMAL:
-				button->text_normal = img;
-				break;
-			case LTK_PRESSED:
-				button->text_pressed = img;
-				break;
-			case LTK_ACTIVE:
-				button->text_active = img;
-				break;
-			case LTK_DISABLED:
-				button->text_disabled = img;
-				break;
-		}
-	}
-	int text_x = rect.x + (rect.w - button->tl->w) / 2;
-	int text_y = rect.y + (rect.h - button->tl->h) / 2;
-	XPutImage(window->dpy, window->xwindow, window->gc, img, 0, 0, text_x, text_y, button->tl->w, button->tl->h);
+	XSetForeground(window->dpy, window->gc, border->xcolor.pixel);
+	XSetLineAttributes(window->dpy, window->gc, bw, LineSolid,
+	    CapButt, JoinMiter);
+	XDrawRectangle(window->dpy, window->xwindow, window->gc,
+	    rect.x + bw / 2, rect.y + bw / 2, rect.w - bw, rect.h - bw);
+
+	int text_w, text_h;
+	ltk_text_line_get_size(button->tl, &text_w, &text_h);
+	int text_x = rect.x + (rect.w - text_w) / 2;
+	int text_y = rect.y + (rect.h - text_h) / 2;
+	ltk_text_line_draw(button->tl, window->gc, text_x, text_y);
 }
 
+static void
+ltk_button_change_state(ltk_button *button) {
+	ltk_window *window = button->widget.window;
+	ltk_button_theme *theme = window->theme->button;
+	LtkColor *fill;
+	switch (button->widget.state) {
+	case LTK_NORMAL:
+		fill = &theme->fill;
+		break;
+	case LTK_PRESSED:
+		fill = &theme->fill_pressed;
+		break;
+	case LTK_ACTIVE:
+		fill = &theme->fill_active;
+		break;
+	case LTK_DISABLED:
+		fill = &theme->fill_disabled;
+		break;
+	default:
+		ltk_fatal("No style found for button!\n");
+	}
+	ltk_text_line_render(button->tl, fill, &theme->text_color);
+}
 
 static void
 ltk_button_mouse_release(ltk_button *button, XEvent event) {
@@ -155,17 +163,17 @@ ltk_button_create(ltk_window *window, const char *id, const char *text) {
 	if (!button) ltk_fatal("ERROR: Unable to allocate memory for ltk_button.\n");
 
 	ltk_fill_widget_defaults(&button->widget, id, window,
-	    <k_button_draw, <k_button_destroy, 1, LTK_BUTTON);
+	    <k_button_draw, <k_button_change_state, <k_button_destroy, 1, LTK_BUTTON);
 	button->widget.mouse_release = <k_button_mouse_release;
 	uint16_t font_size = window->theme->window->font_size;
-	button->tl = ltk_text_line_create(font_size, strdup(text));
+	button->tl = ltk_text_line_create(window->xwindow, font_size, strdup(text), -1);
+	int text_w, text_h;
+	ltk_text_line_get_size(button->tl, &text_w, &text_h);
 	ltk_button_theme *theme = window->theme->button;
-	button->widget.rect.w = button->tl->w + theme->border_width * 2 + theme->pad * 2;
-	button->widget.rect.h = button->tl->h + theme->border_width * 2 + theme->pad * 2;
-	button->text_normal = NULL;
-	button->text_pressed = NULL;
-	button->text_active = NULL;
-	button->text_disabled = NULL;
+	button->widget.rect.w = text_w + theme->border_width * 2 + theme->pad * 2;
+	button->widget.rect.h = text_h + theme->border_width * 2 + theme->pad * 2;
+	/* render text */
+	ltk_button_change_state(button);
 
 	return button;
 }
diff --git a/button.h b/button.h
@@ -1,6 +1,6 @@
 /*
  * This file is part of the Lumidify ToolKit (LTK)
- * Copyright (c) 2016, 2017, 2018 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2016, 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
@@ -24,33 +24,30 @@
 #ifndef _LTK_BUTTON_H_
 #define _LTK_BUTTON_H_
 
-/* Requires the following includes: <X11/Xlib.h>, "ltk.h" */
+/* Requires the following includes: <X11/Xlib.h>, "ltk.h", "color.h", "text.h" */
 
 typedef struct {
 	ltk_widget widget;
-	struct ltk_text_line *tl;
-	XImage *text_normal;
-	XImage *text_active;
-	XImage *text_pressed;
-	XImage *text_disabled;
+	LtkTextLine *tl;
+	Pixmap text_pixmap;
 } ltk_button;
 
 typedef struct ltk_button_theme {
 	int border_width;
-	XColor text_color;
+	LtkColor text_color;
 	int pad;
 
-	XColor border;
-	XColor fill;
+	LtkColor border;
+	LtkColor fill;
 
-	XColor border_pressed;
-	XColor fill_pressed;
+	LtkColor border_pressed;
+	LtkColor fill_pressed;
 
-	XColor border_active;
-	XColor fill_active;
+	LtkColor border_active;
+	LtkColor fill_active;
 
-	XColor border_disabled;
-	XColor fill_disabled;
+	LtkColor border_disabled;
+	LtkColor fill_disabled;
 } ltk_button_theme;
 
 void ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value);
@@ -60,4 +57,4 @@ void ltk_button_cmd(
     char **tokens,
     size_t num_tokens);
 
-#endif
+#endif /* _LTK_BUTTON_H_ */
diff --git a/color.c b/color.c
@@ -0,0 +1,17 @@
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include "util.h"
+#include "color.h"
+
+void
+ltk_color_create(Display *dpy, int screen, Colormap cm, const char *hex, LtkColor *col) {
+	if (!XParseColor(dpy, cm, hex, &col->xcolor)) {
+		/* FIXME: better error reporting */
+		ltk_err("ltk_color_create");
+	}
+	XAllocColor(dpy, cm, &col->xcolor);
+	/* FIXME: replace with XftColorAllocValue; error checking */
+	#ifdef USE_XFT
+	XftColorAllocName(dpy, DefaultVisual(dpy, screen), cm, hex, &col->xftcolor);
+	#endif
+}
diff --git a/color.h b/color.h
@@ -0,0 +1,11 @@
+#include "defs.h"
+#ifdef USE_XFT
+  #include <X11/Xft/Xft.h>
+#endif
+
+typedef struct {
+	XColor xcolor;
+	#ifdef USE_XFT
+	XftColor xftcolor;
+	#endif
+} LtkColor;
diff --git a/config.mk b/config.mk
@@ -1,7 +1,8 @@
 VERSION = -999
 
-CFLAGS = -g -std=c99 -w -fcommon -Wall -Werror -Wextra `pkg-config --cflags x11 fontconfig` -pedantic
-LDFLAGS = -lm `pkg-config --libs x11 fontconfig`
+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
 
 # OpenBSD
 COMPATOBJ = 
diff --git a/defs.h b/defs.h
@@ -0,0 +1,2 @@
+#define USE_PANGO
+#define USE_XFT
diff --git a/draw.c b/draw.c
@@ -80,7 +80,7 @@ ltk_draw_create(ltk_window *window, const char *id, int w, int h, const char *co
 	if (!draw) ltk_fatal("ERROR: Unable to allocate memory for ltk_draw.\n");
 
 	ltk_fill_widget_defaults(&draw->widget, id, window,
-	    <k_draw_draw, <k_draw_destroy, 1, LTK_DRAW);
+	    <k_draw_draw, NULL, <k_draw_destroy, 1, LTK_DRAW);
 	draw->widget.resize = <k_draw_resize;
 	draw->widget.rect.w = w;
 	draw->widget.rect.h = h;
diff --git a/grid.c b/grid.c
@@ -100,7 +100,7 @@ ltk_grid_create(ltk_window *window, const char *id, int rows, int columns) {
 	ltk_grid *grid = malloc(sizeof(ltk_grid));
 
 	ltk_fill_widget_defaults(&grid->widget, id, window, <k_grid_draw,
-	    <k_grid_destroy, 0, LTK_GRID);
+	    NULL, <k_grid_destroy, 0, LTK_GRID);
 	grid->widget.mouse_press = <k_grid_mouse_press;
 	grid->widget.mouse_release = <k_grid_mouse_release;
 	grid->widget.motion_notify = <k_grid_motion_notify;
diff --git a/ltk.c b/ltk.c
@@ -35,10 +35,9 @@
 #include "util.h"
 #include "khash.h"
 #include "ini.h"
-#include "text_common.h"
+#include "text.h"
 #include "ltk.h"
 #include "grid.h"
-#include "text_line.h"
 #include "button.h"
 #include "draw.h"
 
@@ -54,22 +53,6 @@ static size_t tokens_bufsize = 0;
 static size_t cmd_len = 0;
 static size_t tokens_len = 0;
 
-char *
-ltk_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;
-}
-
 static ltk_rect
 ltk_rect_union(ltk_rect r1, ltk_rect r2) {
 	ltk_rect u;
@@ -322,7 +305,6 @@ ltk_create_window(const char *theme_path, const char *title, int x, int y, unsig
 	window->wm_delete_msg = XInternAtom(window->dpy, "WM_DELETE_WINDOW", False);
 
 	ltk_window_theme *wtheme = window->theme->window;
-	ltk_init_default_font(wtheme->font);
 	window->xwindow =
 	    XCreateSimpleWindow(window->dpy, DefaultRootWindow(window->dpy), x, y,
 				w, h, wtheme->border_width,
@@ -335,6 +317,8 @@ ltk_create_window(const char *theme_path, const char *title, int x, int y, unsig
 	XSetWMProtocols(window->dpy, window->xwindow, &window->wm_delete_msg, 1);
 	window->root_widget = NULL;
 
+	ltk_init_text(wtheme->font, window->dpy, window->screen, window->cm);
+
 	window->other_event = <k_window_other_event;
 
 	window->rect.w = 0;
@@ -466,6 +450,8 @@ ltk_window_remove_active_widget(ltk_window *window) {
 	while (widget) {
 		widget->state = LTK_NORMAL;
 		widget->active_widget = NULL;
+		if (widget->change_state)
+			widget->change_state(widget);
 		if (widget->needs_redraw)
 			ltk_window_invalidate_rect(window, widget->rect);
 		widget = widget->parent;
@@ -488,7 +474,8 @@ ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
 
 void
 ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
-    void (*draw) (void *), void (*destroy) (void *, int), unsigned int needs_redraw,
+    void (*draw) (void *), void (*change_state) (void *),
+    void (*destroy) (void *, int), unsigned int needs_redraw,
     ltk_widget_type type) {
 	widget->id = strdup(id);
 	widget->window = window;
@@ -506,6 +493,7 @@ ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
 
 	widget->resize = NULL;
 	widget->draw = draw;
+	widget->change_state = change_state;
 	widget->destroy = destroy;
 
 	widget->needs_redraw = needs_redraw;
@@ -530,6 +518,8 @@ ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event) {
 	if (event.xbutton.button == 1) {
 		ltk_widget *parent = widget->parent;
 		widget->state = LTK_PRESSED;
+		if (widget->change_state)
+			widget->change_state(widget);
 		if (widget->needs_redraw)
 			ltk_window_invalidate_rect(widget->window, widget->rect);
 	}
@@ -544,6 +534,8 @@ ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event) {
 		return;
 	if (widget->state == LTK_PRESSED) {
 		widget->state = LTK_ACTIVE;
+		if (widget->change_state)
+			widget->change_state(widget);
 		if (widget->needs_redraw)
 			ltk_window_invalidate_rect(widget->window, widget->rect);
 	}
@@ -558,6 +550,8 @@ ltk_widget_motion_notify_event(ltk_widget *widget, XEvent event) {
 	short pressed = (event.xmotion.state & Button1Mask) == Button1Mask;
 	if ((widget->state == LTK_NORMAL) && !pressed) {
 		widget->state = LTK_ACTIVE;
+		if (widget->change_state)
+			widget->change_state(widget);
 		if (widget->mouse_enter)
 			widget->mouse_enter(widget, event);
 		/* FIXME: do this properly */
diff --git a/ltk.h b/ltk.h
@@ -73,6 +73,7 @@ typedef struct ltk_widget {
 
 	void (*resize) (void *, int, int);
 	void (*draw) (void *);
+	void (*change_state) (void *);
 	void (*destroy) (void *, int);
 
 	ltk_rect rect;
@@ -156,7 +157,8 @@ void ltk_remove_active_widget(ltk_widget *widget);
 void ltk_set_active_widget(ltk_window *window, ltk_widget *widget);
 
 void ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window * window,
-    void (*draw) (void *), void (*destroy) (void *, int), unsigned int needs_redraw,
+    void (*draw) (void *), void (*change_state) (void *),
+    void (*destroy) (void *, int), unsigned int needs_redraw,
     ltk_widget_type type);
 
 void ltk_widget_mouse_press_event(ltk_widget *widget, XEvent event);
diff --git a/text.h b/text.h
@@ -0,0 +1,16 @@
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <stdint.h>
+#include "defs.h"
+#include "color.h"
+
+#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_pango.c b/text_pango.c
@@ -0,0 +1,95 @@
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xos.h>
+#include <pango/pangoxft.h>
+#include "color.h"
+#include "text_pango.h"
+#include "util.h"
+
+struct {
+	PangoFontMap *fontmap;
+	PangoContext *context;
+	char *default_font;
+	Display *dpy;
+	int screen;
+	Colormap cm;
+} tm = {NULL, NULL, NULL, NULL, 0, 0};
+
+void
+ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap cm) {
+	tm.fontmap = pango_xft_get_font_map(dpy, screen);
+	tm.context = pango_font_map_create_context(tm.fontmap);
+	tm.default_font = strdup(default_font);
+	if (!tm.default_font) ltk_err("ltk_init_text (pango)");
+	tm.dpy = dpy;
+	tm.screen = screen;
+	tm.cm = cm;
+}
+
+void
+ltk_cleanup_text(void) {
+	if (tm.default_font) free(tm.default_font);
+	/* FIXME: destroy fontmap and context */
+}
+
+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);
+	}
+	pango_layout_set_text(line->layout, text, -1);
+	pango_layout_get_size(line->layout, &line->w, &line->h);
+	line->w /= PANGO_SCALE;
+	line->h /= PANGO_SCALE;
+	XWindowAttributes attrs;
+	XGetWindowAttributes(tm.dpy, window, &attrs);
+	line->pixmap = XCreatePixmap(tm.dpy, window, line->w, line->h, attrs.depth);
+	line->draw = XftDrawCreate(tm.dpy, line->pixmap, DefaultVisual(tm.dpy, tm.screen), tm.cm);
+	line->window = window;
+
+	return line;
+}
+
+void
+ltk_text_line_render(LtkTextLine *tl, LtkColor *bg, LtkColor *fg) {
+	XftDrawRect(tl->draw, &bg->xftcolor, 0, 0, tl->w, tl->h);
+	pango_xft_render_layout(tl->draw, &fg->xftcolor, tl->layout, 0, 0);
+}
+
+void
+ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y) {
+	XCopyArea(tm.dpy, tl->pixmap, tl->window, gc, 0, 0, tl->w, tl->h, x, y);
+}
+
+void
+ltk_text_line_get_size(LtkTextLine *tl, int *w, int *h) {
+	*w = tl->w;
+	*h = tl->h;
+}
+
+void
+ltk_text_line_destroy(LtkTextLine *tl) {
+	g_object_unref(tl->layout);
+	XftDrawDestroy(tl->draw);
+	XFreePixmap(tm.dpy, tl->pixmap);
+	free(tl->text);
+	free(tl);
+}
diff --git a/text_pango.h b/text_pango.h
@@ -0,0 +1,51 @@
+/*
+ * 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/util.c b/util.c
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+
+void
+ltk_err(const char *msg) {
+	perror(msg);
+	exit(1);
+}
+
+char *
+ltk_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;
+}
diff --git a/util.h b/util.h
@@ -1,5 +1,31 @@
+/*
+ * 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 __OpenBSD__
 long long
 strtonum(const char *numstr, long long minval, long long maxval,
     const char **errstrp);
 #endif
+
+void ltk_err(const char *msg);
+char *ltk_read_file(const char *path, unsigned long *len);