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

FPDF::doChecks()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
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