スポンサーリンク

HarfBuzzでFreeType2のレイアウトを調整(1)

HarfBuzzの目的

あという文字に゛という結合文字がついている場合、理想的にはあ゙と表示される。

あ゙いう

ところがFreeTyp2だけでラスタライズすると、以下のようになる。

HarfBuzzを使うと、これを以下のように美しくラスタライズできる。

FreeType2だけの場合

まず比較のために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);
}

HarfBuzz + FreeType2

#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);
}

続く。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: