PDFium コンポーネントのテキストシェーピングは、1 つの組み込み可能なオブジェクトを通ります。ConfigureTextShaper は、すべてのシェーピングエントリポイントが経由するシェーパーを組み込み、そこにあったものを置き換えて解放します。ActiveTextShaper は組み込み済みのものを返し、初回使用時にプラットフォーム既定を作成します。ActiveTextShaperName はどのバックエンドが生きているかを報告し、ClearTextShaper は組み込みを取り払い、既定を再び作れるようにします。Windows の既定は TPdfUniscribeTextShaper です。Free Pascal の下には TPdfHarfBuzzTextShaper があり、実行時に libharfbuzz をバインドするため、ライブラリが欠けていることはロード障害ではなく報告される状態になります
1 つのインターフェース、まったく違う形で仕事を分ける 2 つのバックエンドです。この非対称を理解することこそが、ポータブル経路が正しくシェープされ誤って配置されたテキストを生み出すのを止めます
Windows のバックエンドは 1 クラスで、ポータブルは 3 部品なのはなぜか
Uniscribe は 1 つに見せかけた 4 つの API だからです。ScriptItemize が文字列をスクリプトごとに分割し双方向レベルを解決し、ScriptShape が文字をグリフへ対応させ、ScriptPlace が送り幅とオフセットを計算し、ScriptLayout ができ上がったランを視覚順へ並べます。したがってその上に構築されたバックエンドは加えるものが何もなく、Windows のシェーパーが 1 つのメソッドを持つ単一クラスである理由です
HarfBuzz は中の 2 つをカバーします。呼び出し側がすでに方向とスクリプトを決めたランをシェープし配置します。段落がどうランへ分割されるか、それらのランがどんな順序で現れるかについては意見を持ちません。そのためポータブルバックエンドが残りを供給します。双方向アルゴリズムが埋め込みレベルを解決し、HarfBuzz の Unicode 関数がテキストをスクリプトごとに分割し、ランは UAX #9 の規則 L2 が生む視覚順で並べられます。双方向の半分は独自ユニットになるほど充実しており、UAX #9 埋め込みレベルの記事に述べています
シェーパーはフォントを解決しない。それは意図的である
Uniscribe は GDI デバイスコンテキストからフォントバイナリを読みます。それに相当するポータブルなものは存在せず、シェーピングユニットの内側でそれを発明するのは、すべてのアプリケーションに代わって、フォントが fontconfig から来るか、CoreText から来るか、アプリケーションのフォントフォルダから来るか、データベースから来るかを決めることになります。そこで HarfBuzz バックエンドはリゾルバを受け取ります。フォント名を TrueType または OpenType のバイトへ対応させるコールバックです。False を返すと、Windows で読めない GDI フォントが失敗させるのと同じように、シェーピング要求は失敗します
uses
FPdfTextShaping
{$IFDEF FPC}
, FPdfTextShapingHb
{$ENDIF}
;
function TFontCatalogue.Resolve(const FontName: WideString;
out FontData: TBytes): Boolean;
var
Path: string;
begin
// あなたのポリシー:fontconfig、CoreText、アプリのフォントフォルダ、データベース
Result := FLookup.TryGetValue(LowerCase(FontName), Path);
if Result then
FontData := TFile.ReadAllBytes(Path);
end;
procedure InstallShaper(Catalogue: TFontCatalogue);
begin
{$IFDEF FPC}
// 所有権はユニットへ渡る。スタートアップ中に、何かがテキストを
// シェープするより前に 1 回呼ぶ
ConfigureTextShaper(TPdfHarfBuzzTextShaper.Create(Catalogue.Resolve));
{$ENDIF}
// Delphi ではプラットフォーム既定(Uniscribe)が要求時に作成される
// ため、組み込みはまったく不要である
LogInfo('shaping backend: ' + ActiveTextShaperName);
end;
フォントの発見をシェーパーの外に置くことには、サーバーで現れる 2 つ目の利点があります。同じプロセスが、マシンにインストールされているものとは何の関係もない組み込みフォントセットでシェープできます。出力がホスト間でバイト単位で再現可能でなければならないときに望むものです。コンポーネントは、インストール済みフォントが本当に欲しいケースのために、ホストシステムフォントプロバイダーも公開しており、システムフォントプロバイダーの記事で扱っています
結果レコードはバックエンド中立。クラスタがその理由
両バックエンドは同じ TPdfShapedText を埋めます。ソーステキスト、フォント名、サイズ、フォントバイト、ランの配列、総幅、グリフ数、論理文字数です。各 TPdfShapedRun は、ソーステキスト内の自分の区間、視覚 X 位置、幅、双方向レベル、右から左フラグ、そしてグリフを運びます。各 TPdfShapedGlyph は、グリフ識別子、送り幅、X と Y のオフセット、そしてソーステキスト内の開始と長さとしての、自分が属するクラスタを運びます
これらのクラスタフィールドこそが、レコードを単なる情報提供ではなく使えるものにします。シェーピングは 1 対 1 の対応ではありません。デーヴァナーガリーの音節は 4 文字から 1 グリフになり、アラビア文字の合字は 2 つを併合し、1 文字が複数の符号を生むことがあります。クラスタ区間がなければ、キャレットを置けず、クリックのヒットテストもできず、選択範囲をハイライトもできません。どの文字にグリフが属するか言えないからです。あれば、計算は局所的であり、同じコードが両バックエンドで機能します
var
Shaped: TPdfShapedText;
R, G: Integer;
begin
if ShapePdfText(Line, 'Noto Sans Arabic', 14, ptdAuto, Shaped) then
for R := 0 to High(Shaped.Runs) do
begin
// ランは視覚順で到着済みであり、VisualX は埋まっている
X := Shaped.Runs[R].VisualX;
for G := 0 to High(Shaped.Runs[R].Glyphs) do
begin
EmitGlyph(Shaped.Runs[R].Glyphs[G].GlyphID,
X + Shaped.Runs[R].Glyphs[G].OffsetX,
Shaped.Runs[R].Glyphs[G].OffsetY);
X := X + Shaped.Runs[R].Glyphs[G].Advance;
end;
end;
end;
予算はオプションレコードに属する
TPdfTextShapingOptions は方向に加えて 3 つの上限を運びます。最大文字数、最大グリフ数、最大ラン数であり、まともな値を埋める Default クラス関数が付きます。上限は、不正な入力への被害妄想ではありません。算術です。シェーピングは膨らみます。積極的な文脈置換を持つフォントは入力文字より多くのグリフを出力し得え、数文字ごとにスクリプトを切り替える段落は切り替えごとに 1 ランを生みます。両方を最大化するよう組み立てられた文書は、控えめな文字列を大きな割り当てへ変え、信頼できない PDF からテキストをシェープするサービスは、マシンが課す上限ではなく自分が選んだ上限を必要とします
方向を自動のままにするのではなく明示的に設定するのは、すでに分かっているときには行う価値があります。自動は段落方向の規則を適用して、最初の強い文字から推測します。自由テキストには正しく、フォームフィールドには誤りです。方向は、誰かが入力した値の性質ではなく、フィールドの性質だからです
実行時バインディング。ビルド依存ではない
HarfBuzz バックエンドはライブラリを動的にロードします。これは実際の帰結を持つ配備判断です。1 つのバイナリが、HarfBuzz のあるマシンでもないマシンでも動き、2 つ目のケースでは起動に失敗する代わりに機能が減ったことを報告します。他の開発者へ出荷されるライブラリにとって、これが唯一の実行可能な配置です。PDF コンポーネントのあらゆる利用者に、必要としないかもしれないシェーピングライブラリの入手とバージョン合わせを要求することはできないからです
呼び出し側の対応する規則は、確認することです。ActiveTextShaper は、プラットフォームに既定がなく、設定もされなかったとき nil を返し、シェーピングエントリポイントはそれを、シェーピングの失敗ではなく利用できないシェーパーとして報告します。これらは別の問題であり、別のメッセージに値します。片方は配備の欠落であり、もう片方はフォントかテキストの問題です
何かがシェープする前に、1 回だけ組み込む
組み込みは前のシェーパーを置き換えて解放するため、繰り返し呼ぶのは安全ですが無意味であり、別のスレッドがシェーピングしている最中に呼ぶのはまったく安全ではありません。スタートアップ中に行ってください。後でプラットフォーム既定へフォールバックする必要があるなら nil を渡します。これはテストの最後にテストダブルを取り消す方法でもあります
バックエンドが一度組み込まれると、計測と折り返しは両プラットフォームで同じように振る舞います。プラットフォームを直接呼ぶのではなく、ランとグリフのメトリクスを消費するためです。折り返しのモデルはテキスト計測とワードラップの記事に述べています。コンポーネントの対応プラットフォームとツールチェーンは、PDFium Delphi component の製品ページに一覧があります