Unicode文字列からビットマップ作成例題プログラム for Windows, MacOSX, Linux (あと、UTF8 <-> WChar変換)
UTF8っていうキーワードを最初に目にしたのは、MacOSXでCocoaプログラミングを始めたときだった。char *のことをUTF8 Stringと呼んでるようだったので、 ふーん、昔はASCIIキャラクターって言ってたのが、いつのまにかWin32 APIではANSIになって、CocoaではUTF8と呼ぶようになったのか。と、思っていた。けど、当然ASCIIコードと同じと思ってたから、漢字とか2バイトコードをどうしたもんかなと思っていた。

けど、試しに、NSStringに漢字のUnicodeをセットして、UTF8 Stringに変換したらどうなるんじゃ?と、思ったら、UTF8では漢字が変なコードに化けていて、逆変換したら元通りのUnicodeが出てきた。 下のコードは、Terminalから g++ conv.m -framework Cocoa でコンパイル可能。

List: conv.m

#import <Cocoa/Cocoa.h>

int main(void)
{
    int i;
    NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];

    unsigned char utf16[]={0x5c,0x71,0x5d,0xdd,0x03,0xc0,0x00,'A',0x03,0x94,0,0};
    NSString *rawString=[[NSString alloc] 
          initWithBytes:utf16 
          length:10 
          encoding:NSUTF16BigEndianStringEncoding];
    printf("Original Unicode:\n");
    for(i=0; i<[rawString length]; i++)
    {
        printf("%04x ",[rawString characterAtIndex:i]);
    }
    printf("\n");



    const char *utf8=[rawString UTF8String];

    // You will see two kanji characters, greek letter pi, alphabet A, and greek 
    // letter Delta, if the terminal supports UTF8.
    printf("UTF8: %s\n",utf8);
    for(i=0; 0!=utf8[i]; i++)
    {
        int c=utf8[i];
        c&=0xff;
        printf("%02x ",c);
    }
    printf("\n");



    printf("Recovered Unicode:\n");
    NSString *verifyString=[[NSString alloc] initWithUTF8String:utf8];
    for(i=0; i<[verifyString length]; i++)
    {
        printf("%04x ",[verifyString characterAtIndex:i]);
    }
    printf("\n");

    [rawString release];
    [verifyString release];

    [pool release];
    return 0;
}

この実験をやって、おお!これはすごい。これだったら、漢字ファイル名もロス無く処理できるじゃん。と、思ったけど、それでもまだこれは多分Mac OS独自の拡張なんだろうな、と、思っていた。

話は代わって、YSFLIGHTの日本語化は、全日本語メッセージをビットマップで持たせるという無理矢理な方法で実現していた。これ、今まで、文字列ひとつずつペイントブラシで書いては保存という非常に非能率的な方法で ビットマップを作っていた。それはあまりにも非能率的で、Scenery Editorの日本語化のために、またメニューとかダイアログの日本語メッセージをひとつずつペイントブラシで作るのは、あまりに非人間的だと思って、テキストの定義ファイルを書いておいて、一気に日本語メッセージのビットマップを作るプログラムを書こうと思った。

これは、Windows上でビットマップを作ってしまえば、Mac OSXでもLinuxでも同じビットマップを使えばいいんだから、Win32 API直書きでもいいや、と、思って、書き始めた。こんなのにあまり時間使いたくなかったんで、結構いい加減な書き方で突き進めたんだけど、書き始めると欲が出てくるもんで、強力なユーザコミュニティがあるんだから、誰でも使えるように書いたら、他言語用ビットマップとか、誰か作って流通させてくれるんじゃないか?と、思って、少し書き方を丁寧にしてやりなおした。

やりなおしてる途中で、そういや、ウィンドウを作らずに、オフスクリーンビットマップだけ作って、そこにUnicodeの文字列を書くクラスを作れれば、後々いろいろ使い道があるんだけど、昔はたしかなんかの理由でできなかったんだったよな。と、思って、修士課程のころ作ったBlue Impulse 3D Graphics SDKのコードを見直す。

そうそう、オフスクリーンビットマップ(DIB)を作るには、デバイスコンテキストが必要で、デバイスコンテキストを作るにはCreateCompatibleDC関数を使う必要があるんだけど、このCreateCompatibleDC関数の引数(元になるデバイスコンテキスト)にNULLを与えるとクラッシュして、クラッシュさせないためには、元になるデバイスコンテキストが必要で、元になるデバイスコンテキストを作るには、最低でも、目に見えないウィンドウを作らなきゃならないんだった。もっとも、ウィンドウ作っても表示しない(SW_HIDE状態)にしとけばいいんだから、なんとかなるかな?けど、Win32 APIも最近変化してるし、案外CreateCompatibleDCの引数にNULLを与えれるようになってたりして。と、思ってMSDNを見に行く。

「おお、いい勘してるじゃん!」

と、思わずモニターに向かって叫んで周りから変な目で見られる瞬間で、最近って、CreateCompatibleDCってNULLを渡しても落ちないのね。それとも、落ちたのって、Windowsが3.1だったころの極めて短い間だったんだろうか。あと、結構よくあるのが、別の原因で落ちてたんだけど、僕がCreateCompatibleDCにNULLを渡したのが悪かったと思い込んでたって可能性もあるけど。あのころは、Game SDKが通称WinGと呼ばれてた頃で、時代は3Dグラフィックスに向かいつつあったから、当然WinGでも3Dグラフィックスのサポートがあってしかるべきだろうと思ったら肩透かしを食らったんだった。それでも、ゲームのサポートがコンシューマ市場制圧の鍵になるということを見通していたマイクロソフトは偉かった。当時、UnixやMacだった人たちは、ゲームなんて卑しい技術だと見下してたもんね。結局、ゲームに使う技術が、3次元グラフィックス、ビデオ記録・再生、音楽とかと直結してたから、その後のWindows 95・98の圧勝につながったんだよね。しかし、あの状況からAppleはよく盛り返したもんだ、というか、こうなることを見通してやってたんだったらすごいことだけど、というのは話の脱線だった。

というわけで、これだったらオフスクリーンビットマップ作って漢字書いてビットマップだけいただいてくるってのが簡単にできるじゃん!!ということがわかって早速下のようなプログラムを書いてみた。コマンドプロンプトから、

cl font.cpp kernel32.lib gdi32.lib

でコンパイルできるよ。インストールされてるフォントの一覧を表示して、次にオフスクリーンビットマップの内容をコンソールに1と0でできたビットマップを表示する。

List: font.cpp

// cl font.cpp kernel32.lib user32.lib gdi32.lib
//   or
// cl font.cpp kernel32.lib user32.lib gdi32.lib /DUNICODE <- This way this code can be checked against default-unicode environment.

#include <windows.h>
#include <stdio.h>
#include <string.h>

static int CALLBACK EnumFontFamExProc(
    const LOGFONTA *fontInfo,
    const TEXTMETRICA *textMetric,
    DWORD fontType,
    LPARAM lParam)
{
    printf("%s\n",fontInfo->lfFaceName);
    return 1;
}

void EnumerateFont(HDC hDC)
{
    LOGFONTA fontInfo;
    fontInfo.lfCharSet=DEFAULT_CHARSET;
    fontInfo.lfFaceName[0]=0;
    fontInfo.lfPitchAndFamily=0;

    EnumFontFamiliesExA(hDC,&fontInfo,EnumFontFamExProc,(LPARAM)0,0);
}

int main(void)
{
    HDC hDC=CreateCompatibleDC(NULL);
    EnumerateFont(hDC);


    LOGFONTW logFont;
    ZeroMemory(&logFont,sizeof(logFont));

    logFont.lfHeight=-12; // 12 pixels (Positive 12 for 12 points)
    logFont.lfWeight=FW_DONTCARE;
    logFont.lfCharSet=DEFAULT_CHARSET;
    logFont.lfOutPrecision=OUT_TT_PRECIS;
    logFont.lfClipPrecision=CLIP_DEFAULT_PRECIS;
    logFont.lfQuality=DEFAULT_QUALITY;
    logFont.lfPitchAndFamily=DEFAULT_PITCH;
    wcscpy(logFont.lfFaceName,L"Arial"); // Up to LF_FACESIZE chars.
    HFONT hFont=CreateFontIndirectW(&logFont);

    HGDIOBJ hPrevFont=NULL;
    if(NULL!=hFont)
    {
        printf("Font Created.\n");
        hPrevFont=SelectObject(hDC,(HGDIOBJ)hFont);
    }


    RECT rect;
    rect.left=0;
    rect.top=0;
    rect.right=100;
    rect.bottom=100;


    SetTextAlign(hDC,TA_LEFT|TA_TOP|TA_NOUPDATECP);  // Prerequisite for DrawTextEx


    wchar_t str[]={0x5c71,0x5ddd,0x03c0,'A',0x0394,0};
    DrawTextExW(hDC,str,4,&rect,DT_TOP|DT_LEFT|DT_CALCRECT,NULL);
    const int wid=rect.right;
    const int hei=rect.bottom;
    const int bitPerPixel=24;

    printf("Width=%d Height=%d\n",wid,hei);

    BITMAPINFOHEADER hdr=
    {
        sizeof(BITMAPINFOHEADER),
        0,0,1,8,BI_RGB,0,0,0,0,0
    };
    hdr.biWidth=wid;
    hdr.biHeight=-hei;
    hdr.biBitCount=bitPerPixel;
    hdr.biCompression=BI_RGB;



    void *bit;
    HBITMAP hBmp=CreateDIBSection(hDC,(BITMAPINFO *)&hdr,DIB_RGB_COLORS,&bit,NULL,0);
    HGDIOBJ hPrevBmp;
    hPrevBmp=SelectObject(hDC,hBmp);

    int bytePerLine=((wid+3)/4)*4*bitPerPixel/8;
    unsigned char *dat=(unsigned char *)bit;



    int i;
    for(i=0; i<bytePerLine*hei; i++)
    {
        dat[i]=0;
    }

    SetTextColor(hDC,RGB(255,255,255));
    SetBkColor(hDC,RGB(0,0,0));
    SetBkMode(hDC,OPAQUE);

    SetDCPenColor(hDC,RGB(255,0,0));


    rect.left=0;
    rect.top=0;
    rect.right=wid;
    rect.bottom=hei;
    DrawTextExW(hDC,str,4,&rect,DT_TOP|DT_LEFT,NULL);

    int x,y;
    unsigned char *lineTop=dat;
    for(y=0; y<hei; y++)
    {
        for(x=0; x<wid; x++)
        {
            if(0!=lineTop[x*3])
            {
                printf("1");
            }
            else
            {
                printf("0");
            }
        }
        lineTop+=bytePerLine;
        printf("\n");
    }


    if(NULL!=hPrevFont)
    {
        printf("Reset font.\n");
        SelectObject(hDC,hPrevFont);
    }
    if(NULL!=hPrevBmp)
    {
        printf("Reset bmp.\n");
        SelectObject(hDC,hPrevBmp);
    }

    DeleteObject(hBmp);
    DeleteDC(hDC);
    return 0;
}

それじゃあ、CocoaとLinux(X-Window)で同じことできるようにしたら、とりあえず、メジャーなOSで、Unicodeの文字列からビットマップ作るコードが書けるじゃないか。ということで、Macでやってみたら、こっちはあっさり成功。ちょっとひっかかったのは、ひとつは、NSBitmapImageRepの解像度を調べる方法がなかなかわからんかったこと。ただ、わかってみたらデフォルトで1ポイント==1ピクセルになってるらしいことが判明。これは、放っておけばいいらしい。フォント使うときに何が困るって、出力先がモニターだろうが紙だろうが、ポイントでサイズを指定しなきゃならない点。ワードプロセッサ作ってるんでなけりゃ、ピクセルで指定させてくれた方が楽なことの方が多いんだけど。まあ、これは解決したからいい。あとは、NSAttributedStringの使い方がよくわからんかった。これは、O'Reillyの「Cocoa & Objective-C: Up and Running」(Scott Stevenson, O'Reilly) の360ページあたりの例題を見たらなんとかなった。

List: font.m

#import <Cocoa/Cocoa.h>

int main(void)
{
    NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];

    NSFontManager *fontManager=[NSFontManager sharedFontManager];
    if(NULL==fontManager)
    {
        printf("No font manager is present.\n");
    }
    else
    {
        NSArray *availableFonts=[fontManager availableFonts];
        int i;
        for(i=0; i<[availableFonts count]; i++)
        {
            NSString *str=[availableFonts objectAtIndex:i];
            printf("[%d] %s\n",i,[str UTF8String]);
        }
    }

    float fontSize=20.0f;

    NSFont *font=[NSFont fontWithName:@"Times-Roman" size:fontSize];

    NSMutableParagraphStyle *parag=[[NSMutableParagraphStyle alloc] init];
    [parag setMaximumLineHeight:fontSize];

    printf("line spacing %f\n",[[NSParagraphStyle defaultParagraphStyle] lineSpacing]);
    printf("paragraph spacing %f\n",[[NSParagraphStyle defaultParagraphStyle] paragraphSpacing]);
    printf("paragraph spacing before %f\n",[[NSParagraphStyle defaultParagraphStyle] paragraphSpacingBefore]);
    printf("minimum line height %f\n",[[NSParagraphStyle defaultParagraphStyle] minimumLineHeight]);
    printf("maximum line height %f\n",[[NSParagraphStyle defaultParagraphStyle] maximumLineHeight]);
    printf("line height multiplet %f\n",[[NSParagraphStyle defaultParagraphStyle] lineHeightMultiple]);

    NSRect rect;

    NSMutableDictionary *attributes=[NSMutableDictionary dictionary];
    [attributes setObject:font forKey:NSFontAttributeName];
    [attributes setObject:[NSNumber numberWithFloat:0.0] forKey:NSKernAttributeName];
    [attributes setObject:parag forKey:NSParagraphStyleAttributeName];
    [attributes setObject:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
    [attributes setObject:[NSColor blackColor] forKey:NSBackgroundColorAttributeName];

    unsigned char utf16[]={0x5c,0x71,0x5d,0xdd,0x03,0xc0,0x00,'A',0x03,0x94,0,0};
    NSString *rawString=[[NSString alloc] initWithBytes:utf16 length:10 encoding:NSUTF16BigEndianStringEncoding];

    NSAttributedString *string=[[NSAttributedString alloc] initWithString:rawString attributes:attributes];

    NSSize stringSize=[string size];
    printf("Size %f %f\n",stringSize.width,stringSize.height);




    NSBitmapImageRep *bmpImg=[[NSBitmapImageRep alloc] 
         initWithBitmapDataPlanes:NULL
         pixelsWide:(int)stringSize.width
         pixelsHigh:(int)stringSize.height
         bitsPerSample:8 // 8 bit per component
         samplesPerPixel:3 // 3 components
         hasAlpha:NO
         isPlanar:NO
         colorSpaceName:NSDeviceRGBColorSpace
         bytesPerRow:0
         bitsPerPixel:32];
    // The following commented-out block is optional since the resolution of 
    // a bitmap seems tobe 72 DPI by default, which makes 1 pixel per 1 point.
    /* int pixelsWide=[bmpImg pixelsWide],pixelsHigh=[bmpImg pixelsHigh];
    printf("BMP Pix =%d %d\n",pixelsWide,pixelsHigh);
    NSSize sizeInPoint={pixelsWide,pixelsHigh};
    [bmpImg setSize:sizeInPoint]; // This makes 1 point per pixel
    sizeInPoint=[bmpImg size];
    printf("BMP Size=%f %f\n",sizeInPoint.width,sizeInPoint.height); */



    NSGraphicsContext *gc=[NSGraphicsContext 
              graphicsContextWithBitmapImageRep:bmpImg];

    NSGraphicsContext *prevGc=[NSGraphicsContext currentContext];

    [NSGraphicsContext setCurrentContext:gc];


    [gc setShouldAntialias:NO];

    [[NSColor darkGrayColor] set];
    rect.origin.x=0;
    rect.origin.y=0;
    rect.size.width=stringSize.width;
    rect.size.height=stringSize.height;
    NSRectFill(rect);

    [[NSColor whiteColor] set];
    rect.origin.x=0;
    rect.origin.y=0;
    rect.size.width=stringSize.width;
    rect.size.height=stringSize.height;
    [string drawWithRect:rect options:NSStringDrawingUsesLineFragmentOrigin];

    unsigned char *dat=[bmpImg bitmapData];
    unsigned int bytesPerRow=[bmpImg bytesPerRow];
    printf("bytesPerRow %d\n",bytesPerRow);
    int y;
    for(y=0; y<(int)stringSize.height; y++)
    {
        int x;
        for(x=0; x<(int)stringSize.width; x++)
        {
            if(0==dat[y*bytesPerRow+x*4])
            {
                printf("0");
            }
            else
            {
                printf("1");
            }
        }
        printf("\n");
    }

    [NSGraphicsContext setCurrentContext:prevGc];

    [parag release];
    [string release];
    [rawString release];
    [bmpImg release];

    [pool release];

    return 0;
}

ここまでできてくると、これなら、すぐは無理でも、そんなに時間かけずに、ビットマップベースのローカライゼーションから脱却できそうだなあ、と、思った。けど、問題はUnicode。僕は無理やり作った感が強いUnicodeはあんまり好きじゃない。けど、それが標準になっちゃったみたいだから、使うんだったらUnicode。世の中、一番いいものってのは標準にならなくて、大体2番目とか3番目にいいものが標準になることになってるらしい。問題は、テキストファイルにUnicode文字列を保存したくても、標準のUnicodeテキストファイルフォーマットって無いのが困ったなあ、と、思っていた。

それで、そういや、長く愛用している秀丸エディタのエンコーディングの種類の中にEUC、Shift-JISに混じってUnicodeってのがあったっけ。(うろ覚え)。あれで保存すると、どういうデータになるか、バイナリダンプを取って見てやろう、と、思った。そしたら、選択可能なエンコーディングの種類の中に「Unicode (UTF-8)」が。UnicodeでUTF8?8ビットのにどうやってUnicode入れ込むっての?と、思った次の瞬間、脳内でMacOSXで漢字をUTF8に変換したときに出てきたコードと、Unicode (UTF-8)が突然つながった。そうか。あれは、MacOSX独自の拡張じゃなくて、UTF8ってもともとUnicodeのエンコーディングだったのか。ASCIIテキストが呼び名を変えただけじゃ無かったのか。

という、長い道のりを経て、実はUTF8って8ビットコードにUnicode文字を埋め込むためのエンコーディングのことだったということが判明。(というか、僕が知らんかっただけだが)。さらに、Wikipediaで検索したら、エンコードとデコードの方法も簡単であることがわかった。(http://en.wikipedia.org/wiki/UTF-8) これだと簡単なコードで素のUnicodeとUTF8の間の変換ができるね。こりゃ便利だ。

#include <stdio.h>


// Reference: http://en.wikipedia.org/wiki/UTF-8
template <class WCHAR_TYPE>
size_t UnicodeWChar2UTF8(size_t lOut,char out[],WCHAR_TYPE in[])
{
    unsigned int iPtr=0,oPtr=0;
    unsigned char *uOut=(unsigned char *)out;

    while(oPtr<lOut-1 && 0!=in[iPtr])
    {
        if(in[iPtr]<=0x7f)
        {
            uOut[oPtr]=(unsigned char)in[iPtr];
            oPtr++;
        }
        else if(in[iPtr]<=0x7ff)
        {
            if(lOut-2<=oPtr)
            {
                // Buffer overrun.  Cannot put zero at the end.
                break;
            }
            unsigned int low6=in[iPtr]&0x3f;
            unsigned int high5=(in[iPtr]>>6)&0x1f;

            uOut[oPtr++]=(unsigned char)(0xc0|high5);
            uOut[oPtr++]=(unsigned char)(0x80|low6);
        }
        else if(in[iPtr]<=0xffff)
        {
            if(lOut-3<=oPtr)
            {
                // Buffer overrun.  Cannot put zero at the end.
                break;
            }

            unsigned int low6=in[iPtr]&0x3f;
            unsigned int mid6=(in[iPtr]>>6)&0x3f;
            unsigned int high4=(in[iPtr]>>12)&0x0f;

            uOut[oPtr++]=(unsigned char)(0xe0|high4);
            uOut[oPtr++]=(unsigned char)(0x80|mid6);
            uOut[oPtr++]=(unsigned char)(0x80|low6);
        }
        else // The following block will never be used unless wchar_t is greater than 16-bit.
        {
            unsigned long inChar=in[iPtr];
            unsigned int bit00to05=inChar&0x3f;
            unsigned int bit06to11=(inChar>>6)&0x3f;
            unsigned int bit12to17=(inChar>>12)&0x3f;
            unsigned int bit18to23=(inChar>>18)&0x3f;
            unsigned int bit24to29=(inChar>>24)&0x3f;
            unsigned int bit30=(inChar>>30)&0x01;

            if(in[iPtr]<=0x1fffff)
            {
                if(lOut-4<=oPtr)
                {
                    // Buffer overrun.  Cannot put zero at the end.
                    break;
                }
                uOut[oPtr++]=(unsigned char)(0xf0|(bit18to23&0x07));
                uOut[oPtr++]=(unsigned char)(0x80|bit12to17);
                uOut[oPtr++]=(unsigned char)(0x80|bit06to11);
                uOut[oPtr++]=(unsigned char)(0x80|bit00to05);
            }
            else if(in[iPtr]<=0x3ffffff)
            {
                if(lOut-5<=oPtr)
                {
                    // Buffer overrun.  Cannot put zero at the end.
                    break;
                }
                uOut[oPtr++]=(unsigned char)(0xf8|(bit24to29&0x03));
                uOut[oPtr++]=(unsigned char)(0x80|bit18to23);
                uOut[oPtr++]=(unsigned char)(0x80|bit12to17);
                uOut[oPtr++]=(unsigned char)(0x80|bit06to11);
                uOut[oPtr++]=(unsigned char)(0x80|bit00to05);
            }
            else if(in[iPtr]<=0x7fffffff)
            {
                if(lOut-6<=oPtr)
                {
                    // Buffer overrun.  Cannot put zero at the end.
                    break;
                }
                uOut[oPtr++]=(unsigned char)(0xfc|bit30);
                uOut[oPtr++]=(unsigned char)(0x80|bit24to29);
                uOut[oPtr++]=(unsigned char)(0x80|bit18to23);
                uOut[oPtr++]=(unsigned char)(0x80|bit12to17);
                uOut[oPtr++]=(unsigned char)(0x80|bit06to11);
                uOut[oPtr++]=(unsigned char)(0x80|bit00to05);
            }
        }
        iPtr++;
    }

    if(oPtr<lOut)
    {
        out[oPtr]=0;
        oPtr++;
    }

    return oPtr;
}

template <class WCHAR_TYPE>
size_t UnicodeUTF8toWChar(size_t lOut,WCHAR_TYPE out[],const char in[])
{
    unsigned int iPtr=0,oPtr=0;
    unsigned char *uIn=(unsigned char *)in;

    while(oPtr<lOut-1 && 0!=uIn[iPtr])
    {
        if(0==(uIn[iPtr]&0x80))
        {
            out[oPtr]=uIn[iPtr];
            iPtr++;
        }
        else if(0xc0==(uIn[iPtr]&0xe0))
        {
            if(0==uIn[iPtr+1])
            {
                // Pre-mature termination.
                break;
            }
            unsigned int high5=uIn[iPtr]&0x1f;
            unsigned int low6=uIn[iPtr+1]&0x3f;
            out[oPtr]=(WCHAR_TYPE)((high5<<6)|low6);
            iPtr+=2;
        }
        else if(0xe0==(uIn[iPtr]&0xf0))
        {
            if(0==uIn[iPtr+1] || 0==uIn[iPtr+2])
            {
                // Pre-mature termination.
                break;
            }
            unsigned int high4=uIn[iPtr]&0x0f;
            unsigned int mid6=uIn[iPtr+1]&0x3f;
            unsigned int low6=uIn[iPtr+2]&0x3f;
            out[oPtr]=(WCHAR_TYPE)((high4<<12)|(mid6<<6)|low6);
            iPtr+=3;
        }
        else if(0xf0==(uIn[iPtr]&0xf8))
        {
            if(0==uIn[iPtr+1] || 0==uIn[iPtr+2] || 0==uIn[iPtr+3])
            {
                // Pre-mature termination.
                break;
            }
            unsigned int bit18to20=uIn[iPtr]&0x07;
            unsigned int bit12to17=uIn[iPtr+1]&0x3f;
            unsigned int bit06to11=uIn[iPtr+2]&0x3f;
            unsigned int bit00to05=uIn[iPtr+3]&0x3f;
            out[oPtr]=(WCHAR_TYPE)((bit18to20<<18)|(bit12to17<<12)|(bit06to11<<6)|bit00to05);
            iPtr+=4;
        }
        else if(0xf8==(uIn[iPtr]&0xfc))
        {
            if(0==uIn[iPtr+1] || 0==uIn[iPtr+2] || 0==uIn[iPtr+3] || 0==uIn[iPtr+4])
            {
                // Pre-mature termination.
                break;
            }
            unsigned int bit24to25=uIn[iPtr]&0x03;
            unsigned int bit18to23=uIn[iPtr+1]&0x3f;
            unsigned int bit12to17=uIn[iPtr+2]&0x3f;
            unsigned int bit06to11=uIn[iPtr+3]&0x3f;
            unsigned int bit00to05=uIn[iPtr+4]&0x3f;
            out[oPtr]=(WCHAR_TYPE)((bit24to25<<24)|(bit18to23<<18)|(bit12to17<<12)|(bit06to11<<6)|bit00to05);
            iPtr+=5;
        }
        else if(0xfc==(uIn[iPtr]&0xfe))
        {
            if(0==uIn[iPtr+1] || 0==uIn[iPtr+2] || 0==uIn[iPtr+3] || 0==uIn[iPtr+4])
            {
                // Pre-mature termination.
                break;
            }
            unsigned int bit30=uIn[iPtr]&0x01;
            unsigned int bit24to29=uIn[iPtr+1]&0x3f;
            unsigned int bit18to23=uIn[iPtr+2]&0x3f;
            unsigned int bit12to17=uIn[iPtr+3]&0x3f;
            unsigned int bit06to11=uIn[iPtr+4]&0x3f;
            unsigned int bit00to05=uIn[iPtr+5]&0x3f;
            out[oPtr]=(WCHAR_TYPE)((bit30<<30)|(bit24to29<<24)|(bit18to23<<18)|(bit12to17<<12)|(bit06to11<<6)|bit00to05);
            iPtr+=6;
        }
        else
        {
            // Invalid utf8 string
            break;
        }
        oPtr++;
    }

    if(oPtr<lOut)
    {
        out[oPtr]=0;
        oPtr++;
    }

    return oPtr;
}


// L"山川π" should be encoded to  E5 B1 B1  E5  B7 9D  CF 80
int main(void)
{
    wchar_t in[]={0x5c71,0x5ddd,0x03c0,0}; // L"山川π" not recognized correctly in the English environment.
    char utf8[256];

    int i;
    for(i=0; 0!=in[i]; i++)
    {
        printf("%04x ",in[i]);
    }
    printf("\n");

    size_t bytes=UnicodeWChar2UTF8 <wchar_t> (256,utf8,in);
    printf("Encoded to %d bytes (including the last zero terminator).\n",bytes);

    for(i=0; 0!=utf8[i]; i++)
    {
        printf("%02x ",((unsigned char *)utf8)[i]);
    }
    printf("\n");


    wchar_t backConv[256];
    size_t wchars=UnicodeUTF8toWChar <wchar_t> (256,backConv,utf8);
    printf("Decoded to %d wchars (including the last zero terminator).\n",wchars);

    for(i=0; 0!=backConv[i]; i++)
    {
        printf("%04x ",backConv[i]);
    }
    printf("\n");

    return 0;
}

Unicode問題解決。

とりあえず、今の世の中だとWindowsができてMac OSXができれば、Mac OSXはiOSとほとんど一緒だから、あとはあとはLinuxで同じことのやり方がわかればメジャーなOSほとんど全部に対応できる。

と、思ったものの、最も苦労したのがX-Window。やっぱ、このシステムは古い。けど、素のLinuxに入ってくるのはX-Windowだけだから、これサポートしないとLinuxサポートしたことにならないし。それで、驚いたのが、オフスクリーンビットマップのビット配列。今時、24ビットのビットマップで3バイト並んでたら、それぞれ8ビットずつが同一ピクセルのR, G, Bを表すってのは常識だと思ったら、X-Windowで普通にビットマップ取ってきたら、1ビット目がずらーっと並んで、2ビット目がずらーっと並んで、24ビット目まで以下繰り返しになってた。最初白地に黒で「Test」って文字列を表示したとき、最初のバイトがE0になってて、白と黒しかないのに、どうやったらFFと00以外が出てくるんだ?と、思って詰ったところで、風呂に入って考えたら、「E0は最初の5ビットがゼロ。ということは、Tの上の棒のとこがゼロになってるんじゃないか?」と、思って今度はビットごとに1と0でコンソールにパターンを表示してみたら、みごとに「Test」と読めた。FM-7のVRAMを思い出した。いや、これでいいのだ。時代錯誤が売り物のX-Windowなんだから。とにかく、欲しい機能は実現したんだから、これでいいことにしようじゃないか。

そして、半角英字が出たから、今度は漢字を出そうと思った。試しに、Unicodeで0x5c71,0x5ddd,0x03c0,'A',0x0394 (山川πAΔ)を表示しようと思ったら、Aしか出てこない。当然、フォントが違ってるんだろうと思って、いろいろ違うフォントを試してみたが、出ない。ひょっとして、XwcDrawStringが悪いのかと思って、違う関数も試しても、ダメ。それで、Googleであれこれ検索して、行き着いたのが、このページだった。

http://homepage3.nifty.com/rio_i/lab/xlib/011mbcs.htm

これを見たところ、我がプログラムに足りないのは、setlocaleだけ?setlocaleって、呼ばなくてもデフォルトの値が使われるだけなんじゃないの?と、思いつつ、だまされたと思ってsetlocale(LC_CTYPE,"");を入れてみる。

そしたら、出た。システムのLocaleがあるんだから、設定してなかったらシステムのLocaleをデフォルトに使えば良さそうなもんなのに。ちょっと納得いかなかったが、まあ、当初の目標は達成。 下のプログラムは、g++ font.cpp -lX11 でコンパイル可能。

List: font.cpp

#include <stdio.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xlocale.h>

/* Memo for future reference
Meaning of font string.
  http://en.wikipedia.org/wiki/X_logical_font_description

X-Window drawing functions
  http://www-yano.is.tokushima-u.ac.jp/~mituhara/software_e/draw.html

XwcDrawString sample code
  http://homepage3.nifty.com/rio_i/lab/xlib/011mbcs.htm
    This page suggests:
    - Environment variable LANG should be set.
    - A function call setlocale(LC_CTYPE,""); is required.

May also be possible to use XLoadFont, XSetFont
  Font font=XLoadFont(ysXDsp,"6x10");
*/

int main(void)
{
    Display *dsp=XOpenDisplay(NULL);
    if(NULL!=dsp)
    {
        printf("Display Opened. (Depth=%d)\n",DefaultDepth(dsp,0));

        Window rootWin=DefaultRootWindow(dsp);

        int nFont;
        char **fontList=XListFonts(dsp,"*iso10646*",1024,&nFont);
        int i;
        for(i=0; i<nFont; i++)
        {
            printf("[%d] %s\n",i,fontList[i]);
        }

        const char *baseFontName=
            "-*-*-*-*-*-*-16-*-*-*-*-*-iso8859-1,"
            "-*-*-*-*-*-*-16-*-*-*-*-*-iso8859-2,"
            "-*-*-*-*-*-*-16-*-*-*-*-*-iso8859-4,"
            "-*-*-*-*-*-*-16-*-*-*-*-*-iso8859-5,"
            "-*-*-*-*-*-*-16-*-*-*-*-*-iso8859-6,"
            "-*-*-*-*-*-*-16-*-*-*-*-*-iso8859-7,"
            "-*-*-*-*-*-*-16-*-*-*-*-*-iso8859-8,"
            "-*-*-*-*-*-*-16-*-*-*-*-*-iso8859-9,"
            "-*-*-*-*-*-*-16-*-*-*-*-*-iso8859-15,"
            "-*-*-*-*-*-*-16-*-*-*-*-*-iso10646-1";

        const char *localeStr=setlocale(LC_CTYPE,"");
        if(NULL==localeStr || True!=XSupportsLocale())
        {
            setlocale(LC_CTYPE,"en_US.UTF-8");  // Desperate attempt!
        }

        int missCharSetCount;
        char *defStr,**missCharSet;
        XFontSet fontSet=XCreateFontSet(
        dsp,
        baseFontName,
        &missCharSet,
        &missCharSetCount,
        &defStr);
        if(NULL!=fontSet)
        {
            printf("Acquired font set (Missing=%d)\n",missCharSetCount);

            wchar_t str[]={0x5c71,0x5ddd,0x03c0,'A',0x0394,0};
            XRectangle inkRect,logRect;
            int ext=XwcTextExtents(fontSet,str,5,&inkRect,&logRect);

            printf("Extent %d\n",ext);
            printf("Ink Rect %d %d %d %d\n",inkRect.x,inkRect.y,inkRect.width,inkRect.height);
            printf("Log Rect %d %d %d %d\n",logRect.x,logRect.y,logRect.width,logRect.height);


            int bmpWid=ext,bmpHei=logRect.height;
            int depth=DefaultDepth(dsp,0);

            Pixmap pixmap=XCreatePixmap(dsp,rootWin,bmpWid,bmpHei,depth);
            GC gc=XCreateGC(dsp,pixmap,0,0);


            XSetForeground(dsp,gc,BlackPixel(dsp,0));
            XFillRectangle(dsp,pixmap,gc,0,0,bmpWid,bmpHei);

            XSetForeground(dsp,gc,WhitePixel(dsp,0));
            XwcDrawString(dsp,pixmap,fontSet,gc,0,bmpHei-1,str,5);

            XImage *image=XGetImage(dsp,pixmap,0,0,bmpWid,bmpHei,~0,XYPixmap);
            if(NULL!=image)
            {
                printf("Acquired Image %dx%d\n",image->width,image->height);
                printf("Bitmap Unit=%d\n",image->bitmap_unit);
                printf("Bytes per line=%d\n",image->bytes_per_line);
                printf("Depth=%d\n",image->depth);

                int x,y;
                for(y=0; y<image->height; y++)
                {
                    unsigned char mask=1;
                    unsigned char *byteTop=(unsigned char *)
                    image->data+y*image->bytes_per_line;
                    for(x=0; x<image->width; x++)
                    {
                        if((*byteTop)&mask)
                        {
                            printf("1");
                        }
                        else
                        {
                            printf("0");
                        }
                        if(0!=(mask&0x80))
                        {
                            byteTop++;
                            mask=1;
                        }
                        else
                        {
                            mask<<=1;
                        }
                    }
                    printf("\n");
                }

                /* Attempt 1 Failed. Apparently, each of 24 layers is
                packed contiguously.  I was forgetting X-Window is from the stone age.
                int byte_per_pixel=image->bitmap_unit/8;
                for(y=0; y<image->height; y++)
                {
                    for(x=0; x<20; x++)
                    {
                        int offset=y*(64*byte_per_pixel)+x*byte_per_pixel;
                        unsigned char *ptr=(unsigned char *)image->data+offset;
                        printf("%02x%02x%02x ",ptr[0],ptr[1],ptr[2]);
                    }
                    printf("\n");
                } */
            }

            XDestroyImage(image);
            XFreeGC(dsp,gc);
            XFreePixmap(dsp,pixmap);
            XFreeFontSet(dsp,fontSet);
        }
        XCloseDisplay(dsp);
    }
}

コンパイルは、g++ font.cpp -lX11

あとは、このプログラムをうまいことクラスの皮かぶせて、どのプラットフォームから見ても同じようなインターフェースで利用できるようにすれば、PNGファイルを使った無理やりのローカライゼーションから脱出できる予定。

2011/04/27