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を表示した結果