NScripterでプロポーショナルフォント

タイトル通りのことを、なんとかできないかとアレコレやってみた。
一応、まあ、不恰好ながら、できた、みたいな? 本当はもっとプログラム的に低レイヤーにアクセスして、かっこよくやってしまうのが一番なんだろうけど、富豪的にやってみた。

サンプル

http://www.eiji8pou.net/download/fontsize.zip
上記をダウンロードして展開、fontsize.dllができるはずなので、nscr.exeと同じディレクトリに置く。
そして、以下のスクリプトを00.txtに。

*define

deletemenu

numalias cursor_x,100
numalias cursor_y,101
numalias font_width,102
numalias font_offset,103
numalias sp_num,104
numalias ret,105

game
*start

caption "NScripterで無理矢理プロポーショナルフォント"

mov %cursor_x,0
mov %cursor_y,26
mov %sp_num,1

mov $0,"再現可能な乱数発生器R e v e n g e "
lsp 0,":s/26,26,0;#FFFFFF"+$0,0,0
len %0,$0
for %1=0 to %0 step 2
	mid $1,$0,%1,2 ; 一文字切り取る
	lsph %sp_num,":s/26,26,0;#FFFFFF"+$1,0,0 ; 文字を表示。
	itoa $sp_num,%sp_num
	exec_dll "fontsize.dll/"+$sp_num ; 表示した文字の幅とオフセットを取得する。
	getret %ret
	mov %font_width,%ret / 100 ; 幅を取得
	mov %font_offset,%ret mod 100 ; オフセットを取得
	amsp %sp_num,%cursor_x-%font_offset,%cursor_y ; 表示位置をずらす。
	add %cursor_x,%font_width ; カーソル位置を次の文字の場所に動かす。
	vsp %sp_num,1 ; 文字を表示。
	inc %sp_num ; スプライト番号を変更
next
print 1
click
end

実行すると、下記のようになるはず。

上段が普通通りに表示した場合。下段が無理矢理プロポーショナルにした場合。

解説

  • 文字を一つだけ含む文字スプライトを作成する。
  • そのスプライト番号を、fontsize.dllに渡す。(exec_dll "fontsize.dll/スプライト番号")
  • getret %0 で戻って来た数値を取得する。
  • この%0を100で割ると、そのスプライトの透明でない部分の幅(ドット数)が取得できる。
  • この%0を100で割った余りは、そのスプライトの左側透明な部分の幅(ドット数)になる。
  • 先の二つの数値を使って、スプライトをやや左側に移動させたり、次の文字スプライトの位置を調整したりすれば、プロポーショナルフォントになる。

詳しくは、00.txtを見て!

作り方

DLL制作-コンパイル環境編 - Atelier de Muguet 制作記録を参考に。
ソースは以下。

#include <windows.h>

struct DIBDATA {
	int width;
	int height;
	int color;//色数。0はフルカラー、256は256色
	unsigned char *bits;//ビットマップへのポインタ
};


typedef void (*RenderSpriteFuncPtr) (int sp,int cell,int x,int y,int a);
//exec_dllからはRenderSpriteは使えないけど、とりあえず取得の仕方だけここで解説します。

typedef void (*GetSpriteDataFuncPtr) (int sp,int *width,int *height,int *cellmax);
typedef void (*NScrGetWindowSizeFuncPtr) (int *width,int *height);
typedef void (*AllocDIBFuncPtr) (DIBDATA *dib,char *filename);
typedef void (*FreeDIBFuncPtr) (DIBDATA *dib);
typedef BOOL (*NScrDoEventsFuncPtr) (void);

typedef HWND (*NScrGetWindowFuncPtr) (void);
typedef void (*DrawClearFuncPtr) (void);
typedef void (*DrawFillFuncPtr) (int r,int g,int b);
typedef void (*DrawBGFuncPtr)  (void);
typedef void (*DrawBG2FuncPtr) (int x,int y,int xscale,int yscale,int rot);
typedef void (*DrawSPFuncPtr)  (int spnum,int cellnum,int alpha,int x,int y);
typedef void (*DrawSP2FuncPtr) (int spnum,int cellnum,int alpha,int x,int y,int xscale,int yscale,int rot);
typedef BOOL (*NSAGetFileFuncPtr) (char *filename,char *nsaname,unsigned int *ptr,unsigned int *size);
typedef void (*GetSpriteDIBFuncPtr) (int sp, DIBDATA *dib);



RenderSpriteFuncPtr p_RenderSprite;
GetSpriteDataFuncPtr p_GetSpriteData;
NScrGetWindowSizeFuncPtr p_NScrGetWindowSize;
AllocDIBFuncPtr p_AllocDIB;
FreeDIBFuncPtr p_FreeDIB;
NScrDoEventsFuncPtr p_NScrDoEvents;

NScrGetWindowFuncPtr p_NScrGetWindow;
DrawClearFuncPtr p_DrawClear;
DrawFillFuncPtr p_DrawFill;
DrawBGFuncPtr p_DrawBG;
DrawBG2FuncPtr p_DrawBG2;
DrawSPFuncPtr p_DrawSP;
DrawSP2FuncPtr p_DrawSP2;
NSAGetFileFuncPtr p_NSAGetFile;
GetSpriteDIBFuncPtr p_GetSpriteDIB;

inline void RenderSprite (int sp,int cell,int x,int y,int a) {
	(*p_RenderSprite)(sp,cell,x,y,a);
}

inline void GetSpriteData (int sp,int *width,int *height,int *cellmax) {
	(*p_GetSpriteData)(sp,width,height,cellmax);
}

inline void NScrGetWindowSize (int *width,int *height) {
	(*p_NScrGetWindowSize)(width,height);
}

inline void AllocDIB (DIBDATA *dib,char *filename) {
	(*p_AllocDIB)(dib,filename);
}

inline void FreeDIB (DIBDATA *dib) {
	(*p_FreeDIB)(dib);
}

inline BOOL NScrDoEvents(void) {
	return (*p_NScrDoEvents)();
}



inline HWND NScrGetWindow (void) {
	return (*p_NScrGetWindow)();
}

inline void DrawClear (void) {
	(*p_DrawClear)();
}

inline void DrawFill (int r,int g,int b) {
	(*p_DrawFill)(r,g,b);
}

inline void DrawBG  (void) {
	(*p_DrawBG)();
}

inline void DrawBG2 (int x,int y,int xscale,int yscale,int rot) {
	(*p_DrawBG2)(x,y,xscale,yscale,rot);
}

inline void DrawSP  (int spnum,int cellnum,int alpha,int x,int y) {
	(*p_DrawSP)(spnum,cellnum,alpha,x,y);
}

inline void DrawSP2 (int spnum,int cellnum,int alpha,int x,int y,int xscale,int yscale,int rot) {
	(*p_DrawSP2)(spnum,cellnum,alpha,x,y,xscale,yscale,rot);
}

inline BOOL NSAGetFile (char *filename,char *nsaname,unsigned int *ptr,unsigned int *size) {
	return (*p_NSAGetFile)(filename,nsaname,ptr,size);
}

inline void GetSpriteDIB ( int sp, DIBDATA *dib ) {
	return (*p_GetSpriteDIB)(sp, dib);
}

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
	return TRUE;
}



extern "C" {
__declspec(dllexport) BOOL NScrPlugInMain (HINSTANCE hinstance,HWND hwnd,char *param,int *ret_int,char *ret_str);
//プラグイン本体。hinstanceはNScripterのインスタンス。hwndはNScripterのウィンドウハンドル。
//paramはexec_dll命令に渡したパラメータ文字列。
//ゲームその他の処理を作るときは、このウィンドウの子ウィンドウとして
//ぴったりクライアント領域の大きさの独自のウィンドウを上から貼り付け、処理してください。
//時々NScrDoEventsを実行してNScripterのメッセージ処理をしてください。
};


//---------------------------------------------------
//プラグイン個別の関数
//---------------------------------------------------

LRESULT CALLBACK WndProc (HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam);

char text[256];
BOOL program_end=FALSE;

void split_param(char *param, int *point, int *res, int *param_end) {
	*res = 0;
	while ( true ) {
		if ( param[*point] == '\0' ) { *param_end = 1; break; }
		if ( param[*point] == ',' ) { *point = *point + 1; break; }
		*res = *res * 10;
		*res = *res + param[*point] - 48;
		*point = *point + 1;
	}
	return;
}
BOOL NScrPlugInMain (HINSTANCE hinstance,HWND hwnd,char *param,int *ret_int,char *ret_str) {
	char fn[256];
	GetModuleFileName(NULL,fn,sizeof(fn));
	HMODULE hm=LoadLibrary(fn);
	p_RenderSprite=(RenderSpriteFuncPtr)GetProcAddress(hm,"RenderSprite");
	p_GetSpriteData=(GetSpriteDataFuncPtr)GetProcAddress(hm,"GetSpriteData");
	p_NScrGetWindowSize=(NScrGetWindowSizeFuncPtr)GetProcAddress(hm,"NScrGetWindowSize");
	p_AllocDIB=(AllocDIBFuncPtr)GetProcAddress(hm,"AllocDIB");
	p_FreeDIB=(FreeDIBFuncPtr)GetProcAddress(hm,"FreeDIB");
	p_NScrDoEvents=(NScrDoEventsFuncPtr)GetProcAddress(hm,"NScrDoEvents");

	p_NScrGetWindow=(NScrGetWindowFuncPtr)GetProcAddress(hm,"NScrGetWindow");
	p_DrawClear=(DrawClearFuncPtr)GetProcAddress(hm,"DrawClear");
	p_DrawFill=(DrawFillFuncPtr)GetProcAddress(hm,"DrawFill");
	p_DrawBG=(DrawBGFuncPtr)GetProcAddress(hm,"DrawBG");
	p_DrawBG2=(DrawBG2FuncPtr)GetProcAddress(hm,"DrawBG2");
	p_DrawSP=(DrawSPFuncPtr)GetProcAddress(hm,"DrawSP");
	p_DrawSP2=(DrawSP2FuncPtr)GetProcAddress(hm,"DrawSP2");
	p_NSAGetFile=(NSAGetFileFuncPtr)GetProcAddress(hm,"NSAGetFile");
	p_GetSpriteDIB=(GetSpriteDIBFuncPtr)GetProcAddress(hm,"GetSpriteDIB");
	FreeLibrary(hm);

	*ret_int = -1;
	// 何もしませんよ、と。
	int spnum = atoi(param);
	if ( spnum < 0 ) {
	} else if ( spnum > 999 ) {
	} else {
		// スプライトを開きますよ、と。
		int width, height, cellmax;
		GetSpriteData(spnum, &width, &height, &cellmax);
		DIBDATA dib;
		GetSpriteDIB(spnum, &dib);
		int offset, awidth; // 左にどれだけずれるか。実際の幅はどれくらいか。
		int line = dib.width * 3; // 一列が何バイトか。
		while ( 0 != line % 4 ) { line++; } // 補正。
		int red; // 赤を取得してみる。
		offset = 0;
		awidth = 0;
		for ( int w = 0; w<width; w++ ) {
			red = 0;
			for ( int h = 0; h<height; h++ ) {
				red = red + 255 - dib.bits[h*line+w*3+width*3];
			}
			if ( red > 0 ) {
					awidth++;
			} else {
				if ( awidth == 0 ) {
					offset++;
				}
			}
		}
		*ret_int = awidth * 100 + offset;
	}

//	strcpy (ret_str,"あいうえお"); 返り値はこうする。
//	*ret_int=999; 返り値はこうする。
//	strcpy (text,param);
	return FALSE;

}

LRESULT CALLBACK WndProc (HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam) {
	HDC hdc;
	PAINTSTRUCT ps;

	switch (message) {
		case WM_PAINT:
			hdc=BeginPaint (hwnd,&ps);
			TextOut (hdc,20,20,text,strlen(text));
			EndPaint(hwnd,&ps);
			return 0;
		case WM_LBUTTONUP:
			PostMessage(hwnd,WM_CLOSE,0,0);
			return 0;
		case WM_CLOSE:
			DestroyWindow(hwnd);
			return 1;

		case WM_DESTROY:
			PostQuitMessage (0);
			program_end=TRUE;
			return 0;

		default:
			return DefWindowProc(hwnd,message,wParam,lParam);
	}
}

おまじない部分が大半。