PicoPDF

TrueTypeラスタライズ 🔗

PDFにフォントを埋め込むために必須ではないが、TrueTypeフォントをベクトルデータに変換しSVG形式に変換する方法を解説する。

glyfテーブルデータ 🔗

TrueTypeのglyfテーブルにはSimple glyphComposite glyphの2種類がある。
Simple glyphがグリフの座標データを持つもので、Composite glyphは複数のグリフを組み合わせて1つの文字を表現するものである。

Simple glyphであるかどうかはヘッダーのnumberOfContoursが-1であるかどうかで判断する。

Simple glyph 🔗

Simple glyphはflags、xCoordinates、yCoordinatesの3組の配列で表現される。
numberOfContoursが組の数となる。

xCoordinates、yCoordinatesは直前の座標からの相対座標になる。
絶対座標に変換する場合は事前に変換しておくとよい。
座標変換は次のような畳み込みで解決できる。

int[] to_absolute(int[] xs) => xs[1..].Aggregate([xs[0]], (ys, a) => [.. ys, ys[^1] + a]);

endPtsOfContoursから組を取得して面を構成する。
面を構成するパスは閉じている必要があるため、最後の点から最初の点につなぐ必要がある。
SVGであればパスを閉じるコマンドがある。

var xs = to_absolute(xCoordinates);
var ys = to_absolute(yCoordinates);
var prev = 0;
for(var i = 0; i < numberOfContours; i++)
{
    var vs = xs[prev .. endPtsOfContours[i]]
        .Zip(ys[prev .. endPtsOfContours[i]])
        .Select(a => new Vector2(a.First, a.Second));
    if (vs.First() != vs.Last()) vs = [.. vs, vs.First()]; // 必要に応じて追加
    prev = endPtsOfContours[i] + 1;
}

グリフは直線と2次ベジェ曲線で表現される。
直線か曲線かの判断はflagsのON_CURVE_POINT(線上の点)で判断する。
始点と終点が両方ON_CURVE_POINTであれば直線である。
終点がON_CURVE_POINTでない場合は2次ベジェ曲線の制御点となる。
制御点が2回連続して出現することがある。この場合は制御点の中間点を終点とした曲線にする必要がある。
下図は青点がON_CURVE_POINT、赤点が制御点、緑点が中間点である。

文字の下側に引かれた赤線はベースラインである。
yCoordinatesの座標はベースラインより上がプラス、下がマイナスとなる。(数学の座標と同じ、x軸がベースライン)
日本語グリフではベースラインより下側を使用することは少ないが、アルファベットのjなどはベースラインより下側に描画する。
xCoordinatesは通常はプラスであることが多いが、マイナスにもなりうる。

Composite glyph 🔗

Composite glyphは複数のグリフのGIDを指定する。
指定したGIDもComposite glyphである可能性がある。循環してはいけない。
各グリフを移動、回転、拡大縮小して組み合わせる。

argument1、argument2で移動を表す。
フラグのうちWE_HAVE_A_SCALE、WE_HAVE_AN_X_AND_Y_SCALE、WE_HAVE_A_TWO_BY_TWOは排他フラグとなる。
アフィン変換行列を作って座標移動するとよい。

var m = Matrix3x2.Identity;
m.Translation = new Vector2(aArgument1, argument2);

if (flags.HasFlag(WE_HAVE_A_SCALE))
{
    m.M11 = m.M22 = scale;
}
else if (flags.HasFlag(WE_HAVE_AN_X_AND_Y_SCALE))
{
    m.M11 = xscale;
    m.M22 = yscale;
}
else if (flags.HasFlag(WE_HAVE_A_TWO_BY_TWO))
{
    m.M11 = xscale;
    m.M12 = scale01;
    m.M21 = scale10;
    m.M22 = yscale;
}

var vec = Vector2.Transform(v, m); // 各種座標vをアフィン変換

scale、xscale、yscale、scale01、scale10はF2DOT14である。
F2DOT14は上位2bitが整数部、下位14bitが小数部の固定小数点形式であるため、floatへ変換する場合は2 ^ 14(=16384)で割る必要がある。