Hatena::ブログ(Diary)

Webと文字

よければ、はてブしてください。( ´∀`) George.Nagaoka@gmail.com

JavaScript IME:海外からブラウザで日本語を変換 新URL 旧URL:
多言語入力ブックマークレット:ブラウザでロシア語、中国語、アラビア語・・・
【軍曹が】携帯電話開発の現状【語る】をAA化した
AAのデータベース
趣味のページ

2010-12-04

OpenType/CFFの仕様の解説

修正:2011/8/15

 タイトル変更と加筆、修正。間違っている箇所が複数存在しました。ここにお詫びを申し上げます。

今回の記事は、拙作のTTF/OTF Reader ver 0.75.1aと合わせて読んでいただくと、理解が早いかと思われます。

OpenType/CFFとは

 1990年にリリースされたフォント仕様TrueTypeAdobePostScriptフォントに対抗して,MicrosoftAppleが共同して策定をしたという歴史を持つ(参考資料1).

 その後継仕様として策定されたOpenTypeは,上記3社(AdobeMicrosoftApple)によって合議され,1997年にリリースされた(参考資料1).これにより,PostScript形式によるグリフ表現とTrueTypeにおけるグリフ表現を同一のフォント形式で表すことが可能になった.正し,その実態はTrueTypeにPostScriptフォントファイルを付け加えているに過ぎず,単一のフォントで2つの表現を同時に扱う事はできない.

 TrueTypeは4文字名の複数のテーブルで構成されている事を以前に解説したが,OpenTypeでPostScriptフォントを扱う場合,[CFF ]というテーブルが必要となる.その実態はCompact Font Format(以下CFF)と呼ばれるフォント仕様である.CFFはType1(PostScript)表現のグリフ(3次ベジェ曲線で構成されたヒンティングを備えたグリフ)を,Type2 charstringと呼ばれる形式にエンコードして保存する.

 TrueTypeは文字コードからグリフへのマッピング[cmap]テーブルを利用するが,OpenTypeにおいてもそれは同じである.CFFフォント仕様には同様のEncodingと呼ばれるデータが存在するが,OpenTypeの1テーブルとして使用される場合はそのデータを含まないようになっている.正し,マッピングを予め定義された仕様に基づくCID形式を取る場合,事情は少し複雑になる.この他,CFFには複数のフォントを含むことができるが,OpenTypeとして使用されるときは単一のフォントのみを含む.

 CFFに関してはAdobeが用意した以下の資料に目を通すことが必要となる(参考資料3、4).

  • Adobe Technical Note #5176: “The Compact Font Format Specification.”
  • Adobe Technical Note #5177: “Type 2 Charstring Format.”

毎度ながら訳文は存在しないが,参考資料5の一連の記事が大変参考になる.

sfnt version

 一部のシステムはCFF形式とTrueType形式を判別するのに、フォントのヘッダーのsfnt versionをフォーマット識別子として使用する。Windowsではsfnt versionがアスキー文字でOTTO(['0x4f54', '0x544f']に相当)である時のみフォントをCFFとして解析することに注意。FontForgeはこれを無視して解析する。

CFF仕様 データ型とheader

データ型として以下のものがある.

名前範囲説明
Card80-2551バイト符号無し整数
Card160-655362バイト符号無し整数
OffSize1-4オフセットのサイズを指定する1バイト符号無し整数
Offset可変1,2,3または4バイトオフセット
SID0-649992バイト文字列ID

データはアラインメントを無視してパディング無しで保存されるため,Offsetに3バイトという中途半端な型が存在する.SID文字列が集められた表(後述するString INDEX)に付けられるIDで,一つの例外を除いてフォントで使用する文字列はすべてこのIDを利用する.

CFFデータは以下のヘッダーテーブルより始まる.

名前範囲説明
Card8majorFormat major version (starting at 1)
Card8minorFormat minor version (starting at 0)
Card8hdrSizeヘッダーのサイズ (バイト)
OffSizeoffSizeAbsolute offset (0) size

majorとminorはフォーマットのバージョン番号を表す.実装はこれを読み取って各々の仕様に対応した処理を行う.ちなみに私が使用した仕様書はver1.0なのでそれぞれ,1と0になる.現在はこれ以外存在しない。hdrSizeはヘッダーのサイズを表す.これはこの後に存在するNameINDEXデータへのアクセスを提供するものである.

オフセットはCFFテーブルの頭からの場合と,そのデータ構造の頭からの2つのパターンがある.仕様書ではそれぞれ,offset(0)とoffset(self)で表している.OffSizeは全てのoffset(0)のサイズを決定する.

データ構造

上記のheaderを含めたフォント構造を以下に示す.

EntryComments
Header固定
Name INDEX固定
Top DICT INDEX固定
String INDEX固定
Global Subr INDEX固定
Encodings
Charsets
FDSelectCIDFonts only
CharStrings INDEXフォントごとに
Font DICT INDEXフォントごとに, CIDフォントのみ
Private DICTフォントごとに
Local Subr INDEXフォントごとに、もしくはCIDフォントのPrivate DICTごとに存在

ヘッダーとName INDEXの間はヘッダーのhdrSizeによって変わるかもしれないが,その後のName INDEXからGlobal Subr INDEXは順に,隙間なくINDEX構造体(後述)として存在していなければならない.たとえそれが示すDICT構造体(後述)が存在していなくとも,要素無しのINDEX構造体として存在する必要がある(詳しくは後述).

 EncodingsやCharsetsは順序も場所も適当でよく,別に無くてもいい.それぞれのアクセスにはTop DICT INDEX(後述)に存在するオフセット(0)値を使用する.CharStrings INDEXやLocal Subr INDEXはフォントごとに存在する.

データコンテナの仕様

 CFFではデータコンテナにINDEXDICTと呼ばれるデータ構造を用意している.

INDEX

INDEXの構造を以下に示す。

EntryComments
Card16count
OffSizeoffSize
Offsetoffset[count+1]
Card8data[<varies>]

 countはINDEXが含むデータ数を示す。空のINDEXを表現する場合にはcountのみとなり、その値は0の全部で2バイト長となる。offSizeはOffsetの要素のバイト長である。Offsetはcount+1個の配列である。その値はdataの直前のバイトからのオフセット値であり、最初の値は常に1である。データ数が2つであれば、Offsetは3つであり、その値は1,1番目のデータ長+1,1番目のデータ長+1+2番目のデータ長である。このことからデータのオフセット値と共にデータのバイト長を求めることができる。

DICT

 DICT構造においてはキーのバイト列で,以下のような構造をしている。

[ [(=数値指定子 数値)] キー ]

数値指定子は1バイトで、その後に来る数値表現(バイト長や整数かどうかなど)を決める。定子が32-246であれば、数値は存在せず、定子そのものから139を引いたものがとなる。247-250であれば、数値は符号なし1バイトであり、は (定子 –247)*256+数値+108で計算される。251-250も同様で、–(定子 –251)*256–数値–108で計算される。28であれば、数値は符号あり2バイトとなり、それがとなる。29であれば、数値は符号あり4バイトとなり、同様である。30であれば、数値は符号なし1バイトとして取られた後、ニブルに分解され、その値に15が来るまで数値の取得が続く(このデコードの詳細は仕様を参照のこと)。キーが来るまで複数存在する場合がある。

 キーは符号なし1バイト、若しくは符号なし2バイトで表される。1バイトの場合は0-11,13-21である。2バイトの場合は最初の1バイトに12が来て、次の1バイトに0-255が来る。

 キーの組み合わせは、DICTの長さに限界が来るまで続く。逆に言えば、DICTの解析にはDICT自身のバイト長が必要不可欠である。

Name INDEX

 Name INDEXはCFFに存在するフォント名のリストである。*1ここで使われるフォント名はSIDではない唯一の文字列である.名前にはいろいろ制限があるので詳しくは参考資料3の#5088、「Font Naming Issues」を参照のこと.また[name]テーブルのPostScriptName(code 6)に同様のフォント名を書く必要がある.

Top DICT INDEX

 Top DICT INDEXはName INDEXに示されたフォントの基本情報をそれぞれ含む.フォント名やオプションのテーブルへのオフセットなどを格納する.ここで使われるキーの意味はType1と可能なかぎり同じようになっている.以下に代表的なキーを上げる。以下に上げたキーの他にも必要なキーが存在するので必ず仕様を確認すること。

NamekeyOperanddescription
version0SIDバージョン
Notice1SID 
FullName2SIDフルフォント
FamilyName3]SIDファミリー名
Weight4SIDウェイト
isFixedPitch12 1boolean等幅かどうか。[post]テーブルと同じに設定
UnderlinePosition12 3boolean[post]テーブルと異なり、baselineから下線の中央までの長さ
UnderlineThickness12 4boolean[post]下線幅。テーブル同じに設定
FontBBox12 4array全グリフの最大バウンディングボックス。[head]と同じに設定
charset15numbercharset オフセット(0)
CharStrings15number CharStrings オフセット(0)
Private15arrayPrivate DICT サイズ , オフセット (0)

後述するCIDフォント形式であれば、以下のキーが必要で、DICTのキーはROSから始まる必要がある。以下に上げたキーの他にも必要なキーが存在するので必ず仕様を確認すること。

NamekeyOperanddescription
ROS12 30SID SID number登録者-配列(-追補番号)
FDArray12 36numberFont DICT (FD) INDEX offset (0)
FDSelect12 37numberFDSelect offset (0)

String INDEX

 String INDEXは全てのフォントに共通した文字列を集め,表にしたものである.重複は存在してはならない.表のインデックスに391を足したものが,その文字列SIDとなる.0から390の文字列は定義済みで,仕様書の付録Aにその表が存在する.

Global Subr INDEX

 Global Subr INDEXはグリフ表現で使われるType 2 charstirngから呼び出されるサブルーチン(一連の処理)のリストで,ルーチン自体もType 2 charstirngである.ここで定義されたルーチンは全てのフォントから呼び出すことが出来て,最大10回までネストすることも出来る.呼び出すルーチンはType 2 charstirngのcallgsubrオペレーターの引数にGlobal Subr INDEXのインデックスを取ることで決められる(詳しくは後述)。

Charset

 CharsetGID([cmap]テーブルによって文字コードから変換されるグリフを示す一意のID)からグリフ名(実際はSID)へのマッピングを行うものである.実態はINDEXでもDICT構造体でもなく,GIDのバラけ具合によって3つのフォーマットが存在する.フォーマットがどの様に成っているかは、最初の1バイトを見て判断するしか無い。注意点として,GIDが0は「.notdef」と決まっているので,GIDマッピングは1から始まる.以下の表はFormat0である.

Format0

TypeNameDescription
Card8format =0
Card8nCodesNumber of encoded glyphs
Card8code[nCodes]Code array

これは配列を利用したGIDSIDとのマッピングである。特に難しい点は無い。以下の表はFormat1とそこで使われるRange1である.フォーマット1,2はテーブル長が用意されないので、CharStringsINDEXのcountの数を元にループ数を決定する必要がある。

Format1

TypeNameDescription
Card8format =1
structRange1[<varies>]Range1 array

Range1

TypeNameDescription
SIDfirstFirst glyph in range
Card8nLeftGlyphs left in range (excluding first)

このフォーマットはRange1が順に評価され,GIDはインクリメントされる.firstはSIDの開始点を表し,nLeftはインクリメント数-1を表す.今,Range1[0]のfirstが5でnLeftが3であれば,GIDSIDマッピングは 1:5, 2:6, 3:7, 4:8 となる.続くRange1[1]のfirstが192でnLeftが2であれば, 5:192, 6:193, 7:194 となる.

    class C_Charsets:
        def __init__(self,f,offset,count):
            f.seek(offset);
            self.format = unpack('>B',f.read(1))[0];
            if self.format ==1:
                self.Range1 =[];
                self.gindex2SID=[0];
                ccount =0;
                while(ccount <= count):
                    first,nLeft = unpack('>HB',f.read(3));
                    for s in range(first,nLeft+first+1):
                        self.gindex2SID.append(s);
                    self.Range1.append({"first":first,"nLeft":nLeft});
                    ccount +=nLeft+1;

Format2はFormat1のnLeftがCard16となっただけであるので省略する。

Encoding

Encodingは[cmap]に似た構造を持つマッピングテーブルである.前述のとおりOpenType/CFFでは省略されるので解説を省かせてもらう.

CharStrings INDEX

 グリフの個数存在するType2 charstring形式のリストである.GIDに対応して存在する。

Private DICTとLocal Subrs INDEX

 Top DICT INDEXのキー値18で参照されるオフセットとテーブル長を利用してアクセスされる.ここにはTop DICT INDEX以外のフォント情報が保存される.Top DICT INDEXとなぜ分離されているかは仕様書の付録Fを参照のこと.ここにLocal Subrs INDEXへのオフセット(Private DICTの先頭からのオフセットであることに注意!)が保存される.

 Local Subrs INDEXは前述のGlobal Subrs INDEXと異なり,フォント内でしか読み込めない.Type 2 charstirngではcallsubrオペレーターを使用する.それ以外は同じである.

CIDフォント

 CIDとはAdobeが定めた文字集合の番号(参考資料6)のことで、参考資料3にその一覧が存在する。Unicodeのように全ての文字を単一の表に入れるのではなく、登録者-配列(-追補番号)の形で複数存在させ、拡張性を高めているのが特徴である。しかし実際は登録者がAdobeだけなのでAdobe製品ぐらいしか対応せず、文字コード規格でも無いのでコンピュータのテキスト表現としても使えない。どちらかと言えば、「このフォントAdobe-Japan1-6に対応しています!」のように一種の基準として利用されることのほうが多い。

 CIDは文字コード規格ではないので、次のように対応付けられる。まず、コンピューターはテキストの文字コード規格(UTF-8,Shift-JISなど)を読取り、それをデコードして文字コード(例)E3 81 82 (UTF-8)->12354)を取得する。次に[cmap]テーブルにテキストの文字コード規格が存在しているかを検索して、存在していなければ、文字コードUnicodeに変換して*2文字コードGIDへと変換する(例)12354->12)。次にGIDはCFFテーブルのCharsetを利用してCIDへと変換される(例)12->823)。(Charsetは本来GIDSIDへと変換するが、CIDフォントではGIDをCIDへと変換することに注意。)

 CharStrings INDEXはCIDで参照されるが、TopDictINDEXにはPrivate DICTとLocal Subrs INDEXのオフセットは存在しない。代わりに以下の方法でPrivate DICTとLocal Subrs INDEXが定義される。まずFDSelect(後述)がCIDの一定の範囲ごとにfdIndexを定義*3しているので、CIDに対するfdIndexを求める。次にFontDictINDEXのfdIndex番目のDictを参照する。このDictにはPrivate DICTへのオフセット(0)が記述されているのでこれを取得する。次に取得したPrivate DICTにLocal Subrs INDEXへのオフセット(self)が記録されていいればそれを取得する。こうして文字範囲ごとにPrivate DICTとLocal Subrs INDEXが複数存在する。

FontDictINDEX

 fdIndexで参照されるPrivateDICTまでのオフセットを含んだDictの配列

FDSelect

 Format0とFormat3の二種類のフォーマットがある。識別するには先頭バイトを読む必要がある。以下の表はFormat0である.

Format0

TypeNameDescription
Card8format =0
Card8fds[nGlyphs]FD selector array

これは配列を利用したCIDとfdIndexとのマッピングである。nGlyphsはCharStrings INDEXのcountである。以下の表はFormat3とそこで使われるRange3である.

Format3

TypeNameDescription
Card8format =3
Card16nRanges =1
structRange3[nRanges]Range3 array
Card16sentinelSentinel GID (see below)

Range3

TypeNameDescription
Card16firstFirst glyph index in range
Card8fdFD index for all glyphs in range

以下のように定義されていれば、CID0=>fdIndex=0、CID1-232=>fdIndex=12、CID231-1213=>fdIndex=8となる。

firstfd
04
112
2318

sentinel = 1214


Type2 charstring

 Type2 charstringは基本的に逆ポーランド記法 - Wikipediaで記述されている。これは最初に引数が来て、その後に処理命令が来るものである。デコードの手段としてはDICT構造とよく似ているので、目を通しておくこと。

 Type2 charstringの構造は以下のようになってる。ここで*は0以上、?は0か1つ、+は1以上存在することを表す。

w? {hs* vs* cm* hm* mt subpath}? {mt subpath}* endchar

wはグリフ幅である。charstringにこれがあるかないかは、PrivateDICTINDEXのdefaultWidthXとhmtxテーブルから求まるグリフ幅による。もし、defaultWidthXとhmtxのグリフ幅が同じであれば、wは存在せず、同じでなければwはPrivateDICTINDEXのnominalWidthXからの差分として存在する。注意として、wが存在したとしてもそれが必ず最初の数値に来るかどうかは限らない。後述するサブルーチン呼び出しが後ろにあればそれが優先される。

charstring : -107 gsubr 90 32...
 gsubr 0 : 12 23 hstem ...
この時、wが存在したとして、その値は12となる。

{hs* vs* cm* hm* mt subpath}はヒンティングに関するブロックである。この中で特に重要なのはcm(cntrmask)とhm(hintmask)である。このオペレーターは引数が後ろ側に位置し、さらにその引数の数はhs,vsが定義するステム(文字を構成する線のこと)の数に依存する。このことからは、Type2 charstringをデコードするにあたって、ステム数がわかる必要があり、もしもサブルーチンにhs,vsの記述がなく、cm,hmのみ記述があれば、それ単独ではデコードすることができない。すなわち、Type2 charstringは必ず頭から(CharStrings INDEXから)デコードしていかなくてはならない。

{mt subpath}はパスを構成する命令が含まれたブロッグである。ここでもヒンティングブロックにhs,vsでステムが定義されていれば、cm,hmの命令が出てくることに注意が必要である。

hstem,vstem,hstemhm,vstemhm,hintmask,cntrmask

 ヒンティングに関する命令であるが、ここではヒンティングの原理について扱わない*4。hstemは引数偶数で有る必要があり、2個で1つのステムを表すことから、引数の数/2がステム数になる。vstemも同様である。hstemhmとvstemhmはhstemとvstemと同じであるが、このあとに来る命令が必ずhintmask,cntrmaskであることを意味する。また、hintmask,cntrmaskまでの間に数値を入れるともう一方のステム命令を省略することができる。例えば、以下のcharstringはhstemhmの引数に280 100 70 40が、省略されたvstemの引数に400 50が、hintmaskの引数に0xa0がくる。

charstring : 280 100 70 40 hstemhm 400 50 hintmask 0xa0

hintmask,cntrmaskはその命令が出てくるまでに定義された全てのステムに対してビットフラグを設定するものである。その為、後ろに続く引数のバイト数はMath.ceil(ステム数/8)になる。

追記:2012/09/16

 特定のフォントのグリフにhintmaskオペレーターの前にステム命令が存在しないものがあった。おそらくバグであろうがステム数が0の場合は仮に1以上にするなどの調整処理をするべきであろう。

デコード

 デコードは数値指定子が意味する数値表現とキーの範囲が違うだけで、DICTの手法と同じである。

[ [(=数値指定子 数値)] オペレーター (mask) ]

数値指定子は1バイトで、その後に来る数値表現(バイト長や整数かどうかなど)を決める。定子が32-246であれば、数値は存在せず、定子そのものから139を引いたものがとなる。247-250であれば、数値は符号なし1バイトであり、は (定子 –247)*256+数値+108で計算される。251-250も同様で、–(定子 –251)*256–数値–108で計算される。28であれば、数値は符号あり2バイトとなり、それがとなる。255であれば、数値は(符号あり2バイト.符号なし2バイト)の固定小数点数となる。固定小数点数の求め方はここでは割愛する。オペレーターが来るまで複数存在する場合がある。  オペレーターは符号なし1バイト、若しくは符号なし2バイトで表される。1バイトの場合は0-11,13-27,29-31である。2バイトの場合は最初の1バイトに12が来て、次の1バイトに0-255が来る。  (mask)はオペレーターにhintmask,cntrmaskが来た場合のみに存在する。詳しくは前述した通り。

サブルーチン

 オペレーター(10)と(29)はそれぞれLocal Subrs INDEXへの参照を表すcallsubrとGlobal Subrs INDEXへの参照を表すcallgsubrを意味する。オペレーターの1つ手前の数値をINDEXのインデクスとするが、INDEXの個数が0-1240であれば+107、1241-33900であれば1131、それ以上であれば32768を数値へと加算する。  callsubr、callgsubrはそれまでに貯めたスタックをサブルーチンへと引き渡す。以下の例だと、10 20が gsubr 0へと引き渡され、rmoveの引数となる。
charstring : 10 20 -107 gsubr 90 32...
 gsubr 0 : rmove 24 21 ...

パスを構成するオペレーター一覧

 以下の画像はパスを構成するオペレーターとその引数の一覧である。startは開始点、horizontalは水平に点を移動、verticalは垂直に点を移動、青は必ず存在、翠は0個以上存在、ピンクは0もしくは1つ存在、水色の四角はカーブ上に存在、翠の丸は3次ベジェの制御点、赤線は3次ベジェ曲線を表す。  オペレーターによっては引数偶数かそうでないかによって実行する処理が変わることがある。
f:id:project_the_tower2:20110815181943p:image:w360
f:id:project_the_tower2:20110815181949p:image:w360
f:id:project_the_tower2:20110815181948p:image:w360
f:id:project_the_tower2:20110815181947p:image:w360
f:id:project_the_tower2:20110815181946p:image:w360
f:id:project_the_tower2:20110815181945p:image:w360
f:id:project_the_tower2:20110815181944p:image:w360

デコードの実装とパスへの変換

 省略

参考資料

  1. TrueType - Wikipedia
  2. PostScriptフォント - Wikipedia
  3. Font technical notes | Adobe Developer Connection
  4. partners.adobe.com/public/developer/en/font/T1_SPEC.PDF
  5. OpenTypeフォントの続き(10)・・・PostScriptアウトライン: vanillaの日記
  6. CID (文字コード) - Wikipedia

*1:仕様ではName INDEXに複数のフォント名を定義し、それぞれがTop DICT INDEXのデータを参照してフォントを定義できるが、そのようなことをしているフォントファイルは見たことがない。

*2:これはフォント規格において必ずUnicodeからGIDへのマッピングテーブルが存在しているはずだから

*3:例えば、CID0-200までがfdIndexが1、CID201-205までがfdIndexが4、…といった具合である。

*4:というよりも現時点ではわかりません(^^;)

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証