FreeTypeを使う 新しいページはコチラ
提供: yonewiki
(→サンプル2. 公式サイト版 のQtConsolApp) |
(→サンプル6.公式サイトのStatic Jobs LLC作成) |
||
417行: | 417行: | ||
− | <Syntaxhighlight2 lang="cpp" line="107"> | + | <Syntaxhighlight2 lang="cpp" line start="107"> |
idces[i] = FT_Get_Char_Index(face, text[i]); | idces[i] = FT_Get_Char_Index(face, text[i]); | ||
</Syntaxhighlight2> | </Syntaxhighlight2> | ||
428行: | 428行: | ||
− | <Syntaxhighlight2 lang="cpp" line="109"> | + | <Syntaxhighlight2 lang="cpp" line start="109"> |
error = FT_Load_Glyph(face, idces[i], FT_LOAD_DEFAULT); | error = FT_Load_Glyph(face, idces[i], FT_LOAD_DEFAULT); | ||
</Syntaxhighlight2> | </Syntaxhighlight2> | ||
438行: | 438行: | ||
それが115行目だ。 | それが115行目だ。 | ||
− | <Syntaxhighlight2 lang="cpp" line="115"> | + | <Syntaxhighlight2 lang="cpp" line start="115"> |
error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); | error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); | ||
</Syntaxhighlight2> | </Syntaxhighlight2> | ||
684行: | 684行: | ||
− | 出力がTGAファイルなのもイカす。TGAはTruVision社が作った。Graphic形式です。ちょっと改造しないとVisual Studioではヘッダー部分が壊れてしまう感じでした。具体的にいうと、ヘッダーで使われているunsigned int の uint16 | + | 出力がTGAファイルなのもイカす。TGAはTruVision社が作った。Graphic形式です。ちょっと改造しないとVisual Studioではヘッダー部分が壊れてしまう感じでした。具体的にいうと、ヘッダーで使われているunsigned int の uint16 だと4バイトになってしまいます。2バイトでなければならない情報がすべて4バイトになって位置ズレをおこしてしまいます。 |
806行: | 806行: | ||
TGAHeader header; | TGAHeader header; | ||
memset(&header, 0, sizeof(TGAHeader)); | memset(&header, 0, sizeof(TGAHeader)); | ||
− | + | //Header部の長さを指定。指定がない場合は固定の長さ。 | |
− | header. | + | header.idLength = 0; // 1byte 0~255 |
− | + | ||
− | + | //Color Map 0★=無, 1=有(この場合0~127はTruVisionが予約している色、128~255はファイルで定義) | |
− | header. | + | header.paletteType = 0; // 1byte |
− | header. | + | //TGAファイルの形式 |
+ | //0- 画像データは含まれていません。 | ||
+ | //1- 非圧縮、カラーマップ画像 | ||
+ | //2★- 非圧縮、トゥルーカラー画像 | ||
+ | //3- 非圧縮、白黒画像 | ||
+ | //9- ランレングスエンコード、カラーマップ画像 | ||
+ | //10- ランレングスエンコード、トゥルーカラー画像 | ||
+ | //11-ランレングスエンコード、白黒画像 | ||
+ | header.imageType = 2; // 1byte | ||
− | + | //カラーマップ/////////////////////////////// | |
− | + | //カラーマップを使う場合の最初の番号 | |
− | header. | + | header.firstPaletteEntry = 0; // 2byte |
− | + | ||
− | header.depth = 32; | + | //カラーマップを使う場合の総数 |
− | header.descriptor = 0x20; | + | header.numPaletteEntries = 0; // 2byte |
+ | |||
+ | //カラーマップを使う場合の色を表すのに使うビット数。専用カードがある場合でも変わる。 | ||
+ | header.paletteBits = 0; // 1byte | ||
+ | ////////////////////////////////////////////// | ||
+ | |||
+ | //画像原点位置///////////////////////////// | ||
+ | //左下原点としたときの画像開始水平位置 | ||
+ | header.x = 0; // 1byte | ||
+ | |||
+ | //左下原点としたときの画像開始垂直位置 | ||
+ | header.y = 0; // 1byte | ||
+ | ////////////////////////////////////////////// | ||
+ | |||
+ | //画像サイズ/////////////////////////////// | ||
+ | //横幅 | ||
+ | header.width = width; // 2byte | ||
+ | |||
+ | //縦幅 | ||
+ | header.height = height; // 2byte | ||
+ | ////////////////////////////////////////////// | ||
+ | |||
+ | //色表現bit数 | ||
+ | header.depth = 32; // 1byte | ||
+ | |||
+ | //ヘッダ説明部 ビットによって細分化されている | ||
+ | //下位 0~3bit 1ピクセル毎のアルファチャンネルビット数 | ||
+ | //上位 4bit 1なら右から左へ描画 | ||
+ | //上位 5bit 1なら上から下へ描画 | ||
+ | //上位 6~7bit 未使用 通常は00で固定 | ||
+ | header.descriptor = 0x20; // = 0b0010 0000 1byte | ||
file.write((const char*)&header, sizeof(TGAHeader)); | file.write((const char*)&header, sizeof(TGAHeader)); | ||
940行: | 976行: | ||
} | } | ||
} | } | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
} | } | ||
992行: | 1,010行: | ||
//std::ifstream fontFile(argv[1], std::ios::binary); | //std::ifstream fontFile(argv[1], std::ios::binary); | ||
std::ifstream fontFile(filename.toUtf8(), std::ios::binary); | std::ifstream fontFile(filename.toUtf8(), std::ios::binary); | ||
− | if (fontFile) { | + | if (fontFile) { /*フォントファイル名情報処理*/ |
− | fontFile.seekg(0, std::ios::end); | + | fontFile.seekg(0, std::ios::end); //読み取り位置を 最後 に |
− | std::fstream::pos_type fontFileSize = fontFile.tellg(); | + | std::fstream::pos_type fontFileSize = fontFile.tellg(); 現在位置を pos_type型 変数へ格納 |
− | fontFile.seekg(0); | + | fontFile.seekg(0);//読み取り位置を 0 に |
− | unsigned char* fontBuffer = new unsigned char[fontFileSize]; | + | unsigned char* fontBuffer = new unsigned char[fontFileSize]; |
+ | //読み取り位置最後はファイルサイズなので、その大きさのchar型変数配列 fontBufferを作成 | ||
fontFile.read((char*)fontBuffer, fontFileSize); | fontFile.read((char*)fontBuffer, fontFileSize); | ||
+ | //Fontファイルの内容をfontBufferへfontFileSizeの長さだけフォントファイル名取得 | ||
FT_New_Memory_Face(library, fontBuffer, fontFileSize, 0, &face); | FT_New_Memory_Face(library, fontBuffer, fontFileSize, 0, &face); | ||
+ | //読み取ったフォントファイル名情報で&faceを初期化 | ||
WriteGlyphAsTGA(library, "C:/FreeTypeStep03Test.tga"/*QString_TgaFileName.constData()*/, L'B', face, 100, Pixel32(255, 90, 30), Pixel32(255, 255, 255), 3.0f); | WriteGlyphAsTGA(library, "C:/FreeTypeStep03Test.tga"/*QString_TgaFileName.constData()*/, L'B', face, 100, Pixel32(255, 90, 30), Pixel32(255, 255, 255), 3.0f); | ||
1,007行: | 1,028行: | ||
return a.exec(); | return a.exec(); | ||
} | } | ||
+ | </Syntaxhighlight2> | ||
+ | |||
出力結果 | 出力結果 | ||
[[ファイル:FreeTypeStep03Test.jpg|||none|実行結果]] | [[ファイル:FreeTypeStep03Test.jpg|||none|実行結果]] | ||
+ | |||
+ | |||
+ | === ''' サンプル4.公式サイトのErik Mollerさん作成その2 Qt版 ''' === | ||
+ | これは、FreeType2.3.10以降で動作するとされているサンプルで、スタンドアロン B/W(Black/White:白黒:モノトーン)ラスタライザftraster.cの使用方法だそうです。なにが、それほど画期的なモノなのかよくわかっていませんが、フォントファイルを使わずにグラフィックを生成できます。内側と外側の概念があって塗りつぶしもできるようになっています。内側の中に外側だけの図形を描画して、穴をあけたような、図を描画できるかまでは把握していません。文字からの図形にかぎられていたのが、簡単な図形なら、描画できるようになったのが画期的なのかもしれません。 | ||
+ | |||
+ | <Syntaxhighlight2 lang="cpp" line="1"> | ||
+ | #include <QtCore/QCoreApplication> | ||
+ | |||
+ | #include "ftraster.c" | ||
+ | #include "ftraster.h" | ||
+ | #include "ftmisc.h" | ||
+ | #include <fstream> | ||
+ | |||
+ | #include <ft2build.h> | ||
+ | #include FT_FREETYPE_H | ||
+ | |||
+ | #include FT_SYSTEM_H | ||
+ | #include FT_OUTLINE_H | ||
+ | |||
+ | struct Vec2 { | ||
+ | Vec2(float a, float b) : x(a), y(b){} | ||
+ | float x, y; | ||
+ | }; | ||
+ | |||
+ | static Vec2 k_shape[] = { | ||
+ | Vec2(- 3, -18), Vec2( 0, -12), Vec2( 6, -10), Vec2( 12, - 6), Vec2( 12, - 4), | ||
+ | Vec2( 11, - 4), Vec2( 10, - 5), Vec2( 10, 1), Vec2( 9, 6), Vec2( 7, 10), | ||
+ | Vec2( 5, 12), Vec2( 4, 15), Vec2( 3, 14), Vec2( 1, 13), Vec2(- 1, 13), | ||
+ | Vec2(- 5, 11), Vec2(- 8, 8), Vec2(-11, 2), Vec2(-11, - 2), Vec2(-14, 0), | ||
+ | Vec2(-14, - 2), Vec2(-11, - 7), Vec2(- 9, - 9), Vec2(- 8, - 9), Vec2(- 5, -12), | ||
+ | Vec2(- 5, -14), Vec2(- 7, -15), Vec2(- 8, -14), Vec2(- 9, -15), Vec2(- 9, -17), | ||
+ | Vec2(- 7, -17), Vec2(- 6, -18) | ||
+ | }; | ||
+ | |||
+ | void* MY_Alloc_Func(FT_Memory memory, long size){ | ||
+ | return malloc((size_t)size); | ||
+ | } | ||
+ | |||
+ | void MY_Free_Func(FT_Memory memory, void* block) { | ||
+ | free(block); | ||
+ | } | ||
+ | |||
+ | void* MY_Realloc_Func(FT_Memory, long cur_size, long new_size, void* block) { | ||
+ | return realloc(block, (size_t)new_size); | ||
+ | } | ||
+ | |||
+ | static FT_Memory mem; | ||
+ | |||
+ | int main(int argc, char *argv[]) | ||
+ | { | ||
+ | QCoreApplication a(argc, argv); | ||
+ | |||
+ | mem = new FT_MemoryRec_; | ||
+ | mem->alloc = MY_Alloc_Func; | ||
+ | mem->free = MY_Free_Func; | ||
+ | mem->realloc = MY_Realloc_Func; | ||
+ | |||
+ | FT_Outline_ outline; | ||
+ | outline.n_contours = 1; | ||
+ | outline.n_points = sizeof(k_shape) / sizeof(Vec2); | ||
+ | outline.points = new FT_Vector[outline.n_points]; | ||
+ | |||
+ | for (int i = 0; i < outline.n_points; ++i) { | ||
+ | FT_Vector v; | ||
+ | v.x = (20 + k_shape[i].x) * 10 * 64; | ||
+ | v.y = (20 + k_shape[i].y) * 10 * 64; | ||
+ | outline.points[i] = v; | ||
+ | } | ||
+ | outline.tags = new char[outline.n_points]; | ||
+ | for (int i = 0; i < outline.n_points; ++i) { | ||
+ | outline.tags[i] = 1; | ||
+ | } | ||
+ | outline.contours = new short[outline.n_contours]; | ||
+ | outline.contours[0] = outline.n_points - 1; | ||
+ | outline.flags = 0; | ||
+ | |||
+ | const int width = 500; | ||
+ | const int rows = 400; | ||
+ | |||
+ | const int pitch_mono = (width + 7) >> 3; | ||
+ | |||
+ | FT_Bitmap bmp; | ||
+ | FT_Raster_Params params; | ||
+ | |||
+ | const int kRenderPoolSize = 1024 * 1024; | ||
+ | unsigned char* renderPool = new unsigned char[kRenderPoolSize]; | ||
+ | |||
+ | bmp.buffer = new unsigned char[rows * pitch_mono]; | ||
+ | memset(bmp.buffer, 0, rows * pitch_mono); | ||
+ | bmp.width = width; | ||
+ | bmp.rows = rows; | ||
+ | bmp.pitch = pitch_mono; | ||
+ | bmp.pixel_mode = FT_PIXEL_MODE_MONO; | ||
+ | |||
+ | memset(¶ms, 0, sizeof(params)); | ||
+ | params.source = &outline; | ||
+ | params.target = &bmp; | ||
+ | |||
+ | FT_Raster raster; | ||
+ | |||
+ | ft_standard_raster.raster_new(mem, &raster); | ||
+ | ft_standard_raster.raster_reset(raster, renderPool, kRenderPoolSize); | ||
+ | ft_standard_raster.raster_render(raster, ¶ms); | ||
+ | |||
+ | std::ofstream out_mono("out-mono.pbm", std::ios::binary); | ||
+ | out_mono << "P4 " << width << " " << rows << "\n"; | ||
+ | out_mono.write((const char*)bmp.buffer, rows * pitch_mono); | ||
+ | |||
+ | delete[] renderPool; | ||
+ | delete[] bmp.buffer; | ||
+ | delete[] outline.points; | ||
+ | delete[] outline.tags; | ||
+ | delete[] outline.contours; | ||
+ | delete mem; | ||
+ | |||
+ | return a.exec(); | ||
+ | } | ||
+ | |||
+ | </Syntaxhighlight2> | ||
+ | |||
+ | |||
+ | 実行結果 | ||
+ | |||
+ | [[ファイル:Out-mono.bmp|||none|実行結果]] | ||
+ | |||
+ | |||
+ | 上記のようなプログラムで更に、いろいろと必要なプログラムをつまびかないと動作させられませんでした。 | ||
+ | |||
+ | プロジェクトにftimege.hとftmisc.hとfutil.cをライブラリディレクトリからコピーしてmain.cppと同じフォルダに追加。 | ||
+ | |||
+ | プロジェクトのフォルダの中にftraster.cをコピーする必要があり、このファイルはインクルードすることでコンパリンの対象になります。そして、以下のインクルードをftraster.cの中に記載。 | ||
+ | |||
+ | #include "C:\xxx\xxx\xxx\xxx\freetype-2.9.1\include\freetype\internal\internal.h" | ||
+ | #include "C:\xxx\xxx\xxx\xxx\freetype-2.9.1\include\freetype\internal\ftdebug.h" | ||
+ | #include "C:\xxx\xxx\xxx\xxx\freetype-2.9.1\include\freetype\internal\ftcalc.h" | ||
+ | |||
+ | 編集するファイルはすべて、プロジェクト側のディレクトリに移動します。本格的にB/Wラスタライズ機能を繰り返し使う場合は、もっと構成を簡単に綺麗にした方がよいでしょう。自分は無理やりでも動かしてやるということに集中しました。正直、なんにも文献もないのに、全貌を読み解くのは面倒です。簡単には使えないということを理解しました。美しい使い方については、自分もスタンドアロンB/Wラスタライズを使うときに考えたいと思います。 | ||
+ | |||
+ | FT_THROWは存在しない構文としてエラーになって面倒だったので、消しました。例外エラーがでたときにわかりやすくなる命令ではありますが、なくても問題ない。return FT_THROW(... となっている場合はreturn 1;に置き換え。if文の中に1行だけある場合は、int ii = 1; のような疑似プログラムを追加。 | ||
+ | |||
+ | 取り込んだftmisc.hも | ||
+ | |||
+ | <Syntaxhighlight2 lang="cpp" line="1"> | ||
+ | /* | ||
+ | typedef struct FT_MemoryRec_ | ||
+ | { | ||
+ | void* user; | ||
+ | |||
+ | FT_Alloc_Func alloc; | ||
+ | FT_Free_Func free; | ||
+ | FT_Realloc_Func realloc; | ||
+ | |||
+ | } FT_MemoryRec; | ||
+ | */ | ||
+ | </Syntaxhighlight2> | ||
+ | |||
+ | の部分をコメントアウトが必要になります。重複する部分になります。 | ||
+ | |||
+ | ftobjs.hも | ||
+ | |||
+ | <Syntaxhighlight2 lang="cpp" line="1"> | ||
+ | #define FT_INTERNAL_MEMORY_H <freetype/internal/ftmemory.h> | ||
+ | #include FT_INTERNAL_MEMORY_H | ||
+ | #define FT_INTERNAL_GLYPH_LOADER_H <freetype/internal/ftgloadr.h> | ||
+ | #include FT_INTERNAL_GLYPH_LOADER_H | ||
+ | #define FT_INTERNAL_DRIVER_H <freetype/internal/ftdrv.h> | ||
+ | #include FT_INTERNAL_DRIVER_H | ||
+ | #define FT_INTERNAL_AUTOHINT_H <freetype/internal/autohint.h> | ||
+ | #include FT_INTERNAL_AUTOHINT_H | ||
+ | #define FT_INTERNAL_SERVICE_H <freetype/internal/ftserv.h> | ||
+ | #include FT_INTERNAL_SERVICE_H | ||
+ | #define FT_INTERNAL_PIC_H <freetype/internal/ftpic.h> | ||
+ | #include FT_INTERNAL_PIC_H | ||
+ | #define FT_INTERNAL_CALC_H <freetype/internal/ftcalc.h> | ||
+ | #include FT_INTERNAL_CALC_H | ||
+ | </Syntaxhighlight2> | ||
+ | |||
+ | #defineの行を追加しなければならないところが発生しました。 | ||
+ | |||
+ | |||
+ | ftutil.hも | ||
+ | |||
+ | <Syntaxhighlight2 lang="cpp" line="1"> | ||
+ | #include "C:\xxx\xxx\xxx\xxx\freetype-2.9.1\include\freetype\internal\ftdebug.h" | ||
+ | #include "C:\xxx\xxx\xxx\xxx\freetype-2.9.1\include\freetype\internal\ftmemory.h" | ||
+ | #include ".\ftobjs.h" | ||
+ | </Syntaxhighlight2> | ||
+ | |||
+ | のように編集が必要なヘッダファイルはプロジェクト内のものを相対パス参照で、編集が不要でインクルードが必要なモノはライブラリのものを読み込みます。絶対パスでxxxになっているところは、各自の環境に合わせてください。 | ||
+ | |||
+ | |||
+ | これで編集が必要だった箇所を全部を書いたかはわかりませんが、基本的にはやることは同じです。コンパリンエラーになった結果にもとづいて、定義が見つからない場合は、必要なインクルードファイルを追記。リンクエラーになる場合は宣言しか読み込めていないってことなので、プログラムファイルを探して読み込みという感じの作業をします。ファイルの編集が必要な場合はライブラリのものは触らず、ファイルを自分のプロジェクトにコピーしてきて、それを使うように読み込みする仕組みにしたうえで編集をします。 | ||
+ | |||
+ | |||
+ | こういう作業をすることは勉強にはなりません。これを当たり前のようにこなしてから、どうすることが正しかったのかを考えることが大事です。でも今回は管理人には必要ない知識なので、ここではやりません。人が作ったものを使いこなすのは大変だということです。ユーザ数が少ない。文献がすくなければなおさら面倒です。必要に駆られるまで手は出さない。動けばいいな。程度。1週間やってもだめなら諦めますね。パズルと同じです。 | ||
+ | |||
+ | |||
+ | === ''' サンプル5.公式サイトのRóbert Márkiさん作成 ''' === | ||
+ | Róbertが作成したQtプロジェクトでのサンプルですが、プロジェクトファイルが配布されているわけでもないので、再現するのにはどうすればいいのかが、よくわからなかったので、自分でイチカラQt Guiプロジェクト作りこみながら、再現してみました。qmakeファイル配布されてましたけど、何をどうしろというのかというような内容でして、ここまで来て思ったんですけど、FreeTypeのサンプル集はどれも冷たい。どうやってビルドするべきかを環境とツールごとに分けて記述しているプロジェクトもある昨今なのに、ソースファイルとよくわからない*.proファイルの配布。冷たい。 | ||
+ | |||
+ | |||
+ | 不満を並べてもしょうがないので、ビルド手順を簡単に。 | ||
+ | |||
+ | 1.VisualStudioでQt Wigets Applicationの新規作成を始めます。 | ||
+ | |||
+ | 2.今回の場合Wizardがいろいろと分類してくれるのは、ありがたくないのですが、Wizardのいうとおりに作るので、BaseClassを指定するところだけQMainWindowではなく、QWidgetに変更します。あとは規定値でよいです。 | ||
+ | |||
+ | 3.したらばプログラムを書きます。分割プログラムの形式なのでやや複雑ですが、以下の通り記述します。 | ||
+ | |||
+ | まずmain.cpp | ||
+ | |||
+ | <Syntaxhighlight2 lang="cpp" line start="1"> | ||
+ | #include "QtSampleProject.h" | ||
+ | #include <QtWidgets/QApplication> | ||
+ | |||
+ | #include <QPainter> | ||
+ | #include <QFile> | ||
+ | |||
+ | #include <iostream> | ||
+ | |||
+ | #include <ft2build.h> | ||
+ | #include FT_FREETYPE_H | ||
+ | #include FT_OUTLINE_H | ||
+ | |||
+ | QString g_usageText = | ||
+ | "usage:\n" | ||
+ | "example4 FONT_PATH CHARACTER SIZE DIRECT_RENDERING_MODE(1|0)"; | ||
+ | |||
+ | int main(int argc, char *argv[]) | ||
+ | { | ||
+ | bool status = false; | ||
+ | bool isSizeOk = false; | ||
+ | QString path = "C:\\Windows\\Fonts\\BIZ-UDGothicR.ttc"; | ||
+ | QChar character = 'm'; | ||
+ | int size = QString("60").toInt(&isSizeOk); | ||
+ | bool directRender = QString("1").toInt(); | ||
+ | |||
+ | if (QFile::exists(path) && isSizeOk) { | ||
+ | status = true; | ||
+ | QApplication a(argc, argv); | ||
+ | QtSampleProject w(path, character, size, directRender); | ||
+ | w.show(); | ||
+ | return a.exec(); | ||
+ | } | ||
+ | if (!status) { | ||
+ | std::cout << qPrintable(g_usageText) << std::endl; | ||
+ | return 0; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | </Syntaxhighlight2> | ||
+ | |||
+ | |||
+ | プロジェクト名.cpp ここでは仮にQtSampleProjectとしておきます。 | ||
+ | |||
+ | <Syntaxhighlight2 lang="cpp" line start="1"> | ||
+ | #include "QtSampleProject.h" | ||
+ | |||
+ | QtSampleProject::QtSampleProject(QWidget *parent) | ||
+ | : QWidget(parent) | ||
+ | { | ||
+ | m_face = 0; | ||
+ | m_library = 0; | ||
+ | m_directRender = 0; | ||
+ | ui.setupUi(this); | ||
+ | } | ||
+ | QtSampleProject::QtSampleProject(const QString& fileName, QChar character, int pointSize, bool directRender, QWidget* parent) : QWidget(parent), m_directRender(directRender) | ||
+ | { | ||
+ | FT_Error error = FT_Err_Ok; | ||
+ | m_face = 0; | ||
+ | m_library = 0; | ||
+ | error = FT_Init_FreeType(&m_library); | ||
+ | if(!error){ | ||
+ | error = FT_New_Face(m_library, fileName.toLatin1().constData(), 0, &m_face); | ||
+ | |||
+ | if (!error) { | ||
+ | error = FT_Set_Char_Size(m_face, 0, pointSize * 64, physicalDpiX(), physicalDpiY()); | ||
+ | if (!error) { | ||
+ | FT_UInt glyph_index = 0; | ||
+ | glyph_index = FT_Get_Char_Index(m_face, character.unicode()); | ||
+ | error = FT_Load_Glyph(m_face, glyph_index, FT_LOAD_DEFAULT); | ||
+ | if (!error) { | ||
+ | FT_Pos left = m_face->glyph->metrics.horiBearingX; | ||
+ | FT_Pos right = left + m_face->glyph->metrics.width; | ||
+ | FT_Pos top = m_face->glyph->metrics.horiBearingY; | ||
+ | FT_Pos bottom = top - m_face->glyph->metrics.height; | ||
+ | |||
+ | m_glyphRect = QRect(QPoint(TRUNC(left), | ||
+ | -TRUNC(top) + 1), | ||
+ | QSize (TRUNC(right - left) + 1, | ||
+ | TRUNC(top - bottom) + 1) | ||
+ | ); | ||
+ | setFixedSize(m_glyphRect.width(), m_glyphRect.height()); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | ui.setupUi(this); | ||
+ | } | ||
+ | QtSampleProject::~QtSampleProject() | ||
+ | { | ||
+ | if(m_face){ | ||
+ | FT_Done_Face(m_face); | ||
+ | } | ||
+ | if(m_library){ | ||
+ | FT_Done_FreeType(m_library); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | void QtSampleProject::graySpans(int y, int count, const FT_Span_* spans, void* user) { | ||
+ | QPainter* painter = (QPainter*)user; | ||
+ | y = -y; | ||
+ | |||
+ | for (int i = 0; i < count; i++) { | ||
+ | const FT_Span span = spans[i]; | ||
+ | qreal opacity = qreal(span.coverage) / 255.0; | ||
+ | |||
+ | painter->setOpacity(opacity); | ||
+ | |||
+ | if (span.len > 1) { | ||
+ | painter->drawLine(span.x, y, span.x + span.len - 1, y); | ||
+ | } | ||
+ | else { | ||
+ | painter->drawPoint(span.x, y); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | void QtSampleProject::paintEvent(QPaintEvent* event) { | ||
+ | QWidget::paintEvent(event); | ||
+ | |||
+ | if (m_library && m_face) { | ||
+ | FT_Error error = FT_Err_Ok; | ||
+ | QPainter painter(this); | ||
+ | painter.translate(-m_glyphRect.x(), -m_glyphRect.y()); | ||
+ | if (m_directRender) { | ||
+ | painter.setPen(Qt::black); | ||
+ | FT_Raster_Params params; | ||
+ | |||
+ | params.target = 0; | ||
+ | params.flags = FT_RASTER_FLAG_DIRECT | FT_RASTER_FLAG_AA; | ||
+ | params.user = &painter; | ||
+ | params.gray_spans = &QtSampleProject::graySpans; | ||
+ | params.black_spans = 0; | ||
+ | params.bit_set = 0; | ||
+ | params.bit_test = 0; | ||
+ | |||
+ | FT_Outline* outline = &m_face->glyph->outline; | ||
+ | FT_Outline_Render(m_library, outline, ¶ms); | ||
+ | } | ||
+ | else { | ||
+ | error = FT_Render_Glyph(m_face->glyph, FT_RENDER_MODE_NORMAL); | ||
+ | QImage glyphImage(m_face->glyph->bitmap.buffer, | ||
+ | m_face->glyph->bitmap.width, | ||
+ | m_face->glyph->bitmap.rows, | ||
+ | m_face->glyph->bitmap.pitch, | ||
+ | QImage::Format_Indexed8); | ||
+ | painter.translate(m_glyphRect.x(), m_glyphRect.y()); | ||
+ | |||
+ | QVector<QRgb> colorTable; | ||
+ | for (int i = 0; i < 256; ++i) { | ||
+ | colorTable << qRgba(0, 0, 0, i); | ||
+ | } | ||
+ | glyphImage.setColorTable(colorTable); | ||
+ | painter.drawImage(QPoint(0, 0), glyphImage); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </Syntaxhighlight2> | ||
+ | |||
+ | プロジェクト名.h | ||
+ | |||
+ | <Syntaxhighlight2 lang="cpp" line start="1"> | ||
+ | #pragma once | ||
+ | |||
+ | #include <QtWidgets/QWidget> | ||
+ | #include "ui_QtSampleProject.h" | ||
+ | |||
+ | #include <QPainter> | ||
+ | #include <QFile> | ||
+ | |||
+ | #include <iostream> | ||
+ | |||
+ | #include <ft2build.h> | ||
+ | #include FT_FREETYPE_H | ||
+ | #include FT_OUTLINE_H | ||
+ | |||
+ | #define TRUNC(x) ((x) >> 6) | ||
+ | |||
+ | class QtSampleProject : public QWidget | ||
+ | { | ||
+ | Q_OBJECT | ||
+ | |||
+ | public: | ||
+ | QtSampleProject(QWidget* parent = nullptr); | ||
+ | QtSampleProject(const QString& fileName, QChar character, int pointSize, bool directRender, QWidget* parent = 0); | ||
+ | ~QtSampleProject(); | ||
+ | |||
+ | private: | ||
+ | bool m_directRender = 0; | ||
+ | FT_Library m_library = 0; | ||
+ | FT_Face m_face = 0; | ||
+ | QRect m_glyphRect; | ||
+ | |||
+ | static void graySpans(int y, int count, const FT_Span_* spans, void* user); | ||
+ | Ui::QtSampleProjectClass ui; | ||
+ | |||
+ | protected: | ||
+ | void paintEvent(QPaintEvent* event); | ||
+ | }; | ||
+ | |||
+ | </Syntaxhighlight2> | ||
+ | |||
+ | 実行結果 | ||
+ | |||
+ | [[ファイル:QtConsoleFreeTypeStep07.png|||none|実行結果]] | ||
+ | |||
+ | |||
+ | ウィンドウっちっさ!サンプルの通り動かしたらコレなので、自信を持ってお届けする結果です。main.cppの中のmain関数の最初の方に利用するフォント名を設定する箇所、レンダリングに使う文字、文字の大きさ、directRenderとそうじゃない描画のどちらを使うかを決める0 or 1を指定する文字列で結果が、もう少し変わります。このサンプルをもうちょっと面白くするならこの手前でやったようなサンプルのように文字列を与える形式にするとウィンドウがもう少し大きくなって楽しいかもしれません。あるいは変形とかね。 | ||
+ | |||
+ | |||
+ | === ''' サンプル6.公式サイトのStatic Jobs LLC作成 ''' === | ||
+ | Static Jobs LLCという組織によって作られたサンプルがありますが、こちらは、ほぼ変更する必要もなく出力結果のSVGのXMLが標準出力に描画されます。単位はemですが、htmlに単純に貼りつけるとpixelとして扱われます。ものすごく大きい描画になりますが、具体的に活用するときになるまでに、これを編集すればいいことなので、大きな問題ではないです。 | ||
+ | |||
+ | |||
+ | このプログラムのSVG Printで拡大率を縮小する方向にするため ビューポート情報を追加すると簡単に縮小された結果を得ることができると思います。そうやってUD BIZゴシックについてグリフQを書き出したも結果は以下のようになります。 | ||
+ | |||
+ | <Syntaxhighlight2 lang="xml"> | ||
+ | <svg xmlns='http://www.w3.org/2000/svg' | ||
+ | xmlns:xlink='http://www.w3.org/1999/xlink' | ||
+ | width='9px' height='17px' viewBox='63 -1640 922 1796'> | ||
+ | <path d=' | ||
+ | M 901 156 | ||
+ | Q 812 61, 745 -49 | ||
+ | Q 647 84, 502 84 | ||
+ | Q 303 84, 183 -152 | ||
+ | Q 63 -389, 63 -783 | ||
+ | Q 63 -1179, 179 -1408 | ||
+ | Q 295 -1640, 502 -1640 | ||
+ | Q 704 -1640, 824 -1407 | ||
+ | Q 946 -1171, 946 -783 | ||
+ | Q 946 -414, 839 -166 | ||
+ | Q 890 -73, 985 12 | ||
+ | L 901 156 | ||
+ | M 501 -563 | ||
+ | Q 609 -448, 718 -295 | ||
+ | Q 772 -456, 772 -772 | ||
+ | Q 772 -1109, 699 -1299 | ||
+ | Q 627 -1486, 504 -1486 | ||
+ | Q 393 -1486, 322 -1330 | ||
+ | Q 235 -1138, 235 -781 | ||
+ | Q 235 -445, 311 -253 | ||
+ | Q 385 -66, 507 -66 | ||
+ | Q 597 -66, 647 -162 | ||
+ | Q 540 -315, 423 -438 | ||
+ | L 501 -563 | ||
+ | ' | ||
+ | fill='red'/> | ||
+ | </svg> | ||
+ | </Syntaxhighlight2> | ||
+ | |||
+ | |||
+ | このようにフォントの描画を行うためのベジェ曲線の通過座標とその補助座標といった情報に分解されたような情報を得ることができます。Mではじまるのは、そのあとに書かれた座標への移動。Qはそのあとに書かれた2つ座標について、現在の始点は前回の移動点であるとして、1つの制御点と1つの終点をもっています。始点と終点を結んだ直線に対して、制御点が曲線の膨らみや凹みを表します。こういうものを2次ベジェと呼んでいます。3次ベジェはCで始まります。Lは、前回の移動点を始点として、1つの指定された座標を終点とする直線を描画します。詳しくはSVGについての記事を読む必要があると思います。フォント情報はほぼ、このM,L,Q,Cで構成されています。 | ||
+ | |||
+ | |||
+ | |||
+ | これまで見てきたようにFreeTypeはオープンソースでありながらフォント情報から、グラフィックに描画するための情報を抜き出して、かつ、ビットマップにレンダリングする機能。単独で図形を描画する機能。フォントファイルに埋め込まれたベジェ曲線を描くのに必要な情報の抜き出しが出来ました。 | ||
+ | |||
+ | |||
+ | 文字コードに対応するグリフ番号を取得する命令も保有しています。フォント情報を編集することはできませんが、もうひとつのフォントファイルを再構築するような作業もやれなくはない感じがします。プログラムによってテキストや独自の形式で使われた文字コードを蓄積し、そのすべてのグリフ番号を取得することができます。その使われたフォント情報だけを抜き出して再構築するような作業もやれそうですが、freetypeだけでやれるのか。もう少し見ていく必要はありそうです。 | ||
+ | |||
+ | |||
+ | freetypeは文献が少ないですが、普段、われわれが使ってるようなソフトウェアでも使われていたりと実績はかなりあるようです。FreeTypeを使ってPDFに必要な文字サブセット作成とグリフ番号取得への道のりを考えていましたが、もっと発展的に使えばフォントファイルをプログラムによってすべてを一定の手法によって改造した文字を作ることも可能です。角の処理だけを変えたりですね。大量の文字をプログラムによって編集したものを新たなフォントにできるのであれば、手軽でありながら画期的です。 | ||
+ | |||
+ | |||
[[フォント TrueType 構造解析]]に戻る。 | [[フォント TrueType 構造解析]]に戻る。 |