PDFには可変フォントという概念が存在しません。PDFファイルに埋め込まれるフォントは、固定されたメトリクスを持つ固定のアウトライン一式であるため、可変フォントはドキュメントに組み込む前に1つの静的インスタンスへ縮退させる必要があります。HotPDFはこのインスタンス化を内部で行います。可変フォントの軸を調べ、ウェイト620や幅87.5といった座標を選択すると、ライブラリはそれらの値を、準拠したPDFリーダーであればどれでもレンダリングできる、完全に自己完結したフォントプログラムへと焼き込みます
これが重要になる理由は理論的なものではなく実務的なものです。書体メーカーは、十数種類の静的ウェイトの代わりに1つの可変フォントファイルを配布することが増えており、デザインチームは名前付きインスタンスのどれにも当てはまらない値を選ぶことがあります。インスタンス化を行わない場合、レポート生成器はデフォルトインスタンスにフォールバックしてデザイン上の決定を捨ててしまうか、可変フォント全体を埋め込んでビューアが知りようのない軸座標を尊重してくれることを期待するしかありません。しかし、どのリーダーもそれを行うことは義務付けられていません
インスタンス化は実際に何を再構築する必要があるのか?
OpenTypeの可変フォントは、グリフごとに1つのデフォルトアウトラインと、デザイン空間内の位置でインデックス付けされた一連のデルタを保持しています。軸座標を適用するというのは、ヘッダーに数値を書き込むだけの話ではありません。gvarテーブルを走査し、要求された位置に対してデルタを補間し、ポイントを移動させ、それらのポイントから導出されるすべてのものを再計算することを意味します。HotPDFは、グリフアウトライン、long形式のlocaテーブル、水平・垂直の完全なメトリクス、フォント全体のバウンディングボックス、sfntチェックサム調整値を再構築します
同じくらい重要なのは、何を取り除くかです。静的インスタンスはfvar、avar、gvar、HVAR、VVAR、MVAR、STAT、cvarのいずれも保持してはならず、署名対象のバイト列がもはや存在しないため、古いDSIGも削除する必要があります。これらのいずれかを残したままにすると、すでに移動済みのアウトラインを抱えながら自らを可変フォントだと主張するフォントができあがり、バリエーションを実際に適用するリーダーはそれを二重に適用してしまいます
ファントムポイントと二重適用の罠
この処理全体の中で最も繊細な規則はメトリクスに関するものです。gvarでは、グリフのポイント数はアウトラインポイント(複合グリフの場合はコンポーネントポイント)に加えて、左サイドベアリング、アドバンス幅、およびそれらの垂直方向の対応値をエンコードする4つのファントムポイントをカバーしています。これらのファントムポイント自体も、デルタの対象となります
そのため、フォントがgvarテーブルを持つ場合、HotPDFは補間済みのファントムポイントから水平・垂直メトリクスを導出し、HVARやVVARを追加で適用することはありません。両方を適用してしまうのは典型的な誤りです——同じバリエーションが二重に適用され、すべてのアドバンス幅がわずかに広くなりすぎ、両端揃えの行でテキストが少しずつ右へずれていく現象として現れます。フォントにgvarが存在しない場合に限り、ライブラリはメトリクスバリエーションストアをhmtxやvmtxに直接焼き込みます
さらに2つの細かな点が、ジオメトリの正しさを保っています。ファントムポイントは輪郭の補間には一切関与しないため、単純グリフで明示的にリストされていないポイントは、ファントムポイントを除いた各輪郭ごとにIUPで推定されます。また、複合グリフでは、XYパラメータを使用するコンポーネントオフセットに対してデルタが適用され、その後、子の境界が再帰的に再計算されます。この再帰は深さに上限が設けられ循環チェックも行われます。そうしなければ、悪意のある、あるいは単に壊れたコンポーネントグラフが無限に再帰してしまう恐れがあるからです
選択する前にデザイン空間を調べる
インスタンス化ワークフローにおける最初の呼び出しはInspectVariableFontで、書体メーカーが定義した軸と名前付きインスタンスを報告します。軸レコードには4バイトのタグ、最小値、デフォルト値、最大値、フラグ、名前IDが含まれ、名前付きインスタンスにはサブファミリー名ID、フラグ、任意のPostScript名ID、そして軸ごとに1つの座標値が含まれます:
var
Pdf: THotPDF;
Axes: THPDFVariableFontAxisArray;
Instances: THPDFVariableFontNamedInstanceArray;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.InspectVariableFont('C:\Fonts\Inter.ttf', Axes, Instances) then
begin
for I := 0 to High(Axes) do
Writeln(Format('%s min=%.1f default=%.1f max=%.1f',
[string(Axes[I].Tag), Axes[I].MinimumValue,
Axes[I].DefaultValue, Axes[I].MaximumValue]));
Writeln(Format('%d named instance(s) defined', [Length(Instances)]));
end
else
Writeln('not a variable font - embed it as an ordinary TrueType face');
finally
Pdf.Free;
end;
end;
軸の範囲を報告することが重要なのは、軸の値がUIの提供する範囲ではなく、フォントが宣言する範囲にクランプされるためです。wght軸が900までしかないフォントに対して、ユーザーがウェイト1000を要求できるスライダーがあるなら、その補正はフォント層で黙って行うのではなくインターフェース側で行うべきです。さもないと、印刷結果とプレビューが食い違ってしまいます
座標を選択してドキュメントを出力する
軸の選択はステートフルであり、それ以降に登録されるフォントに適用されます。SetVariableFontAxisは4バイトの印字可能なASCIIタグと有限の数値を受け取り、それ以外は静かに無視するのではなく例外として拒否します。ClearVariableFontAxesは選択内容をリセットし、GetVariableFontAxisSelectionsは現在保留中の内容を報告します。これは、複数のコードパスが同じドキュメントオブジェクトに触れている可能性のあるレポートエンジンではログに記録しておく価値があります。ファミリー自体は、他の埋め込みTrueTypeフェイスとまったく同じように、SetFontによって名前で選択します:
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginDoc;
Pdf.SetVariableFontAxis('wght', 620); // セミボールド、名前付きインスタンスではない
Pdf.SetVariableFontAxis('wdth', 87.5); // やや幅を狭く
Pdf.CurrentPage.SetFont('Inter', [], 11);
Pdf.CurrentPage.TextOut(72, 720, 0, 'Quarterly results');
Pdf.ClearVariableFontAxes; // デフォルトインスタンスに戻す
Pdf.CurrentPage.SetFont('Inter', [], 10);
Pdf.CurrentPage.TextOut(72, 700, 0, 'Prepared by the finance team');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
OnVariableFontInstanceイベントは各インスタンスが生成されるたびに発火し、使用された軸の値を報告します。これは、あるPDFが実際に何を含んでいるかをログで証明する最も安上がりな方法です。異なる座標セットはそれぞれ異なるフォントプログラムを生成するため、軸の選択はフォントキャッシュキーの一部として扱ってください。キャッシュの仕組みについては永続フォントサブセットキャッシュで説明しています
インスタンス化はサブセット化・シェーピングとどう関わるのか
インスタンス化はサブセット化より前に実行され、この順序が正しいものです。インスタンス化されたフォントは通常の静的TrueTypeフェイスであるため、通常のサブセッターは他のフォントと同じように扱います。すなわち、グリフクロージャーを計算し、ドキュメントが実際に使用するグリフだけを残して残りを削除します。注意すべき点は、同じファミリーの異なる2つの軸選択は2つの異なるフォントプログラムになるということです。ウェイト400とウェイト620を混在させるドキュメントは、2つのインスタンスを持つ1つの共有フェイスではなく、2つのサブセットを埋め込みます
シェーピングは原則として影響を受けませんが、実際に確認しておく価値はあります。レイアウト機能はGSUBとGPOSに存在し、これらはインスタンス化によって保持されるため、リガチャやスタイル代替字形はOpenType GSUBのスタイル代替字形で説明したとおり引き続き機能します。変化するのは配置です。コンデンス(幅を狭めた)インスタンスはデフォルトよりアドバンス幅が狭いため、インスタンス化の前にテキストを計測したレイアウトは、誤った幅を計測してしまいます。レンダリングに使うのと同じ軸選択で計測すれば、この食い違いは解消します
実装面から最後にもう1つ、防御的な注意点を挙げます。この経路を拡張しようとする人には役立つはずです。垂直メトリクスを持たないフォントであっても、Delphiの呼び出し側では動的配列引数が評価されるため、HasVerticalMetricsチェックによって空のインデックスをショートサーキットすることに頼るのではなく、パース時の配列は常に確保されます。これは、一見ガードされているように見える分岐が、まさにテストしていなかったフォントに対してアクセス違反へと変わってしまう類いの、言語レベルの細部です
可変フォントのサポートは、埋め込み・サブセット化・グリフクロージャーと同じフォントパイプラインに組み込まれており、詳しくはフォントサブセットのクロージャーとシェーピングされたグリフで説明しています。Delphiおよび C++Builder向けの完全なタイポグラフィ機能一覧は、HotPDF Delphi PDFコンポーネントページに掲載されています