login

<     >

2015-07-20 20:59:50 (UTC-03:00)

Marcel Rodrigues <marcelgmr@gmail.com>

First commit.

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5a6c861
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+a.out
+*.o
+*.gif
+*.t
+*.d

diff --git a/6x11.mbf b/6x11.mbf
new file mode 100644
index 0000000..c1c3c85
Binary files /dev/null and b/6x11.mbf differ

diff --git a/cs_437.h b/cs_437.h
new file mode 100644
index 0000000..a8b9f0c
--- /dev/null
+++ b/cs_437.h
@@ -0,0 +1,34 @@
+static uint16_t cs_437[0x100] = {
+    0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
+    0x25D8, 0x2218, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C,
+    0x25BA, 0x25C4, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8,
+    0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC,
+    0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+    0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
+    0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+    0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
+    0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+    0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
+    0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+    0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
+    0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+    0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
+    0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+    0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x2302,
+    0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7,
+    0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5,
+    0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9,
+    0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192,
+    0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA,
+    0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB,
+    0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
+    0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510,
+    0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F,
+    0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567,
+    0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B,
+    0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580,
+    0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4,
+    0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229,
+    0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248,
+    0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0,
+};

diff --git a/cs_vtg.h b/cs_vtg.h
new file mode 100644
index 0000000..c2c45d8
--- /dev/null
+++ b/cs_vtg.h
@@ -0,0 +1,34 @@
+static uint16_t cs_vtg[0x100] = {
+    0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+    0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
+    0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
+    0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
+    0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+    0x0028, 0x0029, 0x002A, 0x2192, 0x2190, 0x2191, 0x2193, 0x002F,
+    0x25AE, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+    0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
+    0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+    0x0048, 0x2603, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
+    0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+    0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
+    0x25C6, 0x2592, 0x0062, 0x0063, 0x0064, 0x0065, 0x00B0, 0x00B1,
+    0x2591, 0x0069, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0x23BA,
+    0x23BB, 0x2500, 0x23BC, 0x23BD, 0x251C, 0x2524, 0x2534, 0x252C,
+    0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7, 0x007F,
+    0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+    0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
+    0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+    0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
+    0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
+    0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF,
+    0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7,
+    0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF,
+    0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
+    0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
+    0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7,
+    0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
+    0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
+    0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
+    0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7,
+    0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF,
+};

diff --git a/default.h b/default.h
new file mode 100644
index 0000000..4babf69
--- /dev/null
+++ b/default.h
@@ -0,0 +1,25 @@
+static uint8_t def_plt[0x30] = {
+    0x00, 0x00, 0x00,
+    0xCD, 0x00, 0x00,
+    0x00, 0xCD, 0x00,
+    0xCD, 0xCD, 0x00,
+    0x00, 0x00, 0xEE,
+    0xCD, 0x00, 0xCD,
+    0x00, 0xCD, 0xCD,
+    0xCD, 0xCD, 0xCD,
+    0x7F, 0x7F, 0x7F,
+    0xFF, 0x00, 0x00,
+    0x00, 0xFF, 0x00,
+    0xFF, 0xFF, 0x00,
+    0x5C, 0x5C, 0xFF,
+    0xFF, 0x00, 0xFF,
+    0x00, 0xFF, 0xFF,
+    0xFF, 0xFF, 0xFF
+};
+
+#define DEF_FORE    0x7
+#define DEF_BACK    0x0
+
+static uint16_t def_mode = M_AUTOWRAP | M_AUTORPT | M_CURSORVIS;
+static uint8_t def_attr = A_NORMAL;
+static uint8_t def_pair = (DEF_FORE << 4) | DEF_BACK;

diff --git a/dump.c b/dump.c
new file mode 100644
index 0000000..0d8b6e1
--- /dev/null
+++ b/dump.c
@@ -0,0 +1,37 @@
+#include <stdint.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "term.h"
+
+void
+dump_txt(Term *term, const char *fname)
+{
+    int i, j, fd;
+    uint16_t code;
+    char ch;
+
+    fd = creat(fname, 0666);
+    if (fd == -1)
+        return;
+    for (i = 0; i < term->rows; i++) {
+        for (j = 0; j < term->cols; j++) {
+            code = term->addr[i][j].code;
+            if (code >= 0x20 && code < 0x7F)
+                ch = (char) code;
+            else
+                ch = 0x20;
+            write(fd, &ch, 1);
+        }
+        write(fd, "\n", 1);
+    }
+    close(fd);
+}
+
+void
+dump_ppm(Term *term, const char *fname)
+{
+    uint8_t pix[term->rows*term->cols];
+}

diff --git a/dump.h b/dump.h
new file mode 100644
index 0000000..bb543c0
--- /dev/null
+++ b/dump.h
@@ -0,0 +1,2 @@
+void dump_txt(Term *term, const char *fname);
+void dump_ppm(Term *term, const char *fname);

diff --git a/gen-maps.lua b/gen-maps.lua
new file mode 100644
index 0000000..b94fac5
--- /dev/null
+++ b/gen-maps.lua
@@ -0,0 +1,95 @@
+local function build_vtg()
+    -- VT100 Graphics
+    local vtg_kv = {
+        {0x2B, 0x2192}, {0x2C, 0x2190}, {0x2D, 0x2191}, {0x2E, 0x2193},
+        {0x30, 0x25AE}, {0x49, 0x2603}, {0x60, 0x25C6}, {0x61, 0x2592},
+        {0x66, 0x00B0}, {0x67, 0x00B1}, {0x68, 0x2591}, {0x6A, 0x2518},
+        {0x6B, 0x2510}, {0x6C, 0x250C}, {0x6D, 0x2514}, {0x6E, 0x253C},
+        {0x6F, 0x23BA}, {0x70, 0x23BB}, {0x71, 0x2500}, {0x72, 0x23BC},
+        {0x73, 0x23BD}, {0x74, 0x251C}, {0x75, 0x2524}, {0x76, 0x2534},
+        {0x77, 0x252C}, {0x78, 0x2502}, {0x79, 0x2264}, {0x7A, 0x2265},
+        {0x7B, 0x03C0}, {0x7C, 0x2260}, {0x7D, 0x00A3}, {0x7E, 0x00B7},
+    }
+    local vtg = {}
+    for i, kv in ipairs(vtg_kv) do
+        local k, v = unpack(kv)
+        vtg[k] = v
+    end
+    return vtg
+end
+
+local function build_ibm()
+    -- Code Page 437
+    local ibm_kv = {
+        {0x01, 0x263A}, {0x02, 0x263B}, {0x03, 0x2665}, {0x04, 0x2666},
+        {0x05, 0x2663}, {0x06, 0x2660}, {0x07, 0x2022}, {0x08, 0x25D8},
+        {0x09, 0x2218}, {0x0A, 0x25D9}, {0x0B, 0x2642}, {0x0C, 0x2640},
+        {0x0D, 0x266A}, {0x0E, 0x266B}, {0x0F, 0x263C}, {0x10, 0x25BA},
+        {0x11, 0x25C4}, {0x12, 0x2195}, {0x13, 0x203C}, {0x14, 0x00B6},
+        {0x15, 0x00A7}, {0x16, 0x25AC}, {0x17, 0x21A8}, {0x18, 0x2191},
+        {0x19, 0x2193}, {0x1A, 0x2192}, {0x1B, 0x2190}, {0x1C, 0x221F},
+        {0x1D, 0x2194}, {0x1E, 0x25B2}, {0x1F, 0x25BC}, {0x7F, 0x2302},
+
+        {0x80, 0x00C7}, {0x81, 0x00FC}, {0x82, 0x00E9}, {0x83, 0x00E2},
+        {0x84, 0x00E4}, {0x85, 0x00E0}, {0x86, 0x00E5}, {0x87, 0x00E7},
+        {0x88, 0x00EA}, {0x89, 0x00EB}, {0x8A, 0x00E8}, {0x8B, 0x00EF},
+        {0x8C, 0x00EE}, {0x8D, 0x00EC}, {0x8E, 0x00C4}, {0x8F, 0x00C5},
+        {0x90, 0x00C9}, {0x91, 0x00E6}, {0x92, 0x00C6}, {0x93, 0x00F4},
+        {0x94, 0x00F6}, {0x95, 0x00F2}, {0x96, 0x00FB}, {0x97, 0x00F9},
+        {0x98, 0x00FF}, {0x99, 0x00D6}, {0x9A, 0x00DC}, {0x9B, 0x00A2},
+        {0x9C, 0x00A3}, {0x9D, 0x00A5}, {0x9E, 0x20A7}, {0x9F, 0x0192},
+        {0xA0, 0x00E1}, {0xA1, 0x00ED}, {0xA2, 0x00F3}, {0xA3, 0x00FA},
+        {0xA4, 0x00F1}, {0xA5, 0x00D1}, {0xA6, 0x00AA}, {0xA7, 0x00BA},
+        {0xA8, 0x00BF}, {0xA9, 0x2310}, {0xAA, 0x00AC}, {0xAB, 0x00BD},
+        {0xAC, 0x00BC}, {0xAD, 0x00A1}, {0xAE, 0x00AB}, {0xAF, 0x00BB},
+        {0xB0, 0x2591}, {0xB1, 0x2592}, {0xB2, 0x2593}, {0xB3, 0x2502},
+        {0xB4, 0x2524}, {0xB5, 0x2561}, {0xB6, 0x2562}, {0xB7, 0x2556},
+        {0xB8, 0x2555}, {0xB9, 0x2563}, {0xBA, 0x2551}, {0xBB, 0x2557},
+        {0xBC, 0x255D}, {0xBD, 0x255C}, {0xBE, 0x255B}, {0xBF, 0x2510},
+        {0xC0, 0x2514}, {0xC1, 0x2534}, {0xC2, 0x252C}, {0xC3, 0x251C},
+        {0xC4, 0x2500}, {0xC5, 0x253C}, {0xC6, 0x255E}, {0xC7, 0x255F},
+        {0xC8, 0x255A}, {0xC9, 0x2554}, {0xCA, 0x2569}, {0xCB, 0x2566},
+        {0xCC, 0x2560}, {0xCD, 0x2550}, {0xCE, 0x256C}, {0xCF, 0x2567},
+        {0xD0, 0x2568}, {0xD1, 0x2564}, {0xD2, 0x2565}, {0xD3, 0x2559},
+        {0xD4, 0x2558}, {0xD5, 0x2552}, {0xD6, 0x2553}, {0xD7, 0x256B},
+        {0xD8, 0x256A}, {0xD9, 0x2518}, {0xDA, 0x250C}, {0xDB, 0x2588},
+        {0xDC, 0x2584}, {0xDD, 0x258C}, {0xDE, 0x2590}, {0xDF, 0x2580},
+        {0xE0, 0x03B1}, {0xE1, 0x00DF}, {0xE2, 0x0393}, {0xE3, 0x03C0},
+        {0xE4, 0x03A3}, {0xE5, 0x03C3}, {0xE6, 0x00B5}, {0xE7, 0x03C4},
+        {0xE8, 0x03A6}, {0xE9, 0x0398}, {0xEA, 0x03A9}, {0xEB, 0x03B4},
+        {0xEC, 0x221E}, {0xED, 0x03C6}, {0xEE, 0x03B5}, {0xEF, 0x2229},
+        {0xF0, 0x2261}, {0xF1, 0x00B1}, {0xF2, 0x2265}, {0xF3, 0x2264},
+        {0xF4, 0x2320}, {0xF5, 0x2321}, {0xF6, 0x00F7}, {0xF7, 0x2248},
+        {0xF8, 0x00B0}, {0xF9, 0x2219}, {0xFA, 0x00B7}, {0xFB, 0x221A},
+        {0xFC, 0x207F}, {0xFD, 0x00B2}, {0xFE, 0x25A0}, {0xFF, 0x00A0}
+    }
+    local ibm = {}
+    for i, kv in ipairs(ibm_kv) do
+        local k, v = unpack(kv)
+        ibm[k] = v
+    end
+    return ibm
+end
+
+local vtg = build_vtg()
+local ibm = build_ibm()
+
+local function gen_header(name, tab)
+    local fname = name..".h"
+    local fp = io.open(fname, "w")
+    fp:write("static uint16_t "..name.."[0x100] = {")
+    local i = 0
+    for j = 1, 32 do
+        fp:write("\n   ")
+        for k = 1, 8 do
+            local c = tab[i] or i
+            fp:write((" 0x%04X,"):format(c))
+            i = i + 1
+        end
+    end
+    fp:write("\n};\n")
+    fp:close()
+end
+
+gen_header("cs_vtg", vtg)
+gen_header("cs_437", ibm)

diff --git a/gif.c b/gif.c
new file mode 100644
index 0000000..1cb52a9
--- /dev/null
+++ b/gif.c
@@ -0,0 +1,218 @@
+/*
+gcc -Wall -Wextra -std=c99 -O0 -g -fsanitize=address -fno-omit-frame-pointer -c gif.c
+gcc -Wall -Wextra -std=c99 -O2 -c gif.c
+gcc -shared -o libgif.so gif.o
+*/
+
+/* NOTES
+   - The encoder dumps uint16_t numbers as is into GIF files, which means it
+     only works on little-endian machines, since this is what GIF requires.
+   - The encoder is very limited:
+     * the global palette size is fixed in 16 colors;
+     * there's no way to add local palettes;
+     * the only GIF89a extension implemented is GCE (to set delay times).
+   - The compressor only sends the clear code once per image, as the first
+     code (no adaptive compression technique is employed). This is suboptimal
+     for large and complex images.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "gif.h"
+
+struct Node {
+    uint16_t key;
+    struct Node *children[0x10];
+};
+typedef struct Node Node;
+
+static Node *
+new_node(uint16_t key)
+{
+    Node *node = calloc(1, sizeof(*node));
+    if (node)
+        node->key = key;
+    return node;
+}
+
+static void
+del_trie(Node *root)
+{
+    if (!root)
+        return;
+    for (int i = 0; i < 0x10; i++)
+        del_trie(root->children[i]);
+    free(root);
+}
+
+GIF *
+new_gif(const char *fname, uint16_t w, uint16_t h, uint8_t *gct)
+{
+    GIF *gif = calloc(1, sizeof(*gif) + 2*w*h);
+    gif->w = w; gif->h = h;
+    gif->cur = (uint8_t *) &gif[1];
+    gif->old = &gif->cur[w*h];
+    /* fill back-buffer with invalid pixels to force overwrite */
+    memset(gif->old, 0x10, w*h);
+    gif->fd = creat(fname, 0666);
+    if (gif->fd == -1)
+        return NULL;
+    write(gif->fd, "GIF89a", 6);
+    write(gif->fd, &w, 2);
+    write(gif->fd, &h, 2);
+    write(gif->fd, (uint8_t []) {0xF3, 0x00, 0x00}, 3);
+    write(gif->fd, gct, 0x30);
+    return gif;
+}
+
+/* Add packed key to buffer, updating offset and partial.
+ *   gif->offset holds position to put next *bit*
+ *   gif->partial holds bits to include in next byte */
+static void
+put_key(GIF *gif, uint16_t key, int key_size)
+{
+    int byte_offset, bit_offset, bits_to_write;
+    byte_offset = gif->offset / 8;
+    bit_offset = gif->offset % 8;
+    gif->partial |= ((uint32_t) key) << bit_offset;
+    bits_to_write = bit_offset + key_size;
+    while (bits_to_write >= 8) {
+        gif->buffer[byte_offset++] = gif->partial & 0xFF;
+        if (byte_offset == 0xFF) {
+            write(gif->fd, "\xFF", 1);
+            write(gif->fd, gif->buffer, 0xFF);
+            byte_offset = 0;
+        }
+        gif->partial >>= 8;
+        bits_to_write -= 8;
+    }
+    gif->offset = (gif->offset + key_size) % (0xFF * 8);
+}
+
+static void
+end_key(GIF *gif)
+{
+    int byte_offset;
+    byte_offset = gif->offset / 8;
+    gif->buffer[byte_offset++] = gif->partial & 0xFF;
+    write(gif->fd, (uint8_t []) {byte_offset}, 1);
+    write(gif->fd, gif->buffer, byte_offset);
+    write(gif->fd, "\0", 1);
+    gif->offset = gif->partial = 0;
+}
+
+static void
+put_image(GIF *gif, uint16_t w, uint16_t h, uint16_t x, uint16_t y)
+{
+    int nkeys, key_size, i, j;
+    Node *node, *child, *root;
+
+    root = malloc(sizeof(*root));
+    write(gif->fd, ",", 1);
+    write(gif->fd, &x, 2);
+    write(gif->fd, &y, 2);
+    write(gif->fd, &w, 2);
+    write(gif->fd, &h, 2);
+    write(gif->fd, (uint8_t []) {0x00, 0x04}, 2);
+    /* Create nodes for single pixels. */
+    for (nkeys = 0; nkeys < 0x10; nkeys++)
+        root->children[nkeys] = new_node(nkeys);
+    node = root;
+    key_size = 5;
+    nkeys += 2; /* skip clear code and stop code */
+    put_key(gif, 0x10, key_size); /* clear code */
+    for (i = y; i < y+h; i++) {
+        for (j = x; j < x+w; j++) {
+#ifdef CROP
+            uint8_t pixel = 2;
+#else
+            uint8_t pixel = gif->cur[i*gif->w+j];
+#endif
+            child = node->children[pixel];
+            if (child) {
+                node = child;
+            } else {
+                put_key(gif, node->key, key_size);
+                if (nkeys < 0x1000) {
+                    if (nkeys == (1 << key_size))
+                        key_size++;
+                    node->children[pixel] = new_node(nkeys++);
+                }
+                node = root->children[pixel];
+            }
+        }
+    }
+    put_key(gif, node->key, key_size);
+    put_key(gif, 0x11, key_size); /* stop code */
+    end_key(gif);
+    del_trie(root);
+}
+
+static void
+get_bbox(GIF *gif, uint16_t *w, uint16_t *h, uint16_t *x, uint16_t *y)
+{
+    int i, j, k;
+    int left, right, top, bottom;
+    left = gif->w; right = 0;
+    top = gif->h; bottom = 0;
+    k = 0;
+    for (i = 0; i < gif->h; i++) {
+        for (j = 0; j < gif->w; j++, k++) {
+            if (gif->cur[k] != gif->old[k]) {
+                if (j < left)   left    = j;
+                if (j > right)  right   = j;
+                if (i < top)    top     = i;
+                if (i > bottom) bottom  = i;
+            }
+        }
+    }
+    *x = left; *y = top;
+    *w = right - left + 1;
+    *h = bottom - top + 1;
+}
+
+static void
+set_delay(GIF *gif, uint16_t d)
+{
+#ifdef CROP
+    write(gif->fd, (uint8_t []) {'!', 0xF9, 0x04, 0x08}, 4);
+#else
+    write(gif->fd, (uint8_t []) {'!', 0xF9, 0x04, 0x04}, 4);
+#endif
+    write(gif->fd, &d, 2);
+    write(gif->fd, "\0\0", 2);
+}
+
+void
+add_frame(GIF *gif, uint16_t d)
+{
+    uint16_t w, h, x, y;
+    uint8_t *tmp;
+
+    if (d)
+        set_delay(gif, d);
+    get_bbox(gif, &w, &h, &x, &y);
+    if (x == gif->w || y == gif->h) {
+        /* image haven't changed; save one pixel just to add delay */
+        w = h = 1;
+        x = y = 0;
+    }
+    put_image(gif, w, h, x, y);
+    tmp = gif->old;
+    gif->old = gif->cur;
+    gif->cur = tmp;
+}
+
+void
+close_gif(GIF* gif)
+{
+    write(gif->fd, ";", 1);
+    close(gif->fd);
+    free(gif);
+}

diff --git a/gif.h b/gif.h
new file mode 100644
index 0000000..a9dbe72
--- /dev/null
+++ b/gif.h
@@ -0,0 +1,14 @@
+#include <stdint.h>
+
+typedef struct GIF {
+    uint16_t w, h;
+    int fd;
+    int offset;
+    uint8_t *cur, *old;
+    uint32_t partial;
+    uint8_t buffer[0xFF];
+} GIF;
+
+GIF *new_gif(const char *fname, uint16_t w, uint16_t h, uint8_t *gct);
+void add_frame(GIF *gif, uint16_t d);
+void close_gif(GIF* gif);

diff --git a/main.c b/main.c
new file mode 100644
index 0000000..6ca77d9
--- /dev/null
+++ b/main.c
@@ -0,0 +1,195 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "term.h"
+//#include "dump.h"
+#include "mbf.h"
+#include "gif.h"
+
+void
+parse_script(Term *term, const char *timing, const char *dialogue)
+{
+    FILE *ft;
+    int fd;
+    float t;
+    int n;
+    uint8_t ch;
+
+    ft = fopen(timing, "r");
+    fd = open(dialogue, O_RDONLY);
+    if (ft == NULL || fd == -1)
+        return;
+    /* discard first line of dialogue */
+    do read(fd, &ch, 1); while (ch != '\n');
+    while (fscanf(ft, "%f %d\n", &t, &n) == 2) {
+        while (n--) {
+            read(fd, &ch, 1);
+            parse(term, ch);
+        }
+    }
+    close(fd);
+    fclose(ft);
+}
+
+int
+get_index(Font *font, uint16_t code)
+{
+    int index;
+
+    index = search_glyph(font, code);
+    if (index == -1)
+        index = search_glyph(font, 0xFFFD);
+    if (index == -1)
+        index = search_glyph(font, 0x003F);
+    if (index == -1)
+        index = search_glyph(font, 0x0020);
+    if (index == -1)
+        index = search_glyph(font, 0x0000);
+    return index;
+}
+
+uint8_t
+get_pair(Term *term, int row, int col)
+{
+    Cell cell;
+    uint8_t fore, back;
+    int inverse;
+
+    /* TODO: add support for A_INVISIBLE */
+    inverse = term->mode & M_REVERSE;
+    //~ if (term->mode & M_CURSORVIS)
+        //~ inverse = term->row == row && term->col == col ? !inverse : inverse;
+    cell = term->addr[row][col];
+    inverse = cell.attr & A_INVERSE ? !inverse : inverse;
+    if (inverse) {
+        fore = cell.pair & 0xF;
+        back = cell.pair >> 4;
+    } else {
+        fore = cell.pair >> 4;
+        back = cell.pair & 0xF;
+    }
+    if (cell.attr & (A_DIM | A_UNDERLINE))
+        fore = 0x6;
+    else if (cell.attr & (A_ITALIC | A_CROSSED))
+        fore = 0x2;
+    if (cell.attr & A_BOLD)
+        fore |= 0x8;
+    return (fore << 4) | (back & 0xF);
+}
+
+void
+draw_char(Font *font, GIF *gif, uint16_t code, uint8_t pair, int row, int col)
+{
+    int i, j;
+    int x, y;
+    int index;
+    int pixel;
+    uint8_t *strip;
+
+    index = get_index(font, code);
+    if (index == -1)
+        return;
+    strip = &font->data[font->stride * font->header.h * index];
+    y = font->header.h * row;
+    for (i = 0; i < font->header.h; i++) {
+        x = font->header.w * col;
+        for (j = 0; j < font->header.w; j++) {
+            pixel = strip[j >> 3] & (1 << (7 - (j & 7)));
+            gif->cur[y * gif->w + x] = pixel ? pair >> 4 : pair & 0xF;
+            x++;
+        }
+        y++;
+        strip += font->stride;
+    }
+}
+
+void
+render(Term *term, Font *font, GIF *gif, uint16_t delay)
+{
+    int i, j;
+    uint16_t code;
+    uint8_t pair;
+
+    for (i = 0; i < term->rows; i++) {
+        for (j = 0; j < term->cols; j++) {
+            code = term->addr[i][j].code;
+            pair = get_pair(term, i, j);
+            draw_char(font, gif, code, pair, i, j);
+        }
+    }
+    add_frame(gif, delay);
+}
+
+void
+convert_script(Term *term, const char *timing, const char *dialogue,
+               const char *mbf, const char *anim)
+{
+    FILE *ft;
+    int fd;
+    float t;
+    int n;
+    uint8_t ch;
+    Font *font;
+    int w, h;
+    int i;
+    uint16_t d;
+    GIF *gif;
+
+    ft = fopen(timing, "r");
+    if (!ft)
+        goto no_ft;
+    fd = open(dialogue, O_RDONLY);
+    if (fd == -1)
+        goto no_fd;
+    font = load_font(mbf);
+    if (!font)
+        goto no_font;
+    w = term->cols * font->header.w;
+    h = term->rows * font->header.h;
+    gif = new_gif(anim, w, h, term->plt);
+    if (!gif)
+        goto no_gif;
+    /* discard first line of dialogue */
+    do read(fd, &ch, 1); while (ch != '\n');
+    i = 0;
+    while (fscanf(ft, "%f %d\n", &t, &n) == 2) {
+        d = (uint16_t) (t * 100.0 / 3.0);
+        if (i)
+            render(term, font, gif, d);
+        while (n--) {
+            read(fd, &ch, 1);
+            parse(term, ch);
+        }
+        i++;
+    }
+    render(term, font, gif, 0);
+    close_gif(gif);
+no_gif:
+    free(font);
+no_font:
+    close(fd);
+no_fd:
+    fclose(ft);
+no_ft:
+    return;
+}
+
+int
+main(int argc, char *argv[])
+{
+    Term *term;
+
+    if (argc != 3)
+        return 1;
+    term = new_term(30, 80);
+    //parse_script(term, argv[1], argv[2]);
+    //dump_txt(term, "matrix.txt");
+    convert_script(term, argv[1], argv[2], "6x11.mbf", "con.gif");
+    free(term);
+    return 0;
+}

diff --git a/mbf.c b/mbf.c
new file mode 100644
index 0000000..b9fa782
--- /dev/null
+++ b/mbf.c
@@ -0,0 +1,104 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "mbf.h"
+
+Font *
+load_font(const char *fname)
+{
+    int fd;
+    char sig[4];
+    Header header;
+    int stride;
+    size_t ranges_size, data_size;
+    Font *font;
+
+    fd = open(fname, O_RDONLY);
+    if (fd == -1)
+        return NULL;
+    read(fd, sig, sizeof(sig));
+    if (memcmp(sig, (char []) {'M', 'B', 'F', 0x01}, sizeof(sig))) {
+        close(fd);
+        return NULL;
+    }
+    read(fd, &header, sizeof(header));
+    /* stride = ceil(w / 8) = floor(w / 8) + (w % 8 ? 1 : 0) */
+    stride = (header.w >> 3) + !!(header.w & 7);
+    ranges_size = header.nr * sizeof(Range);
+    data_size = header.ng * stride * header.h;
+    font = malloc(sizeof(Font) + ranges_size + data_size);
+    if (!font) {
+        close(fd);
+        return NULL;
+    }
+    font->stride = stride;
+    font->header = header;
+    font->ranges = (Range *) &font[1];
+    read(fd, font->ranges, ranges_size);
+    font->data = (uint8_t *) &font->ranges[header.nr];
+    read(fd, font->data, data_size);
+    close(fd);
+    return font;
+}
+
+int
+search_glyph(Font *font, uint16_t code)
+{
+    int index, i;
+    Range r;
+
+    index = 0;
+    for (i = 0; i < font->header.nr; i++) {
+        r = font->ranges[i];
+        if (code < r.offset)
+            return -1;
+        if (code < r.offset + r.length)
+            return index + code - r.offset;
+        index += r.length;
+    }
+    return -1;
+}
+
+void
+print_glyph(Font *font, uint16_t code)
+{
+    int index, pixel, i, j;
+    uint8_t *row;
+
+    index = search_glyph(font, code);
+    if (index == -1)
+        return;
+    row = &font->data[font->stride * font->header.h * index];
+    for (i = 0; i < font->header.h; i++) {
+        for (j = 0; j < font->header.w; j++) {
+            pixel = row[j >> 3] & (1 << (7 - (j & 7)));
+            putchar(pixel ? 'X' : ' ');
+        }
+        putchar('\n');
+        row += font->stride;
+    }
+}
+
+#if 0
+int
+main(int argc, char *argv[])
+{
+    Font *font;
+
+    if (argc != 2)
+        return 1;
+    font = load_font(argv[1]);
+    if (!font)
+        return 1;
+    //printf("%d\n", search_glyph(font, 200));
+    print_glyph(font, 0xFFFD);
+    free(font);
+    return 0;
+}
+#endif

diff --git a/mbf.h b/mbf.h
new file mode 100644
index 0000000..3929984
--- /dev/null
+++ b/mbf.h
@@ -0,0 +1,19 @@
+typedef struct Header {
+    uint16_t ng;
+    uint8_t w, h;
+    uint16_t nr;
+} Header;
+
+typedef struct Range {
+    uint16_t offset, length;
+} Range;
+
+typedef struct Font {
+    Header header;
+    int stride;
+    Range *ranges;
+    uint8_t *data;
+} Font;
+
+Font *load_font(const char *fname);
+int search_glyph(Font *font, uint16_t code);

diff --git a/term.c b/term.c
new file mode 100644
index 0000000..9efe062
--- /dev/null
+++ b/term.c
@@ -0,0 +1,725 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "term.h"
+#include "default.h"
+#include "cs_vtg.h"
+#include "cs_437.h"
+
+#define MIN(A, B)   ((A) < (B) ? (A) : (B))
+#define MAX(A, B)   ((A) > (B) ? (A) : (B))
+
+void
+save_cursor(Term *term)
+{
+    term->save_cursor.row = term->row;
+    term->save_cursor.col = term->col;
+}
+
+void
+load_cursor(Term *term)
+{
+    term->row = term->save_cursor.row;
+    term->col = term->save_cursor.col;
+}
+
+void
+save_misc(Term *term)
+{
+    term->save_misc.row = term->row;
+    term->save_misc.col = term->col;
+    term->save_misc.origin_on = term->mode & M_ORIGIN;
+    term->save_misc.attr = term->attr;
+    term->save_misc.pair = term->pair;
+    term->save_misc.cs_array[0] = term->cs_array[0];
+    term->save_misc.cs_array[1] = term->cs_array[1];
+    term->save_misc.cs_index = term->cs_index;
+}
+
+void
+load_misc(Term *term)
+{
+    term->row = term->save_misc.row;
+    term->col = term->save_misc.col;
+    if (term->save_misc.origin_on)
+        term->mode |= M_ORIGIN;
+    else
+        term->mode &= ~M_ORIGIN;
+    term->attr = term->save_misc.attr;
+    term->pair = term->save_misc.pair;
+    term->cs_array[0] = term->save_misc.cs_array[0];
+    term->cs_array[1] = term->save_misc.cs_array[1];
+    term->cs_index = term->save_misc.cs_index;
+}
+
+void
+reset(Term *term)
+{
+    int i, j;
+
+    term->row = term->col = 0;
+    term->top = 0;
+    term->bot = term->rows - 1;
+    term->mode = def_mode;
+    term->attr = def_attr;
+    term->pair = def_pair;
+    term->cs_array[0] = CS_BMP;
+    term->cs_array[1] = CS_VTG;
+    term->cs_index = 0;
+    term->state = S_ANY;
+    term->parlen = 0;
+    memcpy(term->plt, def_plt, sizeof(term->plt));
+    for (i = 0; i < term->rows; i++) {
+        term->addr[i] = &term->cells[i*term->cols];
+        for (j = 0; j < term->cols; j++)
+            term->addr[i][j] = (Cell) {EMPTY, def_attr, def_pair};
+    }
+    save_cursor(term);
+    save_misc(term);
+}
+
+Term *
+new_term(int rows, int cols)
+{
+    size_t size = sizeof(Term) + rows*sizeof(Cell *) + rows*cols*sizeof(Cell);
+    Term *term = malloc(size);
+    if (!term)
+        return NULL;
+    term->rows = rows;
+    term->cols = cols;
+    term->addr = (Cell **) &term[1];
+    term->cells = (Cell *) &term->addr[rows];
+    reset(term);
+    return term;
+}
+
+uint16_t
+char_code(Term *term)
+{
+    int i;
+    uint16_t code = term->partial[0] & ((1 << (8 - term->parlen)) - 1);
+    for (i = 1; i < term->parlen; i++)
+        code = (code << 6) | (term->partial[i] & 0x3F);
+    return code;
+}
+
+/* Move lines down and put a blank line at the top. */
+void
+scroll_up(Term *term)
+{
+    int row, col;
+    Cell *addr;
+
+    addr = term->addr[term->bot];
+    for (row = term->bot; row > term->top; row--)
+        term->addr[row] = term->addr[row-1];
+    term->addr[term->top] = addr;
+    for (col = 0; col < term->cols; col++)
+        term->addr[term->top][col] = BLANK;
+}
+
+/* Move lines up and put a blank line at the bottom. */
+void
+scroll_down(Term *term)
+{
+    int row, col;
+    Cell *addr;
+
+    addr = term->addr[term->top];
+    for (row = term->top; row < term->bot; row++)
+        term->addr[row] = term->addr[row+1];
+    term->addr[term->bot] = addr;
+    for (col = 0; col < term->cols; col++)
+        term->addr[term->bot][col] = BLANK;
+}
+
+void
+addchar(Term *term, uint16_t code)
+{
+    Cell cell = (Cell) {code, term->attr, term->pair};
+    if (term->mode & M_INSERT) {
+        Cell next;
+        int col;
+        for (col = term->col; col < term->cols; col++) {
+            next = term->addr[term->row][col];
+            term->addr[term->row][col] = cell;
+            cell = next;
+        }
+    } else {
+        term->addr[term->row][term->col] = cell;
+    }
+    if (term->col < term->cols - 1) {
+        term->col++;
+    } else if (term->mode & M_AUTOWRAP) {
+        if (term->row < term->bot)
+            term->row++;
+        else
+            scroll_down(term);
+        term->col = 0;
+    }
+}
+
+void
+linefeed(Term *term)
+{
+    if (term->row == term->bot)
+        scroll_down(term);
+    else
+        term->row++;
+    if (term->mode & M_NEWLINE)
+        term->col = 0;
+}
+
+void
+ctrlchar(Term *term, uint8_t byte)
+{
+    switch (byte) {
+    case 0x08:
+        if (term->col) term->col--;
+        break;
+    case 0x09:
+        /* TODO: go to next tab stop or end of line */
+        fprintf(stderr, "NYI: Control Character 0x09 (TAB)\n");
+        break;
+    case 0x0A: case 0x0B: case 0x0C:
+        linefeed(term);
+        break;
+    case 0x0D:
+        term->col = 0;
+        break;
+    case 0x0E:
+        term->cs_index = 1;
+        break;
+    case 0x0F:
+        term->cs_index = 0;
+        break;
+    }
+}
+
+void
+escseq(Term *term, uint8_t byte)
+{
+    uint8_t first, second;
+
+    if (term->parlen) {
+        first = *term->partial;
+        second = byte;
+    } else {
+        first = byte;
+        second = 0;
+    }
+    switch (first) {
+    case 'c':
+        reset(term);
+        break;
+    case 'D':
+        if (term->row == term->bot)
+            scroll_down(term);
+        else
+            term->row++;
+        break;
+    case 'E':
+        if (term->row == term->bot) {
+            scroll_down(term);
+            term->col = 0;
+        } else {
+            term->row++;
+        }
+        break;
+    case 'H':
+        /* TODO: set tab stop at current column */
+        fprintf(stderr, "NYI: ESC Sequence H (HTS)\n");
+        break;
+    case 'M':
+        if (term->row == term->top)
+            scroll_up(term);
+        else
+            term->row--;
+        break;
+    case 'Z':
+        /* TODO: DEC private identification */
+        fprintf(stderr, "NYI: ESC Sequence Z (DECID)\n");
+        break;
+    case '7':
+        save_misc(term);
+        break;
+    case '8':
+        load_misc(term);
+        break;
+    case '%':
+        /* TODO: select charset */
+        fprintf(stderr, "NYI: ESC Sequence %% (character set selection)\n");
+        break;
+    case '#':
+        /* TODO: DEC screen alignment test */
+        fprintf(stderr, "NYI: ESC Sequence # (DECALN)\n");
+        break;
+    case '(':
+        switch (second) {
+        case 'B':
+            term->cs_array[0] = CS_BMP;
+            break;
+        case '0':
+            term->cs_array[0] = CS_VTG;
+            break;
+        case 'U':
+            term->cs_array[0] = CS_437;
+            break;
+        case 'K':
+            fprintf(stderr, "UNS: user-defined mapping\n");
+        }
+        break;
+    case ')':
+        switch (second) {
+        case 'B':
+            term->cs_array[1] = CS_BMP;
+            break;
+        case '0':
+            term->cs_array[1] = CS_VTG;
+            break;
+        case 'U':
+            term->cs_array[1] = CS_437;
+            break;
+        case 'K':
+            fprintf(stderr, "UNS: user-defined mapping\n");
+        }
+        break;
+    case '>':
+        /* TODO: set numeric keypad mode */
+        fprintf(stderr, "NYI: ESC Sequence > (DECPNM)\n");
+        break;
+    case '=':
+        /* TODO: set application keypad mode */
+        fprintf(stderr, "NYI: ESC Sequence = (DECPAM)\n");
+        break;
+    default:
+        fprintf(stderr, "UNS: ESC Sequence %c\n", first);
+    }
+}
+
+int
+getparams(char *partial, int *params, int n)
+{
+    char *next;
+    char *token = partial;
+    int i = 0;
+    if (!*partial) {
+        params[0] = 0;
+        return 1;
+    }
+    while (i < n && *token) {
+        params[i++] = strtol(token, &next, 10);
+        if (*next) next++;
+        token = next;
+    }
+    if (i < n && *(token-1) == ';')
+        params[i++] = 0;
+    return i;
+}
+
+#define SWITCH(T, F, V) (T)->mode = (V) ? (T)->mode | (F) : (T)->mode & ~(F)
+
+void
+modeswitch(Term *term, int private, int number, int value)
+{
+    if (private) {
+        /* DEC modes */
+        switch (number) {
+        case 1:
+            SWITCH(term, M_CURSORKEY, value);
+            break;
+        case 3:
+            /* TODO: 80/132 columns mode switch */
+            fprintf(stderr, "NYI: DEC mode 3\n");
+            break;
+        case 5:
+            SWITCH(term, M_REVERSE, value);
+            break;
+        case 6:
+            SWITCH(term, M_ORIGIN, value);
+            term->row = term->top;
+            term->col = 0;
+            break;
+        case 7:
+            SWITCH(term, M_AUTOWRAP, value);
+            break;
+        case 8:
+            SWITCH(term, M_AUTORPT, value);
+            break;
+        case 9:
+            SWITCH(term, M_MOUSEX10, value);
+            break;
+        case 25:
+            SWITCH(term, M_CURSORVIS, value);
+            break;
+        case 1000:
+            SWITCH(term, M_MOUSEX11, value);
+            break;
+        default:
+            fprintf(stderr, "UNS: DEC mode %d\n", number);
+        }
+    } else {
+        /* ANSI modes */
+        switch (number) {
+        case 3:
+            SWITCH(term, M_DISPCTRL, value);
+            break;
+        case 4:
+            SWITCH(term, M_INSERT, value);
+            break;
+        case 20:
+            SWITCH(term, M_NEWLINE, value);
+            break;
+        default:
+            fprintf(stderr, "UNS: ANSI mode %d\n", number);
+        }
+    }
+}
+
+void
+sgr(Term *term, int number)
+{
+    switch (number) {
+    case 0:
+        term->attr = def_attr;
+        break;
+    case 1:
+        term->attr |= A_BOLD;
+        break;
+    case 2:
+        term->attr |= A_DIM;
+        break;
+    case 4:
+        term->attr |= A_UNDERLINE;
+        break;
+    case 5:
+        term->attr |= A_BLINK;
+        break;
+    case 7:
+        term->attr |= A_INVERSE;
+        break;
+    case 10:
+        /* TODO: reset toggle meta flag */
+        term->cs_array[term->cs_index = 0] = CS_BMP;
+        term->mode &= ~M_DISPCTRL;
+        break;
+    case 11:
+        /* TODO: reset toggle meta flag */
+        term->cs_array[term->cs_index] = CS_437;
+        term->mode |= M_DISPCTRL;
+        break;
+    case 12:
+        /* TODO: set toggle meta flag */
+        term->cs_array[term->cs_index] = CS_437;
+        term->mode |= M_DISPCTRL;
+        break;
+    case 21:
+        term->attr &= ~A_BOLD;
+        break;
+    case 22:
+        term->attr &= ~A_DIM;
+        break;
+    case 24:
+        term->attr &= ~A_UNDERLINE;
+        break;
+    case 25:
+        term->attr &= ~A_BLINK;
+        break;
+    case 27:
+        term->attr &= ~A_INVERSE;
+        break;
+    case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37:
+        term->pair = ((number - 30) << 4) | (term->pair & 0x0F);
+        break;
+    case 38:
+        term->attr |= A_UNDERLINE;
+        term->pair = (DEF_FORE << 4) | (term->pair & 0x0F);
+        break;
+    case 39:
+        term->attr &= ~A_UNDERLINE;
+        term->pair = (DEF_FORE << 4) | (term->pair & 0x0F);
+        break;
+    case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47:
+        term->pair = (term->pair & 0xF0) | (number - 40);
+        break;
+    case 49:
+        term->pair = (term->pair & 0xF0) | DEF_BACK;
+        break;
+    default:
+        fprintf(stderr, "UNS: SGR %d\n", number);
+    }
+}
+
+void
+ctrlseq(Term *term, uint8_t byte)
+{
+    int private;
+    int n, k, k1;
+    int params[MAX_PARAMS];
+    char *str;
+    int ra, rb, ca, cb;
+    int i, j;
+    Cell cell;
+
+    term->partial[term->parlen] = '\0';
+    if (*term->partial == '?') {
+        private = 1;
+        str = (char *) term->partial + 1;
+    } else {
+        private = 0;
+        str = (char *) term->partial;
+    }
+    n = getparams(str, params, MAX_PARAMS);
+    k = n ? *params : 0;
+    k1 = k ? k : 1;
+    switch (byte) {
+    case '@':
+        /* TODO: insert the indicated # of blank characters */
+        fprintf(stderr, "BYI: Control Sequence @ (ICH)\n");
+        break;
+    case 'A':
+        term->row -= k1;
+        break;
+    case 'B': case 'e':
+        term->row += k1;
+        break;
+    case 'C': case 'a':
+        term->col += k1;
+        break;
+    case 'D':
+        term->col -= k1;
+        break;
+    case 'E':
+        term->row += k1;
+        term->col = 0;
+        break;
+    case 'F':
+        term->row -= k1;
+        term->col = 0;
+        break;
+    case 'G': case '`':
+        term->col = k1 - 1;
+        break;
+    case 'H': case 'f':
+        if (n == 2) {
+            term->row = MAX(params[0], 1) - 1;
+            term->col = MAX(params[1], 1) - 1;
+        } else {
+            term->row = term->col = 0;
+        }
+        if (term->mode & M_ORIGIN)
+            term->row += term->top;
+        break;
+    case 'J':
+        ra = 0; rb = term->rows - 1;
+        ca = 0; cb = term->cols - 1;
+        if (k == 0) {
+            ra = term->row;
+            ca = term->col;
+        } else if (k == 1) {
+            rb = term->row;
+            cb = term->col;
+        }
+        for (j = ca; j < term->cols; j++)
+            term->addr[ra][j] = BLANK;
+        for (i = ra+1; i < rb; i++) {
+            for (j = 0; j < term->cols; j++) {
+                term->addr[i][j] = BLANK;
+            }
+        }
+        for (j = 0; j <= cb; j++)
+            term->addr[rb][j] = BLANK;
+        break;
+    case 'K':
+        ca = 0; cb = term->cols - 1;
+        if (k == 0)
+            ca = term->col;
+        else if (k == 1)
+            cb = term->col;
+        for (j = ca; j <= cb; j++)
+            term->addr[term->row][j] = BLANK;
+        break;
+    case 'L':
+        if (term->row < term->top || term->row > term->bot)
+            break;
+        /* This is implemented naively:
+             1. temporarily change the top margin to current row;
+             2. scroll up as many times as requested;
+             3. restore top margin to previous value. */
+        i = term->top;
+        term->top = term->row;
+        for (j = 0; j < k1; j++)
+            scroll_up(term);
+        term->top = i;
+        break;
+    case 'M':
+        if (term->row < term->top || term->row > term->bot)
+            break;
+        /* This is implemented naively:
+             1. temporarily change the top margin to current row;
+             2. scroll down as many times as requested;
+             3. restore top margin to previous value. */
+        /* TODO:
+             vt102-ug says:
+               "Lines added to bottom of screen have spaces with same character
+               attributes as last line moved up."
+             we need a more flexible scroll_down() to fix this. */
+        i = term->top;
+        term->top = term->row;
+        for (j = 0; j < k1; j++)
+            scroll_down(term);
+        term->top = i;
+        break;
+    case 'P':
+        cell = term->addr[term->row][term->cols-1];
+        cell.code = EMPTY;
+        for (j = term->col; j < term->cols-k1; j++)
+            term->addr[term->row][j] = term->addr[term->row][j+k1];
+        for (j = term->cols-k1; j < term->cols; j++)
+            term->addr[term->row][j] = cell;
+        break;
+    case 'X':
+        for (j = 0; j < k1; j++)
+            term->addr[term->row][term->col+j] = BLANK;
+        break;
+    case 'c':
+        /* TODO: answer ESC [ ? 6 c */
+        fprintf(stderr, "NYI: Control Sequence c (DA)\n");
+        break;
+    case 'd':
+        term->row = k1 - 1;
+        break;
+    case 'g':
+        /* TODO: clear tab stop */
+        fprintf(stderr, "NYI: Control Sequence g (TBC)\n");
+        break;
+    case 'h':
+        for (i = 0; i < n; i++)
+            modeswitch(term, private, params[i], 1);
+        break;
+    case 'l':
+        for (i = 0; i < n; i++)
+            modeswitch(term, private, params[i], 0);
+        break;
+    case 'm':
+        for (i = 0; i < n; i++)
+            sgr(term, params[i]);
+        break;
+    case 'n':
+        /* TODO: status report */
+        fprintf(stderr, "NYI: Control Sequence n (DSR)\n");
+        break;
+    case 'q':
+        /* TODO: set keyboard LEDs */
+        fprintf(stderr, "NYI: Control Sequence q (DECLL)\n");
+        break;
+    case 'r':
+        if (n == 2) {
+            term->top = MAX(params[0], 1) - 1;
+            term->bot = MAX(params[1], 1) - 1;
+        } else {
+            term->top = 0;
+            term->bot = term->rows - 1;
+        }
+        term->row = term->mode & M_ORIGIN ? term->top : 0;
+        term->col = 0;
+        break;
+    case 's':
+        save_cursor(term);
+        break;
+    case 'u':
+        load_cursor(term);
+        break;
+    default:
+        fprintf(stderr, "UNS: Control Sequence %c\n", byte);
+    }
+}
+
+#define CHARSET(T)      ((T)->cs_array[(T)->cs_index])
+#define PARCAT(T, B)    ((T)->partial[(T)->parlen++] = (B))
+#define RESET_STATE(T)  do { (T)->state = S_ANY; (T)->parlen = 0; } while(0)
+#define CHARLEN(B)      ((B) < 0xE0 ? 2 : ((B) < 0xF0 ? 3 : 4))
+
+void
+parse(Term *term, uint8_t byte)
+{
+    int es;
+
+    if (byte != 0x1B && byte < 0x20 && !(term->mode & M_DISPCTRL)) {
+        ctrlchar(term, byte);
+    } else {
+        switch (term->state) {
+        case S_ANY:
+            switch (byte) {
+            case 0x1B:
+                term->state = S_ESC;
+                break;
+            case 0x9B:
+                term->state = S_CSI;
+                break;
+            default:
+                switch (CHARSET(term)) {
+                case CS_BMP:
+                    if (byte < 0x80) {
+                        /* single-byte UTF-8, i.e. ASCII */
+                        addchar(term, byte);
+                    } else {
+                        term->unilen = CHARLEN(byte);
+                        PARCAT(term, byte);
+                        term->state = S_UNI;
+                    }
+                    break;
+                case CS_VTG:
+                    addchar(term, cs_vtg[byte]);
+                    break;
+                case CS_437:
+                    addchar(term, cs_437[byte]);
+                    break;
+                }
+            }
+            break;
+        case S_ESC:
+            es = 1;
+            if (!term->parlen) {
+                if (byte == 0x5B) {
+                    term->state = S_CSI;
+                    es = 0;
+                } else if (byte == 0x5D) {
+                    term->state = S_OSC;
+                    es = 0;
+                }
+            }
+            if (es) {
+                if (byte >= 0x20 && byte < 0x30) {
+                    PARCAT(term, byte);
+                } else {
+                    escseq(term, byte);
+                    RESET_STATE(term);
+                }
+            }
+            break;
+        case S_CSI:
+            if (byte < 0x40 || byte >= 0x7F) {
+                PARCAT(term, byte);
+            } else {
+                ctrlseq(term, byte);
+                RESET_STATE(term);
+            }
+            break;
+        case S_OSC:
+            /* TODO: set/reset palette entries */
+            fprintf(stderr, "NYI: Operating System Sequence\n");
+            RESET_STATE(term);
+            break;
+        case S_UNI:
+            PARCAT(term, byte);
+            if (term->parlen == term->unilen) {
+                addchar(term, char_code(term));
+                RESET_STATE(term);
+            }
+            break;
+        }
+    }
+}

diff --git a/term.h b/term.h
new file mode 100644
index 0000000..3785f53
--- /dev/null
+++ b/term.h
@@ -0,0 +1,78 @@
+#define A_NORMAL    0x00
+#define A_BOLD      0x01
+#define A_DIM       0x02
+#define A_ITALIC    0x04
+#define A_UNDERLINE 0x08
+#define A_BLINK     0x10
+#define A_INVERSE   0x20
+#define A_INVISIBLE 0x40
+#define A_CROSSED   0x80
+
+#define M_DISPCTRL  0x0001
+#define M_INSERT    0x0002
+#define M_NEWLINE   0x0004
+#define M_CURSORKEY 0x0008
+#define M_WIDETERM  0x0010
+#define M_REVERSE   0x0020
+#define M_ORIGIN    0x0040
+#define M_AUTOWRAP  0x0080
+#define M_AUTORPT   0x0100
+#define M_MOUSEX10  0x0200
+#define M_CURSORVIS 0x0400
+#define M_MOUSEX11  0x0800
+
+#define EMPTY       0x0020
+#define BCE         1
+#if BCE
+  #define BLANK (Cell) {EMPTY, def_attr, def_pair}
+#else
+  #define BLANK (Cell) {EMPTY, term->attr, term->pair}
+#endif
+
+#define MAX_PARTIAL 0x100
+#define MAX_PARAMS  0x10
+
+typedef struct Cell {
+    uint16_t code;
+    uint8_t attr;
+    uint8_t pair;
+} Cell;
+
+typedef enum CharSet {CS_BMP, CS_VTG, CS_437} CharSet;
+typedef enum State {S_ANY, S_ESC, S_CSI, S_OSC, S_UNI} State;
+
+typedef struct SaveCursor {
+    int row, col;
+} SaveCursor;
+
+typedef struct SaveMisc {
+    int row, col;
+    int origin_on;
+    uint8_t attr;
+    uint8_t pair;
+    CharSet cs_array[2];
+    int cs_index;
+} SaveMisc;
+
+typedef struct Term {
+    int rows, cols;
+    int row, col;
+    int top, bot;
+    uint16_t mode;
+    uint8_t attr;
+    uint8_t pair;
+    Cell **addr;
+    Cell *cells;
+    CharSet cs_array[2];
+    int cs_index;
+    SaveCursor save_cursor;
+    SaveMisc save_misc;
+    State state;
+    int parlen;
+    int unilen;
+    uint8_t partial[MAX_PARTIAL];
+    uint8_t plt[0x30];
+} Term;
+
+Term *new_term(int rows, int cols);
+void parse(Term *term, uint8_t byte);