]> git.mdlowis.com Git - projs/tide.git/commitdiff
Added support for display of unicode characters
authorMichael D. Lowis <mike@mdlowis.com>
Sat, 15 Oct 2016 01:37:53 +0000 (21:37 -0400)
committerMichael D. Lowis <mike@mdlowis.com>
Sat, 15 Oct 2016 01:37:53 +0000 (21:37 -0400)
buf.c
charset.c
edit.h
tests/tests.c
tools/fontdemo.c [new file with mode: 0644]
tools/runes.c [new file with mode: 0644]
xedit.c

diff --git a/buf.c b/buf.c
index 91c68926b9c6ed375897f5764ec37f71ee6f86d5..7a523bf3ae0d0ab5803f12b6c15ef85e4c270e89 100644 (file)
--- a/buf.c
+++ b/buf.c
@@ -9,7 +9,7 @@
 #include "edit.h"
 
 typedef struct {
-    char* buf;
+    uint8_t* buf;
     size_t len;
 } FMap;
 
@@ -21,7 +21,7 @@ static FMap fmap(char* path) {
         (fstat(fd, &sb) < 0) ||
         (sb.st_size == 0))
         return file;
-    file.buf = (char*)mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+    file.buf = (uint8_t*)mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
     file.len = sb.st_size;
     if (file.buf == MAP_FAILED)
         die("memory mapping of file failed");
@@ -224,4 +224,3 @@ unsigned buf_setcol(Buf* buf, unsigned pos, unsigned col) {
     unsigned len = buf_eol(buf, pos) - bol;
     return buf_byrune(buf, bol, (len > col ? col : len));
 }
-
index 0aaa329decd86e7154cf1e8604ed7c747e34871d..24bf4e35c87057811043c00ea6b69f25e14422f2 100644 (file)
--- a/charset.c
+++ b/charset.c
@@ -23,14 +23,15 @@ static const char Utf8Valid[256] = {
     1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,
 };
 
-int charset(const char* buf, size_t len) {
+int charset(const uint8_t* buf, size_t len) {
     /* look for a BOM and parse it */
     for (size_t i = 0; i < (sizeof(BOMS)/sizeof(BOMS[0])); i++)
-        if (!strncmp(buf, BOMS[i].seq, BOMS[i].len))
+        if (!strncmp((char*)buf, BOMS[i].seq, BOMS[i].len))
             return BOMS[i].type;
     /* look for bytes that are invalid in utf-8 */
     int type = UTF_8;
-    for (size_t i = 0; type && (i < len); i++)
+    size_t i = 0;
+    for (i = 0; type && (i < len); i++)
         type = Utf8Valid[(int)buf[i]];
     return type;
 }
diff --git a/edit.h b/edit.h
index 4202cd1021eeed47edd44c5e72e39892c4668718..5fa99f1dd38e65834d507b633e81f73457406d2a 100644 (file)
--- a/edit.h
+++ b/edit.h
@@ -27,7 +27,7 @@ enum {
     UTF_32LE,
 };
 
-int charset(const char* buf, size_t len);
+int charset(const uint8_t* buf, size_t len);
 size_t utf8encode(char str[UTF_MAX], Rune rune);
 bool utf8decode(Rune* rune, size_t* length, int byte);
 Rune fgetrune(FILE* f);
@@ -183,7 +183,7 @@ void screen_status(char* fmt, ...);
 
 /* Miscellaneous Functions
  *****************************************************************************/
-void die(char* msg);
+void die(const char* fmt, ...);
 
 /* Color Scheme Handling
  *****************************************************************************/
@@ -232,6 +232,7 @@ enum {
     TabWidth    = 4,    /* maximum number of spaces used to represent a tab */
     ScrollLines = 1,    /* number of lines to scroll by for mouse wheel scrolling */
     BufSize     = 8192, /* default buffer size */
+    MaxFonts    = 16    /* maximum number of fonts to cache */
 };
 
 static const Color Palette[][2] = {
@@ -256,9 +257,9 @@ static const Color Palette[][2] = {
 
 /* choose the font to  use for xft */
 #ifdef __MACH__
-#define FONTNAME "Inconsolata:pixelsize=14:antialias=true:autohint=true"
+#define FONTNAME "Monaco:size=10:antialias=true:autohint=true"
 #else
-#define FONTNAME "Liberation Mono:pixelsize=14:antialias=true:autohint=true"
+#define FONTNAME "Monaco:size=10.5:antialias=true:autohint=true"
 #endif
 
 #define DEFAULT_COLORSCHEME DARK
index 428a74056a865c11eeaefca658076d7099a8d622..2c92b66a2fe14ada89253183487676387c256076 100644 (file)
@@ -7,7 +7,7 @@ unsigned CursorPos;
 unsigned TargetCol;
 enum ColorScheme ColorBase;
 
-void die(char* m) {
+void die(const char* m, ...) {
     (void)m;
 }
 
diff --git a/tools/fontdemo.c b/tools/fontdemo.c
new file mode 100644 (file)
index 0000000..ca9954a
--- /dev/null
@@ -0,0 +1,298 @@
+#define _GNU_SOURCE
+#include <time.h>
+#include <signal.h>
+#include <locale.h>
+#include <wchar.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <X11/Xlib.h>
+#include <X11/Xft/Xft.h>
+
+uint32_t Msg1[] = {
+    0x5916, 0x56fd, 0x8a9e, 0x306e, 0x5b66, 0x7fd2, 0x3068, 0x6559, 0x6388
+};
+
+uint32_t Msg2[] = {
+    0x4c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x20, 0x4c,
+    0x65, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x6e,
+    0x64, 0x20, 0x54, 0x65, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x67
+};
+
+struct {
+    Display* display;
+    Visual* visual;
+    Colormap colormap;
+    unsigned depth;
+    int screen;
+    GC gc;
+    Window window;
+    Pixmap pixmap;
+    XftDraw* xft;
+    int width;
+    int height;
+    int rows;
+    int cols;
+    XIC xic;
+    XIM xim;
+} X;
+
+static void die(const char* msgfmt, ...) {
+    va_list args;
+    va_start(args, msgfmt);
+    fprintf(stderr, "Error: ");
+    vfprintf(stderr, msgfmt, args);
+    fprintf(stderr, "\n");
+    va_end(args);
+    exit(EXIT_FAILURE);
+}
+
+static XftColor xftcolor(uint32_t c) {
+    XftColor xc;
+    xc.color.red   = ((c & 0x00FF0000) >> 8);
+    xc.color.green = ((c & 0x0000FF00));
+    xc.color.blue  = ((c & 0x000000FF) << 8);
+    xc.color.alpha = UINT16_MAX;
+    XftColorAllocValue(X.display, X.visual, X.colormap, &xc.color, &xc);
+    return xc;
+}
+
+static int init(void) {
+    signal(SIGPIPE, SIG_IGN); // Ignore the SIGPIPE signal
+    /* open the X display and get basic attributes */
+    if (!(X.display = XOpenDisplay(0)))
+        die("cannot open display");
+    Window root = DefaultRootWindow(X.display);
+    XWindowAttributes wa;
+    XGetWindowAttributes(X.display, root, &wa);
+    X.visual   = wa.visual;
+    X.colormap = wa.colormap;
+    X.screen   = DefaultScreen(X.display);
+    X.depth    = DefaultDepth(X.display, X.screen);
+
+    /* create the main window */
+    X.window = XCreateSimpleWindow(X.display, root, 0, 0, 640, 480, 0, 0, WhitePixel(X.display, X.screen));
+    XSetWindowAttributes swa;
+    swa.backing_store = WhenMapped;
+    swa.bit_gravity = NorthWestGravity;
+    XChangeWindowAttributes(X.display, X.window, CWBackingStore|CWBitGravity, &swa);
+    XStoreName(X.display, X.window, "edit");
+    XSelectInput(X.display, X.window, StructureNotifyMask|ExposureMask|FocusChangeMask);
+
+    /* simulate an initial resize and map the window */
+    XConfigureEvent ce;
+    ce.type   = ConfigureNotify;
+    ce.width  = 640;
+    ce.height = 480;
+    XSendEvent(X.display, X.window, False, StructureNotifyMask, (XEvent *)&ce);
+    XMapWindow(X.display, X.window);
+
+    /* set input methods */
+    if((X.xim = XOpenIM(X.display, 0, 0, 0)))
+        X.xic = XCreateIC(X.xim, XNInputStyle, XIMPreeditNothing|XIMStatusNothing, XNClientWindow, X.window, XNFocusWindow, X.window, NULL);
+
+    /* initialize the graphics context */
+    XGCValues gcv;
+    gcv.foreground = WhitePixel(X.display, X.screen);
+    gcv.graphics_exposures = False;
+    X.gc = XCreateGC(X.display, X.window, GCForeground|GCGraphicsExposures, &gcv);
+
+    /* initialize pixmap and drawing context */
+    X.pixmap = XCreatePixmap(X.display, X.window, 640, 480, X.depth);
+    X.xft = XftDrawCreate(X.display, X.pixmap, X.visual, X.colormap);
+
+    return XConnectionNumber(X.display);
+}
+
+static void handle_event(XEvent* e) {
+    switch (e->type) {
+        case FocusIn:     if (X.xic) XSetICFocus(X.xic);   break;
+        case FocusOut:    if (X.xic) XUnsetICFocus(X.xic); break;
+        case ConfigureNotify: // Resize the window
+            if (e->xconfigure.width != X.width || e->xconfigure.height != X.height) {
+                X.width  = e->xconfigure.width;
+                X.height = e->xconfigure.height;
+                X.pixmap = XCreatePixmap(X.display, X.window, X.width, X.height, X.depth);
+                X.xft    = XftDrawCreate(X.display, X.pixmap, X.visual, X.colormap);
+            }
+            break;
+    }
+}
+
+/*****************************************************************************/
+
+typedef uint32_t Rune;
+
+static char FontName[] = "Liberation Mono:size=14:antialias=true:autohint=true";
+
+/* globals */
+#define MaxFonts 16
+struct {
+    struct {
+        int height;
+        int width;
+        int ascent;
+        int descent;
+        XftFont* match;
+        FcFontSet* set;
+        FcPattern* pattern;
+    } base;
+    struct {
+        XftFont* font;
+        Rune unicodep;
+    } cache[MaxFonts];
+    int ncached;
+} Fonts;
+
+void font_init(void);
+void font_find(XftGlyphFontSpec* spec, Rune rune);
+int font_makespecs(XftGlyphFontSpec* specs, const Rune* runes, int len, int x, int y);
+void font_drawspecs(const XftGlyphFontSpec* specs, int len, XftColor* fg, XftColor* bg, int x, int y);
+
+void draw_runes(unsigned x, unsigned y, XftColor* fg, XftColor* bg, Rune* runes, size_t rlen);
+static void redraw(void);
+
+int main(int argc, char** argv) {
+    setlocale(LC_CTYPE, "");
+    XSetLocaleModifiers("");
+    init();
+    font_init();
+    XEvent e;
+    while (true) {
+        XPeekEvent(X.display,&e);
+        while (XPending(X.display)) {
+            XNextEvent(X.display, &e);
+            if (!XFilterEvent(&e, None))
+                handle_event(&e);
+        }
+        redraw();
+    }
+    return 0;
+}
+
+static void redraw(void) {
+    /* Allocate the colors */
+    XftColor bkgclr = xftcolor(0x002b36);
+    XftColor txtclr = xftcolor(0x839496);
+    /* draw the background colors */
+    XftDrawRect(X.xft, &bkgclr, 0, 0, X.width, X.height);
+    /* Draw text */
+    draw_runes(0, 0, &txtclr, &bkgclr, (FcChar32*)(Msg1), (sizeof(Msg1)/sizeof(Msg1[0])));
+    draw_runes(0, 1, &txtclr, &bkgclr, (FcChar32*)(Msg1), (sizeof(Msg1)/sizeof(Msg1[0])));
+    draw_runes(0, 2, &txtclr, &bkgclr, (FcChar32*)(Msg2), (sizeof(Msg2)/sizeof(Msg2[0])));
+    /* flush pixels to the screen */
+    XCopyArea(X.display, X.pixmap, X.window, X.gc, 0, 0, X.width, X.height, 0, 0);
+    XFlush(X.display);
+}
+
+/*****************************************************************************/
+
+void draw_runes(unsigned x, unsigned y, XftColor* fg, XftColor* bg, Rune* runes, size_t rlen) {
+    XftGlyphFontSpec specs[rlen];
+    size_t nspecs = font_makespecs(specs, runes, rlen, x, y);
+    font_drawspecs(specs, nspecs, fg, bg, x, y);
+}
+
+void font_init(void) {
+    /* init the library and the base font pattern */
+    if (!FcInit())
+        die("Could not init fontconfig.\n");
+    FcPattern* pattern = FcNameParse((FcChar8 *)FontName);
+    if (!pattern)
+        die("st: can't open font %s\n", FontName);
+
+    /* load the base font */
+    FcResult result;
+    FcPattern* match = XftFontMatch(X.display, X.screen, pattern, &result);
+    if (!match || !(Fonts.base.match = XftFontOpenPattern(X.display, match)))
+        die("could not load default font: %s", FontName);
+
+    /* get base font extents */
+    XGlyphInfo extents;
+    const FcChar8 ascii[] =
+        " !\"#$%&'()*+,-./0123456789:;<=>?"
+        "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
+        "`abcdefghijklmnopqrstuvwxyz{|}~";
+    XftTextExtentsUtf8(X.display, Fonts.base.match, ascii, sizeof(ascii), &extents);
+    Fonts.base.set     = NULL;
+    Fonts.base.pattern = FcPatternDuplicate(pattern);
+    Fonts.base.ascent  = Fonts.base.match->ascent;
+    Fonts.base.descent = Fonts.base.match->descent;
+    Fonts.base.height  = Fonts.base.ascent + Fonts.base.descent;
+    Fonts.base.width   = ((extents.xOff + (sizeof(ascii) - 1)) / sizeof(ascii));
+    FcPatternDestroy(pattern);
+}
+
+void font_find(XftGlyphFontSpec* spec, Rune rune) {
+    /* if the rune is in the base font, set it and return */
+    FT_UInt glyphidx = XftCharIndex(X.display, Fonts.base.match, rune);
+    if (glyphidx) {
+        puts("font: default");
+        spec->font  = Fonts.base.match;
+        spec->glyph = glyphidx;
+        return;
+    }
+    /* Otherwise check the cache */
+    for (int f = 0; f < Fonts.ncached; f++) {
+        glyphidx = XftCharIndex(X.display, Fonts.cache[f].font, rune);
+        /* Fond a suitable font or found a default font */
+        if (glyphidx || (!glyphidx && Fonts.cache[f].unicodep == rune)) {
+            puts("font: match");
+            spec->font  = Fonts.cache[f].font;
+            spec->glyph = glyphidx;
+            return;
+        }
+    }
+    /* if all other options fail, ask fontconfig for a suitable font */
+    FcResult fcres;
+    if (!Fonts.base.set)
+        Fonts.base.set = FcFontSort(0, Fonts.base.pattern, 1, 0, &fcres);
+    FcFontSet* fcsets[]  = { Fonts.base.set };
+    FcPattern* fcpattern = FcPatternDuplicate(Fonts.base.pattern);
+    FcCharSet* fccharset = FcCharSetCreate();
+    FcCharSetAddChar(fccharset, rune);
+    FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
+    FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
+    FcConfigSubstitute(0, fcpattern, FcMatchPattern);
+    FcDefaultSubstitute(fcpattern);
+    FcPattern* fontmatch = FcFontSetMatch(0, fcsets, 1, fcpattern, &fcres);
+    /* add the font to the cache and use it */
+    if (Fonts.ncached >= MaxFonts) {
+        Fonts.ncached = MaxFonts - 1;
+        XftFontClose(X.display, Fonts.cache[Fonts.ncached].font);
+    }
+    Fonts.cache[Fonts.ncached].font = XftFontOpenPattern(X.display, fontmatch);
+    Fonts.cache[Fonts.ncached].unicodep = rune;
+    spec->glyph = XftCharIndex(X.display, Fonts.cache[Fonts.ncached].font, rune);
+    spec->font  = Fonts.cache[Fonts.ncached].font;
+    Fonts.ncached++;
+    FcPatternDestroy(fcpattern);
+    FcCharSetDestroy(fccharset);
+    puts("font: fontconfig");
+}
+
+int font_makespecs(XftGlyphFontSpec* specs, const Rune* runes, int len, int x, int y) {
+    int winx = x * Fonts.base.width, winy = y * Fonts.base.height, xp, yp;
+    int numspecs = 0;
+    for (int i = 0, xp = winx, yp = winy + Fonts.base.ascent; i < len; ++i) {
+        if (!runes[i]) continue;
+        font_find(&(specs[numspecs]), runes[i]);
+        int runewidth = wcwidth(runes[i]) * Fonts.base.width;
+        specs[numspecs].x = (short)xp;
+        specs[numspecs].y = (short)yp;
+        xp += runewidth;
+        numspecs++;
+    }
+    return numspecs;
+}
+
+void font_drawspecs(const XftGlyphFontSpec* specs, int len, XftColor* fg, XftColor* bg, int x, int y) {
+    //int charlen = len; // * ((base.mode & ATTR_WIDE) ? 2 : 1);
+    //int winx = x * Fonts.base.width, winy = y * Fonts.base.height,
+    //    width = charlen * Fonts.base.width;
+    //XRectangle r;
+
+    /* Render the glyphs. */
+    XftDrawGlyphFontSpec(X.xft, fg, specs, len);
+}
+
+
diff --git a/tools/runes.c b/tools/runes.c
new file mode 100644 (file)
index 0000000..8a0be2b
--- /dev/null
@@ -0,0 +1,118 @@
+/**
+  @brief Simple UTF-8 encoding and decoding routines.
+  @author Michael D. Lowis
+  @license BSD 2-clause License
+*/
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+enum {
+    UTF_MAX   = 6u,        /* maximum number of bytes that make up a rune */
+    RUNE_SELF = 0x80,      /* byte values larger than this are *not* ascii */
+    RUNE_ERR  = 0xFFFD,    /* rune value representing an error */
+    RUNE_MAX  = 0x10FFFF,  /* Maximum decodable rune value */
+    RUNE_EOF  = UINT32_MAX /* rune value representing end of file */
+};
+
+typedef uint32_t Rune;
+
+const uint8_t UTF8_SeqBits[] = { 0x00u, 0x80u, 0xC0u, 0xE0u, 0xF0u, 0xF8u, 0xFCu, 0xFEu };
+const uint8_t UTF8_SeqMask[] = { 0x00u, 0xFFu, 0x1Fu, 0x0Fu, 0x07u, 0x03u, 0x01u, 0x00u };
+const uint8_t UTF8_SeqLens[] = { 0x01u, 0x00u, 0x02u, 0x03u, 0x04u, 0x05u, 0x06u, 0x00u };
+
+static bool runevalid(Rune val) {
+    return (val <= RUNE_MAX)
+        && ((val & 0xFFFEu) != 0xFFFEu)
+        && ((val < 0xD800u) || (val > 0xDFFFu))
+        && ((val < 0xFDD0u) || (val > 0xFDEFu));
+}
+
+static size_t runelen(Rune rune) {
+    if(!runevalid(rune))
+        return 0;
+    else if(rune <= 0x7F)
+        return 1;
+    else if(rune <= 0x07FF)
+        return 2;
+    else if(rune <= 0xFFFF)
+        return 3;
+    else
+        return 4;
+}
+
+static uint8_t utfseq(uint8_t byte) {
+    for (int i = 1; i < 8; i++)
+        if ((byte & UTF8_SeqBits[i]) == UTF8_SeqBits[i-1])
+            return UTF8_SeqLens[i-1];
+    return 0;
+}
+
+size_t utf8encode(char str[UTF_MAX], Rune rune) {
+    size_t len = runelen(rune);
+    str[0] = (len == 1 ? 0x00 : UTF8_SeqBits[len])
+           | (UTF8_SeqMask[len] & (rune >> (6 * (len-1))));
+    for (size_t i = 1; i < len; i++)
+        str[i] = 0x80u | (0x3Fu & (rune >> (6 * (len-i-1))));
+    return len;
+}
+
+bool utf8decode(Rune* rune, size_t* length, int byte) {
+    /* Handle the start of a new rune */
+    if (*length == 0) {
+        /* If we were fed in an EOF as a start byte, handle it here */
+        if (byte == EOF) {
+            *rune = RUNE_EOF;
+        } else {
+            /* Otherwise, decode the first byte of the rune */
+            *length = utfseq(byte);
+            *rune   = (*length == 0) ? RUNE_ERR : (byte & UTF8_SeqMask[*length]);
+            (*length)--;
+        }
+    /* Handle continuation bytes */
+    } else if ((byte & 0xC0) == 0x80) {
+        /* add bits from continuation byte to rune value
+         * cannot overflow: 6 byte sequences contain 31 bits */
+        *rune = (*rune << 6) | (byte & 0x3F); /* 10xxxxxx */
+        (*length)--;
+        /* Sanity check the final rune value before finishing */
+        if ((*length == 0) && !runevalid(*rune))
+            *rune = RUNE_ERR;
+    /* Didn't get the continuation byte we expected */
+    } else {
+        *rune = RUNE_ERR;
+    }
+    /* Tell the caller whether we finished or not */
+    return ((*length == 0) || (*rune == RUNE_ERR));
+}
+
+Rune fgetrune(FILE* f) {
+    Rune rune = 0;
+    size_t length = 0;
+    while (!utf8decode(&rune, &length, fgetc(f)));
+    return rune;
+}
+
+void fputrune(Rune rune, FILE* f) {
+    char utf[UTF_MAX] = {0};
+    utf8encode(utf, rune);
+    fprintf(f, "%s", utf);
+}
+
+void print_runes(FILE* in) {
+    if (!in) return;
+    Rune r;
+    while (RUNE_EOF != (r = fgetrune(in)))
+        printf("%#x\n", r);
+    fclose(in);
+}
+
+int main(int argc, char** argv) {
+    if (argc == 1)
+        print_runes(stdin);
+    else
+        for (int i = 1; i < argc; i++)
+            print_runes(fopen(argv[i], "rb"));
+    return 0;
+}
diff --git a/xedit.c b/xedit.c
index 69fdd53f0f4e118644e152a7aad652283029af2c..3bc56847da5caadb40a13019a0b6f4f6faa7b701 100644 (file)
--- a/xedit.c
+++ b/xedit.c
@@ -2,6 +2,7 @@
 #include <time.h>
 #include <signal.h>
 #include <locale.h>
+#include <wchar.h>
 #include <X11/Xlib.h>
 #include <X11/Xft/Xft.h>
 
@@ -22,10 +23,6 @@ struct {
     Pixmap pixmap;
     XftDraw* xft;
     XftFont* font;
-    unsigned fheight;
-    unsigned fwidth;
-    unsigned fascent;
-    unsigned fdescent;
     int width;
     int height;
     int rows;
@@ -34,100 +31,125 @@ struct {
     XIM xim;
 } X;
 
-/*****************************************************************************/
+struct {
+    struct {
+        int height;
+        int width;
+        int ascent;
+        int descent;
+        XftFont* match;
+        FcFontSet* set;
+        FcPattern* pattern;
+    } base;
+    struct {
+        XftFont* font;
+        Rune unicodep;
+    } cache[MaxFonts];
+    int ncached;
+} Fonts;
 
-struct XFont {
-    struct XFont* next;
-    XftFont* font;
-    FcPattern* pattern;
-}* FontList = NULL;
-
-struct XFont* fontbyname(char* fontname) {
-    struct XFont* xfont = (struct XFont*)calloc(1, sizeof(struct XFont));
-    if (!(xfont->font = XftFontOpenName(X.display, X.screen, fontname)))
-        die("cannot open default font");
-    if (!(xfont->pattern = FcNameParse((FcChar8 *)FONTNAME)))
-        die("cannot convert fontname to font pattern");
-    return xfont;
-}
+/*****************************************************************************/
 
-struct XFont* fontbypatt(FcPattern* pattern) {
-    struct XFont* xfont = (struct XFont*)calloc(1, sizeof(struct XFont));
-    if (!(xfont->font = XftFontOpenPattern(X.display, pattern)))
-        die("cannot get font by pattern");
-    return xfont;
+void font_init(void) {
+    /* init the library and the base font pattern */
+    if (!FcInit())
+        die("Could not init fontconfig.\n");
+    FcPattern* pattern = FcNameParse((FcChar8 *)FONTNAME);
+    if (!pattern)
+        die("st: can't open font %s\n", FONTNAME);
+
+    /* load the base font */
+    FcResult result;
+    FcPattern* match = XftFontMatch(X.display, X.screen, pattern, &result);
+    if (!match || !(Fonts.base.match = XftFontOpenPattern(X.display, match)))
+        die("could not load default font: %s", FONTNAME);
+
+    /* get base font extents */
+    XGlyphInfo extents;
+    const FcChar8 ascii[] =
+        " !\"#$%&'()*+,-./0123456789:;<=>?"
+        "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
+        "`abcdefghijklmnopqrstuvwxyz{|}~";
+    XftTextExtentsUtf8(X.display, Fonts.base.match, ascii, sizeof(ascii), &extents);
+    Fonts.base.set     = NULL;
+    Fonts.base.pattern = FcPatternDuplicate(pattern);
+    Fonts.base.ascent  = Fonts.base.match->ascent;
+    Fonts.base.descent = Fonts.base.match->descent;
+    Fonts.base.height  = Fonts.base.ascent + Fonts.base.descent;
+    Fonts.base.width   = ((extents.xOff + (sizeof(ascii) - 1)) / sizeof(ascii));
+    FcPatternDestroy(pattern);
 }
 
-struct XFont* getfont(Rune r) {
-    /* check to see if the font is already loaded */
-    struct XFont* xfont = FontList;
-    while (xfont) {
-        printf("does font have rune? %d\n", XftCharExists(X.display, xfont->font, r));
-        if (XftCharExists(X.display, xfont->font, r)) {
-            //puts("found font in font list");
-            return xfont;
+void font_find(XftGlyphFontSpec* spec, Rune rune) {
+    /* if the rune is in the base font, set it and return */
+    FT_UInt glyphidx = XftCharIndex(X.display, Fonts.base.match, rune);
+    if (glyphidx) {
+        spec->font  = Fonts.base.match;
+        spec->glyph = glyphidx;
+        return;
+    }
+    /* Otherwise check the cache */
+    for (int f = 0; f < Fonts.ncached; f++) {
+        glyphidx = XftCharIndex(X.display, Fonts.cache[f].font, rune);
+        /* Fond a suitable font or found a default font */
+        if (glyphidx || (!glyphidx && Fonts.cache[f].unicodep == rune)) {
+            spec->font  = Fonts.cache[f].font;
+            spec->glyph = glyphidx;
+            return;
         }
-        xfont = xfont->next;
     }
-    /* search for a new font that has the rune */
-    XftResult result;
+    /* if all other options fail, ask fontconfig for a suitable font */
+    FcResult fcres;
+    if (!Fonts.base.set)
+        Fonts.base.set = FcFontSort(0, Fonts.base.pattern, 1, 0, &fcres);
+    FcFontSet* fcsets[]  = { Fonts.base.set };
+    FcPattern* fcpattern = FcPatternDuplicate(Fonts.base.pattern);
     FcCharSet* fccharset = FcCharSetCreate();
-    FcCharSetAddChar(fccharset, r);
-    FcPattern* fcpattern = FcPatternDuplicate(FontList->pattern);
+    FcCharSetAddChar(fccharset, rune);
     FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
-    FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
-    FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
+    FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
+    FcConfigSubstitute(0, fcpattern, FcMatchPattern);
     FcDefaultSubstitute(fcpattern);
-    FcPattern* match = XftFontMatch(X.display, X.screen, fcpattern, &result);
-    FcCharSetDestroy(fccharset);
-    FcPatternDestroy(fcpattern);
-    if (match) {
-        //puts("found new font, adding it to the list");
-        struct XFont* newfont  = fontbypatt(match);
-        struct XFont* lastfont = FontList;
-        printf("newfont: %p, %d\n", newfont->font, XftCharExists(X.display, newfont->font, r));
-        if (XftCharExists(X.display, newfont->font, r)) {
-            for (; lastfont->next; lastfont = lastfont->next)
-                ; /* NOP */
-            lastfont->next = newfont;
-            return newfont;
-        } else {
-            XftFontClose(X.display, newfont->font);
-            free(newfont);
-        }
+    FcPattern* fontmatch = FcFontSetMatch(0, fcsets, 1, fcpattern, &fcres);
+    /* add the font to the cache and use it */
+    if (Fonts.ncached >= MaxFonts) {
+        Fonts.ncached = MaxFonts - 1;
+        XftFontClose(X.display, Fonts.cache[Fonts.ncached].font);
     }
-    puts("failed to find a font with the given rune");
-    return FontList;
+    Fonts.cache[Fonts.ncached].font = XftFontOpenPattern(X.display, fontmatch);
+    Fonts.cache[Fonts.ncached].unicodep = rune;
+    spec->glyph = XftCharIndex(X.display, Fonts.cache[Fonts.ncached].font, rune);
+    spec->font  = Fonts.cache[Fonts.ncached].font;
+    Fonts.ncached++;
+    FcPatternDestroy(fcpattern);
+    FcCharSetDestroy(fccharset);
 }
 
-void fontinit(char* fontstr)
-{
-    double fontval;
-    float ceilf(float);
-    /* initialize fontconfig */
-    if (!FcInit())
-        die("Could not init fontconfig");
-    /* get pattern from font name */
-    FcPattern *pattern;
-    if (fontstr[0] == '-') {
-        pattern = XftXlfdParse(fontstr, False, False);
-    } else {
-        pattern = FcNameParse((FcChar8 *)fontstr);
+int font_makespecs(XftGlyphFontSpec* specs, const Rune* runes, int len, int x, int y) {
+    int winx = x * Fonts.base.width, winy = y * Fonts.base.height;
+    int numspecs = 0;
+    for (int i = 0, xp = winx, yp = winy + Fonts.base.ascent; i < len; ++i) {
+        if (!runes[i]) continue;
+        font_find(&(specs[numspecs]), runes[i]);
+        int runewidth = wcwidth(runes[i]) * Fonts.base.width;
+        specs[numspecs].x = xp;
+        specs[numspecs].y = yp;
+        xp += runewidth;
+        numspecs++;
     }
-    if (!pattern)
-        die("failed to open default font");
-    /* load the font by pattern */
-    if (!(FontList = fontbypatt(pattern)))
-        die("failed to load the font");
-    FcPatternDestroy(pattern);
+    return numspecs;
 }
 
-
 /*****************************************************************************/
 
-void die(char* m) {
-    fprintf(stderr, "dying, %s\n", m);
-    exit(1);
+void die(const char* msgfmt, ...) {
+    va_list args;
+    va_start(args, msgfmt);
+    fprintf(stderr, "Error: ");
+    vfprintf(stderr, msgfmt, args);
+    fprintf(stderr, "\n");
+    va_end(args);
+    exit(EXIT_FAILURE);
 }
 
 static XftColor xftcolor(enum ColorId cid) {
@@ -181,12 +203,7 @@ static int init(void) {
     XMapWindow(X.display, X.window);
 
     /* allocate font */
-    //fontinit(FONTNAME);
-    FontList   = fontbyname(FONTNAME);
-    X.fheight  = FontList->font->height;
-    X.fwidth   = FontList->font->max_advance_width;
-    X.fascent  = FontList->font->ascent;
-    X.fdescent = FontList->font->descent;
+    font_init();
 
     /* set input methods */
     if ((X.xim = XOpenIM(X.display, 0, 0, 0)))
@@ -255,8 +272,8 @@ static MouseEvent* getmouse(XEvent* e) {
     if (e->type == MotionNotify) {
         event.type   = MouseMove;
         event.button = MOUSE_NONE;
-        event.x      = e->xmotion.x / X.fwidth;
-        event.y      = e->xmotion.y / (X.fascent + X.fdescent);
+        event.x      = e->xmotion.x / Fonts.base.width;
+        event.y      = e->xmotion.y / (Fonts.base.ascent + Fonts.base.descent);
     } else {
         event.type = (e->type = ButtonPress ? MouseDown : MouseUp);
         /* set the button id */
@@ -268,8 +285,8 @@ static MouseEvent* getmouse(XEvent* e) {
             case Button5: event.button = MOUSE_WHEELDOWN; break;
             default:      event.button = MOUSE_NONE;      break;
         }
-        event.x = e->xbutton.x / X.fwidth;
-        event.y = e->xbutton.y / (X.fascent + X.fdescent);
+        event.x = e->xbutton.x / Fonts.base.width;
+        event.y = e->xbutton.y / (Fonts.base.ascent + Fonts.base.descent);
     }
     return &event;
 }
@@ -288,42 +305,18 @@ static void handle_event(XEvent* e) {
                 X.xft    = XftDrawCreate(X.display, X.pixmap, X.visual, X.colormap);
                 screen_setsize(
                     &Buffer,
-                    X.height / X.fheight,
-                    X.width  / X.fwidth);
+                    X.height / Fonts.base.height,
+                    X.width  / Fonts.base.width);
             }
             break;
     }
 }
 
-#include <assert.h>
-
-static void draw_string(XftDraw* xft, XftColor* clr, unsigned x, unsigned y, Rune* r, unsigned len) {
-    struct XFont* curfont = NULL;
-    for (unsigned i = 0, n = 0; i < len; i += n, n = 0) {
-        //printf("i: %d, n: %d\n", i, n);
-        Rune* str = r+i;
-        struct XFont* newfont = NULL;
-        curfont = getfont(str[n]);
-        while (curfont == (newfont = getfont(str[n]))) {
-            n++;
-        }
-        if (n) {
-            //printf("printing: %p %u\n", curfont, n);
-            //if (!curfont) curfont = FontList;
-            XftDrawString32(xft, clr, (curfont ? curfont->font : FontList->font), x, y, str, n);
-        }
-    }
-    //for (unsigned i = 0; i < len;) {
-    //    struct XFont* newfont = NULL;
-    //    Rune* curr = r+i;
-    //    unsigned n = 0;
-    //    while (curfont == (newfont = getfont(curr[n])))
-    //        n++;
-    //    if (!curfont) curfont = FontList;
-    //    XftDrawString32(xft, clr, curfont->font, x, y, curr, n);
-    //    curfont = newfont;
-    //    i += n;
-    //}
+void draw_runes(unsigned x, unsigned y, XftColor* fg, XftColor* bg, Rune* runes, size_t rlen) {
+    (void)bg;
+    XftGlyphFontSpec specs[rlen];
+    size_t nspecs = font_makespecs(specs, runes, rlen, x, y);
+    XftDrawGlyphFontSpec(X.xft, fg, specs, nspecs);
 }
 
 static void redraw(void) {
@@ -335,8 +328,8 @@ static void redraw(void) {
 
     /* draw the background colors */
     XftDrawRect(X.xft, &bkgclr, 0, 0, X.width, X.height);
-    XftDrawRect(X.xft, &gtrclr, 79 * X.fwidth, 0, X.fwidth, X.height);
-    XftDrawRect(X.xft, &txtclr, 0, 0, X.width, X.fheight + X.fdescent);
+    XftDrawRect(X.xft, &gtrclr, 79 * Fonts.base.width, 0, Fonts.base.width, X.height);
+    XftDrawRect(X.xft, &txtclr, 0, 0, X.width, Fonts.base.height);
 
     /* update the screen buffer and retrieve cursor coordinates */
     unsigned csrx, csry;
@@ -352,17 +345,17 @@ static void redraw(void) {
     );
     for (unsigned y = 0; y < nrows; y++) {
         Row* row = screen_getrow(y);
-        draw_string(X.xft, (y == 0 ? &bkgclr : &txtclr), 0, (y+1) * X.fheight, row->cols, row->len);
+        draw_runes(0, y, (y == 0 ? &bkgclr : &txtclr), NULL, row->cols, row->len);
     }
 
     /* Place cursor on screen */
     Rune csrrune = screen_getcell(csry,csrx);
     if (Buffer.insert_mode) {
-        XftDrawRect(X.xft, &csrclr, csrx * X.fwidth, csry * X.fheight + X.fdescent, 2, X.fheight);
+        XftDrawRect(X.xft, &csrclr, csrx * Fonts.base.width, csry * Fonts.base.height, 2, Fonts.base.height);
     } else {
         unsigned width = ('\t' == buf_get(&Buffer, CursorPos) ? (TabWidth - (csrx % TabWidth)) : 1);
-        XftDrawRect(X.xft, &csrclr, csrx * X.fwidth, csry * X.fheight + X.fdescent, width * X.fwidth, X.fheight);
-        draw_string(X.xft, &bkgclr, csrx * X.fwidth, (csry+1) * X.fheight, (FcChar32*)&csrrune, 1);
+        XftDrawRect(X.xft, &csrclr, csrx * Fonts.base.width, csry * Fonts.base.height, width * Fonts.base.width, Fonts.base.height);
+        draw_runes(csrx, csry, &bkgclr, NULL, (FcChar32*)&csrrune, 1);
     }
 
     /* flush pixels to the screen */