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