# CFFラスタライズ PDFにフォントを埋め込むために必須ではないが、Compact Font Formatフォントをベクトルデータに変換しSVG形式に変換する方法を解説する。 特に断りがない場合、Type 2 Charstring Formatについて解説する。 * [https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf](https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf){:target="_blank"} ## CharStrings概要 Compact Font Formatの[CharStrings INDEX](CFFデータ読み込み#CharStringsINDEX)にはグリフを描画するPostScriptバイナリが格納されている。 PostScriptは一部例外[^PolishNotation]を除いて[逆ポーランド記法](https://en.wikipedia.org/wiki/Reverse_Polish_notation){:target="_blank"}で記述されている。 [^PolishNotation]: 前置きになる命令はhintmask、cntrmask、shortint ### 可変長数値 前置きの可変長で表現される数値である。 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](CFFデータ読み込み#DICTDataのnumberについて)とほぼ同じだが、b0が255の場合の挙動が異なる。 また、b0が28の場合は[shortint](#shortint)命令と定義されているが、挙動はDICTDataのnumberと同じである。 ```cs 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バイトの後置き[^PolishNotation]の可変長となる。 b0が31以下であれば命令である。 b0が12の場合はエスケープとし、次の1バイトと合わせて2バイト命令となる。 スタックを引数として命令を実行する。 定義が `|-` で始まる場合、スタックの底から全て引数として消費し、スタックを空にしてから次の命令実行に移ることを意味する。 `x`、`y` は絶対座標、`dx`、`dy` で始まるのは相対座標である。 | 値 | 命令 | 定義 | |------------|--------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------| | 0 | 予約 | | | 1 | [hstem](#hstem、vstem、hstemhm、vstemhm) | |- y dy {dya dyb}* hstem |- | | 2 | 予約 | | | 3 | [vstem](#hstem、vstem、hstemhm、vstemhm) | |- x dx {dxa dxb}* vstem |- | | 4 | [vmoveto](#rmoveto、vmoveto、hmoveto) | |- dy1 vmoveto |- | | 5 | [rlineto](#rlineto、vlineto、hlineto) | |- {dxa dya}+ rlineto |- | | 6 | [hlineto](#rlineto、vlineto、hlineto) | |- dx1 {dya dxb}* hlineto |-
|- {dxa dyb}+ hlineto |- | | 7 | [vlineto](#rlineto、vlineto、hlineto) | |- dy1 {dxa dyb}* vlineto |-
|- {dya dxb}+ vlineto |- | | 8 | [rrcurveto](#rrcurveto、rcurveline、rlinecurve) | |- {dxa dya dxb dyb dxc dyc}+ rrcurveto |- | | 9 | 予約 | | | 10 | [callsubr](#callsubr、callgsubr) | subr callsubr | | 11 | [return](#return) | return | | 12 | [escape](#escape) | 次の1バイトと合わせて2バイト命令とする | | 13 | 予約 | | | 14 | [endchar](#endchar) | endchar | | 15 | 予約 | | | 16 | 予約 | | | 17 | 予約 | | | 18 | [hstemhm](#hstem、vstem、hstemhm、vstemhm) | |- y dy {dya dyb}* hstemhm |- | | 19 | [hintmask](#hintmask、cntrmask) | |- (x dx {dxa dxb}*)? hintmask mask+ |- | | 20 | [cntrmask](#hintmask、cntrmask) | |- (x dx {dxa dxb}*)? cntrmask mask+ |- | | 21 | [rmoveto](#rmoveto、vmoveto、hmoveto) | |- dx1 dy1 rmoveto |- | | 22 | [hmoveto](#rmoveto、vmoveto、hmoveto) | |- dx1 hmoveto |- | | 23 | [vstemhm](#hstem、vstem、hstemhm、vstemhm) | |- x dx {dxa dxb}* vstemhm |- | | 24 | [rcurveline](#rrcurveto、rcurveline、rlinecurve) | |- {dxa dya dxb dyb dxc dyc}+ dxd dyd rcurveline |- | | 25 | [rlinecurve](#rrcurveto、rcurveline、rlinecurve) | |- {dxa dya}+ dxb dyb dxc dyc dxd dyd rlinecurve |- | | 26 | [vvcurveto](#vvcurveto、hhcurveto) | |- dx1? {dya dxb dyb dyc}+ vvcurveto |- | | 27 | [hhcurveto](#vvcurveto、hhcurveto) | |- dy1? {dxa dxb dyb dxc}+ hhcurveto |- | | 28 | [shortint](#shortint) | shortint b1 b2 | | 29 | [callgsubr](#callsubr、callgsubr) | globalsubr callgsubr | | 30 | [vhcurveto](#vhcurveto、hvcurveto) | |- 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](#vhcurveto、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](#and、or、eq) | num1 num2 and | | 12 4 | [or](#and、or、eq) | num1 num2 or | | 12 5 | [not](#not、neg) | num1 not | | 12 6 | 予約 | | | 12 7 | 予約 | | | 12 8 | 予約 | | | 12 9 | [abs](#abs) | num abs | | 12 10 | [add](#add、sub、mul、div) | num1 num2 add | | 12 11 | [sub](#add、sub、mul、div) | num1 num2 sub | | 12 12 | [div](#add、sub、mul、div) | num1 num2 div | | 12 13 | 予約 | | | 12 14 | [neg](#not、neg) | num neg | | 12 15 | [eq](#and、or、eq) | num1 num2 eq | | 12 16 | 予約 | | | 12 17 | 予約 | | | 12 18 | [drop](#drop) | num drop | | 12 19 | 予約 | | | 12 20 | [put](#put、get) | val i put | | 12 21 | [get](#put、get) | i get | | 12 22 | [ifelse](#ifelse) | s1 s2 v1 v2 ifelse | | 12 23 | [random](#random) | random | | 12 24 | [mul](#add、sub、mul、div) | num1 num2 mul | | 12 25 | 予約 | | | 12 26 | [sqrt](#sqrt) | num sqrt | | 12 27 | [dup](#dup) | any dup | | 12 28 | [exch](#exch) | num1 num2 exch | | 12 29 | [index](#index) | i index | | 12 30 | [roll](#roll) | N J roll | | 12 31 | 予約 | | | 12 32 | 予約 | | | 12 33 | 予約 | | | 12 34 | [hflex](#hflex) | |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex |- | | 12 35 | [flex](#flex) | |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex |- | | 12 36 | [hflex1](#hflex1) | |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 |- | | 12 37 | [flex1](#flex1) | |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 |- | | 12 38~255 | 予約 | | ### width PostScriptバイナリには命令の並びが定められている。 並び順は `width? ステム系命令* ヒント系命令* {moveto系命令 サブパス命令}* endchar` である。 先頭のwidthはグリフの送り幅である。 サブパス命令[^Subpath]以外のhstem、vstem、hstemhm、vstemhm、hintmask、cntrmask、rmoveto、vmoveto、hmoveto、endchar命令がwidthの直後になりうる。 命令実行時のスタックの残りがwidthとなる。 これらはスタックの消費数が固定のため、余った先頭の数値がwidthとなる。 [^Subpath]: サブパス命令はrlineto、vlineto、hlineto、rrcurveto、rcurveline、rlinecurve、vvcurveto、hhcurveto、vhcurveto、hvcurveto | 命令 | スタック消費数 | 補足 | |----------|----------------|----------------------------------------------------------------------------------------| | 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](CFFデータ読み込み#PrivateDICT)のnominalWidthXを加算したものが実際のwidthとなる。 widthが指定されなかった場合はdefaultWidthXがwidthとなる。 ```cs 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はスタックから取り除かれたものとして説明する。 ### パスの終了 サブパス命令[^Subpath]で面を構成する。 面を構成するパスは閉じている必要があるため、最後の点から最初の点につなぐ必要がある。 moveto系命令[^Moveto]や[endchar](#endchar)が出現するとパスを閉じる必要がある。 SVGであれば[パスを閉じるコマンド](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/d#closepath){:target="_blank"}がある。 [^Moveto]: moveto系命令はrmoveto、vmoveto、hmoveto ### 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次ベジェ曲線を描画する。 ```cs 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次ベジェ曲線を描画する。 ```cs 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型変数としてスタックに入れる。 [可変長数値](#可変長数値)の一部として扱ってもよい。 ```cs 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系命令[^Moveto]、サブパス命令[^Subpath]だけを実装すれば十分な場合が多い。 ただし、通常のフォントにはステム系[^Stem]、hintmask、cntrmaskが含まれるため無視することができない。 ヒントはフォントを小さなサイズで表示する場合の情報である。 SVG形式に変換する場合は無視してしまって構わない。 [^Stem]: ステム系命令はhstem、vstem、hstemhm、vstemhm ```cs 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; } ```