PicoPDF

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バイト命令となる。
スタックを引数として命令を実行する。
定義が |- で始まる場合、スタックの底から全て引数として消費し、スタックを空にしてから次の命令実行に移ることを意味する。
xy は絶対座標、dxdy で始まるのは相対座標である。

命令 定義
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系命令3endcharが出現するとパスを閉じる必要がある。
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;
}
  1. 前置きになる命令はhintmask、cntrmask、shortint  2

  2. サブパス命令はrlineto、vlineto、hlineto、rrcurveto、rcurveline、rlinecurve、vvcurveto、hhcurveto、vhcurveto、hvcurveto  2 3

  3. moveto系命令はrmoveto、vmoveto、hmoveto  2

  4. ステム系命令はhstem、vstem、hstemhm、vstemhm