Completed
Push — master ( ec35c3...f0cf26 )
by Julito
32:40 queued 21:23
created

FPDF::AliasNbPages()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/*******************************************************************************
3
* FPDF                                                                         *
4
*                                                                              *
5
* Version: 1.81                                                                *
6
* Date:    2015-12-20                                                          *
7
* Author:  Olivier PLATHEY                                                     *
8
*******************************************************************************/
9
10
define('FPDF_VERSION', '1.81');
11
12
class FPDF
13
{
14
    protected $page;               // current page number
15
protected $n;                  // current object number
16
protected $offsets;            // array of object offsets
17
protected $buffer;             // buffer holding in-memory PDF
18
protected $pages;              // array containing pages
19
protected $state;              // current document state
20
protected $compress;           // compression flag
21
protected $k;                  // scale factor (number of points in user unit)
22
protected $DefOrientation;     // default orientation
23
protected $CurOrientation;     // current orientation
24
protected $StdPageSizes;       // standard page sizes
25
protected $DefPageSize;        // default page size
26
protected $CurPageSize;        // current page size
27
protected $CurRotation;        // current page rotation
28
protected $PageInfo;           // page-related data
29
protected $wPt;
30
    protected $hPt;          // dimensions of current page in points
31
    protected $w;
32
    protected $h;              // dimensions of current page in user unit
33
protected $lMargin;            // left margin
34
protected $tMargin;            // top margin
35
protected $rMargin;            // right margin
36
protected $bMargin;            // page break margin
37
protected $cMargin;            // cell margin
38
protected $x;
39
    protected $y;              // current position in user unit
40
protected $lasth;              // height of last printed cell
41
protected $LineWidth;          // line width in user unit
42
protected $fontpath;           // path containing fonts
43
protected $CoreFonts;          // array of core font names
44
protected $fonts;              // array of used fonts
45
protected $FontFiles;          // array of font files
46
protected $encodings;          // array of encodings
47
protected $cmaps;              // array of ToUnicode CMaps
48
protected $FontFamily;         // current font family
49
protected $FontStyle;          // current font style
50
protected $underline;          // underlining flag
51
protected $CurrentFont;        // current font info
52
protected $FontSizePt;         // current font size in points
53
protected $FontSize;           // current font size in user unit
54
protected $DrawColor;          // commands for drawing color
55
protected $FillColor;          // commands for filling color
56
protected $TextColor;          // commands for text color
57
protected $ColorFlag;          // indicates whether fill and text colors are different
58
protected $WithAlpha;          // indicates whether alpha channel is used
59
protected $ws;                 // word spacing
60
protected $images;             // array of used images
61
protected $PageLinks;          // array of links in pages
62
protected $links;              // array of internal links
63
protected $AutoPageBreak;      // automatic page breaking
64
protected $PageBreakTrigger;   // threshold used to trigger page breaks
65
protected $InHeader;           // flag set when processing header
66
protected $InFooter;           // flag set when processing footer
67
protected $AliasNbPages;       // alias for total number of pages
68
protected $ZoomMode;           // zoom display mode
69
protected $LayoutMode;         // layout display mode
70
protected $metadata;           // document properties
71
protected $PDFVersion;         // PDF version number
72
73
/*******************************************************************************
74
*                               Public methods                                 *
75
*******************************************************************************/
76
77
    public function __construct($orientation='P', $unit='mm', $size='A4')
78
    {
79
        // Some checks
80
        $this->_dochecks();
81
        // Initialization of properties
82
        $this->state = 0;
83
        $this->page = 0;
84
        $this->n = 2;
85
        $this->buffer = '';
86
        $this->pages = [];
87
        $this->PageInfo = [];
88
        $this->fonts = [];
89
        $this->FontFiles = [];
90
        $this->encodings = [];
91
        $this->cmaps = [];
92
        $this->images = [];
93
        $this->links = [];
94
        $this->InHeader = false;
95
        $this->InFooter = false;
96
        $this->lasth = 0;
97
        $this->FontFamily = '';
98
        $this->FontStyle = '';
99
        $this->FontSizePt = 12;
100
        $this->underline = false;
101
        $this->DrawColor = '0 G';
102
        $this->FillColor = '0 g';
103
        $this->TextColor = '0 g';
104
        $this->ColorFlag = false;
105
        $this->WithAlpha = false;
106
        $this->ws = 0;
107
        // Font path
108
        if (defined('FPDF_FONTPATH')) {
109
            $this->fontpath = FPDF_FONTPATH;
110
            if (substr($this->fontpath, -1)!='/' && substr($this->fontpath, -1)!='\\') {
111
                $this->fontpath .= '/';
112
            }
113
        } elseif (is_dir(dirname(__FILE__).'/font')) {
114
            $this->fontpath = dirname(__FILE__).'/font/';
115
        } else {
116
            $this->fontpath = '';
117
        }
118
        // Core fonts
119
        $this->CoreFonts = ['courier', 'helvetica', 'times', 'symbol', 'zapfdingbats'];
120
        // Scale factor
121
        if ($unit=='pt') {
122
            $this->k = 1;
123
        } elseif ($unit=='mm') {
124
            $this->k = 72/25.4;
125
        } elseif ($unit=='cm') {
126
            $this->k = 72/2.54;
127
        } elseif ($unit=='in') {
128
            $this->k = 72;
129
        } else {
130
            $this->Error('Incorrect unit: '.$unit);
131
        }
132
        // Page sizes
133
        $this->StdPageSizes = ['a3'=>[841.89,1190.55], 'a4'=>[595.28,841.89], 'a5'=>[420.94,595.28],
134
        'letter'=>[612,792], 'legal'=>[612,1008]];
135
        $size = $this->_getpagesize($size);
136
        $this->DefPageSize = $size;
137
        $this->CurPageSize = $size;
138
        // Page orientation
139
        $orientation = strtolower($orientation);
140
        if ($orientation=='p' || $orientation=='portrait') {
141
            $this->DefOrientation = 'P';
142
            $this->w = $size[0];
143
            $this->h = $size[1];
144
        } elseif ($orientation=='l' || $orientation=='landscape') {
145
            $this->DefOrientation = 'L';
146
            $this->w = $size[1];
147
            $this->h = $size[0];
148
        } else {
149
            $this->Error('Incorrect orientation: '.$orientation);
150
        }
151
        $this->CurOrientation = $this->DefOrientation;
152
        $this->wPt = $this->w*$this->k;
153
        $this->hPt = $this->h*$this->k;
154
        // Page rotation
155
        $this->CurRotation = 0;
156
        // Page margins (1 cm)
157
        $margin = 28.35/$this->k;
158
        $this->SetMargins($margin, $margin);
159
        // Interior cell margin (1 mm)
160
        $this->cMargin = $margin/10;
161
        // Line width (0.2 mm)
162
        $this->LineWidth = .567/$this->k;
163
        // Automatic page break
164
        $this->SetAutoPageBreak(true, 2*$margin);
165
        // Default display mode
166
        $this->SetDisplayMode('default');
167
        // Enable compression
168
        $this->SetCompression(true);
169
        // Set default PDF version number
170
        $this->PDFVersion = '1.3';
171
    }
172
173
    public function SetMargins($left, $top, $right=null)
174
    {
175
        // Set left, top and right margins
176
        $this->lMargin = $left;
177
        $this->tMargin = $top;
178
        if ($right===null) {
179
            $right = $left;
180
        }
181
        $this->rMargin = $right;
182
    }
183
184
    public function SetLeftMargin($margin)
185
    {
186
        // Set left margin
187
        $this->lMargin = $margin;
188
        if ($this->page>0 && $this->x<$margin) {
189
            $this->x = $margin;
190
        }
191
    }
192
193
    public function SetTopMargin($margin)
194
    {
195
        // Set top margin
196
        $this->tMargin = $margin;
197
    }
198
199
    public function SetRightMargin($margin)
200
    {
201
        // Set right margin
202
        $this->rMargin = $margin;
203
    }
204
205
    public function SetAutoPageBreak($auto, $margin=0)
206
    {
207
        // Set auto page break mode and triggering margin
208
        $this->AutoPageBreak = $auto;
209
        $this->bMargin = $margin;
210
        $this->PageBreakTrigger = $this->h-$margin;
211
    }
212
213
    public function SetDisplayMode($zoom, $layout='default')
214
    {
215
        // Set display mode in viewer
216
        if ($zoom=='fullpage' || $zoom=='fullwidth' || $zoom=='real' || $zoom=='default' || !is_string($zoom)) {
217
            $this->ZoomMode = $zoom;
218
        } else {
219
            $this->Error('Incorrect zoom display mode: '.$zoom);
220
        }
221
        if ($layout=='single' || $layout=='continuous' || $layout=='two' || $layout=='default') {
222
            $this->LayoutMode = $layout;
223
        } else {
224
            $this->Error('Incorrect layout display mode: '.$layout);
225
        }
226
    }
227
228
    public function SetCompression($compress)
229
    {
230
        // Set page compression
231
        if (function_exists('gzcompress')) {
232
            $this->compress = $compress;
233
        } else {
234
            $this->compress = false;
235
        }
236
    }
237
238
    public function SetTitle($title, $isUTF8=false)
239
    {
240
        // Title of document
241
        $this->metadata['Title'] = $isUTF8 ? $title : utf8_encode($title);
242
    }
243
244
    public function SetAuthor($author, $isUTF8=false)
245
    {
246
        // Author of document
247
        $this->metadata['Author'] = $isUTF8 ? $author : utf8_encode($author);
248
    }
249
250
    public function SetSubject($subject, $isUTF8=false)
251
    {
252
        // Subject of document
253
        $this->metadata['Subject'] = $isUTF8 ? $subject : utf8_encode($subject);
254
    }
255
256
    public function SetKeywords($keywords, $isUTF8=false)
257
    {
258
        // Keywords of document
259
        $this->metadata['Keywords'] = $isUTF8 ? $keywords : utf8_encode($keywords);
260
    }
261
262
    public function SetCreator($creator, $isUTF8=false)
263
    {
264
        // Creator of document
265
        $this->metadata['Creator'] = $isUTF8 ? $creator : utf8_encode($creator);
266
    }
267
268
    public function AliasNbPages($alias='{nb}')
269
    {
270
        // Define an alias for total number of pages
271
        $this->AliasNbPages = $alias;
272
    }
273
274
    public function Error($msg)
275
    {
276
        // Fatal error
277
        throw new Exception('FPDF error: '.$msg);
278
    }
279
280
    public function Close()
281
    {
282
        // Terminate document
283
        if ($this->state==3) {
284
            return;
285
        }
286
        if ($this->page==0) {
287
            $this->AddPage();
288
        }
289
        // Page footer
290
        $this->InFooter = true;
291
        $this->Footer();
292
        $this->InFooter = false;
293
        // Close page
294
        $this->_endpage();
295
        // Close document
296
        $this->_enddoc();
297
    }
298
299
    public function AddPage($orientation='', $size='', $rotation=0)
300
    {
301
        // Start a new page
302
        if ($this->state==3) {
303
            $this->Error('The document is closed');
304
        }
305
        $family = $this->FontFamily;
306
        $style = $this->FontStyle.($this->underline ? 'U' : '');
307
        $fontsize = $this->FontSizePt;
308
        $lw = $this->LineWidth;
309
        $dc = $this->DrawColor;
310
        $fc = $this->FillColor;
311
        $tc = $this->TextColor;
312
        $cf = $this->ColorFlag;
313
        if ($this->page>0) {
314
            // Page footer
315
            $this->InFooter = true;
316
            $this->Footer();
317
            $this->InFooter = false;
318
            // Close page
319
            $this->_endpage();
320
        }
321
        // Start new page
322
        $this->_beginpage($orientation, $size, $rotation);
323
        // Set line cap style to square
324
        $this->_out('2 J');
325
        // Set line width
326
        $this->LineWidth = $lw;
327
        $this->_out(sprintf('%.2F w', $lw*$this->k));
328
        // Set font
329
        if ($family) {
330
            $this->SetFont($family, $style, $fontsize);
331
        }
332
        // Set colors
333
        $this->DrawColor = $dc;
334
        if ($dc!='0 G') {
335
            $this->_out($dc);
336
        }
337
        $this->FillColor = $fc;
338
        if ($fc!='0 g') {
339
            $this->_out($fc);
340
        }
341
        $this->TextColor = $tc;
342
        $this->ColorFlag = $cf;
343
        // Page header
344
        $this->InHeader = true;
345
        $this->Header();
346
        $this->InHeader = false;
347
        // Restore line width
348
        if ($this->LineWidth!=$lw) {
349
            $this->LineWidth = $lw;
350
            $this->_out(sprintf('%.2F w', $lw*$this->k));
351
        }
352
        // Restore font
353
        if ($family) {
354
            $this->SetFont($family, $style, $fontsize);
355
        }
356
        // Restore colors
357
        if ($this->DrawColor!=$dc) {
358
            $this->DrawColor = $dc;
359
            $this->_out($dc);
360
        }
361
        if ($this->FillColor!=$fc) {
362
            $this->FillColor = $fc;
363
            $this->_out($fc);
364
        }
365
        $this->TextColor = $tc;
366
        $this->ColorFlag = $cf;
367
    }
368
369
    public function Header()
370
    {
371
        // To be implemented in your own inherited class
372
    }
373
374
    public function Footer()
375
    {
376
        // To be implemented in your own inherited class
377
    }
378
379
    public function PageNo()
380
    {
381
        // Get current page number
382
        return $this->page;
383
    }
384
385
    public function SetDrawColor($r, $g=null, $b=null)
386
    {
387
        // Set color for all stroking operations
388
        if (($r==0 && $g==0 && $b==0) || $g===null) {
389
            $this->DrawColor = sprintf('%.3F G', $r/255);
390
        } else {
391
            $this->DrawColor = sprintf('%.3F %.3F %.3F RG', $r/255, $g/255, $b/255);
392
        }
393
        if ($this->page>0) {
394
            $this->_out($this->DrawColor);
395
        }
396
    }
397
398
    public function SetFillColor($r, $g=null, $b=null)
399
    {
400
        // Set color for all filling operations
401
        if (($r==0 && $g==0 && $b==0) || $g===null) {
402
            $this->FillColor = sprintf('%.3F g', $r/255);
403
        } else {
404
            $this->FillColor = sprintf('%.3F %.3F %.3F rg', $r/255, $g/255, $b/255);
405
        }
406
        $this->ColorFlag = ($this->FillColor!=$this->TextColor);
407
        if ($this->page>0) {
408
            $this->_out($this->FillColor);
409
        }
410
    }
411
412
    public function SetTextColor($r, $g=null, $b=null)
413
    {
414
        // Set color for text
415
        if (($r==0 && $g==0 && $b==0) || $g===null) {
416
            $this->TextColor = sprintf('%.3F g', $r/255);
417
        } else {
418
            $this->TextColor = sprintf('%.3F %.3F %.3F rg', $r/255, $g/255, $b/255);
419
        }
420
        $this->ColorFlag = ($this->FillColor!=$this->TextColor);
421
    }
422
423
    public function GetStringWidth($s)
424
    {
425
        // Get width of a string in the current font
426
        $s = (string)$s;
427
        $cw = &$this->CurrentFont['cw'];
428
        $w = 0;
429
        $l = strlen($s);
430
        for ($i=0;$i<$l;$i++) {
431
            $w += $cw[$s[$i]];
432
        }
433
        return $w*$this->FontSize/1000;
434
    }
435
436
    public function SetLineWidth($width)
437
    {
438
        // Set line width
439
        $this->LineWidth = $width;
440
        if ($this->page>0) {
441
            $this->_out(sprintf('%.2F w', $width*$this->k));
442
        }
443
    }
444
445
    public function Line($x1, $y1, $x2, $y2)
446
    {
447
        // Draw a line
448
        $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));
449
    }
450
451
    public function Rect($x, $y, $w, $h, $style='')
452
    {
453
        // Draw a rectangle
454
        if ($style=='F') {
455
            $op = 'f';
456
        } elseif ($style=='FD' || $style=='DF') {
457
            $op = 'B';
458
        } else {
459
            $op = 'S';
460
        }
461
        $this->_out(sprintf('%.2F %.2F %.2F %.2F re %s', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k, $op));
462
    }
463
464
    public function AddFont($family, $style='', $file='')
465
    {
466
        // Add a TrueType, OpenType or Type1 font
467
        $family = strtolower($family);
468
        if ($file=='') {
469
            $file = str_replace(' ', '', $family).strtolower($style).'.php';
470
        }
471
        $style = strtoupper($style);
472
        if ($style=='IB') {
473
            $style = 'BI';
474
        }
475
        $fontkey = $family.$style;
476
        if (isset($this->fonts[$fontkey])) {
477
            return;
478
        }
479
        $info = $this->_loadfont($file);
480
        $info['i'] = count($this->fonts)+1;
481
        if (!empty($info['file'])) {
482
            // Embedded font
483
            if ($info['type']=='TrueType') {
484
                $this->FontFiles[$info['file']] = ['length1'=>$info['originalsize']];
485
            } else {
486
                $this->FontFiles[$info['file']] = ['length1'=>$info['size1'], 'length2'=>$info['size2']];
487
            }
488
        }
489
        $this->fonts[$fontkey] = $info;
490
    }
491
492
    public function SetFont($family, $style='', $size=0)
493
    {
494
        // Select a font; size given in points
495
        if ($family=='') {
496
            $family = $this->FontFamily;
497
        } else {
498
            $family = strtolower($family);
499
        }
500
        $style = strtoupper($style);
501
        if (strpos($style, 'U')!==false) {
502
            $this->underline = true;
503
            $style = str_replace('U', '', $style);
504
        } else {
505
            $this->underline = false;
506
        }
507
        if ($style=='IB') {
508
            $style = 'BI';
509
        }
510
        if ($size==0) {
511
            $size = $this->FontSizePt;
512
        }
513
        // Test if font is already selected
514
        if ($this->FontFamily==$family && $this->FontStyle==$style && $this->FontSizePt==$size) {
515
            return;
516
        }
517
        // Test if font is already loaded
518
        $fontkey = $family.$style;
519
        if (!isset($this->fonts[$fontkey])) {
520
            // Test if one of the core fonts
521
            if ($family=='arial') {
522
                $family = 'helvetica';
523
            }
524
            if (in_array($family, $this->CoreFonts)) {
525
                if ($family=='symbol' || $family=='zapfdingbats') {
526
                    $style = '';
527
                }
528
                $fontkey = $family.$style;
529
                if (!isset($this->fonts[$fontkey])) {
530
                    $this->AddFont($family, $style);
531
                }
532
            } else {
533
                $this->Error('Undefined font: '.$family.' '.$style);
534
            }
535
        }
536
        // Select it
537
        $this->FontFamily = $family;
538
        $this->FontStyle = $style;
539
        $this->FontSizePt = $size;
540
        $this->FontSize = $size/$this->k;
541
        $this->CurrentFont = &$this->fonts[$fontkey];
542
        if ($this->page>0) {
543
            $this->_out(sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
544
        }
545
    }
546
547
    public function SetFontSize($size)
548
    {
549
        // Set font size in points
550
        if ($this->FontSizePt==$size) {
551
            return;
552
        }
553
        $this->FontSizePt = $size;
554
        $this->FontSize = $size/$this->k;
555
        if ($this->page>0) {
556
            $this->_out(sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
557
        }
558
    }
559
560
    public function AddLink()
561
    {
562
        // Create a new internal link
563
        $n = count($this->links)+1;
564
        $this->links[$n] = [0, 0];
565
        return $n;
566
    }
567
568
    public function SetLink($link, $y=0, $page=-1)
569
    {
570
        // Set destination of internal link
571
        if ($y==-1) {
572
            $y = $this->y;
573
        }
574
        if ($page==-1) {
575
            $page = $this->page;
576
        }
577
        $this->links[$link] = [$page, $y];
578
    }
579
580
    public function Link($x, $y, $w, $h, $link)
581
    {
582
        // Put a link on the page
583
        $this->PageLinks[$this->page][] = [$x*$this->k, $this->hPt-$y*$this->k, $w*$this->k, $h*$this->k, $link];
584
    }
585
586
    public function Text($x, $y, $txt)
587
    {
588
        // Output a string
589
        if (!isset($this->CurrentFont)) {
590
            $this->Error('No font has been set');
591
        }
592
        $s = sprintf('BT %.2F %.2F Td (%s) Tj ET', $x*$this->k, ($this->h-$y)*$this->k, $this->_escape($txt));
593
        if ($this->underline && $txt!='') {
594
            $s .= ' '.$this->_dounderline($x, $y, $txt);
595
        }
596
        if ($this->ColorFlag) {
597
            $s = 'q '.$this->TextColor.' '.$s.' Q';
598
        }
599
        $this->_out($s);
600
    }
601
602
    public function AcceptPageBreak()
603
    {
604
        // Accept automatic page break or not
605
        return $this->AutoPageBreak;
606
    }
607
608
    public function Cell($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='')
609
    {
610
        // Output a cell
611
        $k = $this->k;
612
        if ($this->y+$h>$this->PageBreakTrigger && !$this->InHeader && !$this->InFooter && $this->AcceptPageBreak()) {
613
            // Automatic page break
614
            $x = $this->x;
615
            $ws = $this->ws;
616
            if ($ws>0) {
617
                $this->ws = 0;
618
                $this->_out('0 Tw');
619
            }
620
            $this->AddPage($this->CurOrientation, $this->CurPageSize, $this->CurRotation);
621
            $this->x = $x;
622
            if ($ws>0) {
623
                $this->ws = $ws;
624
                $this->_out(sprintf('%.3F Tw', $ws*$k));
625
            }
626
        }
627
        if ($w==0) {
628
            $w = $this->w-$this->rMargin-$this->x;
629
        }
630
        $s = '';
631
        if ($fill || $border==1) {
632
            if ($fill) {
633
                $op = ($border==1) ? 'B' : 'f';
634
            } else {
635
                $op = 'S';
636
            }
637
            $s = sprintf('%.2F %.2F %.2F %.2F re %s ', $this->x*$k, ($this->h-$this->y)*$k, $w*$k, -$h*$k, $op);
638
        }
639
        if (is_string($border)) {
640
            $x = $this->x;
641
            $y = $this->y;
642
            if (strpos($border, 'L')!==false) {
643
                $s .= sprintf('%.2F %.2F m %.2F %.2F l S ', $x*$k, ($this->h-$y)*$k, $x*$k, ($this->h-($y+$h))*$k);
644
            }
645
            if (strpos($border, 'T')!==false) {
646
                $s .= sprintf('%.2F %.2F m %.2F %.2F l S ', $x*$k, ($this->h-$y)*$k, ($x+$w)*$k, ($this->h-$y)*$k);
647
            }
648
            if (strpos($border, 'R')!==false) {
649
                $s .= sprintf('%.2F %.2F m %.2F %.2F l S ', ($x+$w)*$k, ($this->h-$y)*$k, ($x+$w)*$k, ($this->h-($y+$h))*$k);
650
            }
651
            if (strpos($border, 'B')!==false) {
652
                $s .= sprintf('%.2F %.2F m %.2F %.2F l S ', $x*$k, ($this->h-($y+$h))*$k, ($x+$w)*$k, ($this->h-($y+$h))*$k);
653
            }
654
        }
655
        if ($txt!=='') {
656
            if (!isset($this->CurrentFont)) {
657
                $this->Error('No font has been set');
658
            }
659
            if ($align=='R') {
660
                $dx = $w-$this->cMargin-$this->GetStringWidth($txt);
661
            } elseif ($align=='C') {
662
                $dx = ($w-$this->GetStringWidth($txt))/2;
663
            } else {
664
                $dx = $this->cMargin;
665
            }
666
            if ($this->ColorFlag) {
667
                $s .= 'q '.$this->TextColor.' ';
668
            }
669
            $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));
670
            if ($this->underline) {
671
                $s .= ' '.$this->_dounderline($this->x+$dx, $this->y+.5*$h+.3*$this->FontSize, $txt);
672
            }
673
            if ($this->ColorFlag) {
674
                $s .= ' Q';
675
            }
676
            if ($link) {
677
                $this->Link($this->x+$dx, $this->y+.5*$h-.5*$this->FontSize, $this->GetStringWidth($txt), $this->FontSize, $link);
678
            }
679
        }
680
        if ($s) {
681
            $this->_out($s);
682
        }
683
        $this->lasth = $h;
684
        if ($ln>0) {
685
            // Go to next line
686
            $this->y += $h;
687
            if ($ln==1) {
688
                $this->x = $this->lMargin;
689
            }
690
        } else {
691
            $this->x += $w;
692
        }
693
    }
694
695
    public function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false)
696
    {
697
        // Output text with automatic or explicit line breaks
698
        if (!isset($this->CurrentFont)) {
699
            $this->Error('No font has been set');
700
        }
701
        $cw = &$this->CurrentFont['cw'];
702
        if ($w==0) {
703
            $w = $this->w-$this->rMargin-$this->x;
704
        }
705
        $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize;
706
        $s = str_replace("\r", '', $txt);
707
        $nb = strlen($s);
708
        if ($nb>0 && $s[$nb-1]=="\n") {
709
            $nb--;
710
        }
711
        $b = 0;
712
        if ($border) {
713
            if ($border==1) {
714
                $border = 'LTRB';
715
                $b = 'LRT';
716
                $b2 = 'LR';
717
            } else {
718
                $b2 = '';
719
                if (strpos($border, 'L')!==false) {
720
                    $b2 .= 'L';
721
                }
722
                if (strpos($border, 'R')!==false) {
723
                    $b2 .= 'R';
724
                }
725
                $b = (strpos($border, 'T')!==false) ? $b2.'T' : $b2;
726
            }
727
        }
728
        $sep = -1;
729
        $i = 0;
730
        $j = 0;
731
        $l = 0;
732
        $ns = 0;
733
        $nl = 1;
734
        while ($i<$nb) {
735
            // Get next character
736
            $c = $s[$i];
737
            if ($c=="\n") {
738
                // Explicit line break
739
                if ($this->ws>0) {
740
                    $this->ws = 0;
741
                    $this->_out('0 Tw');
742
                }
743
                $this->Cell($w, $h, substr($s, $j, $i-$j), $b, 2, $align, $fill);
744
                $i++;
745
                $sep = -1;
746
                $j = $i;
747
                $l = 0;
748
                $ns = 0;
749
                $nl++;
750
                if ($border && $nl==2) {
751
                    $b = $b2;
752
                }
753
                continue;
754
            }
755
            if ($c==' ') {
756
                $sep = $i;
757
                $ls = $l;
758
                $ns++;
759
            }
760
            $l += $cw[$c];
761
            if ($l>$wmax) {
762
                // Automatic line break
763
                if ($sep==-1) {
764
                    if ($i==$j) {
765
                        $i++;
766
                    }
767
                    if ($this->ws>0) {
768
                        $this->ws = 0;
769
                        $this->_out('0 Tw');
770
                    }
771
                    $this->Cell($w, $h, substr($s, $j, $i-$j), $b, 2, $align, $fill);
772
                } else {
773
                    if ($align=='J') {
774
                        $this->ws = ($ns>1) ? ($wmax-$ls)/1000*$this->FontSize/($ns-1) : 0;
775
                        $this->_out(sprintf('%.3F Tw', $this->ws*$this->k));
776
                    }
777
                    $this->Cell($w, $h, substr($s, $j, $sep-$j), $b, 2, $align, $fill);
778
                    $i = $sep+1;
779
                }
780
                $sep = -1;
781
                $j = $i;
782
                $l = 0;
783
                $ns = 0;
784
                $nl++;
785
                if ($border && $nl==2) {
786
                    $b = $b2;
787
                }
788
            } else {
789
                $i++;
790
            }
791
        }
792
        // Last chunk
793
        if ($this->ws>0) {
794
            $this->ws = 0;
795
            $this->_out('0 Tw');
796
        }
797
        if ($border && strpos($border, 'B')!==false) {
798
            $b .= 'B';
799
        }
800
        $this->Cell($w, $h, substr($s, $j, $i-$j), $b, 2, $align, $fill);
801
        $this->x = $this->lMargin;
802
    }
803
804
    public function Write($h, $txt, $link='')
805
    {
806
        // Output text in flowing mode
807
        if (!isset($this->CurrentFont)) {
808
            $this->Error('No font has been set');
809
        }
810
        $cw = &$this->CurrentFont['cw'];
811
        $w = $this->w-$this->rMargin-$this->x;
812
        $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize;
813
        $s = str_replace("\r", '', $txt);
814
        $nb = strlen($s);
815
        $sep = -1;
816
        $i = 0;
817
        $j = 0;
818
        $l = 0;
819
        $nl = 1;
820
        while ($i<$nb) {
821
            // Get next character
822
            $c = $s[$i];
823
            if ($c=="\n") {
824
                // Explicit line break
825
                $this->Cell($w, $h, substr($s, $j, $i-$j), 0, 2, '', false, $link);
826
                $i++;
827
                $sep = -1;
828
                $j = $i;
829
                $l = 0;
830
                if ($nl==1) {
831
                    $this->x = $this->lMargin;
832
                    $w = $this->w-$this->rMargin-$this->x;
833
                    $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize;
834
                }
835
                $nl++;
836
                continue;
837
            }
838
            if ($c==' ') {
839
                $sep = $i;
840
            }
841
            $l += $cw[$c];
842
            if ($l>$wmax) {
843
                // Automatic line break
844
                if ($sep==-1) {
845
                    if ($this->x>$this->lMargin) {
846
                        // Move to next line
847
                        $this->x = $this->lMargin;
848
                        $this->y += $h;
849
                        $w = $this->w-$this->rMargin-$this->x;
850
                        $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize;
851
                        $i++;
852
                        $nl++;
853
                        continue;
854
                    }
855
                    if ($i==$j) {
856
                        $i++;
857
                    }
858
                    $this->Cell($w, $h, substr($s, $j, $i-$j), 0, 2, '', false, $link);
859
                } else {
860
                    $this->Cell($w, $h, substr($s, $j, $sep-$j), 0, 2, '', false, $link);
861
                    $i = $sep+1;
862
                }
863
                $sep = -1;
864
                $j = $i;
865
                $l = 0;
866
                if ($nl==1) {
867
                    $this->x = $this->lMargin;
868
                    $w = $this->w-$this->rMargin-$this->x;
869
                    $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize;
870
                }
871
                $nl++;
872
            } else {
873
                $i++;
874
            }
875
        }
876
        // Last chunk
877
        if ($i!=$j) {
878
            $this->Cell($l/1000*$this->FontSize, $h, substr($s, $j), 0, 0, '', false, $link);
879
        }
880
    }
881
882
    public function Ln($h=null)
883
    {
884
        // Line feed; default value is the last cell height
885
        $this->x = $this->lMargin;
886
        if ($h===null) {
887
            $this->y += $this->lasth;
888
        } else {
889
            $this->y += $h;
890
        }
891
    }
892
893
    public function Image($file, $x=null, $y=null, $w=0, $h=0, $type='', $link='')
894
    {
895
        // Put an image on the page
896
        if ($file=='') {
897
            $this->Error('Image file name is empty');
898
        }
899
        if (!isset($this->images[$file])) {
900
            // First use of this image, get info
901
            if ($type=='') {
902
                $pos = strrpos($file, '.');
903
                if (!$pos) {
904
                    $this->Error('Image file has no extension and no type was specified: '.$file);
905
                }
906
                $type = substr($file, $pos+1);
907
            }
908
            $type = strtolower($type);
909
            if ($type=='jpeg') {
910
                $type = 'jpg';
911
            }
912
            $mtd = '_parse'.$type;
913
            if (!method_exists($this, $mtd)) {
914
                $this->Error('Unsupported image type: '.$type);
915
            }
916
            $info = $this->$mtd($file);
917
            $info['i'] = count($this->images)+1;
918
            $this->images[$file] = $info;
919
        } else {
920
            $info = $this->images[$file];
921
        }
922
923
        // Automatic width and height calculation if needed
924
        if ($w==0 && $h==0) {
925
            // Put image at 96 dpi
926
            $w = -96;
927
            $h = -96;
928
        }
929
        if ($w<0) {
930
            $w = -$info['w']*72/$w/$this->k;
931
        }
932
        if ($h<0) {
933
            $h = -$info['h']*72/$h/$this->k;
934
        }
935
        if ($w==0) {
936
            $w = $h*$info['w']/$info['h'];
937
        }
938
        if ($h==0) {
939
            $h = $w*$info['h']/$info['w'];
940
        }
941
942
        // Flowing mode
943
        if ($y===null) {
944
            if ($this->y+$h>$this->PageBreakTrigger && !$this->InHeader && !$this->InFooter && $this->AcceptPageBreak()) {
945
                // Automatic page break
946
                $x2 = $this->x;
947
                $this->AddPage($this->CurOrientation, $this->CurPageSize, $this->CurRotation);
948
                $this->x = $x2;
949
            }
950
            $y = $this->y;
951
            $this->y += $h;
952
        }
953
954
        if ($x===null) {
955
            $x = $this->x;
956
        }
957
        $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']));
958
        if ($link) {
959
            $this->Link($x, $y, $w, $h, $link);
960
        }
961
    }
962
963
    public function GetPageWidth()
964
    {
965
        // Get current page width
966
        return $this->w;
967
    }
968
969
    public function GetPageHeight()
970
    {
971
        // Get current page height
972
        return $this->h;
973
    }
974
975
    public function GetX()
976
    {
977
        // Get x position
978
        return $this->x;
979
    }
980
981
    public function SetX($x)
982
    {
983
        // Set x position
984
        if ($x>=0) {
985
            $this->x = $x;
986
        } else {
987
            $this->x = $this->w+$x;
988
        }
989
    }
990
991
    public function GetY()
992
    {
993
        // Get y position
994
        return $this->y;
995
    }
996
997
    public function SetY($y, $resetX=true)
998
    {
999
        // Set y position and optionally reset x
1000
        if ($y>=0) {
1001
            $this->y = $y;
1002
        } else {
1003
            $this->y = $this->h+$y;
1004
        }
1005
        if ($resetX) {
1006
            $this->x = $this->lMargin;
1007
        }
1008
    }
1009
1010
    public function SetXY($x, $y)
1011
    {
1012
        // Set x and y positions
1013
        $this->SetX($x);
1014
        $this->SetY($y, false);
1015
    }
1016
1017
    public function Output($dest='', $name='', $isUTF8=false)
1018
    {
1019
        // Output PDF to some destination
1020
        $this->Close();
1021
        if (strlen($name)==1 && strlen($dest)!=1) {
1022
            // Fix parameter order
1023
            $tmp = $dest;
1024
            $dest = $name;
1025
            $name = $tmp;
1026
        }
1027
        if ($dest=='') {
1028
            $dest = 'I';
1029
        }
1030
        if ($name=='') {
1031
            $name = 'doc.pdf';
1032
        }
1033
        switch (strtoupper($dest)) {
1034
        case 'I':
1035
            // Send to standard output
1036
            $this->_checkoutput();
1037
            if (PHP_SAPI!='cli') {
1038
                // We send to a browser
1039
                header('Content-Type: application/pdf');
1040
                header('Content-Disposition: inline; '.$this->_httpencode('filename', $name, $isUTF8));
1041
                header('Cache-Control: private, max-age=0, must-revalidate');
1042
                header('Pragma: public');
1043
            }
1044
            echo $this->buffer;
1045
            break;
1046
        case 'D':
1047
            // Download file
1048
            $this->_checkoutput();
1049
            header('Content-Type: application/x-download');
1050
            header('Content-Disposition: attachment; '.$this->_httpencode('filename', $name, $isUTF8));
1051
            header('Cache-Control: private, max-age=0, must-revalidate');
1052
            header('Pragma: public');
1053
            echo $this->buffer;
1054
            break;
1055
        case 'F':
1056
            // Save to local file
1057
            if (!file_put_contents($name, $this->buffer)) {
1058
                $this->Error('Unable to create output file: '.$name);
1059
            }
1060
            break;
1061
        case 'S':
1062
            // Return as a string
1063
            return $this->buffer;
1064
        default:
1065
            $this->Error('Incorrect output destination: '.$dest);
1066
    }
1067
        return '';
1068
    }
1069
1070
    /*******************************************************************************
1071
    *                              Protected methods                               *
1072
    *******************************************************************************/
1073
1074
    protected function _dochecks()
1075
    {
1076
        // Check mbstring overloading
1077
        if (ini_get('mbstring.func_overload') & 2) {
1078
            $this->Error('mbstring overloading must be disabled');
1079
        }
1080
        // Ensure runtime magic quotes are disabled
1081
        if (get_magic_quotes_runtime()) {
1082
            @set_magic_quotes_runtime(0);
1083
        }
1084
    }
1085
1086
    protected function _checkoutput()
1087
    {
1088
        if (PHP_SAPI!='cli') {
1089
            if (headers_sent($file, $line)) {
1090
                $this->Error("Some data has already been output, can't send PDF file (output started at $file:$line)");
1091
            }
1092
        }
1093
        if (ob_get_length()) {
1094
            // The output buffer is not empty
1095
            if (preg_match('/^(\xEF\xBB\xBF)?\s*$/', ob_get_contents())) {
1096
                // It contains only a UTF-8 BOM and/or whitespace, let's clean it
1097
                ob_clean();
1098
            } else {
1099
                $this->Error("Some data has already been output, can't send PDF file");
1100
            }
1101
        }
1102
    }
1103
1104
    protected function _getpagesize($size)
1105
    {
1106
        if (is_string($size)) {
1107
            $size = strtolower($size);
1108
            if (!isset($this->StdPageSizes[$size])) {
1109
                $this->Error('Unknown page size: '.$size);
1110
            }
1111
            $a = $this->StdPageSizes[$size];
1112
            return [$a[0]/$this->k, $a[1]/$this->k];
1113
        } else {
1114
            if ($size[0]>$size[1]) {
1115
                return [$size[1], $size[0]];
1116
            } else {
1117
                return $size;
1118
            }
1119
        }
1120
    }
1121
1122
    protected function _beginpage($orientation, $size, $rotation)
1123
    {
1124
        $this->page++;
1125
        $this->pages[$this->page] = '';
1126
        $this->state = 2;
1127
        $this->x = $this->lMargin;
1128
        $this->y = $this->tMargin;
1129
        $this->FontFamily = '';
1130
        // Check page size and orientation
1131
        if ($orientation=='') {
1132
            $orientation = $this->DefOrientation;
1133
        } else {
1134
            $orientation = strtoupper($orientation[0]);
1135
        }
1136
        if ($size=='') {
1137
            $size = $this->DefPageSize;
1138
        } else {
1139
            $size = $this->_getpagesize($size);
1140
        }
1141
        if ($orientation!=$this->CurOrientation || $size[0]!=$this->CurPageSize[0] || $size[1]!=$this->CurPageSize[1]) {
1142
            // New size or orientation
1143
            if ($orientation=='P') {
1144
                $this->w = $size[0];
1145
                $this->h = $size[1];
1146
            } else {
1147
                $this->w = $size[1];
1148
                $this->h = $size[0];
1149
            }
1150
            $this->wPt = $this->w*$this->k;
1151
            $this->hPt = $this->h*$this->k;
1152
            $this->PageBreakTrigger = $this->h-$this->bMargin;
1153
            $this->CurOrientation = $orientation;
1154
            $this->CurPageSize = $size;
1155
        }
1156
        if ($orientation!=$this->DefOrientation || $size[0]!=$this->DefPageSize[0] || $size[1]!=$this->DefPageSize[1]) {
1157
            $this->PageInfo[$this->page]['size'] = [$this->wPt, $this->hPt];
1158
        }
1159
        if ($rotation!=0) {
1160
            if ($rotation%90!=0) {
1161
                $this->Error('Incorrect rotation value: '.$rotation);
1162
            }
1163
            $this->CurRotation = $rotation;
1164
            $this->PageInfo[$this->page]['rotation'] = $rotation;
1165
        }
1166
    }
1167
1168
    protected function _endpage()
1169
    {
1170
        $this->state = 1;
1171
    }
1172
1173
    protected function _loadfont($font)
1174
    {
1175
        // Load a font definition file from the font directory
1176
        if (strpos($font, '/')!==false || strpos($font, "\\")!==false) {
1177
            $this->Error('Incorrect font definition file name: '.$font);
1178
        }
1179
        include($this->fontpath.$font);
1180
        if (!isset($name)) {
1181
            $this->Error('Could not include font definition file');
1182
        }
1183
        if (isset($enc)) {
1184
            $enc = strtolower($enc);
1185
        }
1186
        if (!isset($subsetted)) {
1187
            $subsetted = false;
1188
        }
1189
        return get_defined_vars();
1190
    }
1191
1192
    protected function _isascii($s)
1193
    {
1194
        // Test if string is ASCII
1195
        $nb = strlen($s);
1196
        for ($i=0;$i<$nb;$i++) {
1197
            if (ord($s[$i])>127) {
1198
                return false;
1199
            }
1200
        }
1201
        return true;
1202
    }
1203
1204
    protected function _httpencode($param, $value, $isUTF8)
1205
    {
1206
        // Encode HTTP header field parameter
1207
        if ($this->_isascii($value)) {
1208
            return $param.'="'.$value.'"';
1209
        }
1210
        if (!$isUTF8) {
1211
            $value = utf8_encode($value);
1212
        }
1213
        if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')!==false) {
1214
            return $param.'="'.rawurlencode($value).'"';
1215
        } else {
1216
            return $param."*=UTF-8''".rawurlencode($value);
1217
        }
1218
    }
1219
1220
    protected function _UTF8toUTF16($s)
1221
    {
1222
        // Convert UTF-8 to UTF-16BE with BOM
1223
        $res = "\xFE\xFF";
1224
        $nb = strlen($s);
1225
        $i = 0;
1226
        while ($i<$nb) {
1227
            $c1 = ord($s[$i++]);
1228
            if ($c1>=224) {
1229
                // 3-byte character
1230
                $c2 = ord($s[$i++]);
1231
                $c3 = ord($s[$i++]);
1232
                $res .= chr((($c1 & 0x0F)<<4) + (($c2 & 0x3C)>>2));
1233
                $res .= chr((($c2 & 0x03)<<6) + ($c3 & 0x3F));
1234
            } elseif ($c1>=192) {
1235
                // 2-byte character
1236
                $c2 = ord($s[$i++]);
1237
                $res .= chr(($c1 & 0x1C)>>2);
1238
                $res .= chr((($c1 & 0x03)<<6) + ($c2 & 0x3F));
1239
            } else {
1240
                // Single-byte character
1241
                $res .= "\0".chr($c1);
1242
            }
1243
        }
1244
        return $res;
1245
    }
1246
1247
    protected function _escape($s)
1248
    {
1249
        // Escape special characters
1250
        if (strpos($s, '(')!==false || strpos($s, ')')!==false || strpos($s, '\\')!==false || strpos($s, "\r")!==false) {
1251
            return str_replace(['\\','(',')',"\r"], ['\\\\','\\(','\\)','\\r'], $s);
1252
        } else {
1253
            return $s;
1254
        }
1255
    }
1256
1257
    protected function _textstring($s)
1258
    {
1259
        // Format a text string
1260
        if (!$this->_isascii($s)) {
1261
            $s = $this->_UTF8toUTF16($s);
1262
        }
1263
        return '('.$this->_escape($s).')';
1264
    }
1265
1266
    protected function _dounderline($x, $y, $txt)
1267
    {
1268
        // Underline text
1269
        $up = $this->CurrentFont['up'];
1270
        $ut = $this->CurrentFont['ut'];
1271
        $w = $this->GetStringWidth($txt)+$this->ws*substr_count($txt, ' ');
1272
        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);
1273
    }
1274
1275
    protected function _parsejpg($file)
1276
    {
1277
        // Extract info from a JPEG file
1278
        $a = getimagesize($file);
1279
        if (!$a) {
1280
            $this->Error('Missing or incorrect image file: '.$file);
1281
        }
1282
        if ($a[2]!=2) {
1283
            $this->Error('Not a JPEG file: '.$file);
1284
        }
1285
        if (!isset($a['channels']) || $a['channels']==3) {
1286
            $colspace = 'DeviceRGB';
1287
        } elseif ($a['channels']==4) {
1288
            $colspace = 'DeviceCMYK';
1289
        } else {
1290
            $colspace = 'DeviceGray';
1291
        }
1292
        $bpc = isset($a['bits']) ? $a['bits'] : 8;
1293
        $data = file_get_contents($file);
1294
        return ['w'=>$a[0], 'h'=>$a[1], 'cs'=>$colspace, 'bpc'=>$bpc, 'f'=>'DCTDecode', 'data'=>$data];
1295
    }
1296
1297
    protected function _parsepng($file)
1298
    {
1299
        // Extract info from a PNG file
1300
        $f = fopen($file, 'rb');
1301
        if (!$f) {
1302
            $this->Error('Can\'t open image file: '.$file);
1303
        }
1304
        $info = $this->_parsepngstream($f, $file);
1305
        fclose($f);
1306
        return $info;
1307
    }
1308
1309
    protected function _parsepngstream($f, $file)
1310
    {
1311
        // Check signature
1312
        if ($this->_readstream($f, 8)!=chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10)) {
1313
            $this->Error('Not a PNG file: '.$file);
1314
        }
1315
1316
        // Read header chunk
1317
        $this->_readstream($f, 4);
1318
        if ($this->_readstream($f, 4)!='IHDR') {
1319
            $this->Error('Incorrect PNG file: '.$file);
1320
        }
1321
        $w = $this->_readint($f);
1322
        $h = $this->_readint($f);
1323
        $bpc = ord($this->_readstream($f, 1));
1324
        if ($bpc>8) {
1325
            $this->Error('16-bit depth not supported: '.$file);
1326
        }
1327
        $ct = ord($this->_readstream($f, 1));
1328
        if ($ct==0 || $ct==4) {
1329
            $colspace = 'DeviceGray';
1330
        } elseif ($ct==2 || $ct==6) {
1331
            $colspace = 'DeviceRGB';
1332
        } elseif ($ct==3) {
1333
            $colspace = 'Indexed';
1334
        } else {
1335
            $this->Error('Unknown color type: '.$file);
1336
        }
1337
        if (ord($this->_readstream($f, 1))!=0) {
1338
            $this->Error('Unknown compression method: '.$file);
1339
        }
1340
        if (ord($this->_readstream($f, 1))!=0) {
1341
            $this->Error('Unknown filter method: '.$file);
1342
        }
1343
        if (ord($this->_readstream($f, 1))!=0) {
1344
            $this->Error('Interlacing not supported: '.$file);
1345
        }
1346
        $this->_readstream($f, 4);
1347
        $dp = '/Predictor 15 /Colors '.($colspace=='DeviceRGB' ? 3 : 1).' /BitsPerComponent '.$bpc.' /Columns '.$w;
1348
1349
        // Scan chunks looking for palette, transparency and image data
1350
        $pal = '';
1351
        $trns = '';
1352
        $data = '';
1353
        do {
1354
            $n = $this->_readint($f);
1355
            $type = $this->_readstream($f, 4);
1356
            if ($type=='PLTE') {
1357
                // Read palette
1358
                $pal = $this->_readstream($f, $n);
1359
                $this->_readstream($f, 4);
1360
            } elseif ($type=='tRNS') {
1361
                // Read transparency info
1362
                $t = $this->_readstream($f, $n);
1363
                if ($ct==0) {
1364
                    $trns = [ord(substr($t, 1, 1))];
1365
                } elseif ($ct==2) {
1366
                    $trns = [ord(substr($t, 1, 1)), ord(substr($t, 3, 1)), ord(substr($t, 5, 1))];
1367
                } else {
1368
                    $pos = strpos($t, chr(0));
1369
                    if ($pos!==false) {
1370
                        $trns = [$pos];
1371
                    }
1372
                }
1373
                $this->_readstream($f, 4);
1374
            } elseif ($type=='IDAT') {
1375
                // Read image data block
1376
                $data .= $this->_readstream($f, $n);
1377
                $this->_readstream($f, 4);
1378
            } elseif ($type=='IEND') {
1379
                break;
1380
            } else {
1381
                $this->_readstream($f, $n+4);
1382
            }
1383
        } while ($n);
1384
1385
        if ($colspace=='Indexed' && empty($pal)) {
1386
            $this->Error('Missing palette in '.$file);
1387
        }
1388
        $info = ['w'=>$w, 'h'=>$h, 'cs'=>$colspace, 'bpc'=>$bpc, 'f'=>'FlateDecode', 'dp'=>$dp, 'pal'=>$pal, 'trns'=>$trns];
1389
        if ($ct>=4) {
1390
            // Extract alpha channel
1391
            if (!function_exists('gzuncompress')) {
1392
                $this->Error('Zlib not available, can\'t handle alpha channel: '.$file);
1393
            }
1394
            $data = gzuncompress($data);
1395
            $color = '';
1396
            $alpha = '';
1397
            if ($ct==4) {
1398
                // Gray image
1399
                $len = 2*$w;
1400
                for ($i=0;$i<$h;$i++) {
1401
                    $pos = (1+$len)*$i;
1402
                    $color .= $data[$pos];
1403
                    $alpha .= $data[$pos];
1404
                    $line = substr($data, $pos+1, $len);
1405
                    $color .= preg_replace('/(.)./s', '$1', $line);
1406
                    $alpha .= preg_replace('/.(.)/s', '$1', $line);
1407
                }
1408
            } else {
1409
                // RGB image
1410
                $len = 4*$w;
1411
                for ($i=0;$i<$h;$i++) {
1412
                    $pos = (1+$len)*$i;
1413
                    $color .= $data[$pos];
1414
                    $alpha .= $data[$pos];
1415
                    $line = substr($data, $pos+1, $len);
1416
                    $color .= preg_replace('/(.{3})./s', '$1', $line);
1417
                    $alpha .= preg_replace('/.{3}(.)/s', '$1', $line);
1418
                }
1419
            }
1420
            unset($data);
1421
            $data = gzcompress($color);
1422
            $info['smask'] = gzcompress($alpha);
1423
            $this->WithAlpha = true;
1424
            if ($this->PDFVersion<'1.4') {
1425
                $this->PDFVersion = '1.4';
1426
            }
1427
        }
1428
        $info['data'] = $data;
1429
        return $info;
1430
    }
1431
1432
    protected function _readstream($f, $n)
1433
    {
1434
        // Read n bytes from stream
1435
        $res = '';
1436
        while ($n>0 && !feof($f)) {
1437
            $s = fread($f, $n);
1438
            if ($s===false) {
1439
                $this->Error('Error while reading stream');
1440
            }
1441
            $n -= strlen($s);
1442
            $res .= $s;
1443
        }
1444
        if ($n>0) {
1445
            $this->Error('Unexpected end of stream');
1446
        }
1447
        return $res;
1448
    }
1449
1450
    protected function _readint($f)
1451
    {
1452
        // Read a 4-byte integer from stream
1453
        $a = unpack('Ni', $this->_readstream($f, 4));
1454
        return $a['i'];
1455
    }
1456
1457
    protected function _parsegif($file)
1458
    {
1459
        // Extract info from a GIF file (via PNG conversion)
1460
        if (!function_exists('imagepng')) {
1461
            $this->Error('GD extension is required for GIF support');
1462
        }
1463
        if (!function_exists('imagecreatefromgif')) {
1464
            $this->Error('GD has no GIF read support');
1465
        }
1466
        $im = imagecreatefromgif($file);
1467
        if (!$im) {
1468
            $this->Error('Missing or incorrect image file: '.$file);
1469
        }
1470
        imageinterlace($im, 0);
1471
        ob_start();
1472
        imagepng($im);
1473
        $data = ob_get_clean();
1474
        imagedestroy($im);
1475
        $f = fopen('php://temp', 'rb+');
1476
        if (!$f) {
1477
            $this->Error('Unable to create memory stream');
1478
        }
1479
        fwrite($f, $data);
1480
        rewind($f);
1481
        $info = $this->_parsepngstream($f, $file);
1482
        fclose($f);
1483
        return $info;
1484
    }
1485
1486
    protected function _out($s)
1487
    {
1488
        // Add a line to the document
1489
        if ($this->state==2) {
1490
            $this->pages[$this->page] .= $s."\n";
1491
        } elseif ($this->state==1) {
1492
            $this->_put($s);
1493
        } elseif ($this->state==0) {
1494
            $this->Error('No page has been added yet');
1495
        } elseif ($this->state==3) {
1496
            $this->Error('The document is closed');
1497
        }
1498
    }
1499
1500
    protected function _put($s)
1501
    {
1502
        $this->buffer .= $s."\n";
1503
    }
1504
1505
    protected function _getoffset()
1506
    {
1507
        return strlen($this->buffer);
1508
    }
1509
1510
    protected function _newobj($n=null)
1511
    {
1512
        // Begin a new object
1513
        if ($n===null) {
1514
            $n = ++$this->n;
1515
        }
1516
        $this->offsets[$n] = $this->_getoffset();
1517
        $this->_put($n.' 0 obj');
1518
    }
1519
1520
    protected function _putstream($data)
1521
    {
1522
        $this->_put('stream');
1523
        $this->_put($data);
1524
        $this->_put('endstream');
1525
    }
1526
1527
    protected function _putstreamobject($data)
1528
    {
1529
        if ($this->compress) {
1530
            $entries = '/Filter /FlateDecode ';
1531
            $data = gzcompress($data);
1532
        } else {
1533
            $entries = '';
1534
        }
1535
        $entries .= '/Length '.strlen($data);
1536
        $this->_newobj();
1537
        $this->_put('<<'.$entries.'>>');
1538
        $this->_putstream($data);
1539
        $this->_put('endobj');
1540
    }
1541
1542
    protected function _putpage($n)
1543
    {
1544
        $this->_newobj();
1545
        $this->_put('<</Type /Page');
1546
        $this->_put('/Parent 1 0 R');
1547
        if (isset($this->PageInfo[$n]['size'])) {
1548
            $this->_put(sprintf('/MediaBox [0 0 %.2F %.2F]', $this->PageInfo[$n]['size'][0], $this->PageInfo[$n]['size'][1]));
1549
        }
1550
        if (isset($this->PageInfo[$n]['rotation'])) {
1551
            $this->_put('/Rotate '.$this->PageInfo[$n]['rotation']);
1552
        }
1553
        $this->_put('/Resources 2 0 R');
1554
        if (isset($this->PageLinks[$n])) {
1555
            // Links
1556
            $annots = '/Annots [';
1557
            foreach ($this->PageLinks[$n] as $pl) {
1558
                $rect = sprintf('%.2F %.2F %.2F %.2F', $pl[0], $pl[1], $pl[0]+$pl[2], $pl[1]-$pl[3]);
1559
                $annots .= '<</Type /Annot /Subtype /Link /Rect ['.$rect.'] /Border [0 0 0] ';
1560
                if (is_string($pl[4])) {
1561
                    $annots .= '/A <</S /URI /URI '.$this->_textstring($pl[4]).'>>>>';
1562
                } else {
1563
                    $l = $this->links[$pl[4]];
1564
                    if (isset($this->PageInfo[$l[0]]['size'])) {
1565
                        $h = $this->PageInfo[$l[0]]['size'][1];
1566
                    } else {
1567
                        $h = ($this->DefOrientation=='P') ? $this->DefPageSize[1]*$this->k : $this->DefPageSize[0]*$this->k;
1568
                    }
1569
                    $annots .= sprintf('/Dest [%d 0 R /XYZ 0 %.2F null]>>', $this->PageInfo[$l[0]]['n'], $h-$l[1]*$this->k);
1570
                }
1571
            }
1572
            $this->_put($annots.']');
1573
        }
1574
        if ($this->WithAlpha) {
1575
            $this->_put('/Group <</Type /Group /S /Transparency /CS /DeviceRGB>>');
1576
        }
1577
        $this->_put('/Contents '.($this->n+1).' 0 R>>');
1578
        $this->_put('endobj');
1579
        // Page content
1580
        if (!empty($this->AliasNbPages)) {
1581
            $this->pages[$n] = str_replace($this->AliasNbPages, $this->page, $this->pages[$n]);
1582
        }
1583
        $this->_putstreamobject($this->pages[$n]);
1584
    }
1585
1586
    protected function _putpages()
1587
    {
1588
        $nb = $this->page;
1589
        for ($n=1;$n<=$nb;$n++) {
1590
            $this->PageInfo[$n]['n'] = $this->n+1+2*($n-1);
1591
        }
1592
        for ($n=1;$n<=$nb;$n++) {
1593
            $this->_putpage($n);
1594
        }
1595
        // Pages root
1596
        $this->_newobj(1);
1597
        $this->_put('<</Type /Pages');
1598
        $kids = '/Kids [';
1599
        for ($n=1;$n<=$nb;$n++) {
1600
            $kids .= $this->PageInfo[$n]['n'].' 0 R ';
1601
        }
1602
        $this->_put($kids.']');
1603
        $this->_put('/Count '.$nb);
1604
        if ($this->DefOrientation=='P') {
1605
            $w = $this->DefPageSize[0];
1606
            $h = $this->DefPageSize[1];
1607
        } else {
1608
            $w = $this->DefPageSize[1];
1609
            $h = $this->DefPageSize[0];
1610
        }
1611
        $this->_put(sprintf('/MediaBox [0 0 %.2F %.2F]', $w*$this->k, $h*$this->k));
1612
        $this->_put('>>');
1613
        $this->_put('endobj');
1614
    }
1615
1616
    protected function _putfonts()
1617
    {
1618
        foreach ($this->FontFiles as $file=>$info) {
1619
            // Font file embedding
1620
            $this->_newobj();
1621
            $this->FontFiles[$file]['n'] = $this->n;
1622
            $font = file_get_contents($this->fontpath.$file, true);
1623
            if (!$font) {
1624
                $this->Error('Font file not found: '.$file);
1625
            }
1626
            $compressed = (substr($file, -2)=='.z');
1627
            if (!$compressed && isset($info['length2'])) {
1628
                $font = substr($font, 6, $info['length1']).substr($font, 6+$info['length1']+6, $info['length2']);
1629
            }
1630
            $this->_put('<</Length '.strlen($font));
1631
            if ($compressed) {
1632
                $this->_put('/Filter /FlateDecode');
1633
            }
1634
            $this->_put('/Length1 '.$info['length1']);
1635
            if (isset($info['length2'])) {
1636
                $this->_put('/Length2 '.$info['length2'].' /Length3 0');
1637
            }
1638
            $this->_put('>>');
1639
            $this->_putstream($font);
1640
            $this->_put('endobj');
1641
        }
1642
        foreach ($this->fonts as $k=>$font) {
1643
            // Encoding
1644
            if (isset($font['diff'])) {
1645
                if (!isset($this->encodings[$font['enc']])) {
1646
                    $this->_newobj();
1647
                    $this->_put('<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$font['diff'].']>>');
1648
                    $this->_put('endobj');
1649
                    $this->encodings[$font['enc']] = $this->n;
1650
                }
1651
            }
1652
            // ToUnicode CMap
1653
            if (isset($font['uv'])) {
1654
                if (isset($font['enc'])) {
1655
                    $cmapkey = $font['enc'];
1656
                } else {
1657
                    $cmapkey = $font['name'];
1658
                }
1659
                if (!isset($this->cmaps[$cmapkey])) {
1660
                    $cmap = $this->_tounicodecmap($font['uv']);
1661
                    $this->_putstreamobject($cmap);
1662
                    $this->cmaps[$cmapkey] = $this->n;
1663
                }
1664
            }
1665
            // Font object
1666
            $this->fonts[$k]['n'] = $this->n+1;
1667
            $type = $font['type'];
1668
            $name = $font['name'];
1669
            if ($font['subsetted']) {
1670
                $name = 'AAAAAA+'.$name;
1671
            }
1672
            if ($type=='Core') {
1673
                // Core font
1674
                $this->_newobj();
1675
                $this->_put('<</Type /Font');
1676
                $this->_put('/BaseFont /'.$name);
1677
                $this->_put('/Subtype /Type1');
1678
                if ($name!='Symbol' && $name!='ZapfDingbats') {
1679
                    $this->_put('/Encoding /WinAnsiEncoding');
1680
                }
1681
                if (isset($font['uv'])) {
1682
                    $this->_put('/ToUnicode '.$this->cmaps[$cmapkey].' 0 R');
1683
                }
1684
                $this->_put('>>');
1685
                $this->_put('endobj');
1686
            } elseif ($type=='Type1' || $type=='TrueType') {
1687
                // Additional Type1 or TrueType/OpenType font
1688
                $this->_newobj();
1689
                $this->_put('<</Type /Font');
1690
                $this->_put('/BaseFont /'.$name);
1691
                $this->_put('/Subtype /'.$type);
1692
                $this->_put('/FirstChar 32 /LastChar 255');
1693
                $this->_put('/Widths '.($this->n+1).' 0 R');
1694
                $this->_put('/FontDescriptor '.($this->n+2).' 0 R');
1695
                if (isset($font['diff'])) {
1696
                    $this->_put('/Encoding '.$this->encodings[$font['enc']].' 0 R');
1697
                } else {
1698
                    $this->_put('/Encoding /WinAnsiEncoding');
1699
                }
1700
                if (isset($font['uv'])) {
1701
                    $this->_put('/ToUnicode '.$this->cmaps[$cmapkey].' 0 R');
1702
                }
1703
                $this->_put('>>');
1704
                $this->_put('endobj');
1705
                // Widths
1706
                $this->_newobj();
1707
                $cw = &$font['cw'];
1708
                $s = '[';
1709
                for ($i=32;$i<=255;$i++) {
1710
                    $s .= $cw[chr($i)].' ';
1711
                }
1712
                $this->_put($s.']');
1713
                $this->_put('endobj');
1714
                // Descriptor
1715
                $this->_newobj();
1716
                $s = '<</Type /FontDescriptor /FontName /'.$name;
1717
                foreach ($font['desc'] as $k=>$v) {
1718
                    $s .= ' /'.$k.' '.$v;
1719
                }
1720
                if (!empty($font['file'])) {
1721
                    $s .= ' /FontFile'.($type=='Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
1722
                }
1723
                $this->_put($s.'>>');
1724
                $this->_put('endobj');
1725
            } else {
1726
                // Allow for additional types
1727
                $mtd = '_put'.strtolower($type);
1728
                if (!method_exists($this, $mtd)) {
1729
                    $this->Error('Unsupported font type: '.$type);
1730
                }
1731
                $this->$mtd($font);
1732
            }
1733
        }
1734
    }
1735
1736
    protected function _tounicodecmap($uv)
1737
    {
1738
        $ranges = '';
1739
        $nbr = 0;
1740
        $chars = '';
1741
        $nbc = 0;
1742
        foreach ($uv as $c=>$v) {
1743
            if (is_array($v)) {
1744
                $ranges .= sprintf("<%02X> <%02X> <%04X>\n", $c, $c+$v[1]-1, $v[0]);
1745
                $nbr++;
1746
            } else {
1747
                $chars .= sprintf("<%02X> <%04X>\n", $c, $v);
1748
                $nbc++;
1749
            }
1750
        }
1751
        $s = "/CIDInit /ProcSet findresource begin\n";
1752
        $s .= "12 dict begin\n";
1753
        $s .= "begincmap\n";
1754
        $s .= "/CIDSystemInfo\n";
1755
        $s .= "<</Registry (Adobe)\n";
1756
        $s .= "/Ordering (UCS)\n";
1757
        $s .= "/Supplement 0\n";
1758
        $s .= ">> def\n";
1759
        $s .= "/CMapName /Adobe-Identity-UCS def\n";
1760
        $s .= "/CMapType 2 def\n";
1761
        $s .= "1 begincodespacerange\n";
1762
        $s .= "<00> <FF>\n";
1763
        $s .= "endcodespacerange\n";
1764
        if ($nbr>0) {
1765
            $s .= "$nbr beginbfrange\n";
1766
            $s .= $ranges;
1767
            $s .= "endbfrange\n";
1768
        }
1769
        if ($nbc>0) {
1770
            $s .= "$nbc beginbfchar\n";
1771
            $s .= $chars;
1772
            $s .= "endbfchar\n";
1773
        }
1774
        $s .= "endcmap\n";
1775
        $s .= "CMapName currentdict /CMap defineresource pop\n";
1776
        $s .= "end\n";
1777
        $s .= "end";
1778
        return $s;
1779
    }
1780
1781
    protected function _putimages()
1782
    {
1783
        foreach (array_keys($this->images) as $file) {
1784
            $this->_putimage($this->images[$file]);
1785
            unset($this->images[$file]['data']);
1786
            unset($this->images[$file]['smask']);
1787
        }
1788
    }
1789
1790
    protected function _putimage(&$info)
1791
    {
1792
        $this->_newobj();
1793
        $info['n'] = $this->n;
1794
        $this->_put('<</Type /XObject');
1795
        $this->_put('/Subtype /Image');
1796
        $this->_put('/Width '.$info['w']);
1797
        $this->_put('/Height '.$info['h']);
1798
        if ($info['cs']=='Indexed') {
1799
            $this->_put('/ColorSpace [/Indexed /DeviceRGB '.(strlen($info['pal'])/3-1).' '.($this->n+1).' 0 R]');
1800
        } else {
1801
            $this->_put('/ColorSpace /'.$info['cs']);
1802
            if ($info['cs']=='DeviceCMYK') {
1803
                $this->_put('/Decode [1 0 1 0 1 0 1 0]');
1804
            }
1805
        }
1806
        $this->_put('/BitsPerComponent '.$info['bpc']);
1807
        if (isset($info['f'])) {
1808
            $this->_put('/Filter /'.$info['f']);
1809
        }
1810
        if (isset($info['dp'])) {
1811
            $this->_put('/DecodeParms <<'.$info['dp'].'>>');
1812
        }
1813
        if (isset($info['trns']) && is_array($info['trns'])) {
1814
            $trns = '';
1815
            for ($i=0;$i<count($info['trns']);$i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1816
                $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
1817
            }
1818
            $this->_put('/Mask ['.$trns.']');
1819
        }
1820
        if (isset($info['smask'])) {
1821
            $this->_put('/SMask '.($this->n+1).' 0 R');
1822
        }
1823
        $this->_put('/Length '.strlen($info['data']).'>>');
1824
        $this->_putstream($info['data']);
1825
        $this->_put('endobj');
1826
        // Soft mask
1827
        if (isset($info['smask'])) {
1828
            $dp = '/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns '.$info['w'];
1829
            $smask = ['w'=>$info['w'], 'h'=>$info['h'], 'cs'=>'DeviceGray', 'bpc'=>8, 'f'=>$info['f'], 'dp'=>$dp, 'data'=>$info['smask']];
1830
            $this->_putimage($smask);
1831
        }
1832
        // Palette
1833
        if ($info['cs']=='Indexed') {
1834
            $this->_putstreamobject($info['pal']);
1835
        }
1836
    }
1837
1838
    protected function _putxobjectdict()
1839
    {
1840
        foreach ($this->images as $image) {
1841
            $this->_put('/I'.$image['i'].' '.$image['n'].' 0 R');
1842
        }
1843
    }
1844
1845
    protected function _putresourcedict()
1846
    {
1847
        $this->_put('/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
1848
        $this->_put('/Font <<');
1849
        foreach ($this->fonts as $font) {
1850
            $this->_put('/F'.$font['i'].' '.$font['n'].' 0 R');
1851
        }
1852
        $this->_put('>>');
1853
        $this->_put('/XObject <<');
1854
        $this->_putxobjectdict();
1855
        $this->_put('>>');
1856
    }
1857
1858
    protected function _putresources()
1859
    {
1860
        $this->_putfonts();
1861
        $this->_putimages();
1862
        // Resource dictionary
1863
        $this->_newobj(2);
1864
        $this->_put('<<');
1865
        $this->_putresourcedict();
1866
        $this->_put('>>');
1867
        $this->_put('endobj');
1868
    }
1869
1870
    protected function _putinfo()
1871
    {
1872
        $this->metadata['Producer'] = 'FPDF '.FPDF_VERSION;
1873
        $this->metadata['CreationDate'] = 'D:'.@date('YmdHis');
1874
        foreach ($this->metadata as $key=>$value) {
1875
            $this->_put('/'.$key.' '.$this->_textstring($value));
1876
        }
1877
    }
1878
1879
    protected function _putcatalog()
1880
    {
1881
        $n = $this->PageInfo[1]['n'];
1882
        $this->_put('/Type /Catalog');
1883
        $this->_put('/Pages 1 0 R');
1884
        if ($this->ZoomMode=='fullpage') {
1885
            $this->_put('/OpenAction ['.$n.' 0 R /Fit]');
1886
        } elseif ($this->ZoomMode=='fullwidth') {
1887
            $this->_put('/OpenAction ['.$n.' 0 R /FitH null]');
1888
        } elseif ($this->ZoomMode=='real') {
1889
            $this->_put('/OpenAction ['.$n.' 0 R /XYZ null null 1]');
1890
        } elseif (!is_string($this->ZoomMode)) {
1891
            $this->_put('/OpenAction ['.$n.' 0 R /XYZ null null '.sprintf('%.2F', $this->ZoomMode/100).']');
1892
        }
1893
        if ($this->LayoutMode=='single') {
1894
            $this->_put('/PageLayout /SinglePage');
1895
        } elseif ($this->LayoutMode=='continuous') {
1896
            $this->_put('/PageLayout /OneColumn');
1897
        } elseif ($this->LayoutMode=='two') {
1898
            $this->_put('/PageLayout /TwoColumnLeft');
1899
        }
1900
    }
1901
1902
    protected function _putheader()
1903
    {
1904
        $this->_put('%PDF-'.$this->PDFVersion);
1905
    }
1906
1907
    protected function _puttrailer()
1908
    {
1909
        $this->_put('/Size '.($this->n+1));
1910
        $this->_put('/Root '.$this->n.' 0 R');
1911
        $this->_put('/Info '.($this->n-1).' 0 R');
1912
    }
1913
1914
    protected function _enddoc()
1915
    {
1916
        $this->_putheader();
1917
        $this->_putpages();
1918
        $this->_putresources();
1919
        // Info
1920
        $this->_newobj();
1921
        $this->_put('<<');
1922
        $this->_putinfo();
1923
        $this->_put('>>');
1924
        $this->_put('endobj');
1925
        // Catalog
1926
        $this->_newobj();
1927
        $this->_put('<<');
1928
        $this->_putcatalog();
1929
        $this->_put('>>');
1930
        $this->_put('endobj');
1931
        // Cross-ref
1932
        $offset = $this->_getoffset();
1933
        $this->_put('xref');
1934
        $this->_put('0 '.($this->n+1));
1935
        $this->_put('0000000000 65535 f ');
1936
        for ($i=1;$i<=$this->n;$i++) {
1937
            $this->_put(sprintf('%010d 00000 n ', $this->offsets[$i]));
1938
        }
1939
        // Trailer
1940
        $this->_put('trailer');
1941
        $this->_put('<<');
1942
        $this->_puttrailer();
1943
        $this->_put('>>');
1944
        $this->_put('startxref');
1945
        $this->_put($offset);
1946
        $this->_put('%%EOF');
1947
        $this->state = 3;
1948
    }
1949
}
1950