]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/IO/Pdf.php
cdd16bc9855200dd8b33d5faaab3c565ecbebed8
[galette.git] / galette / lib / Galette / IO / Pdf.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * PDF class for galette
7 * Traps tcpdf errors by overloading tcpdf::error method
8 * Adds convenient method to convert color html codes
9 * Adds a _parsegif function to convert gif to png
10 *
11 * PHP version 5
12 *
13 * Copyright © 2007-2014 The Galette Team
14 *
15 * This file is part of Galette (http://galette.tuxfamily.org).
16 *
17 * Galette is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * Galette is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
29 *
30 * @category IO
31 * @package Galette
32 *
33 * @author John Perr <johnperr@abul.org>
34 * @author Johan Cwiklinski <johan@x-tnd.be>
35 * @copyright 2007-2014 The Galette Team
36 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
37 * @version SVN: $Id$
38 * @link http://galette.tuxfamily.org
39 * @since Available since 0.7dev - 2007-07-21
40 */
41
42 namespace Galette\IO;
43
44 use Galette\Core\Preferences;
45 use Galette\Entity\PdfModel;
46 use Analog\Analog;
47
48 /*
49 * TCPDF configuration file for Galette
50 */
51 require_once GALETTE_CONFIG_PATH . 'galette_tcpdf_config.php';
52
53 /**
54 * PDF class for galette
55 *
56 * @category IO
57 * @name PDF
58 * @package Galette
59 * @abstract Class for expanding TCPDF.
60 * @author John Perr <johnperr@abul.org>
61 * @author Johan Cwiklinski <johan@x-tnd.be>
62 * @copyright 2007-2014 The Galette Team
63 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
64 * @link http://galette.tuxfamily.org
65 * @since Available since 0.7dev - 2007-07-21
66 */
67
68 class Pdf extends \TCPDF
69 {
70
71 const FONT = 'DejaVuSans';
72 const FONT_SIZE = 10;
73
74 protected $preferences;
75 protected $i18n;
76 private $model;
77 private $paginated = false;
78 protected $filename;
79
80 /**
81 * Main constructor, set creator and author
82 *
83 * @param Preferences $prefs Preferences
84 * @param PdfModel $model Related model
85 */
86 public function __construct(Preferences $prefs, $model = null)
87 {
88 global $i18n;
89
90 $this->preferences = $prefs;
91 $this->i18n = $i18n;
92 parent::__construct('P', 'mm', 'A4', true, 'UTF-8');
93 //set some values
94 $this->SetCreator(PDF_CREATOR);
95 //add helvetica, hard-called from lib
96 $this->SetFont('helvetica');
97 //and then, set real font
98 $this->SetFont(self::FONT, '', self::FONT_SIZE);
99 $name = preg_replace(
100 '/%s/',
101 $this->preferences->pref_nom,
102 _T("Association %s")
103 );
104 $this->SetAuthor(
105 $name . ' (using Galette ' . GALETTE_VERSION . ')'
106 );
107
108 if ($this->i18n->isRTL()) {
109 $this->setRTL(true);
110 }
111
112 if ($model !== null) {
113 if ($model instanceof PdfModel) {
114 $this->model = $model;
115 $this->SetTitle($this->model->htitle);
116 } else {
117 throw new \UnexpectedValueException(
118 'Provided model must be an instance of PdfModel!'
119 );
120 }
121 }
122 }
123
124 /**
125 * Set show pagination
126 *
127 * @return void
128 */
129 public function showPagination()
130 {
131 $this->paginated = true;
132 }
133
134 /**
135 * Destructor
136 */
137 public function __destruct()
138 {
139 parent::__destruct();
140 }
141
142 /**
143 * This method is automatically called in case of fatal error;
144 * it simply outputs the message and halts the execution.
145 * An inherited class may override it to customize the error
146 * handling but should always halt the script, or the resulting
147 * document would probably be invalid.
148 * 2004-06-11 :: Nicola Asuni : changed bold tag with strong
149 * 2007-07-21 :: John Perr : changed function to return error to session
150 * 2017-02-14 :: Johan Cwiklinski : use slim's flash message; do not rely on session for redirect
151 *
152 * @param string $msg The error message
153 *
154 * @return void
155 * @access public
156 * @since 1.0
157 */
158 public function Error($msg) // phpcs:ignore PSR1.Methods.CamelCapsMethodName
159 {
160 global $container;
161
162 Analog::log(
163 'PDF error: ' . $msg,
164 Analog::ERROR
165 );
166
167 $container->get('flash')->addMessage(
168 'error_detected',
169 $msg
170 );
171
172 $redirect = (isset($_SERVER['HTTP_REFERER']) ?
173 $_SERVER['HTTP_REFERER'] : $container->get('router')->pathFor('slash'));
174 header('Location: ' . $redirect);
175 die();
176 }
177
178 /**
179 * Converts color from HTML format #RRVVBB
180 * to RGB 3 colors array.
181 *
182 * @param string $hex6 7 chars string #RRVVBB
183 *
184 * @return array
185 */
186 public function colorHex2Dec($hex6)
187 {
188 $dec = array(
189 "R" => hexdec(substr($hex6, 1, 2)),
190 "G" => hexdec(substr($hex6, 3, 2)),
191 "B" => hexdec(substr($hex6, 5, 2))
192 );
193 return $dec;
194 }
195
196 /** FIXME: is this function used somewhere? */
197 /**
198 * Extract info from a GIF file
199 * (In fact: converts gif image to png and feeds it to _parsepng)
200 *
201 * @param string $file path to the gif file
202 *
203 * @return void
204 * @access protected
205 */
206 protected function parsegif($file)
207 {
208 $a = GetImageSize($file);
209 if (empty($a)) {
210 $this->Error(_T("Missing or incorrect image file ") . $file);
211 }
212 if ($a[2] != 1) {
213 $this->Error(_T("Not a GIF file ") . $file);
214 }
215
216 // Tentative d'ouverture du fichier
217 if (function_exists('gd_info')) {
218 $data = @imagecreatefromgif($file);
219
220 // Test d'échec & Affichage d'un message d'erreur
221 if (!$data) {
222 $this->Error(_T("Error loading ") . $file);
223 }
224 if (imagepng($data, GALETTE_ROOT . 'tempimages/gif2png.png')) {
225 return $this->_parsepng(GALETTE_ROOT . 'tempimages/gif2png.png');
226 } else {
227 $this->Error(_T("Error creating temporary png file from ") . $file);
228 }
229 } else {
230 $this->Error(_T("Unable to convert GIF file ") . $file);
231 }
232 }
233
234 /**
235 * Draws PDF page Header
236 *
237 * @return void
238 */
239 public function Header() // phpcs:ignore PSR1.Methods.CamelCapsMethodName
240 {
241 //just ovverride default header to prevent black line at top
242 }
243
244 /**
245 * Draws PDF page footer
246 *
247 * @return void
248 */
249 public function Footer() // phpcs:ignore PSR1.Methods.CamelCapsMethodName
250 {
251 $this->SetY(-20);
252 if (isset($this->model)) {
253 $hfooter = '';
254 if (trim($this->model->hstyles) !== '') {
255 $hfooter .= "<style>\n" . $this->model->hstyles . "\n</style>\n\n";
256 }
257 $hfooter .= $this->model->hfooter;
258 $this->writeHtml($hfooter);
259 } else {
260 $this->SetFont(self::FONT, '', self::FONT_SIZE - 2);
261 $this->SetTextColor(0, 0, 0);
262
263 $name = preg_replace(
264 '/%s/',
265 $this->preferences->pref_nom,
266 _T("Association %s")
267 );
268
269 $address = $this->preferences->getPostalAddress();
270
271 $this->MultiCell(
272 0,
273 4,
274 $address,
275 0,
276 'C'
277 );
278
279 if ($this->paginated) {
280 $this->SetFont(self::FONT, '', self::FONT_SIZE - 3);
281 $this->Ln();
282 $this->Cell(
283 0,
284 4,
285 $this->getAliasRightShift() . $this->PageNo() .
286 '/' . $this->getAliasNbPages(),
287 0,
288 1,
289 ($this->i18n->isRTL() ? 'L' : 'R')
290 );
291 }
292 }
293 }
294
295 /**
296 * Draws PDF page header
297 *
298 * @param string $title Additionnal title to display just after logo
299 *
300 * @return void
301 */
302 public function PageHeader($title = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName
303 {
304 if (isset($this->model)) {
305 $html = null;
306 if (trim($this->model->hstyles) !== '') {
307 $html .= "<style>\n" . $this->model->hstyles . "\n</style>\n\n";
308 }
309 $html .= "<div dir=\"" . ($this->i18n->isRTL() ? 'rtl' : 'ltr') . "\">" . $this->model->hheader . "</div>";
310 $this->writeHtml($html, true, false, true, false, '');
311
312 if ($title !== null) {
313 $this->writeHtml('<h2 style="text-align:center;">' . $title . '</h2>');
314 }
315
316 if (trim($this->model->title) !== '') {
317 $htitle = '';
318 if (trim($this->model->hstyles) !== '') {
319 $htitle .= "<style>\n" . $this->model->hstyles .
320 "\n</style>\n\n";
321 }
322 $htitle .= '<div id="pdf_title">' . $this->model->htitle . '</div>';
323 $this->writeHtml($htitle);
324 }
325 if (trim($this->model->subtitle) !== '') {
326 $hsubtitle = '';
327 if (trim($this->model->hstyles) !== '') {
328 $hsubtitle .= "<style>\n" . $this->model->hstyles .
329 "\n</style>\n\n";
330 }
331 $hsubtitle .= '<div id="pdf_subtitle">' . $this->model->hsubtitle .
332 '</div>';
333 $this->writeHtml($hsubtitle);
334 }
335 if (trim($this->model->title) !== ''
336 || trim($this->model->subtitle) !== ''
337 ) {
338 $this->Ln(5);
339 }
340 } else {
341 //default header
342 $print_logo = new \Galette\Core\PrintLogo();
343 $logofile = $print_logo->getPath();
344
345 // Set logo size to max width 30 mm or max height 25 mm
346 $ratio = $print_logo->getWidth()/$print_logo->getHeight();
347 if ($ratio < 1) {
348 if ($print_logo->getHeight() > 16) {
349 $hlogo = 25;
350 } else {
351 $hlogo = $print_logo->getHeight();
352 }
353 $wlogo = round($hlogo*$ratio);
354 } else {
355 if ($print_logo->getWidth() > 16) {
356 $wlogo = 30;
357 } else {
358 $wlogo = $print_logo->getWidth();
359 }
360 $hlogo = round($wlogo/$ratio);
361 }
362
363 $this->SetFont(self::FONT, 'B', self::FONT_SIZE + 4);
364 $this->SetTextColor(0, 0, 0);
365
366 $y = $this->GetY();
367 $this->Ln(2);
368 $ystart = $this->GetY();
369
370 $this->MultiCell(
371 180 - $wlogo,
372 6,
373 $this->preferences->pref_nom,
374 0,
375 ($this->i18n->isRTL() ? 'R' : 'L')
376 );
377 $this->SetFont(self::FONT, 'B', self::FONT_SIZE + 2);
378
379 if ($title !== null) {
380 $this->Cell(0, 6, $title, 0, 1, ($this->i18n->isRTL() ? 'R' : 'L'), 0);
381 }
382 $yend = $this->getY(); //store position at the end of the text
383
384 $this->SetY($ystart);
385 if ($this->i18n->isRTL()) {
386 $x = $this->getX();
387 } else {
388 $x = 190 - $wlogo; //right align
389 }
390 $this->Image($logofile, $x, $this->GetY(), $wlogo, $hlogo);
391 $this->y += $hlogo + 3;
392 //if position after logo is < than position after text,
393 //we have to change y
394 if ($this->getY() < $yend) {
395 $this->setY($yend);
396 }
397 }
398 }
399
400 /**
401 * Draws body from model
402 *
403 * @return void
404 */
405 public function PageBody() // phpcs:ignore PSR1.Methods.CamelCapsMethodName
406 {
407 $hbody = '';
408 if (trim($this->model->hstyles) !== '') {
409 $hbody .= "<style>\n" . $this->model->hstyles . "\n</style>\n\n";
410 }
411 $hbody .= $this->model->hbody;
412 $this->writeHtml($hbody);
413 }
414
415 /**
416 * Fix text size
417 *
418 * @param string $text Text content
419 * @param integer $maxsize Maximal size
420 * @param integer $fontsize Font size
421 * @param string $fontstyle Font style (defaults to '')
422 * @param string $fontname Font name (defaults to static::FONT)
423 *
424 * @return void
425 */
426 protected function fixSize($text, $maxsize, $fontsize, $fontstyle = '', $fontname = null)
427 {
428 if ($fontname === null) {
429 $fontname = static::FONT;
430 }
431 $this->SetFontSize($fontsize);
432 while ($this->GetStringWidth($text, $fontname, $fontstyle, $fontsize) > $maxsize) {
433 $fontsize--;
434 $this->SetFontSize($fontsize);
435 }
436 }
437
438 /**
439 * Cut a string
440 *
441 * @param string $str Original string
442 * @param integer $length Max length
443 *
444 * @return string
445 */
446 protected function cut($str, $length)
447 {
448 $length = $length - 2; //keep a margin
449 if ($this->GetStringWidth($str) > $length) {
450 while ($this->GetStringWidth($str . '...') > $length) {
451 $str = mb_substr($str, 0, -1, 'UTF-8');
452 }
453 $str .= '...';
454 }
455 return $str;
456 }
457
458 /**
459 * Stretch a header string
460 *
461 * @param string $str Original string
462 * @param integer $length Max length
463 *
464 * @return string
465 */
466 protected function stretchHead($str, $length)
467 {
468 $this->SetFont(self::FONT, 'B', self::FONT_SIZE);
469 $stretch = 100;
470 if ($this->GetStringWidth($str) > $length) {
471 while ($this->GetStringWidth($str) > $length) {
472 $this->setFontStretching(--$stretch);
473 }
474 }
475 return $str;
476 }
477
478 /**
479 * Get filename
480 *
481 * @return string
482 */
483 public function getFilename()
484 {
485 return $this->filename;
486 }
487
488 /**
489 * Download PDF from browser
490 *
491 * @return void
492 */
493 public function download()
494 {
495 $this->Output($this->filename, 'D');
496 }
497 }