Passed
Push — main ( 654b08...ddfb19 )
by Stefan
01:51
created

FPDF::Cell()   F

Complexity

Conditions 28
Paths > 20000

Size

Total Lines 78
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 28
eloc 59
nc 395760
nop 8
dl 0
loc 78
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
namespace OPlathey\FPDF;
3
4
/**
5
 * Modified version of O.Platheys FPDF.php
6
 *
7
 * Based on version 1.82 of FPDF.php, extended by 
8
 * - namespace to include through autoloader
9
 * - PHP 7.4 typehints
10
 * - phpDoc comments  
11
 *
12
 * @package OPlathey/FPDF
13
 * @version 1.82
14
 * @author O.Plathey
15
 * @copyright MIT License - see the LICENSE file for details
16
 */
17
18
/*******************************************************************************
19
* FPDF                                                                         *
20
*                                                                              *
21
* Version: 1.82                                                                *
22
* Date:    2019-12-07                                                          *
23
* Author:  Olivier PLATHEY                                                     *
24
* http://www.fpdf.org/en/doc/index.php
25
*******************************************************************************/
26
27
define('FPDF_VERSION','1.82');
28
29
class FPDF
30
{
31
    /** @var int current page number     */
32
    protected int $page = 0;
33
    /** @var int current object number     */
34
    protected int $n = 2;
35
    /** @var array array of object offsets     */
36
    protected array $offsets; 
37
    /** @var string buffer holding in-memory PDF     */
38
    protected string $buffer = ''; 
39
    /** @var array array containing pages     */
40
    protected array $pages = array(); 
41
    /** @var int current document state     */
42
    protected int $state = 0; 
43
    /** @var bool compression flag     */
44
    protected bool $compress;
45
    /** @var float scale factor (number of points in user unit)     */
46
    protected float $k;
47
    /** @var string default orientation     */
48
    protected string $DefOrientation;
49
    /** @var string current orientation     */
50
    protected string $CurOrientation;
51
    /** @var array default page size     */
52
    protected array $DefPageSize;
53
    /** @var array current page size     */
54
    protected array $CurPageSize;
55
    /** @var int current page rotation     */
56
    protected int $CurRotation;
57
    /** @var array page-related data     */
58
    protected array $PageInfo = array();
59
    /** @var float width of current page in points     */
60
    protected float $wPt;
61
    /** @var float height of current page in points     */
62
    protected float $hPt;
63
    /** @var float width of current page in user unit     */
64
    protected float $w;
65
    /** @var float height of current page in user unit     */
66
    protected float $h;
67
    /** @var float left margin     */
68
    protected float $lMargin;
69
    /** @var float top margin     */
70
    protected float $tMargin;
71
    /** @var float right margin     */
72
    protected float $rMargin;
73
    /** @var float page break margin     */
74
    protected float $bMargin;
75
    /** @var float cell margin     */
76
    protected float $cMargin;
77
    /** @var float current X-position in user unit     */
78
    protected float $x; 
79
    /** @var float current Y-position in user unit     */
80
    protected float $y;
81
    /** @var float height of last printed cell     */
82
    protected float $lasth = 0.0;
83
    /** @var float line width in user unit     */
84
    protected float $LineWidth;
85
    /** @var string path containing fonts     */
86
    protected string $fontpath;
87
    /** @var array array of core font names     */
88
    protected array $CoreFonts;
89
    /** @var array array of used fonts     */
90
    protected array $fonts = array();
91
    /** @var array array of font files     */
92
    protected array $FontFiles = array();
93
    /** @var array array of encodings     */
94
    protected array $encodings = array();
95
    /** @var array array of ToUnicode CMaps     */
96
    protected array $cmaps = array();
97
    /** @var string current font family     */
98
    protected string $FontFamily = '';
99
    /** @var string current font style     */
100
    protected string $FontStyle = '';
101
    /** @var bool underlining flag     */
102
    protected bool $underline = false;
103
    /** @var array current font info     */
104
    protected array $CurrentFont;
105
    /** @var float current font size in points     */
106
    protected float $FontSizePt = 12.0;
107
    /** @var float current font size in user unit     */
108
    protected float $FontSize;
109
    /** @var string commands for drawing color     */
110
    protected string $DrawColor = '0 G';
111
    /** @var string commands for filling color     */
112
    protected string $FillColor = '0 g';
113
    /** @var string commands for text color     */
114
    protected string $TextColor = '0 g';
115
    /** @var bool indicates whether fill and text colors are different     */
116
    protected bool $ColorFlag = false;
117
    /** @var bool indicates whether alpha channel is used     */
118
    protected bool $WithAlpha = false;
119
    /** @var float word spacing     */
120
    protected float $ws = 0.0;
121
    /** @var array array of used images     */
122
    protected array $images = array();
123
    /** @var array array of links in pages     */
124
    protected array $PageLinks;
125
    /** @var array array of internal links     */
126
    protected array $links = array();
127
    /** @var bool automatic page breaking     */
128
    protected bool $AutoPageBreak;
129
    /** @var float threshold used to trigger page breaks     */
130
    protected float $PageBreakTrigger;
131
    /** @var bool flag set when processing header     */
132
    protected bool $InHeader = false;
133
    /** @var bool flag set when processing footer     */
134
    protected bool $InFooter = false;
135
    /** @var string alias for total number of pages     */
136
    protected string $AliasNbPages;
137
    /** @var string|float zoom display mode     */
138
    protected $ZoomMode;
139
    /** @var string layout display mode     */
140
    protected string $LayoutMode;
141
    /** @var array document properties     */
142
    protected array $metadata;
143
    /** @var string PDF version number     */
144
    protected string $PDFVersion;
145
146
    /**
147
     * This is the class constructor. 
148
     * It allows to set up the page size, the orientation and the unit of measure used 
149
     * in all methods (except for font sizes).
150
     * @param string $orientation   Default page orientation. <br/>
151
     *                              Possible values are (case insensitive): <ul> 
152
     *                              <li>   'P' or 'Portrait' </li>
153
     *                              <li>   'L' or 'Landscape' </li></ul> 
154
     *                              Default value is 'P'. <br/>
155
     * @param string $unit          User unit.  <br/>
156
     *                              Possible values are: <ul>
157
     *                              <li>   'pt': point,  </li> 
158
     *                              <li>   'mm': millimeter,  </li>
159
     *                              <li>   'cm': centimeter,  </li>
160
     *                              <li>   'in': inch </li></ul>
161
     *                              A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). <br/>
162
     *                              This is a very common unit in typography; font sizes are expressed in that unit. <br/>
163
     *                              Default value is 'mm'. <br/>
164
     * @param string|array $size    The size used for pages. <br/>
165
     *                              It can be either one of the following values (case insensitive): <ul>
166
     *                              <li> 'A3' </li>
167
     *                              <li> 'A4' </li>
168
     *                              <li> 'A5' </li>
169
     *                              <li> 'Letter' </li>
170
     *                              <li> 'Legal' </li></ul>
171
     *                              or an array containing the width and the height (expressed in the unit given by unit). <br/>
172
     *                              Default value is 'A4'.
173
     */
174
    public function __construct(string $orientation='P', string $unit='mm', $size='A4')
175
    {
176
    	// Some checks
177
    	$this->doChecks();
178
179
    	$this->getFontPath();
180
    	
181
    	// Core fonts
182
    	$this->CoreFonts = array('courier', 'helvetica', 'times', 'symbol', 'zapfdingbats');
183
    	
184
    	// Scale factor
185
    	switch ($unit) {
186
    	    case 'pt':
187
        		$this->k = 1;
188
        		break;
189
    	    case 'mm':
190
    	        $this->k = 72/25.4;
191
    	        break;
192
    	    case 'cm':
193
    	        $this->k = 72/2.54;
194
    	        break;
195
    	    case 'in':
196
    	        $this->k = 72;
197
    	        break;
198
    	    default:
199
        		$this->Error('Incorrect unit: ' . $unit);
200
        		break;
201
    	}
202
    	
203
    	// Page sizes
204
    	$size = $this->getPageSize($size);
205
    	$this->DefPageSize = $size;
206
    	$this->CurPageSize = $size;
207
    	
208
    	// Page orientation
209
    	$orientation = strtolower($orientation);
210
    	if ($orientation == 'p' || $orientation == 'portrait') {
211
    		$this->DefOrientation = 'P';
212
    		$this->w = $size[0];
213
    		$this->h = $size[1];
214
    	} elseif ($orientation == 'l' || $orientation == 'landscape') {
215
    		$this->DefOrientation = 'L';
216
    		$this->w = $size[1];
217
    		$this->h = $size[0];
218
    	} else {
219
    		$this->Error('Incorrect orientation: ' . $orientation);
220
    	}
221
    	$this->CurOrientation = $this->DefOrientation;
222
    	$this->wPt = $this->w * $this->k;
223
    	$this->hPt = $this->h * $this->k;
224
    	
225
    	// set some default values 
226
    	// - no page rotation
227
    	// - page margins 1cm
228
    	// - interior cell margin 1mm
229
    	// - line width 0.2mm
230
    	// - automatic page break
231
    	// - default display mode
232
    	// - enable compression
233
    	// - PDF version 1.3
234
    	$this->CurRotation = 0;
235
    	
236
    	$margin = 28.35 / $this->k;
237
    	$this->setMargins($margin, $margin);
238
    	$this->cMargin = $margin / 10;
239
    	$this->LineWidth = 0.567 / $this->k;
240
    	$this->setAutoPageBreak(true, 2 * $margin);
241
    	$this->setDisplayMode('default');
242
    	$this->setCompression(true);
243
    	$this->PDFVersion = '1.3';
244
    }
245
246
    /**
247
     * Defines the left, top and right margins. 
248
     * By default, they equal 1 cm. Call this method to change them. 
249
     * @param float $left   Left margin.
250
     * @param float $top    Top margin.
251
     * @param float $right  Right margin. Default value is the left one.
252
     */
253
    public function SetMargins(float $left, float $top, ?float $right=null) : void
254
    {
255
    	// Set left, top and right margins
256
    	$this->lMargin = $left;
257
    	$this->tMargin = $top;
258
    	if($right===null)
259
    		$right = $left;
260
    	$this->rMargin = $right;
261
    }
262
263
    /**
264
     * Defines the left margin. 
265
     * The method can be called before creating the first page.
266
     * If the current X-position gets out of page, it is brought back to the margin. 
267
     * @param float $margin Left margin.
268
     */
269
    public function SetLeftMargin(float $margin) : void
270
    {
271
    	// Set left margin
272
    	$this->lMargin = $margin;
273
    	if($this->page>0 && $this->x<$margin)
274
    		$this->x = $margin;
275
    }
276
277
    /**
278
     * Defines the top margin. 
279
     * The method can be called before creating the first page.  
280
     * @param float $margin
281
     */
282
    public function SetTopMargin(float $margin) : void
283
    {
284
    	// Set top margin
285
    	$this->tMargin = $margin;
286
    }
287
288
    /**
289
     * Defines the right margin. 
290
     * The method can be called before creating the first page. 
291
     * @param float $margin
292
     */
293
    public function SetRightMargin(float $margin) : void
294
    {
295
    	// Set right margin
296
    	$this->rMargin = $margin;
297
    }
298
299
    /**
300
     * Enables or disables the automatic page breaking mode. 
301
     * When enabling, the second parameter is the distance from the bottom of the page 
302
     * that defines the triggering limit.
303
     * By default, the mode is on and the margin is 2 cm. 
304
     * @param bool $auto    indicating if mode should be on or off. 
305
     * @param float $margin Distance from the bottom of the page. 
306
     */
307
    public function SetAutoPageBreak(bool $auto, float $margin=0) : void
308
    {
309
    	// Set auto page break mode and triggering margin
310
    	$this->AutoPageBreak = $auto;
311
    	$this->bMargin = $margin;
312
    	$this->PageBreakTrigger = $this->h-$margin;
313
    }
314
315
    /**
316
     * Defines the way the document is to be displayed by the viewer. 
317
     * The zoom level can be set: <br/> 
318
     * pages can be displayed <ul>
319
     * <li> entirely on screen </li>
320
     * <li> occupy the full width of the window </li>
321
     * <li> use real size </li>
322
     * <li> be scaled by a specific zooming factor </li>
323
     * <li> or use viewer default (configured in the Preferences menu of Adobe Reader). </li></ul> 
324
     * The page layout can be specified too: <ul> 
325
     * <li> single at once </li>
326
     * <li> continuous display </li>
327
     * <li> two columns </li>
328
     * <li> or viewer default. </li></ul> 
329
     * @param string|float $zoom    The zoom to use. <br/>
330
     *                              It can be one of the following string values: <ul>
331
     *                              <li> 'fullpage': displays the entire page on screen </li>
332
     *                              <li> 'fullwidth': uses maximum width of window </li>
333
     *                              <li> 'real': uses real size (equivalent to 100% zoom) </li>
334
     *                              <li> 'default': uses viewer default mode </li>
335
     *                              <li> or a number indicating the zooming factor to use. </li></ul> 
336
     * @param string $layout        The page layout. Possible values are: <ul>
337
     *                              <li> 'single': displays one page at once </li>
338
     *                              <li> 'continuous': displays pages continuously </li>
339
     *                              <li> 'two': displays two pages on two columns </li>
340
     *                              <li> 'defaul't: uses viewer default mode </li></ul>
341
     *                              Default value is default. 
342
     */
343
    public function SetDisplayMode($zoom, string $layout='default') : void
344
    {
345
    	// Set display mode in viewer
346
    	if($zoom=='fullpage' || $zoom=='fullwidth' || $zoom=='real' || $zoom=='default' || !is_string($zoom))
347
    		$this->ZoomMode = $zoom;
348
    	else
349
    		$this->Error('Incorrect zoom display mode: '.$zoom);
350
    	if($layout=='single' || $layout=='continuous' || $layout=='two' || $layout=='default')
351
    		$this->LayoutMode = $layout;
352
    	else
353
    		$this->Error('Incorrect layout display mode: '.$layout);
354
    }
355
356
    /**
357
     * Activates or deactivates page compression. 
358
     * When activated, the internal representation of each page is compressed, which leads to 
359
     * a compression ratio of about 2 for the resulting document.
360
     * Compression is on by default. <br/>
361
     * <br/>
362
     * <b>Note: the Zlib extension is required for this feature. If not present, compression will be turned off.</b> 
363
     * @param bool $compress
364
     */
365
    public function SetCompression(bool $compress) : void
366
    {
367
    	// Set page compression
368
    	if(function_exists('gzcompress'))
369
    		$this->compress = $compress;
370
    	else
371
    		$this->compress = false;
372
    }
373
374
    /**
375
     * Defines the title of the document. 
376
     * @param string $title The title.
377
     * @param bool $isUTF8  Indicates if the string is encoded in ISO-8859-1 (false) or UTF-8 (true). Default value: false. 
378
     */
379
    public function SetTitle(string $title, bool $isUTF8=false) : void
380
    {
381
    	// Title of document
382
    	$this->metadata['Title'] = $isUTF8 ? $title : utf8_encode($title);
383
    }
384
385
    /**
386
     * Defines the author of the document. 
387
     * @param string $author
388
     * @param bool $isUTF8  Indicates if the string is encoded in ISO-8859-1 (false) or UTF-8 (true). Default value: false.
389
     */
390
    public function SetAuthor(string $author, bool $isUTF8=false) : void
391
    {
392
    	// Author of document
393
    	$this->metadata['Author'] = $isUTF8 ? $author : utf8_encode($author);
394
    }
395
396
    /**
397
     * Defines the subject of the document. 
398
     * @param string $subject
399
     * @param bool $isUTF8  Indicates if the string is encoded in ISO-8859-1 (false) or UTF-8 (true). Default value: false.
400
     */
401
    public function SetSubject(string $subject, bool $isUTF8=false) : void
402
    {
403
    	// Subject of document
404
    	$this->metadata['Subject'] = $isUTF8 ? $subject : utf8_encode($subject);
405
    }
406
    
407
    /**
408
     * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'. 
409
     * @param string $keywords
410
     * @param bool $isUTF8  Indicates if the string is encoded in ISO-8859-1 (false) or UTF-8 (true). Default value: false.
411
     */
412
    public function SetKeywords(string $keywords, bool $isUTF8=false) : void
413
    {
414
    	// Keywords of document
415
    	$this->metadata['Keywords'] = $isUTF8 ? $keywords : utf8_encode($keywords);
416
    }
417
    
418
    /**
419
     * Defines the creator of the document. This is typically the name of the application that generates the PDF. 
420
     * @param string $creator
421
     * @param bool $isUTF8  Indicates if the string is encoded in ISO-8859-1 (false) or UTF-8 (true). Default value: false.
422
     */
423
    public function SetCreator(string $creator, bool $isUTF8=false) : void
424
    {
425
    	// Creator of document
426
    	$this->metadata['Creator'] = $isUTF8 ? $creator : utf8_encode($creator);
427
    }
428
429
    /**
430
     * Defines an alias for the total number of pages. It will be substituted as the document is closed. 
431
     * @param string $alias The alias. Default value: {nb}. 
432
     */
433
    public function AliasNbPages(string $alias='{nb}') : void
434
    {
435
    	// Define an alias for total number of pages
436
    	$this->AliasNbPages = $alias;
437
    }
438
    
439
    /**
440
     * This method is automatically called in case of a fatal error.
441
     * It simply throws an exception with the provided message.
442
     * An inherited class may override it to customize the error handling but 
443
     * the method should never return, otherwise the resulting document would probably be invalid. 
444
     * @param string $msg The error message.
445
     * @throws \Exception
446
     */
447
    public function Error(string $msg) : void
448
    {
449
    	// Fatal error
450
    	throw new \Exception('FPDF error: '.$msg);
451
    }
452
    
453
    /**
454
     * Terminates the PDF document. 
455
     * It is not necessary to call this method explicitly because Output() does it 
456
     * automatically. If the document contains no page, AddPage() is called to prevent 
457
     * from getting an invalid document.
458
     */
459
    public function Close() : void
460
    {
461
    	// Terminate document
462
    	if($this->state==3)
463
    		return;
464
    	if($this->page==0)
465
    		$this->addPage();
466
    	// Page footer
467
    	$this->InFooter = true;
468
    	$this->Footer();
469
    	$this->InFooter = false;
470
    	// Close page
471
    	$this->_endpage();
472
    	// Close document
473
    	$this->_enddoc();
474
    }
475
    
476
    /**
477
     * Adds a new page to the document. 
478
     * If a page is already present, the Footer() method is called first to output the 
479
     * footer. Then the page is added, the current position set to the top-left corner 
480
     * according to the left and top margins, and Header() is called to display the header.
481
     * The font which was set before calling is automatically restored. There is no need 
482
     * to call SetFont() again if you want to continue with the same font. The same is 
483
     * true for colors and line width.
484
     * The origin of the Y-position system is at the top-left corner and increasing 
485
     * Y-positions go downwards.
486
     * @param string $orientation   Default page orientation. <br/>
487
     *                              Possible values are (case insensitive): <ul> 
488
     *                              <li> 'P' or 'Portrait' </li>
489
     *                              <li> 'L' or 'Landscape' </li></ul>
490
     *                              Default value is 'P'. <br/> 
491
     * @param string|array $size    The size used for pages. <br/>
492
     *                              It can be either one of the following values (case insensitive): <ul>
493
     *                              <li> 'A3' </li>
494
     *                              <li> 'A4' </li>
495
     *                              <li> 'A5' </li>
496
     *                              <li> 'Letter' </li>
497
     *                              <li> 'Legal' </li></ul>
498
     *                              or an array containing the width and the height (expressed in the unit given by unit). <br/>
499
     *                              Default value is 'A4'.  <br/>
500
     * @param int $rotation         Angle by which to rotate the page. <br/>
501
     *                              It must be a multiple of 90; positive values mean clockwise rotation. </br>
502
     *                              The default value is 0.
503
     */
504
    public function AddPage(string $orientation='', $size='', int $rotation=0) : void
505
    {
506
    	// Start a new page
507
        if ($this->state == 3) {
508
    		$this->Error('The document is closed');
509
        }
510
    	$family = $this->FontFamily;
511
    	$style = $this->FontStyle . ($this->underline ? 'U' : '');
512
    	$fontsize = $this->FontSizePt;
513
    	$lw = $this->LineWidth;
514
    	$dc = $this->DrawColor;
515
    	$fc = $this->FillColor;
516
    	$tc = $this->TextColor;
517
    	$cf = $this->ColorFlag;
518
    	if ($this->page > 0) {
519
    		// Page footer
520
    		$this->InFooter = true;
521
    		$this->Footer();
522
    		$this->InFooter = false;
523
    		// Close page
524
    		$this->_endpage();
525
    	}
526
    	// Start new page
527
    	$this->_beginpage($orientation, $size, $rotation);
528
    	// Set line cap style to square
529
    	$this->_out('2 J');
530
    	// Set line width
531
    	$this->LineWidth = $lw;
532
    	$this->_out(sprintf('%.2F w', $lw * $this->k));
533
    	// Set font
534
    	if ($family) {
535
    		$this->setFont($family, $style, $fontsize);
536
    	}
537
    	// Set colors
538
    	$this->DrawColor = $dc;
539
    	if ($dc != '0 G') {
540
    		$this->_out($dc);
541
    	}
542
    	$this->FillColor = $fc;
543
    	if ($fc != '0 g') {
544
    		$this->_out($fc);
545
    	}
546
    	$this->TextColor = $tc;
547
    	$this->ColorFlag = $cf;
548
    	// Page header
549
    	$this->InHeader = true;
550
    	$this->Header();
551
    	$this->InHeader = false;
552
    	// Restore line width
553
    	if ($this->LineWidth != $lw) {
554
    		$this->LineWidth = $lw;
555
    		$this->_out(sprintf('%.2F w', $lw * $this->k));
556
    	}
557
    	// Restore font
558
    	if ($family) {
559
    		$this->setFont($family, $style, $fontsize);
560
    	}
561
    	// Restore colors
562
    	if ($this->DrawColor != $dc) {
563
    		$this->DrawColor = $dc;
564
    		$this->_out($dc);
565
    	}
566
    	if ($this->FillColor != $fc) {
567
    		$this->FillColor = $fc;
568
    		$this->_out($fc);
569
    	}
570
    	$this->TextColor = $tc;
571
    	$this->ColorFlag = $cf;
572
    }
573
    
574
    /**
575
     * This method is used to render the page header. 
576
     * It is automatically called by AddPage() and should not be called directly by the 
577
     * application. The implementation in FPDF is empty, so you have to subclass it and 
578
     * override the method if you want a specific processing.
579
     */
580
    public function Header() : void
581
    {
582
    	// To be implemented in your own inherited class
583
    }
584
    
585
    /**
586
     * This method is used to render the page footer. 
587
     * It is automatically called by AddPage() and Close() and should not be called 
588
     * directly by the application. The implementation in FPDF is empty, so you have to 
589
     * subclass it and override the method if you want a specific processing.
590
     */
591
    public function Footer() : void
592
    {
593
    	// To be implemented in your own inherited class
594
    }
595
    
596
    /**
597
     * Returns the current page number.
598
     * @return int
599
     */
600
    public function PageNo() : int
601
    {
602
    	// Get current page number
603
    	return $this->page;
604
    }
605
    
606
    /**
607
     * Defines the color used for all drawing operations (lines, rectangles and cell borders). 
608
     * It can be expressed in RGB components or gray scale. The method can be called before 
609
     * the first page is created and the value is retained from page to page.
610
     * @param int $r    If g and b are given, red component; if not, indicates the gray level. Value between 0 and 255.
611
     * @param int $g    Green component (between 0 and 255).
612
     * @param int $b    Blue component (between 0 and 255).
613
     */
614
    public function SetDrawColor(int $r, ?int $g=null, ?int $b=null) : void
615
    {
616
    	// Set color for all stroking operations
617
        if (($r === 0 && $g === 0 && $b === 0) || $g === null) {
618
    		$this->DrawColor = sprintf('%.3F G', $r / 255);
619
        } else {
620
    		$this->DrawColor = sprintf('%.3F %.3F %.3F RG', $r / 255, $g / 255, $b / 255);
621
        }
622
        if ($this->page > 0) {
623
    		$this->_out($this->DrawColor);
624
        }
625
    }
626
    
627
    /**
628
     * Defines the color used for all filling operations (filled rectangles and cell backgrounds). 
629
     * It can be expressed in RGB components or gray scale. The method can be called before the 
630
     * first page is created and the value is retained from page to page.
631
     * @param int $r    If g and b are given, red component; if not, indicates the gray level. Value between 0 and 255.
632
     * @param int $g    Green component (between 0 and 255).
633
     * @param int $b    Blue component (between 0 and 255).
634
     */
635
    public function SetFillColor(int $r, ?int $g=null, ?int $b=null) : void
636
    {
637
    	// Set color for all filling operations
638
        if (($r === 0 && $g === 0 && $b === 0) || $g === null) {
639
    		$this->FillColor = sprintf('%.3F g', $r / 255);
640
        } else {
641
    		$this->FillColor = sprintf('%.3F %.3F %.3F rg', $r / 255, $g / 255, $b / 255);
642
        }
643
    	$this->ColorFlag = ($this->FillColor != $this->TextColor);
644
    	if ($this->page > 0) {
645
    		$this->_out($this->FillColor);
646
    	}
647
    }
648
    
649
    /**
650
     * Defines the color used for text. 
651
     * It can be expressed in RGB components or gray scale. The method can be called before the 
652
     * first page is created and the value is retained from page to page.
653
     * @param int $r    If g and b are given, red component; if not, indicates the gray level. Value between 0 and 255.
654
     * @param int $g    Green component (between 0 and 255).
655
     * @param int $b    Blue component (between 0 and 255).
656
     */
657
    public function SetTextColor(int $r, ?int $g=null, ?int $b=null) : void
658
    {
659
    	// Set color for text
660
        if (($r === 0 && $g === 0 && $b === 0) || $g === null) {
661
    		$this->TextColor = sprintf('%.3F g', $r / 255);
662
        } else {
663
    		$this->TextColor = sprintf('%.3F %.3F %.3F rg', $r / 255, $g / 255, $b / 255);
664
        }
665
    	$this->ColorFlag = ($this->FillColor != $this->TextColor);
666
    }
667
    
668
    /**
669
     * Returns the length of a string in user unit for current Font. 
670
     * A font must be selected.
671
     * @param string $s The string whose length is to be computed.
672
     * @return float
673
     */
674
    public function GetStringWidth(string $s) : float
675
    {
676
    	// Get width of a string in the current font
677
    	$s = (string)$s;
678
    	$cw = &$this->CurrentFont['cw'];
679
    	$w = 0;
680
    	$l = strlen($s);
681
    	for($i=0;$i<$l;$i++)
682
    		$w += $cw[$s[$i]];
683
    	return $w*$this->FontSize/1000;
684
    }
685
    
686
    /**
687
     * Defines the line width. 
688
     * By default, the value equals 0.2 mm. The method can be called before the first 
689
     * page is created and the value is retained from page to page.
690
     * @param float $width
691
     */
692
    public function SetLineWidth(float $width) : void
693
    {
694
    	// Set line width
695
    	$this->LineWidth = $width;
696
    	if($this->page>0)
697
    		$this->_out(sprintf('%.2F w',$width*$this->k));
698
    }
699
    
700
    /**
701
     * Draws a line between two points.
702
     * The X/Y-positions refer to the top left corner of the page. 
703
     * Set margins are NOT taken into account.
704
     * @param float $x1     X-position upper left corner
705
     * @param float $y1     Y-position upper left corner
706
     * @param float $x2     X-position lower right corner
707
     * @param float $y2     Y-position lower right corner
708
     */
709
    public function Line(float $x1, float $y1, float $x2, float $y2) : void
710
    {
711
    	// Draw a line
712
    	$this->_out(sprintf('%.2F %.2F m %.2F %.2F l S',$x1*$this->k,($this->h-$y1)*$this->k,$x2*$this->k,($this->h-$y2)*$this->k));
713
    }
714
715
    /**
716
     * Outputs a rectangle. 
717
     * It can be drawn (border only), filled (with no border) or both.
718
     * The X/Y-position refer to the top left corner of the page. 
719
     * Set margins are NOT taken into account.
720
     * @param float $x      X-position upper left corner
721
     * @param float $y      Y-position upper left corner
722
     * @param float $w      Width
723
     * @param float $h      Height
724
     * @param string $style Style of rendering. <br/>
725
     *                      Possible values are: <ul>
726
     *                      <li>   'D' or empty string: draw the shape. This is the default value. </li>
727
     *                      <li>   'F': fill. </li>
728
     *                      <li>   'DF' or 'FD': draw the shape and fill. </li></ul>
729
     */
730
    public function Rect(float $x, float $y, float $w, float $h, string $style='') : void
731
    {
732
    	// Draw a rectangle
733
    	if($style=='F')
734
    		$op = 'f';
735
    	elseif($style=='FD' || $style=='DF')
736
    		$op = 'B';
737
    	else
738
    		$op = 'S';
739
    	$this->_out(sprintf('%.2F %.2F %.2F %.2F re %s',$x*$this->k,($this->h-$y)*$this->k,$w*$this->k,-$h*$this->k,$op));
740
    }
741
    
742
    /**
743
     * Imports a TrueType, OpenType or Type1 font and makes it available. 
744
     * It is necessary to generate a font definition file first with the MakeFont utility.
745
     * The definition file (and the font file itself when embedding) must be present in 
746
     * the font directory. If it is not found, the error "Could not include font definition file" 
747
     * is raised.
748
     * @param string $family    Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
749
     * @param string $style     Font style. <br/>
750
     *                          Possible values are (case insensitive): <ul>
751
     *                          <li> empty string: regular </li>
752
     *                          <li> 'B': bold </li>
753
     *                          <li> 'I': italic </li>
754
     *                          <li> 'BI' or 'IB': bold italic </li></ul>
755
     *                          The default value is regular. <br/>
756
     * @param string $file      The font definition file. <br/>
757
     *                          By default, the name is built from the family and style, in lower case with no space.
758
     */
759
    public function AddFont(string $family, string $style='', string $file='') : void
760
    {
761
    	// Add a TrueType, OpenType or Type1 font
762
    	$family = strtolower($family);
763
    	if($file=='')
764
    		$file = str_replace(' ','',$family).strtolower($style).'.php';
765
    	$style = strtoupper($style);
766
    	if($style=='IB')
767
    		$style = 'BI';
768
    	$fontkey = $family.$style;
769
    	if(isset($this->fonts[$fontkey]))
770
    		return;
771
    	$info = $this->_loadfont($file);
772
    	$info['i'] = count($this->fonts)+1;
773
    	if(!empty($info['file']))
774
    	{
775
    		// Embedded font
776
    		if($info['type']=='TrueType')
777
    			$this->FontFiles[$info['file']] = array('length1'=>$info['originalsize']);
778
    		else
779
    			$this->FontFiles[$info['file']] = array('length1'=>$info['size1'], 'length2'=>$info['size2']);
780
    	}
781
    	$this->fonts[$fontkey] = $info;
782
    }
783
    
784
    /**
785
     * Sets the font used to print character strings. 
786
     * It is mandatory to call this method at least once before printing text or the 
787
     * resulting document would not be valid.
788
     * The font can be either a standard one or a font added via the AddFont() method. 
789
     * Standard fonts use the Windows encoding cp1252 (Western Europe).
790
     * The method can be called before the first page is created and the font is kept from page to page.
791
     * If you just wish to change the current font size, it is simpler to call SetFontSize().<br/>
792
     * 
793
     * <b>Note:</b><br/>
794
     * the font definition files must be accessible. 
795
     * They are searched successively in: <ul>
796
     * <li> The directory defined by the FPDF_FONTPATH constant (if this constant is defined) </li>
797
     * <li> The 'font' directory located in the same directory as fpdf.php (if it exists) </li>
798
     * <li> The directories accessible through include() </li></ul>
799
     * @param string $family    Family font. <br/>
800
     *                          It can be either a name defined by AddFont() or one of the standard families (case insensitive): <ul>
801
     *                          <li> 'Courier' (fixed-width) </li>
802
     *                          <li> 'Helvetica' or 'Arial' (synonymous; sans serif) </li>
803
     *                          <li> 'Times' (serif) </li>
804
     *                          <li> 'Symbol' (symbolic) </li>
805
     *                          <li> 'ZapfDingbats' (symbolic)</li></ul>
806
     *                          It is also possible to pass an empty string. In that case, the current family is kept.<br/>
807
     * @param string $style     Font style. <br>
808
     *                          ossible values are (case insensitive): <ul>
809
     *                          <li> empty string: regular </li>
810
     *                          <li> 'B': bold </li>
811
     *                          <li> 'I': italic </li>
812
     *                          <li> 'U': underline </li> 
813
     *                          <li> or any combination. </li></ul>
814
     *                          The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats.<br/>
815
     * @param float $size       Font size in points. <br/>
816
     *                          The default value is the current size. <br/>
817
     *                          If no size has been specified since the beginning of the document, the value taken is 12.
818
     */
819
    public function SetFont(string $family, string $style='', float $size=0) : void
820
    {
821
    	// Select a font; size given in points
822
    	if($family=='')
823
    		$family = $this->FontFamily;
824
    	else
825
    		$family = strtolower($family);
826
    	$style = strtoupper($style);
827
    	if(strpos($style,'U')!==false)
828
    	{
829
    		$this->underline = true;
830
    		$style = str_replace('U','',$style);
831
    	}
832
    	else
833
    		$this->underline = false;
834
    	if($style=='IB')
835
    		$style = 'BI';
836
    	if($size==0)
837
    		$size = $this->FontSizePt;
838
    	// Test if font is already selected
839
    	if($this->FontFamily==$family && $this->FontStyle==$style && $this->FontSizePt==$size)
840
    		return;
841
    	// Test if font is already loaded
842
    	$fontkey = $family.$style;
843
    	if(!isset($this->fonts[$fontkey]))
844
    	{
845
    		// Test if one of the core fonts
846
    		if($family=='arial')
847
    			$family = 'helvetica';
848
    		if(in_array($family,$this->CoreFonts))
849
    		{
850
    			if($family=='symbol' || $family=='zapfdingbats')
851
    				$style = '';
852
    			$fontkey = $family.$style;
853
    			if(!isset($this->fonts[$fontkey]))
854
    				$this->addFont($family,$style);
855
    		}
856
    		else
857
    			$this->Error('Undefined font: '.$family.' '.$style);
858
    	}
859
    	// Select it
860
    	$this->FontFamily = $family;
861
    	$this->FontStyle = $style;
862
    	$this->FontSizePt = $size;
863
    	$this->FontSize = $size/$this->k;
864
    	$this->CurrentFont = &$this->fonts[$fontkey];
865
    	if($this->page>0)
866
    		$this->_out(sprintf('BT /F%d %.2F Tf ET',$this->CurrentFont['i'],$this->FontSizePt));
867
    }
868
    
869
    /**
870
     * Defines the size of the current font.
871
     * @param float $size   The size (in points).
872
     */
873
    public function SetFontSize(float $size) : void
874
    {
875
    	// Set font size in points
876
    	if($this->FontSizePt==$size)
877
    		return;
878
    	$this->FontSizePt = $size;
879
    	$this->FontSize = $size/$this->k;
880
    	if($this->page>0)
881
    		$this->_out(sprintf('BT /F%d %.2F Tf ET',$this->CurrentFont['i'],$this->FontSizePt));
882
    }
883
    
884
    /**
885
     * Creates a new internal link and returns its identifier. 
886
     * An internal link is a clickable area which directs to another place within the document.
887
     * The identifier can then be passed to Cell(), Write(), Image() or Link(). 
888
     * The destination is defined with SetLink().
889
     * @return int
890
     */
891
    public function AddLink() : int
892
    {
893
    	// Create a new internal link
894
    	$n = count($this->links)+1;
895
    	$this->links[$n] = array(0, 0);
896
    	return $n;
897
    }
898
    
899
    /**
900
     * Defines the page and position a link points to.
901
     * @param int $link The link identifier created by AddLink().
902
     * @param float $y  Y-position of target position; -1 indicates the current position. The default value is 0 (top of page).
903
     * @param int $page Number of target page; -1 indicates the current page. This is the default value.
904
     */
905
    public function SetLink(int $link, float $y=0, int $page=-1) : void
906
    {
907
    	// Set destination of internal link
908
    	if($y==-1)
909
    		$y = $this->y;
910
    	if($page==-1)
911
    		$page = $this->page;
912
    	$this->links[$link] = array($page, $y);
913
    }
914
    
915
    /**
916
     * Puts a link on a rectangular area of the page. 
917
     * Text or image links are generally put via Cell(), Write() or Image(), but this 
918
     * method can be useful for instance to define a clickable area inside an image.
919
     * Target can be an external URL or an internal link ID created and specified by AddLink()/SetLink() 
920
     * @param float $x          X-position
921
     * @param float $y          Y-position
922
     * @param float $w          Width
923
     * @param float $h          Height
924
     * @param string|int $link  URL or link-ID
925
     */
926
    public function Link(float $x, float $y, float $w, float $h, $link) : void
927
    {
928
    	// Put a link on the page
929
    	$this->PageLinks[$this->page][] = array($x*$this->k, $this->hPt-$y*$this->k, $w*$this->k, $h*$this->k, $link);
930
    }
931
    
932
    /**
933
     * Prints a character string. 
934
     * The origin is on the left of the first character, on the baseline. 
935
     * This method allows to place a string precisely on the page, but it is usually 
936
     * easier to use Cell(), MultiCell() or Write() which are the standard methods 
937
     * to print text.
938
     * @param float $x      X-position
939
     * @param float $y      Y-position
940
     * @param string $txt   String to print.
941
     */
942
    public function Text(float $x, float $y, string $txt) : void
943
    {
944
    	// Output a string
945
    	if(!isset($this->CurrentFont))
946
    		$this->Error('No font has been set');
947
    	$s = sprintf('BT %.2F %.2F Td (%s) Tj ET',$x*$this->k,($this->h-$y)*$this->k,$this->_escape($txt));
948
    	if($this->underline && $txt!='')
949
    		$s .= ' '.$this->_dounderline($x,$y,$txt);
950
    	if($this->ColorFlag)
951
    		$s = 'q '.$this->TextColor.' '.$s.' Q';
952
    	$this->_out($s);
953
    }
954
    
955
    /**
956
     * Whenever a page break condition is met, the method is called, and the break is 
957
     * issued or not depending on the returned value. 
958
     * The default implementation returns a value according to the mode selected by 
959
     * SetAutoPageBreak().
960
     * This method is called automatically and should not be called directly by the application.<br/>
961
     * <br/>
962
     * For usage in derived classes see example at http://www.fpdf.org/en/doc/acceptpagebreak.htm.
963
     * @link http://www.fpdf.org/en/doc/acceptpagebreak.htm
964
     * @return bool
965
     */
966
    public function AcceptPageBreak() : bool
967
    {
968
    	// Accept automatic page break or not
969
    	return $this->AutoPageBreak;
970
    }
971
    
972
    /**
973
     * Prints a cell (rectangular area) with optional borders, background color and character string. 
974
     * The upper-left corner of the cell corresponds to the current position. The text can be 
975
     * aligned or centered. After the call, the current position moves to the right or to the next line. 
976
     * It is possible to put a link on the text.
977
     * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done 
978
     * before outputting.
979
     * @param float $w          Cell width. If 0, the cell extends up to the right margin.
980
     * @param float $h          Cell height. Default value: 0.
981
     * @param string $txt       String to print. Default value: empty string.
982
     * @param int|string $border    Indicates if borders must be drawn around the cell. <br/>
983
     *                          The value can be either a number: <ul>
984
     *                          <li>0: no border </li>
985
     *                          <li>1: frame </li></ul>
986
     *                          or a string containing some or all of the following characters (in any order): <ul>
987
     *                          <li> 'L': left </li>
988
     *                          <li> 'T': top </li>
989
     *                          <li> 'R': right </li>
990
     *                          <li> 'B': bottom </li></ul>
991
     *                          Default value: 0. <br/>
992
     * @param float $ln         Indicates where the current position should go after the call. <br/>
993
     *                          Possible values are: <ul>
994
     *                          <li> 0: to the right </li>
995
     *                          <li> 1: to the beginning of the next line </li>
996
     *                          <li> 2: below </li></ul>
997
     *                          Putting 1 is equivalent to putting 0 and calling Ln() just after. <br/>
998
     *                          Default value: 0. <br/>
999
     * @param string $align     Allows to center or align the text. <br/>
1000
     *                          Possible values are: <ul>
1001
     *                          <li> 'L' or empty string: left align (default value) </li> 
1002
     *                          <li> 'C': center </li> 
1003
     *                          <li> 'R': right align </li></ul>
1004
     * @param boolean $fill     Indicates if the cell background must be painted (true) or transparent (false). <br/>
1005
     *                          If set to true, current FillColor is used for the background. <br/>
1006
     *                          Default value: false. <br/>
1007
     * @param string|int $link  URL or identifier for internal link created by AddLink().
1008
     */
1009
    public function Cell(float $w, float $h=0, string $txt='', $border=0, float $ln=0, $align='', $fill=false, $link='') : void
1010
    {
1011
    	// Output a cell
1012
    	$k = $this->k;
1013
    	if($this->y+$h>$this->PageBreakTrigger && !$this->InHeader && !$this->InFooter && $this->AcceptPageBreak())
1014
    	{
1015
    		// Automatic page break
1016
    		$x = $this->x;
1017
    		$ws = $this->ws;
1018
    		if($ws>0)
1019
    		{
1020
    			$this->ws = 0;
1021
    			$this->_out('0 Tw');
1022
    		}
1023
    		$this->addPage($this->CurOrientation,$this->CurPageSize,$this->CurRotation);
1024
    		$this->x = $x;
1025
    		if($ws>0)
1026
    		{
1027
    			$this->ws = $ws;
1028
    			$this->_out(sprintf('%.3F Tw',$ws*$k));
1029
    		}
1030
    	}
1031
    	if($w==0)
1032
    		$w = $this->w-$this->rMargin-$this->x;
1033
    	$s = '';
1034
    	if($fill || $border==1)
1035
    	{
1036
    		if($fill)
1037
    			$op = ($border==1) ? 'B' : 'f';
1038
    		else
1039
    			$op = 'S';
1040
    		$s = sprintf('%.2F %.2F %.2F %.2F re %s ',$this->x*$k,($this->h-$this->y)*$k,$w*$k,-$h*$k,$op);
1041
    	}
1042
    	if(is_string($border))
1043
    	{
1044
    		$x = $this->x;
1045
    		$y = $this->y;
1046
    		if(strpos($border,'L')!==false)
1047
    			$s .= sprintf('%.2F %.2F m %.2F %.2F l S ',$x*$k,($this->h-$y)*$k,$x*$k,($this->h-($y+$h))*$k);
1048
    		if(strpos($border,'T')!==false)
1049
    			$s .= sprintf('%.2F %.2F m %.2F %.2F l S ',$x*$k,($this->h-$y)*$k,($x+$w)*$k,($this->h-$y)*$k);
1050
    		if(strpos($border,'R')!==false)
1051
    			$s .= sprintf('%.2F %.2F m %.2F %.2F l S ',($x+$w)*$k,($this->h-$y)*$k,($x+$w)*$k,($this->h-($y+$h))*$k);
1052
    		if(strpos($border,'B')!==false)
1053
    			$s .= sprintf('%.2F %.2F m %.2F %.2F l S ',$x*$k,($this->h-($y+$h))*$k,($x+$w)*$k,($this->h-($y+$h))*$k);
1054
    	}
1055
    	if($txt!=='')
1056
    	{
1057
    		if(!isset($this->CurrentFont))
1058
    			$this->Error('No font has been set');
1059
    		if($align=='R')
1060
    			$dx = $w-$this->cMargin-$this->getStringWidth($txt);
1061
    		elseif($align=='C')
1062
    			$dx = ($w-$this->getStringWidth($txt))/2;
1063
    		else
1064
    			$dx = $this->cMargin;
1065
    		if($this->ColorFlag)
1066
    			$s .= 'q '.$this->TextColor.' ';
1067
    		$s .= sprintf('BT %.2F %.2F Td (%s) Tj ET',($this->x+$dx)*$k,($this->h-($this->y+.5*$h+.3*$this->FontSize))*$k,$this->_escape($txt));
1068
    		if($this->underline)
1069
    			$s .= ' '.$this->_dounderline($this->x+$dx,$this->y+.5*$h+.3*$this->FontSize,$txt);
1070
    		if($this->ColorFlag)
1071
    			$s .= ' Q';
1072
    		if($link)
1073
    			$this->Link($this->x+$dx,$this->y+.5*$h-.5*$this->FontSize,$this->getStringWidth($txt),$this->FontSize,$link);
1074
    	}
1075
    	if($s)
1076
    		$this->_out($s);
1077
    	$this->lasth = $h;
1078
    	if($ln>0)
1079
    	{
1080
    		// Go to next line
1081
    		$this->y += $h;
1082
    		if($ln==1)
1083
    			$this->x = $this->lMargin;
1084
    	}
1085
    	else
1086
    		$this->x += $w;
1087
    }
1088
    
1089
    /**
1090
     * This method allows printing text with line breaks. 
1091
     * They can be automatic (as soon as the text reaches the right border of the cell) or 
1092
     * explicit (via the \n character). 
1093
     * As many cells as necessary are output, one below the other.
1094
     * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
1095
     * @param float $w          Cell width. If 0, the cell extends up to the right margin.
1096
     * @param float $h          Cell height. Default value: 0.
1097
     * @param string $txt       String to print. Default value: empty string.
1098
     * @param int|string $border    Indicates if borders must be drawn around the cell. <br/>
1099
     *                          The value can be either a number: <ul>
1100
     *                          <li>0: no border </li>
1101
     *                          <li>1: frame </li></ul>
1102
     *                          or a string containing some or all of the following characters (in any order): <ul>
1103
     *                          <li> 'L': left </li>
1104
     *                          <li> 'T': top </li>
1105
     *                          <li> 'R': right </li>
1106
     *                          <li> 'B': bottom </li></ul>
1107
     *                          Default value: 0. <br/>
1108
     * @param string $align     Allows to center or align the text. <br/>
1109
     *                          Possible values are: <ul>
1110
     *                          <li> 'L' or empty string: left align (default value) </li> 
1111
     *                          <li> 'C': center </li> 
1112
     *                          <li> 'R': right align </li></ul>
1113
     * @param boolean $fill     Indicates if the cell background must be painted (true) or transparent (false). <br/>
1114
     *                          If set to true, current FillColor is used for the background. <br/>
1115
     *                          Default value: false.
1116
     */
1117
    public function MultiCell(float $w, float $h, string $txt, $border=0, string $align='J', bool $fill=false) : void
1118
    {
1119
    	// Output text with automatic or explicit line breaks
1120
    	if(!isset($this->CurrentFont))
1121
    		$this->Error('No font has been set');
1122
    	$cw = &$this->CurrentFont['cw'];
1123
    	if($w==0)
1124
    		$w = $this->w-$this->rMargin-$this->x;
1125
    	$wmax = ($w-2*$this->cMargin)*1000/$this->FontSize;
1126
    	$s = str_replace("\r",'',$txt);
1127
    	$nb = strlen($s);
1128
    	if($nb>0 && $s[$nb-1]=="\n")
1129
    		$nb--;
1130
    	$b = 0;
1131
    	$b2 = '';
1132
    	if($border)
1133
    	{
1134
    		if($border==1)
1135
    		{
1136
    			$border = 'LTRB';
1137
    			$b = 'LRT';
1138
    			$b2 = 'LR';
1139
    		}
1140
    		else
1141
    		{
1142
    			$b2 = '';
1143
    			if(strpos($border,'L')!==false)
1144
    				$b2 .= 'L';
1145
    			if(strpos($border,'R')!==false)
1146
    				$b2 .= 'R';
1147
    			$b = (strpos($border,'T')!==false) ? $b2.'T' : $b2;
1148
    		}
1149
    	}
1150
    	$sep = -1;
1151
    	$i = 0;
1152
    	$j = 0;
1153
    	$l = 0;
1154
    	$ns = 0;
1155
    	$nl = 1;
1156
    	$ls = 0;
1157
    	while($i<$nb)
1158
    	{
1159
    		// Get next character
1160
    		$c = $s[$i];
1161
    		if($c=="\n")
1162
    		{
1163
    			// Explicit line break
1164
    			if($this->ws>0)
1165
    			{
1166
    				$this->ws = 0;
1167
    				$this->_out('0 Tw');
1168
    			}
1169
    			$this->Cell($w,$h,substr($s,$j,$i-$j),$b,2,$align,$fill);
1170
    			$i++;
1171
    			$sep = -1;
1172
    			$j = $i;
1173
    			$l = 0;
1174
    			$ns = 0;
1175
    			$nl++;
1176
    			if($border && $nl==2)
1177
    				$b = $b2;
1178
    			continue;
1179
    		}
1180
    		if($c==' ')
1181
    		{
1182
    			$sep = $i;
1183
    			$ls = $l;
1184
    			$ns++;
1185
    		}
1186
    		$l += $cw[$c];
1187
    		if($l>$wmax)
1188
    		{
1189
    			// Automatic line break
1190
    			if($sep==-1)
1191
    			{
1192
    				if($i==$j)
1193
    					$i++;
1194
    				if($this->ws>0)
1195
    				{
1196
    					$this->ws = 0;
1197
    					$this->_out('0 Tw');
1198
    				}
1199
    				$this->Cell($w,$h,substr($s,$j,$i-$j),$b,2,$align,$fill);
1200
    			}
1201
    			else
1202
    			{
1203
    				if($align=='J')
1204
    				{
1205
    					$this->ws = ($ns>1) ? ($wmax-$ls)/1000*$this->FontSize/($ns-1) : 0;
1206
    					$this->_out(sprintf('%.3F Tw',$this->ws*$this->k));
1207
    				}
1208
    				$this->Cell($w,$h,substr($s,$j,$sep-$j),$b,2,$align,$fill);
1209
    				$i = $sep+1;
1210
    			}
1211
    			$sep = -1;
1212
    			$j = $i;
1213
    			$l = 0;
1214
    			$ns = 0;
1215
    			$nl++;
1216
    			if($border && $nl==2)
1217
    				$b = $b2;
1218
    		}
1219
    		else
1220
    			$i++;
1221
    	}
1222
    	// Last chunk
1223
    	if($this->ws>0)
1224
    	{
1225
    		$this->ws = 0;
1226
    		$this->_out('0 Tw');
1227
    	}
1228
    	if($border && strpos($border,'B')!==false)
1229
    		$b .= 'B';
1230
    	$this->Cell($w,$h,substr($s,$j,$i-$j),$b,2,$align,$fill);
1231
    	$this->x = $this->lMargin;
1232
    }
1233
    
1234
    /**
1235
     * This method prints text from the current position. 
1236
     * When the right margin is reached (or the \n character is met) a line break occurs 
1237
     * and text continues from the left margin. Upon method exit, the current position 
1238
     * is left just at the end of the text.
1239
     * It is possible to put a link on the text.
1240
     * @param float $h          Line height.
1241
     * @param string $txt       String to print.
1242
     * @param string|int $link  URL or identifier for internal link created by AddLink().
1243
     */
1244
    public function Write(float $h, string $txt, $link='') : void
1245
    {
1246
    	// Output text in flowing mode
1247
    	if(!isset($this->CurrentFont))
1248
    		$this->Error('No font has been set');
1249
    	$cw = &$this->CurrentFont['cw'];
1250
    	$w = $this->w-$this->rMargin-$this->x;
1251
    	$wmax = ($w-2*$this->cMargin)*1000/$this->FontSize;
1252
    	$s = str_replace("\r",'',$txt);
1253
    	$nb = strlen($s);
1254
    	$sep = -1;
1255
    	$i = 0;
1256
    	$j = 0;
1257
    	$l = 0;
1258
    	$nl = 1;
1259
    	while($i<$nb)
1260
    	{
1261
    		// Get next character
1262
    		$c = $s[$i];
1263
    		if($c=="\n")
1264
    		{
1265
    			// Explicit line break
1266
    			$this->Cell($w,$h,substr($s,$j,$i-$j),0,2,'',false,$link);
1267
    			$i++;
1268
    			$sep = -1;
1269
    			$j = $i;
1270
    			$l = 0;
1271
    			if($nl==1)
1272
    			{
1273
    				$this->x = $this->lMargin;
1274
    				$w = $this->w-$this->rMargin-$this->x;
1275
    				$wmax = ($w-2*$this->cMargin)*1000/$this->FontSize;
1276
    			}
1277
    			$nl++;
1278
    			continue;
1279
    		}
1280
    		if($c==' ')
1281
    			$sep = $i;
1282
    		$l += $cw[$c];
1283
    		if($l>$wmax)
1284
    		{
1285
    			// Automatic line break
1286
    			if($sep==-1)
1287
    			{
1288
    				if($this->x>$this->lMargin)
1289
    				{
1290
    					// Move to next line
1291
    					$this->x = $this->lMargin;
1292
    					$this->y += $h;
1293
    					$w = $this->w-$this->rMargin-$this->x;
1294
    					$wmax = ($w-2*$this->cMargin)*1000/$this->FontSize;
1295
    					$i++;
1296
    					$nl++;
1297
    					continue;
1298
    				}
1299
    				if($i==$j)
1300
    					$i++;
1301
    				$this->Cell($w,$h,substr($s,$j,$i-$j),0,2,'',false,$link);
1302
    			}
1303
    			else
1304
    			{
1305
    				$this->Cell($w,$h,substr($s,$j,$sep-$j),0,2,'',false,$link);
1306
    				$i = $sep+1;
1307
    			}
1308
    			$sep = -1;
1309
    			$j = $i;
1310
    			$l = 0;
1311
    			if($nl==1)
1312
    			{
1313
    				$this->x = $this->lMargin;
1314
    				$w = $this->w-$this->rMargin-$this->x;
1315
    				$wmax = ($w-2*$this->cMargin)*1000/$this->FontSize;
1316
    			}
1317
    			$nl++;
1318
    		}
1319
    		else
1320
    			$i++;
1321
    	}
1322
    	// Last chunk
1323
    	if($i!=$j)
1324
    		$this->Cell($l/1000*$this->FontSize,$h,substr($s,$j),0,0,'',false,$link);
1325
    }
1326
    
1327
    /**
1328
     * Performs a line break. 
1329
     * The current X-position goes back to the left margin and the Y-position increases by 
1330
     * the amount passed in parameter.
1331
     * @param float $h  The height of the break. <br/>
1332
     *                  By default, the value equals the height of the last printed cell.
1333
     */
1334
    public function Ln(float $h=null) : void
1335
    {
1336
    	// Line feed; default value is the last cell height
1337
    	$this->x = $this->lMargin;
1338
    	if($h===null)
1339
    		$this->y += $this->lasth;
1340
    	else
1341
    		$this->y += $h;
1342
    }
1343
    
1344
    /**
1345
     * Puts an image. 
1346
     * The size it will take on the page can be specified in different ways: <ul>
1347
     * <li> explicit width and height (expressed in user unit or dpi) </li> 
1348
     * <li> one explicit dimension, the other being calculated automatically in order to keep the original proportions </li> 
1349
     * <li> no explicit dimension, in which case the image is put at 96 dpi </li></ul>
1350
     * Supported formats are JPEG, PNG and GIF. <b>The GD extension is required for GIF.</b><br/>
1351
     * For JPEGs, all flavors are allowed: <ul>
1352
     * <li> gray scales </li> 
1353
     * <li> true colors (24 bits) </li>
1354
     * <li> CMYK (32 bits) </li></ul>
1355
     * For PNGs, are allowed: <ul>
1356
     * <li> gray scales on at most 8 bits (256 levels) </li>
1357
     * <li> indexed colors </li>
1358
     * <li> true colors (24 bits) </li></ul>
1359
     * For GIFs: in case of an animated GIF, only the first frame is displayed. <br/><br/>
1360
     * Transparency is supported. <br/><br/>
1361
     * The format can be specified explicitly or inferred from the file extension. <br/><br/>
1362
     * It is possible to put a link on the image. <br/><br/>
1363
     * <b>Remark:</b> if an image is used several times, only one copy is embedded in the file.
1364
     * @param string $file  Path or URL of the image.
1365
     * @param float $x      X-position of the upper-left corner. <br/> <br/>
1366
     *                      If not specified or equal to null, the current X-position is used. <br/>
1367
     * @param float $y      Y-position of the upper-left corner. <br/>
1368
     *                      If not specified or equal to null, the current Y-position is used; <br/>
1369
     *                      moreover, a page break is triggered first if necessary (in case automatic page breaking is enabled) and, <br/>
1370
     *                      after the call, the current Y-position is moved to the bottom of the image. <br/>
1371
     * @param float $w      Width of the image in the page. <br/>
1372
     *                      There are three cases: <ul>
1373
     *                      <li> If the value is positive, it represents the width in user unit </li> 
1374
     *                      <li> If the value is negative, the absolute value represents the horizontal resolution in dpi </li> 
1375
     *                      <li> If the value is not specified or equal to zero, it is automatically calculated </li><ul>
1376
     * @param float $h      Height of the image in the page. <br/>
1377
     *                      There are three cases: <ul>
1378
     *                      <li> If the value is positive, it represents the height in user unit </li> 
1379
     *                      <li> If the value is negative, the absolute value represents the vertical resolution in dpi </li> 
1380
     *                      <li> If the value is not specified or equal to zero, it is automatically calculated </li><ul>
1381
     * @param string $type  Image format. <br/>
1382
     *                      Possible values are (case insensitive): <ul> 
1383
     *                      <li> JPG </li> 
1384
     *                      <li> JPEG </li> 
1385
     *                      <li> PNG </li>
1386
     *                      <li> GIF </li></ul>
1387
     *                      If not specified, the type is inferred from the file extension. <br/>
1388
     * @param string|int $link  URL or identifier for internal link created by AddLink().
1389
     */
1390
    public function Image(string $file, ?float $x=null, ?float $y=null, float $w=0, float $h=0, string $type='', $link='') : void
1391
    {
1392
    	// Put an image on the page
1393
    	if($file=='')
1394
    		$this->Error('Image file name is empty');
1395
    	if(!isset($this->images[$file]))
1396
    	{
1397
    		// First use of this image, get info
1398
    		if($type=='')
1399
    		{
1400
    			$pos = strrpos($file,'.');
1401
    			if(!$pos)
1402
    				$this->Error('Image file has no extension and no type was specified: '.$file);
1403
    			$type = substr($file,$pos+1);
1404
    		}
1405
    		$type = strtolower($type);
1406
    		if($type=='jpeg')
1407
    			$type = 'jpg';
1408
    		$mtd = '_parse'.$type;
1409
    		if(!method_exists($this,$mtd))
1410
    			$this->Error('Unsupported image type: '.$type);
1411
    		$info = $this->$mtd($file);
1412
    		$info['i'] = count($this->images)+1;
1413
    		$this->images[$file] = $info;
1414
    	}
1415
    	else
1416
    		$info = $this->images[$file];
1417
    
1418
    	// Automatic width and height calculation if needed
1419
    	if($w==0 && $h==0)
1420
    	{
1421
    		// Put image at 96 dpi
1422
    		$w = -96;
1423
    		$h = -96;
1424
    	}
1425
    	if($w<0)
1426
    		$w = -$info['w']*72/$w/$this->k;
1427
    	if($h<0)
1428
    		$h = -$info['h']*72/$h/$this->k;
1429
    	if($w==0)
1430
    		$w = $h*$info['w']/$info['h'];
1431
    	if($h==0)
1432
    		$h = $w*$info['h']/$info['w'];
1433
    
1434
    	// Flowing mode
1435
    	if($y===null)
1436
    	{
1437
    		if($this->y+$h>$this->PageBreakTrigger && !$this->InHeader && !$this->InFooter && $this->AcceptPageBreak())
1438
    		{
1439
    			// Automatic page break
1440
    			$x2 = $this->x;
1441
    			$this->addPage($this->CurOrientation,$this->CurPageSize,$this->CurRotation);
1442
    			$this->x = $x2;
1443
    		}
1444
    		$y = $this->y;
1445
    		$this->y += $h;
1446
    	}
1447
    
1448
    	if($x===null)
1449
    		$x = $this->x;
1450
    	$this->_out(sprintf('q %.2F 0 0 %.2F %.2F %.2F cm /I%d Do Q',$w*$this->k,$h*$this->k,$x*$this->k,($this->h-($y+$h))*$this->k,$info['i']));
1451
    	if($link)
1452
    		$this->Link($x,$y,$w,$h,$link);
1453
    }
1454
    
1455
    /**
1456
     * Get current page width.
1457
     * @return float
1458
     */
1459
    public function GetPageWidth() : float
1460
    {
1461
    	// Get current page width
1462
    	return $this->w;
1463
    }
1464
    
1465
    /**
1466
     * Get current page height.
1467
     * @return float
1468
     */
1469
    public function GetPageHeight() : float
1470
    {
1471
    	// Get current page height
1472
    	return $this->h;
1473
    }
1474
    
1475
    /**
1476
     * Get current x position.
1477
     * @return float
1478
     */
1479
    public function GetX() : float
1480
    {
1481
    	// GetX position
1482
    	return $this->x;
1483
    }
1484
    
1485
    /**
1486
     * Set new X position.
1487
     * If the passed value is negative, it is relative to the right of the page.
1488
     * @param float $x
1489
     */
1490
    public function SetX(float $x) : void
1491
    {
1492
    	// Set x position
1493
    	if($x>=0)
1494
    		$this->x = $x;
1495
    	else
1496
    		$this->x = $this->w+$x;
1497
    }
1498
1499
    /**
1500
     * Get current Y position.
1501
     * @return float
1502
     */
1503
    public function GetY() : float
1504
    {
1505
    	// Get y position
1506
    	return $this->y;
1507
    }
1508
1509
    /**
1510
     * Set new Y position and optionally moves the current X-position back to the left margin.
1511
     * If the passed value is negative, it is relative to the bottom of the page.
1512
     * @param float $y
1513
     * @param bool $resetX
1514
     */
1515
    public function SetY(float $y, bool $resetX=true) : void
1516
    {
1517
    	// Set y position and optionally reset x
1518
    	if($y>=0)
1519
    		$this->y = $y;
1520
    	else
1521
    		$this->y = $this->h+$y;
1522
    	if($resetX)
1523
    		$this->x = $this->lMargin;
1524
    }
1525
    
1526
    /**
1527
     * Set new X and Y position.
1528
     * If the passed values are negative, they are relative respectively to the right and bottom of the page.
1529
     * @param float $x
1530
     * @param float $y
1531
     */
1532
    public function SetXY(float $x, float $y) : void
1533
    {
1534
    	// Set x and y positions
1535
    	$this->setX($x);
1536
    	$this->setY($y,false);
1537
    }
1538
    
1539
    /**
1540
     * Send the document to a given destination: browser, file or string. 
1541
     * In the case of a browser, the PDF viewer may be used or a download may be forced.
1542
     * The method first calls Close() if necessary to terminate the document.
1543
     * @param string $dest  Destination where to send the document. <br/>
1544
     *                      It can be one of the following: <ul> 
1545
     *                      <li> 'I': send the file inline to the browser. The PDF viewer is used if available. </li> 
1546
     *                      <li> 'D': send to the browser and force a file download with the name given by name. </li> 
1547
     *                      <li> 'F': save to a local file with the name given by name (may include a path). </li> 
1548
     *                      <li> 'S': return the document as a string. </li></ul>
1549
     *                      The default value is I. <br/>
1550
     * @param string $name  The name of the file. It is ignored in case of destination 'S'. <br/>
1551
     *                      The default value is doc.pdf. <br/>
1552
     * @param bool $isUTF8  Indicates if name is encoded in ISO-8859-1 (false) or UTF-8 (true). <br/>
1553
     *                      Only used for destinations I and D. <br/>
1554
     *                      The default value is false. <br/>
1555
     * @return string
1556
     */
1557
    public function Output(string $dest='', string $name='', bool $isUTF8=false) : string
1558
    {
1559
    	// Output PDF to some destination
1560
    	$this->Close();
1561
    	if(strlen($name)==1 && strlen($dest)!=1)
1562
    	{
1563
    		// Fix parameter order
1564
    		$tmp = $dest;
1565
    		$dest = $name;
1566
    		$name = $tmp;
1567
    	}
1568
    	if($dest=='')
1569
    		$dest = 'I';
1570
    	if($name=='')
1571
    		$name = 'doc.pdf';
1572
    	switch(strtoupper($dest))
1573
    	{
1574
    		case 'I':
1575
    			// Send to standard output
1576
    			$this->_checkoutput();
1577
    			if(PHP_SAPI!='cli')
1578
    			{
1579
    				// We send to a browser
1580
    				header('Content-Type: application/pdf; charset=UTF-8');
1581
    				header('Content-Disposition: inline; '.$this->_httpencode('filename',$name,$isUTF8));
1582
    				header('Cache-Control: private, max-age=0, must-revalidate');
1583
    				header('Pragma: public');
1584
    			}
1585
    			echo $this->buffer;
1586
    			break;
1587
    		case 'D':
1588
    			// Download file
1589
    			$this->_checkoutput();
1590
    			header('Content-Type: application/x-download');
1591
    			header('Content-Disposition: attachment; '.$this->_httpencode('filename',$name,$isUTF8));
1592
    			header('Cache-Control: private, max-age=0, must-revalidate');
1593
    			header('Pragma: public');
1594
    			echo $this->buffer;
1595
    			break;
1596
    		case 'F':
1597
    			// Save to local file
1598
    			if(!file_put_contents($name,$this->buffer))
1599
    				$this->Error('Unable to create output file: '.$name);
1600
    			break;
1601
    		case 'S':
1602
    			// Return as a string
1603
    			return $this->buffer;
1604
    		default:
1605
    			$this->Error('Incorrect output destination: '.$dest);
1606
    	}
1607
    	return '';
1608
    }
1609
1610
    /**
1611
     * Some internal checks before starting.
1612
     */
1613
    protected function doChecks() : void
1614
    {
1615
        // Check mbstring overloading
1616
        if ((ini_get('mbstring.func_overload') & 2) !== 0) {
1617
            $this->Error('mbstring overloading must be disabled');
1618
        }
1619
    }
1620
1621
    /**
1622
     * get the path for the font definitions
1623
     */
1624
    protected function getFontPath() : void 
1625
    {
1626
        // Font path
1627
        $this->fontpath = '';
1628
        if (defined('FPDF_FONTPATH')) {
1629
            $this->fontpath = FPDF_FONTPATH;
1630
            if (substr($this->fontpath, -1) != '/' && substr($this->fontpath, -1) != '\\') {
1631
                $this->fontpath .= '/';
1632
            }
1633
        } elseif (is_dir(dirname(__FILE__) . '/font')) {
1634
            $this->fontpath = dirname(__FILE__) . '/font/';
1635
        }
1636
    }
1637
    
1638
    /**
1639
     * Some internal checks before output.
1640
     */
1641
    protected function _checkoutput() : void
1642
    {
1643
    	if(PHP_SAPI!='cli')
1644
    	{
1645
    	    $file = '';
1646
    	    $line = 0;
1647
    		if(headers_sent($file,$line))
1648
    			$this->Error("Some data has already been output, can't send PDF file (output started at $file:$line)");
1649
    	}
1650
    	if(ob_get_length())
1651
    	{
1652
    		// The output buffer is not empty
1653
    		if(preg_match('/^(\xEF\xBB\xBF)?\s*$/',ob_get_contents()))
1654
    		{
1655
    			// It contains only a UTF-8 BOM and/or whitespace, let's clean it
1656
    			ob_clean();
1657
    		}
1658
    		else
1659
    			$this->Error("Some data has already been output, can't send PDF file");
1660
    	}
1661
    }
1662
    
1663
    /**
1664
     * Get dimensions of selected pagesize.
1665
     * @param string|array $size
1666
     * @return array
1667
     */
1668
    protected function getPageSize($size) : array
1669
    {
1670
        if (is_string($size)) {
1671
            $aStdPageSizes = array(
1672
                'a3'=>array(841.89, 1190.55),
1673
                'a4'=>array(595.28, 841.89),
1674
                'a5'=>array(420.94, 595.28),
1675
                'letter'=>array(612, 792),
1676
                'legal'=>array(612, 1008)
1677
            );
1678
            
1679
            $size = strtolower($size);
1680
            if (!isset($aStdPageSizes[$size])) {
1681
                $this->Error('Unknown page size: ' . $size);
1682
            } else {
1683
                $a = $aStdPageSizes[$size];
1684
            }
1685
            return array($a[0] / $this->k, $a[1] / $this->k);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $a does not seem to be defined for all execution paths leading up to this point.
Loading history...
1686
        } else {
1687
            if ($size[0] > $size[1]) {
1688
                return array($size[1], $size[0]);
1689
            } else {
1690
                return $size;
1691
            }
1692
        }
1693
    }
1694
    
1695
    /**
1696
     * Start new page.
1697
     * @param string $orientation
1698
     * @param string|array $size
1699
     * @param int $rotation
1700
     */
1701
    protected function _beginpage(string $orientation, $size, int $rotation) : void
1702
    {
1703
    	$this->page++;
1704
    	$this->pages[$this->page] = '';
1705
    	$this->state = 2;
1706
    	$this->x = $this->lMargin;
1707
    	$this->y = $this->tMargin;
1708
    	$this->FontFamily = '';
1709
    	// Check page size and orientation
1710
    	if($orientation=='')
1711
    		$orientation = $this->DefOrientation;
1712
    	else
1713
    		$orientation = strtoupper($orientation[0]);
1714
    	if($size=='')
1715
    		$size = $this->DefPageSize;
1716
    	else
1717
    		$size = $this->getPageSize($size);
1718
    	if($orientation!=$this->CurOrientation || $size[0]!=$this->CurPageSize[0] || $size[1]!=$this->CurPageSize[1])
1719
    	{
1720
    		// New size or orientation
1721
    		if($orientation=='P')
1722
    		{
1723
    			$this->w = $size[0];
1724
    			$this->h = $size[1];
1725
    		}
1726
    		else
1727
    		{
1728
    			$this->w = $size[1];
1729
    			$this->h = $size[0];
1730
    		}
1731
    		$this->wPt = $this->w*$this->k;
1732
    		$this->hPt = $this->h*$this->k;
1733
    		$this->PageBreakTrigger = $this->h-$this->bMargin;
1734
    		$this->CurOrientation = $orientation;
1735
    		$this->CurPageSize = $size;
1736
    	}
1737
    	if($orientation!=$this->DefOrientation || $size[0]!=$this->DefPageSize[0] || $size[1]!=$this->DefPageSize[1])
1738
    		$this->PageInfo[$this->page]['size'] = array($this->wPt, $this->hPt);
1739
    	if($rotation!=0)
1740
    	{
1741
    		if($rotation%90!=0)
1742
    			$this->Error('Incorrect rotation value: '.$rotation);
1743
    		$this->CurRotation = $rotation;
1744
    		$this->PageInfo[$this->page]['rotation'] = $rotation;
1745
    	}
1746
    }
1747
    
1748
    /**
1749
     * End of current page.
1750
     */
1751
    protected function _endpage() : void
1752
    {
1753
    	$this->state = 1;
1754
    }
1755
    
1756
    /**
1757
     * Load a font definition file from the font directory.
1758
     * @param string $font
1759
     * @return array
1760
     */
1761
    protected function _loadfont(string $font) : array
1762
    {
1763
    	// Load a font definition file from the font directory
1764
        if(strpos($font,'/')!==false || strpos($font,"\\")!==false) {
1765
    		$this->Error('Incorrect font definition file name: '.$font);
1766
        }
1767
        // following vars must be initialized in the font definition file beeing included
1768
    	$name = null; 
1769
    	$enc = null;
1770
    	$subsetted = null;
1771
    	include($this->fontpath.$font);
1772
    	
1773
    	// phpstan can't see the code dynamicly included before so assuming $name, $enc, $subsetted always set to null!
1774
    	if(!isset($name)) {            /* @phpstan-ignore-line */
1775
    		$this->Error('Could not include font definition file');
1776
    	}
1777
    	if(isset($enc)) {              /* @phpstan-ignore-line */
1778
    		$enc = strtolower($enc);
1779
    	}
1780
    	if(!isset($subsetted)) {       /* @phpstan-ignore-line */
1781
    		$subsetted = false;
1782
    	}
1783
    	return get_defined_vars();
1784
    }
1785
    
1786
    /**
1787
     * Check if string only contains ascii chars (0...127).
1788
     * @param string $s
1789
     * @return bool
1790
     */
1791
    protected function _isascii(string $s) : bool
1792
    {
1793
    	// Test if string is ASCII
1794
    	$nb = strlen($s);
1795
    	for($i=0;$i<$nb;$i++)
1796
    	{
1797
    		if(ord($s[$i])>127)
1798
    			return false;
1799
    	}
1800
    	return true;
1801
    }
1802
    
1803
    /**
1804
     * @param string $param
1805
     * @param string $value
1806
     * @param bool $isUTF8
1807
     * @return string
1808
     */
1809
    protected function _httpencode(string $param, string $value, bool $isUTF8) : string
1810
    {
1811
    	// Encode HTTP header field parameter
1812
    	if($this->_isascii($value))
1813
    		return $param.'="'.$value.'"';
1814
    	if(!$isUTF8)
1815
    		$value = utf8_encode($value);
1816
    	if(strpos($_SERVER['HTTP_USER_AGENT'],'MSIE')!==false)
1817
    		return $param.'="'.rawurlencode($value).'"';
1818
    	else
1819
    		return $param."*=UTF-8''".rawurlencode($value);
1820
    }
1821
    
1822
    /**
1823
     * Convert UTF8 to UTF16.
1824
     * @param string $s
1825
     * @return string
1826
     */
1827
    protected function _UTF8toUTF16(string $s) : string
1828
    {
1829
    	// Convert UTF-8 to UTF-16BE with BOM
1830
    	$res = "\xFE\xFF";
1831
    	$nb = strlen($s);
1832
    	$i = 0;
1833
    	while($i<$nb)
1834
    	{
1835
    		$c1 = ord($s[$i++]);
1836
    		if($c1>=224)
1837
    		{
1838
    			// 3-byte character
1839
    			$c2 = ord($s[$i++]);
1840
    			$c3 = ord($s[$i++]);
1841
    			$res .= chr((($c1 & 0x0F)<<4) + (($c2 & 0x3C)>>2));
1842
    			$res .= chr((($c2 & 0x03)<<6) + ($c3 & 0x3F));
1843
    		}
1844
    		elseif($c1>=192)
1845
    		{
1846
    			// 2-byte character
1847
    			$c2 = ord($s[$i++]);
1848
    			$res .= chr(($c1 & 0x1C)>>2);
1849
    			$res .= chr((($c1 & 0x03)<<6) + ($c2 & 0x3F));
1850
    		}
1851
    		else
1852
    		{
1853
    			// Single-byte character
1854
    			$res .= "\0".chr($c1);
1855
    		}
1856
    	}
1857
    	return $res;
1858
    }
1859
    
1860
    /**
1861
     * Escape special characters.
1862
     * @param string $s
1863
     * @return string
1864
     */
1865
    protected function _escape(string $s) : string
1866
    {
1867
    	// Escape special characters
1868
    	if(strpos($s,'(')!==false || strpos($s,')')!==false || strpos($s,'\\')!==false || strpos($s,"\r")!==false)
1869
    		return str_replace(array('\\','(',')',"\r"), array('\\\\','\\(','\\)','\\r'), $s);
1870
    	else
1871
    		return $s;
1872
    }
1873
    
1874
    /**
1875
     * Format a text string.
1876
     * @param string $s
1877
     * @return string
1878
     */
1879
    protected function _textstring(string $s) : string
1880
    {
1881
    	// Format a text string
1882
    	if(!$this->_isascii($s))
1883
    		$s = $this->_UTF8toUTF16($s);
1884
    	return '('.$this->_escape($s).')';
1885
    }
1886
    
1887
    /**
1888
     * Underline text with 'simple' line.
1889
     * @param float $x
1890
     * @param float $y
1891
     * @param string $txt
1892
     * @return string
1893
     */
1894
    protected function _dounderline(float $x, float $y, string $txt) : string
1895
    {
1896
    	// Underline text
1897
    	$up = $this->CurrentFont['up'];
1898
    	$ut = $this->CurrentFont['ut'];
1899
    	$w = $this->getStringWidth($txt)+$this->ws*substr_count($txt,' ');
1900
    	return sprintf('%.2F %.2F %.2F %.2F re f',$x*$this->k,($this->h-($y-$up/1000*$this->FontSize))*$this->k,$w*$this->k,-$ut/1000*$this->FontSizePt);
1901
    }
1902
    
1903
    /**
1904
     * Extract info from a JPEG file.
1905
     * @param string $file
1906
     * @return array
1907
     */
1908
    protected function _parsejpg(string $file) : array
1909
    {
1910
    	// Extract info from a JPEG file
1911
    	$a = getimagesize($file);
1912
    	if(!$a)
1913
    		$this->Error('Missing or incorrect image file: '.$file);
1914
    	if($a[2]!=2)
1915
    		$this->Error('Not a JPEG file: '.$file);
1916
    	if(!isset($a['channels']) || $a['channels']==3)
1917
    		$colspace = 'DeviceRGB';
1918
    	elseif($a['channels']==4)
1919
    		$colspace = 'DeviceCMYK';
1920
    	else
1921
    		$colspace = 'DeviceGray';
1922
    	$bpc = isset($a['bits']) ? $a['bits'] : 8;
1923
    	$data = file_get_contents($file);
1924
    	return array('w'=>$a[0], 'h'=>$a[1], 'cs'=>$colspace, 'bpc'=>$bpc, 'f'=>'DCTDecode', 'data'=>$data);
1925
    }
1926
    
1927
    /**
1928
     * Extract info from a PNG file.
1929
     * @param string $file
1930
     * @return array
1931
     */
1932
    protected function _parsepng(string $file) : array
1933
    {
1934
    	// Extract info from a PNG file
1935
    	$f = fopen($file, 'rb');
1936
    	if ($f === false) {
1937
    		$this->Error('Can\'t open image file: ' . $file);
1938
    	}
1939
    	$info = $this->_parsepngstream($f, $file);
1940
    	fclose($f);
1941
    	return $info;
1942
    }
1943
    
1944
    /**
1945
     * Extract info from a PNG stream
1946
     * @param resource $f
1947
     * @param string $file
1948
     * @return array
1949
     */
1950
    protected function _parsepngstream($f, string $file) : array
1951
    {
1952
    	// Check signature
1953
    	if($this->_readstream($f,8)!=chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10))
1954
    		$this->Error('Not a PNG file: '.$file);
1955
    
1956
    	// Read header chunk
1957
    	$this->_readstream($f,4);
1958
    	if($this->_readstream($f,4)!='IHDR')
1959
    		$this->Error('Incorrect PNG file: '.$file);
1960
    	$w = $this->_readint($f);
1961
    	$h = $this->_readint($f);
1962
    	$bpc = ord($this->_readstream($f,1));
1963
    	if($bpc>8)
1964
    		$this->Error('16-bit depth not supported: '.$file);
1965
    	$ct = ord($this->_readstream($f,1));
1966
    	$colspace = '';
1967
    	if($ct==0 || $ct==4)
1968
    		$colspace = 'DeviceGray';
1969
    	elseif($ct==2 || $ct==6)
1970
    		$colspace = 'DeviceRGB';
1971
    	elseif($ct==3)
1972
    		$colspace = 'Indexed';
1973
    	else
1974
    		$this->Error('Unknown color type: '.$file);
1975
    	if(ord($this->_readstream($f,1))!=0)
1976
    		$this->Error('Unknown compression method: '.$file);
1977
    	if(ord($this->_readstream($f,1))!=0)
1978
    		$this->Error('Unknown filter method: '.$file);
1979
    	if(ord($this->_readstream($f,1))!=0)
1980
    		$this->Error('Interlacing not supported: '.$file);
1981
    	$this->_readstream($f,4);
1982
    	$dp = '/Predictor 15 /Colors '.($colspace=='DeviceRGB' ? 3 : 1).' /BitsPerComponent '.$bpc.' /Columns '.$w;
1983
    
1984
    	// Scan chunks looking for palette, transparency and image data
1985
    	$pal = '';
1986
    	$trns = '';
1987
    	$data = '';
1988
    	do
1989
    	{
1990
    		$n = $this->_readint($f);
1991
    		$type = $this->_readstream($f,4);
1992
    		if($type=='PLTE')
1993
    		{
1994
    			// Read palette
1995
    			$pal = $this->_readstream($f,$n);
1996
    			$this->_readstream($f,4);
1997
    		}
1998
    		elseif($type=='tRNS')
1999
    		{
2000
    			// Read transparency info
2001
    			$t = $this->_readstream($f,$n);
2002
    			if($ct==0)
2003
    				$trns = array(ord(substr($t,1,1)));
2004
    			elseif($ct==2)
2005
    				$trns = array(ord(substr($t,1,1)), ord(substr($t,3,1)), ord(substr($t,5,1)));
2006
    			else
2007
    			{
2008
    				$pos = strpos($t,chr(0));
2009
    				if($pos!==false)
2010
    					$trns = array($pos);
2011
    			}
2012
    			$this->_readstream($f,4);
2013
    		}
2014
    		elseif($type=='IDAT')
2015
    		{
2016
    			// Read image data block
2017
    			$data .= $this->_readstream($f,$n);
2018
    			$this->_readstream($f,4);
2019
    		}
2020
    		elseif($type=='IEND')
2021
    			break;
2022
    		else
2023
    			$this->_readstream($f,$n+4);
2024
    	}
2025
    	while($n);
2026
    
2027
    	if($colspace=='Indexed' && empty($pal))
2028
    		$this->Error('Missing palette in '.$file);
2029
    	$info = array('w'=>$w, 'h'=>$h, 'cs'=>$colspace, 'bpc'=>$bpc, 'f'=>'FlateDecode', 'dp'=>$dp, 'pal'=>$pal, 'trns'=>$trns);
2030
    	if($ct>=4)
2031
    	{
2032
    		// Extract alpha channel
2033
    		if(!function_exists('gzuncompress'))
2034
    			$this->Error('Zlib not available, can\'t handle alpha channel: '.$file);
2035
    		$data = gzuncompress($data);
2036
    		$color = '';
2037
    		$alpha = '';
2038
    		if($ct==4)
2039
    		{
2040
    			// Gray image
2041
    			$len = 2*$w;
2042
    			for($i=0;$i<$h;$i++)
2043
    			{
2044
    				$pos = (1+$len)*$i;
2045
    				$color .= $data[$pos];
2046
    				$alpha .= $data[$pos];
2047
    				$line = substr($data,$pos+1,$len);
2048
    				$color .= preg_replace('/(.)./s','$1',$line);
2049
    				$alpha .= preg_replace('/.(.)/s','$1',$line);
2050
    			}
2051
    		}
2052
    		else
2053
    		{
2054
    			// RGB image
2055
    			$len = 4*$w;
2056
    			for($i=0;$i<$h;$i++)
2057
    			{
2058
    				$pos = (1+$len)*$i;
2059
    				$color .= $data[$pos];
2060
    				$alpha .= $data[$pos];
2061
    				$line = substr($data,$pos+1,$len);
2062
    				$color .= preg_replace('/(.{3})./s','$1',$line);
2063
    				$alpha .= preg_replace('/.{3}(.)/s','$1',$line);
2064
    			}
2065
    		}
2066
    		unset($data);
2067
    		$data = gzcompress($color);
2068
    		$info['smask'] = gzcompress($alpha);
2069
    		$this->WithAlpha = true;
2070
    		if($this->PDFVersion<'1.4')
2071
    			$this->PDFVersion = '1.4';
2072
    	}
2073
    	$info['data'] = $data;
2074
    	return $info;
2075
    }
2076
    
2077
    /**
2078
     * Read n bytes from stream.
2079
     * @param resource $f
2080
     * @param int $n
2081
     * @return string
2082
     */
2083
    protected function _readstream($f, int $n) : string
2084
    {
2085
    	// Read n bytes from stream
2086
    	$res = '';
2087
    	while($n>0 && !feof($f))
2088
    	{
2089
    		$s = fread($f,$n);
2090
    		if($s===false)
2091
    			$this->Error('Error while reading stream');
2092
    		$n -= strlen($s);
2093
    		$res .= $s;
2094
    	}
2095
    	if($n>0)
2096
    		$this->Error('Unexpected end of stream');
2097
    	return $res;
2098
    }
2099
    
2100
    /**
2101
     * Read a 4-byte integer from stream.
2102
     * @param resource $f
2103
     * @return int
2104
     */
2105
    protected function _readint($f) : int
2106
    {
2107
    	// Read a 4-byte integer from stream
2108
    	$a = unpack('Ni',$this->_readstream($f,4));
2109
    	return $a['i'];
2110
    }
2111
    
2112
    /**
2113
     * Extract info from a GIF file (via PNG conversion).
2114
     * @param string $file
2115
     * @return array
2116
     */
2117
    protected function _parsegif(string $file) : array
2118
    {
2119
    	// Extract info from a GIF file (via PNG conversion)
2120
        if (!function_exists('imagepng')) {
2121
    		$this->Error('GD extension is required for GIF support');
2122
        }
2123
        if (!function_exists('imagecreatefromgif')) {
2124
    		$this->Error('GD has no GIF read support');
2125
        }
2126
    	$im = imagecreatefromgif($file);
2127
    	if ($im === false) {
2128
    		$this->Error('Missing or incorrect image file: ' . $file);
2129
    	}
2130
    	imageinterlace($im, 0);
2131
    	ob_start();
2132
    	imagepng($im);
2133
    	$data = ob_get_clean();
2134
    	imagedestroy($im);
2135
    	$f = fopen('php://temp', 'rb+');
2136
    	if ($f === false) {
2137
    		$this->Error('Unable to create memory stream');
2138
    	}
2139
    	fwrite($f, $data);
2140
    	rewind($f);
2141
    	$info = $this->_parsepngstream($f, $file);
2142
    	fclose($f);
2143
    	return $info;
2144
    }
2145
    
2146
    /**
2147
     * Add a line to the document.
2148
     * @param string $s
2149
     */
2150
    protected function _out(string $s) : void
2151
    {
2152
    	// Add a line to the document
2153
    	if($this->state==2)
2154
    		$this->pages[$this->page] .= $s."\n";
2155
    	elseif($this->state==1)
2156
    		$this->_put($s);
2157
    	elseif($this->state==0)
2158
    		$this->Error('No page has been added yet');
2159
    	elseif($this->state==3)
2160
    		$this->Error('The document is closed');
2161
    }
2162
    
2163
    /**
2164
     * Add a command to the document.
2165
     * @param string $s
2166
     */
2167
    protected function _put(string $s) : void
2168
    {
2169
    	$this->buffer .= $s."\n";
2170
    }
2171
    
2172
    /**
2173
     * Get current length of the output buffer.
2174
     * @return int
2175
     */
2176
    protected function _getoffset() : int
2177
    {
2178
    	return strlen($this->buffer);
2179
    }
2180
    
2181
    /**
2182
     * Begin a new object.
2183
     * @param int $n
2184
     */
2185
    protected function _newobj(?int $n=null) : void
2186
    {
2187
    	// Begin a new object
2188
    	if($n===null)
2189
    		$n = ++$this->n;
2190
    	$this->offsets[$n] = $this->_getoffset();
2191
    	$this->_put($n.' 0 obj');
2192
    }
2193
    
2194
    /**
2195
     * Add data from stream to the document.
2196
     * @param string $data
2197
     */
2198
    protected function _putstream(string $data) : void
2199
    {
2200
    	$this->_put('stream');
2201
    	$this->_put($data);
2202
    	$this->_put('endstream');
2203
    }
2204
    
2205
    /**
2206
     * Add Stream-object to the document. 
2207
     * @param string $data
2208
     */
2209
    protected function _putstreamobject(string $data) : void
2210
    {
2211
    	if($this->compress)
2212
    	{
2213
    		$entries = '/Filter /FlateDecode ';
2214
    		$data = gzcompress($data);
2215
    	}
2216
    	else
2217
    		$entries = '';
2218
    	$entries .= '/Length '.strlen($data);
2219
    	$this->_newobj();
2220
    	$this->_put('<<'.$entries.'>>');
2221
    	$this->_putstream($data);
2222
    	$this->_put('endobj');
2223
    }
2224
    
2225
    /**
2226
     * Add Pageinfos to the document.
2227
     * @param int $n
2228
     */
2229
    protected function _putpage(int $n) : void
2230
    {
2231
    	$this->_newobj();
2232
    	$this->_put('<</Type /Page');
2233
    	$this->_put('/Parent 1 0 R');
2234
    	if(isset($this->PageInfo[$n]['size']))
2235
    		$this->_put(sprintf('/MediaBox [0 0 %.2F %.2F]',$this->PageInfo[$n]['size'][0],$this->PageInfo[$n]['size'][1]));
2236
    	if(isset($this->PageInfo[$n]['rotation']))
2237
    		$this->_put('/Rotate '.$this->PageInfo[$n]['rotation']);
2238
    	$this->_put('/Resources 2 0 R');
2239
    	if(isset($this->PageLinks[$n]))
2240
    	{
2241
    		// Links
2242
    		$annots = '/Annots [';
2243
    		foreach($this->PageLinks[$n] as $pl)
2244
    		{
2245
    			$rect = sprintf('%.2F %.2F %.2F %.2F',$pl[0],$pl[1],$pl[0]+$pl[2],$pl[1]-$pl[3]);
2246
    			$annots .= '<</Type /Annot /Subtype /Link /Rect ['.$rect.'] /Border [0 0 0] ';
2247
    			if(is_string($pl[4]))
2248
    				$annots .= '/A <</S /URI /URI '.$this->_textstring($pl[4]).'>>>>';
2249
    			else
2250
    			{
2251
    				$l = $this->links[$pl[4]];
2252
    				if(isset($this->PageInfo[$l[0]]['size']))
2253
    					$h = $this->PageInfo[$l[0]]['size'][1];
2254
    				else
2255
    					$h = ($this->DefOrientation=='P') ? $this->DefPageSize[1]*$this->k : $this->DefPageSize[0]*$this->k;
2256
    				$annots .= sprintf('/Dest [%d 0 R /XYZ 0 %.2F null]>>',$this->PageInfo[$l[0]]['n'],$h-$l[1]*$this->k);
2257
    			}
2258
    		}
2259
    		$this->_put($annots.']');
2260
    	}
2261
    	if($this->WithAlpha)
2262
    		$this->_put('/Group <</Type /Group /S /Transparency /CS /DeviceRGB>>');
2263
    	$this->_put('/Contents '.($this->n+1).' 0 R>>');
2264
    	$this->_put('endobj');
2265
    	// Page content
2266
    	if(!empty($this->AliasNbPages))
2267
    		$this->pages[$n] = str_replace($this->AliasNbPages, strval($this->page), $this->pages[$n]);
2268
    	$this->_putstreamobject($this->pages[$n]);
2269
    }
2270
    
2271
    /**
2272
     * Add all pages to the document.
2273
     */
2274
    protected function _putpages() : void
2275
    {
2276
    	$nb = $this->page;
2277
    	for($n=1;$n<=$nb;$n++)
2278
    		$this->PageInfo[$n]['n'] = $this->n+1+2*($n-1);
2279
    	for($n=1;$n<=$nb;$n++)
2280
    		$this->_putpage($n);
2281
    	// Pages root
2282
    	$this->_newobj(1);
2283
    	$this->_put('<</Type /Pages');
2284
    	$kids = '/Kids [';
2285
    	for($n=1;$n<=$nb;$n++)
2286
    		$kids .= $this->PageInfo[$n]['n'].' 0 R ';
2287
    	$this->_put($kids.']');
2288
    	$this->_put('/Count '.$nb);
2289
    	if($this->DefOrientation=='P')
2290
    	{
2291
    		$w = $this->DefPageSize[0];
2292
    		$h = $this->DefPageSize[1];
2293
    	}
2294
    	else
2295
    	{
2296
    		$w = $this->DefPageSize[1];
2297
    		$h = $this->DefPageSize[0];
2298
    	}
2299
    	$this->_put(sprintf('/MediaBox [0 0 %.2F %.2F]',$w*$this->k,$h*$this->k));
2300
    	$this->_put('>>');
2301
    	$this->_put('endobj');
2302
    }
2303
    
2304
    /**
2305
     * Add fonts to the document.
2306
     */
2307
    protected function _putfonts() : void
2308
    {
2309
    	foreach ($this->FontFiles as $file => $info) {
2310
    		// Font file embedding
2311
    		$this->_newobj();
2312
    		$this->FontFiles[$file]['n'] = $this->n;
2313
    		$font = file_get_contents($this->fontpath . $file, true);
2314
    		if (!$font) {
2315
    			$this->Error('Font file not found: ' . $file);
2316
    		}
2317
    		$compressed = (substr($file, -2) == '.z');
2318
    		if (!$compressed && isset($info['length2'])) {
2319
    			$font = substr($font, 6, $info['length1']) . substr($font, 6 + $info['length1'] + 6, $info['length2']);
2320
    		}
2321
    		$this->_put('<</Length ' . strlen($font));
2322
    		if ($compressed) {
2323
    			$this->_put('/Filter /FlateDecode');
2324
    		}
2325
    		$this->_put('/Length1 ' . $info['length1']);
2326
    		if (isset($info['length2'])) {
2327
    			$this->_put('/Length2 '.$info['length2'].' /Length3 0');
2328
    		}
2329
    		$this->_put('>>');
2330
    		$this->_putstream($font);
2331
    		$this->_put('endobj');
2332
    	}
2333
    	foreach ($this->fonts as $k => $font) {
2334
    		// Encoding
2335
    		if (isset($font['diff'])) {
2336
    			if (!isset($this->encodings[$font['enc']])) {
2337
    				$this->_newobj();
2338
    				$this->_put('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [' . $font['diff'] . ']>>');
2339
    				$this->_put('endobj');
2340
    				$this->encodings[$font['enc']] = $this->n;
2341
    			}
2342
    		}
2343
    		// ToUnicode CMap
2344
    		$cmapkey = '';
2345
    		if (isset($font['uv'])) {
2346
    		    if (isset($font['enc'])) {
2347
    				$cmapkey = $font['enc'];
2348
    		    } else {
2349
    				$cmapkey = $font['name'];
2350
    		    }
2351
    			if (!isset($this->cmaps[$cmapkey])) {
2352
    				$cmap = $this->_tounicodecmap($font['uv']);
2353
    				$this->_putstreamobject($cmap);
2354
    				$this->cmaps[$cmapkey] = $this->n;
2355
    			}
2356
    		}
2357
    		// Font object
2358
    		$this->fonts[$k]['n'] = $this->n + 1;
2359
    		$type = $font['type'];
2360
    		$name = $font['name'];
2361
    		if ($font['subsetted']) {
2362
    			$name = 'AAAAAA+' . $name;
2363
    		}
2364
    		if ($type=='Core') {
2365
    			// Core font
2366
    			$this->_newobj();
2367
    			$this->_put('<</Type /Font');
2368
    			$this->_put('/BaseFont /' . $name);
2369
    			$this->_put('/Subtype /Type1');
2370
    			if ($name != 'Symbol' && $name != 'ZapfDingbats') {
2371
    				$this->_put('/Encoding /WinAnsiEncoding');
2372
    			}
2373
    			if (isset($font['uv'])) {
2374
    				$this->_put('/ToUnicode ' . $this->cmaps[$cmapkey] . ' 0 R');
2375
    			}
2376
    			$this->_put('>>');
2377
    			$this->_put('endobj');
2378
    		} elseif ($type == 'Type1' || $type == 'TrueType') {
2379
    			// Additional Type1 or TrueType/OpenType font
2380
    			$this->_newobj();
2381
    			$this->_put('<</Type /Font');
2382
    			$this->_put('/BaseFont /' . $name);
2383
    			$this->_put('/Subtype /' . $type);
2384
    			$this->_put('/FirstChar 32 /LastChar 255');
2385
    			$this->_put('/Widths ' . ($this->n + 1) . ' 0 R');
2386
    			$this->_put('/FontDescriptor ' . ($this->n + 2) . ' 0 R');
2387
    			if (isset($font['diff'])) {
2388
    				$this->_put('/Encoding ' . $this->encodings[$font['enc']] . ' 0 R');
2389
    			} else {
2390
    				$this->_put('/Encoding /WinAnsiEncoding');
2391
    			}
2392
    			if (isset($font['uv'])) {
2393
    				$this->_put('/ToUnicode ' . $this->cmaps[$cmapkey] . ' 0 R');
2394
    			}
2395
    			$this->_put('>>');
2396
    			$this->_put('endobj');
2397
    			// Widths
2398
    			$this->_newobj();
2399
    			$cw = &$font['cw'];
2400
    			$s = '[';
2401
    			for ($i = 32; $i <= 255; $i++) {
2402
    				$s .= $cw[chr($i)] . ' ';
2403
    			}
2404
    			$this->_put($s . ']');
2405
    			$this->_put('endobj');
2406
    			// Descriptor
2407
    			$this->_newobj();
2408
    			$s = '<</Type /FontDescriptor /FontName /' . $name;
2409
    			foreach ($font['desc'] as $k2 => $v) {
2410
    				$s .= ' /' . $k2 . ' ' . $v;
2411
    			}
2412
    			if (!empty($font['file'])) {
2413
    				$s .= ' /FontFile' . ($type == 'Type1' ? '' : '2') . ' ' . $this->FontFiles[$font['file']]['n'] . ' 0 R';
2414
    			}
2415
    			$this->_put($s . '>>');
2416
    			$this->_put('endobj');
2417
    		} else {
2418
    			// Allow for additional types
2419
    			$mtd = '_put' . strtolower($type);
2420
    			if (!method_exists($this, $mtd)) {
2421
    				$this->Error('Unsupported font type: ' . $type);
2422
    			}
2423
    			$this->$mtd($font);
2424
    		}
2425
    	}
2426
    }
2427
    
2428
    /**
2429
     * @param array $uv
2430
     * @return string
2431
     */
2432
    protected function _tounicodecmap(array $uv) : string
2433
    {
2434
    	$ranges = '';
2435
    	$nbr = 0;
2436
    	$chars = '';
2437
    	$nbc = 0;
2438
    	foreach($uv as $c=>$v)
2439
    	{
2440
    		if(is_array($v))
2441
    		{
2442
    			$ranges .= sprintf("<%02X> <%02X> <%04X>\n",$c,$c+$v[1]-1,$v[0]);
2443
    			$nbr++;
2444
    		}
2445
    		else
2446
    		{
2447
    			$chars .= sprintf("<%02X> <%04X>\n",$c,$v);
2448
    			$nbc++;
2449
    		}
2450
    	}
2451
    	$s = "/CIDInit /ProcSet findresource begin\n";
2452
    	$s .= "12 dict begin\n";
2453
    	$s .= "begincmap\n";
2454
    	$s .= "/CIDSystemInfo\n";
2455
    	$s .= "<</Registry (Adobe)\n";
2456
    	$s .= "/Ordering (UCS)\n";
2457
    	$s .= "/Supplement 0\n";
2458
    	$s .= ">> def\n";
2459
    	$s .= "/CMapName /Adobe-Identity-UCS def\n";
2460
    	$s .= "/CMapType 2 def\n";
2461
    	$s .= "1 begincodespacerange\n";
2462
    	$s .= "<00> <FF>\n";
2463
    	$s .= "endcodespacerange\n";
2464
    	if($nbr>0)
2465
    	{
2466
    		$s .= "$nbr beginbfrange\n";
2467
    		$s .= $ranges;
2468
    		$s .= "endbfrange\n";
2469
    	}
2470
    	if($nbc>0)
2471
    	{
2472
    		$s .= "$nbc beginbfchar\n";
2473
    		$s .= $chars;
2474
    		$s .= "endbfchar\n";
2475
    	}
2476
    	$s .= "endcmap\n";
2477
    	$s .= "CMapName currentdict /CMap defineresource pop\n";
2478
    	$s .= "end\n";
2479
    	$s .= "end";
2480
    	return $s;
2481
    }
2482
    
2483
    /**
2484
     * Add all containing images to the document.
2485
     */
2486
    protected function _putimages() : void
2487
    {
2488
    	foreach(array_keys($this->images) as $file)
2489
    	{
2490
    		$this->_putimage($this->images[$file]);
2491
    		unset($this->images[$file]['data']);
2492
    		unset($this->images[$file]['smask']);
2493
    	}
2494
    }
2495
    
2496
    /**
2497
     * Add image to the document.
2498
     * @param array $info
2499
     */
2500
    protected function _putimage(array &$info) : void
2501
    {
2502
    	$this->_newobj();
2503
    	$info['n'] = $this->n;
2504
    	$this->_put('<</Type /XObject');
2505
    	$this->_put('/Subtype /Image');
2506
    	$this->_put('/Width ' . $info['w']);
2507
    	$this->_put('/Height ' . $info['h']);
2508
    	if ($info['cs'] == 'Indexed') {
2509
    		$this->_put('/ColorSpace [/Indexed /DeviceRGB ' . (strlen($info['pal']) / 3 - 1) . ' ' . ($this->n + 1) . ' 0 R]');
2510
    	} else {
2511
    		$this->_put('/ColorSpace /' . $info['cs']);
2512
    		if($info['cs']=='DeviceCMYK') {
2513
    			$this->_put('/Decode [1 0 1 0 1 0 1 0]');
2514
    		}
2515
    	}
2516
    	$this->_put('/BitsPerComponent ' . $info['bpc']);
2517
    	if (isset($info['f'])) {
2518
    		$this->_put('/Filter /' . $info['f']);
2519
    	}
2520
    	if (isset($info['dp'])) {
2521
    		$this->_put('/DecodeParms <<' . $info['dp'] . '>>');
2522
    	}
2523
    	if (isset($info['trns']) && is_array($info['trns']))	{
2524
    		$trns = '';
2525
    		$cnt = count($info['trns']);
2526
    		for ($i = 0; $i < $cnt; $i++) {
2527
    			$trns .= $info['trns'][$i] . ' ' . $info['trns'][$i] . ' ';
2528
    		}
2529
    		$this->_put('/Mask [' . $trns . ']');
2530
    	}
2531
    	if (isset($info['smask'])) {
2532
    		$this->_put('/SMask ' . ($this->n+1) . ' 0 R');
2533
    	}
2534
    	$this->_put('/Length ' . strlen($info['data']) . '>>');
2535
    	$this->_putstream($info['data']);
2536
    	$this->_put('endobj');
2537
    	// Soft mask
2538
    	if (isset($info['smask'])) {
2539
    		$dp = '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns ' . $info['w'];
2540
    		$smask = array(
2541
    		    'w'=>$info['w'], 
2542
    		    'h'=>$info['h'], 
2543
    		    'cs'=>'DeviceGray', 
2544
    		    'bpc'=>8, 
2545
    		    'f'=>$info['f'], 
2546
    		    'dp'=>$dp, 
2547
    		    'data'=>$info['smask']
2548
    		);
2549
    		$this->_putimage($smask);
2550
    	}
2551
    	// Palette
2552
    	if ($info['cs'] == 'Indexed') {
2553
    		$this->_putstreamobject($info['pal']);
2554
    	}
2555
    }
2556
    
2557
    /**
2558
     * 
2559
     */
2560
    protected function _putxobjectdict() : void
2561
    {
2562
        foreach ($this->images as $image) {
2563
    		$this->_put('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R');
2564
        }
2565
    }
2566
    
2567
    /**
2568
     * 
2569
     */
2570
    protected function _putresourcedict() : void
2571
    {
2572
    	$this->_put('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
2573
    	$this->_put('/Font <<');
2574
    	foreach ($this->fonts as $font) {
2575
    		$this->_put('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R');
2576
    	}
2577
    	$this->_put('>>');
2578
    	$this->_put('/XObject <<');
2579
    	$this->_putxobjectdict();
2580
    	$this->_put('>>');
2581
    }
2582
    
2583
    /**
2584
     * 
2585
     */
2586
    protected function _putresources() : void
2587
    {
2588
    	$this->_putfonts();
2589
    	$this->_putimages();
2590
    	// Resource dictionary
2591
    	$this->_newobj(2);
2592
    	$this->_put('<<');
2593
    	$this->_putresourcedict();
2594
    	$this->_put('>>');
2595
    	$this->_put('endobj');
2596
    }
2597
    
2598
    /**
2599
     * 
2600
     */
2601
    protected function _putinfo() : void
2602
    {
2603
    	$this->metadata['Producer'] = 'FPDF '.FPDF_VERSION;
2604
    	$this->metadata['CreationDate'] = 'D:' . @date('YmdHis');
2605
    	foreach ($this->metadata as $key=>$value) {
2606
    		$this->_put('/' . $key . ' ' . $this->_textstring($value));
2607
    	}
2608
    }
2609
    
2610
    /**
2611
     * 
2612
     */
2613
    protected function _putcatalog() : void
2614
    {
2615
    	$n = $this->PageInfo[1]['n'];
2616
    	$this->_put('/Type /Catalog');
2617
    	$this->_put('/Pages 1 0 R');
2618
    	if($this->ZoomMode=='fullpage') {
2619
    		$this->_put('/OpenAction [' . $n . ' 0 R /Fit]');
2620
    	} elseif ($this->ZoomMode=='fullwidth') {
2621
    		$this->_put('/OpenAction [' . $n . ' 0 R /FitH null]');
2622
    	} elseif($this->ZoomMode=='real') {
2623
    		$this->_put('/OpenAction [' . $n . ' 0 R /XYZ null null 1]');
2624
    	} elseif(!is_string($this->ZoomMode)) {
2625
    		$this->_put('/OpenAction [' . $n . ' 0 R /XYZ null null ' . sprintf('%.2F', $this->ZoomMode / 100) . ']');
2626
    	}
2627
    	if($this->LayoutMode=='single') {
2628
    		$this->_put('/PageLayout /SinglePage');
2629
    	} elseif($this->LayoutMode=='continuous') {
2630
    		$this->_put('/PageLayout /OneColumn');
2631
    	} elseif($this->LayoutMode=='two') {
2632
    		$this->_put('/PageLayout /TwoColumnLeft');
2633
    	}
2634
    }
2635
    
2636
    /**
2637
     * 
2638
     */
2639
    protected function _putheader() : void
2640
    {
2641
    	$this->_put('%PDF-' . $this->PDFVersion);
2642
    }
2643
    
2644
    /**
2645
     * 
2646
     */
2647
    protected function _puttrailer() : void
2648
    {
2649
    	$this->_put('/Size ' . ($this->n + 1));
2650
    	$this->_put('/Root ' . $this->n . ' 0 R');
2651
    	$this->_put('/Info ' . ($this->n - 1) . ' 0 R');
2652
    }
2653
    
2654
    /**
2655
     * 
2656
     */
2657
    protected function _enddoc() : void
2658
    {
2659
    	$this->_putheader();
2660
    	$this->_putpages();
2661
    	$this->_putresources();
2662
    	// Info
2663
    	$this->_newobj();
2664
    	$this->_put('<<');
2665
    	$this->_putinfo();
2666
    	$this->_put('>>');
2667
    	$this->_put('endobj');
2668
    	// Catalog
2669
    	$this->_newobj();
2670
    	$this->_put('<<');
2671
    	$this->_putcatalog();
2672
    	$this->_put('>>');
2673
    	$this->_put('endobj');
2674
    	// Cross-ref
2675
    	$offset = $this->_getoffset();
2676
    	$this->_put('xref');
2677
    	$this->_put('0 ' . ($this->n + 1));
2678
    	$this->_put('0000000000 65535 f ');
2679
    	for ($i = 1; $i <= $this->n; $i++) {
2680
    		$this->_put(sprintf('%010d 00000 n ', $this->offsets[$i]));
2681
    	}
2682
    	// Trailer
2683
    	$this->_put('trailer');
2684
    	$this->_put('<<');
2685
    	$this->_puttrailer();
2686
    	$this->_put('>>');
2687
    	$this->_put('startxref');
2688
    	$this->_put(strval($offset));
2689
    	$this->_put('%%EOF');
2690
    	$this->state = 3;
2691
    }
2692
}
2693