freetype2

freetype2はGPL2またはBSDライクなライセンスのフォントをラスタライズしてくれるライブラリ。
クロスプラットフォーム対応なので、IPAゴシックなどのフリーのフォントファイルを、
アプリと一緒に配布すれば同じフォントで描画できる。
…が、まずやりたいのは、Windowsにインストールされているメイリオフォントをfreetype2で使いたい。

0. freetype2のビルド
特にエラーも出ずすんなり終わった

1. freetype2の初期化/終了


#include <ft2build.h>
#include FT_FREETYPE_H
#include <tchar.h>
#include <Windows.h>

namespace
{
    FT_Library  library;
    FT_Face meiryo_face;

    typedef std::vector<char> FontBuffer;
    FontBuffer meiryo_font_buffer;

    bool init() {
return FT_Init_FreeType(&library) == 0;
    }

    void destroy() {
        if (meiryo_face) FT_Done_Face(meiryo_face);
        if (library) FT_Done_FreeType(library);
    }
}

何もはまらなかった


2. windowsからフォントを引っ張ってくる


namespace
{
    bool load_meiryo_face(void* hWnd, int size, FontBuffer& font_buffer)
    {
        // get font binary
        HWND hwnd = *(reinterpret_cast<HWND*>(&hWnd));
        HDC hdc = GetDC(hwnd);
        wchar_t* name = _T("メイリオ");
        if (HFONT font = CreateFont(
            size,
            0,
            0,
            0,
            FW_REGULAR,
            FALSE, // italic
            FALSE, // underline
            FALSE, // strikeout
            DEFAULT_CHARSET,
            OUT_DEFAULT_PRECIS,
            CLIP_DEFAULT_PRECIS,
            DEFAULT_QUALITY,
            DEFAULT_PITCH | FF_DONTCARE,
            name))
        {
            if (HGDIOBJ old = SelectObject(hdc, font))
            {
                if (UINT buffer_size = GetOutlineTextMetrics(hdc, 0, NULL))
                {
                    std::vector<char> metric_buffer(buffer_size);
                    LPOUTLINETEXTMETRIC metric = reinterpret_cast<LPOUTLINETEXTMETRIC>(&(*metric_buffer.begin()));
                    if (GetOutlineTextMetrics(hdc, buffer_size, metric))
                    {
                        if ((metric->otmfsType & 1) == 0)
                        {
                            if (DWORD byte = GetFontData(hdc, 0x66637474, 0, NULL, 0))
                            {
                                if (byte != GDI_ERROR)
                                {
                                    font_buffer.resize(byte);
                                    if (GetFontData(hdc, 0x66637474, 0, &(*font_buffer.begin()), byte) == GDI_ERROR)
                                    {
                                        font_buffer.clear();
                                    }
                                }
                            }
                        }
                    }
                }
                DeleteObject(old);
            }
        }
        ReleaseDC(hwnd, hdc);

        if (int error = FT_New_Memory_Face(
            library, 
            reinterpret_cast<const FT_Byte*>(&(*font_buffer.begin())),
            static_cast<FT_Long>(font_buffer.size()),
            0,
            &meiryo_face))
        {
            return false;
        }
        // for FT_LOAD_DEFAULT
        if (int error = FT_Set_Pixel_Sizes(
            meiryo_face,
            0,
            size))
        {
            return false;
        }
        return true;
    }
}

FT_New_Memory_Faceというやつで、メモリ上のフォントデータを読み込める。

ここでハマったこと
meiryo_font_bufferのスコープを広くしているが、作成したmeiryo_faceを使っている間存在している必要があるようだ。
・GetFontDataに0x66637474を入れないとフォントデータが正しく取得できない。(MSの英語サイトのほうのヘルプにしか書いていない)
FT_Set_Pixel_Sizesを行わないと、後で FT_Load_Glyph を呼び出したときに FT_LOAD_NO_SCALE 以外だとエラーになってしまう。

3. 指定した文字列を画像へ落とし込み


512x512の画像バッファに左上から文字を描いていきたい。
後でテクスチャアトラスに落とし込むので、何文字か描いて、1文字を確実に包む矩形のの幅と高さが分かればよい。
高さは上でセットしたFT_Set_Pixel_Sizeのsize+1か+2くらいでいいだろう。
幅については、メイリオは、日本語部分は等幅で、アルファベットはプロポーショナルらしい。

freetypeではビットマップ(左下原点)にラスタライズできるようだ。
得られるビットマップの幅(width)と高さ(rows)による矩形は以下の画像の白い部分だった。



オフセットだらけで、若干理解が怪しいので、結果とコードだけ貼り付けておく。
特にハマったのはFT_Load_Glyphに渡すhinting情報である。
日本語に対してNO_HINTINGを渡さないと、かなり汚い文字が描画された。
逆にアルファベットに対してはデフォルトにしておかないと、「_」が繋がらなかった。
freetype (with cairo) を使用している Inkscape の結果とアルファベット部分が異なるが、
Inkscapeではどんなhintingを指定しているのだろうか…

結果画像:

当然だがメイリオはClearTypeに最適化されたフォントなので、
ワードパッドなどの結果とこれを比べると全然違う…。
が、まぁGUIの文字で使うくらいならこれでもいいか…。



/**
 * create font image
 */
UMImagePtr UMFont::create_font_image(void* hWnd, const std::u16string& text, int font_size) const
{
    if (text.empty()) return false;
    // create image
    UMImagePtr image(std::make_shared<UMImage>());
    image->init(512, 512);
    for (UMImage::ImageBuffer::iterator it = image->mutable_list().begin(); it != image->mutable_list().end(); ++it)
    {
        (*it) = umbase::UMVec4d(1);
    }

    if (load_meiryo_face(hWnd, font_size, meiryo_font_buffer))
    {
        if (meiryo_face)
        {
            const int image_w = image->width();
            const int image_h = image->height();
            // bitmap : base point is left-bottom.
            // image  : base point is left-top.
            int posx = 0;
            int posy = 0;
            int imagex = 0;
            int imagey = 0;

            for (int i = 0; i < static_cast<int>(text.size()); ++i)
            {
                FT_UInt glyph_index = FT_Get_Char_Index(meiryo_face, text[i]);
                if (text[i] < 0xFF && isgraph(text[i]))
                {
                    // ascii
                    if (int error = FT_Load_Glyph(meiryo_face, glyph_index, FT_LOAD_NO_BITMAP))
                    {
                        continue;
                    }
                }
                else
                {
                    if (int error = FT_Load_Glyph(meiryo_face, glyph_index, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT))
                    {
                        continue;
                    }
                }

                if (int error = FT_Render_Glyph(meiryo_face->glyph, FT_RENDER_MODE_NORMAL))
                {
                    continue;
                }
                
                FT_GlyphSlot slot = meiryo_face->glyph;
                unsigned char* bitmap_buffer = slot->bitmap.buffer;
                const int bitmap_w = slot->bitmap.width;
                const int bitmap_h = slot->bitmap.rows;
                const int advance_x = static_cast<int>(slot->metrics.horiAdvance/64.0);
                const int advance_y = static_cast<int>(slot->metrics.vertAdvance/64.0);
                const int iyoffset = font_size - slot->bitmap_top;
                const int ixoffset = slot->bitmap_left;
                
                const int ysize = posy + bitmap_h;
                for (int y = posy, iy = imagey; y < ysize; ++y, ++iy)
                {
                    const int xsize = posx + bitmap_w;
                    for (int x = posx, ix = imagex; x < xsize; ++x, ++ix)
                    {
                        const int buffer_pos = bitmap_w * (y - posy) + (x - posx);
                        const int image_pos = image_w * (iy + iyoffset) + (ix + ixoffset);
                        const double alpha = bitmap_buffer[buffer_pos] / static_cast<double>(0xFF);
                        const double inv_alpha = 1.0 - alpha;
                        // for alpha image
                        // image->mutable_list().at(image_pos) = umbase::UMVec4d(0, 0, 0, alpha);
                        image->mutable_list().at(image_pos) = umbase::UMVec4d(inv_alpha, inv_alpha, inv_alpha, 1.0);
                    };
                }
                posx += bitmap_w;
                imagex += advance_x;
                if (imagex >= image_w - font_size)
                {
                    imagex = 0;
                    imagey += advance_y;
                }
            }
        }
    }
    return image;
}




Comments