技術記事

DelphiでのHotPDF TextOut:サイズ、スタイル、回転、および間隔

HotPDFドキュメント内のすべての目に見える文字列は、1つの呼び出しを介して到着します:TextOut(X, Y, angle, Text)。Hello Worldの例では、これを最もプレーンな形で使用しており、フォントを1回設定し、4つの引数を適切なデフォルトのままにしています。その最初のページを過ぎると、同じ4つの引数がレイアウトの全重量を支えます。3番目の引数は実行を回転させます。その直前に設定されたフォントがサイズとスタイルを決定します。そして、ページの角からポイント単位で測定されるX、Yのペアは、きれいなレポートと、重なったり、切り取られたり、他の人のプリンターで1行下にずれたりするテキストとの間に立ちはだかる唯一のものです。これがTextOutがその価値を証明する場所であり、デフォルトでは不十分になる場所です

シグネチャは何よりもまず念頭に置いておく価値があります:XYはポイント単位のSingleであり、angleは度単位のExtendedであり、TextWideStringであるため、Unicodeは別の呼び出しなしで通過します。2つ目のオーバーロードは、すでにグリフコードを保持している場合のために、PWORDに加えて長さを取りますが、通常の文字列の場合、手を伸ばすのはWideString形式です

サイズとスタイルはTextOutからではなくSetFontから来ます

TextOutにはサイズパラメーターがありません。サイズ、ウェイト、スラントなど、そのすべては実行に先行するSetFont呼び出しに存在し、次のSetFontがそれを置き換えるまで有効なままです。これが初日の混乱の大部分を説明する単一の事実です:3回の呼び出し前に何かが[fsBold]を設定し、何もそれをクリアしなかったため、行が太字で出力されます

Pdf.CurrentPage.SetFont('Times New Roman', [], 24);
Pdf.CurrentPage.TextOut(72, 740, 0, 'Quarterly Report');        // 24pt regular

Pdf.CurrentPage.SetFont('Times New Roman', [fsBold], 12);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Revenue');                 // 12pt bold

Pdf.CurrentPage.SetFont('Times New Roman', [fsItalic], 11);
Pdf.CurrentPage.TextOut(72, 694, 0, 'figures in thousands');    // 11pt italic

Pdf.CurrentPage.SetFont('Courier New', [fsBold, fsItalic], 10);
Pdf.CurrentPage.TextOut(72, 676, 0, '  +18.4% YoY');            // styles combine

2番目の引数はTFontStylesのセットであるため、[fsBold, fsItalic]は太字の斜体であり、[]はプレーンです。サイズはポイント単位であり、座標と同じ単位であるため、垂直方向の間隔について推論しやすくなります:12ポイントの行は呼吸するために約14から16ポイントの垂直ステップを必要とするため、行ごとにYを14落とすことは合理的な開始レディングです。自動の行送りはありません。ベースラインは自分で計算します。これは段落にとっては面倒ですが、すべてのフィールドが固定座標に配置されるフォームにとっては正確です

フォント名に関する2つの実用的な注意点。それはビルドマシンにインストールされているフォントに対して解決され、OSが返すものが何であれ埋め込まれるため、デスクトップで解決される名前とビルドサーバーで解決される名前が同じ書体であることは保証されません。そして、フォントは文字列内のスクリプトをカバーしている必要があります。ラテン語のみの書体の下でのキリル文字またはCJKテキストの実行は、エラーなしに欠落したグリフのボックスとしてレンダリングされます。これが、Hello Worldページが言語を混在させるときに幅広いUnicode書体に手を伸ばす理由です

Arial、Times New Roman、およびCourier Newを、いくつかの文字セットにわたってレギュラー、ボールド、イタリックのスタイルでレンダリングしたHotPDF TextOutページ

angle引数はアンカーを中心に回転します

3番目の引数は、ほとんどのコードが永遠にゼロのままにしておくものです。ゼロ以外の値を渡すと、実行はそれ自身の(X、Y)アンカー、つまりテキストの左下を中心に、その度数だけ反時計回りに回転します。アンカー自体は移動しないため、水平ラベルを配置したのと同じ座標が回転した双子を配置します。グリフが進む方向のみが変わります

Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);

// A vertical axis label down the left margin: 90 degrees reads bottom-to-top.
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');

// A diagonal DRAFT watermark across the page body.
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');

// Column headers tilted 60 degrees so long labels fit a narrow table.
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(120, 600, 60, 'Q1 actual');
Pdf.CurrentPage.TextOut(160, 600, 60, 'Q2 actual');

90度は一般的なケースで、グラフの側面に沿って走るラベルや背表紙のタイトルです。45度は傾いた列ヘッダーを処理し、広いラベルを狭い列の上に隣の列にこぼれることなく配置できるトリックです。回転はアンカーの解釈方法を変更しないため、人々をつまずかせます:90度の実行は依然として(X、Y)から始まり、そこから上に向かって成長するため、回転したラベルを中央に配置するには、角度ではなくアンカーを調整します。複数の回転した実行がベースラインを共有する場合、積み上げられた水平線のためにYをステップするのとまったく同じように、それらに同じYを与えてXをステップさせます

推測せずに座標を配置する

座標はレビューを生き延びるか、または静かに失敗する部分です。HotPDFはページの左下隅から測定し、Yは上に向かって成長し、1インチあたり72のポイントで測定されます。USレターページは612 x 792ポイントです。A4は595 x 842です。したがって、Letterの1インチの上マージンは、最初のベースラインを上部近くの小さな数字ではなく、Y = 792マイナス72マイナスフォントサイズの近くに配置します。Yがゼロから下に向かって成長する画面座標から来た人は皆、最初の行を下端から外れて書き、それがどこに行ったのかを10分間疑問に思います

レイアウトを、マジックナンバーの列ではなく、名前付きアンカーに対する算術として扱います。左マージン、行ごとにデクリメントするランニングベースライン、および固定のレディングにより、ラベルのブロックはリテラルの壁ではなく短いループに変わります:

const
  LeftMargin = 72;        // 1 inch in
  TopBaseline = 720;       // first line, ~1 inch down on Letter
  Leading = 16;            // vertical step between lines
var
  Y: Single;
  Line: string;
begin
  Pdf.CurrentPage.SetFont('Arial', [], 11);
  Y := TopBaseline;
  for Line in ReportLines do
  begin
    Pdf.CurrentPage.TextOut(LeftMargin, Y, 0, Line);
    Y := Y - Leading;
    if Y < 72 then            // bottom margin reached
    begin
      Pdf.AddPage;
      Pdf.CurrentPage.SetFont('Arial', [], 11);  // font resets on a new page
      Y := TopBaseline;
    end;
  end;
end;

改ページのガードは誰もが最初に忘れる行であり、現場が最も大きな打撃を受ける行です。TextOutの下にフローレイアウトはありません。下マージンを超えてデクリメントすると、テキストは警告なしにガターへ、ページから外れ、何もないところへと描画され続けます。そのため、自分でYを監視し、フロアを横切ったときにAddPageを呼び出し、ベースラインをリセットします。AddPageの後のSetFontはオプションのパディングではありません:現在のフォントは改ページを生き延びず、それをスキップすると、新しいページの最初の実行はビューアのデフォルトの書体で出力されます

フィットと配置のための文字と単語の間隔

時々、文字列は正しいが幅が間違っていることがあります:固定のルールにまたがらなければならないヘッダー、より風通しの良い数字で読むべきコード、値をナッジして整列させる必要がある列などです。PDFには、このための2つのテキスト状態演算子があります。文字間隔(Tc、すべてのグリフの後に追加される余分なスペース)と単語間隔(Tw、各スペース文字に追加される余分なスペース)であり、どちらもスケーリングされていないテキストスペース単位、事実上現在のフォントサイズでのポイントで表されます。これらはTextOutの引数ではなく状態であるため、設定し、描画し、元に戻します

// Letter-space a short heading so it stretches across a rule.
Pdf.CurrentPage.SetCharacterSpacing(4);
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(72, 740, 0, 'S U M M A R Y');
Pdf.CurrentPage.SetCharacterSpacing(0);   // reset before normal body text

// Open up the gaps between words on a single wide line.
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 712, 0, 'Name        Department        Extension');
Pdf.CurrentPage.SetWordSpacing(0);

単語間隔はスペース文字(コード32)にのみ作用します。これには知っておくべき結果があります:ASCIIスペースを持たないCJKの実行の中では何もしませんし、バイトではなくグリフインデックスとしてエンコードされたテキストと奇妙に相互作用します。ラテン語の表形式の出力の場合、文字列を再入力せずにギャップを広げるための安価な方法です。文字間隔は、調整をスペースに集中させるのではなく、すべてのグリフに均等に広げるため、目標の幅に到達しなければならない見出しに適したツールです

リセットがすべての規律です。間隔は、フォントと同様に、ページの描画状態の一部であり、状態は変更するまで持続します。1つの見出しを文字間隔で設定し、それをゼロにすることを忘れると、下のすべての段落がそのストレッチを継承します。これは、カジュアルな校正は生き延びるが慎重な校正では失敗する、微妙で配置しにくい間違いとして読み取られます。信頼できる習慣は、間隔の値を設定し、それを必要とする実行を描画し、次の行でそれをゼロに戻すことです。そうすれば、後続のコードは前のセクションが何をしたかを知る必要がありません

水平方向のテキストスケーリング、文字間隔、単語間隔、および塗りつぶしとストロークのレンダリングモードを比較するHotPDF TextOutページ

実際に壊れる場所での出力の確認

テキストレイアウトは最初のマシンではなく2番目のマシンで失敗するため、重要なチェックはデスクから離れた場所で行われます。開発用フォントセットがインストールされていないシステムで生成されたファイルを開き、アクセント付きラテン文字、非ラテン文字スクリプト、句読点を含む埋め込まれた書体が、簡単な文字をスポットチェックするのではなく、1回のパスで依然としてレンダリングされることを確認します。数行を選択してコピーし、テキストがアウトラインではなく実際のテキストであることを確認します。これは検索や抽出がスコープに入った瞬間に重要になります。整頓されたプレースホルダーではなく、最も長いドイツ語のラベルと最も広い数字など、代表的なデータをレイアウトに供給します。フィールドをオーバーフローさせる実行は常に手で入力しなかったものだからです。そして、ページがプレプリントされたフォームに着地する必要がある場合は、1つのサンプルを印刷またはラスタライズして元のサンプルの上に置きます。1/4ミリのベースラインのドリフトは画面上では見えませんが、紙の上では明らかです

まだ1ページも書いていない場合は、HotPDF Hello Worldの例から始めてください。これはドキュメント、フォント、および上記のすべてが依存する左下の座標系を設定します。ここに示すTextOutSetFont、および間隔の呼び出しは、DelphiおよびC++Builder用のHotPDF Componentの一部です