はじめに
PHPでPDFのテンプレートファイルを読み込んで帳票を出力する方法をご紹介します。ライブラリは、TCPDFとFPDIを使用します。
今回、ただ普通のサンプルコードを紹介するだけでは面白くないので、動作確認も兼ねてPDF履歴書を作成できるWebサイトを作ってみました。
こちら↓
PDF履歴書作成ツール | 登録不要でサクッとブラウザで作る!
本エントリーでは『PDF履歴書作成ツール』の中身を解説する形で、PHPでPDFテンプレートを読み込んでデータを書き込む方法をご紹介したいと思います。
クライアント側
Bootstrapを使用しています。
サーバ側
TCPDFのダウンロード
https://github.com/tecnickcom/tcpdf
FPDIのダウンロード
https://www.setasign.com/products/fpdi/downloads/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
<?php // // ********************************************************************* // // 初期設定 // // ********************************************************************* require_once('tcpdf/tcpdf.php'); require_once('fpdi/autoload.php'); use setasign\Fpdi\TcpdfFpdi; $pdf = new TcpdfFpdi(); $pdf->SetMargins(0, 0, 0); $pdf->SetCellPadding(0); $pdf->SetAutoPageBreak(false); $pdf->setPrintHeader(false); $pdf->setPrintFooter(false); // テンプレート読み込み $pdf->setSourceFile('resume_template.pdf'); // 用紙サイズ $pdf->AddPage('L', 'A3'); $pdf->useTemplate($pdf->importPage(1)); // フォント設定 - IPA明朝 $tcpdf_fonts = new TCPDF_FONTS(); $font = $tcpdf_fonts->addTTFfont('tcpdf/fonts/ipam.ttf'); // // ********************************************************************* // // 各項目の座標値、文字サイズ設定 // // ********************************************************************* $item = array(); $item["output_year"] = array("x" => 85.0, "y" => 21.8, "size" => 9); // 出力年 $item["output_month"] = array("x" => 105.0, "y" => 21.8, "size" => 9); // 出力月 $item["output_day"] = array("x" => 121.0, "y" => 21.8, "size" => 9); // 出力日 $item["photo"] = array("x" => 149.5, "y" => 17.5, "size" => 0, "width" => 28); // 顔写真 $item["name_kana"] = array("x" => 40.0, "y" => 30.0, "size" => 8); // 氏名ふりがな $item["name"] = array("x" => 40.0, "y" => 37.0, "size" => 16); // 氏名 $item["birth_year"] = array("x" => 35.5, "y" => 52.9, "size" => 9); // 生年月日・年 $item["birth_month"] = array("x" => 57.5, "y" => 52.9, "size" => 9); // 生年月日・月 $item["birth_day"] = array("x" => 74.0, "y" => 52.9, "size" => 9); // 生年月日・日 $item["age"] = array("x" => 98.5, "y" => 52.9, "size" => 9); // 歳 $item["male"] = array("x" => 122.2, "y" => 50.4, "size" => 20); // 性別・男 $item["female"] = array("x" => 131.7, "y" => 50.4, "size" => 20); // 性別・女 $item["address_kana"] = array("x" => 33.5, "y" => 63.3, "size" => 7); // 現住所ふりがな $item["zip"] = array("x" => 38.0, "y" => 69.3, "size" => 10); // 現住所 郵便番号 $item["address"] = array("x" => 33.5, "y" => 76.0, "size" => 10); // 現住所 $item["tel"] = array("x" => 149.0, "y" => 71.0, "size" => 11); // 電話1 $item["another_address_kana"] = array("x" => 33.5, "y" => 83.8, "size" => 7); // 連絡先 ふりがな $item["another_zip"] = array("x" => 38.0, "y" => 89.8, "size" => 10); // 連絡先 郵便番号 $item["another_address"] = array("x" => 33.5, "y" => 96.6, "size" => 10); // 連絡先 $item["another_tel"] = array("x" => 149.0, "y" => 91.5, "size" => 11); // 電話2 // 学歴・職歴 1~16 $line_margin = 9.45; // 行間のマージン $career_1_geo_y = 118.3; // 左ページ学歴・職歴の1行目のY座標の位置 for ($i = 1; $i <= 16; $i++) { $career_i_geo_y = $career_1_geo_y + (($i - 1) * $line_margin); $item["career_{$i}_year"] = array("x" => 20.8, "y" => $career_i_geo_y, "size" => 14); $item["career_{$i}_month"] = array("x" => 43.7, "y" => $career_i_geo_y, "size" => 14); $item["career_{$i}_detail"] = array("x" => 56.5, "y" => $career_i_geo_y, "size" => 14); } // 学歴・職歴 17~22 $creer_17_geo_y = 29.5; // 右ページ学歴・職歴の1行目のY座標の位置 for ($i = 17; $i <= 22; $i++) { $career_i_geo_y = $creer_17_geo_y + (($i - 17) * $line_margin); $item["career_{$i}_year"] = array("x" => 223.5, "y" => $career_i_geo_y, "size" => 14); $item["career_{$i}_month"] = array("x" => 246.9, "y" => $career_i_geo_y, "size" => 14); $item["career_{$i}_detail"] = array("x" => 259.7, "y" => $career_i_geo_y, "size" => 14); } // 学歴・職歴の開始と終了文言のX座標値 $career_title_geo_x_left = 108; $career_title_geo_x_right = 314; $career_close_geo_x_left = 172; $career_close_geo_x_right = 380; // 免許・資格 $line_margin = 9.13; // 行間のマージン $license_1_geo_y = 100.0; // 免許・資格の1行目のY座標の位置 for ($i = 1; $i <= 7; $i++) { $license_i_geo_y = $license_1_geo_y + (($i - 1) * $line_margin); $item["license_{$i}_year"] = array("x" => 223.5, "y" => $license_i_geo_y, "size" => 14); $item["license_{$i}_month"] = array("x" => 246.9, "y" => $license_i_geo_y, "size" => 14); $item["license_{$i}_detail"] = array("x" => 259.7, "y" => $license_i_geo_y, "size" => 14); } $item["commute_hour"] = array("x" => 351.0, "y" => 170.8, "size" => 10); // 通勤時間・時間 $item["commute_minute"] = array("x" => 373.0, "y" => 170.8, "size" => 10); // 通勤時間・分 $item["huyoukazoku"] = array("x" => 366.0, "y" => 189.9, "size" => 10); // 扶養家族 $item["haigusya_yes"] = array("x" => 346.2, "y" => 205.0, "size" => 20); // 配偶者・有 $item["haigusya_no"] = array("x" => 355.8, "y" => 205.0, "size" => 20); // 配偶者・無 $item["haigusyahuyou_yes"] = array("x" => 372.8, "y" => 205.0, "size" => 20); // 配偶者の扶養義務・有 $item["haigusyahuyou_no"] = array("x" => 382.3, "y" => 205.0, "size" => 20); // 配偶者の扶養義務・無 // 志望動機 $item["pr"] = array("x" => 223.0, "y" => 172.0, "size" => 12, "width" => 115, "height" => 12, "max_height" => 40, "line_height" => 1.5); // 本人希望記入欄 $item["comment"] = array("x" => 223.0, "y" => 218.5, "size" => 12, "width" => 170, "height" => 12, "max_height" => 50, "line_height" => 2.3); // // ********************************************************************* // // 出力処理 // // ********************************************************************* // -------------------------------- // 出力年月日 // -------------------------------- $pdf->SetFont($font, '', $item["output_year"]["size"]); $pdf->Text($item["output_year"]["x"], $item["output_year"]["y"], date('Y')); $pdf->Text($item["output_month"]["x"], $item["output_month"]["y"], date('n')); $pdf->Text($item["output_day"]["x"], $item["output_day"]["y"], date('j')); // -------------------------------- // 顔写真 // -------------------------------- $photo_extension = array("jpg", "JPG", "jpeg", "png", "PNG"); if(is_uploaded_file($_FILES['photo']['tmp_name'])) { $file_extension = pathinfo($_FILES['photo']['name'], PATHINFO_EXTENSION); if (in_array($file_extension, $photo_extension)) { $photofile = sprintf("./tmp/%s_%s.%s", time(), uniqid(), $file_extension); if(move_uploaded_file($_FILES['photo']['tmp_name'], $photofile)) { $pdf->Image($photofile, $item["photo"]["x"], $item["photo"]["y"], $item["photo"]["width"]); unlink($photofile); } } } // -------------------------------- // 氏名ふりがな // -------------------------------- $pdf->SetFont($font, '', $item["name_kana"]["size"]); $pdf->Text($item["name_kana"]["x"], $item["name_kana"]["y"], $_REQUEST["name_kana"]); // -------------------------------- // 氏名 // -------------------------------- $pdf->SetFont($font, '', $item["name"]["size"]); $pdf->Text($item["name"]["x"], $item["name"]["y"], $_REQUEST["name"]); // -------------------------------- // 生年月日 // -------------------------------- $pdf->SetFont($font, '', $item["birth_year"]["size"]); $pdf->Text($item["birth_year"]["x"], $item["birth_year"]["y"], $_REQUEST["birth_year"]); $pdf->Text($item["birth_month"]["x"], $item["birth_month"]["y"], $_REQUEST["birth_month"]); $pdf->Text($item["birth_day"]["x"], $item["birth_day"]["y"], $_REQUEST["birth_day"]); $pdf->Text($item["age"]["x"], $item["age"]["y"], $_REQUEST["age"]); // -------------------------------- // 性別 // -------------------------------- $pdf->SetFont($font, '', $item["male"]["size"]); if ($_REQUEST["gender"] == "1") { $pdf->Text($item["male"]["x"], $item["male"]["y"], '○'); } elseif ($_REQUEST["gender"] == "2") { $pdf->Text($item["female"]["x"], $item["female"]["y"], '○'); } // -------------------------------- // 現住所 ふりがな // -------------------------------- $pdf->SetFont($font, '', $item["address_kana"]["size"]); $pdf->Text($item["address_kana"]["x"], $item["address_kana"]["y"], $_REQUEST["address_kana"]); // -------------------------------- // 現住所 郵便番号 // -------------------------------- $pdf->SetFont($font, '', $item["zip"]["size"]); $pdf->Text($item["zip"]["x"], $item["zip"]["y"], $_REQUEST["zip"]); // -------------------------------- // 現住所 // -------------------------------- $pdf->SetFont($font, '', $item["address"]["size"]); $pdf->Text($item["address"]["x"], $item["address"]["y"], $_REQUEST["address"]); // -------------------------------- // 電話1 // -------------------------------- $pdf->SetFont($font, '', $item["tel"]["size"]); $pdf->Text($item["tel"]["x"], $item["tel"]["y"], $_REQUEST["tel"]); // -------------------------------- // 連絡先 ふりがな // -------------------------------- $pdf->SetFont($font, '', $item["another_address_kana"]["size"]); $pdf->Text($item["another_address_kana"]["x"], $item["another_address_kana"]["y"], $_REQUEST["another_address_kana"]); // -------------------------------- // 連絡先 郵便番号 // -------------------------------- $pdf->SetFont($font, '', $item["another_zip"]["size"]); $pdf->Text($item["another_zip"]["x"], $item["another_zip"]["y"], $_REQUEST["another_zip"]); // -------------------------------- // 連絡先 住所 // -------------------------------- $pdf->SetFont($font, '', $item["another_address"]["size"]); $pdf->Text($item["another_address"]["x"], $item["another_address"]["y"], $_REQUEST["another_address"]); // -------------------------------- // 電話2 // -------------------------------- $pdf->SetFont($font, '', $item["another_tel"]["size"]); $pdf->Text($item["another_tel"]["x"], $item["another_tel"]["y"], $_REQUEST["another_tel"]); // -------------------------------- // 学歴・職歴 // -------------------------------- $career = array(); // --- 学歴データを配列に格納 --- $schools = array(); for ($i = 1; $i <= 7; $i++) { if ($_REQUEST["school_{$i}"]) { $schools[] = array( 'type' => "", 'year' => $_REQUEST["school_{$i}_year"], 'month' => $_REQUEST["school_{$i}_month"], 'detail' => $_REQUEST["school_{$i}"], ); } } $career[] = array('type' => "title", 'year' => "", 'month' => "", 'detail' => "学 歴"); if ($schools) { foreach ($schools as $school) { $career[] = $school; } } else { $career[] = array('type' => "", 'year' => "", 'month' => "", 'detail' => "なし"); } // 学歴と職歴の間に空白を1行挟む $career[] = array('type' => "title", 'year' => "", 'month' => "", 'detail' => ""); // --- 職歴データを配列に格納 --- $jobs = array(); for ($i = 1; $i <= 10; $i++) { if ($_REQUEST["job_{$i}"]) { $jobs[] = array( 'type' => "", 'year' => $_REQUEST["job_{$i}_year"], 'month' => $_REQUEST["job_{$i}_month"], 'detail' => $_REQUEST["job_{$i}"], ); } } $career[] = array('type' => "title", 'year' => "", 'month' => "", 'detail' => "職 歴"); if ($jobs) { foreach ($jobs as $job) { $career[] = $job; } $career[] = array('type' => "", 'year' => "", 'month' => "", 'detail' => "現在に至る"); } else { $career[] = array('type' => "", 'year' => "", 'month' => "", 'detail' => "なし"); } $career[] = array('type' => "close", 'year' => "", 'month' => "", 'detail' => "以上"); // --- データ書き出し --- $pdf->SetFont($font, '', $item["career_1_year"]["size"]); foreach ($career as $seq => $content) { $i = $seq + 1; if ($i > 22) { // 22行以上は表示しない break; } else { if ($content["type"] == "title") { if ($i <= 16) { // 16行以下は左ページ $pdf->Text($career_title_geo_x_left, $item["career_{$i}_detail"]["y"], $content["detail"]); } else { // 17行以上は右ページ $pdf->Text($career_title_geo_x_right, $item["career_{$i}_detail"]["y"], $content["detail"]); } } elseif ($content["type"] == "close") { if ($i <= 16) { $pdf->Text($career_close_geo_x_left, $item["career_{$i}_detail"]["y"], $content["detail"]); } else { $pdf->Text($career_close_geo_x_right, $item["career_{$i}_detail"]["y"], $content["detail"]); } } else { $pdf->Text($item["career_{$i}_year"]["x"], $item["career_{$i}_year"]["y"], $content["year"]); if ($content["month"] < 10) { // 月が1桁の場合はX座標に+1.5 $pdf->Text($item["career_{$i}_month"]["x"] + 1.5, $item["career_{$i}_month"]["y"], $content["month"]); } else { $pdf->Text($item["career_{$i}_month"]["x"], $item["career_{$i}_month"]["y"], $content["month"]); } $pdf->Text($item["career_{$i}_detail"]["x"], $item["career_{$i}_detail"]["y"], $content["detail"]); } } } // -------------------------------- // 免許・資格 // -------------------------------- $licenses = array(); // --- 免許・資格データを配列に格納 --- for ($i = 1; $i <= 7; $i++) { if ($_REQUEST["license_{$i}"]) { $licenses[] = array( 'type' => "", 'year' => $_REQUEST["license_{$i}_year"], 'month' => $_REQUEST["license_{$i}_month"], 'detail' => $_REQUEST["license_{$i}"], ); } } // --- データ書き出し --- $pdf->SetFont($font, '', $item["license_1_year"]["size"]); foreach ($licenses as $seq => $content) { $i = $seq + 1; $pdf->Text($item["license_{$i}_year"]["x"], $item["license_{$i}_year"]["y"], $content["year"]); if ($content["month"] < 10) { // 月が1桁の場合はX座標に+1.5 $pdf->Text($item["license_{$i}_month"]["x"] + 1.5, $item["license_{$i}_month"]["y"], $content["month"]); } else { $pdf->Text($item["license_{$i}_month"]["x"], $item["license_{$i}_month"]["y"], $content["month"]); } $pdf->Text($item["license_{$i}_detail"]["x"], $item["license_{$i}_detail"]["y"], $content["detail"]); } // -------------------------------- // 通勤時間 // -------------------------------- $pdf->SetFont($font, '', $item["commute_hour"]["size"]); $pdf->Text($item["commute_hour"]["x"], $item["commute_hour"]["y"], $_REQUEST["commute_hour"]); $pdf->Text($item["commute_minute"]["x"], $item["commute_minute"]["y"], $_REQUEST["commute_minute"]); // -------------------------------- // 扶養家族 // -------------------------------- $pdf->SetFont($font, '', $item["huyoukazoku"]["size"]); $pdf->Text($item["huyoukazoku"]["x"], $item["huyoukazoku"]["y"], $_REQUEST["huyoukazoku"]); // -------------------------------- // 配偶者 // -------------------------------- $pdf->SetFont($font, '', $item["haigusya_yes"]["size"]); if ($_REQUEST["haigusya"] == "1") { $pdf->Text($item["haigusya_yes"]["x"], $item["haigusya_yes"]["y"], '○'); } elseif ($_REQUEST["haigusya"] == "2") { $pdf->Text($item["haigusya_no"]["x"], $item["haigusya_no"]["y"], '○'); } // -------------------------------- // 配偶者の扶養義務 // -------------------------------- $pdf->SetFont($font, '', $item["haigusyahuyou_yes"]["size"]); if ($_REQUEST["haigusyahuyou"] == "1") { $pdf->Text($item["haigusyahuyou_yes"]["x"], $item["haigusyahuyou_yes"]["y"], '○'); } elseif ($_REQUEST["haigusyahuyou"] == "2") { $pdf->Text($item["haigusyahuyou_no"]["x"], $item["haigusyahuyou_no"]["y"], '○'); } // -------------------------------- // 志望動機 // -------------------------------- $text = $_REQUEST["pr"]; $pdf->SetFont($font, '', $item["pr"]["size"]); $pdf->setCellHeightRatio($item["pr"]["line_height"]); // 行の高さ調整 $pdf->MultiCell($item["pr"]["width"], $item["pr"]["height"], $text, 0, '', false, 1, $item["pr"]["x"], $item["pr"]["y"], true, 0, false, true, $item["pr"]["max_height"], 'T', false); // -------------------------------- // 本人希望記入欄 // -------------------------------- $text = $_REQUEST["comment"]; $pdf->SetFont($font, '', $item["comment"]["size"]); $pdf->setCellHeightRatio($item["comment"]["line_height"]); // 行の高さ調整 $pdf->MultiCell($item["comment"]["width"], $item["comment"]["height"], $text, 0, '', false, 1, $item["comment"]["x"], $item["comment"]["y"], true, 0, false, true, $item["comment"]["max_height"], 'T', false); // -------------------------------- // ファイル出力 // -------------------------------- $pdf->Output(sprintf("MyResume_%s.pdf", time()), 'I'); ?> |
解説1)初期設定:10~28行目
ライブラリを呼び出し、 setSourceFile() でPDFテンプレートの読み込みや、用紙サイズ・フォントの設定などを行っています。
フォントはIPAのサイトからIPA明朝をダウンロードして使用しました。プロポーショナルフォントだと文字数の制御が面倒なので、等幅のフォントにしています。
解説2)座標値・文字サイズ定義:37~104行目
ここでは、各項目のXY座標値や文字サイズなどを設定しています。
最初は整数しか設定できないと思い微妙な位置ズレをどうしようかと調べた結果、小数も有効なことが分かってからは思い通りの位置に出力できるようになりました。
ちなみに、座標値を設定するときはIllustratorでPDFのテンプレートファイルを開いて位置を調べ(単位はピクセルではなくミリ)、後は実際に出力しながら微調整していくという作業を行いました。
解説3)出力処理:113~388行目
ここでブラウザから受け取ったデータをPDFに書き出していますが、主に使用しているメソッドは以下の2つです。
SetFont() は文字サイズなどが変更になるタイミングで呼び出せば良いので、もし帳票内ですべて同一のフォントや文字サイズを使用する場合は最初に呼び出すだけで良いです。
従って、基本的には文字列を出力する
Text() を駆使してPDFのテンプレートにデータを書き込んでいるということになります。なお、画像の書き出しには
Image() を、複数行エリアでの出力では
MultiCell() を使用しています。
最初に知っておきたかったこと・・
文字列を出力するメソッドとして Text() の他に Cell() があります。
- $pdf->Cell() ・・・ 矩形領域(セル)を出力する
これは、中央寄せや右寄せなどアラインメントの指定を行う場合には必須のメソッドです。また、セルの名の通り表形式での書き出し時にも非常に便利で、今回 Cell() を使っておけばもっと簡単に書けただろうなという箇所がいくつもあるのですが、 Cell() メソッドの存在を知ったのが Text() で一通り書き終わった後だったので、書き直すのが面倒でそのままにしています・・。 Cell() だと文字がはみ出るのを制御することもできるので、最初にもっと調査するべきでした。。
結論:
①左寄せ、②文字数(桁数)が固定、③表形式ではない単発の出力箇所、の場合は
Text() で問題ないが、基本的には
Cell() を使ったほうが良い。
最後に
以上が、PHP(TCPDF + PDFI)でPDFテンプレートから帳票を出力する方法となります。
ネットで『PHP PDF 帳票』や『PHP PDF テンプレート』などと検索すると簡単なサンプルコードはいくつか出てくるのですが、あまり突っ込んだ内容のものはなかったので、実際にPDFを出力できるサイトを作ってみてそのコードを紹介するという形にしてみました。
実際に作成されるPDFを確認してみたい方はこちらをご覧ください↓
PDF履歴書作成ツール | 登録不要でサクッとブラウザで作る!
https://takahashi-it.com/web-resume/