技術記事

DelphiでのHotPDFとTPrinterを使用したロード済みPDFの印刷

HotPDFは、3つの呼び出しを介して、ロードされたPDFを物理プリンター、Windowsデバイスコンテキスト、またはクリップボードに出力できます。PrintLoadedDocumentはVCLのTPrinterを制御し、RenderLoadedPageToDCは任意のHDCにページを直接描画し、RenderLoadedPageToClipboardはページをビットマップまたはベクターメタファイルとしてコピーします。HotPDFはDelphiおよびC++Builder用のネイティブVCL PDFコンポーネントであり、v2.331.0でこの高度な印刷パスを追加し、v2.348.0でさらに改良しました。そのため、ビットマップレンダラーを囲むTPrinterのループ処理を手動で記述する必要はもうありません。3つの呼び出しはすべて、ページのベクターコンテンツを直接描画するため、プリンターが表現できる最高の鮮明さで印刷できます

PDFページをプリンターに等倍で直接描画する理由

PDFページはベクター描画演算子のプログラム(ISO 32000-1グラフィックスモデルに準拠して実行されるパス、テキスト、および画像)であり、ピクセルのグリッドではありません。最も分かりやすい印刷方法は、ページをビットマップにラスタライズし、そのビットマップをプリンターのキャンバスに転送(Blit)することです。これは機能しますが、プリンターの最大の強みを無駄にしてしまいます。最新のレーザープリンターの解像度は600または1200 DPIです。150または300 DPIでラスタライズされたビットマップを紙面に合うように引き伸ばすと、文字の輪郭がギザギザになり、補間の跡が目立つなど、ぼやけた仕上がりになります。RenderLoadedPageToDCはより優れた手法を採用しています。ページのコンテンツストリームをターゲットデバイスコンテキストに直接出力されるGDI呼び出しとして再生するため、ベクターテキストや線画はベクター形式のままプリンターに到達し、プリンター自身の解像度で一度だけラスタライズされます

DelphiでロードされたPDFを物理プリンターで印刷するにはどうすればよいですか?

HotPDFは、割り当てられたVCLのTPrinterTHPDFPrintOptionsレコードを受け取るPrintLoadedDocument(Printer, Options)を使用して、ロードされたドキュメントを印刷し、送信されたページ数を返します。まずPrinter.PrinterIndexを設定してデバイスを選択し、オプションレコード(クラス関数THPDFPrintOptions.Defaultで適切なデフォルト値を取得できます)を入力して、1回呼び出すだけです。ライブラリはPrinter.BeginDocで印刷ジョブを開始し、ページ範囲を走査してページ間にPrinter.NewPageを挿入し、Printer.EndDocで終了するため、印刷ジョブのライフサイクルを手動で管理する必要はありません

uses Printers;

var
  Pdf: THotPDF;
  Opts: THPDFPrintOptions;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('invoice.pdf') > 0 then
    begin
      Printer.PrinterIndex := -1;         // -1 selects the default printer
      Opts := THPDFPrintOptions.Default;  // all pages, 300 DPI, FitPage, colour
      Opts.PageRange := '1,3-5';          // one-based; empty prints every page
      Opts.Copies := 2;
      Pdf.PrintLoadedDocument(Printer, Opts);
    end;
  finally
    Pdf.Free;
  end;
end;

PageRange文字列は1ベースで動作し、一般的な印刷ダイアログと同じように記述できます。たとえば、'1,3-5'は1ページ目と3〜5ページ目を印刷し、空の文字列はドキュメント全体を印刷します。CopiesはそのままPrinter.Copiesにマッピングされます。ただし、部単位で印刷(Collate)する場合は注意が必要です。VCLにおける部単位印刷の制御はDelphiのバージョン間で一貫性がないため、HotPDFはコピー数を設定するのみで、部単位印刷の挙動はプリンタードライバーのデフォルトに委ねます。そのため、確実な順序で部単位印刷を行う必要がある場合は、このフラグに頼るのではなく、TPrinterSetupDialogを介してドライバー側で設定してください

DPIとFitPageは印刷倍率をどのように決定しますか?

システムを出荷する前に、FitPageDPIの相互作用について理解しておく価値があります。デフォルト値であるFitPageをTrueに設定すると、HotPDFは各ページのMediaBoxを読み取り、アスペクト比を維持したままPrinter.PageWidthPrinter.PageHeightに合わせてスケーリングし、ページが用紙全体に収まるような倍率でレンダリングします。このモードでは、出力がプリンターの物理的な用紙サイズに一致する必要があるため、Options.DPIは用紙フィッティングのスケールによって実質的に上書きされます。FitPageをFalseに設定すると、HotPDFは72 PDFユニットをDPIデバイスピクセルにマッピングし、正確なOptions.DPIで各ページを等倍(1:1)でレンダリングします。これは、元のページと用紙がすでに同じサイズであり、正確な解像度を維持したい場合に適しています。元のページサイズとトレイサイズが異なる場合はFitPageを選択し、サイズが一致し、決定論的なスケーリングを行いたい場合は等倍(1:1)を選択してください

v2.348.0で追加された2つのオプションは、印刷の端が切れる問題を解決します。すべての物理プリンターには、ハードウェアが届かない非印刷領域である「ハードマージン」が存在し、デフォルトではページの原点がその内側に配置されるため、全面印刷(フチなし印刷など)を行うと2つの端が切れてしまいます。IgnoreHardMarginをTrueに設定すると、HotPDFはプリンターのPHYSICALOFFSETXおよびPHYSICALOFFSETYを照会し、デバイスの原点をシフトしてこのオフセットを相殺するため、ページの原点が用紙の真の角にマッピングされます。2つ目のオプションであるAutoRotateはデフォルトで有効になっており、各ページのMediaBoxを読み取って、ページの幅が高さより大きい場合にプリンターを横方向に切り替えるため、横長のページが縦方向でカットされることなく印刷されます。AutoRotateは、Orientationが明示的に設定された場合は無効になり、明示的な方向指定が常に優先されます

PDFページを任意のデバイスコンテキストにレンダリングするにはどうすればよいですか?

RenderLoadedPageToDC(PageIndex, DC, DPI, W, H)は印刷ループの基礎となるプリミティブであり、単体でも非常に有用です。PrintLoadedDocumentは、プリンターキャンバスに対してこのメソッドをページごとに呼び出しますが、描画対象はオフスクリーンメモリDC、カスタムビューアー内のコントロールキャンバス、印刷プレビュー画面など、任意のWindowsのHDCに指定できます。PageIndexは0ベースで機能し、WHはターゲット領域のデバイス単位を示し、レンダラーはページの/Contentsを走査してGDI呼び出しをDCに出力します。1つの重要な仕様として、このメソッドは背景の塗りつぶしを行いません。呼び出し側が描画領域を所有するため、ページの背景に白い用紙を置く場合は、レンダリングを実行する前に開発者自身でDCを白で塗りつぶす必要があります。この仕様により、必要に応じて既存のキャンバスの上にページを重ねて合成できます

// Draw page 1 into an off-screen 24-bit surface at 150 DPI
var
  Surface: TBitmap;
begin
  Surface := TBitmap.Create;
  try
    Surface.PixelFormat := pf24bit;
    Surface.SetSize(1275, 1650);           // 8.5 x 11 in at 150 DPI
    Surface.Canvas.Brush.Color := clWhite;
    Surface.Canvas.FillRect(Surface.Canvas.ClipRect);  // caller paints the background
    Pdf.RenderLoadedPageToDC(0, Surface.Canvas.Handle, 150, Surface.Width, Surface.Height);
    // Surface now holds the composited page, ready to blit or save
  finally
    Surface.Free;
  end;
end;

サムネイル、画像へのエクスポート、キャッシュ用のプレビューなど、完成したTBitmapが必要な場合は、代わりにPDFページのビットマップへのレンダリングで紹介されているRenderLoadedPageToBitmapを使用してください。違いは描画領域の所有権にあります。ビットマップ呼び出しはピクセルバッファを割り当てて返しますが、RenderLoadedPageToDCは、プリンターキャンバスやペイントハンドラーから提供される、開発者がすでに保持しているデバイスコンテキストに直接描画します。どちらも同じコンテンツストリームのインタープリターと埋め込みグリフエンジンを共有するため、いずれの方法でもテキストは正確な埋め込みアウトラインで出力されます

PDFページをWordや画像エディタに貼り付けるにはどうすればよいですか?

RenderLoadedPageToClipboard(PageIndex, DPI, AsMetafile)は、Windowsのクリップボードに1ページをコピーします。これにより、Word、PowerPoint、電子メール、または画像エディタに直接貼り付けることができます。AsMetafileフラグによってフォーマットが選択されますが、この選択は見た目以上に重要です。Falseを渡すと、HotPDFは指定されたDPIでレンダリングされた24ビットデバイスビットマップ(CF_BITMAP)を出力します。これは任意の場所に貼り付け可能ですが、拡大すると画質が低下する固定のピクセルグリッドです。Trueを渡すと、ページのテキストとベクターアートがGDI描画コマンドとして保持される拡張メタファイル(CF_ENHMETAFILE)が出力されるため、貼り付けた後もズームレベルに関係なく鮮明さが維持され、貼り付け先ドキュメントのサイズに応じてスケーリングされます

// Copy page 2 as a scalable vector metafile for pasting into Word
Pdf.RenderLoadedPageToClipboard(1, 300, True);

// Copy the same page as a fixed 300-DPI bitmap instead
Pdf.RenderLoadedPageToClipboard(1, 300, False);

ルールは非常に単純です。ユーザーが文書に貼り付けた後にサイズを変更する可能性がある場合(レポート内の図や契約書の草案など)は、ベクターテキストがクリアにズームおよび印刷されるように拡張メタファイル(EMF)を使用してください。貼り付け先がラスタデータしか解釈できない場合や、特定の解像度でページを固定スナップショットとして扱いたい場合はビットマップを使用してください。画像ではなくテキストを抽出するタスクについては、これとはまったく異なる処理になり、キャラクターとその位置情報を返すテキスト抽出APIによって処理されます

印刷パスの限界

このページで説明されているすべての機能はWindows専用です。PrintLoadedDocumentRenderLoadedPageToDC、およびRenderLoadedPageToClipboardは、TPrinter、GDIデバイスコンテキストモデル、Windowsクリップボード、および拡張メタファイルといったWin32およびWin64の仕組みの上に構築されています。他のターゲット環境でも、HotPDFの他の機能を使用してPDFの作成、編集、読み取りは可能ですが、このGDIに依存する印刷およびクリップボードの機能は利用できません。また、レンダラーはテキスト、パス、および画像といった一般的なドキュメントのサブセットを対象としているため、グラデーションを多用するデザイン校正やICC管理カラーの印刷などは、厳密な色彩校正ではなく忠実なプレビュー出力として印刷されます。これはビットマップレンダラーと同様の限界仕様です

多数のファイルに対して一括印刷を実行する場合は、各ドキュメントを順番にロードして印刷し、メモリ使用量に注意してください。PrintLoadedDocumentは一度に1ページずつレンダリングしますが、ロードされたドキュメントのオブジェクトグラフ自体は処理全体を通してメモリ上に保持されます。数百個の巨大なファイルに対して印刷キューを制御する場合は、ストリーミング用の大規模PDFワークフロー向けのDirect File APIと組み合わせると効果的です。まずディスク上で必要なページを分割、マージ、または選択してから、軽量化された結果のみをロードして印刷します。このようにして、PrintLoadedDocumentRenderLoadedPageToDC、およびRenderLoadedPageToClipboardは、外部DLLへの依存関係がなく、PDFの作成、編集、テキスト抽出、レンダリング、そしてTPrinter、任意のデバイスコンテキスト、クリップボードへの直接印刷までをカバーする、DelphiおよびC++Builder用のネイティブVCLライブラリであるHotPDF Componentの一部として提供されています