CFFラスタライズ 🔗
PDFにフォントを埋め込むために必須ではないが、Compact Font Formatフォントをベクトルデータに変換しSVG形式に変換する方法を解説する。
特に断りがない場合、Type 2 Charstring Formatについて解説する。
CharStrings概要 🔗
Compact Font FormatのCharStrings INDEXにはグリフを描画するPostScriptバイナリが格納されている。
PostScriptは一部例外1を除いて逆ポーランド記法で記述されている。
可変長数値 🔗
前置きの可変長で表現される数値である。
b0が32~255の場合は可変長数値となる。
先頭からb0、b1、b2、b3、b4と並んでいる場合の計算方法である。
| サイズ | b0の範囲 | 値の範囲 | 計算式 |
|---|---|---|---|
| 1 | 32 ~ 246 | -107 ~ +107 | b0 - 139 |
| 2 | 247 ~ 250 | +108 ~ +1131 | (b0 - 247) * 256 + b1 + 108 |
| 2 | 251 ~ 254 | -1131 ~ -108 | -(b0 - 251) * 256 - b1 - 108 |
| 5 | 255 | -(2 ^ 16) ~ +(2 ^ 16 - 1) | (b1 « 24 | b2 « 16 | b3 « 8 | b4) / 65536 |
DICT Dataのnumberとほぼ同じだが、b0が255の場合の挙動が異なる。
また、b0が28の場合はshortint命令と定義されているが、挙動はDICTDataのnumberと同じである。
var b0 = CharStrings[i];
if (b0 is >= 32 and <= 246) number = b0 - 139;
else if (b0 is >= 247 and <= 250) number = (b0 - 247) * 256 + CharStrings[++i] + 108;
else if (b0 is >= 251 and <= 254) number = -((b0 - 251) * 256) - CharStrings[++i] - 108;
else if (b0 == 255) number = ((short)(CharStrings[++i] << 8 | CharStrings[++i])) + ((short)(CharStrings[++i] << 8 | CharStrings[++i])) / 65536f;
以降の例では可変長数値は変換されているものとして説明する。
命令 🔗
命令(オペレーター)は1~2バイトの後置き1の可変長となる。
b0が31以下であれば命令である。
b0が12の場合はエスケープとし、次の1バイトと合わせて2バイト命令となる。
スタックを引数として命令を実行する。
定義が |- で始まる場合、スタックの底から全て引数として消費し、スタックを空にしてから次の命令実行に移ることを意味する。
x、y は絶対座標、dx、dy で始まるのは相対座標である。
| 値 | 命令 | 定義 |
|---|---|---|
| 0 | 予約 | |
| 1 | hstem | |- y dy {dya dyb}* hstem |- |
| 2 | 予約 | |
| 3 | vstem | |- x dx {dxa dxb}* vstem |- |
| 4 | vmoveto | |- dy1 vmoveto |- |
| 5 | rlineto | |- {dxa dya}+ rlineto |- |
| 6 | hlineto | |- dx1 {dya dxb}* hlineto |- |- {dxa dyb}+ hlineto |- |
| 7 | vlineto | |- dy1 {dxa dyb}* vlineto |- |- {dya dxb}+ vlineto |- |
| 8 | rrcurveto | |- {dxa dya dxb dyb dxc dyc}+ rrcurveto |- |
| 9 | 予約 | |
| 10 | callsubr | subr callsubr |
| 11 | return | return |
| 12 | escape | 次の1バイトと合わせて2バイト命令とする |
| 13 | 予約 | |
| 14 | endchar | endchar |
| 15 | 予約 | |
| 16 | 予約 | |
| 17 | 予約 | |
| 18 | hstemhm | |- y dy {dya dyb}* hstemhm |- |
| 19 | hintmask | |- (x dx {dxa dxb}*)? hintmask mask+ |- |
| 20 | cntrmask | |- (x dx {dxa dxb}*)? cntrmask mask+ |- |
| 21 | rmoveto | |- dx1 dy1 rmoveto |- |
| 22 | hmoveto | |- dx1 hmoveto |- |
| 23 | vstemhm | |- x dx {dxa dxb}* vstemhm |- |
| 24 | rcurveline | |- {dxa dya dxb dyb dxc dyc}+ dxd dyd rcurveline |- |
| 25 | rlinecurve | |- {dxa dya}+ dxb dyb dxc dyc dxd dyd rlinecurve |- |
| 26 | vvcurveto | |- dx1? {dya dxb dyb dyc}+ vvcurveto |- |
| 27 | hhcurveto | |- dy1? {dxa dxb dyb dxc}+ hhcurveto |- |
| 28 | shortint | shortint b1 b2 |
| 29 | callgsubr | globalsubr callgsubr |
| 30 | vhcurveto | |- dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf? vhcurveto |- |- {dya dxb dyb dxc dxd dxe dye dyf}+ dxf? vhcurveto |- |
| 31 | hvcurveto | |- dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf? hvcurveto |- |- {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf? hvcurveto |- |
| 12 0 | 予約 | |
| 12 1 | 予約 | |
| 12 2 | 予約 | |
| 12 3 | and | num1 num2 and |
| 12 4 | or | num1 num2 or |
| 12 5 | not | num1 not |
| 12 6 | 予約 | |
| 12 7 | 予約 | |
| 12 8 | 予約 | |
| 12 9 | abs | num abs |
| 12 10 | add | num1 num2 add |
| 12 11 | sub | num1 num2 sub |
| 12 12 | div | num1 num2 div |
| 12 13 | 予約 | |
| 12 14 | neg | num neg |
| 12 15 | eq | num1 num2 eq |
| 12 16 | 予約 | |
| 12 17 | 予約 | |
| 12 18 | drop | num drop |
| 12 19 | 予約 | |
| 12 20 | put | val i put |
| 12 21 | get | i get |
| 12 22 | ifelse | s1 s2 v1 v2 ifelse |
| 12 23 | random | random |
| 12 24 | mul | num1 num2 mul |
| 12 25 | 予約 | |
| 12 26 | sqrt | num sqrt |
| 12 27 | dup | any dup |
| 12 28 | exch | num1 num2 exch |
| 12 29 | index | i index |
| 12 30 | roll | N J roll |
| 12 31 | 予約 | |
| 12 32 | 予約 | |
| 12 33 | 予約 | |
| 12 34 | hflex | |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex |- |
| 12 35 | flex | |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex |- |
| 12 36 | hflex1 | |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 |- |
| 12 37 | flex1 | |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 |- |
| 12 38~255 | 予約 |
width 🔗
PostScriptバイナリには命令の並びが定められている。
並び順は width? ステム系命令* ヒント系命令* {moveto系命令 サブパス命令}* endchar である。
先頭のwidthはグリフの送り幅である。
サブパス命令2以外のhstem、vstem、hstemhm、vstemhm、hintmask、cntrmask、rmoveto、vmoveto、hmoveto、endchar命令がwidthの直後になりうる。
命令実行時のスタックの残りがwidthとなる。
これらはスタックの消費数が固定のため、余った先頭の数値がwidthとなる。
| 命令 | スタック消費数 | 補足 |
|---|---|---|
| hstem | 偶数 | スタック数が奇数であればスタックの底をwidthとする |
| vstem | 偶数 | スタック数が奇数であればスタックの底をwidthとする |
| hstemhm | 偶数 | スタック数が奇数であればスタックの底をwidthとする |
| vstemhm | 偶数 | スタック数が奇数であればスタックの底をwidthとする |
| hintmask | 偶数 | スタック数が奇数であればスタックの底をwidthとする、残りのスタックはvstemのヒントとなる |
| cntrmask | 偶数 | スタック数が奇数であればスタックの底をwidthとする、残りのスタックはvstemのヒントとなる |
| rmoveto | 2個 | スタック数が3であればスタックの底をwidthとする |
| vmoveto | 1個 | スタック数が2であればスタックの底をwidthとする |
| hmoveto | 1個 | スタック数が2であればスタックの底をwidthとする |
| endchar | 0個 | スタック数が1であればスタックの底をwidthとする |
widthはPrivate DICTのnominalWidthXを加算したものが実際のwidthとなる。
widthが指定されなかった場合はdefaultWidthXがwidthとなる。
switch (ope)
{
case Hstem:
case Vstem:
case Hstemhm:
case Vstemhm:
case Hintmask:
case Cntrmask:
if (stack.Count % 2 == 1) width ?= stack.Shift() + nominalWidthX;
break;
case Rmoveto:
if (stack.Count > 2) width ?= stack.Shift() + nominalWidthX;
break;
case Vmoveto:
case Hmoveto:
if (stack.Count > 1) width ?= stack.Shift() + nominalWidthX;
break;
case Endchar:
if (stack.Count > 0) width ?= stack.Shift() + nominalWidthX;
break;
}
width ?= defaultWidthX; // widthが未指定の場合
以降の例ではwidthはスタックから取り除かれたものとして説明する。
パスの終了 🔗
サブパス命令2で面を構成する。
面を構成するパスは閉じている必要があるため、最後の点から最初の点につなぐ必要がある。
moveto系命令3やendcharが出現するとパスを閉じる必要がある。
SVGであればパスを閉じるコマンドがある。
rmoveto、vmoveto、hmoveto 🔗
現在座標を (dx1, dy1) に移動する。座標は相対指定である。
最初の座標は (0, 0) である。
rlineto、vlineto、hlineto 🔗
rlinetoは現在位置から相対座標で (dxa, dya) に直線を描画する。
vlinetoはdy1の縦直線を描画した後dxaの横直線→dybの縦直線を描画する。
hlinetoはdx1の横直線を描画した後dyaの縦直線→dxbの横直線を描画する。
50, 250 Rmoveto # 現在座標を(50, 250)へ移動
250, 70, -250 hlineto # (50, 250)-(50, 500)の横線 → (50, 500)-(120, 500)の縦線 → (120, 500)-(120, 250)の横線を描画
endchar # パスを閉じるため(120, 250)-(50, 250)の直線を描画してアウトライン終了
rrcurveto、rcurveline、rlinecurve 🔗
rrcurvetoとrcurvelineは現在位置から相対座標で (dxa, dya) を1番目の制御点、(dxb, dyb) を2番目の制御点とし (dxc, dyc) への3次ベジェ曲線を描画する。
その後、rcurvelineは (dxd, dyd) に直線を描画する。
rlinecurveは現在位置から相対座標で (dxa, dya) に直線を引いた後、 (dxb, dyb) を1番目の制御点、(dxc, dyc) を2番目の制御点とし (dxd, dyd) への3次ベジェ曲線を描画する。
if (ope == Rlinecurve)
{
while (stack.Count >= 8)
{
var end = new Vector2(start.X + stack.Shift(), start.Y + stack.Shift());
// start-end の直線を描画
start = end;
}
}
while (stack.Count >= 6)
{
var cp1 = new Vector2(start.X + stack.Shift(), start.Y + stack.Shift());
var cp2 = new Vector2(cp1.X + stack.Shift(), cp1.Y + stack.Shift());
var end = new Vector2(cp2.X + stack.Shift(), cp2.Y + stack.Shift());
// start-cp1-cp2-end のベジェ曲線を描画
start = end;
}
if (ope == Rcurveline)
{
var end = new Vector2(start.X + stack.Shift(), start.Y + stack.Shift());
// start-end の直線を描画
start = end;
}
vvcurveto、hhcurveto 🔗
現在位置から相対座標で (dxa + dx1, dya + dy1) を1番目の制御点、(dxb, dyb) を2番目の制御点とし (dxc, dyc) への3次ベジェ曲線を描画する。
var d1 = stack.Count % 2 == 0 ? 0f : stack.Shift();
while (stack.Count >= 4)
{
var cp1 = new Vector2(start.X + ope == Vvcurveto ? stack.Shift() : d1, start.Y + ope != Vvcurveto ? stack.Shift() : d1);
var cp2 = new Vector2(cp1.X + stack.Shift(), cp1.Y + stack.Shift());
var end = new Vector2(cp2.X + ope != Vvcurveto ? stack.Shift() : 0, cp2.Y + ope == Vvcurveto ? stack.Shift() : 0);
// start-cp1-cp2-end のベジェ曲線を描画
start = end;
d1 = 0f;
}
vhcurveto、hvcurveto 🔗
shortint 🔗
前置き型の命令である。
次の2バイトをshort型変数としてスタックに入れる。
可変長数値の一部として扱ってもよい。
var b0 = CharStrings[i];
if (b0 == Shortint) number = (short)(CharStrings[++i] << 8 | CharStrings[++i]);
callsubr、callgsubr 🔗
return 🔗
サブルーチンを終了する。
スタックに積まれた数値は戻り値となる。
endchar 🔗
アウトラインを終了する。
サブルーチン内でendcharが登場する可能性もある。
escape 🔗
hstem、vstem、hstemhm、vstemhm 🔗
hintmask、cntrmask 🔗
add、sub、mul、div 🔗
and、or、eq 🔗
not、neg 🔗
abs 🔗
sqrt 🔗
drop 🔗
put、get 🔗
ifelse 🔗
random 🔗
exch 🔗
index 🔗
roll 🔗
hflex 🔗
flex 🔗
hflex1 🔗
flex1 🔗
実装の優先順位 🔗
通常のフォントでは使用されない命令も多い。
可変長数値、shortint、callsubr、callgsubr、return、endcharは実装必須である。
通常のフォントでは上記に加えmoveto系命令3、サブパス命令2だけを実装すれば十分な場合が多い。
ただし、通常のフォントにはステム系4、hintmask、cntrmaskが含まれるため無視することができない。
ヒントはフォントを小さなサイズで表示する場合の情報である。
SVG形式に変換する場合は無視してしまって構わない。
switch (ope)
{
case Hstem:
case Vstem:
case Hstemhm:
case Vstemhm:
stem += stack.Count / 2; // ヒントは偶数個ペア
stack.Clear();
break;
case Hintmask:
case Cntrmask:
stem += stack.Count / 2; // スタックがあればvstem相当
i += (stem + 7) / 8; // ヒントのペア数 * 1bitのマスクを読み飛ばす
stack.Clear();
break;
}