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