Linuxなどのメモ書き

FreeTypeでフォントのアウトラインを取る


概要

FreeTypeでTrueTypeフォントのアウトラインを抽出して描画する。

アウトラインの取得

 FT_Load_Char()でグリフをグリフスロットに読み込んだ後、slot->outlineからアウトライン情報にアクセスできる。slot->outline(FT_Outline構造体)のデータに直接アクセスしてもいいのだが、 FT_Outline_Decompose()関数を使えばアウトラインを簡単にパス情報に変換できる。FT_Outline_Decompose()を使った変換例は以下のとおり。extract.cppはフォントのアウトラインをSVG形式で出力する。

extract.cpp

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <freetype/ftglyph.h>
#include <freetype/ftoutln.h>

const int kResolution = 100;  // dpi
const char *font_file = "/usr/share/fonts/vlgothic/VL-Gothic-Regular.ttf";

void dump(const FT_Outline& outline)
{
  std::cout << "n_contours: " << outline.n_contours << std::endl;
  std::cout << "n_points: " << outline.n_points << std::endl;
  for (int i = 0 ; i < outline.n_contours ; i++) {
    std::cout << "contours[" << i << "]: " << outline.contours[i] << std::endl;
  }
}
void dump(const FT_Vector& vec)
{
  std::cout << "{x: " << vec.x << ", y: " << vec.y << "}" << std::endl;
}

FT_Vector coordinate_ft2svg(const FT_Vector& v)
{
  // y軸の向きが逆なのでy値を反転させて、26.6固定少数点を整数化。
  // オフセットの100は適当。
  FT_Vector v_in_svg = {x: v.x / 64, y: -v.y / 64 + 100};

  return v_in_svg;
}

struct DecomposeUserData {
  std::ostream* ostream;
};

std::string stringify_vector(const FT_Vector& v)
{
  std::ostringstream s;
  s << v.x << "," << v.y << " ";
  return s.str();
}

int move_to(const FT_Vector* to, void* user)
{
  FT_Vector to_in_svg = coordinate_ft2svg(*to);
  DecomposeUserData* data = reinterpret_cast<DecomposeUserData *>(user);

  *(data->ostream) << "M " << stringify_vector(to_in_svg);
  return 0;
}
int line_to(const FT_Vector* to, void* user)
{
  FT_Vector to_in_svg = coordinate_ft2svg(*to);
  DecomposeUserData* data = reinterpret_cast<DecomposeUserData *>(user);

  *(data->ostream) << "L " << stringify_vector(to_in_svg);
  return 0;
}
int conic_to(const FT_Vector* control, const FT_Vector* to, void* user)
{
  FT_Vector control_in_svg = coordinate_ft2svg(*control);
  FT_Vector to_in_svg = coordinate_ft2svg(*to);
  DecomposeUserData* data = reinterpret_cast<DecomposeUserData *>(user);

  *(data->ostream) << "Q " << stringify_vector(control_in_svg) << stringify_vector(to_in_svg);
  return 0;
}
int cubic_to(const FT_Vector* control1, const FT_Vector* control2, const FT_Vector* to, void* user)
{
  FT_Vector control1_in_svg = coordinate_ft2svg(*control1);
  FT_Vector control2_in_svg = coordinate_ft2svg(*control2);
  FT_Vector to_in_svg = coordinate_ft2svg(*to);
  DecomposeUserData* data = reinterpret_cast<DecomposeUserData *>(user);

  *(data->ostream) << "C " << stringify_vector(control1_in_svg) << stringify_vector(control2_in_svg) << stringify_vector(to_in_svg);
  return 0;
}


int main(int argc, char *argv[])
{
  FT_Library library;
  FT_Face face;
  FT_Error error;

  if (FT_Init_FreeType(&library)) {
    throw std::runtime_error("Initialize error.");
  }

  if (FT_New_Face(library, font_file, 0, &face)) {
    throw std::runtime_error("Can't create font.");
  }

  /* use 80pt at 100dpi */
  error = FT_Set_Char_Size(face, 80 * 64, 0, kResolution, 0);
  if (error) {
    throw std::runtime_error("FT_Set_Char_Size() returned error.");
  }

  error = FT_Load_Char(face, L'あ', FT_LOAD_DEFAULT);
  if (error) {
    throw std::runtime_error("FT_Load_Char() returned error.");
  }

  FT_GlyphSlot slot = face->glyph;
  FT_Outline outline = slot->outline;

  dump(slot->outline);

  std::ofstream outsvg("result.svg");
  outsvg << "<svg width=\"400\" height=\"400\" viewBox=\"0 0 400 400\" style=\"background: #eee\">" << std::endl;

  outsvg << "<path fill=\"none\" stroke=\"red\" stroke-width=\"1\" d=\"";

  FT_Outline_Funcs funcs;
  funcs.move_to = move_to;
  funcs.line_to = line_to;
  funcs.conic_to = conic_to;
  funcs.cubic_to = cubic_to;
  funcs.shift = 0;
  funcs.delta = 0;
  DecomposeUserData user;
  user.ostream = &outsvg;
  FT_Outline_Decompose(&outline, &funcs, &user);

  outsvg << "\" />" << std::endl;

  outsvg << "</svg>" << std::endl;


  return 0;
}


FT_Outline_Decompose()は解析したいグリフのアウトライン情報(FT_Outline)とコールバック関数群(FT_Outline_Funcs)を受け取る。FT_Outline_Decompose()を呼び出すと、アウトライン情報をペンの移動、直線、二次ベジェ曲線、三次べジェ曲線に分割し、FT_Outline_Funcsで指定したコールバック関数を呼び出す。各コールバック関数で描画処理を行なえば、フォントのアウトラインを描画できる。extract.cppでは描画が面倒なのでSVGのpathとして出力した。

 extract.cppのコンパイルは以下のとおり。

コンパイル

g++ -Wall -O2 -std=c++0x -o extract `pkg-config --cflags freetype2`  `pkg-config --libs freetype2` extract.cpp

 

 ./extractを実行すると以下のようなSVGファイルが出力される。

出力されるSVG

<svg width="400" height="400" viewBox="0 0 400 400" style="background: #eee">
<path fill="none" stroke="red" stroke-width="1" d="M 66,100 Q 78,98 85,91 Q 92,85 92,76 Q 92,61 78,56 Q 70,77 56,90 Q 42,101 29,101 Q 20,101 15,97 Q 10,92 10,83 Q 10,71 18,62 Q 25,53 38,49 L 38,33 L 14,33 L 14,26 L 38,26 L 38,12 L 46,12 L 46,26 L 96,26 L 96,33 L 46,33 L 46,47 Q 54,46 61,46 Q 79,46 89,54 Q 100,62 100,76 Q 100,88 91,96 Q 83,104 68,107 L 66,100 M 70,54 Q 66,53 61,53 Q 53,53 46,55 L 46,88 Q 62,77 70,54 M 38,93 L 38,57 Q 29,61 24,68 Q 18,74 18,82 Q 18,88 21,91 Q 24,95 29,95 Q 33,95 38,93 " />
</svg>

 

SVGを表示してみると、以下のようにアウトラインを描画できているのがわかる。

SVGを表示した結果

 


最終更新 2017/12/24 23:29:55 - kztomita
(2017/12/24 22:50:21 作成)
添付ファイル
outline.png - kztomita


リンク

その他のWiki
Linuxメモ
Xnuメモ

会社
(有)ビットハイブ
受託開発やってます。

よくやる仕事

・Webシステム開発(LAMP環境)
・Linuxサーバー設定関連
サーバー移転作業代行

開発事例にデジタルカタログ/マンガビューワーを追加しました。

draggable.jsのスマホ対応版デモページを追加しました。説明はこちら

検索

Adsense
最近のコメント