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