GPOS テーブル (1)

GPOS テーブル
「GPOS テーブル」には、グリフの位置を調整するデータが格納されています。

構造的にはほぼ GSUB テーブルと同じで、LookupList のサブテーブルが GPOS 固有のフォーマットとなっています。
テーブルデータ
uint16 majorVersionメジャーバージョン = 1
uint16 minorVersionマイナーバージョン = 0 or 1
Offset16 scriptListOffsetScriptList テーブルへのオフセット位置。
(GPOS テーブルの先頭を 0 とする)
Offset16 featureListOffsetFeatureList テーブルへのオフセット位置
Offset16 lookupListOffsetLookupList テーブルへのオフセット位置
ver 1.1 の場合、以下が続く
Offset32 featureVariationsOffsetFeatureVariations テーブルへのオフセット位置。
0 の場合あり。

GSUB と同じです。
プログラム (1)
まずは ScriptList、FeatureList、LookupList の一覧を表示してみます。

>> 23a_gpos_list.c

...
== 'hani' ==

<LangSys> [default] requiredFeatureIndex: 65535
(5,19,33,47,61,75,89,)

-- LangSysRecord ['JAN '] --
<LangSys> requiredFeatureIndex: 65535
(6,20,34,48,62,76,90,)

...

---- FeatureList ----

[0] 'halt' | 2,
[1] 'halt' | 2,
..
[42] 'vert' | 0,1,
..
[97] 'vpal' | 7,

---- LookupList ----

==== [0] ====

{Lookup} lookupType <1> | lookupFlag:0x0000 | subTableCount:1

# {SubTable} format <1>
...

Feature Tag には、GPOS 用のタグが設定されています。
(GSUB と GPOS の両方で使われるタグもあります)
Feature Tag
GPOS で使われる主な Feature Tag は、以下の通りです。

vert縦書きグリフの配置
halt(横書き) 全角幅→半角幅
vhal(縦書き) 全角高さ→半角高さ
palt(横書き) 等幅→プロポーショナル
vpal(縦書き) 等幅→プロポーショナル
kern(横書き) グリフ間の余白調整
vkrn(縦書き) グリフ間の余白調整
markマークグリフの配置
mkmkマークグリフを他のマークに対して配置
LookupType
GPOS では、LookupType の意味が GSUB とは異なります。

1Single adjustment単一グリフの位置調整
2Pair adjustmentペアグリフの位置調整
3Cursive attachmentcursive (筆記体) グリフの接続
4MarkToBase attachmentベースグリフとマークグリフの接続
5MarkToLigature attachment合字グリフとマークグリフの接続
6MarkToMark attachmentマークグリフとマークグリフの接続
7Context positioningコンテキストの1つ以上のグリフの配置
8Chained Context positioning連鎖したコンテキストの1つ以上のグリフの配置
9Extension positioningOffset32 の位置にサブテーブルを置く
プログラム (2)
先にプログラムの方を紹介しておきます。
各 LookupType のデータは以降で解説します。

>> 23b_gpos_type1.c

GPOS の LookupList から、LookupType 1・2・9 のデータを表示するプログラムです。
output.txt」に出力します。
共通テーブル
Lookup サブテーブルで使われる共通のテーブルデータです。
ValueRecord
グリフ位置を調整するためのデータです。
各項目はオプションであり、対応するフラグ (valueFormat) が ON の場合のみ、データが格納されています。

int16 xPlacementbit 0x 位置の調整値 (デザイン単位)
int16 yPlacementbit 1y 位置の調整値
int16 xAdvancebit 2(水平レイアウト) advance の x 位置調整
int16 yAdvancebit 3(垂直レイアウト) advance の y 位置調整
Offset16 xPlaDeviceOffsetbit 4(非可変フォント時) Device テーブルへのオフセット位置。
(可変フォント時) VariationIndex テーブルへのオフセット位置。

一つ上の親テーブルの先頭を 0 とする。
0 の場合あり。
Offset16 yPlaDeviceOffsetbit 5-
Offset16 xAdvDeviceOffsetbit 6-
Offset16 yAdvDeviceOffsetbit 7-
Anchor
接続する位置の情報。

Format 1
uint16 anchorFormatフォーマット番号 = 1
int16 xCoordinatex の値 (デザイン単位)
int16 yCoordinatey の値 (デザイン単位)

Format 2
uint16 anchorFormatフォーマット番号 = 2
int16 xCoordinatex の値 (デザイン単位)
int16 yCoordinatey の値 (デザイン単位)
uint16 anchorPoint輪郭線の座標のインデックス

Format 3
uint16 anchorFormatフォーマット番号 = 3
int16 xCoordinatex の値 (デザイン単位)
int16 yCoordinatey の値 (デザイン単位)
Offset16 xDeviceOffset(非可変フォント時) Device テーブルへのオフセット位置。
(可変フォント時) VariationIndex テーブルへのオフセット位置。

Anchor テーブルの先頭を 0 とする。0 の場合あり。
Offset16 yDeviceOffsetY 方向
LookupType 1 (単一グリフの調整)
LookupType 1 の場合の Lookup サブテーブルです。

単一のグリフの調整を行います。
サブテーブルのフォーマットは、2種類あります。

Coverage テーブルでグリフを検索し、ValueRecord の値で位置を調整します。

Format 1
すべてのグリフに対して同じ値を使います。

uint16 posFormatフォーマット番号 = 1
Offset16 coverageOffsetCoverage テーブルへのオフセット位置。
(サブテーブルの先頭を 0 とする)
uint16 valueFormatValueRecord のフォーマットフラグ値
ValueRecord valueRecordすべてのグリフに適用する調整値

Format 2
各グリフごとに ValueRecord が定義されています。

uint16 posFormatフォーマット番号 = 2
Offset16 coverageOffsetCoverage テーブルへのオフセット位置
uint16 valueFormatValueRecord のフォーマットフラグ値
uint16 valueCount配列の数。
Coverage テーブルのグリフ数と一致している。
ValueRecord valueRecords[valueCount]ValueRecord の配列
出力例
▼ 'halt'

[1404] (〈) U+3008 [xPlacement:-500, xAdvance:-500, ]
[1406] (《) U+300A [xPlacement:-500, xAdvance:-500, ]
[1408] (「) U+300C [xPlacement:-500, xAdvance:-500, ]
[1410] (『) U+300E [xPlacement:-500, xAdvance:-500, ]
[1412] (【) U+3010 [xPlacement:-500, xAdvance:-500, ]
..
[1397] (、) U+3001 [xAdvance:-500, ]
[1398] (。) U+3002 [xAdvance:-500, ]

halt」は、横書きにおいて、全角幅のグリフを半角幅に調整します。

かっこの場合は、左に余白があるため、xPlacement でグリフの x 位置を -500 し、xAdvance で、次のグリフへの x 移動距離を -500 します。
LookupType 2 (二つのグリフの位置調整) - format 1
LookupType 2 では、ペアとなる二つのグリフの調整を行います。

サブテーブルのフォーマットは、2種類あります。
まずは Format 1 から。
Format 1
Coverage テーブルでペアの最初のグリフを検索し、PairSet のリストから2番目のグリフを検索。
PairValueRecord の2つの ValueRecord で各グリフの位置を調整します。

uint16 posFormatフォーマット番号 = 1
Offset16 coverageOffsetCoverage テーブルへのオフセット位置。
ペアの最初のグリフを探すためのデータ。
uint16 valueFormat1最初のグリフの ValueRecord フォーマットフラグ値。
0 の場合あり。
uint16 valueFormat22番目のグリフの ValueRecord フォーマットフラグ値。
0 の場合あり。
uint16 pairSetCount配列の数
Offset16 pairSetOffsets[pairSetCount]PairSet テーブルへのオフセット位置。
(サブテーブルの先頭を 0 とする)

ペアの最初のグリフの Coverage index に対応している。
ペアの最初のグリフに対応する、2番目のグリフと調整値のデータ。

▼ PairSet
uint16 pairValueCount配列の数
PairValueRecord pairValueRecords[pairValueCount]PairValueRecord の配列

▼ PairValueRecord
uint16 secondGlyphペアの2番目のグリフ ID
ValueRecord valueRecord1最初のグリフの調整値 (フラグが 0 なら空)
ValueRecord valueRecord22番目のグリフの調整値 (フラグが 0 なら空)
出力例
▼ 'kern'

[16] (/) U+002F
 + [4] (#) U+0023 (1)[xAdvance:-21, ]
 + [16] (/) U+002F (1)[xAdvance:-82, ]
 + [55] (V) U+0056 (1)[xAdvance:13, ]
 + [81] (p) U+0070 (1)[xAdvance:-19, ]

kern」は、横書き用のカーニングです。
特定のペアの2文字が並んでいる場合、グリフ間の余白を調整します。

「/#」の2文字の場合は、「/」の幅を -21 します。
「//」の2文字の場合は、最初の「/」の幅を -82 します。
LookupType 2 - format 2
「LookupType 2」の「format 2」です。
少々複雑なので、format 1 とは分けました。

ペアとなる各グリフを任意のクラスに定義して、各クラスのグリフをペアとして適用させます。
Format 2
uint16 posFormatフォーマット番号 = 2
Offset16 coverageOffsetCoverage テーブルへのオフセット位置。
ペアの最初のグリフを探すためのデータ。
uint16 valueFormat1最初のグリフの ValueRecord フォーマットフラグ値。
0 の場合あり。
uint16 valueFormat22番目のグリフの ValueRecord フォーマットフラグ値。
0 の場合あり。
Offset16 classDef1Offset最初のグリフの ClassDef テーブルへのオフセット位置。
(サブテーブルの先頭を 0 とする)

ペアの最初の各グリフのクラスを定義する。
クラスが定義されていないグリフはすべて class = 0 とする。
Offset16 classDef2Offset2番目のグリフの ClassDef テーブルへのオフセット位置
uint16 class1Count最初のグリフのクラスの数 (class = 0 を含む)。
1〜9 のクラス番号が定義されていたら、10 となる。
uint16 class2Count2番目のグリフのクラスの数 (class = 0 を含む)
Class1Record class1Records[class1Count]Class1Record の配列。
ペアの最初の各クラスのデータ。

▼ Class1Record
Class2Record class2Records[class2Count]Class2Record の配列。
ペアの2番目の各クラスのデータ。

▼ Class2Record
ValueRecord valueRecord1最初のグリフの ValueRecord。空の場合あり。
ValueRecord valueRecord22番目のグリフの ValueRecord。空の場合あり。
ClassDef
GDEF でも使われるテーブルです。
指定された範囲のグリフを、指定したクラスに定義します。

クラス値は「1〜」の任意の数値で、クラスが定義されていないグリフは、すべて class = 0 となります。

Format 1
「startGlyphID 〜 startGlyphID + glyphCount - 1」のグリフ ID に、クラス値の配列から値を割り当てます。

uint16 classFormatフォーマット番号 = 1
uint16 startGlyphIDclassValueArray の最初のグリフ ID
uint16 glyphCount配列の数
uint16 classValueArray[glyphCount]各グリフの、クラス値の配列

Format 2
指定範囲のグリフ ID に、同じクラス値を割り当てます。

uint16 classFormatフォーマット番号 = 2
uint16 classRangeCount配列の数
ClassRangeRecord classRangeRecords[classRangeCount]ClassRangeRecord の配列。
startGlyphID の小さい順に並んでいる。

▼ ClassRangeRecord
uint16 startGlyphID範囲の最初のグリフ ID
uint16 endGlyphID範囲の最後のグリフ ID
uint16 class範囲のグリフに適用されるクラス値
使い方
まず、Coverage テーブルから、ペアの最初のグリフを探します。

次に、classDef1Offset のオフセット位置にある ClassDef テーブルから、ペアの最初のグリフのクラス値を取得します。
Coverage テーブルに存在して、ClassDef テーブルに存在しないグリフの場合は、class = 0 となります。

なお、クラス値は、このサブテーブルでのみ意味のある値であり、任意の数字を設定することができます。

次に、classDef2Offset のオフセット位置にある ClassDef テーブルから、ペアの2番目のグリフを検索し、クラス値を取得します。

各グリフのクラス値を「class1」「class2」とした場合、
class1Records[class1] の位置の class2Records[class2] から、ValueRecord を取得します。

class1Records・class2Records の配列は、class = 0 のデータも含んでいるので、クラス値をそのまま添字として使います。
出力例
## Coverage ##

[4] (#) U+0023
[7] (&) U+0026
[16] (/) U+002F
[32] (?) U+003F
[33] (@) U+0040
...

##
classDef1Offset: 39494
classDef2Offset: 39732
class1Count: 40
class2Count: 68

## ClassDef 1 ##

[4] (#) U+0023 <class 30>
[7] (&) U+0026 <class 17>
[16] (/) U+002F <class 35>
[32] (?) U+003F <class 32>
[33] (@) U+0040 <class 18>
...

## ClassDef 2 ##

[3] (") U+0022 <class 14>
[8] (') U+0027 <class 14>
[9] (() U+0028 <class 11>
[10] ()) U+0029 <class 12>
[11] (*) U+002A <class 17>
[13] (,) U+002C <class 36>
[14] (‑) U+2011 <class 10>
...

##

class1[0] + class2[0] | (1)[xAdvance:0, ]
class1[0] + class2[1] | (1)[xAdvance:-63, ]
class1[0] + class2[2] | (1)[xAdvance:-10, ]
class1[0] + class2[3] | (1)[xAdvance:-38, ]
class1[0] + class2[4] | (1)[xAdvance:-48, ]
LookupType 3 (cursive グリフの接続)
「LookupType 3」のサブテーブルのフォーマットは1つのみです。

アルファベットが流れるように繋がっている cursive (筆記体) フォントにおいて、
前のグリフの出口ポイントと、次のグリフの入口ポイントを接続させることで、グリフを繋げるためのデータです。

ただし、グリフ間の余白調整ならカーニングでも出来ますし、GPOS を使わないとグリフが繋がらないような設計だと、対応しているソフトウェアが限られてくるため、あまり実用的ではありません。

LookupType 3 を使っているフォントはあまりないと思うので、フォーマットのみ解説しておきます。


uint16 posFormatフォーマット番号 = 1
Offset16 coverageOffsetCoverage テーブルへのオフセット位置
uint16 entryExitCount配列の数
EntryExitRecord entryExitRecord[entryExitCount]EntryExitRecord の配列

▼ EntryExitRecord
Offset16 entryAnchorOffset入口の Anchor テーブルへのオフセット位置。
(サブテーブルの先頭を 0 とする)
0 の場合あり。
Offset16 exitAnchorOffset出口の Anchor テーブルへのオフセット位置。
(サブテーブルの先頭を 0 とする)
0 の場合あり。
LookupType 9 (Offset32)
GSUB の LookupType 7 と同じで、サブテーブルを Offset32 の位置に置くためのデータです。

uint16 posFormatフォーマット番号 = 1
uint16 extensionLookupType実体の LookupType 値
Offset32 extensionOffset実体のサブテーブルへのオフセット位置。
(このサブテーブルの先頭を 0 とする)