URI: 
       tAdd pango text support; break other text support :( - ltk - Socket-based GUI for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 38fe277a39863666574934ce221dcf9bd9ee4cad
   DIR parent 5aff106f8dd7610fe450e26880243949481ba5af
  HTML 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(-)
       ---
   DIR diff --git a/Makefile b/Makefile
       t@@ -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)
   DIR diff --git a/README.md b/README.md
       t@@ -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
   DIR diff --git a/button.c b/button.c
       t@@ -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);
       t@@ -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;
       t@@ -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);
                }
       t@@ -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) {
       t@@ -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,
       -            &ltk_button_draw, &ltk_button_destroy, 1, LTK_BUTTON);
       +            &ltk_button_draw, &ltk_button_change_state, &ltk_button_destroy, 1, LTK_BUTTON);
                button->widget.mouse_release = &ltk_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;
        }
   DIR diff --git a/button.h b/button.h
       t@@ -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
       t@@ -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);
       t@@ -60,4 +57,4 @@ void ltk_button_cmd(
            char **tokens,
            size_t num_tokens);
        
       -#endif
       +#endif /* _LTK_BUTTON_H_ */
   DIR diff --git a/color.c b/color.c
       t@@ -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
       +}
   DIR diff --git a/color.h b/color.h
       t@@ -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;
   DIR diff --git a/config.mk b/config.mk
       t@@ -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 = 
   DIR diff --git a/defs.h b/defs.h
       t@@ -0,0 +1,2 @@
       +#define USE_PANGO
       +#define USE_XFT
   DIR diff --git a/draw.c b/draw.c
       t@@ -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,
       -            &ltk_draw_draw, &ltk_draw_destroy, 1, LTK_DRAW);
       +            &ltk_draw_draw, NULL, &ltk_draw_destroy, 1, LTK_DRAW);
                draw->widget.resize = &ltk_draw_resize;
                draw->widget.rect.w = w;
                draw->widget.rect.h = h;
   DIR diff --git a/grid.c b/grid.c
       t@@ -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, &ltk_grid_draw,
       -            &ltk_grid_destroy, 0, LTK_GRID);
       +            NULL, &ltk_grid_destroy, 0, LTK_GRID);
                grid->widget.mouse_press = &ltk_grid_mouse_press;
                grid->widget.mouse_release = &ltk_grid_mouse_release;
                grid->widget.motion_notify = &ltk_grid_motion_notify;
   DIR diff --git a/ltk.c b/ltk.c
       t@@ -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"
        
       t@@ -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;
       t@@ -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,
       t@@ -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 = &ltk_window_other_event;
        
                window->rect.w = 0;
       t@@ -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;
       t@@ -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;
       t@@ -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;
       t@@ -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);
                }
       t@@ -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);
                }
       t@@ -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 */
   DIR diff --git a/ltk.h b/ltk.h
       t@@ -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;
       t@@ -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);
   DIR diff --git a/text.h b/text.h
       t@@ -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
   DIR diff --git a/text_pango.c b/text_pango.c
       t@@ -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);
       +}
   DIR diff --git a/text_pango.h b/text_pango.h
       t@@ -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_ */
   DIR diff --git a/util.c b/util.c
       t@@ -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;
       +}
   DIR diff --git a/util.h b/util.h
       t@@ -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);