HotPDF Componentでは、THotPDF.Resolutionが描画単位を定義します。すべてのX、Y座標、すべてのマージン、SetFontへ渡すサイズ、そしてTextWidthとGetWideTextWidthの結果は、1/Resolutionインチで測られます。THPDFPage.WidthとHeightはこれに従わず、ポイントのままです。だからレイアウトの境界は、読み取り専用のUserWidthとUserHeightから取る必要があります。Resolutionに触れるいつもの理由は移植です。1/96や1/144インチで考えるレポートエンジンは、PDF側が同じ単位で話すときの方が、すべての呼び出し箇所に変換係数を差し込むときより、移しやすいものです。どの数値が新しい単位へ移り、どれが取り残されたかを知っていれば、それはうまく機能します
THotPDF.Resolutionが実際に変えるもの
THotPDF.Resolutionが変えるのは、HotPDFがあなたの渡す数値をどう読むかだけです。書き出されるPDFは同じです。セッターは2行です。SetResolutionが値を保存し、DocScale := Value / 72を設定します。以後、XProjectionとYProjectionは、コンテンツストリームへの道すがらですべての座標をDocScaleで割り、SetFontはサイズを同じやり方で割ってから記録します。PDFユーザー空間のデフォルトは1/72インチ(ISO 32000-1 §8.3.2.3)なので、デフォルトのResolution 72では射影は恒等写像、144では1描画単位は半ポイントです。/UserUnitエントリーは書かれません。PDF 1.6で追加されたこのページ属性は別物で、HotPDFはTHPDFPage.SetUserUnitとして公開します。TextOutのチュートリアルから来た人がひっかかる細部が1つあります。ページ座標は左上コーナーから始まり、Yは下向きに増えます。YProjectionがMediaBoxの上端からスケール済みのYを引くためで、これはあらゆるResolutionで変わりません
var
Pdf: THotPDF;
Page: THPDFPage;
Margin: Single;
Title: WideString;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice.pdf';
Pdf.Resolution := 144; // 1描画単位 = 1/144インチ
Pdf.BeginDoc;
Page := Pdf.CurrentPage; // A4:Width = 595、UserWidth = 1190
Margin := 144; // 描画単位で1インチ
Page.SetFont('Arial', [fsBold], 28); // 28/144インチ、14ポイントのフォント
Title := 'INVOICE 2026-0417';
// 同じ単位で測ったページ端に対して右寄せ
Page.TextOut(Page.UserWidth - Margin - Page.GetWideTextWidth(Title),
Margin, 0, Title);
Page.SetLineWidth(2); // 1ポイントの罫線
Page.MoveTo(Margin, Margin + 48);
Page.LineTo(Page.UserWidth - Margin, Margin + 48);
Page.Stroke;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Resolution 144でPage.Widthが座標と食い違う理由
THPDFPage.WidthとHeightは、ドキュメントのResolutionが何であれページをポイントで報告します。一方あなたの座標は1/Resolutionインチです。だから144では、ページは実際の半分の幅に見えます。A4ページはResolution 72でWidth = 595、Height = 842と読め、144でも595と842のままです。実際には右端はX = 1190にあります。v2.766.0で追加されたUserWidthとUserHeightは、Width * DocScaleを返します。つまりあなたが描く単位でのページサイズです。これらが存在する前、ライブラリは内部で2つを混ぜていました。Resolution 144での症状は劇的です。段落が文字ごとに折り返し、THPDFTable.Renderは各行を新しいページへ押しやり、HTMLインポーターとXFAフラットナーはコンテンツを半分のサイズで描き、フラット化されたフォームは左上コーナーに密集しました。段落レイアウト、テーブル描画、HTMLインポート、EMFのセンタリング、WMFのページクリップ、レイアウト診断は、今はすべてユーザー単位のサイズを読みます。あなたのレイアウトコードもそうすべきです。描画座標と比較するもの(右マージン、改ページテスト、センタリング計算)は、UserWidthとUserHeightの側に属し、決してWidthとHeightの側には属しません
罠その1:WidthやHeightの代入がページをポイントへ切り替える
Page.WidthやPage.Heightの設定は、ページを黙ってUserDefinedへ変え、UserDefinedのページはDocScaleを完全に無視します。だから以後そこへ描くすべては、1/Resolutionインチでなくポイントです。セッターは古く、設計上ポイントを取ります。意味がそのままにされたのはそのためです。UserDefinedページの射影は素のX + MinXで、SetFontはサイズをそのまま保存します。Resolution 144では、コンテンツが突然、直前のページの2倍の大きさで出るページができます。ライブラリ自身がまさにこの間違いをしました。段落の続きページは以前、前のページのサイズをWidth経由でコピーし、すべてのオーバーフローページがポイントへ切り替わっていました。今はそれらのページがSize、Orientation、そしてページのResolutionをコピーし、元のページがすでにUserDefinedだったときだけWidthとHeightへフォールバックします
出口は2つあります。何が必要かによります。標準の用紙で足りるなら、Page.SizeとPage.Orientationを設定し、Resolution単位のまま描き続けてください。本当にカスタムサイズが要るなら、それがポイントのページであることを受け入れ、ポイントで描いてください。そこではUserWidthはWidthと等しいので、常にUserWidthを読むレイアウトコードは、両方の種類のページで動き続けます。ユニットテストはこれを固定します。Resolution 144でA4ページは1190のUserWidthを報告しますが、Width := 500とHeight := 400の後は500と400を報告します。ロード済みページも同じように振る舞います。既存PDFから再構築されたページは、ポイントでのMediaBoxしか知らず、ポイントで描くからです。このドキュメントが作ったページは、CurrentPageNumberで離れて戻ってきても、自分の単位を保ちます。v2.766.26以来の挙動です
罠その2:フォントサイズが半分のサイズになる理由
ポイントとして始まったフォントサイズが、Resolution 144で半分のサイズになるのは、SetFontがサイズ引数を描画単位として扱い、保存の前にポイントへ変換するからです。内部では、SetFontは現在のフォントオブジェクトへASize / DocScale * DPIを保存します。保存される値は常にポイントです。ライブラリはこれに2度つまずきました。WideTextOutBoxExのフォントフォールバックと段落の続きページが、どちらも保存済みのポイント値をSetFontへ返し、もう1度スケールされてテキストが半分になりました。あなたのコードは保存サイズを読めませんが、同じバグは、どこか他からのポイント値がSetFontへ届くたびに現れます。VCLフォームのTFont.Size、レポート定義のサイズ、CSSのptの長さ。まず変換し、係数にページ自身のResolutionとUserDefinedの場合を含めてください。メタファイル再生がページのCanvasを再生するときにやるように(その経路はHotPDFがEMFとWMFベクターグラフィックスをインポートする方法を見てください):」
// 現在のページの1ポイントあたり描画単位数。HotPDFが使う射影を
// 鏡像する:Width/Heightでサイズ化されたページでは1、それ以外は
// (document Resolution / 72) * (page Resolution / 72)
function UnitsPerPoint(Pdf: THotPDF): Single;
begin
if Pdf.CurrentPage.Size = UserDefined then
Result := 1
else
Result := (Pdf.Resolution / 72) * (Pdf.CurrentPage.Resolution / 72);
end;
procedure SetFontFromVcl(Pdf: THotPDF; Font: TFont);
begin
// TFont.Sizeはポイント。SetFontは描画単位を期待する
Pdf.CurrentPage.SetFont(AnsiString(Font.Name), Font.Style,
Font.Size * UnitsPerPoint(Pdf));
end;
ライブラリは同じルールを、自分のポイント定数へ適用します。すべての新しいページが始まる12ポイントのフォントは、今や内部のユニット/ポイント係数と乗算されるので、あらゆるResolutionで12ポイントです。マージン、ラベルサイズ、線幅がすべてハードコードされたポイントであるDrawChartは、スケールを一時的に1にして走ります。意図的に描画単位のまま残るのは、DrawQRCodeのモジュールサイズやデフォルトのテーブルフォントサイズといった、公開パラメーターのデフォルトです。API契約の一部なので、Resolution 144では72での意味の半分になります。テンプレートからレポートのサイズを決めるなら、HotPDFでフォントと画像を使ったレポート出力のガイドが、それらの値がどこから来るかを扱います
レイアウトがResolution非依存だとどう検証するか
最も信頼できる検査はバイト比較です。同じページをResolution 72で、そして144で、すべての座標とサイズを2倍にして描くと、非圧縮のコンテンツストリームは同一でなければなりません。両方の実行は射影後に同じポイント値へ着地するので、差異があればそれは変換をスキップした値です。HotPDFのテストスイートが段落、テーブル、HTMLインポート、XFAフラット化、円弧、メタファイル、画像を検査するのは、この方法です。ほとんどハーネスなしで、自分のレポートコードにも同じ技法が使えます
procedure RenderPage(const FileName: string; Res: Integer; K: Single);
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.Compression := cmNone; // 読めるコンテンツストリーム
Pdf.FileName := FileName;
Pdf.Resolution := Res;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 10 * K);
Pdf.CurrentPage.TextOut(36 * K, 36 * K, 0, 'Line 1');
Pdf.CurrentPage.Rectangle(36 * K, 60 * K, 200 * K, 40 * K);
Pdf.CurrentPage.Stroke;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
// RenderPage('r72.pdf', 72, 1) と RenderPage('r144.pdf', 144, 2) は
// バイト単位で同一のページコンテンツストリームを出さなければならない
数値を運ぶ演算子を調べてください。Td、Tm、Tf、re、w、そしてTJ配列です。ファイルレベルのバイトは作成日時と/IDでまだ違います。だからファイル全体でなくストリームを比較してください。不一致はほぼ常に上の2つの罠のどちらかを指します。Width経由でリサイズされたページか、そのままSetFontへ渡されたポイント値です。描画呼び出し自体が初めてなら、まずサイズ、スタイル、回転を扱うHotPDF TextOut解説から始め、レイアウトがUserWidthを読むようになったら戻ってきてResolutionを切り替えてください。API詳細の全文とトライアルダウンロードは、HotPDF Delphi PDF component pageにあります