[61] [DFN[[CODE[GSUB]]]] は[[グリフ]]の置き換えを、
[DFN[[CODE[GPOS]]]] は[[グリフ]]の位置調整に関する情報を記述する
[[OpenType]] [[フォント]]の[[表][OpenType表]]です。

[104] 
[DFN[[CODE[JSTF]]]]
は [[justification]] のための[[グリフ]]の置き換えや位置調整に関する情報を記述する
[[OpenType]] [[フォント]]の[[表][OpenType表]]です。

* 仕様書

[REFS[
-
[12] 
[CITE@ja-jp[OpenType layout common table formats (OpenType 1.9) - Typography | [[Microsoft]] Docs]], [[PeterCon]], [TIME[2022-08-29T13:36:14.000Z]] <https://docs.microsoft.com/ja-jp/typography/opentype/spec/chapter2#features-and-lookups>
-
[67] 
[CITE@ja-jp[OpenType layout common table formats (OpenType 1.9) - Typography | [[Microsoft]] Docs]], [[PeterCon]], [TIME[2022-08-30T11:17:29.000Z]] <https://docs.microsoft.com/ja-jp/typography/opentype/spec/chapter2#lookup-table>
-
[72] 
[CITE@ja-jp[OpenType layout common table formats (OpenType 1.9) - Typography | [[Microsoft]] Docs]], [[PeterCon]], [TIME[2022-08-30T11:42:01.000Z]] <https://docs.microsoft.com/ja-jp/typography/opentype/spec/chapter2#common-structures-for-contextual-lookup-subtables>
- [1] 
[CITE@en-us[GSUB — Glyph Substitution Table (OpenType 1.9) - Typography | Microsoft Docs]], [[PeterCon]], [TIME[2022-08-13T05:25:57.000Z]] <https://docs.microsoft.com/en-us/typography/opentype/spec/gsub>
-
[77] 
[CITE@ja-jp[[[GPOS]] — Glyph Positioning Table (OpenType 1.9) - Typography | Microsoft Docs]], [[PeterCon]], [TIME[2022-09-04T12:39:10.000Z]] <https://docs.microsoft.com/ja-jp/typography/opentype/spec/gpos>
-
[103] 
[CITE@ja-jp[[[JSTF]] — Justification Table (OpenType 1.9) - Typography | Microsoft Docs]], [[PeterCon]], [TIME[2022-09-08T09:23:07.000Z]] <https://docs.microsoft.com/ja-jp/typography/opentype/spec/jstf>


]REFS]

* 意味

[89] 
[CODE[GSUB]] は [DFN[Glyph Substitution]] の意です。
[SRC[>>1]]

[76] 
[CODE[GPOS]] は [DFN[Glyph Positioning]] の意です。
[SRC[>>77]]

* 構造

[5] 
[CODE[GSUB]] には[[グリフ]]の置き換えのための対応表データが入っていて、
[CODE[GPOS]] には[[グリフ]]の利用に関係する[[座標]]等のデータが入っています。

[4] 
[CODE[GSUB]],
[CODE[GPOS]]
とも似たような構造になっています。
末端のデータ等細部が微妙に違っているので注意が必要です。

[62] 
どちらも、
まず対象となる[[用字系]]と[[言語系]]について、
それに関わる[[機能][フォント機能]]がいくつかあって、
[[機能][フォント機能]]における [[lookup]] がいくつかあり、
その [[lookup]] に条件と処理が書かれているという構造です。

[105] 
[CODE[JSTF]] 
は対象となる[[用字系]]と[[言語系]]について、
適用可能な調整がいくつかあるという構造です。
[CODE[JSTF]]
も
[[lookup]] を参照したり、含めたりできますが、
[CODE[GSUB]] や [CODE[GPOS]] とは構造が違います。


* 処理

[58] 表示処理全体の中での位置は[[文字のレンダリング]]参照。

[60] [[機能][フォント機能]]の選択方法は[[機能][フォント機能]]参照。

[85] 
[CODE[GSUB]] の
[[lookup]] の条件に一致した場合は[[グリフ]](列)を[[グリフ]](列)に置き換えることになります。
このときどれがどれに変化したかはある程度追跡できる
(元の[[グリフ]](列)を得た大元の[[文字列]]との対応関係を知れる)
必要があります。
[SEE[ [[文字のレンダリング]] ]]

[81] 
[CODE[GPOS]] の
[[lookup]] の条件に一致した場合に行うべき処理や
[[JstfMax lookup]] の解釈は、
[[グリフ位置決定]]を参照。

[159] 
[[shaping]] において[[連なり]]に分割してから処理されるために、
[[用字系]]が違うと
[CODE[GSUB]]
が適用されないことがあるようです。
[SEE[ [[連なり]] ]]


* 機能

[8] 
[CODE[GSUB]] [[表]], 
[CODE[GPOS]] [[表]]は[[用字系]]や[[言語系]]に対して適用可能な[[機能][フォント機能]]を記述できます。
[SEE[ [[フォント機能]] ]]

[59] 
[[機能][フォント機能]]に対して、実施されるべき操作を [[lookup]] として記述できます。

[46] 
[CODE[GSUB]] [[表]], 
[CODE[GPOS]] [[表]]には同じ[[タグ][機能タグ]]の[[機能][フォント機能]]を複数個含められます。
[[用字系]]や[[言語系]]に対してどの[[機能]]を適用するかを選択できますから、
同じ[[機能][フォント機能]]でも[[用字系]]と[[言語系]]に合わせて違う [[lookup]]
が適用されるようにできます。

* lookup

[144] 
[DFN[lookup]] には[[グリフ]]列の一部分について一致するかしないかの条件と、
一致した場合に行う[[グリフ]](列)置換または[[グリフ]]の位置調整を記述できます。

[145] 
[CODE[GSUB]] [[表][OpenType表]]と [CODE[GPOS]] [[表][OpenType表]]では、
それぞれの [DFN[[F[[CODE[LookupList]]]]]] 内に任意個含められます。
[SRC[>>103]]

[146] [F[[CODE[LookupList]]]] 中の [[lookup]] の出現順序 ([[索引]]) は、

- [147] 同じ[[表][OpenType表]]の[[機能][OpenType]]から参照するため
- [148] 同じ[[表][OpenType表]]の他の [[lookup]] から参照するため
- [149] [CODE[JSTF]] [[表][OpenType表]]の提案から参照するため
- [150] [[lookup]] 相互の適用順序を決めるため

... に使われます。

[151] 
[CODE[JSTF]] [[表][OpenType表]]では、
各提案に行を長くする用と短くする用で各1つまで含められます。
[SRC[>>103]]

[152] 
同じ [[lookup]] と呼ばれますが、
[CODE[GSUB]] [[表][OpenType表]]では[[グリフ]](列)の置換の記述のため、
[CODE[GPOS]] [[表][OpenType表]]では[[グリフ]]の位置調整の記述のため、
と少し構造が違います。
一致条件の部分が同じような意味、同じような構造でも、
[F[[CODE[lookupType]]]]
が両者で違っています。


-*-*-

[63] 
[CODE[GSUB]] や [CODE[GPOS]] を適用するには、
[[用字系]], [[言語系]], [[機能][フォント機能]]から利用する
[[lookup]] 
を決定します。
[SEE[ 選び方は[[フォント機能]] ]]

[64] 
複数の [[lookup]] を適用する場合、
適用順序は
[CODE[LookupList]]
の出現順とします。
[SRC[>>12, >>67, >>1]]
1つの 
[[lookup]]
で[[グリフ]]列を先頭から末尾まで処理し、
次の
[[lookup]] 
へと進みます。
[SRC[>>67, >>1]]
ただし
[CODE[GSUB]] [F[[CODE[lookupType]]]] [N[8]]
は[[グリフ]]列の末尾から先頭に向かって処理します。
[SRC[>>1]]

[65] 
[[フォント]]開発者は、
複数の[[機能][フォント機能]]を同時適用するときの相互作用を考慮して
[CODE[LookupList]] 
中の [[lookup]] の順序を決められます。
1つの[[機能][フォント機能]]に複数の [[lookup]]
を結びつけることが出来ますから、
サンドイッチ式の適用も記述できます。

;; [66] ただし、全体の処理において[[機能][フォント機能]]ごとに複数回に分けて適用する場合もあり
[SEE[ [[フォント機能]] ]]、
その場合は[[フォント]]開発者の制御が及ばなくなります。

[68] 
同時に複数の[[機能][フォント機能]]を適用する場合、
それらの [[lookup]] の[RUBYB[[[和集合]]][union]]を使います。
[SRC[>>67]]
つまり重複した [[lookup]] があるときは、1回分だけ適用されます。

[69] 
なお、[[機能][フォント機能]]ごとに異なる部分[[グリフ]]列にのみ適用することができます。
[SRC[>>67]]

[70] 
同じ [[lookup]] で異なる部分[[グリフ]]列に適用されるべきときはどうなるのでしょう。
適用対象の[[グリフ]]も[[和集合]]になるのでしょうか。

[167] 
[[lookup]] には複数の subtable を含めることができます。
[[lookup]] の順序は処理結果に影響しますが、
subtable の順序が影響するのかどうか (が明示的に決められているのかどうか)
よくわかりません。

[168] 
[[Windows]] の [[Chrome]] で実際に試してみると、
1つの [[lookup]] に複数の subtable があるとき、
そのすべてのうち最初に一致するものが適用されるようで、
[[lookup]] の順序とは扱いが違うようです。
例えば subtables[0] に BC -> X があり、
subtables[1] に AB -> Y があって 
ABC が入力されたとき、 YC になるようです。
[TIME[2025-01-27T12:34:27.600Z]]


[169]
[[Windows]] の [[Chrome]] や [[Firefox]] で実際に試してみると、
同じ [[lookup]] で異なる長さの候補があるとき、最短のものと一致します。
例えば AB -> X, ABC -> Y と置換されるとき、入力が ABC だと出力は XC になります。
これを避けるには lookups[0] に ABC -> Y, lookups[1] に AB -> X 
を入れることになります。
[TIME[2025-01-28T13:58:28.800Z]]

[170] 
>>169 これは [CODE[GSUB]] [F[[CODE[lookupType]]]] [N[4]] の場合なのですが、
仕様書によると LigatureSet 内の先頭から先に優先されるようです。
従って同じ lookup, subtable でも ABC -> Y を先に置けば ABC の出力は Y 
になるはず。

[171] 
また別解として、 [CODE[GPOS]] の nested contexts で長い方を先に選ばせたい時は、
先の subtable に長い方、後の subtable に短い方を指定すれば長い方に先に一致します。
逆にすると短い方にしか一致しません。


[174] 
複数の [CODE[GPOS]] [[lookup]] が同じ[[グリフ]]や[[グリフ]]対に適用されるときの処置は、
仕様上明確ではありません。
実装によって違いがあるそうです。
[[Windows]] の [[Chrome]] ではすべてを加算した結果が適用されるようです。
[TIME[2025-04-05T05:09:28.500Z]]

[175] 
[CODE[GPOS]] [[lookup]] を他の[[機能]]や [[lookup]] 次第の条件付きの適用はできません。
例えば[[機能]] [VAR[F]] が適用されないときだけ適用されるべき [[lookup]]
を指定するようなことはできません。

;; [176] [CODE[GSUB]] で他の[[グリフ]]に置き換えるなどの別の方法での対策が必要となります。

-*-*-

[78] 
[[lookup]] には、
一致条件 (と一致時の挙動) を記述する[[部分表]]を1個[[以上]]含められます。
[SRC[>>77, >>1]]

[79] 
各[[部分表]]はいくつかの種類があって、
[F[[CODE[substFormat]]]]
や
[F[[CODE[posFormat]]]]
によって区別されます。
どの形式によるかは記述方法の違いによるストレージ効率で選べます。
[[部分表]]ごとに違う形式も選べるので、
都合に応じて区分できます。
[SRC[>>77, >>1]]
[[部分表]]の構成の違いは [[lookup]] の挙動には直接影響しません。

;; [102] 
ただし[[機能][フォント機能]]によっては実装状況が悪いのでこの形式でなければならない、
[[部分表]]は1個でなければならない、といった[[バッドノウハウ]]があるようです。
各[[機能][フォント機能]]の項参照。

[80] 
とある入力が1つの [[lookup]] に含まれる複数の条件に一致することは、
明確には認められても禁止されてもいないようです。
その場合の挙動も不明です。
どんな入力にも高々1回だけ一致するようにするべきと思われます。




-*-*-

[13] [[lookup]] 
は,
[CODE[lookupType]]
と
[CODE[substFormat]]
にもよりますが、
[[グリフ]]の列の一致条件を3通り記述できます。すなわち、

- [DFN[[RUBYB[[RUBY[後][あと]][RUBY[戻][もど]]り][backtrack]]]]する[[グリフ]]の0個以上の列
- [DFN[[RUBYB[[RUBY[入][にゅう]][RUBY[力][りょく]]][input]]]]の[[グリフ]]の1個以上の列
- [DFN[[RUBYB[[RUBY[先][さき]][RUBY[読][よ]]み][lookahead]]]]する[[グリフ]]の0個以上の列

[73] 
ここで[[グリフ]]列は[[論理順]]とします。
[SRC[>>72]]

[14] 
[[入力]]の[[グリフ]]列は、指定された処理が施される対象となります。

[15] 
[[後戻り]]は[[入力]]の直前、
[[先読み]]は[[入力]]の直後にあるべき[[グリフ]]列で、
一致するかどうかの判断には使いますが、
処理の対象にはなりません。

[16] 
一致した場合、[[入力]]の次の[[グリフ]]に進んで一致するかどうかの検査を繰り返します
[SRC[>>67, >>77]]。
[[先読み]]列があれば、その先頭からということになります。

[17] 
ただし、
[CODE[GPOS]] の [CODE[lookupType]] [N[2]]
は2つの[[グリフ]]の組に対して値を設定するもので、
常に2つの[[グリフ]]が[[入力]]となるのですが、
第2[[グリフ]]に与えられた値がないときには、
その次の処理の[[入力]]は前の処理の第2[[グリフ]]からとなります。
[SRC[>>67, >>72, >>77]]

;; [18] 1つ [[lookup]] についての処理において、
[CODE[GPOS]] の組の指定の例外を除き、
[[入力]]列を処理して得られた出力に二重に処理が適用されることはありません。

[71] 
[[先読み]]と[[後戻り]]が一致する[[グリフ]]は、
[[機能][フォント機能]]の適用対象となる[[グリフ]]でなくても構いません。
[SRC[>>67]]

[19] 
[[入力]]の第2の[[グリフ]]以降[[先読み]]の最後の[[グリフ]]までの各[[グリフ]]の前、
あるいは[[後戻り]]の各[[グリフ]]の後には、
特定の種類の[[グリフ]]があったとしても、
一致判定においては無視する場合があります。
その条件は
[[lookup]]
の
[F[[CODE[lookupFlag]]]]
で記述されます。

- [20] [[lookup]] の [F[[CODE[lookupFlag]]]] の
[DFN[[CODE[IGNORE_BASE_GLYPHS]]]]
(ビット1)
が指定されている場合、
[CODE[GDEF]] [[表]]の[[グリフ級定義]]で [N[1]] (基底) とされた[[グリフ]]は無視します。
[SRC[>>1]]
- [21] [[lookup]] の [F[[CODE[lookupFlag]]]] の
[DFN[[CODE[IGNORE_LIGATURES]]]]
(ビット2)
が指定されている場合、
[CODE[GDEF]] [[表]]の[[グリフ級定義]]で [N[2]] (合字) とされた[[グリフ]]は無視します。
[SRC[>>1]]
- [22] [[lookup]] の [F[[CODE[lookupFlag]]]] の
[DFN[[CODE[IGNORE_MARKS]]]]
(ビット3)
が指定されている場合、
[CODE[GDEF]] [[表]]の[[グリフ級定義]]で [N[3]] (マーク) とされた[[グリフ]]は無視します。
[SRC[>>1]]
- [28] 
[[lookup]] の [F[[CODE[lookupFlag]]]] の
[CODE[IGNORE_MARKS]]
が指定されていない場合、
-- [25] [[lookup]] の [F[[CODE[lookupFlag]]]] の
[DFN[[CODE[USE_MARK_FILTERING_SET]]]]
(ビット4)
が指定されている場合、
[[lookup]] の [DFN[[F[[CODE[markFilteringSet]]]]]]
を参照します。
[SRC[>>1]]
---
[26] 
[DFN[[CODE[MarkFilteringSet]]]]
は
[CODE[uint16]]
です。
第[VAR[i]]ビットが[[マークグリフ集合群]]の第[VAR[i]]番の集合を表すと思われます。
---
[27] 
指定された[[マークグリフ集合]]に''ない''[[マーク]]グリフは無視します。
[SRC[>>1]]
--- 
[30] ここでいう[[マーク]]は
[CODE[GDEF]] [[表]]の[[グリフ級定義]]で [N[3]] (マーク) とされた[[グリフ]]を指すと思われます。
--[29] 
[[lookup]] の [F[[CODE[lookupFlag]]]] の
[CODE[USE_MARK_FILTERING_SET]]
が指定されていない場合、
--- [23] [[lookup]] の [F[[CODE[lookupFlag]]]] の
[DFN[[CODE[MARK_ATTACHMENT_TYPE_MASK]]]]
(ビット8 - ビット15)
が [N[0]] でない場合、
この16ビットを[[級値]]と解釈します。
[[マーク添付級]]が指定された値では''ない''[[マーク]]グリフは無視します。
[SRC[>>1]]
---- [24] ここでいう[[マーク]]は
[CODE[GDEF]] [[表]]の[[グリフ級定義]]で [N[3]] (マーク) とされた[[グリフ]]を指すと思われます。

;; [82] [F[[CODE[lookupFlag]]]] には他に [F[[CODE[RIGHT_TO_LEFT]]]] 
[[フラグ]]がありますが、機能性は大きく違っています。

[34] 
[[マーク]]系の機能が3つあります。
[[アラビア文字]]のように前後の文字との位置関係によって[[グリフ]]を変化させつつ、
主たる[[グリフ]]に付随する[[マーク]]は[[合字]]化の判定で適宜無視したりしなかったり、
といった条件を記述するために用意されているようです。

[35] 
[[マーク添付級]]と[[マークグリフ集合]]は似たような機能ですが、
[[マークグリフ集合]]の方が記述能力が高い (複雑) です。
[[マークグリフ集合]]の方が後から追加された機能です。
[[マーク添付級]]だけでは不十分ということで追加されたのでしょうか。


[36] 
[[グリフ級]]に基づく無視は 
[CODE[GSUB]]
[CODE[lookupType]] [N[6]] と共にしばしば使われているようです。
[CODE[lookupType]] [N[6]]
では一致した[[入力列]]に更に他の [[lookup]]
を適用することになりますが、
このとき[[入力列]]の何番目の位置の[[グリフ]]であるかに依存して適用する
[[lookup]]
を決めます
([CODE[seqLookupRecords]])。
何番目であるかには、無視された[[グリフ]]を算入しません。
従って無視された[[グリフ]]の有無でどの [[lookup]] がどの[[グリフ]]に適用されるかは変化しませんし、
無視された[[グリフ]]には [[lookup]] が適用されません。
ただし一致した[[入力列]]の[[グリフ]]に対して [[lookup]]
を適用するときに、その [[lookup]] が前後の条件を記述していれば、
無視された[[グリフ]]の有無がその結果に影響を与える可能性はあります。


[37] 
[CODE[GSUB]]
[CODE[lookupType]] [N[4]] (複数の[[グリフ]]の列から1つの[[合字]]への置き換え)
での利用も禁止されているわけではありませんが、
その意味するところは定かではありません。
無視して読み飛ばした[[グリフ]]も含めて[[入力列]]の全体が置き換えられるべきなのでしょうか?

[EG[

[177] 
例えば[[基底グリフ]] [VAR[B]] と[[マークグリフ]] [VAR[M]], [VAR[N]]
があるとして、
入力が

- [180] [VAR[B]], [VAR[M]]
- [178] [VAR[B]], [VAR[M]], [VAR[N]]
- [179] [VAR[B]], [VAR[N]], [VAR[M]]

のどれであっても [VAR[B]], [VAR[M]] を [VAR[C]] に置き換えたいとします。
このとき無関係の [VAR[N]] は邪魔です。 [VAR[N]] を含めた置換パターンを用意するのは手間がかかる上に、
組み合わせが多いと爆発的に必要な条件数が増加します。

[181] 
このとき mark filtering set で [VAR[M]] を含み [VAR[N]] は含まないよう指定すると、
[VAR[N]]
はないものとして条件を書けます。

[182] 
入力が >>178 と >>179 のどちらであっても、出力は [VAR[C]], [VAR[N]] になります。
条件との一致判定で「無視」された [VAR[N]] は消え去るわけではなく結果には残ります。


]EG]

[54] 
[CODE[GPOS]] [CODE[lookupType]] [N[2]] ([[グリフ]]の組に対する位置調整)
での利用も禁止されているわけではありませんが、
どう適用されるべきなのか明確ではありません。
第1グリフは適用対象の[[グリフ]]の範囲が指定されますが、
第2グリフは任意(すべて)の[[グリフ]]が対象となり得ますから、
読み飛ばしとの相互作用をどうするべきか、
処理結果にいくつかの解釈が存在し得ます。

[56] 
[CODE[GPOS]] [CODE[lookupType]] [N[4]], [N[5]], [N[6]] 
は付加する[[マークグリフ]]と[[基底グリフ]], [[合字グリフ]], 
基底となる[[マークグリフ]]との位置関係を調整するものです。
[[入力]]は付加しようとする[[マークグリフ]]です [SRC[>>77]]。

-
[83] 
[CODE[GPOS]] [F[[CODE[lookupType]]]] [N[4]]
(Mark-to-Base Attachment Positioning, MarkBasePos)
では、
[[マークグリフ]]に対し、
[[グリフ]]列を遡って[[基底グリフ]]を探さなければ[RUBYB[なりません][must]]。
[SRC[>>77]]
-
[84] 
[CODE[GPOS]] [F[[CODE[lookupType]]]] [N[5]]
(Mark-to-Ligature Attachment Positioning, MarkLigPos)
では、
[[マークグリフ]]に対し、
[[グリフ]]列を遡って[[合字グリフ]]を探さなければ[RUBYB[なりません][must]]。
[SRC[>>77]]
-
[86] 
[CODE[GPOS]] [F[[CODE[lookupType]]]] [N[6]]
(Mark-to-Mark Attachment Positioning, MarkMarkPos)
では、
付加する[[マークグリフ]] mark1 に対し、
[[グリフ]]列を遡って基底となる[[マークグリフ]] mark2
を探します。
[SRC[>>77]]

[88] 
[N[6]] では [F[[CODE[lookupFlag]]]] に応じて[[マークグリフ]] mark2 を探します。
[SRC[>>77]]

[87] 
[N[4]] と [N[5]] も読み飛ばしとの相互作用がどう扱われるのが想定される挙動なのか不明瞭です。

-*-*-

[31] 
[[入力列]],
[[先読み列]],
[[後戻り列]]として指定できる[[グリフ]]の列の長さ ([[グリフ]]の数)
には上限が規定されていません。
[[表]]に於いて個数を表す値が [CODE[uint16]] なので、
2[SUP[16]] - 1 が構造上の上限となります。
無視して読み飛ばす[[グリフ]]の個数にも上限が規定されていません。

[32] 
実際の[[自然言語]]の記述に使うための[[フォント]]でそこまで長いものが必要にはなり得ませんから、
それよりずっと小さな、しかし十分大きな個数で探索を打ち切るような実装が普通でしょうし、
それをしないで杜撰に実装すると[[セキュリティー][文字のセキュリティー]]の問題にもなり得ます。

[33] 
実際上どれくらいの長さが必要なのでしょうかね。
16個では少し心もとない気がします。
128個だと過大な気がします。


-*-*-

[51] 
[CODE[GSUB]]
[CODE[lookupType]] [N[5]] は [CODE[lookupType]] [N[6]] に容易に書き換えられるようです。
[N[5]] の利用例が少ないのは [N[5]] の機能では不十分な場合が多いのでしょう。

[55] 
[CODE[GPOS]]
は[[カーニング]]で
[N[2]] 
が使われていることが多いようです。
[CODE[GPOS]]
[CODE[lookupType]] 
[N[1]], [N[2]] しか対応していない実装もあります。

-*-*-

[47] 
[[lookup]]
は[[機能]]から参照されて適用されるものと、
他の 
[[lookup]]
から参照されるものとがあります。

[48] 
[[入れ子]]の [[lookup]] は 
[CODE[GSUB]]
[CODE[lookupType]] [N[1]] が多いですが、
[N[2]] の例もあります。

[49] 
仕様書には[[入れ子]]にできる [CODE[lookupType]] の制限は特にないようですが、
どれも適用できるのか不安感はあります。

[52] 
[CODE[GSUB]]
[CODE[lookupType]] [N[4]], [CODE[substFormat]] [N[1]] 
を使った事例があります。
このとき入力の複数の隣接した[[グリフ]]が[[合字]]化された1つの[[グリフ]]に置き換えられることがあります。

[53] 
もしその場合に[[入力列]]側で無視される[[グリフ]]が間に挟まっていたらどう処理されるべきなのか、
[[入れ子]]の [[lookup]] で使われる [CODE[sequenceIndex]] が指すのが[[合字]]化により消失する[[グリフ]]のときどう処理されるべきなのか、
よくわかりません。

[50] 
実装によっては 
[CODE[GSUB]]
[CODE[lookupType]] [N[1]], [CODE[substFormat]] [N[2]]
しか対応していないこともあるようです。

[57] 
[[入れ子]]の [[lookup]] と >>17 の挙動の関係も若干不明瞭です。
[[入れ子]]の [[lookup]] でも適用される旨が仕様書には書かれている
[SRC[>>72]]
のですが、どういうイメージなのでしょう。

[74] 
[[入れ子]]を二重以上深くできるのか不明です。仕様書では特に禁止はしていませんが。

[154] 
[[JstfMax lookup]] は [CODE[GPOS]] [[lookup]] の [F[[CODE[lookupType]]]]
のうち contextual positioning lookups 以外を使えます。 [SRC[>>103]]
つまり[[入れ子]]に [[lookup]] するものは認められていないようです。
(指定された場合にどう処理するべきなのかは不明。)

* 文脈

[6] 
[CODE[GSUB]],
[CODE[GPOS]]
とも必須の[[表]]ではなく、
不要な[[フォント]]には入っていません。
どちらかだけの[[フォント]]もあります。

[7] 
といっても使わないで済むのは[[欧米]]や[[東アジア]]の昔ながらの簡易的な表示に使う[[フォント]]くらいのものです。
それ以外の地域で使われる[[文字]]の多くは
[CODE[GSUB]] や [CODE[GPOS]]
の機能が必要です。
[[欧米]]や[[東アジア]]の[[文字]]も、[[カーニング]]や[[縦書き]]や[[合字]]など、
高品質
[WEAK[(現在の計算機環境では標準的に実現されているレベルも含む。)]]
な[[文字のレンダリング]]にはそうした機能が必要となります。

* [CODE[GSUB]] 代替グリフの選択

[90]
[CODE[GSUB]] の [[lookup]] (群) を適応する処理は、
原則的に入力が1つの[[グリフ]]列、
出力も1つの[[グリフ]]列です。

[91] 
ただし一部の [F[[CODE[lookupType]]]] (を使う一部の[[機能][フォント機能]])
は、 [CODE[GSUB]] [[lookup]] の適用結果に複数の候補を示すことが出来ます。

[EG[
[93] 例えば[[機能][フォント機能]] [CODE[salt]] は異なるデザインの多数の[[グリフ]]を提示できます。

]EG]

[92] 
複数の結果になるのが最後の [[lookup]] なら複数通りの最終出力の可能性があり得ますし、
最後でない [[lookup]] ならそれ以降の [[lookup]] で最終出力の違いが更に広がっていく可能性があります。
1つの[[グリフ]]列中にそのような箇所が複数あれば、
最終出力はそれぞれの選択のすべての組合せ分あり得ることになります。

[94] 
[CITE[[[OpenType]]]] 仕様書の[[機能タグ]]登録簿の記述によれば、
そうした[[機能][フォント機能]]は
[[DTP]] ソフトウェアの編集画面などに[[グリフ]]ごとに他の選択肢を表示して、
[[利用者]]に適切なものを選ばせることが想定されているようです。


[95] 
1度限りの[[印刷]]なら選んで終わりで済みますが、
[[情報交換]]や長期保存のためには選択結果を恒久的に固定する方法が必要となります。

- [96] 
1つは[[グリフID]]など[[グリフ]]を直接特定する方法です。
[SEE[ [[グリフ]] ]]
-- [101] この場合 [CODE[GSUB]] は編集中の[[グリフ]]検索の手段として用いただけで、
以後の参照には関係しないことになります。
- [97] 
もう1つは得られた複数の候補の中で第何番目かを指定する方法です。
-- [98] 
[[CSS Fonts]] ではこの方法が使われています。

[99] 
どちらも[[フォント]]の改訂などで構造が変わってしまうと指していた[[グリフ]]が変わってしまうリスクはあります。
もっとも[[フォント]]の改訂はどの[[グリフ]]も前のものとの「同一性」が保たれる保証はないので、
あまり気にしても仕方がないかもしれません。互換性を気にする[[フォント]]開発者なら気を使ってくれるはずです。
それが期待できないなら[[フォント]]を[[埋め込む]]など、
利用[[フォント]]含め転送・保存するしかありません。

;; [100] 
異なる[[フォント]]で[[グリフID]]や候補リスト中の位置が共通化されることはほぼ期待できません。
同じ[[フォント]]提供者による別の[[フォント]]なら、多少期待してもいいかもしれない、
という程度です。
[[グリフ]]特定に [[CID]] が使える (そして[[標準化]]された [[CID]] が振られている)
なら別ですが...

[183] 
[CODE[jp78]]
では、最初の選択肢が好ましい形であることが定められています。
[SEE[ [[jp78]] ]]

[155] 
[CITE@ja[IllustratorとInDesignでaaltのルールが異なる件 - 帰ってきた💫Unicode刑事〔デカ〕リターンズ]], [TIME[2022-10-10T08:01:48.000Z]] <https://moji-memo.hatenablog.jp/entry/20070720/1184917140>

>Illustratorの「aalt 0」って、InDesignで言うところの「aalt 1」(CID順で1番目の異体字)のことだと思うのだが、なぜ別のルールを採用しているのだろう。

* [CODE[JSTF]] の調整

[106] 
[[justification]] は期待される[[行]]長と実際の[[行]]の内容の長さの関係を調整する処理です。
[[グリフ]]間の[[スペース]]量を加減したり、[[グリフ]]を[[合字グリフ]]に置き換えたりします。

[107] 
[CODE[JSTF]] 
にはその[[フォント]]に適した調整方法と調整量の提案を何段階かに分けて記述できます
[SRC[>>103]]。
[[文字のレンダリング]]の際にはこれを参照してその[[フォント]]に適した
[[justification]] を実装できます。
[CITE[[[OpenType]]]] は具体的な [[justification]] の手法までは[[規定]]していません。
[[行]]長をもっと減らしたいとき、増やしたいときにどの[[グリフ]]をどうすればいいかを取得できますが、
もっと増やしたい、減らしたいという判断は別途行わないといけません。

[108] 
[CODE[JSTF]] には、 [[用字系]]と[[言語系]]に対して、
適用可能な提案のリストが含まれます。
リスト中の各提案は、 priority level (影響の小さい順、「悪くない」順)
に並べて格納します。
適用時には、初めの提案から順に必要な提案まで適用していきます。
[SRC[>>103]]

[126] 
各提案には次のデータを含められます。
[SRC[>>103]]

- [127] line shrinkage 用の [CODE[GSUB]] で有効にする [[lookup]] 群
- [128] line shrinkage 用の [CODE[GSUB]] で無効にする [[lookup]] 群
- [129] line shrinkage 用の [CODE[GPOS]] で有効にする [[lookup]] 群
- [130] line shrinkage 用の [CODE[GPOS]] で無効にする [[lookup]] 群
- [141] line shrinkage 用の [[JstfMax lookup]]
- [131] line extension 用の [CODE[GSUB]] で有効にする [[lookup]] 群
- [132] line extension 用の [CODE[GSUB]] で無効にする [[lookup]] 群
- [133] line extension 用の [CODE[GPOS]] で有効にする [[lookup]] 群
- [134] line extension 用の [CODE[GPOS]] で無効にする [[lookup]] 群
- [142] line extension 用の [[JstfMax lookup]]

[135] [CODE[GSUB]] や [CODE[GPOS]] の [[lookup]] は、
それぞれの[[表][OpenType表]] の [F[[CODE[LookupList]]]]
における[[index]]により指定します。
[SRC[>>103]]

[143] 
[[JstfMax lookup]] ([[justify lookup]]) は
[CODE[JSTF]] [[表][OpenType表]]に直接記述します。
[SRC[>>103]]

[111] 
提案を適用した調整は、次のようにします。
[SRC[>>103]]

[FIG(steps)[

= [139] [VAR[フォント]]を入力の[[フォント]]とします。
= [112] [VAR[原グリフ列]]を入力の[[グリフ]]列とします。
= [113] [VAR[機能群]]を入力の[[機能]]群とします。
= [137] [VAR[用字系]]を入力の[[用字系]]とします。
= [138] [VAR[言語系]]を入力の[[言語系]]とします。
= [136] [VAR[要望]]を入力の [I[line shrinkage]] または [I[line extension]] 
とします。
= [116] [VAR[提案群]]を[VAR[フォント]]の [CODE[JSTF]] 
[[表][OpenType表]]において[VAR[用字系]]と[VAR[言語系]]から決まる提案群とします。
= [114] [VAR[提案]]を、空の提案に設定します。
= [115] 繰り返し、
== [123] [VAR[lookup群]]を、 
[VAR[フォント]]から[VAR[機能群]]の [[lookup]] を集めて整列した結果に設定します。
([[variable font]] の変更を踏まえて実際に適用される [[lookup]] を選びます。)
== [124] [VAR[lookup群]]を[VAR[提案]]と[VAR[要望]]に基づき編集します。
== [117] [VAR[グリフ列]]を、[VAR[原グリフ列]]に[VAR[lookup群]]を適用した結果に設定します。
== [118] [VAR[グリフ列]]の長さが適当と判断される場合、
=== [119] [VAR[グリフ列]]を返してここで停止します。
== [121] [VAR[提案群]]の次の提案がもうない場合、
=== [122] [VAR[グリフ列]]を返してここで停止します。
== [120] [VAR[提案]]を、[VAR[提案群]]の次の提案に設定します。

]FIG]

[125] 
実際には1つの[[行]]は[[フォント]]や[[書字方向]]が違う複数の[[連なり]]で構成されているかもしれませんし、
[[機能][フォント機能]]はすべて同時に適用するのではなく [[shaping]]
過程で複数回に分けて適用されたり、[[グリフ]]列の一部分のみに適用されたりするのでしょうから、
ここに示した[[手順群]]がそのまま実行できるとは限りません。
[[追い込み]]や[[追い出し]]のため[[連なり]]を再構成する場合もあるかもしれません。
[[HTML]] の[[子要素]]が[[行内]]の[[箱]]になって含まれる場合のように、
[[連なり]]以外の[[オブジェクト]]が[[行]]に入っていること、
その[[オブジェクト]]内部の構造も再構成に影響してくることがあります。

[161] 
実際の[[手順群]]の構成は [[shaping]] の設計や [[justification]] 
周りのアルゴリズムに依存します。
しかし [CODE[JSTF]] の指定の適用に関わる部分だけを眺めれば、
およそこのような[[手順群]]に要約できるものと考えられます。

[153] [[JstfMax lookup]] で設定された値の利用については[[グリフ位置決定]]を参照。

-*-*-

[109] 
[CODE[JSTF]] には[[用字系]]ごとに[RUBYB[拡張子グリフ][extender glyph]]のリストも指定できます。
[[justification]] の処理では各提案の他にこの[[グリフ]]も使えます。
[SRC[>>103]]

[EG[
[110] 例えば[[アラビア文字]]の [[kashida]] をリストに含められます。
[SRC[>>103]]
]EG]

[140] 
[CITE[OpenType]] 仕様としてはリストを指定できるだけで、その使い方は決められていません。

* 関連

[75] 
実装の効率化のため、
[CODE[GPOS]] による[[添付点]]の情報を [CODE[GDEF]] に要約して記述することが出来ます。

[156] 
似たもので組み合わせを指定するだけの [CODE[MERG]] があります。


* 実例

[10] 各 [[feature]] の利用例は [[font feature]] 参照。

[41] 参考: [[将軍様専用文字]]

[9] 
[CITE[[[Nishiki-teki]]]]
は
[CODE[GSUB]],
[CODE[GPOS]]
ともに
[[script]]
が多数あって、
[CODE[langSysRecords]]
をも複数使っています。

また
[CODE[GSUB]],
[CODE[GPOS]]
ともに同じ [[feature]] tag の別の feature list item があります。


- [39] [CITE@ja['''['''こかげ''']''' フォント : Nu みちしるべ]], [TIME[2020-11-05T13:34:49.000Z]], [TIME[2022-08-18T05:18:48.827Z]] <http://kokagem.sakura.ne.jp/font/michishirube/>
--[40] [CITE@ja[「Nu みちしるべ」フォントの中身を見る - omuronの備忘録]], [TIME[2022-08-18T05:19:07.000Z]] <https://omuron.hateblo.jp/entry/2020/11/07/142500>

-[42] [CITE@en[GitHub - ayaka14732/FanWunMing: A Simplified-Chinese-to-Traditional-Chinese font based on GenYoMin, which can handle the one-to-many problem | 繁媛明朝是基於源樣明體開發的簡轉繁字型,能處理一簡對多繁]], [TIME[2022-08-18T05:28:22.000Z]] <https://github.com/ayaka14732/FanWunMing>
--[43] [CITE@zh-HK[正確實現簡轉繁字型]], [[三日月綾香]], [TIME[2022-07-05T04:28:59.000Z]], [TIME[2022-08-18T05:29:34.570Z]] <https://ayaka.shn.hk/s2tfont/hant/>

[45] >>42
[CODE[GSUB]]
1, 2; 3, 1
[CODE[GPOS]]
1, 1; 1, 2; 9, 1 (2, 1)

[44] 
[CITE@en[โครงการอักษรอีสาน]], [TIME[2022-03-22T07:25:24.000Z]], [TIME[2022-08-18T05:37:35.328Z]] <https://linux.thai.net/~thep/esaan-scripts/>

[CITE[Khottabun]]

[CODE[GSUB]]
2, 1; 4, 1; 5, 2; 6, 1; 6, 2

([[入れ子]]の lookup が 2, 1; 4, 1)

[CODE[GPOS]]
4, 1; 6, 1



[157] [CITE@zh-HK[正確實現簡轉繁字型]], [[三日月綾香]], [TIME[2023-04-11T04:19:14.000Z]], [TIME[2023-07-03T14:02:11.747Z]] <https://ayaka.shn.hk/s2tfont/hant/>

[158] >>157 [[簡体字]]から[[繁体字]]への変換に [CODE[GSUB]] + [CODE[trad]]
を使う。

[160] 
[CITE@ja[Yu Gothic UIに text-spacing-trim を適用するとバグる]], [TIME[2024-03-25T13:44:45.000Z]] <https://zenn.dev/inaniwaudon/scraps/f224417d4c51ee>

-[163] [CITE@en[Some suggestions of symbols in TC · Issue #73 · adobe-fonts/source-han-serif · GitHub]], [TIME[2024-10-15T14:11:19.000Z]] <https://github.com/adobe-fonts/source-han-serif/issues/73>
-[162] [CITE@en[Incorrect placement of bopomofo tone marks in vertical writing mode · Issue #532 · harfbuzz/harfbuzz · GitHub]], [TIME[2024-10-15T13:59:37.000Z]] <https://github.com/harfbuzz/harfbuzz/issues/532>

[166] [CITE@ja[フォントでTUT-Code - にせねこメモ]], [TIME[2024-10-24T12:52:34.000Z]] <https://nixeneko.hatenablog.com/entry/2015/12/03/000000>


* 歴史

[11] 
[CITE@en[opentype-layout/proposals at master · OpenType/opentype-layout · GitHub]], [TIME[2022-08-17T07:14:06.000Z]] <https://github.com/OpenType/opentype-layout/tree/master/proposals>

* メモ

[38] 
[CITE[Spec for Thai OpenType Creation]], [TIME[2022-03-22T07:23:44.000Z]], [TIME[2022-08-18T02:34:48.288Z]] <https://linux.thai.net/~thep/th-otf/>

-
[2] 
[CITE[Manipulating OpenType Lookups — FontForge 20220308 documentation]], [TIME[2022-07-16T19:58:44.000Z]], [TIME[2022-08-13T05:26:05.464Z]] <https://fontforge.org/docs/ui/dialogs/lookups.html>


[3] [CITE@ja[GSUB テーブル (1) - ScriptList]], [TIME[2022-08-13T05:26:21.000Z]] <https://aznote.jakou.com/prog/opentype/17_gsub1.html>


[164] [CITE@ja[OpenTypeフォントでFizzBuzz(その2) - にせねこメモ]], [TIME[2024-10-24T12:22:19.000Z]] <https://nixeneko.hatenablog.com/entry/2015/05/10/234628>

[165] >>164 置換とは[[計算]]



[172] 
[[Windows]] の [[Chrome]] でも [[Firefox]]
でも、
[CODE[GSUB]] で漢字、漢字や仮名、仮名の[[合字]]化はできるのに、
なぜか漢字、仮名や仮名、漢字は置換されません。
[TIME[2025-02-01T05:34:28.500Z]]

[173] 
[[ラテン文字]]と[[仮名]]でも[[合字]]化できません。[[用字系]]をまたがるからでしょうか。
