PDFlibPas は拡張メタファイルをラスター化するのではなく、レコード単位で本物の PDF ページコンテンツへ変換します。取り込んだチャートや CAD 図面がどのズームでもくっきり保たれるのはこのためです。この変換機能は約 6500 行で、VCL を前提に書かれていたため、ライブラリに Free Pascal ターゲットが加わったときには移植不可能と分類され、スタブに置き換えられました。この分類は誤りでした。そしてその誤り方こそが、書き直しを決める前に依存関係を監査する方法についての有益な教訓になります
実際には、その 6500 行のうち VCL に触れている部分は小さいことが判明しました。ピクセル形式、ストリーム保存、ハンドル、キャンバス、スキャンラインのために使われていたビットマップクラス、幅、高さ、ハンドルのために使われていたメタファイルクラス、そして 2 つの定数とともに使われていた色型だけです。これらはすべて、non-VCL ビルドでも同等物を提供できるようにと存在するライブラリ独自のグラフィックスユニットが、すでに備えているものでした。変換機能を止めていたのは VCL ではまったくなく、Free Pascal の Windows ユニットだったのです
コードが実際に依存する軸で分割する
つまり変更は再実装ではありません。1 つの条件定義でした。「VCL なしでビルドするときにスタブをコンパイルする」から「Windows 向けにビルドしないときにスタブをコンパイルする」への変更です。これが正しい軸であり、理由を言葉にすれば違いは明白になります。拡張メタファイルは Windows のコンテナです。変換機能は上から下まで Windows GDI レコードのパーサーです。ホストアプリケーションが VCL を使うか、別のウィジェットセットを使うか、まったく使わないかは、これらのレコードを解釈できるかどうかとは無関係です。ターゲットが Windows かどうかは、そのすべてに関わります
正しい軸を選べば、その帰結はおまけのように手に入ります。このライブラリでは Windows プラットフォームシンボルが未定義にされる C++Builder ビルドは、例外を投げるスタブを保持し、以前とまったく同じ振る舞いをします。macOS は、解析すべき GDI レコードが存在しないため、正しくスタブを保持します。Delphi VCL ビルドは無変更です。そして non-VCL のウィジェットセットを使う Windows ビルドは、副作用としてベクター EMF 取り込みを獲得します。誰も実装する必要のなかった部分です。実際の依存関係に沿った条件定義は、プラットフォーム作業を 1 行の変更に変えます。誤った依存関係に沿った条件定義は、永遠にスケジュールに乗らない書き直しに変えます
Free Pascal の不足分は宣言であり、ロジックではなかった
実際に不足していたのは、Delphi の Windows ユニットが提供し Free Pascal のそれが提供しない Win32 宣言でした。それらを変換機能のあちこちに条件定義として散らすのではなく、1 つの互換ユニットに集めたことで、パーサーの可読性が保たれました。この一覧は、2 つの RTL の間でヘッダーの網羅度がいかに不均一かを示す点で参考になります。メタファイルレコード型定数 113 個、拡張テキスト出力フラグ 2 個、グラデーション塗りつぶしモード定数 3 個、ハンドルテーブルポインタ型、グラデーション頂点レコードとプリミティブレコードのエイリアス、さらに Free Pascal がまったく宣言していない 3 つのレコード型で、アルファブレンド、透過ブリット、カラーマネジメントモードをカバーします
どれも単体では興味深いものではありません。しかしパーサーがコンパイルを通るにはすべてが正しくなければならず、ヘッダードキュメントとユニット単位で差分を取れる互換ユニットは、その自然な置き場所です
黙って誤った絵を描いてしまう 1 つ
これらの宣言のうち 2 つは、単に欠けているのではなく、存在しながらこの用途には誤りです。メタファイルに触れることが一度もないとしても、覚えておく価値のある部分です
Free Pascal は、ブラシ作成レコードの中に実行時のブラシ構造体を組み込んで宣言し、拡張ペンレコードの中に実行時のペン構造体を組み込んで宣言しています。これらの実行時構造体はどちらも、hatch メンバーをポインタサイズの整数として宣言しています。実際の GDI 呼び出しではこのメンバーがハンドルを運べるからです。しかしメタファイルは常に 32 ビット形式を格納します。レコードレイアウトはシリアライズされたファイル形式の一部であり、プロセスのビット数によって変わらないからです
32 ビットビルドでは両者は一致し、何も起こりません。Win64 ではポインタサイズのメンバーは 8 バイトである一方、ファイル上は 4 バイトなので、hatch メンバー以降のすべてのフィールドが誤ったオフセットから読まれます。例外もなく、パースエラーもなく、警告もありません。メタファイルはただ誤って描画されます。誤ったバイトからの色、誤ったバイトからのペン幅、そして構造体レイアウトのバグではなくレンダリングのバグに見える絵です。Delphi が両構造体の明示的な 32 ビット版を出荷しているのはまさにこの理由からであり、互換ユニットも同じ方法で再宣言しています
// Win64 では誤り:Hatch はポインタサイズだが、ファイルは 32 ビットを
// 格納し、エラーなしに以降の全フィールドが 4 バイトずれる
type
TLogBrushRuntime = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: ULONG_PTR; // 64 ビットプロセスでは 8 バイト
end;
// 正しい:シリアライズされたレイアウト。ビット数に依存しない固定幅
type
TLogBrush32 = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: DWORD; // メタファイルに格納されるとおり、常に 4 バイト
end;
一般的な規則はこうです。実行時の API 引数としても、シリアライズされたフィールドレイアウトとしても現れる構造体には 2 つの宣言が必要であり、シリアライズ側は全体を固定幅の型で書かなければなりません。ファイル形式の中のポインタサイズのメンバーは、常に 64 ビットビルドを待ち構えるバグです
シグネチャの差異はラッパーで吸収する。個々の呼び出し箇所ではない
残りの差異はありふれたシグネチャの不一致であり、それを吸収する方法は、各呼び出し箇所への条件定義ではなく転送ラッパーです。変換行列を合成する関数は、Delphi が参照パラメータを取るところを Free Pascal ではポインタを取るため、ラッパーは参照を受け取りアドレスを渡します。さらに、両方のソース引数をまずローカル変数へコピーします。変換機能には出力先マトリックスが同時にソースの 1 つでもある呼び出し箇所があり、読みながら書く関数に同じアドレスを 2 回渡すと、回転したコンテンツでだけ現れる微妙に誤った変換が生まれるからです
function CombineTransformCompat(var Dest: TXForm;
const A, B: TXForm): BOOL;
var
SrcA, SrcB: TXForm;
begin
// まずコピーする:呼び出し側は Dest を A または B として正当に渡し得る
SrcA := A;
SrcB := B;
{$IFDEF FPC}
Result := Windows.CombineTransform(@Dest, @SrcA, @SrcB);
{$ELSE}
Result := Windows.CombineTransform(Dest, SrcA, SrcB);
{$ENDIF}
end;
矩形型と点型はもう 1 つのケースです。Free Pascal はメタファイルの矩形レコードと点レコードを汎用グラフィックスのそれらとは別の型として扱うため、8 か所の代入箇所で、同一レイアウトのレコード間の明示的なキャストが必要になりました。両コンパイラともキャスト形式を受け付けるため、これらの箇所には条件定義が一切残らず、多少の見た目の悪さには見合う価値があります
Free Pascal 配備においてこれが変わること
ベクター EMF 取り込みは、Free Pascal の下の Windows で機能し、Delphi ビルドと同じページコンテンツを生成します。パスはパスとして、グラデーションはパターンコンテンツとして、テキストはテキストとして出力されます。Windows 以外ではラスター経路が引き続き答えであり、これは移植の制限ではなくフォーマットの制限です。変換機能が供給する座標とクリッピングの状態についてはコンテンツストリームの CTM とクリッピングトラッカーの記事に、出力されるベクタープリミティブについてはベクターグラフィックス、シェーダーとグラデーションの記事で扱っています
同じ機会がないか自分のコードベースを監査するなら、役に立つ演習はこの話の始まりになったものです。依存していると思っているフレームワークから、実際に使っているメンバーを書き出してみてください。答えは import 一覧が示唆するよりはるかに短いことが多く、本当の制約はまったく別の場所にあるのが普通です。デバイスコンテキストベースの取り込み経路については印刷プレビューとデバイスコンテキストの記事に述べており、プラットフォームとツールチェーンの対応状況はlosLab PDF Developer Library の製品ページに一覧があります