ギャップバッファで単純なテキストエディタをつくります。
単純なのでC言語だけで書きます。
ただし、プログラムのコンパイルと実行にはOpenGLとGLUTが必要です。
大きな文字列を割り当ててギャップバッファとして使います。
更に、ポインタ配列を割り当て、それもギャップバッファとして、
行の先頭へのポインタを格納します。
typedef struct { int length; unsigned char **buffer; unsigned char *left, *right; int line, top, next; int col; } buffer; extern buffer b; void b_init(void); int b_prevchar(void); int b_nextchar(void); int b_remove(void); int b_newline(void); int b_nextline(void); int b_prevline(void); int b_insert(int c);
pixels.h は BOTTOM
の定義のためだけに include します。
画面に表示される行は
top
から top + BOTTOM - 1
までです。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "pixels.h" #include "buffer.h" static void error(const char *file, int line) { fprintf(stderr, "%s %d\n", file, line); exit(EXIT_FAILURE); } #define error_if(x) if (x) error(__FILE__, __LINE__) buffer b; /* つづく */
void b_init(void) { size_t size = 65536; b.length = 1000; b.buffer = malloc((b.length + 1) * sizeof *b.buffer); error_if(!b.buffer); b.buffer[0] = malloc(size); error_if(!b.buffer[0]); b.buffer[b.length] = b.buffer[0] + size; /* sentinel */ b.left = b.buffer[0]; b.right = b.buffer[b.length]; b.line = b.top = 0; b.next = b.length; b.col = -1; }
初期化。
b.col
を使わないときは-1を入れておきます。
int b_prevchar(void) { if (b.left == b.buffer[b.line]) { if (b.line == 0) { return -1; } b.buffer[--b.next] = b.right; if (b.line-- == b.top) { b.top--; } } else { *--b.right = *--b.left; } b.col = -1; return 0; }
カーソルが行の先頭にある場合と、それ以外の場合に分けます。
カーソルが行の先頭にある場合、前の行の末尾に移動します。
このとき、文字列バッファはそのままでポインタ配列だけ変更します。
(文字列バッファには改行文字が入っていないので)
int b_nextchar(void) { if (b.right == b.buffer[b.next]) { if (b.next == b.length) { return -1; } b.buffer[b.line + 1] = b.left; if (++b.line == b.top + BOTTOM) { b.top++; } b.next++; } else { *b.left++ = *b.right++; } b.col = -1; return 0; }
b_prevcharと同様。
インクリメント・デクリメントは前置と後置とでは意味が違います。
int b_remove(void) { if (b.left == b.buffer[b.line]) { if (b.line == 0) { return -1; } if (b.line-- == b.top) { b.top--; } } else { b.left--; } b.col = -1; return 0; }
これもb_prevcharとよく似ています。
int b_newline(void) { if (b.line == b.next - 1) { int i; unsigned char **p; /* realloc */ if (b.length > 0x1000000) { return -1; } p = b.buffer; b.buffer = realloc(p, (b.length * 2 + 1) * sizeof *b.buffer); if (!b.buffer) { b.buffer = p; return -1; } /* move pointers */ for (i = b.length; i >= b.next; --i) { b.buffer[i + b.length] = b.buffer[i]; } b.next += b.length; b.length += b.length; } b.buffer[b.line + 1] = b.left; if (++b.line == b.top + BOTTOM) { b.top++; } b.col = -1; return 0; }
改行文字を挿入します。
実際には内部の文字列バッファには改行文字を書き込みません。
ポインタ配列と文字列バッファの両方が溢れる場合を考えなくて済む、
というメリットがあります。
行数が0x1000000を越えたらエラーとしますが
この数字に深い意味はありません。
int b_nextline(void) { int gap, off, len, nlen; int move; if (b.next == b.length) { return -1; } gap = b.right - b.left; off = b.left - b.buffer[b.line]; len = b.buffer[b.next] - b.right; nlen = b.buffer[b.next + 1] - b.buffer[b.next]; if (b.col == -1) { if (nlen < off) { b.col = off; move = len + nlen; } else { move = len + off; } } else if (nlen < b.col) { move = len + nlen; } else { move = len + b.col; b.col = -1; } memmove(b.left, b.right, move); b.left += move; b.right += move; b.buffer[b.line + 1] = b.buffer[b.next] - gap; if (++b.line == b.top + BOTTOM) { b.top++; } b.next++; return 0; }
move
を計算という流れ。
memmoveはコピー元とコピー先の一部が重なっている場合にも問題なく使えます。
int b_prevline(void) { int gap, off, plen; int move; if (b.line == 0) { return -1; } gap = b.right - b.left; off = b.left - b.buffer[b.line]; plen = b.buffer[b.line] - b.buffer[b.line - 1]; if (b.col == -1) { if (plen < off) { b.col = off; move = off; } else { move = plen; } } else if (plen < b.col) { move = off; } else { move = off + plen - b.col; b.col = -1; } b.left -= move; b.right -= move; memmove(b.right, b.left, move); b.buffer[--b.next] = b.buffer[b.line] + gap; if (b.line-- == b.top) { b.top--; } return 0; }
int b_insert(int c) { if (b.left == b.right) { unsigned char *p; size_t size; int i; /* realloc */ size = b.buffer[b.length] - b.buffer[0]; if (size > 0x1000000) { return -1; } p = b.buffer[0]; b.buffer[0] = realloc(p, size * 2); if (!b.buffer[0]) { b.buffer[0] = p; return -1; } /* update pointers */ for (i = 1; i <= b.length; ++i) { b.buffer[i] = b.buffer[0] + (b.buffer[i] - p); } b.left = b.buffer[0] + (b.left - p); b.right = b.buffer[0] + (b.right - p); /* memmove */ memmove(b.right + size, b.right, b.buffer[b.length] - b.right); /* update pointers */ b.right += size; for (i = b.next; i <= b.length; ++i) { b.buffer[i] += size; } } *b.left++ = c; b.col = -1; return 0; }
#define WIDTH 640 #define HEIGHT 480 enum { red, green, blue }; extern unsigned char pixels[HEIGHT][WIDTH][3]; #define pixels_(x, y) pixels[HEIGHT - 1 - (y)][x] #define BOTTOM 24 void draw_string(int x, int y, const unsigned char *p, int len); void cursor(int x, int y); void clear(void);
自前のフレームバッファのようなものに書き込んで表示します。
フォントデータは JISX0213(所謂第3,4水準漢字)用bdfフォントのページ にあるものを元にしています。
#include "pixels.h" static unsigned char ascii[][14] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x00,0x00,0x18,0x18,0x18,0x18,0x10,0x10,0x10,0x00,0x00,0x18,0x18,0x00}, {0x00,0x6c,0x6c,0x24,0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x00,0x00,0x24,0x24,0x24,0x7e,0x24,0x24,0x48,0x7e,0x48,0x48,0x48,0x00}, {0x00,0x00,0x10,0x3c,0x52,0x52,0x50,0x38,0x14,0x52,0x52,0x3c,0x10,0x00}, {0x00,0x00,0x22,0x52,0x54,0x54,0x28,0x18,0x14,0x2a,0x2a,0x4a,0x44,0x00}, {0x00,0x00,0x10,0x28,0x28,0x10,0x26,0x52,0x52,0x4c,0x44,0x2a,0x10,0x00}, {0x00,0x30,0x30,0x10,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x00,0x04,0x08,0x10,0x10,0x20,0x20,0x20,0x20,0x20,0x10,0x10,0x08,0x04}, {0x00,0x20,0x10,0x08,0x08,0x04,0x04,0x04,0x04,0x04,0x08,0x08,0x10,0x20}, {0x00,0x00,0x00,0x10,0x10,0xd6,0x54,0x38,0x54,0xd6,0x10,0x10,0x00,0x00}, {0x00,0x00,0x00,0x10,0x10,0x10,0x10,0x7e,0x10,0x10,0x10,0x10,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x10,0x20}, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7e,0x00,0x00,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00}, {0x02,0x02,0x04,0x04,0x08,0x08,0x10,0x10,0x20,0x20,0x40,0x40,0x80,0x80}, {0x00,0x00,0x18,0x24,0x24,0x42,0x42,0x42,0x42,0x24,0x24,0x18,0x00,0x00}, {0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7c,0x00,0x00}, {0x00,0x00,0x18,0x24,0x42,0x22,0x04,0x08,0x10,0x20,0x22,0x7e,0x00,0x00}, {0x00,0x00,0x18,0x24,0x42,0x44,0x18,0x04,0x42,0x42,0x24,0x18,0x00,0x00}, {0x00,0x00,0x04,0x0c,0x14,0x14,0x24,0x24,0x7e,0x04,0x04,0x0e,0x00,0x00}, {0x00,0x00,0x7c,0x40,0x40,0x58,0x64,0x42,0x02,0x42,0x24,0x18,0x00,0x00}, {0x00,0x00,0x1c,0x22,0x42,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00}, {0x00,0x00,0x3e,0x22,0x42,0x04,0x04,0x08,0x08,0x08,0x08,0x08,0x00,0x00}, {0x00,0x00,0x18,0x24,0x42,0x24,0x18,0x24,0x42,0x42,0x24,0x18,0x00,0x00}, {0x00,0x00,0x18,0x24,0x42,0x42,0x26,0x1a,0x02,0x44,0x24,0x18,0x00,0x00}, {0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x08,0x10,0x00}, {0x00,0x00,0x02,0x04,0x08,0x10,0x20,0x40,0x20,0x10,0x08,0x04,0x02,0x00}, {0x00,0x00,0x00,0x00,0x7e,0x00,0x00,0x00,0x7e,0x00,0x00,0x00,0x00,0x00}, {0x00,0x00,0x40,0x20,0x10,0x08,0x04,0x02,0x04,0x08,0x10,0x20,0x40,0x00}, {0x00,0x00,0x18,0x24,0x42,0x42,0x04,0x08,0x10,0x10,0x00,0x18,0x18,0x00}, {0x00,0x00,0x18,0x24,0x22,0x4a,0x56,0x56,0x56,0x4a,0x20,0x22,0x1c,0x00}, {0x00,0x00,0x10,0x10,0x28,0x28,0x28,0x24,0x7c,0x44,0x44,0xee,0x00,0x00}, {0x00,0x00,0x78,0x24,0x24,0x28,0x3c,0x22,0x22,0x22,0x24,0x78,0x00,0x00}, {0x00,0x00,0x1a,0x26,0x22,0x42,0x40,0x40,0x40,0x22,0x26,0x18,0x00,0x00}, {0x00,0x00,0x78,0x24,0x24,0x22,0x22,0x22,0x22,0x24,0x24,0x78,0x00,0x00}, {0x00,0x00,0x7c,0x22,0x20,0x24,0x3c,0x24,0x20,0x20,0x22,0x7e,0x00,0x00}, {0x00,0x00,0x7c,0x22,0x20,0x24,0x3c,0x24,0x20,0x20,0x20,0x78,0x00,0x00}, {0x00,0x00,0x1a,0x26,0x22,0x42,0x40,0x4e,0x42,0x22,0x26,0x1a,0x00,0x00}, {0x00,0x00,0xee,0x44,0x44,0x44,0x7c,0x44,0x44,0x44,0x44,0xee,0x00,0x00}, {0x00,0x00,0x7c,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7c,0x00,0x00}, {0x00,0x00,0x1e,0x04,0x04,0x04,0x04,0x04,0x44,0x44,0x48,0x30,0x00,0x00}, {0x00,0x00,0x6e,0x24,0x28,0x28,0x30,0x28,0x24,0x24,0x22,0x76,0x00,0x00}, {0x00,0x00,0x70,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x22,0x7c,0x00,0x00}, {0x00,0x00,0x42,0x66,0x66,0x6a,0x5a,0x52,0x52,0x42,0x42,0x66,0x00,0x00}, {0x00,0x00,0x46,0x62,0x62,0x52,0x52,0x4a,0x4a,0x46,0x46,0x62,0x00,0x00}, {0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00}, {0x00,0x00,0x78,0x24,0x22,0x22,0x24,0x38,0x20,0x20,0x20,0x70,0x00,0x00}, {0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x72,0x4e,0x24,0x18,0x06,0x00}, {0x00,0x00,0x78,0x24,0x22,0x22,0x24,0x38,0x28,0x24,0x24,0x72,0x00,0x00}, {0x00,0x00,0x1a,0x26,0x42,0x20,0x18,0x04,0x02,0x42,0x64,0x58,0x00,0x00}, {0x00,0x00,0x7e,0x52,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x38,0x00,0x00}, {0x00,0x00,0x76,0x22,0x22,0x22,0x22,0x22,0x22,0x22,0x14,0x08,0x00,0x00}, {0x00,0x00,0x66,0x42,0x24,0x24,0x24,0x28,0x18,0x10,0x10,0x10,0x00,0x00}, {0x00,0x00,0x92,0x92,0x92,0x52,0x5a,0x6a,0x6c,0x24,0x24,0x24,0x00,0x00}, {0x00,0x00,0x62,0x44,0x24,0x28,0x10,0x18,0x28,0x24,0x44,0x4e,0x00,0x00}, {0x00,0x00,0xe6,0x42,0x24,0x28,0x18,0x10,0x10,0x10,0x10,0x38,0x00,0x00}, {0x00,0x00,0x3e,0x44,0x04,0x08,0x08,0x10,0x20,0x20,0x42,0x7c,0x00,0x00}, {0x00,0x3c,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x3c}, {0x80,0x80,0x40,0x40,0x20,0x20,0x10,0x10,0x08,0x08,0x04,0x04,0x02,0x02}, {0x00,0x3c,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x3c}, {0x00,0x10,0x28,0x44,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfe}, {0x00,0x18,0x18,0x10,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x00,0x00,0x00,0x00,0x38,0x44,0x44,0x1c,0x24,0x44,0x44,0x3a,0x00,0x00}, {0x00,0x00,0x60,0x20,0x28,0x34,0x22,0x22,0x22,0x22,0x24,0x38,0x00,0x00}, {0x00,0x00,0x00,0x00,0x1a,0x26,0x42,0x40,0x40,0x42,0x26,0x18,0x00,0x00}, {0x00,0x00,0x0c,0x04,0x14,0x2c,0x44,0x44,0x44,0x44,0x24,0x1e,0x00,0x00}, {0x00,0x00,0x00,0x00,0x18,0x24,0x42,0x7e,0x40,0x42,0x22,0x1c,0x00,0x00}, {0x00,0x00,0x0c,0x12,0x12,0x7c,0x10,0x10,0x10,0x10,0x10,0x38,0x00,0x00}, {0x00,0x00,0x00,0x00,0x1a,0x24,0x24,0x24,0x18,0x20,0x5c,0x42,0x42,0x3c}, {0x00,0x00,0xc0,0x40,0x50,0x68,0x44,0x44,0x44,0x44,0x44,0xc6,0x00,0x00}, {0x00,0x00,0x18,0x18,0x00,0x38,0x08,0x08,0x08,0x08,0x08,0x3c,0x00,0x00}, {0x00,0x00,0x0c,0x0c,0x00,0x1c,0x04,0x04,0x04,0x04,0x04,0x44,0x48,0x30}, {0x00,0x00,0xc0,0x40,0x46,0x44,0x48,0x58,0x68,0x44,0x44,0xce,0x00,0x00}, {0x00,0x00,0x38,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x3e,0x00,0x00}, {0x00,0x00,0x00,0x00,0xac,0xd2,0x92,0x92,0x92,0x92,0x92,0x92,0x00,0x00}, {0x00,0x00,0x00,0x00,0xd8,0x64,0x44,0x44,0x44,0x44,0x44,0xc6,0x00,0x00}, {0x00,0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00}, {0x00,0x00,0x00,0x00,0x58,0x24,0x22,0x22,0x22,0x22,0x24,0x38,0x20,0x70}, {0x00,0x00,0x00,0x00,0x1a,0x24,0x44,0x44,0x44,0x44,0x24,0x1c,0x04,0x0e}, {0x00,0x00,0x00,0x00,0x5c,0x22,0x22,0x20,0x20,0x20,0x20,0x70,0x00,0x00}, {0x00,0x00,0x00,0x00,0x3c,0x44,0x40,0x30,0x0c,0x42,0x62,0x5c,0x00,0x00}, {0x00,0x00,0x10,0x10,0x10,0x7c,0x10,0x10,0x10,0x10,0x12,0x0c,0x00,0x00}, {0x00,0x00,0x00,0x00,0xcc,0x44,0x44,0x44,0x44,0x44,0x4c,0x32,0x00,0x00}, {0x00,0x00,0x00,0x00,0x66,0x42,0x44,0x24,0x28,0x18,0x10,0x10,0x00,0x00}, {0x00,0x00,0x00,0x00,0x92,0x92,0x92,0x92,0x5a,0x6c,0x24,0x24,0x00,0x00}, {0x00,0x00,0x00,0x00,0x66,0x24,0x28,0x18,0x18,0x14,0x24,0x66,0x00,0x00}, {0x00,0x00,0x00,0x00,0x66,0x22,0x22,0x14,0x14,0x08,0x08,0x48,0x50,0x20}, {0x00,0x00,0x00,0x00,0x3e,0x44,0x08,0x08,0x10,0x10,0x22,0x7e,0x00,0x00}, {0x00,0x06,0x08,0x10,0x10,0x10,0x10,0x20,0x10,0x10,0x10,0x10,0x08,0x06}, {0x00,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10}, {0x00,0x60,0x10,0x08,0x08,0x08,0x08,0x04,0x08,0x08,0x08,0x08,0x10,0x60}, {0x00,0x32,0x4c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe}}; unsigned char pixels[HEIGHT][WIDTH][3]; /* つづく */
void draw_char(int x, int y, int c) { int i, j, k; if (!(0 <= x && x < WIDTH / 8 && 0 <= y && y < HEIGHT / 16)) { return; } if (c < ' ' || '~' < c) { c = '\x7f'; } c -= ' '; x *= 8; y *= 16; for (j = 0; j < 14; ++j) { for (i = 0; i < 7; ++i) { k = 1 & (ascii[c][j] >> (7 - i)); pixels_(x + i + 1, y + j + 1)[red] = 0xff * k; pixels_(x + i + 1, y + j + 1)[green] = 0xff * k; pixels_(x + i + 1, y + j + 1)[blue] = 0xff * k; } } } void draw_string(int x, int y, const unsigned char *p, int len) { if (!(0 <= x && x < WIDTH / 8)) { return; } if (x + len > WIDTH / 8) { len = WIDTH / 8 - x; } while (len-- > 0) { draw_char(x++, y, *p++); } }
void cursor(int x, int y) { int i; if (0 <= x && x < WIDTH / 8) { x *= 8; for (i = 0; i < BOTTOM * 16; ++i) { pixels_(x, i)[red] = 100; pixels_(x, i)[green] = 100; pixels_(x, i)[blue] = 100; } } if (0 <= y && y < BOTTOM) { y = y * 16 + 15; for (i = 0; i < WIDTH; ++i) { pixels_(i, y)[red] = 100; pixels_(i, y)[green] = 100; pixels_(i, y)[blue] = 100; } } }
カーソルを描きます。
void clear(void) { int i, j; for (j = 0; j < BOTTOM * 16; ++j) { for (i = 0; i < WIDTH; ++i) { pixels_(i, j)[red] = 0; pixels_(i, j)[green] = 0; pixels_(i, j)[blue] = 0; } } }
OpenGLとGLUTを使っていますが、高度なことは何もしていません。
glRasterPosでラスタ位置を指定するとき、
誤差により位置が画面の外にずれると、何も表示されなくなるらしいので注意。
#include <GL/glut.h> #include "pixels.h" #include "buffer.h" static void reshape(int w, int h) { (void)w; glViewport(0, h - HEIGHT, WIDTH, HEIGHT); } static void display(void) { int x, y, i; unsigned char **p; glClear(GL_COLOR_BUFFER_BIT); clear(); x = b.left - b.buffer[b.line]; y = b.line - b.top; cursor(x, y); for (i = 0, p = &b.buffer[b.top]; i < y; ++i, ++p) { draw_string(0, i, p[0], p[1] - p[0]); } draw_string(0, y, *p, x); draw_string(x, y, b.right, b.buffer[b.next] - b.right); for (i = y + 1, p = &b.buffer[b.next]; i < BOTTOM && p < &b.buffer[b.length]; ++i, ++p) { draw_string(0, i, p[0], p[1] - p[0]); } glRasterPos2i(-1, -1); glDrawPixels(WIDTH, HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, pixels); glFlush(); } static void keyboard(unsigned char key, int x, int y) { (void)x; (void)y; switch (key) { case 2: /* C-b */ b_prevchar(); break; case 6: /* C-f */ b_nextchar(); break; case '\b': b_remove(); break; case '\n': case '\r': b_newline(); break; case 14: /* C-n */ b_nextline(); break; case 16: /* C-p */ b_prevline(); break; default: b_insert(key); break; } glutPostRedisplay(); } int main(int argc, char *argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); glutInitWindowSize(WIDTH, HEIGHT); glutInitWindowPosition(0, 0); glutCreateWindow(argv[0]); glClearColor(0.0, 0.0, 0.5, 1.0); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); b_init(); glutReshapeFunc(reshape); glutDisplayFunc(display); glutKeyboardFunc(keyboard); glutMainLoop(); return 0; }