PDFiumは、ChromeのPDFタブの背後にあるレンダラーとして、ビューアエンジンとしての評判がありますが、最初に明確にしておくべきことは、PDFium Componentはこれまで存在しなかったドキュメントを構築することもできるということです。オーサリング側は、PDFiumのページオブジェクトAPIをラップしています。空のドキュメントを作成し、明示的な寸法のページを追加し、選択した座標で各ページにテキスト、ベクターパス、および画像をドロップします。学習するページ記述言語はなく、ループ内にプリントドライバーもありません。メソッドを呼び出すと、ライブラリがPDFオブジェクトを組み立て、SaveAs が結果をシリアル化します
手に入らないのはレイアウトエンジンです。これは、以下のすべての例を形作るため、前もって言っておく価値があります。PDFium Componentは、指示した場所に絶対座標でコンテンツを配置し、他の場所には配置しません。段落を折り返したり、改ページを越えてテキストを流し込んだり、行と列からテーブルを計算したりすることはありません。それらはあなたの仕事です。ワープロのように文章をリフローするものを期待して来たのであれば、今すぐ調整してください。これは正確で低レベルな配置APIであり、ドキュメントの植字というよりもキャンバスへの描画に近いです。生成された請求書、証明書、ラベル、およびすべての要素が属する場所がすでにわかっているレポートページの場合、その精度こそがまさに必要なものです
ファイルを生成する最小構成
空の TPdf と保存されたPDFの間には、ドキュメントの作成、ページの追加、書き出しという3つの呼び出しがあります。それ以外のすべては、その間にレイヤー化するコンテンツです
uses
Vcl.Graphics, // for clBlack and TColor
PDFium; // TPdf lives here
procedure CreateBlankPdf(const FileName: string);
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.CreateDocument; // empty in-memory document
Pdf.AddPage(0, 595, 842); // A4 portrait, in points
Pdf.AddText('First page', 'Arial', 18, 50, 780);
Pdf.SaveAs(FileName); // serialize to disk
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
古いスニペットを見たことがある人をつまずかせる詳細が1つあります。それは、CreateDocument の後に Pdf.Active := True を割り当てないということです。Active プロパティはドキュメントハンドルが存在するかどうかを報告し、CreateDocument はすでにそれを作成しているため、その呼び出しが戻った瞬間にプロパティはTrueになります。それを再度設定することは、せいぜい何もしない操作であり、最悪の場合、次の読者に誤解を与えます。Active は終了時にその役割を果たします。Falseを割り当てると、Free の前に基になるドキュメントが解放されます。これはクリーンなティアダウンの順序です。CreateDocument とファイルロードのオープンは相互に排他的なものとして扱ってください。ライブラリは、すでにドキュメントを開いている TPdf 上で新しいドキュメントを作成することを拒否するため、再利用とは現在のドキュメントを最初に閉じることを意味します
座標は左下から始まる
AddText、およびすべての配置呼び出しへの2番目の引数のペアは、PDFユーザー空間のポイントです。原点はページの左下隅にあり、Xは右に、Yは「上」に向かって実行されます。1単位は1ポイント(1/72インチ)であるため、A4ページは595×842単位であり、USレターは612×792です。この上向きのYは、「テキストがページから外れている」という混乱の最も一般的な原因です。画面やビットマップの座標は原点を上に置き、Yが下に向かって大きくなるためです。高さ842ポイントのページでは、上部付近の見出しはY 60ではなくY 780のあたりに配置されます。実行が予期しない場所に着地した場合、ページの高さからYを引いたものが、ほぼ常にあなたが実際に意図した数値です
AddPage は、最初の引数として1ベースで表される挿入位置を受け取り、便利な「ドキュメントの先頭」の省略表現として0を使用します。最初のページには0または1を渡すと、ページは先頭に挿入されます。末尾に追加するには、追加するカウントと一致する値を渡します。新しく追加されたページは現在のページにもなり、その後の描画呼び出しのターゲットになるため、追加した後に個別の「このページを選択する」ステップはありません。複数のページを追加し、後で前のページに戻って描画する必要がある場合は、PageNumber を設定してカーソルを移動します。作成した順にページを埋めている間は、そのままにしておくことができます
テキストの書き込み、そして静かに噛みつくフォントルール
AddText シグネチャには、単一の実行が必要とするすべてが含まれています。文字列、フォント名、ポイント単位のサイズ、XとYのアンカー、そしてオプションの色、透明度のためのアルファバイト、および度単位の回転角度です
procedure WriteHeader(Pdf: TPdf; const Title, Author: string);
begin
// Title in black, default opacity, no rotation
Pdf.AddText(Title, 'Arial', 20, 50, 780);
// A lighter byline 24 points below it
Pdf.AddText('By ' + Author, 'Arial', 11, 50, 756, clGray);
// A faint diagonal draft stamp across the page
Pdf.AddText('DRAFT', 'Arial', 64, 180, 380, clGray, $30, 45.0);
end;
アルファバイトは $00 (不可視) から $FF (不透明) まで実行されます。これが、ドラフトスタンプを単色のブロックではなく透かしにする理由です。$30 は約19%の不透明度であり、透かして読むのに十分です。角度はアンカーの周りを反時計回りに回転させるため、45度は古典的なコーナーからコーナーへのスタンプになります。これらのいずれにも個別の透かし機能は必要ありません。透かしは、大きくて半透明で回転した AddText の呼び出しにすぎず、本文の前または後にそれを描画することで、コンテンツの背後に配置されるか上に配置されるかが決まります
フォントは、失敗モードが静かであるため、注意深い文章に値します。フォント名を渡すと、PDFium ComponentはオペレーティングシステムにそのフォントのTrueTypeデータを要求し、それをドキュメントに埋め込みます。そのため、マシン上で構築されたファイルは、フォントがインストールされたことのないマシン上でも同じようにレンダリングされます。落とし穴は、名前が解決されない場合(タイプミスや、ビルドマシンに単に存在しない書体の場合)に何が起こるかです。例外はありません。ライブラリは、ラベルとして名前だけを持ち、何も埋め込まれていないテキストオブジェクトを作成することにフォールバックし、何が近いと見なすかの代用をビューアに任せます。テストではテキストが表示され、もっともらしく見えますが、異なるフォントがインストールされている場所でファイルを開いた瞬間にメトリクスやグリフがシフトします。生成マシンに存在することがわかっている名前を使用し、フォントリストをデプロイメントの依存関係として扱い、出力を信頼する前にクリーンなシステムのビューアでサンプルを開いてください
ベクターシェイプ:パスを構築してからコミットする
線、長方形、および塗りつぶされた領域はパスを経由します。CreatePath で1つを開きます。これは、開始点とすべてのスタイル設定(塗りつぶしモード、独自のアルファバイトを持つ塗りつぶしとストロークの色、ストローク幅、ラインキャップと結合)を一度に設定します。次に、LineTo、BezierTo、および ClosePath でそれを拡張し、最後に AddPath が完成したパスをページにコミットします。コミットのステップは忘れやすく、スキップすると何も生成されません
procedure DrawDivider(Pdf: TPdf; X, Y, Width: Single);
begin
// A thin horizontal rule. The rectangle overload sets a box directly:
// X, Y, Width, Height, then fill mode and colors.
Pdf.CreatePath(X, Y, Width, 0.5, fmNone, clBlack, $FF,
True, clBlack, $FF, 1.0);
Pdf.AddPath;
end;
procedure DrawTriangle(Pdf: TPdf);
begin
// Point overload: start at the first vertex, line to the rest, close.
Pdf.CreatePath(200, 300, fmWinding, clBlue, $80, True, clNavy, $FF, 2.0);
Pdf.LineTo(300, 300);
Pdf.LineTo(250, 400);
Pdf.ClosePath;
Pdf.AddPath; // nothing is drawn until this runs
end;
2つのオーバーロードが一般的なケースをカバーしています。4座標の形式は、X、Y、幅、および高さを取り、1回の呼び出しで軸に揃った長方形を提供します。これは、罫線、セルの境界線、または塗りつぶされた背景パネルを描画するために使用するものです。2座標の形式は開始点のみを設定し、残りの輪郭は LineTo と BezierTo で自分でトレースします。塗りつぶしモードは、重なり合う領域がどのようにペイントされるかを制御します。fmWinding(非ゼロのワインディング)はほとんどのソリッドな図形に適しており、fmAlternate(奇偶)は切り抜きや自己交差する輪郭を処理します。fmNone は塗りつぶしのないストロークのみのパスを残します。これは上記の仕切りが使用するものです
テーブルはパスとテキストであり、手作業で組み立てられる
テーブルプリミティブがないため、テーブルはループです。列のXオフセットと行の高さを決定し、AddText で各セルを記述し、長方形のパスで罫線を描画します。計算はあなた次第ですが、単純であり、一度書けば必要なあらゆるグリッドに一般化できます
procedure DrawTable(Pdf: TPdf; Left, Top: Double);
const
ColX: array[0..2] of Double = (0, 110, 210); // column offsets
RowH = 20;
var
Y: Double;
Row: Integer;
begin
// Header row
Pdf.AddText('Item', 'Arial', 10, Left + ColX[0], Top);
Pdf.AddText('Qty', 'Arial', 10, Left + ColX[1], Top);
Pdf.AddText('Price', 'Arial', 10, Left + ColX[2], Top);
// Rule under the header
Pdf.CreatePath(Left, Top - 5, 260, 0.5, fmNone, clBlack, $FF);
Pdf.AddPath;
// Data rows, stepping Y downward each iteration
Y := Top;
for Row := 1 to 3 do
begin
Y := Y - RowH;
Pdf.AddText('Item ' + IntToStr(Row), 'Arial', 9, Left + ColX[0], Y);
Pdf.AddText(IntToStr(Row * 2), 'Arial', 9, Left + ColX[1], Y);
Pdf.AddText('$' + IntToStr(Row * 10) + '.00', 'Arial', 9, Left + ColX[2], Y);
end;
end;
ここでも上方向が正であるため、各パスで行の高さだけYが下に向かって進んでいることに注意してください。これは、テキスト測定の欠如が示される場所でもあります。ライブラリは文字列がどのくらいの幅でレンダリングされたかを知らないため、長い項目名が次の列にはみ出すのを防ぐものは何もありません。データを制御する固定フォーマットの出力の場合、列のサイズを寛大に設定して次に進みます。純粋に可変のコンテンツの場合、入力を制限するか、配置する前に自分でグリフ幅を測定します。この時点で、専用のコンポジションライブラリが元を取り始めます
画像と複数ページ
ラスターコンテンツは、画像ヘルパーを介して取り込まれます。AddPicture はロードされた TPicture を取得してポイントに配置し、オプションの幅と高さでスケーリングします。AddImage はファイルパスまたは TBitmap を直接受け入れ、AddJpegImage はビットマップを介した往復なしでJPEGバイトをストリームします。他のすべてと同様に、配置座標はユーザー空間における画像の左下隅であり、幅と高さはポイント単位のページ上のサイズであり、ソースのピクセル寸法ではありません
procedure CreateMultiPageReport(const FileName: string; PageCount: Integer);
var
Pdf: TPdf;
P: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.CreateDocument;
for P := 1 to PageCount do
begin
Pdf.AddPage(P, 595, 842); // append; the new page becomes current
Pdf.AddText('Page ' + IntToStr(P) + ' of ' + IntToStr(PageCount),
'Arial', 10, 50, 30); // footer near the bottom edge
// ... draw this page's body here ...
end;
Pdf.SaveAs(FileName);
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
複数ページのドキュメントは、ループ内の単一ページのパターンです。各 AddPage はページを追加して現在のページにするため、次に描画する本文とフッターは、いま追加したページに着地します。ページを追加することですでにカーソルがそこに移動しているため、このループ内で PageNumber を再割り当てすることはありません。PageNumber が必要になるのは、作成順序とは関係なくページに戻るときだけです。最後のページが埋められた後、最後に1回 SaveAs を呼び出します。プレーンなファイルではなくアーカイブプロファイルが必要な場合、同じドキュメントオブジェクトが SaveAsPdfA およびその他の適合バリアントを公開しているため、出力標準の選択は異なる保存呼び出しであり、異なるビルドパスではありません
これが適合する場所
正直な枠組みとしては、PDFium ComponentのオーサリングAPIは、PDFiumのページオブジェクトモデル上の忠実で薄いレイヤーであるということです。実際のドキュメント作成、実際の埋め込みフォント、実際のベクターおよびラスターコンテンツであり、標準に準拠したファイルにシリアル化されます。それはリフローするドキュメントエンジンではありませんし、そのふりをすることもありません。境界線はテキストレイアウトです。出力がテンプレート化されている場合(請求書、証明書、ラベル、固定グリッドにレンダリングされるダッシュボードなど)、絶対座標モデルは直接的かつ高速であり、コードは読みやすいままです。出力が独自に折り返してページネーションする必要がある長文の文章である場合、これらの呼び出しの上にレイアウトエンジンを再構築することになり、それはその仕事には間違ったツールです。自分がその境界線のどちら側にいるかを知ることが、決定の大部分を占めます
ここで説明されている作成メソッドは、Delphi用の PDFium Component の一部であり、このオーサリングパスと、PDFiumがよりよく知られているレンダリングおよびテキスト抽出機能を組み合わせています