« ノベルゲーム風に一文字ずつ表示する【UTF-8/DXライブラリ版】 | トップページ | 壁に沿って転がる玉 »

2011年11月21日 (月)

ノベルゲーム風に一文字ずつ表示する【UTF-8/SDL版】

SDL版作った。

SDL_ttfの関数にUTF-8文字列を渡すために、DXライブラリ版と違ってバッファにはUTF-8のまま記録する。SDL_ttf以外もSDLは基本UTF-8だし。

禁則文字の判定にはワイド文字を使う。Visual C++とMinGWは標準ライブラリでUTF-8に対応していないので、Win32 APIを使った関数に差し替える。MinGWは未確認。

こういう使い方をするとSDL_ttfは重いな。

#include "SDL/SDL_ttf.h"
#include "VGLib.hpp"
#include <stdlib.h>
#include <locale.h>
#include <limits.h>

#if defined(_MSC_VER) || defined(__MINGW32__)
#include <windows.h>
#undef MB_CUR_MAX
#define MB_CUR_MAX MB_LEN_MAX
#define mbtowc mbtowc_win32
static int mbtowc_win32(wchar_t *wchar, const char *mbchar, size_t count)
{
	wchar_t wc;
	if (mbchar == NULL) return 0;
	if (*mbchar == '\0') return 0;
	if (wchar == NULL) {
		wchar = &wc;
	}
	for (int n=1; n<=count; ++n) {
		if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mbchar, n, wchar, 1) > 0) {
			return n;
		}
	}
	return -1;
}
#endif

/*
 *  行頭禁則文字かどうか
 */
static bool isProhibitLineBreakBeforeW(wchar_t *wc);
/* ※関数本体省略:【UTF-8/DXライブラリ版】参照 */
/*
 *  行末禁則文字かどうか
 */
static bool isProhibitLineBreakAfterW(wchar_t *wc);
/* ※関数本体省略:【UTF-8/DXライブラリ版】参照 */

struct WrapText
{
	static const int BUF_SIZE = 4*1024;
	static const int LINE_NUM = 64;
	char buf[BUF_SIZE];
	int line_top[LINE_NUM];
	int line_len[LINE_NUM];
	int line_num;
	int hold_len; // 送り出し保留分文字列長
	int hold_cnt; // 送り出し保留分確定猶予文字数カウンタ
	int curr_idx; // 逐次描画対象行のインデックス
	int curr_len; // 逐次描画対象行の描画済み文字列長さ

	void clear();
	void update();
	void render(TTF_Font *font, int width, int lineheight);
};

static const char *pStream;

void WrapText::clear(void)
{
	line_num = 0;
	line_top[0] = 0;
	line_len[0] = 0;
	hold_len = 0;
	hold_cnt = 0;
	curr_idx = 0;
	curr_len = 0;
}

void WrapText::update()
{
	if (line_len[curr_idx] > curr_len) {
		/* 逐次描画対象行に文字列が残っているとき */
		// 一文字ずつ描画対象を伸ばしていく
		int mb_len = mbtowc(NULL, &buf[line_top[curr_idx] + curr_len], MB_CUR_MAX);
		curr_len += mb_len;
	}
	else {
		/* 逐次描画対象行に文字列が残っていないとき */
		if (curr_idx < line_num - 1) {
			// 次の行に逐次描画対象を移す
			++curr_idx;
			curr_len = 0;
		}
	}
}

void WrapText::render(TTF_Font *font, int width, int lineheight)
{
	char mb[MB_LEN_MAX];
	int mb_len;
	wchar_t wc;

	SDL_Color ColorWhite = { 255, 255, 255 };

	if (font == NULL) return;

	while ((mb[0] = *pStream) != '\0')
	{
		pStream++;

		if (mb[0] == '\r') {
			/* 改行コードで強制改行 */
			int n = line_num;
			line_len[n] += hold_len;
			hold_len = 0;
			line_num++;
			line_top[line_num] = line_top[n] + line_len[n];
			line_len[line_num] = 0;
			continue;
		}

		mb_len = 1;
		while (mbtowc(&wc, mb, mb_len) < 1) {
			if (*pStream == '\0') {
				mb_len = 0;
				break;
			}
			mb[mb_len] = *(pStream++);
			mb_len++;
			mbtowc(NULL, NULL, 0);
		}
		if (mb_len == 0) break;

		int n = line_num;
		for (int i=0; i<mb_len; ++i) {
			buf[line_top[n]+line_len[n]+hold_len+i] = mb[i];
		}
		buf[line_top[n]+line_len[n]+hold_len+mb_len] = '\0';
		if (isProhibitLineBreakBeforeW(&wc)) {
			/* 行頭禁則文字のとき */
			// 確定猶予カウントをリセット
			hold_cnt = 0;
		}
		else if (isProhibitLineBreakAfterW(&wc)) {
			/* 行末禁則文字のとき */
			if (hold_cnt == 0) {
				// 猶予なしなら保留分を確定
				line_len[n] += hold_len;
				hold_len = 0;
			}
			// 続く1文字を確定猶予
			hold_cnt = 1;
		}
		else {
			/* 禁則文字以外のとき */
			if (hold_cnt > 0) {
				// 猶予カウンタ分は保留
				hold_cnt--;
			}
			else {
				// 猶予なしなら保留分を確定
				line_len[n] += hold_len;
				hold_len = 0;
			}
		}
		int w;
		TTF_SizeUTF8(font, &buf[line_top[n]], &w, NULL);
		if (w > width) {
			if (line_len[n] == 0) {
				// 1行すべて保留分のとき確定する
				line_len[n] += hold_len;
				hold_len = 0;
			}
			line_num++;
			line_top[line_num] = line_top[n] + line_len[n];
			line_len[line_num] = 0;
		}
		hold_len += mb_len;
	}
	if (hold_len > 0) {
		// 保留分が残っていたら確定する
		line_len[line_num] += hold_len;
		hold_len = 0;
		hold_cnt = 0;
	}
	if (line_len[line_num] > 0) {
		// 最終行の〆
		int n = line_num;
		line_num++;
		line_top[line_num] = line_top[n] + line_len[n];
		line_len[line_num] = 0;
	}

	/*
	 *  描画
	 */
	for (int i=0; i<=curr_idx; ++i) { // 逐次描画対象行まで
		// 逐次描画対象より前の行なら全体を描画
		int len = i < curr_idx ? line_len[i] : curr_len;
		char c = buf[line_top[i] + len];
		buf[line_top[i] + len] = '\0';
		SDL_Surface *image = TTF_RenderUTF8_Blended(font, &buf[line_top[i]], ColorWhite);
		if (image) {
			SDL_Rect rcSrc = { 0, 0, image->w, image->h };
			SDL_Rect rcDst = { 0, lineheight*i };
			SDL_BlitSurface(image, &rcSrc, SDL_GetVideoSurface(), &rcDst);
			SDL_FreeSurface(image);
		}
		buf[line_top[i] + len] = c;
	}
}

struct Main : public VGCanvas
{
	TTF_Font *font;
	WrapText wt;
	int width;
	bool skip;

	Main() : VGCanvas() {}
	void init();
	void destroy();
	void frameUpdate(int skiiped);
	void frameRender(void);
} canvas;

VGCanvas *VGLibInit()
{
	setlocale(LC_CTYPE, "");
	if (SDL_SetVideoMode(640, 480, 0, SDL_HWSURFACE | SDL_DOUBLEBUF) == NULL) {
		fprintf(stderr, "ERROR->SDL_SetVideoMode: %s\n", SDL_GetError());
		return NULL;
	}
	return &canvas;
}

#define FONT_SIZE 20
#define MIN_WIDTH (FONT_SIZE * 10)

static const char *SampleText =
	"\xE5\x8F\xA3\xE8\xAA\x9E\xE8\xA1\xA8\xE7\x8F\xBE\xE3\x81\xAF\xE7\xA6\x81"
	"\xE5\x89\x87\xE5\x87\xA6\xE7\x90\x86\xE3\x81\x8C\xE3\x82\x81\xE3\x82\x93"
	"\xE3\x81\xA9\xE3\x81\x86\xE3\x81\xAA\xE3\x81\xAE\xE3\x81\xA0\xE3\x80\x82\r"
	"\xE3\x80\x8C\xE3\x82\xAE\xE3\x83\xA3\xE3\x83\xBC\xE3\x83\xBC\xE3\x83\xBC"
	"\xE3\x83\xBC\xE3\x83\xBC\xE3\x83\xBC\xE3\x83\xBC\xE3\x83\xBC\xE3\x83\x83"
	"\xEF\xBC\x81\r"
	"\xE3\x80\x80\xE3\x82\x84\xE3\x81\xA3\xE3\x81\xA1\xE3\x82\x83\xE3\x81\xA3"
	"\xE3\x81\x9F\xE3\x80\x82\xE3\x80\x8D\r"
	"\xE3\x81\xA8\xE3\x81\x84\xE3\x81\x86\xE5\x8F\xB0\xE8\xA9\x9E\xE3\x81\xAE"
	"\xE5\xA0\xB4\xE5\x90\x88\xE3\x80\x81\xE5\xB9\x85\xE3\x81\x8C\xE7\x8B\xAD"
	"\xE3\x81\x84\xE3\x81\xA8\xE7\xA0\xB4\xE7\xB6\xBB\xE3\x81\x99\xE3\x82\x8B"
	"\xE3\x81\x8C\xE4\xBB\x95\xE6\xA7\x98\xE3\x81\x8C\xE3\x81\xAA\xE3\x81\x84"
	"\xE3\x80\x82\r\r"
	"\xE3\x81\x9D\xE3\x81\x93\xE3\x81\xAF\xE9\x81\x8B\xE7\x94\xA8\xE3\x81\xA7"
	"\xE3\x82\xAB\xE3\x83\x90\xE3\x83\xBC\xE3\x81\xA0\xE3\x80\x82\r";

void Main::init()
{
	font = NULL;
	if (TTF_Init() == 0) {
		font = TTF_OpenFont("ipag.ttf", FONT_SIZE);
	}

	width = MIN_WIDTH;

	wt.clear();
	pStream = SampleText;
}

void Main::destroy()
{
	if (font) {
		TTF_CloseFont(font);
		font = NULL;
	}
	TTF_Quit();
}

void Main::frameUpdate(int)
{
	SDL_Surface *screen = SDL_GetVideoSurface();
	Uint8 *keystate = SDL_GetKeyState(NULL);

	int width_old = width;
	if (keystate[SDLK_LEFT]) {
		width -= 2;
		if (width < MIN_WIDTH) {
			width = MIN_WIDTH;
		}
	}
	if (keystate[SDLK_RIGHT]) {
		width += 2;
		if (width > screen->w) {
			width = screen->w;
		}
	}
	skip = false;
	if (width != width_old) {
		wt.clear();
		pStream = SampleText;
		skip = true;
	}
	wt.update();
}

void Main::frameRender(void)
{
	SDL_Surface *screen = SDL_GetVideoSurface();
	SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0));
	SDL_Rect rcLine = { width, 0, 1, screen->h };
	SDL_FillRect(screen, &rcLine, SDL_MapRGB(screen->format, 255, 0, 0));
	if (!skip) wt.render(font, width, FONT_SIZE);
}

|

« ノベルゲーム風に一文字ずつ表示する【UTF-8/DXライブラリ版】 | トップページ | 壁に沿って転がる玉 »

C/C++」カテゴリの記事

SDL」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)




« ノベルゲーム風に一文字ずつ表示する【UTF-8/DXライブラリ版】 | トップページ | 壁に沿って転がる玉 »