テキストを描画する - PDF::API2で帳票作成
PDF::API2で、PDFにテキストを描画するには、次のようにします。
use PDF::API2; my $pdf = PDF::API2->new; my $page = $pdf->page; # コアフォントの読み込み my $font = $pdf->corefont('Helvetica'); # テキストを表現するコンテンツオブジェクトを取得 my $text = $page->text(); # フォントを設定 $text->font($font, 11); # 描画位置を設定(原点は、左下。デフォルトでは、ページの幅が612単位、高さが792単位) $text->translate(30, 750); # テキストを描画 $text->text('Hello World!'); my $pdf_file = 'text_core_font.pdf'; $pdf->saveas($pdf_file);
フォントオブジェクトの取得
PDF::API2では、フォントを表現するフォントオブジェクトをcorefontメソッドあるいはttfontメソッドを使って取得します。ここでは、corefontオブジェクトを使って描画しています。
# コアフォントの読み込み my $font = $pdf->corefont('Helvetica');
コアフォントを取得するにはcorefontメソッドを使います。注意点としては、corefontでは、日本語が描画できないことに注意してください。日本語を利用したい場合は、ttfontで日本語をサポートするトゥルータイプフォントを、指定する必要があります。
テキストを表現するコンテンツオブジェクト
PDF::API2では、テキストを表現するコンテンツオブジェクトをページオブジェクトのtextメソッドを使って取得します。ここでは、テキストを表現するコンテンツオブジェクトをテキストオブジェクトと呼びます。これはPDF::API2::Contentオブジェクトです。
# テキストを表現するコンテンツオブジェクトを取得 my $text = $page->text();
フォントの設定
PDF::API2では、テキストオブジェクトのfontメソッドで、フォントを設定します。
# フォントを設定 $text->font($font, 11);
描画位置の指定
PDF::API2では、テキストオブジェクトのtranslateメソッドで描画位置を設定します。
# 描画位置を設定(原点は、左下。デフォルトでは、ページの幅が612単位、高さが792単位) $text->translate(30, 750);
原点は、左下。デフォルトでは、ページの幅が612単位、高さが792単位です。PDF座標については、PDFファイルを作成するの「用紙サイズを設定する」の解説を見てください。
注意点としては、ページの余白やテキストの行数などの設定などはできず、描画位置は、translateメソッドで指定する必要があることです。描画位置を自分で計算して指定する必要があることに注意してください。
テキストの描画
PDF::API2では、テキストオブジェクトのtextメソッドでテキストを描画します。
# テキストを描画 $text->text('Hello World!');
テキストを描画すると、現在の描画位置のx座標がテキストの終端まで移動します。この位置は、textposメソッドで取得できます。
テキストはフォントのベースライン上に書き出されることに注意してください。
フォントの色を設定する
フォントの色を設定するには、strokecolorとfillcolorに色を設定します。フォントの色を直接指定できるメソッドはないので注意してください。
# フォントの色を設定する - 色名 $text->strokecolor('red'); $text->fillcolor('red'); # フォントの色を設定する - RGB $text->strokecolor('red'); $text->fillcolor('red'); # フォントの色を設定する - CMYK $text->strokecolor('%FF000000'); $text->fillcolor('%FF000000');
テキストの描画も、コンテンツの描画の機能を使って、フォントに存在するアウトラインのパスを作成した後に、パスの内部を塗りつぶすことで作成しています。
図形の描画とstrokecolorとfillcolorの詳しい解説は、基本図形の描画と表の作成を参考にしてください。
背景色を設定する
背景色を描画して、その上に、テキストを描画する方法を解説します。実は、これには、落とし穴があるので、注意しましょう。
PDF::API2の動作として、最初に定義されたコンテンツオブジェクトを先に描画するという特性があります。
もし、テキストコンテンツオブジェクトを生成した後に、グラフィックコンテンツオブジェクトを生成したとすれば、テキストは、グラフィックで、消されてしまいます。ですので、必ず、グラフィックコンテンツオブジェクトを先に、テキストコンテンツオブジェクトを後に生成してください。
# グラフィックコンテンツオブジェクトを先に生成 my $gfx = $page->gfx; # テキストコンテンツオブジェクトを次にに生成 my $text = $page->text; # 間違い(落とし穴、グラフィックでテキストが消える) my $text = $page->text; my $gfx = $page->gfx;
もう一つ必要なこととして、フォント色を設定しておく必要があります。これを忘れると、背景色が文字色なって、まるで描画されていないようになります。
my $gfx = $page->gfx; # フォント色を忘れずに設定 my $text = $page->text; $text->strokecolor('#000'); $text->fillcolor('#000'); # 背景を描画 # ... # テキストを描画 $text->text('Hello'); <pre> 背景色は、単純にグラフィックコンテンツオブジェクトの機能を使って、書きます。たとえば、四角形を描画して、fillすれば、背景色になります。<a href="/blog/20200101082734.html">基本図形の描画と表の作成</a>を参考にしてください。 こうすることで、背景色の上に、テキストを描画することができます。 <h3>アンダーラインを描画</h3> PDF::API2でテキストにアンダーラインを描画するにはtextメソッドの「-underline」オプションを指定します。「auto」で自動的にアンダーラインの位置が決まります。 <pre> # アンダーラインを描画 $text->text('Hello World!', -underline => 'auto');
太字にする
コアフォントで、太字を使うには、太字用のフォントを指定します。
# コアフォントの読み込み my $font = $pdf->corefont('Helvetica-Bold');
コンピューターにおける太字というのは、表現方法に二種類あって、太字用のフォントを使うか、アプリケーションで、文字を重ねて描画するかのどちらかです。
注意点として、PDF::API2は、太字にするためのAPIを持っていません。文字を横に拡大するhscaleというメソッドを持っていますが、文字間隔も開いて、太字としては、不自然な見栄えになってしまいます。
代替的な表現として、フォントサイズを1増やすことも検討してみてください。
# 太字の代替表現として、フォントサイズを1増やす $text->font($font, $font_size + 1);
文字間隔を設定
文字間隔を設定するにはcharspaceメソッドを使用します。デフォルトは0です。
# 文字間隔を設定 $text->charspace(1);
テキストを指定位置を中心として描画
PDF::API2でテキストを指定位置を中心として描画するには、text_centerメソッドを使用します。オプションは、textメソッドと同一です。
# アンダーラインを描画 $text->text_center('Hello World!');
ページの見出しなどを画面の中央に表示したい場合などに利用できます。
テキストを指定位置を右端として描画
PDF::API2でテキストを指定位置を右端として描画として描画するには、text_rightメソッドを使用します。オプションは、textメソッドと同一です。
ページ番号などの、右下に表示したい場合などに利用できます。
# アンダーラインを描画 $text->text_right('Hello World!');
現在のテキストの描画位置を取得する
PDF::API2で現在のテキストの描画位置を取得するには、textposメソッドを使用します。
# 現在のテキストの描画位置を取得 my ($x, $y) = $text->textpos();
この値を使って、改行すべき位置をチェックしたり、改ページすべき位置をチェックすることができます。
フォントサイズを取得する
PDF::API2で、既存のテキストのフォントサイズを取得するには、textstateメソッドを引数なしで呼び出し、テキストの状態を表現するハッシュが返されるので、fontsizeの値を取り出します。
# 既存のテキストのフォントサイズを取得 my %text_state = $text->textstate; my $font_size = $text_state{fontsize};
テキストの高さを取得する
テキストの高さは、実は、フォントサイズと同じです。フォントサイズ16は、PDF単位の16と同じです。改行をする場合は、フォントサイズで改行すれば、重なることがありません。
my $font_size = 16; my $text_height = $font_size;
行の高さ
行と行の間は行間が少し空いていますね。行の高さは、テキストの高さに、行間の幅を加えたものになります。フォントサイズ(テキストの高さ)が11で、行間が2だとすると、行の高さは、13となります。
# フォントサイズ(テキストの高さ) my $font_size = 16; my $text_height = $font_size; # 行間 my $line_margin = 2; # 行の高さ my $line_height = $text_height + $line_margin;
例えば、改行をして、移動するときは、行の高さを減算してあげればよいです。
描画するテキストの幅を取得
描画するテキストの幅をあらかじめ取得するにはadvancewidthメソッドを使用します。
my $text_width = $text->advancewidth($string);
右側にはみ出した場合に改行という処理を行う場合に、利用できます。
余白の設定
PDF::API2を使う場合は、ページに余白を設定したい場合は、GUIでの画面のように、余白の値を設定することはできません。自分で計算する必要があります。
ただしデメリットばかりではなく、この方法を覚えておくと、奇数ページと偶数ページで、余白を変えたりと、非常に柔軟性があります。
PDF単位とインチとmmの関係
まず最初にPDFで指定されるPDF単位とインチとmmの関係を覚えましょう。
PDFの1単位は、1/72インチです。
1インチは、25.4mmです。
PDFの1単位は、mmに換算すると約0.352777777777778mmです。割り切れません。
PDFの3単位は、mmに換算すると約1.05833333333333mmです。割り切れません。
PDFの9単位は、mmに換算すると、3.175mmです。
PDFの3単位が1mmに近いですね。9単位がきりが良いです。
余白をだいたい12mm~13mmにしたいとすれば、PDF単位の36を使います。考え方としては、PDFの9単位は、mmに換算すると、3.175mmですから、これに4をかけると、36単位で、12.7mmとなります。
ここでの解説は、上下左右のすべての余白が36単位であるとします。
# ページ上余白 my $page_top_margin = 36; # ページ下余白 my $page_bottom_margin = 36; # ページ左余白 my $page_left_margin = 36; # ページ右余白 my $page_right_margin = 36;
上下余白については、フォントのベースラインが基準になりますので、見た目よりも、すこし文字がはみ出ることに注意してください。
描画開始位置と描画終了位置
余白を考える場合は、描画の開始位置のXY座標と、描画終了位置のXY座標を計算で求める必要があります。
まず、用紙サイズの幅を取得しましょう。(参考:PDFファイルを作成するの用紙サイズを取得する)
# 用紙サイズの幅と高さを取得 my @page_size_infos = $page->mediabox; my $page_width = $page_size_infos[2]; my $page_height = $page_size_infos[3];
PDFの座標は、左下から始まるということを、意識しておきましょう。これが、典型的なPDFドキュメントとすると、ページの幅が、612単位、高さが792単位ですので、ページの左上の座標は(0, 791)です。(0, 0)ではないので注意してください。
x座標は、そのまま計算できますが、y座標は、ページの高さから減算して求める必要があります。
描画開始位置のxy座標と、描画終了位置のxy座標を計算して求めると次のようになります。
# 描画開始x座標 my $render_start_x = 0; # 描画開始y座標 my $render_start_y = $page_height - 1; # 描画終了x座標 my $render_end_x = $page_width - 1; # 描画終了y座標 = 0; my $render_end_y = 0;
これに余白を追加すると以下のようになります。
# 描画開始x座標 my $render_start_x = $page_left_margin; # 描画開始y座標 my $render_start_y = $page_height - 1 - $page_top_margin; # 描画終了x座標 my $render_end_x = $page_width - 1 - $page_right_margin; # 描画終了y座標 my $render_end_y = $page_bottom_margin;
余白、改行、改ページに対応したテキストの描画
では、ここで余白、改行、改ページを意識して、テキストを、描画してみましょう。右端に到達すると改行します。英語のみです。英単語は、1文字づつの単位で改行されます。下に到達すると改ページします。
use strict; use warnings; use utf8; use PDF::API2; my $pdf = PDF::API2->new; # コアフォントの読み込み my $font = $pdf->corefont('Helvetica-Bold'); my $font_size = 11; # テキストの高さ my $text_height = $font_size; # 行間 my $line_margin = 2; # 行の高さ my $line_height = $text_height + $line_margin; # 用紙サイズのデフォルトのグローバル設定(用紙サイズを取得するため) $pdf->mediabox(undef); # ページの高さと幅 my @page_size_infos = $pdf->mediabox; my $page_width = $page_size_infos[2]; my $page_height = $page_size_infos[3]; # ページ余白 my $page_top_margin = 36; my $page_bottom_margin = 36; my $page_left_margin = 36; my $page_right_margin = 36; # 描画開始x座標 my $render_start_x = $page_left_margin; # 描画開始y座標 my $render_start_y = $page_height - 1 - $page_top_margin; # 描画終了x座標 my $render_end_x = $page_width - 1 - $page_right_margin; # 描画終了y座標 my $render_end_y = $page_bottom_margin; # メッセージ(1000回の同じメッセージ繰り返し) my $message = 'Hello World!' x 1000; # 改行戻りx座標を保存 my $new_line_left_x = $render_start_x; # 現在描画位置 my $cur_render_x = $render_start_x; my $cur_render_y = $render_start_y; # ページの作成 my $page = $pdf->page; # テキストを表現するコンテンツオブジェクトを取得 my $text = $page->text(); # フォント設定 $text->font($font, $font_size); # 描画開始位置に移動 $text->translate($render_start_x, $render_start_y); # 1文字づつ取り出し while ($message =~ /(.)/g) { my $char = $1; # 現在描画位置が、描画終了x座標を超えていれば改行 if ($cur_render_x > $render_end_x) { $cur_render_x = $new_line_left_x; $cur_render_y -= $line_height; # 描画開始位置に移動 $text->translate($cur_render_x, $cur_render_y); } # 現在描画位置が、描画終了y座標を超えていれば改ページ if ($cur_render_y < $render_end_y) { $cur_render_x = $render_start_x; $cur_render_y = $render_start_y; # ページの作成 $page = $pdf->page; # テキストを表現するコンテンツオブジェクトを取得 $text = $page->text(); # フォント設定 $text->font($font, $font_size); # 描画開始位置に移動 $text->translate($cur_render_x, $cur_render_y); } # 文字を描画 $text->text($char); # 描画位置を更新 ($cur_render_x) = $text->textpos; } my $pdf_file = 'margin_full_text.pdf'; $pdf->saveas($pdf_file);
「Hello World!」が繰り返される、余白つきページが、3ページ出力されます。
僕の環境では、1万2000文字で1~2秒かかっています。PDF出力は、意外に重い処理です。
HTMLの見出しやpタグを表現するには?
ここでは、詳しく解説しませんが、ヒントだけ書いておきます。少なくとも、タグを解析する必要があります。正規表現や、HTMLを解析してくれるモジュールが利用できるでしょう。後で、リストやテーブルに対応することを考えると、HTMLを解析してくれるモジュールを使うのが、汎用的でしょうか。
<h1>タイトル</h1> <p> メッセージ </p>
XML::Simpleの利用が、簡単そうです。固定書式であれば、難しいことを考えることがないので、XML::Simpleで、Perlの多次元データ構造にできれば、よさそうな感じはします。
本を作るときのような、固定書式で出力するのであれば、CSSの解析はいらなさそうです。見出しやpタグごとの、マージンの設定をプログラム上で行う必要がありそうですね。
英単語の単位で改行するには?
英単語を改行の途中で切りたくないというのは、よくありそうですね。この場合は、少し難しいですが、次のように考えましょう。
まず、英文字が始まるx座標を覚えておきます。また、英文字が始まった場合に、これを、$tmp_wordのような文字列に保存しておきましょう。テキスト出力はまだ待って、次のループに移動しましょう。英文字以外が来た場合に、その直前までが、一つの英単語です。もし、$tmp_wordの幅が、右端を超えていたら、改行して出力、超えていなかったら、そのまま出力です。
日本語を描画するためのフォント
PDF::API2で日本語を描画するためには、日本語に対応したフォントが必要になります。コアフォントは、英語だけです。
フォントの中でも、TrueType Font(トゥルータイプフォント)と呼ばれるフォントを使う必要があります。
フォントは、一つのファイルとして提供されていて、TrueType Fontの拡張子は、ttfです。
TrueType Fontの取得
無料で利用できるTrueTypeフォントがあるので、それを取得しましょう。検索するとたくさんでてきます。
選択の注意点としては、オープンフォントではなく、トゥルータイプフォントを選ぶことと、PDFに埋め込み可能なフォントを選ぶことです。
トゥルータイプフォントファイルは、ポータブルなので、機種依存文字を使わない限りは、WindowsでもMacでもLinuxでも同じように扱えます。OSへのフォントのインストール方法に違いがありますが、ファイルを直接読み込む場合は、関係ありません。(参考:Windows用フォントとMac用フォントは違うのか)
ここでは、日本語を描画できるTrueTypeフォントの中で、kochi-gothic-subst.ttfというものを一つ紹介しておきます。
ダウンロードして、好きな場所に配置してください。
TrueType Fontの読み込み
TrueType Fontは、ttfontメソッドで読み込むことができます。取得したTrueType Fontのパスを指定します。
my $true_type_font_file = '/path/kochi-gothic-subst.ttf'; my $font = $pdf->ttfont($true_type_font_file);
corefontでコアフォントを読み込んでいる場所を、上記に書き換えると、日本語を出力できます。
Perlの日本語を扱う場合のお約束として、ファイルはUTF-8で保存して、use utf8を行ってください。
use utf8;
PDF::API2においては、フォントは、PDFファイルに埋め込まれ、PDFファイルは、フォントファイルを参照しません。
プログラムのおいたディレクトリにフォントファイルを置いて、読み込む場合は、FindBinモジュールを使って、次のようにするのが簡単です。
use FindBin; my $true_type_font_file = "$FindBin::Bin/kochi-gothic-subst.ttf";
日本語対応のTrueTypeフォントで太字にしたい
日本語対応のTrueType Fontで太字にしたい場合は、ノーマルフォントとは別に、太字のフォントも用意されているフォントを利用しましょう。
日本語、TrueTypeフォント、太字あり、商用利用可能なものを、調べたので紹介します。
あおぞら明朝フォント
ダウンロードは以下から。
aozoramincho-readme-ttf.zipを解凍すると、以下のフォントファイルがあります。
AozoraMincho-bold.ttf 太字 AozoraMincho-thin.ttf AozoraMinchoBlack.ttf AozoraMinchoHeavy.ttf AozoraMinchoLight.ttf AozoraMinchoMedium.ttf AozoraMinchoRegular.ttf 通常
これらのファイルを使うと、通常と太字に対応できます。
# 通常 my $true_type_font_file = '/path/AozoraMinchoRegular.ttf'; my $font = $pdf->ttfont($true_type_font_file); # 太字 my $true_type_font_bold_file = '/path/AozoraMincho-bold.ttf'; my $font_bold = $pdf->ttfont($true_type_font_file);
日本語フォントにおいては、テキストのベースラインは、文字の下端に存在することがほとんどだと想定しますが、あおぞら明朝フォントもベースラインは、文字の下端に存在します。つまり、translateで移動した点に対して、右上にはみ出るように、文字が描画されるということです。
この場合は、余白の上の設定は、文字が上側にはみ出るために、その分を考慮する必要があることに注意してください。
余白、改行、改ページ、日本語、太字に対応したサンプル
余白、改行、改ページ、日本語、太字に対応したサンプルです。「こんにちは、世界。」と繰り返し表示され、世界の場合だけ太字になります。
use strict; use warnings; use utf8; use FindBin; use PDF::API2; my $pdf = PDF::API2->new; # 日本語に対応したTrueTypeフォントの読み込み my $true_type_font_file = "$FindBin::Bin/AozoraMinchoRegular.ttf"; my $font = $pdf->ttfont($true_type_font_file); my $true_type_font_bold_file = "$FindBin::Bin/AozoraMincho-bold.ttf"; my $font_bold = $pdf->ttfont($true_type_font_bold_file); my $font_size = 11; # テキストの高さ my $text_height = $font_size; # 行間 my $line_margin = 2; # 行の高さ my $line_height = $text_height + $line_margin; # 用紙サイズのデフォルトのグローバル設定(用紙サイズを取得するため) $pdf->mediabox(undef); # ページの高さと幅 my @page_size_infos = $pdf->mediabox; my $page_width = $page_size_infos[2]; my $page_height = $page_size_infos[3]; # ページ余白 my $page_top_margin = 36; my $page_bottom_margin = 36; my $page_left_margin = 36; my $page_right_margin = 36; # 描画開始x座標 my $render_start_x = $page_left_margin; # 描画開始y座標 my $render_start_y = $page_height - 1 - $page_top_margin; # 描画終了x座標 my $render_end_x = $page_width - 1 - $page_right_margin; # 描画終了y座標 my $render_end_y = $page_bottom_margin; # メッセージ(1000回の同じメッセージ繰り返し) my $message = 'こんにちは、世界。' x 1000; # 改行戻りx座標を保存 my $new_line_left_x = $render_start_x; # 現在描画位置 my $cur_render_x = $render_start_x; my $cur_render_y = $render_start_y; # ページの作成 my $page = $pdf->page; # テキストを表現するコンテンツオブジェクトを取得 my $text = $page->text(); # フォント設定 $text->font($font, $font_size); # 描画開始位置に移動 $text->translate($render_start_x, $render_start_y); # 1文字づつ取り出し while ($message =~ /(.)/g) { my $char = $1; # 現在描画位置が、描画終了x座標を超えていれば改行 if ($cur_render_x > $render_end_x) { $cur_render_x = $new_line_left_x; $cur_render_y -= $line_height; # 描画開始位置に移動 $text->translate($cur_render_x, $cur_render_y); } # 現在描画位置が、描画終了y座標を超えていれば改ページ if ($cur_render_y < $render_end_y) { $cur_render_x = $render_start_x; $cur_render_y = $render_start_y; # ページの作成 $page = $pdf->page; # テキストを表現するコンテンツオブジェクトを取得 $text = $page->text(); # フォント設定 $text->font($font, $font_size); # 描画開始位置に移動 $text->translate($cur_render_x, $cur_render_y); } # 世界を太字 if ($char eq '世' || $char eq '界') { $text->font($font_bold, $font_size); } else { $text->font($font, $font_size); } # 文字を描画 $text->text($char); # 描画位置を更新 ($cur_render_x) = $text->textpos; } my $pdf_file = 'margin_full_text_jp.pdf'; $pdf->saveas($pdf_file);