あという文字に゛という結合文字がついている場合、理想的にはあ゙と表示される。
ところがFreeTyp2だけでラスタライズすると、以下のようになる。
HarfBuzzを使うと、これを以下のように美しくラスタライズできる。
まず比較のためにFreeType2だけでラスタライズするプログラムを書く。
#include <string> #include <array> #include <ft2build.h> #include FT_FREETYPE_H #pragma warning(disable:4996) #if _DEBUG #pragma comment(lib,"freetyped.lib") #else #pragma comment(lib,"freetype.lib") #endif ////////////////////////// // 書き込み先画像 const int imageWidth = 300; const int imageHeight = 150; std::array<unsigned char, imageWidth* imageHeight> image; ////////////////////////// //! @brief imageへの書き込み時のピクセル計算 inline int pixel_pos(const int x, const int y) { return y * imageWidth + x; } //! @brief imageへbmpの内容を書き込む //! @param [in] bmp 文字画像 //! @param [in] startx image画像内の書き込み開始位置 //! @param [in] starty image画像内の書き込み開始位置 void draw(const FT_Bitmap& bmp, int startx, int starty); //! @brief PBM(1byte,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @details 1画素1Byteのメモリを渡すと、0,1テキストでファイル名fnameで書き込む void pbmP1_Write(const char* const fname, const int width, const int height, const unsigned char* const p); int main() { FT_Library library; // handle to library FT_Error error; error = FT_Init_FreeType(&library); if (error) return -1; FT_Face face; // handle to face object // フォントファイル読み込み error = FT_New_Face( library, "C:\\Windows\\Fonts\\msgothic.ttc", 0, &face ); //std::u32string text = U"あいう"; // ① std::u32string text = U"あ゙いう"; // ② //文字コード指定 error = FT_Select_Charmap(face,FT_ENCODING_UNICODE); if (error == FT_Err_Unknown_File_Format) return -1; else if (error) return -1; //文字サイズをピクセルで指定 FT_Set_Pixel_Sizes(face,0,64); int pen_x = 0; int pen_y = 0; for (size_t k = 0; k < text.size(); k++) { // 文字の取得 FT_ULong character = text[k]; FT_UInt char_index = FT_Get_Char_Index(face, character); // グリフ(字の形状)読込 error = FT_Load_Glyph(face, char_index, FT_LOAD_RENDER); if (error) return -1; // ignore errors // 文字を画像化 FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); // 画像書き込み draw( face->glyph->bitmap, pen_x + face->glyph->bitmap_left, pen_y - face->glyph->bitmap_top + 100 ); // 描画位置を更新 pen_x += face->glyph->advance.x >> 6; pen_y += face->glyph->advance.y >> 6; } // ファイル書き込み pbmP1_Write("C:\\test\\freetypetest.pbm",imageWidth,imageHeight,&image[0]); // FreeType2の解放 FT_Done_Face(face); FT_Done_FreeType(library); }
//! @brief imageへbmpの内容を書き込む //! @param [in] bmp 文字画像 //! @param [in] startx image画像内の書き込み開始位置 //! @param [in] starty image画像内の書き込み開始位置 void draw(const FT_Bitmap& bmp, int startx, int starty) { int Width = bmp.width; int Height = bmp.rows; for (size_t y = 0; y < Height; y++) { for (size_t x = 0; x < Width; x++) { int xx = startx + x; int yy = starty + y; if (xx < 0)continue; if (yy < 0)continue; if (xx > imageWidth - 1)continue; if (yy > imageHeight - 1)continue; #if 0 //枠書き込み if (x == 0 || y == 0 || y == Height - 1 || x == Width - 1) { image[pixel_pos(xx, yy)] = 1; } #endif if (bmp.buffer[y * Width + x]) { image[pixel_pos(xx, yy)] = 1; } } } }
//! @brief PBM(1byte,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @details 1画素1Byteのメモリを渡すと、0,1テキストでファイル名fnameで書き込む void pbmP1_Write(const char* const fname, const int width, const int height, const unsigned char* const p) { // PPM ASCII FILE* fp = fopen(fname, "wb"); fprintf(fp, "P1\n%d\n%d\n", width, height); size_t k = 0; for (size_t i = 0; i < (size_t)height; i++) { for (size_t j = 0; j < (size_t)width; j++) { fprintf(fp, "%d ", p[k] ? 0 : 1); k++; } fprintf(fp, "\n"); } fclose(fp); }
#include <string> #include <array> #include <ft2build.h> #include FT_FREETYPE_H #include <hb.h> #include <hb-ft.h> #pragma warning(disable:4996) #ifdef _DEBUG #pragma comment(lib,"freetyped.lib") #pragma comment(lib,"harfbuzzd.lib") #else #pragma comment(lib,"freetype.lib") #pragma comment(lib,"harfbuzz.lib") #endif const int imageWidth = 300; const int imageHeight = 150; std::array<unsigned char, imageWidth* imageHeight> image; //! @brief imageへの書き込み時のピクセル計算 int pixel_pos(const int x, const int y) { return y * imageWidth + x; } //! @brief imageへbmpの内容を書き込む //! @param [in] bmp 文字画像 //! @param [in] startx image画像内の書き込み開始位置 //! @param [in] starty image画像内の書き込み開始位置 void draw(const FT_Bitmap& bmp, int startx, int starty); //! @brief PBM(1byte,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @details 1画素1Byteのメモリを渡すと、0,1テキストでファイル名fnameで書き込む void pbmP1_Write(const char* const fname, const int width, const int height, const unsigned char* const p); //! @brief フォントの読込 FreeType2の処理 //! @param [in] library FT_Library //! @param [in] fontfile フォントファイルへのパス //! @return フェイスオブジェクト FT_Face* my_LoadFonts( FT_Library& library, const char* fontfile ) { FT_Face* face = new FT_Face; FT_Error error; // フォントファイル読み込み error = FT_New_Face( library, fontfile, 0, face ); //文字コード指定 error = FT_Select_Charmap( *face, // target face object FT_ENCODING_UNICODE // エンコード指定 ); if (error == FT_Err_Unknown_File_Format) return nullptr; else if (error) return nullptr; int pixel_size_y = 64; error = FT_Set_Pixel_Sizes( *face, // handle to face object 0, // pixel_width pixel_size_y); // pixel_height return face; }
//! @brief HarfBuzzで計算した座標を使いFreeType2で文字列を描画する void my_FaceDraw(hb_buffer_t* hbbuf,FT_Face* face) { // 描画先をクリア std::fill(image.begin(), image.end(), 0); //文字数を格納 (書記素数ではない。例えば「あ゙」は2文字) unsigned int glyph_count; hb_glyph_info_t* glyph_info = hb_buffer_get_glyph_infos(hbbuf, &glyph_count); hb_glyph_position_t* glyph_pos = hb_buffer_get_glyph_positions(hbbuf, &glyph_count); hb_position_t cursor_x = 0; hb_position_t cursor_y = 0; // 各文字ごとに描画する for (unsigned int i = 0; i < glyph_count; i++) { // codepointという変数名だが実際にはグリフインデクスが入っている hb_codepoint_t glyphid = glyph_info[i].codepoint; // 一文字分のオフセット。本来描画される位置からどれぐらいずれるか hb_position_t x_offset = glyph_pos[i].x_offset >> 6;// 結合文字の゛は本来の位置より左側に描画するので hb_position_t y_offset = glyph_pos[i].y_offset >> 6;// x_offsetにはマイナスの値が入る // 次の文字の描画開始位置までのピクセル数 hb_position_t x_advance = glyph_pos[i].x_advance >> 6; hb_position_t y_advance = glyph_pos[i].y_advance >> 6; /////////////////////////////////////// /////////////////////////////////////// /////////////////////////////////////// FT_Error error = FT_Load_Glyph(*face, glyphid, FT_LOAD_RENDER); if (error) { continue; } // 文字を画像化 FT_Render_Glyph( (*face)->glyph, FT_RENDER_MODE_NORMAL); // 画像書き込み // オフセットを加えて座標調整する draw( (*face)->glyph->bitmap, cursor_x + x_offset + (*face)->glyph->bitmap_left, cursor_y + y_offset - (*face)->glyph->bitmap_top + 100 ); /////////////////////////////////////// /////////////////////////////////////// /////////////////////////////////////// // 次の文字の描画開始値 cursor_x += x_advance; cursor_y += y_advance; } }
int main() { FT_Library library; // handle to library FT_Error error; error = FT_Init_FreeType(&library); if (error) return -1; ///////////////////////////////////////////// FT_Face* face_jp = my_LoadFonts(library, "C:\\Windows\\Fonts\\msgothic.ttc"); //////////////////////// hb_font_t* hbfont; hb_buffer_t* hbbuf; //////////////////////// std::u32string text = U"あ゙いう"; //////////////////////// hbbuf = hb_buffer_create(); hb_buffer_add_utf32(hbbuf, (const std::uint32_t*)text.data(), -1, 0, -1);// 描画したいテキストの設定 hb_buffer_set_direction(hbbuf, HB_DIRECTION_LTR);// 文字の方向を左から右として設定 hb_buffer_set_script(hbbuf, HB_SCRIPT_HIRAGANA);// Unicodeの用字(Script)として日本語を指定 hb_buffer_set_language(hbbuf, hb_language_from_string("jp", -1));// 言語として日本語を設定 //////////////////////// hbfont = hb_ft_font_create(*face_jp, nullptr); hb_shape(hbfont, hbbuf, NULL, 0); //////////////////////// my_FaceDraw(hbbuf, face_jp);//FreeType2とHarfBuzzで文字列描画 //////////////////////// hb_buffer_destroy(hbbuf);// バッファ破棄 hb_font_destroy(hbfont); // フォント破棄 //////////////////////// FT_Done_Face(*face_jp); pbmP1_Write("C:\\test\\freetypetest_mgj.pbm", imageWidth, imageHeight, &image[0]); ///////////////////////////////////////////// // FreeType2の解放 FT_Done_FreeType(library); }
//! @brief imageへbmpの内容を書き込む //! @param [in] bmp 文字画像 //! @param [in] startx image画像内の書き込み開始位置 //! @param [in] starty image画像内の書き込み開始位置 void draw(const FT_Bitmap& bmp, int startx, int starty) { int Width = bmp.width; int Height = bmp.rows; for (size_t y = 0; y < Height; y++) { for (size_t x = 0; x < Width; x++) { int xx = startx + x; int yy = starty + y; #if 0 if (x == 0 || y == 0 || x == Width - 1 || y == Height - 1) { image[pixel_pos(xx, yy)] = 1; } #endif if (xx < 0)continue; if (yy < 0)continue; if (xx >= imageWidth)continue; if (yy >= imageHeight)continue; if (bmp.buffer[y * Width + x]) { image[pixel_pos(xx, yy)] = 1; } } } } //! @brief PBM(1byte,テキスト)を書き込む //! @param [in] fname ファイル名 //! @param [in] width 画像の幅 //! @param [in] height 画像の高さ //! @param [in] p 画像のメモリへのアドレス //! @details 1画素1Byteのメモリを渡すと、0,1テキストでファイル名fnameで書き込む void pbmP1_Write(const char* const fname, const int width, const int height, const unsigned char* const p) { // PPM ASCII FILE* fp = fopen(fname, "wb"); fprintf(fp, "P1\n%d\n%d\n", width, height); size_t k = 0; for (size_t i = 0; i < (size_t)height; i++) { for (size_t j = 0; j < (size_t)width; j++) { fprintf(fp, "%d ", p[k] ? 0 : 1); k++; } fprintf(fp, "\n"); } fclose(fp); }
続く。