FreeTypeを使う 新しいページはコチラ
提供: yonewiki
(→サンプル4.公式サイトのErik Mollerさん作成その2 Qt版) |
(→サンプル6.公式サイトのStatic Jobs LLC作成) |
||
1,230行: | 1,230行: | ||
こういう作業をすることは勉強にはなりません。これを当たり前のようにこなしてから、どうすることが正しかったのかを考えることが大事です。でも今回は管理人には必要ない知識なので、ここではやりません。人が作ったものを使いこなすのは大変だということです。ユーザ数が少ない。文献がすくなければなおさら面倒です。必要に駆られるまで手は出さない。動けばいいな。程度。1週間やってもだめなら諦めますね。パズルと同じです。 | こういう作業をすることは勉強にはなりません。これを当たり前のようにこなしてから、どうすることが正しかったのかを考えることが大事です。でも今回は管理人には必要ない知識なので、ここではやりません。人が作ったものを使いこなすのは大変だということです。ユーザ数が少ない。文献がすくなければなおさら面倒です。必要に駆られるまで手は出さない。動けばいいな。程度。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 構造解析]]に戻る。 |