FreeTypeを使うのソースを表示
新しいページはコチラ
移動:
案内
,
検索
[[フォント TrueType 構造解析]]に戻る。 == '''概要''' == この記事はFreeTypeを使うであって、FreeTypeをまとめる記事ではないです。なので遊び場程度の記事になります。遊べるかは謎です。Font情報をどれくらい操作できるのか試してみるところです。目標はPDFを作るためのグリフ番号取得と、フォントプログラムの再構成。サブセットフォントを作るということです。 そこまですることなんか?とは思っています。フォントの構造を知ることに重きを置きながらがんばります。 まずはコンソールプログラムでチュートリアルっぽいことを。 ってチュートリアルのとおり、作り始めたらQt64bitコンソールアプリケーションを作ってるせいで、64bitのライブラリが必要になってしまった。開発時にダイナミックリンクを使うと勉強の質が低下してしまうので、64bit32bit混在で動かすわけにもいかず、似非64bitのfreetypeライブラリを作成するために、libpngで似非64bitプラットフォームを作成した。似非というのは、何もプログラムを変更しないで、ただ64bit宣言するだけのことです。bit演算が凄まじいアプリケーション群なので、これが原因でアドレスの使い方が変わってバグる可能性はあるが、莫大なコードを目の前に、全てを潰していくのはサハラ砂漠にダイアモンドを埋めたのを探すようなモノ。無謀な道とは知りつつ、しばらく、これで突き進む。 ちな 似非を作るには[[VC PlusPlus:似非64bitプラットフォームの追加|コチラ]]の手順として記載。 Qtコンソールプログラムの書き始めは以下のような状態。Qtについては、Qt導入の記事をみて下さい。なんでQtなん?って思う人いるとおもいますけど、さほどQtの要素は使わないので、安心して下さい。Qtやってるっていう見せかけですよ。 <Syntaxhighlight2 lang="cpp"> #include <QtCore/QCoreApplication> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); return a.exec(); } </Syntaxhighlight2> 自分は64bit版ですが、64bit版を生成するのが面倒な人はQtの32bit版のLibraryを導入して新規作成するとよいね。Qtの64bit版を使ってるつもりで説明します。ところどころ32bit版の人向けのコメントも添えます。 まずはプロジェクトの設定を変えて、freetypeが使える状態にします。 インクルードファイルのありかを設定します。ライブラリを使うときはまずはヘッダファイルで関数の全ての情報が必要です。標準関数でさえ#include <stdio.h>って設定するもんね。同じこと。 ソリューション構成をDebug、ソリューションプラットフォームをx64<span>(</span>32bitに人はWin32<span>)</span>に設定します。ツールバーのリストを選ぶところですね。次に、メニューの[プロジェクト]-[プロパティ]を選択します。 [VC++ディレクトリ]の項目の中の[外部インクルードディレクトリ]に以下を追加します。 C:\…\…\…\…\freetype-2.9.1\include [VC++ディレクトリ]の項目の中の[ライブラリディレクトリ]に以下を追加します。 C:\…\…\…\…\freetype-2.9.1\objs\x64\Debug 絶対パスなので、…の部分はそれぞれの、freetypeの配置した場所に、freetype-2.9.1となっているところもそれぞれのバージョン番号だったり、名前を変えてfreetypeにしたりしてる場合も合わせて下さい。64bitの場合はx64ですが、32bitのQtアプリの場合はx64ではなくWin32です。 構成のRelease版も合わせて変更しておくとよいでしょう。インクルードディレクトリは同じで、ライブラリディレクトリはDebugがReleaseに代わります。 実際にデバッグでもなんでもテストでアプリを動かす時は、ダイナミックリンクライブラリを使うので、dllをがうごかせるように実行ファイルと同じディレクトリ置くとか、環境変数のPathの参照可能な範囲に配置する指定をしないと駄目です。プロジェクトごとにどの環境で動かすのかを見極めて環境変数の設定しないとだめです。プログラマなら実行ファイルが完成するまではDebug版のダイナミックリンクライブラリを使うはずなので、環境変数Pathにfreetype.libのx64 <span>(</span>Win32をQtアプリの人はWin32<span>)</span> のDebug版にPathを設定しましょう。 コンパネのシステムの右側の詳細設定から詳細。環境変数の中のPathに C:¥…\…\…\…\…\freetype-2.9.1\objs\x64\Debug を追加しましょう。コマンドプロンプトでwhere %path% freetype.dll とすると、フルパスが帰ってきたら、パスが通っていて、freetype.dllがどの実行ファイルからも参照できるようになっていることを意味します。フルパスが表示されず。また次のプロンプトだけが表示された場合は失敗しています。もう一度確認しましょう。デバッグ押しても、もちろんアプリを起動できません。 そうすると、うまく設定できたかを確かめるために、少しだけプログラムを記述してビルドして確かめてみます。 <Syntaxhighlight2 lang="cpp" line=1> #include <QtCore/QCoreApplication> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); FT_Library library; int error; error = FT_Init_FreeType(&library); if (error) { fprintf(stderr, qPrintable("")); exit(1); } return a.exec(); } </Syntaxhighlight2> 6行目から14行目を追加しました。これで一度ビルド Ctrl+Shift+B します。ちゃんと動くか確かめるためです。もちろん管理人はスーパープログラマなので、うまく動かせました。え。なにがスーパーやねん。わかります。へっぽこでした。 うまくいくと例の安心の出力になります。この正常終了って文字。プログラマにとってはアドレナリン・ドーパミン?だっけがでますね。 <Syntaxhighlight2 lang="text"> ビルドを開始しました... 1>------ ビルド開始: プロジェクト: QtConsole###########, 構成: Debug x64 ------ 1>QtConsole###########.vcxproj -> C:\…\…\…\…\QtConsole###########\x64\Debug\QtConsole###########.exe ========== ビルド: 1 正常終了、0 失敗、0 更新不要、0 スキップ ========== </Syntaxhighlight2> === '''サンプル1.Qt ConsoleApplicationでテキスト出力 ''' === ここまできたらFreetypeを使える状態になったと言えます。いよいよ使っていきましょう。ところでfreetypeって、どういうことができるのか、 ひとことで言えば、フォントから文字を描画するのに必要な情報を取り出す作業ができる。どんな情報?っていうのが詳しい説明になるわけですが、 *フォントデータからプログラマが指定したフォントサイズ<span>(</span>ポイント<span>)</span>をもとに、指定したグリフ番号のビットマップモノトーン値をピクセル毎に取得できます。 :なかなかえぐい計算量ですが、まぁ最近のPCの早いこと。しゅっとフォントがビットマップになります。 *前項のような作業のために文字コード番号から標準のグリフを取得したり、文字コード+付帯情報でその関連グリフの番号を取得できる。 :つまり、PDFでやりたかった文字コードからグリフ番号を取得する作業ができるということです。このライブラリを使えば目的は一つ達成できる。 これが最初のチュートリアルで紹介するサンプルの機能です。公式サイトにはQtでのサンプルもありますので、管理人と同じくQtで頑張っている人と共に勉強していけるはずです。 <Syntaxhighlight2 lang="cpp" line=1> #include <QtCore/QCoreApplication> #include <QString> #include <QFile> #include <QTextStream> #include <QImage> #include <QColor> #include <QSize> #include <ft2build.h> #include FT_FREETYPE_H static unsigned int text[] = { 0x00003042, 0x00003044, 0x00003046, 0x00003048, //あ、い、う、え 0x0000304a, 0x0000304b, 0x0000304d, 0x0000304f, //お、か、き、く 0x00003051, 0x00003053, 0x00003055, 0x00003057, //け、こ、さ、し 0x00003059, 0x0000305b, 0x0000305d, 0x0000305f, //す、せ、そ、た 0x00003061, 0x00003064, 0x00003066, 0x00003068, //ち、つ、て、と 0x0000000a, //改行 0x00000061, 0x00000062, 0x00000063, 0x00000064, //a、b、c、d 0x00000065, 0x00000066, 0x00000067, 0x00000068, //e、f、g、h 0x00000069, 0x0000006a, 0x0000006b, 0x0000006c, //i、j、k、l 0x0000006d, 0x0000006e, 0x0000006f, 0x00000070, //m、n、o、p 0x00000071, 0x00000072, 0x00000073, 0x00000074, //q、r、s、t 0x00000075, 0x00000076, 0x00000077, 0x00000078, //u、v、w、x 0x00000079, 0x0000007a, //y, z 0x00000061, 0x00000062, 0x00000063, 0x00000064, //a、b、c、d 0x00000065, 0x00000066, 0x00000067, 0x00000068, //e、f、g、h 0x00000069, 0x0000006a, 0x0000006b, 0x0000006c, //i、j、k、l 0x0000006d, 0x0000006e, 0x0000006f, 0x00000070, //m、n、 0x0000000a, //改行 }; #define LEN (sizeof text / sizeof text[0]) #define WIDTH 1024 #define HEIGHT 256 #define LEFT 16 #define TOP 32 static unsigned char canvas[HEIGHT][WIDTH]; static void draw(FT_Bitmap* bitmap, int x, int y) { for (int j = 0; j < bitmap->rows; j++) { for (int i = 0; i < bitmap->width; i++) { unsigned char c = bitmap->buffer[j * bitmap->pitch + i]; if (c) { if (y + j >= 0 && y + j < HEIGHT) { if (x + i >= 0 && x + i < WIDTH) { canvas[y + j][x + i] = c; } } } } } } int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); QString QString_FileName = "C:/FreeTypeStep01Test.txt"; QString QString_ImageFileName = "C:/FreeTypeStep01Test.bmp"; QFile QFile_Text(QString_FileName); QString QString_ImageRowMonoBuffer = ""; QSize QSize_xy(WIDTH, HEIGHT); QImage QImage_Canvas(QSize_xy, QImage::Format_ARGB32_Premultiplied);//空イメージ FT_Library library; int error; error = FT_Init_FreeType(&library); if (error) { fprintf(stderr, qPrintable("ft init error\n")); exit(1); } FT_Face face; error = FT_New_Face(library, qPrintable("C:\\Windows\\Fonts\\KozGoPr6N-Medium.otf"), 0, &face); if (error) { fprintf(stderr, qPrintable("new face error\n")); exit(1); } error = FT_Set_Char_Size(face, 0, 8 * 64, 300, 300); if (error) { fprintf(stderr, qPrintable("set size error\n")); exit(1); } #define LEFT64(x) ((x) << 6) // 1/64が1の 32 bit 26.6 ニィロク ロク固定小数。 #define RIGHT64(x) ((x) >> 6) FT_Vector pen; pen.x = LEFT64(LEFT); pen.y = LEFT64(TOP); unsigned int idces[LEN]; for (int i = 0; i < LEN; i++) { if (text[i] == '\n') { fprintf(stderr, qPrintable("%ld\n"), pen.x); pen.x = LEFT64(LEFT); pen.y += LEFT64(48); continue; } idces[i] = FT_Get_Char_Index(face, text[i]); error = FT_Load_Glyph(face, idces[i], FT_LOAD_DEFAULT); if (error) { fprintf(stderr, qPrintable("no glyph error/\n")); exit(1); } error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); if (error) { fprintf(stderr, qPrintable("glyph render error\n")); exit(1); } draw(&face->glyph->bitmap, RIGHT64(pen.x) + face->glyph->bitmap_left, RIGHT64(pen.y) + face->glyph->bitmap_top); pen.x += face->glyph->advance.x; } fprintf(stderr, qPrintable("\n")); for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { QImage_Canvas.setPixel(x, y, qRgb(canvas[y][x], canvas[y][x], canvas[y][x])); } } QImage_Canvas.save(QString_ImageFileName, "BMP"); printf(qPrintable("P3\n%d %d\n%d\n"), WIDTH, HEIGHT, 255); if (QFile_Text.open(QIODevice::WriteOnly)) { QTextStream out(&QFile_Text); for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { if (canvas[y][x]) { QString_ImageRowMonoBuffer = QString_ImageRowMonoBuffer + " 1"; //printf(qPrintable("%2d"), 1); } else { QString_ImageRowMonoBuffer = QString_ImageRowMonoBuffer + " 0"; //printf(qPrintable("%2d"), 0); } } QString_ImageRowMonoBuffer = QString_ImageRowMonoBuffer + "\n"; //printf(qPrintable("\n")); out << QString_ImageRowMonoBuffer; QString_ImageRowMonoBuffer = ""; } QFile_Text.close(); } else { QString QString_Error(qPrintable("No Open or Create Error\n")); QString_Error = QString_Error + QFile_Text.errorString(); fprintf(stderr, qPrintable(QString_Error)); } return a.exec(); } </Syntaxhighlight2> テキストファイルと画像を出力するQtアプリケーションfreetype連動プログラムになっています。 生成される画像 [[ファイル:FreeTypeStep01Test.bmp|400px|thumb|none|実行結果]] 文字列は白黒を1ビットづつ見やすいように出力したものです。画像の方はNormalRenderingのアンチエイリアスがかかっていますので少し綺麗。文字が上にいったり下にいったりしてるのは、Adobe AcrobatReaderの小塚ゴシックを参照したため。この仕組みで、違反者をさがしてるのかな。それともだいたいのフォントがこんな風にずれるようになってるのか。気になる。そのまえに、FreeTypeの基本について説明していかないとな。 ついでにQtの勉強にもなるという。いいね。このSiteは、いいねボタンないけどね。 FreeTypeの勉強っていうけど、一般の人からしたら、だから何がうれしいのっていうようなプログラムですね。日々、目にしているグラフィックツールのテキストレンダリングは、これぐらい膨大なプログラミングによって実現されているっていうね。テレビや動画や音楽もしかり、実はものすごい技術です。LEDで文字が表示されてる看板とかね。この形の文字になるように、だれかが作ってんだよって言ってやりたいね。手で書いてるのとは訳が違う。そういえば、コンピュータのごとく手で書いちゃう人もいましたね。あれはあれであれのほうが凄い。コンピュータは誰かが苦労して書いたやつをみんなで再利用してるという効率の良さ。原理を知らない人ばかりになるとどうなるんだこの世界。 === '''サンプル1.Qt ConsoleApplicationでテキスト画像表示の解説 ''' === プログラム最初らへんのQで始まるヘッダファイル、Qt 関連のインクルード挿入。割と最小構成でインクルードする手間のかかる指定をする癖があります。それほど無駄がないと確信したら、巨大なインクルードで済ませることもありますが、こういう巨大なライブラリだと延々と理解できないので、無駄が多くなりがち。以下のように対応しています。 *<QtCore/QCoreApplication> : QCoreApplication クラスと、そのメンバ関数 exec()、 qPrintable()関数 *<QString> : QString クラス *<QFile> : QFile クラス *<QTextStream> : QTextStream クラス *<QImage> : QImage クラス *<QColor> : qRGB() QColorの静的メンバ関数 *<QSize> : QImage 関数の初期化に使う。QSize クラス freetype ライブラリを使うためのヘッダファイルを宣言し、そのヘッダファイルで宣言されているヘッダ名マクロをインクルードします。 <Syntaxhighlight2 lang="cpp"> #include <ft2build.h> #include FT_FREETYPE_H </Syntaxhighlight2> 12行目~32行目:処理したい文字配列です。8桁の16進数で、文字コードの「あ~と」までの20文字。番号がとびとびになっているのはUnicodeでは「あ」の後は「ぁ」とか濁点が付いたモノのように順番になっているからです。フォントファイルの中では実際はグリフIDというのが必要ですが、CMapのような構造をフォントファイル内に保持していて、どのような順番になっているかは、フォントファイルの構造を掌握していて、対応表を検出できるようなプログラム。例えば、今回のfreetypeのようなものが必要です。一番最後にLENというマクロが配列の大きさを保持するように設定しています。 34行目35行目のWIDTHとHEIGHTは、キャンバスの幅と高さについてのピクセル数を保持するマクロです。符号なし文字型<span>(</span>符号なし1バイト数値<span>)</span> canvasという型でその配列の大きさを利用します。値はcanvasの型どおりですが、0~255の値を保持します。白黒なので256段階の数値が保持できれば十分です。 LEFT RIGHTマクロは最初の「あ」を描画する位置を保持します。単位はポイントで8ポイントで1文字ぶんくらいの余白になります。 41~54行目はdrawという関数で、RGB=#000000の真っ黒の描画エリア=canvasに対して、受け取る引数。1文字分のグリフのbitmap情報とxとyによる文字描画位置から1文字分の全ピクセルの範囲について白黒濃度値を設定していきます。全部の文字分処理、LENの数値分、繰り返したら、全部を反映したことになります。text 配列の配列数分がLENです。その繰り返しの回数だけ関数が呼び出されます。 60行目からはメイン処理の追加文です。 QtのQStringで画像ファイルとテキストファイルのパスを指定します。長いパスは疲れるのでC:¥とCドライブの直下に置くことにしました。 テキストクラスのQFileに初期値として、パス名を与えることで、初期化できるので利用した感じです。あとから違うファイルを開くためにファイルをcloseした状態でならファイルパスを変更することもできると思いますが、今回はやっていません。 QString_ImageRowMonoBufferというQStringクラスの変数を実体化していますが、これは後々、キャンバスサイズの1行分の白黒情報を2桁の整数で吐き出します。実際は0と1の2値なので、1文字空白が発生するようになります。テキストでみたときに2文字が全角1文字なので、1文字が正方形で表せるため縦横比がテキストファイル上でいい感じにとらえることができます。 67行目でQImageクラスの変数を実体化しています。空の画像格納用変数になります。最初の引数に画像の横幅と縦幅を格納したQSizeクラスの値が必要なので、その上の行で、QSize クラスの変数を実体化しています。第二引数はどういう形式の画像にするかというフラグ設定列挙子です。今回はフルカラーRGBと透明度が設定可能な形式。 69行目からはfreetypeのおきまりの初期化が始まります。91行目までは、このようなサンプルのおきまりの初期化の流れになります。freetypeの初期化をlibraryというクラス変数で実施して、次にその初期化されたクラス変数設定状態を有した状態でフォント情報を保持したfaceというクラスを生成。 81行目 FT_New_Face(library, qPrintable("C:\\Windows\\Fonts\\KozGoPr6N-Medium.otf"), 0, &face); のようにして、face変数に第一引数のフォント名を関連付けたオブジェクトとして操作できるようになる。 そして87行目で1文字を読み取るためのグリフサイズをポイント値で指定。第一引数はこれまでの初期化で生成されたオブジェクトfaceで、次の2つ引数は横幅、縦幅の大きさをポイント*64倍の値を設定する。ここで8は8ポイントを意味していて、ここに指定する1単位は1/64倍されたポイント数になるので、8ポイントにするための8/64を64倍して、8ポイントとして扱うことができる。このとき、このポイント数は26.6<span>(</span>ニィロク ロク<span>)</span>固定小数と呼ぶ形式になっていると言える。どちらかに値が設定されていればいい。片方は0なら、フォント自身が持つ縦横比が使われる。両方をしていした場合は縦横比が変形する。その後ろの2つつづく300は横・縦の内部解像度の扱いを指定している。こちらも片方が0なら、どちらかの値と同じになる。表示する部分がせいぜい8ポイント程度の画面上の表示でも1インチあたりは300個の情報を内部的に保持する形式になり、印刷してもそれなりに綺麗にみえる。300dpiというが、これは印刷業界の一般的な知識です。300dpiなら、人間にはそれなりに見えるということだ。画面上で拡大していくと汚くても。印刷したら大丈夫。これが大事。 ここまでの処理には、不具合があるとError番号に0以外が渡されるので、それを元にエラーがあって強制終了したことを標準エラー出力画面に表示させるStdErrでプログラマに知らせる形式となっている。 93・94行目は26.6形式に対応するべく、64倍するという操作をマクロ化しています。何回か使うのでスッキリ表示したいというものです。 << とか >> は演算子のオーバーロードが働いているC++のストリーム標準出力とはちがい、オーバーロード宣言される前の標準演算子で言えば、論理シフトという操作になります。 << は左論理シフトです。 >>は右論理シフト。 論理シフトを行うと 2進数の 0b0010が左だと0b0100のように左にひとつ移動し、右からは0が挿入されます。このとき10進数で言えば、2から4に増えています。実は1つ移動するごとに2倍されいます。つまり6回移動すると64倍です。2, 4 , 8, 16, 32, 64 ね。逆に右へ移動させると、1/2倍されていきます。 しらないと、何やってんのコレって思っちゃいますが、わかるとなんてことはない感じです。 97・98行目では算出した初期値へ移動するため、FT_Vector型の座標を記憶できる構造のクラスのメンバ変数 pen.x, pen.y にそれぞれ、LEFT 16とTOP 32のポイント値を64倍した値を記憶させています。 そして次の行で、テキストの文字数にあたる大きさの配列を準備しています。あとで、それぞれのテキスト番号に対応するグリフ番号を取得する予定です。 そして100行目から126行目の間を、テキストの文字数分だけ繰り返します。このとき繰り返しごとに増えていく数値が操作する入力テキスト文字コード配列の番号と対応します。 まず101行目のif文ではテキスト文字が改行コードかどうかを判断します。該当したら、pen.y への移動量を設定して、キャンバス上の縦への移動を行います。pen.xは復帰の扱いの処理で、初期値のLEFT 16 のマクロ値を設定します。このときも、64倍した値です。yは48ピクセル下へ移動します。6文字分くらい下ですね。300dpiでは8ポイントが32ピクセル相当です。 改行コードの場合はこのif文の中の処理がおわったら、ほかの処理はもう必要ないので、全部を無視して、for文の最初にもどり、繰り返し処理を再開します。 107行目は、文字コードを含んだ text 配列の for文処理の対象文字について、フォントファイルのCMapと照合して、グリフ番号を返してくれる命令です。 <Syntaxhighlight2 lang="cpp" line="107"> idces[i] = FT_Get_Char_Index(face, text[i]); </Syntaxhighlight2> 引数にはフォント情報をもっている face を第一引数として設定、第二引数で文字コード番号を含んだ数値を設定。これで戻り値にはグリフ番号が取得でき、結果として、for文の処理が終わった所で文字コードをグリフ番号に変換することを実現している。この部分は、PDFで使った文字コードをグリフ番号で保持するPDFの仕様に対応できる部分だ。 次の行ではフォントの情報を保持できるクラスに対して、対象のグリフ番号の情報を保持するという処理を行う。まずは、情報を読み込む処理として <Syntaxhighlight2 lang="cpp" line="109"> error = FT_Load_Glyph(face, idces[i], FT_LOAD_DEFAULT); </Syntaxhighlight2> 109行目のFT_Load_Glyphを実行する。第一引数は一文字分のグリフ情報を保持するためのFT_Face クラスの変数 face を使う。先のグリフ番号取得でも使った face の役割はいくつかありそう。そうして読み込んだ状態では、そのメンバ関数がグリフに関する情報をさらに保持していけるので、次の命令を実行できる。 それが115行目だ。 <Syntaxhighlight2 lang="cpp" line="115"> error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); </Syntaxhighlight2> FT_Render_Glyphの第一引数の face->glyph メンバポインタ変数に描画後の情報の標準的なbitmap値を生成する。FT_RENDER_MODE_NORMALという列挙子によってbitmapに生成する仕組みがかわるようだ。これで、使えそうなbitmapが生成されたので、次の命令によって、キャンバスに反映させる。ここではキャンバスに反映させるが、ここはプログラムの用途によっていろいろな方法で情報を受け取ることになると思う。 このチュートリアルでは逐一、テキスト配列を処理していく毎にキャンバスの色情報保持配列に反映させてしまう。それが121行目から125行目の処理となる。 ビットマップ値はFT_Face型の変数faceを使って &face->glyph->bitmapのアドレス番号を先頭に保持されているので、この情報をプログラマ側で作ったキャンバスへ反映するdrawという関数の第一引数に指定する。この関数は、描画位置情報のxとyの値も必要としているので、改行したり、文字を描画がしたときに次のpenの位置を移動したりする操作をしていく。 draw関数では、face->glyph->bitmap_leftに読み込んだビットマップの左側の座標を渡す。topは上側だ。かならずしも端の座標は 0 とは限らない。フォントをはみ出してマイナスの位置からグリフ情報をもっている可能性もあるためだ、その場合、少し戻った位置にたいしてキャンバスへの描画をおこなう必要がある。 文字の送り幅自体はface->glyph->advance.xでpen.xに反映させる。125行目 字詰めという処理を必要とするプロポーショナルフォントの場合は特に上記のような操作にしておかないと、はみ出た分の文字のデザインが描画されないばかりか、バランスがおかしくなる。 これがおわるとキャンバスの配列ができあがる。これをビットマップに吐き出したり、標準出力に吐き出したり、テキストとして保存してみたりというのが、この先の処理だ。結果を目にみえるものにするのは楽しい。やってることは、単純だが、ここまで苦労して得られたものは、プログラマにしかわからない優越感を味わえる。フォントをイチカラ活用してやったぜ。っていう感じです。 129~134行目であらかじめ準備していたQImageの空のイメージ空間を使って、キャンバスの全ピクセル値を使って白黒のグレースケールイメージファイルを構成し、保存する処理を実施している。 137行目から最後まではテキストファイルの生成処理を実施している。このあたりはfreetypeとはもう関係ないので説明しない。 === '''サンプル2. 公式サイト版 のQtConsolApp''' === <Syntaxhighlight2 lang="cpp" line="1"> #include <QtCore/QCoreApplication> #include <QObject> #include <QString> #include <QFile> #include <QTextStream> #include <QImage> #include <QColor> #include <QSize> #pragma execution_character_set("utf-8") #include <ft2build.h> #include FT_FREETYPE_H #define WIDTH 640 #define HEIGHT 480 #define LEFT 32 #define TOP 32 static unsigned char canvas[HEIGHT][WIDTH]; static void draw_bitmap(FT_Bitmap* bitmap, FT_Int x, FT_Int y) { FT_Int i, j, p, q; FT_Int x_max = x + bitmap->width; FT_Int y_max = y + bitmap->rows; for (i = x, p = 0; i < x_max; i++, p++) { for (j = y, q = 0; j < y_max; j++, q++) { if (i < 0 || j < 0 || i >= WIDTH || j >= HEIGHT) { continue; } canvas[j][i] |= bitmap->buffer[q * bitmap->width + p]; } } } int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); QString QString_FileName = "C:/FreeTypeStep02Test.txt"; QString QString_ImageFileName = "C:/FreeTypeStep02Test.bmp"; QFile QFile_Text(QString_FileName); QString QString_ImageRowMonoBuffer = ""; QSize QSize_xy(WIDTH, HEIGHT); QImage QImage_Canvas(QSize_xy, QImage::Format_ARGB32_Premultiplied);//空イメージ FT_Library library; FT_Face face; FT_GlyphSlot slot; FT_Matrix matrix; FT_Vector pen; FT_Error error; QString text = QString::fromUtf8(u8"あいさがしの果てに\n辿り付いた寝屋川市駅\nというtest文章"); // ヒラギノ角5 DShirkg5.ttc BIZ UD ゴシック BIZ-UDGothicR.ttc KozGoPr6N-Medium.otf QString filename = "C:\\Windows\\Fonts\\BIZ-UDGothicR.ttc"; QString SingleStr = ""; double angle; int target_height; int n; int num_chars; num_chars = text.length(); angle = (0.0 / 360.0) * 3.141592 * 2.0; target_height = HEIGHT; error = FT_Init_FreeType(&library); if (error) { fprintf(stderr, qPrintable("ft init error\n")); exit(1); } error = FT_New_Face(library, filename.toUtf8(), 0, &face); if (error) { fprintf(stderr, qPrintable("new face error\n")); exit(1); } error = FT_Set_Char_Size(face, 30 * 64, 0, 100, 0); if (error) { fprintf(stderr, qPrintable("set size error\n")); exit(1); } slot = face->glyph; #define LEFT64(x) ((x) << 6) // 1/64が1の 32 bit 26.6 ニィロク ロク固定小数。 #define RIGHT64(x) ((x) >> 6) pen.x = LEFT64(LEFT); pen.y = LEFT64(target_height - 200); matrix.xx = (FT_Fixed)(cos(angle) * 0x10000L); matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L); matrix.yx = (FT_Fixed)(sin(angle) * 0x10000L); matrix.yy = (FT_Fixed)(cos(angle) * 0x10000L); unsigned int* idces = new unsigned int[num_chars]; for (int i = 0; i < num_chars; i++) { if (text.at(i) == '\n') { fprintf(stderr, qPrintable("%ld\n"), pen.x); pen.x = LEFT64(LEFT); pen.y += LEFT64(48); continue; } SingleStr = text.at(i); FT_Set_Transform(face, &matrix, &pen); QChar ch = SingleStr.at(0); FT_ULong FT_ULong_code = ch.unicode(); idces[i] = FT_Get_Char_Index(face, FT_ULong_code); error = FT_Load_Char(face, FT_ULong_code, FT_LOAD_RENDER); if (error) { continue; } draw_bitmap(&slot->bitmap, slot->bitmap_left, HEIGHT - slot->bitmap_top); pen.x += slot->advance.x; pen.y += slot->advance.y; } fprintf(stderr, qPrintable("\n")); for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { QImage_Canvas.setPixel(x, y, qRgb(canvas[y][x], canvas[y][x], canvas[y][x])); } } QImage_Canvas.save(QString_ImageFileName, "BMP"); printf(qPrintable("P3\n%d %d\n%d\n"), WIDTH, HEIGHT, 255); if (QFile_Text.open(QIODevice::WriteOnly)) { QTextStream out(&QFile_Text); for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { if (canvas[y][x]) { QString_ImageRowMonoBuffer = QString_ImageRowMonoBuffer + " 1"; //printf(qPrintable("%2d"), 1); } else { QString_ImageRowMonoBuffer = QString_ImageRowMonoBuffer + " 0"; //printf(qPrintable("%2d"), 0); } } QString_ImageRowMonoBuffer = QString_ImageRowMonoBuffer + "\n"; //printf(qPrintable("\n")); out << QString_ImageRowMonoBuffer; QString_ImageRowMonoBuffer = ""; } QFile_Text.close(); } else { QString QString_Error(qPrintable("No Open or Create Error\n")); QString_Error = QString_Error + QFile_Text.errorString(); fprintf(stderr, qPrintable(QString_Error)); } delete[] idces; FT_done_Face (face); FT_Done_FreeType (library); return a.exec(); } </Syntaxhighlight2> 出力結果ファイル [[ファイル:FreeTypeStep02Test.bmp|400px|thumb|none|実行結果]] 今回は公式サイトの最初のチュートリアルをQtConsoleApp版にしたものです。うごきも少し違う工夫がなされていて、下への移動のつもりは、下から上に書いていくチュートリアルになってしまっていました。キャンバスへの引数はY座標が逆転する要素があるようになっていますね。解説はしません。あと勉強になったのは2点 *プログラムの最後の方にある。オブジェクトの消滅作業ですね。 : Initによる初期化で動的に生成されたオブジェクトをこの二つの方法で綺麗に掃除しています。これが行儀のいい書き方なのですね。もっと大きいソリューションとして構築する場合はデストラクタとかで処理するようにしたいものです。 *グリフ番号を取得しなくてもFT_Load_Charで、ユニコード番号だけで、グリフのビットマップが得られる方法がある。 : 最初のチュートリアルでグリフ番号を取得してグリフのビットマップを取得していました。PDFを作るという目的においては重要ですが、グリフを描画するという処理だけならあまり意識はしたくない処理が省けるというのもいい感じです。おもしろい。 freetypeで独自のフォントの変形処理を作るのが醍醐味なのかもしれません。ここまでのチュートリアルがわかっただけでも、相当いろいろな変形処理が作れそう。任意の半径の丸い円弧状にそって文字を配置したりもできるようになりそうです。出来栄えを確かめたりする作業をもった素早くするには、それなりのユーザインタフェースを作らないと駄目ですね。ややこしそう。画像をあやつる一歩を踏み出した感じ。いいね。この例にも文字を変形させる処理があります。FT_Set_Transformという関数とそのちょっとまえのFT_Matrixというクラス変数のmatrixというオブジェクトの準備作業が必要なようです。angleの計算の割り算の優先度を上げているところでカッコの中の数字のどちらかに小数点がないと、なにをやっても計算結果が整数の 0 になって、angleは常に 0 になってしまいます。注意されたし。 https://subscription.packtpub.com/book/game-development/9781782167785/2/ch02lvl1sec28/using-the-freetype-library-for-text-rendering [[フォント TrueType 構造解析]]に戻る。
FreeTypeを使う
に戻る。
個人用ツール
ログイン
名前空間
ページ
議論
変種
表示
閲覧
ソースを表示
履歴表示
操作
検索
案内
メインページ
コミュニティ・ポータル
最近の出来事
最近の更新
おまかせ表示
ヘルプ
ツールボックス
リンク元
関連ページの更新状況
特別ページ