Translated using Weblate (Hungarian)
[phpmyadmin.git] / libraries / tcpdf / tcpdf.php
blob30ad76f6fb86d532b70e3354b8e61a6bf9217780
1 <?php
2 //============================================================+
3 // File name : tcpdf.php
4 // Version : 6.0.077
5 // Begin : 2002-08-03
6 // Last Update : 2014-05-06
7 // Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com
8 // License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
9 // -------------------------------------------------------------------
10 // Copyright (C) 2002-2014 Nicola Asuni - Tecnick.com LTD
12 // This file is part of TCPDF software library.
14 // TCPDF is free software: you can ioredistribute it and/or modify it
15 // under the terms of the GNU Lesser General Public License as
16 // published by the Free Software Foundation, either version 3 of the
17 // License, or (at your option) any later version.
19 // TCPDF is distributed in the hope that it will be useful, but
20 // WITHOUT ANY WARRANTY; without even the implied warranty of
21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
22 // See the GNU Lesser General Public License for more details.
24 // You should have received a copy of the License
25 // along with TCPDF. If not, see
26 // <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>.
28 // See LICENSE.TXT file for more information.
29 // -------------------------------------------------------------------
31 // Description :
32 // This is a PHP class for generating PDF documents without requiring external extensions.
34 // NOTE:
35 // This class was originally derived in 2002 from the Public
36 // Domain FPDF class by Olivier Plathey (http://www.fpdf.org),
37 // but now is almost entirely rewritten and contains thousands of
38 // new lines of code and hundreds new features.
40 // Main features:
41 // * no external libraries are required for the basic functions;
42 // * all standard page formats, custom page formats, custom margins and units of measure;
43 // * UTF-8 Unicode and Right-To-Left languages;
44 // * TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;
45 // * font subsetting;
46 // * methods to publish some XHTML + CSS code, Javascript and Forms;
47 // * images, graphic (geometric figures) and transformation methods;
48 // * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)
49 // * 1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extention, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;
50 // * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;
51 // * automatic page header and footer management;
52 // * document encryption up to 256 bit and digital signature certifications;
53 // * transactions to UNDO commands;
54 // * PDF annotations, including links, text and file attachments;
55 // * text rendering modes (fill, stroke and clipping);
56 // * multiple columns mode;
57 // * no-write page regions;
58 // * bookmarks, named destinations and table of content;
59 // * text hyphenation;
60 // * text stretching and spacing (tracking);
61 // * automatic page break, line break and text alignments including justification;
62 // * automatic page numbering and page groups;
63 // * move and delete pages;
64 // * page compression (requires php-zlib extension);
65 // * XOBject Templates;
66 // * Layers and object visibility.
67 // * PDF/A-1b support
68 //============================================================+
70 /**
71 * @file
72 * This is a PHP class for generating PDF documents without requiring external extensions.<br>
73 * TCPDF project (http://www.tcpdf.org) was originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
74 * <h3>TCPDF main features are:</h3>
75 * <ul>
76 * <li>no external libraries are required for the basic functions;</li>
77 * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
78 * <li>UTF-8 Unicode and Right-To-Left languages;</li>
79 * <li>TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;</li>
80 * <li>font subsetting;</li>
81 * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
82 * <li>images, graphic (geometric figures) and transformation methods;
83 * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)</li>
84 * <li>1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extention, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, Datamatrix, QR-Code, PDF417;</li>
85 * <li>JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
86 * <li>automatic page header and footer management;</li>
87 * <li>document encryption up to 256 bit and digital signature certifications;</li>
88 * <li>transactions to UNDO commands;</li>
89 * <li>PDF annotations, including links, text and file attachments;</li>
90 * <li>text rendering modes (fill, stroke and clipping);</li>
91 * <li>multiple columns mode;</li>
92 * <li>no-write page regions;</li>
93 * <li>bookmarks, named destinations and table of content;</li>
94 * <li>text hyphenation;</li>
95 * <li>text stretching and spacing (tracking);</li>
96 * <li>automatic page break, line break and text alignments including justification;</li>
97 * <li>automatic page numbering and page groups;</li>
98 * <li>move and delete pages;</li>
99 * <li>page compression (requires php-zlib extension);</li>
100 * <li>XOBject Templates;</li>
101 * <li>Layers and object visibility;</li>
102 * <li>PDF/A-1b support.</li>
103 * </ul>
104 * Tools to encode your unicode fonts are on fonts/utils directory.</p>
105 * @package com.tecnick.tcpdf
106 * @author Nicola Asuni
107 * @version 6.0.077
110 // TCPDF configuration
111 require_once(dirname(__FILE__).'/tcpdf_autoconfig.php');
112 // TCPDF static font methods and data
113 require_once(dirname(__FILE__).'/include/tcpdf_font_data.php');
114 // TCPDF static font methods and data
115 require_once(dirname(__FILE__).'/include/tcpdf_fonts.php');
116 // TCPDF static color methods and data
117 require_once(dirname(__FILE__).'/include/tcpdf_colors.php');
118 // TCPDF static image methods and data
119 require_once(dirname(__FILE__).'/include/tcpdf_images.php');
120 // TCPDF static methods and data
121 require_once(dirname(__FILE__).'/include/tcpdf_static.php');
123 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
126 * @class TCPDF
127 * PHP class for generating PDF documents without requiring external extensions.
128 * TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
129 * @package com.tecnick.tcpdf
130 * @brief PHP class for generating PDF documents without requiring external extensions.
131 * @version 6.0.077
132 * @author Nicola Asuni - info@tecnick.com
134 class TCPDF {
136 // Protected properties
139 * Current page number.
140 * @protected
142 protected $page;
145 * Current object number.
146 * @protected
148 protected $n;
151 * Array of object offsets.
152 * @protected
154 protected $offsets = array();
157 * Array of object IDs for each page.
158 * @protected
160 protected $pageobjects = array();
163 * Buffer holding in-memory PDF.
164 * @protected
166 protected $buffer;
169 * Array containing pages.
170 * @protected
172 protected $pages = array();
175 * Current document state.
176 * @protected
178 protected $state;
181 * Compression flag.
182 * @protected
184 protected $compress;
187 * Current page orientation (P = Portrait, L = Landscape).
188 * @protected
190 protected $CurOrientation;
193 * Page dimensions.
194 * @protected
196 protected $pagedim = array();
199 * Scale factor (number of points in user unit).
200 * @protected
202 protected $k;
205 * Width of page format in points.
206 * @protected
208 protected $fwPt;
211 * Height of page format in points.
212 * @protected
214 protected $fhPt;
217 * Current width of page in points.
218 * @protected
220 protected $wPt;
223 * Current height of page in points.
224 * @protected
226 protected $hPt;
229 * Current width of page in user unit.
230 * @protected
232 protected $w;
235 * Current height of page in user unit.
236 * @protected
238 protected $h;
241 * Left margin.
242 * @protected
244 protected $lMargin;
247 * Right margin.
248 * @protected
250 protected $rMargin;
253 * Cell left margin (used by regions).
254 * @protected
256 protected $clMargin;
259 * Cell right margin (used by regions).
260 * @protected
262 protected $crMargin;
265 * Top margin.
266 * @protected
268 protected $tMargin;
271 * Page break margin.
272 * @protected
274 protected $bMargin;
277 * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
278 * @since 5.9.000 (2010-10-03)
279 * @protected
281 protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
284 * Array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
285 * @since 5.9.000 (2010-10-04)
286 * @protected
288 protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
291 * Current horizontal position in user unit for cell positioning.
292 * @protected
294 protected $x;
297 * Current vertical position in user unit for cell positioning.
298 * @protected
300 protected $y;
303 * Height of last cell printed.
304 * @protected
306 protected $lasth;
309 * Line width in user unit.
310 * @protected
312 protected $LineWidth;
315 * Array of standard font names.
316 * @protected
318 protected $CoreFonts;
321 * Array of used fonts.
322 * @protected
324 protected $fonts = array();
327 * Array of font files.
328 * @protected
330 protected $FontFiles = array();
333 * Array of encoding differences.
334 * @protected
336 protected $diffs = array();
339 * Array of used images.
340 * @protected
342 protected $images = array();
345 * Array of cached files.
346 * @protected
348 protected $cached_files = array();
351 * Array of Annotations in pages.
352 * @protected
354 protected $PageAnnots = array();
357 * Array of internal links.
358 * @protected
360 protected $links = array();
363 * Current font family.
364 * @protected
366 protected $FontFamily;
369 * Current font style.
370 * @protected
372 protected $FontStyle;
375 * Current font ascent (distance between font top and baseline).
376 * @protected
377 * @since 2.8.000 (2007-03-29)
379 protected $FontAscent;
382 * Current font descent (distance between font bottom and baseline).
383 * @protected
384 * @since 2.8.000 (2007-03-29)
386 protected $FontDescent;
389 * Underlining flag.
390 * @protected
392 protected $underline;
395 * Overlining flag.
396 * @protected
398 protected $overline;
401 * Current font info.
402 * @protected
404 protected $CurrentFont;
407 * Current font size in points.
408 * @protected
410 protected $FontSizePt;
413 * Current font size in user unit.
414 * @protected
416 protected $FontSize;
419 * Commands for drawing color.
420 * @protected
422 protected $DrawColor;
425 * Commands for filling color.
426 * @protected
428 protected $FillColor;
431 * Commands for text color.
432 * @protected
434 protected $TextColor;
437 * Indicates whether fill and text colors are different.
438 * @protected
440 protected $ColorFlag;
443 * Automatic page breaking.
444 * @protected
446 protected $AutoPageBreak;
449 * Threshold used to trigger page breaks.
450 * @protected
452 protected $PageBreakTrigger;
455 * Flag set when processing page header.
456 * @protected
458 protected $InHeader = false;
461 * Flag set when processing page footer.
462 * @protected
464 protected $InFooter = false;
467 * Zoom display mode.
468 * @protected
470 protected $ZoomMode;
473 * Layout display mode.
474 * @protected
476 protected $LayoutMode;
479 * If true set the document information dictionary in Unicode.
480 * @protected
482 protected $docinfounicode = true;
485 * Document title.
486 * @protected
488 protected $title = '';
491 * Document subject.
492 * @protected
494 protected $subject = '';
497 * Document author.
498 * @protected
500 protected $author = '';
503 * Document keywords.
504 * @protected
506 protected $keywords = '';
509 * Document creator.
510 * @protected
512 protected $creator = '';
515 * Starting page number.
516 * @protected
518 protected $starting_page_number = 1;
521 * The right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image.
522 * @since 2002-07-31
523 * @author Nicola Asuni
524 * @protected
526 protected $img_rb_x;
529 * The right-bottom corner Y coordinate of last inserted image.
530 * @since 2002-07-31
531 * @author Nicola Asuni
532 * @protected
534 protected $img_rb_y;
537 * Adjusting factor to convert pixels to user units.
538 * @since 2004-06-14
539 * @author Nicola Asuni
540 * @protected
542 protected $imgscale = 1;
545 * Boolean flag set to true when the input text is unicode (require unicode fonts).
546 * @since 2005-01-02
547 * @author Nicola Asuni
548 * @protected
550 protected $isunicode = false;
553 * PDF version.
554 * @since 1.5.3
555 * @protected
557 protected $PDFVersion = '1.7';
560 * ID of the stored default header template (-1 = not set).
561 * @protected
563 protected $header_xobjid = false;
566 * If true reset the Header Xobject template at each page
567 * @protected
569 protected $header_xobj_autoreset = false;
572 * Minimum distance between header and top page margin.
573 * @protected
575 protected $header_margin;
578 * Minimum distance between footer and bottom page margin.
579 * @protected
581 protected $footer_margin;
584 * Original left margin value.
585 * @protected
586 * @since 1.53.0.TC013
588 protected $original_lMargin;
591 * Original right margin value.
592 * @protected
593 * @since 1.53.0.TC013
595 protected $original_rMargin;
598 * Default font used on page header.
599 * @protected
601 protected $header_font;
604 * Default font used on page footer.
605 * @protected
607 protected $footer_font;
610 * Language templates.
611 * @protected
613 protected $l;
616 * Barcode to print on page footer (only if set).
617 * @protected
619 protected $barcode = false;
622 * Boolean flag to print/hide page header.
623 * @protected
625 protected $print_header = true;
628 * Boolean flag to print/hide page footer.
629 * @protected
631 protected $print_footer = true;
634 * Header image logo.
635 * @protected
637 protected $header_logo = '';
640 * Width of header image logo in user units.
641 * @protected
643 protected $header_logo_width = 30;
646 * Title to be printed on default page header.
647 * @protected
649 protected $header_title = '';
652 * String to pring on page header after title.
653 * @protected
655 protected $header_string = '';
658 * Color for header text (RGB array).
659 * @since 5.9.174 (2012-07-25)
660 * @protected
662 protected $header_text_color = array(0,0,0);
665 * Color for header line (RGB array).
666 * @since 5.9.174 (2012-07-25)
667 * @protected
669 protected $header_line_color = array(0,0,0);
672 * Color for footer text (RGB array).
673 * @since 5.9.174 (2012-07-25)
674 * @protected
676 protected $footer_text_color = array(0,0,0);
679 * Color for footer line (RGB array).
680 * @since 5.9.174 (2012-07-25)
681 * @protected
683 protected $footer_line_color = array(0,0,0);
686 * Text shadow data array.
687 * @since 5.9.174 (2012-07-25)
688 * @protected
690 protected $txtshadow = array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal');
693 * Default number of columns for html table.
694 * @protected
696 protected $default_table_columns = 4;
698 // variables for html parser
701 * HTML PARSER: array to store current link and rendering styles.
702 * @protected
704 protected $HREF = array();
707 * List of available fonts on filesystem.
708 * @protected
710 protected $fontlist = array();
713 * Current foreground color.
714 * @protected
716 protected $fgcolor;
719 * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
720 * @protected
722 protected $listordered = array();
725 * HTML PARSER: array count list items on nested lists.
726 * @protected
728 protected $listcount = array();
731 * HTML PARSER: current list nesting level.
732 * @protected
734 protected $listnum = 0;
737 * HTML PARSER: indent amount for lists.
738 * @protected
740 protected $listindent = 0;
743 * HTML PARSER: current list indententation level.
744 * @protected
746 protected $listindentlevel = 0;
749 * Current background color.
750 * @protected
752 protected $bgcolor;
755 * Temporary font size in points.
756 * @protected
758 protected $tempfontsize = 10;
761 * Spacer string for LI tags.
762 * @protected
764 protected $lispacer = '';
767 * Default encoding.
768 * @protected
769 * @since 1.53.0.TC010
771 protected $encoding = 'UTF-8';
774 * PHP internal encoding.
775 * @protected
776 * @since 1.53.0.TC016
778 protected $internal_encoding;
781 * Boolean flag to indicate if the document language is Right-To-Left.
782 * @protected
783 * @since 2.0.000
785 protected $rtl = false;
788 * Boolean flag used to force RTL or LTR string direction.
789 * @protected
790 * @since 2.0.000
792 protected $tmprtl = false;
794 // --- Variables used for document encryption:
797 * IBoolean flag indicating whether document is protected.
798 * @protected
799 * @since 2.0.000 (2008-01-02)
801 protected $encrypted;
804 * Array containing encryption settings.
805 * @protected
806 * @since 5.0.005 (2010-05-11)
808 protected $encryptdata = array();
811 * Last RC4 key encrypted (cached for optimisation).
812 * @protected
813 * @since 2.0.000 (2008-01-02)
815 protected $last_enc_key;
818 * Last RC4 computed key.
819 * @protected
820 * @since 2.0.000 (2008-01-02)
822 protected $last_enc_key_c;
825 * File ID (used on document trailer).
826 * @protected
827 * @since 5.0.005 (2010-05-12)
829 protected $file_id;
831 // --- bookmark ---
834 * Outlines for bookmark.
835 * @protected
836 * @since 2.1.002 (2008-02-12)
838 protected $outlines = array();
841 * Outline root for bookmark.
842 * @protected
843 * @since 2.1.002 (2008-02-12)
845 protected $OutlineRoot;
847 // --- javascript and form ---
850 * Javascript code.
851 * @protected
852 * @since 2.1.002 (2008-02-12)
854 protected $javascript = '';
857 * Javascript counter.
858 * @protected
859 * @since 2.1.002 (2008-02-12)
861 protected $n_js;
864 * line through state
865 * @protected
866 * @since 2.8.000 (2008-03-19)
868 protected $linethrough;
871 * Array with additional document-wide usage rights for the document.
872 * @protected
873 * @since 5.8.014 (2010-08-23)
875 protected $ur = array();
878 * DPI (Dot Per Inch) Document Resolution (do not change).
879 * @protected
880 * @since 3.0.000 (2008-03-27)
882 protected $dpi = 72;
885 * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
886 * @protected
887 * @since 3.0.000 (2008-03-27)
889 protected $newpagegroup = array();
892 * Array that contains the number of pages in each page group.
893 * @protected
894 * @since 3.0.000 (2008-03-27)
896 protected $pagegroups = array();
899 * Current page group number.
900 * @protected
901 * @since 3.0.000 (2008-03-27)
903 protected $currpagegroup = 0;
906 * Array of transparency objects and parameters.
907 * @protected
908 * @since 3.0.000 (2008-03-27)
910 protected $extgstates;
913 * Set the default JPEG compression quality (1-100).
914 * @protected
915 * @since 3.0.000 (2008-03-27)
917 protected $jpeg_quality;
920 * Default cell height ratio.
921 * @protected
922 * @since 3.0.014 (2008-05-23)
924 protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
927 * PDF viewer preferences.
928 * @protected
929 * @since 3.1.000 (2008-06-09)
931 protected $viewer_preferences;
934 * A name object specifying how the document should be displayed when opened.
935 * @protected
936 * @since 3.1.000 (2008-06-09)
938 protected $PageMode;
941 * Array for storing gradient information.
942 * @protected
943 * @since 3.1.000 (2008-06-09)
945 protected $gradients = array();
948 * Array used to store positions inside the pages buffer (keys are the page numbers).
949 * @protected
950 * @since 3.2.000 (2008-06-26)
952 protected $intmrk = array();
955 * Array used to store positions inside the pages buffer (keys are the page numbers).
956 * @protected
957 * @since 5.7.000 (2010-08-03)
959 protected $bordermrk = array();
962 * Array used to store page positions to track empty pages (keys are the page numbers).
963 * @protected
964 * @since 5.8.007 (2010-08-18)
966 protected $emptypagemrk = array();
969 * Array used to store content positions inside the pages buffer (keys are the page numbers).
970 * @protected
971 * @since 4.6.021 (2009-07-20)
973 protected $cntmrk = array();
976 * Array used to store footer positions of each page.
977 * @protected
978 * @since 3.2.000 (2008-07-01)
980 protected $footerpos = array();
983 * Array used to store footer length of each page.
984 * @protected
985 * @since 4.0.014 (2008-07-29)
987 protected $footerlen = array();
990 * Boolean flag to indicate if a new line is created.
991 * @protected
992 * @since 3.2.000 (2008-07-01)
994 protected $newline = true;
997 * End position of the latest inserted line.
998 * @protected
999 * @since 3.2.000 (2008-07-01)
1001 protected $endlinex = 0;
1004 * PDF string for width value of the last line.
1005 * @protected
1006 * @since 4.0.006 (2008-07-16)
1008 protected $linestyleWidth = '';
1011 * PDF string for CAP value of the last line.
1012 * @protected
1013 * @since 4.0.006 (2008-07-16)
1015 protected $linestyleCap = '0 J';
1018 * PDF string for join value of the last line.
1019 * @protected
1020 * @since 4.0.006 (2008-07-16)
1022 protected $linestyleJoin = '0 j';
1025 * PDF string for dash value of the last line.
1026 * @protected
1027 * @since 4.0.006 (2008-07-16)
1029 protected $linestyleDash = '[] 0 d';
1032 * Boolean flag to indicate if marked-content sequence is open.
1033 * @protected
1034 * @since 4.0.013 (2008-07-28)
1036 protected $openMarkedContent = false;
1039 * Count the latest inserted vertical spaces on HTML.
1040 * @protected
1041 * @since 4.0.021 (2008-08-24)
1043 protected $htmlvspace = 0;
1046 * Array of Spot colors.
1047 * @protected
1048 * @since 4.0.024 (2008-09-12)
1050 protected $spot_colors = array();
1053 * Symbol used for HTML unordered list items.
1054 * @protected
1055 * @since 4.0.028 (2008-09-26)
1057 protected $lisymbol = '';
1060 * String used to mark the beginning and end of EPS image blocks.
1061 * @protected
1062 * @since 4.1.000 (2008-10-18)
1064 protected $epsmarker = 'x#!#EPS#!#x';
1067 * Array of transformation matrix.
1068 * @protected
1069 * @since 4.2.000 (2008-10-29)
1071 protected $transfmatrix = array();
1074 * Current key for transformation matrix.
1075 * @protected
1076 * @since 4.8.005 (2009-09-17)
1078 protected $transfmatrix_key = 0;
1081 * Booklet mode for double-sided pages.
1082 * @protected
1083 * @since 4.2.000 (2008-10-29)
1085 protected $booklet = false;
1088 * Epsilon value used for float calculations.
1089 * @protected
1090 * @since 4.2.000 (2008-10-29)
1092 protected $feps = 0.005;
1095 * Array used for custom vertical spaces for HTML tags.
1096 * @protected
1097 * @since 4.2.001 (2008-10-30)
1099 protected $tagvspaces = array();
1102 * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1103 * @protected
1104 * @since 4.2.007 (2008-11-12)
1106 protected $customlistindent = -1;
1109 * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
1110 * @protected
1111 * @since 4.2.010 (2008-11-14)
1113 protected $opencell = true;
1116 * Array of files to embedd.
1117 * @protected
1118 * @since 4.4.000 (2008-12-07)
1120 protected $embeddedfiles = array();
1123 * Boolean flag to indicate if we are inside a PRE tag.
1124 * @protected
1125 * @since 4.4.001 (2008-12-08)
1127 protected $premode = false;
1130 * Array used to store positions of graphics transformation blocks inside the page buffer.
1131 * keys are the page numbers
1132 * @protected
1133 * @since 4.4.002 (2008-12-09)
1135 protected $transfmrk = array();
1138 * Default color for html links.
1139 * @protected
1140 * @since 4.4.003 (2008-12-09)
1142 protected $htmlLinkColorArray = array(0, 0, 255);
1145 * Default font style to add to html links.
1146 * @protected
1147 * @since 4.4.003 (2008-12-09)
1149 protected $htmlLinkFontStyle = 'U';
1152 * Counts the number of pages.
1153 * @protected
1154 * @since 4.5.000 (2008-12-31)
1156 protected $numpages = 0;
1159 * Array containing page lengths in bytes.
1160 * @protected
1161 * @since 4.5.000 (2008-12-31)
1163 protected $pagelen = array();
1166 * Counts the number of pages.
1167 * @protected
1168 * @since 4.5.000 (2008-12-31)
1170 protected $numimages = 0;
1173 * Store the image keys.
1174 * @protected
1175 * @since 4.5.000 (2008-12-31)
1177 protected $imagekeys = array();
1180 * Length of the buffer in bytes.
1181 * @protected
1182 * @since 4.5.000 (2008-12-31)
1184 protected $bufferlen = 0;
1187 * If true enables disk caching.
1188 * @protected
1189 * @since 4.5.000 (2008-12-31)
1191 protected $diskcache = false;
1194 * Counts the number of fonts.
1195 * @protected
1196 * @since 4.5.000 (2009-01-02)
1198 protected $numfonts = 0;
1201 * Store the font keys.
1202 * @protected
1203 * @since 4.5.000 (2009-01-02)
1205 protected $fontkeys = array();
1208 * Store the font object IDs.
1209 * @protected
1210 * @since 4.8.001 (2009-09-09)
1212 protected $font_obj_ids = array();
1215 * Store the fage status (true when opened, false when closed).
1216 * @protected
1217 * @since 4.5.000 (2009-01-02)
1219 protected $pageopen = array();
1222 * Default monospace font.
1223 * @protected
1224 * @since 4.5.025 (2009-03-10)
1226 protected $default_monospaced_font = 'courier';
1229 * Cloned copy of the current class object.
1230 * @protected
1231 * @since 4.5.029 (2009-03-19)
1233 protected $objcopy;
1236 * Array used to store the lengths of cache files.
1237 * @protected
1238 * @since 4.5.029 (2009-03-19)
1240 protected $cache_file_length = array();
1243 * Table header content to be repeated on each new page.
1244 * @protected
1245 * @since 4.5.030 (2009-03-20)
1247 protected $thead = '';
1250 * Margins used for table header.
1251 * @protected
1252 * @since 4.5.030 (2009-03-20)
1254 protected $theadMargins = array();
1257 * Boolean flag to enable document digital signature.
1258 * @protected
1259 * @since 4.6.005 (2009-04-24)
1261 protected $sign = false;
1264 * Digital signature data.
1265 * @protected
1266 * @since 4.6.005 (2009-04-24)
1268 protected $signature_data = array();
1271 * Digital signature max length.
1272 * @protected
1273 * @since 4.6.005 (2009-04-24)
1275 protected $signature_max_length = 11742;
1278 * Data for digital signature appearance.
1279 * @protected
1280 * @since 5.3.011 (2010-06-16)
1282 protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1285 * Array of empty digital signature appearances.
1286 * @protected
1287 * @since 5.9.101 (2011-07-06)
1289 protected $empty_signature_appearance = array();
1292 * Regular expression used to find blank characters (required for word-wrapping).
1293 * @protected
1294 * @since 4.6.006 (2009-04-28)
1296 protected $re_spaces = '/[^\S\xa0]/';
1299 * Array of $re_spaces parts.
1300 * @protected
1301 * @since 5.5.011 (2010-07-09)
1303 protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1306 * Digital signature object ID.
1307 * @protected
1308 * @since 4.6.022 (2009-06-23)
1310 protected $sig_obj_id = 0;
1313 * ID of page objects.
1314 * @protected
1315 * @since 4.7.000 (2009-08-29)
1317 protected $page_obj_id = array();
1320 * List of form annotations IDs.
1321 * @protected
1322 * @since 4.8.000 (2009-09-07)
1324 protected $form_obj_id = array();
1327 * Deafult Javascript field properties. Possible values are described on official Javascript for Acrobat API reference. Annotation options can be directly specified using the 'aopt' entry.
1328 * @protected
1329 * @since 4.8.000 (2009-09-07)
1331 protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1334 * Javascript objects array.
1335 * @protected
1336 * @since 4.8.000 (2009-09-07)
1338 protected $js_objects = array();
1341 * Current form action (used during XHTML rendering).
1342 * @protected
1343 * @since 4.8.000 (2009-09-07)
1345 protected $form_action = '';
1348 * Current form encryption type (used during XHTML rendering).
1349 * @protected
1350 * @since 4.8.000 (2009-09-07)
1352 protected $form_enctype = 'application/x-www-form-urlencoded';
1355 * Current method to submit forms.
1356 * @protected
1357 * @since 4.8.000 (2009-09-07)
1359 protected $form_mode = 'post';
1362 * List of fonts used on form fields (fontname => fontkey).
1363 * @protected
1364 * @since 4.8.001 (2009-09-09)
1366 protected $annotation_fonts = array();
1369 * List of radio buttons parent objects.
1370 * @protected
1371 * @since 4.8.001 (2009-09-09)
1373 protected $radiobutton_groups = array();
1376 * List of radio group objects IDs.
1377 * @protected
1378 * @since 4.8.001 (2009-09-09)
1380 protected $radio_groups = array();
1383 * Text indentation value (used for text-indent CSS attribute).
1384 * @protected
1385 * @since 4.8.006 (2009-09-23)
1387 protected $textindent = 0;
1390 * Store page number when startTransaction() is called.
1391 * @protected
1392 * @since 4.8.006 (2009-09-23)
1394 protected $start_transaction_page = 0;
1397 * Store Y position when startTransaction() is called.
1398 * @protected
1399 * @since 4.9.001 (2010-03-28)
1401 protected $start_transaction_y = 0;
1404 * True when we are printing the thead section on a new page.
1405 * @protected
1406 * @since 4.8.027 (2010-01-25)
1408 protected $inthead = false;
1411 * Array of column measures (width, space, starting Y position).
1412 * @protected
1413 * @since 4.9.001 (2010-03-28)
1415 protected $columns = array();
1418 * Number of colums.
1419 * @protected
1420 * @since 4.9.001 (2010-03-28)
1422 protected $num_columns = 1;
1425 * Current column number.
1426 * @protected
1427 * @since 4.9.001 (2010-03-28)
1429 protected $current_column = 0;
1432 * Starting page for columns.
1433 * @protected
1434 * @since 4.9.001 (2010-03-28)
1436 protected $column_start_page = 0;
1439 * Maximum page and column selected.
1440 * @protected
1441 * @since 5.8.000 (2010-08-11)
1443 protected $maxselcol = array('page' => 0, 'column' => 0);
1446 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1447 * @protected
1448 * @since 5.8.000 (2010-08-11)
1450 protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1453 * Text rendering mode: 0 = Fill text; 1 = Stroke text; 2 = Fill, then stroke text; 3 = Neither fill nor stroke text (invisible); 4 = Fill text and add to path for clipping; 5 = Stroke text and add to path for clipping; 6 = Fill, then stroke text and add to path for clipping; 7 = Add text to path for clipping.
1454 * @protected
1455 * @since 4.9.008 (2010-04-03)
1457 protected $textrendermode = 0;
1460 * Text stroke width in doc units.
1461 * @protected
1462 * @since 4.9.008 (2010-04-03)
1464 protected $textstrokewidth = 0;
1467 * Current stroke color.
1468 * @protected
1469 * @since 4.9.008 (2010-04-03)
1471 protected $strokecolor;
1474 * Default unit of measure for document.
1475 * @protected
1476 * @since 5.0.000 (2010-04-22)
1478 protected $pdfunit = 'mm';
1481 * Boolean flag true when we are on TOC (Table Of Content) page.
1482 * @protected
1484 protected $tocpage = false;
1487 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1488 * @protected
1489 * @since 5.0.000 (2010-04-26)
1491 protected $rasterize_vector_images = false;
1494 * Boolean flag: if true enables font subsetting by default.
1495 * @protected
1496 * @since 5.3.002 (2010-06-07)
1498 protected $font_subsetting = true;
1501 * Array of default graphic settings.
1502 * @protected
1503 * @since 5.5.008 (2010-07-02)
1505 protected $default_graphic_vars = array();
1508 * Array of XObjects.
1509 * @protected
1510 * @since 5.8.014 (2010-08-23)
1512 protected $xobjects = array();
1515 * Boolean value true when we are inside an XObject.
1516 * @protected
1517 * @since 5.8.017 (2010-08-24)
1519 protected $inxobj = false;
1522 * Current XObject ID.
1523 * @protected
1524 * @since 5.8.017 (2010-08-24)
1526 protected $xobjid = '';
1529 * Percentage of character stretching.
1530 * @protected
1531 * @since 5.9.000 (2010-09-29)
1533 protected $font_stretching = 100;
1536 * Increases or decreases the space between characters in a text by the specified amount (tracking).
1537 * @protected
1538 * @since 5.9.000 (2010-09-29)
1540 protected $font_spacing = 0;
1543 * Array of no-write regions.
1544 * ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right)
1545 * @protected
1546 * @since 5.9.003 (2010-10-14)
1548 protected $page_regions = array();
1551 * Boolean value true when page region check is active.
1552 * @protected
1554 protected $check_page_regions = true;
1557 * Array of PDF layers data.
1558 * @protected
1559 * @since 5.9.102 (2011-07-13)
1561 protected $pdflayers = array();
1564 * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1565 * @protected
1566 * @since 5.9.097 (2011-06-23)
1568 protected $dests = array();
1571 * Object ID for Named Destinations
1572 * @protected
1573 * @since 5.9.097 (2011-06-23)
1575 protected $n_dests;
1578 * Embedded Files Names
1579 * @protected
1580 * @since 5.9.204 (2013-01-23)
1582 protected $efnames = array();
1585 * Directory used for the last SVG image.
1586 * @protected
1587 * @since 5.0.000 (2010-05-05)
1589 protected $svgdir = '';
1592 * Deafult unit of measure for SVG.
1593 * @protected
1594 * @since 5.0.000 (2010-05-02)
1596 protected $svgunit = 'px';
1599 * Array of SVG gradients.
1600 * @protected
1601 * @since 5.0.000 (2010-05-02)
1603 protected $svggradients = array();
1606 * ID of last SVG gradient.
1607 * @protected
1608 * @since 5.0.000 (2010-05-02)
1610 protected $svggradientid = 0;
1613 * Boolean value true when in SVG defs group.
1614 * @protected
1615 * @since 5.0.000 (2010-05-02)
1617 protected $svgdefsmode = false;
1620 * Array of SVG defs.
1621 * @protected
1622 * @since 5.0.000 (2010-05-02)
1624 protected $svgdefs = array();
1627 * Boolean value true when in SVG clipPath tag.
1628 * @protected
1629 * @since 5.0.000 (2010-04-26)
1631 protected $svgclipmode = false;
1634 * Array of SVG clipPath commands.
1635 * @protected
1636 * @since 5.0.000 (2010-05-02)
1638 protected $svgclippaths = array();
1641 * Array of SVG clipPath tranformation matrix.
1642 * @protected
1643 * @since 5.8.022 (2010-08-31)
1645 protected $svgcliptm = array();
1648 * ID of last SVG clipPath.
1649 * @protected
1650 * @since 5.0.000 (2010-05-02)
1652 protected $svgclipid = 0;
1655 * SVG text.
1656 * @protected
1657 * @since 5.0.000 (2010-05-02)
1659 protected $svgtext = '';
1662 * SVG text properties.
1663 * @protected
1664 * @since 5.8.013 (2010-08-23)
1666 protected $svgtextmode = array();
1669 * Array of SVG properties.
1670 * @protected
1671 * @since 5.0.000 (2010-05-02)
1673 protected $svgstyles = array(array(
1674 'alignment-baseline' => 'auto',
1675 'baseline-shift' => 'baseline',
1676 'clip' => 'auto',
1677 'clip-path' => 'none',
1678 'clip-rule' => 'nonzero',
1679 'color' => 'black',
1680 'color-interpolation' => 'sRGB',
1681 'color-interpolation-filters' => 'linearRGB',
1682 'color-profile' => 'auto',
1683 'color-rendering' => 'auto',
1684 'cursor' => 'auto',
1685 'direction' => 'ltr',
1686 'display' => 'inline',
1687 'dominant-baseline' => 'auto',
1688 'enable-background' => 'accumulate',
1689 'fill' => 'black',
1690 'fill-opacity' => 1,
1691 'fill-rule' => 'nonzero',
1692 'filter' => 'none',
1693 'flood-color' => 'black',
1694 'flood-opacity' => 1,
1695 'font' => '',
1696 'font-family' => 'helvetica',
1697 'font-size' => 'medium',
1698 'font-size-adjust' => 'none',
1699 'font-stretch' => 'normal',
1700 'font-style' => 'normal',
1701 'font-variant' => 'normal',
1702 'font-weight' => 'normal',
1703 'glyph-orientation-horizontal' => '0deg',
1704 'glyph-orientation-vertical' => 'auto',
1705 'image-rendering' => 'auto',
1706 'kerning' => 'auto',
1707 'letter-spacing' => 'normal',
1708 'lighting-color' => 'white',
1709 'marker' => '',
1710 'marker-end' => 'none',
1711 'marker-mid' => 'none',
1712 'marker-start' => 'none',
1713 'mask' => 'none',
1714 'opacity' => 1,
1715 'overflow' => 'auto',
1716 'pointer-events' => 'visiblePainted',
1717 'shape-rendering' => 'auto',
1718 'stop-color' => 'black',
1719 'stop-opacity' => 1,
1720 'stroke' => 'none',
1721 'stroke-dasharray' => 'none',
1722 'stroke-dashoffset' => 0,
1723 'stroke-linecap' => 'butt',
1724 'stroke-linejoin' => 'miter',
1725 'stroke-miterlimit' => 4,
1726 'stroke-opacity' => 1,
1727 'stroke-width' => 1,
1728 'text-anchor' => 'start',
1729 'text-decoration' => 'none',
1730 'text-rendering' => 'auto',
1731 'unicode-bidi' => 'normal',
1732 'visibility' => 'visible',
1733 'word-spacing' => 'normal',
1734 'writing-mode' => 'lr-tb',
1735 'text-color' => 'black',
1736 'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1740 * If true force sRGB color profile for all document.
1741 * @protected
1742 * @since 5.9.121 (2011-09-28)
1744 protected $force_srgb = false;
1747 * If true set the document to PDF/A mode.
1748 * @protected
1749 * @since 5.9.121 (2011-09-27)
1751 protected $pdfa_mode = false;
1754 * Document creation date-time
1755 * @protected
1756 * @since 5.9.152 (2012-03-22)
1758 protected $doc_creation_timestamp;
1761 * Document modification date-time
1762 * @protected
1763 * @since 5.9.152 (2012-03-22)
1765 protected $doc_modification_timestamp;
1768 * Custom XMP data.
1769 * @protected
1770 * @since 5.9.128 (2011-10-06)
1772 protected $custom_xmp = '';
1775 * Overprint mode array.
1776 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1777 * @protected
1778 * @since 5.9.152 (2012-03-23)
1780 protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1783 * Alpha mode array.
1784 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1785 * @protected
1786 * @since 5.9.152 (2012-03-23)
1788 protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1791 * Define the page boundaries boxes to be set on document.
1792 * @protected
1793 * @since 5.9.152 (2012-03-23)
1795 protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1798 * If true print TCPDF meta link.
1799 * @protected
1800 * @since 5.9.152 (2012-03-23)
1802 protected $tcpdflink = true;
1805 * Cache array for computed GD gamma values.
1806 * @protected
1807 * @since 5.9.1632 (2012-06-05)
1809 protected $gdgammacache = array();
1811 //------------------------------------------------------------
1812 // METHODS
1813 //------------------------------------------------------------
1816 * This is the class constructor.
1817 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1819 * IMPORTANT: Please note that this method sets the mb_internal_encoding to ASCII, so if you are using the mbstring module functions with TCPDF you need to correctly set/unset the mb_internal_encoding when needed.
1821 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
1822 * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
1823 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
1824 * @param $unicode (boolean) TRUE means that the input text is unicode (default = true)
1825 * @param $encoding (string) Charset encoding (used only when converting back html entities); default is UTF-8.
1826 * @param $diskcache (boolean) If TRUE reduce the RAM memory usage by caching temporary data on filesystem (slower).
1827 * @param $pdfa (boolean) If TRUE set the document to PDF/A mode.
1828 * @public
1829 * @see getPageSizeFromFormat(), setPageFormat()
1831 public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1832 /* Set internal character encoding to ASCII */
1833 if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
1834 $this->internal_encoding = mb_internal_encoding();
1835 mb_internal_encoding('ASCII');
1837 $this->font_obj_ids = array();
1838 $this->page_obj_id = array();
1839 $this->form_obj_id = array();
1840 // set pdf/a mode
1841 $this->pdfa_mode = $pdfa;
1842 $this->force_srgb = false;
1843 // set disk caching
1844 $this->diskcache = $diskcache ? true : false;
1845 // set language direction
1846 $this->rtl = false;
1847 $this->tmprtl = false;
1848 // some checks
1849 $this->_dochecks();
1850 // initialization of properties
1851 $this->isunicode = $unicode;
1852 $this->page = 0;
1853 $this->transfmrk[0] = array();
1854 $this->pagedim = array();
1855 $this->n = 2;
1856 $this->buffer = '';
1857 $this->pages = array();
1858 $this->state = 0;
1859 $this->fonts = array();
1860 $this->FontFiles = array();
1861 $this->diffs = array();
1862 $this->images = array();
1863 $this->links = array();
1864 $this->gradients = array();
1865 $this->InFooter = false;
1866 $this->lasth = 0;
1867 $this->FontFamily = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
1868 $this->FontStyle = '';
1869 $this->FontSizePt = 12;
1870 $this->underline = false;
1871 $this->overline = false;
1872 $this->linethrough = false;
1873 $this->DrawColor = '0 G';
1874 $this->FillColor = '0 g';
1875 $this->TextColor = '0 g';
1876 $this->ColorFlag = false;
1877 $this->pdflayers = array();
1878 // encryption values
1879 $this->encrypted = false;
1880 $this->last_enc_key = '';
1881 // standard Unicode fonts
1882 $this->CoreFonts = array(
1883 'courier'=>'Courier',
1884 'courierB'=>'Courier-Bold',
1885 'courierI'=>'Courier-Oblique',
1886 'courierBI'=>'Courier-BoldOblique',
1887 'helvetica'=>'Helvetica',
1888 'helveticaB'=>'Helvetica-Bold',
1889 'helveticaI'=>'Helvetica-Oblique',
1890 'helveticaBI'=>'Helvetica-BoldOblique',
1891 'times'=>'Times-Roman',
1892 'timesB'=>'Times-Bold',
1893 'timesI'=>'Times-Italic',
1894 'timesBI'=>'Times-BoldItalic',
1895 'symbol'=>'Symbol',
1896 'zapfdingbats'=>'ZapfDingbats'
1898 // set scale factor
1899 $this->setPageUnit($unit);
1900 // set page format and orientation
1901 $this->setPageFormat($format, $orientation);
1902 // page margins (1 cm)
1903 $margin = 28.35 / $this->k;
1904 $this->SetMargins($margin, $margin);
1905 $this->clMargin = $this->lMargin;
1906 $this->crMargin = $this->rMargin;
1907 // internal cell padding
1908 $cpadding = $margin / 10;
1909 $this->setCellPaddings($cpadding, 0, $cpadding, 0);
1910 // cell margins
1911 $this->setCellMargins(0, 0, 0, 0);
1912 // line width (0.2 mm)
1913 $this->LineWidth = 0.57 / $this->k;
1914 $this->linestyleWidth = sprintf('%F w', ($this->LineWidth * $this->k));
1915 $this->linestyleCap = '0 J';
1916 $this->linestyleJoin = '0 j';
1917 $this->linestyleDash = '[] 0 d';
1918 // automatic page break
1919 $this->SetAutoPageBreak(true, (2 * $margin));
1920 // full width display mode
1921 $this->SetDisplayMode('fullwidth');
1922 // compression
1923 $this->SetCompression();
1924 // set default PDF version number
1925 $this->setPDFVersion();
1926 $this->tcpdflink = true;
1927 $this->encoding = $encoding;
1928 $this->HREF = array();
1929 $this->getFontsList();
1930 $this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
1931 $this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
1932 $this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
1933 $this->extgstates = array();
1934 $this->setTextShadow();
1935 // user's rights
1936 $this->sign = false;
1937 $this->ur['enabled'] = false;
1938 $this->ur['document'] = '/FullSave';
1939 $this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
1940 $this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
1941 $this->ur['signature'] = '/Modify';
1942 $this->ur['ef'] = '/Create/Delete/Modify/Import';
1943 $this->ur['formex'] = '';
1944 $this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
1945 $this->empty_signature_appearance = array();
1946 // set default JPEG quality
1947 $this->jpeg_quality = 75;
1948 // initialize some settings
1949 TCPDF_FONTS::utf8Bidi(array(''), '', false, $this->isunicode, $this->CurrentFont);
1950 // set default font
1951 $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
1952 $this->setHeaderFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1953 $this->setFooterFont(array($this->FontFamily, $this->FontStyle, $this->FontSizePt));
1954 // check if PCRE Unicode support is enabled
1955 if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
1956 // PCRE unicode support is turned ON
1957 // \s : any whitespace character
1958 // \p{Z} : any separator
1959 // \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
1960 // \xa0 : Unicode Character 'NO-BREAK SPACE' (U+00A0)
1961 //$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
1962 $this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
1963 } else {
1964 // PCRE unicode support is turned OFF
1965 $this->setSpacesRE('/[^\S\xa0]/');
1967 $this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1968 // set file ID for trailer
1969 $serformat = (is_array($format) ? serialize($format) : $format);
1970 $this->file_id = md5(TCPDF_STATIC::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1971 // set document creation and modification timestamp
1972 $this->doc_creation_timestamp = time();
1973 $this->doc_modification_timestamp = $this->doc_creation_timestamp;
1974 // get default graphic vars
1975 $this->default_graphic_vars = $this->getGraphicVars();
1976 $this->header_xobj_autoreset = false;
1977 $this->custom_xmp = '';
1981 * Default destructor.
1982 * @public
1983 * @since 1.53.0.TC016
1985 public function __destruct() {
1986 // restore internal encoding
1987 if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
1988 mb_internal_encoding($this->internal_encoding);
1990 // unset all class variables
1991 $this->_destroy(true);
1995 * Set the units of measure for the document.
1996 * @param $unit (string) User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
1997 * @public
1998 * @since 3.0.015 (2008-06-06)
2000 public function setPageUnit($unit) {
2001 $unit = strtolower($unit);
2002 //Set scale factor
2003 switch ($unit) {
2004 // points
2005 case 'px':
2006 case 'pt': {
2007 $this->k = 1;
2008 break;
2010 // millimeters
2011 case 'mm': {
2012 $this->k = $this->dpi / 25.4;
2013 break;
2015 // centimeters
2016 case 'cm': {
2017 $this->k = $this->dpi / 2.54;
2018 break;
2020 // inches
2021 case 'in': {
2022 $this->k = $this->dpi;
2023 break;
2025 // unsupported unit
2026 default : {
2027 $this->Error('Incorrect unit: '.$unit);
2028 break;
2031 $this->pdfunit = $unit;
2032 if (isset($this->CurOrientation)) {
2033 $this->setPageOrientation($this->CurOrientation);
2038 * Change the format of the current page
2039 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numners (width, height) or an array containing the following measures and options:<ul>
2040 * <li>['format'] = page format name (one of the above);</li>
2041 * <li>['Rotate'] : The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.</li>
2042 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2043 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2044 * <li>['MediaBox']['llx'] : lower-left x coordinate in points</li>
2045 * <li>['MediaBox']['lly'] : lower-left y coordinate in points</li>
2046 * <li>['MediaBox']['urx'] : upper-right x coordinate in points</li>
2047 * <li>['MediaBox']['ury'] : upper-right y coordinate in points</li>
2048 * <li>['CropBox'] : the visible region of default user space:</li>
2049 * <li>['CropBox']['llx'] : lower-left x coordinate in points</li>
2050 * <li>['CropBox']['lly'] : lower-left y coordinate in points</li>
2051 * <li>['CropBox']['urx'] : upper-right x coordinate in points</li>
2052 * <li>['CropBox']['ury'] : upper-right y coordinate in points</li>
2053 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2054 * <li>['BleedBox']['llx'] : lower-left x coordinate in points</li>
2055 * <li>['BleedBox']['lly'] : lower-left y coordinate in points</li>
2056 * <li>['BleedBox']['urx'] : upper-right x coordinate in points</li>
2057 * <li>['BleedBox']['ury'] : upper-right y coordinate in points</li>
2058 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2059 * <li>['TrimBox']['llx'] : lower-left x coordinate in points</li>
2060 * <li>['TrimBox']['lly'] : lower-left y coordinate in points</li>
2061 * <li>['TrimBox']['urx'] : upper-right x coordinate in points</li>
2062 * <li>['TrimBox']['ury'] : upper-right y coordinate in points</li>
2063 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2064 * <li>['ArtBox']['llx'] : lower-left x coordinate in points</li>
2065 * <li>['ArtBox']['lly'] : lower-left y coordinate in points</li>
2066 * <li>['ArtBox']['urx'] : upper-right x coordinate in points</li>
2067 * <li>['ArtBox']['ury'] : upper-right y coordinate in points</li>
2068 * <li>['BoxColorInfo'] :specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for each of the possible page boundaries other than the MediaBox:</li>
2069 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2070 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2071 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2072 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2073 * <li>['trans'] : the style and duration of the visual transition to use when moving from another page to the given page during a presentation</li>
2074 * <li>['trans']['Dur'] : The page's display duration (also called its advance timing): the maximum length of time, in seconds, that the page shall be displayed during presentations before the viewer application shall automatically advance to the next page.</li>
2075 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2076 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2077 * <li>['trans']['Dm'] : (Split and Blinds transition styles only) The dimension in which the specified transition effect shall occur: H = Horizontal, V = Vertical. Default value: H.</li>
2078 * <li>['trans']['M'] : (Split, Box and Fly transition styles only) The direction of motion for the specified transition effect: I = Inward from the edges of the page, O = Outward from the center of the pageDefault value: I.</li>
2079 * <li>['trans']['Di'] : (Wipe, Glitter, Fly, Cover, Uncover and Push transition styles only) The direction in which the specified transition effect shall moves, expressed in degrees counterclockwise starting from a left-to-right direction. If the value is a number, it shall be one of: 0 = Left to right, 90 = Bottom to top (Wipe only), 180 = Right to left (Wipe only), 270 = Top to bottom, 315 = Top-left to bottom-right (Glitter only). If the value is a name, it shall be None, which is relevant only for the Fly transition when the value of SS is not 1.0. Default value: 0.</li>
2080 * <li>['trans']['SS'] : (Fly transition style only) The starting or ending scale at which the changes shall be drawn. If M specifies an inward transition, the scale of the changes drawn shall progress from SS to 1.0 over the course of the transition. If M specifies an outward transition, the scale of the changes drawn shall progress from 1.0 to SS over the course of the transition. Default: 1.0.</li>
2081 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2082 * </ul>
2083 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul>
2084 * <li>P or Portrait (default)</li>
2085 * <li>L or Landscape</li>
2086 * <li>'' (empty string) for automatic orientation</li>
2087 * </ul>
2088 * @protected
2089 * @since 3.0.015 (2008-06-06)
2090 * @see getPageSizeFromFormat()
2092 protected function setPageFormat($format, $orientation='P') {
2093 if (!empty($format) AND isset($this->pagedim[$this->page])) {
2094 // remove inherited values
2095 unset($this->pagedim[$this->page]);
2097 if (is_string($format)) {
2098 // get page measures from format name
2099 $pf = TCPDF_STATIC::getPageSizeFromFormat($format);
2100 $this->fwPt = $pf[0];
2101 $this->fhPt = $pf[1];
2102 } else {
2103 // the boundaries of the physical medium on which the page shall be displayed or printed
2104 if (isset($format['MediaBox'])) {
2105 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', $format['MediaBox']['llx'], $format['MediaBox']['lly'], $format['MediaBox']['urx'], $format['MediaBox']['ury'], false, $this->k, $this->pagedim);
2106 $this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
2107 $this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
2108 } else {
2109 if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2110 $pf = array(($format[0] * $this->k), ($format[1] * $this->k));
2111 } else {
2112 if (!isset($format['format'])) {
2113 // default value
2114 $format['format'] = 'A4';
2116 $pf = TCPDF_STATIC::getPageSizeFromFormat($format['format']);
2118 $this->fwPt = $pf[0];
2119 $this->fhPt = $pf[1];
2120 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2122 // the visible region of default user space
2123 if (isset($format['CropBox'])) {
2124 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'CropBox', $format['CropBox']['llx'], $format['CropBox']['lly'], $format['CropBox']['urx'], $format['CropBox']['ury'], false, $this->k, $this->pagedim);
2126 // the region to which the contents of the page shall be clipped when output in a production environment
2127 if (isset($format['BleedBox'])) {
2128 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'BleedBox', $format['BleedBox']['llx'], $format['BleedBox']['lly'], $format['BleedBox']['urx'], $format['BleedBox']['ury'], false, $this->k, $this->pagedim);
2130 // the intended dimensions of the finished page after trimming
2131 if (isset($format['TrimBox'])) {
2132 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'TrimBox', $format['TrimBox']['llx'], $format['TrimBox']['lly'], $format['TrimBox']['urx'], $format['TrimBox']['ury'], false, $this->k, $this->pagedim);
2134 // the page's meaningful content (including potential white space)
2135 if (isset($format['ArtBox'])) {
2136 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'ArtBox', $format['ArtBox']['llx'], $format['ArtBox']['lly'], $format['ArtBox']['urx'], $format['ArtBox']['ury'], false, $this->k, $this->pagedim);
2138 // specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2139 if (isset($format['BoxColorInfo'])) {
2140 $this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
2142 if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
2143 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2144 $this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
2146 if (isset($format['PZ'])) {
2147 // The page's preferred zoom (magnification) factor
2148 $this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
2150 if (isset($format['trans'])) {
2151 // The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2152 if (isset($format['trans']['Dur'])) {
2153 // The page's display duration
2154 $this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
2156 $stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2157 if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2158 // The transition style that shall be used when moving to this page from another during a presentation
2159 $this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
2160 $valid_effect = array('Split', 'Blinds');
2161 $valid_vals = array('H', 'V');
2162 if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2163 $this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
2165 $valid_effect = array('Split', 'Box', 'Fly');
2166 $valid_vals = array('I', 'O');
2167 if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2168 $this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
2170 $valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2171 if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2172 if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2173 OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2174 OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2175 $this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
2178 if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2179 $this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
2181 if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2182 $this->pagedim[$this->page]['trans']['B'] = 'true';
2184 } else {
2185 $this->pagedim[$this->page]['trans']['S'] = 'R';
2187 if (isset($format['trans']['D'])) {
2188 // The duration of the transition effect, in seconds
2189 $this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
2190 } else {
2191 $this->pagedim[$this->page]['trans']['D'] = 1;
2195 $this->setPageOrientation($orientation);
2199 * Set page orientation.
2200 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
2201 * @param $autopagebreak (boolean) Boolean indicating if auto-page-break mode should be on or off.
2202 * @param $bottommargin (float) bottom margin of the page.
2203 * @public
2204 * @since 3.0.015 (2008-06-06)
2206 public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
2207 if (!isset($this->pagedim[$this->page]['MediaBox'])) {
2208 // the boundaries of the physical medium on which the page shall be displayed or printed
2209 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true, $this->k, $this->pagedim);
2211 if (!isset($this->pagedim[$this->page]['CropBox'])) {
2212 // the visible region of default user space
2213 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'CropBox', $this->pagedim[$this->page]['MediaBox']['llx'], $this->pagedim[$this->page]['MediaBox']['lly'], $this->pagedim[$this->page]['MediaBox']['urx'], $this->pagedim[$this->page]['MediaBox']['ury'], true, $this->k, $this->pagedim);
2215 if (!isset($this->pagedim[$this->page]['BleedBox'])) {
2216 // the region to which the contents of the page shall be clipped when output in a production environment
2217 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'BleedBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
2219 if (!isset($this->pagedim[$this->page]['TrimBox'])) {
2220 // the intended dimensions of the finished page after trimming
2221 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'TrimBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
2223 if (!isset($this->pagedim[$this->page]['ArtBox'])) {
2224 // the page's meaningful content (including potential white space)
2225 $this->pagedim = TCPDF_STATIC::setPageBoxes($this->page, 'ArtBox', $this->pagedim[$this->page]['CropBox']['llx'], $this->pagedim[$this->page]['CropBox']['lly'], $this->pagedim[$this->page]['CropBox']['urx'], $this->pagedim[$this->page]['CropBox']['ury'], true, $this->k, $this->pagedim);
2227 if (!isset($this->pagedim[$this->page]['Rotate'])) {
2228 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2229 $this->pagedim[$this->page]['Rotate'] = 0;
2231 if (!isset($this->pagedim[$this->page]['PZ'])) {
2232 // The page's preferred zoom (magnification) factor
2233 $this->pagedim[$this->page]['PZ'] = 1;
2235 if ($this->fwPt > $this->fhPt) {
2236 // landscape
2237 $default_orientation = 'L';
2238 } else {
2239 // portrait
2240 $default_orientation = 'P';
2242 $valid_orientations = array('P', 'L');
2243 if (empty($orientation)) {
2244 $orientation = $default_orientation;
2245 } else {
2246 $orientation = strtoupper($orientation[0]);
2248 if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2249 $this->CurOrientation = $orientation;
2250 $this->wPt = $this->fhPt;
2251 $this->hPt = $this->fwPt;
2252 } else {
2253 $this->CurOrientation = $default_orientation;
2254 $this->wPt = $this->fwPt;
2255 $this->hPt = $this->fhPt;
2257 if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
2258 // swap X and Y coordinates (change page orientation)
2259 $this->pagedim = TCPDF_STATIC::swapPageBoxCoordinates($this->page, $this->pagedim);
2261 $this->w = ($this->wPt / $this->k);
2262 $this->h = ($this->hPt / $this->k);
2263 if (TCPDF_STATIC::empty_string($autopagebreak)) {
2264 if (isset($this->AutoPageBreak)) {
2265 $autopagebreak = $this->AutoPageBreak;
2266 } else {
2267 $autopagebreak = true;
2270 if (TCPDF_STATIC::empty_string($bottommargin)) {
2271 if (isset($this->bMargin)) {
2272 $bottommargin = $this->bMargin;
2273 } else {
2274 // default value = 2 cm
2275 $bottommargin = 2 * 28.35 / $this->k;
2278 $this->SetAutoPageBreak($autopagebreak, $bottommargin);
2279 // store page dimensions
2280 $this->pagedim[$this->page]['w'] = $this->wPt;
2281 $this->pagedim[$this->page]['h'] = $this->hPt;
2282 $this->pagedim[$this->page]['wk'] = $this->w;
2283 $this->pagedim[$this->page]['hk'] = $this->h;
2284 $this->pagedim[$this->page]['tm'] = $this->tMargin;
2285 $this->pagedim[$this->page]['bm'] = $bottommargin;
2286 $this->pagedim[$this->page]['lm'] = $this->lMargin;
2287 $this->pagedim[$this->page]['rm'] = $this->rMargin;
2288 $this->pagedim[$this->page]['pb'] = $autopagebreak;
2289 $this->pagedim[$this->page]['or'] = $this->CurOrientation;
2290 $this->pagedim[$this->page]['olm'] = $this->original_lMargin;
2291 $this->pagedim[$this->page]['orm'] = $this->original_rMargin;
2295 * Set regular expression to detect withespaces or word separators.
2296 * The pattern delimiter must be the forward-slash character "/".
2297 * Some example patterns are:
2298 * <pre>
2299 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2300 * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2301 * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2302 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2303 * \s : any whitespace character
2304 * \p{Z} : any separator
2305 * \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2306 * \xa0 : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2307 * </pre>
2308 * @param $re (string) regular expression (leave empty for default).
2309 * @public
2310 * @since 4.6.016 (2009-06-15)
2312 public function setSpacesRE($re='/[^\S\xa0]/') {
2313 $this->re_spaces = $re;
2314 $re_parts = explode('/', $re);
2315 // get pattern parts
2316 $this->re_space = array();
2317 if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2318 $this->re_space['p'] = $re_parts[1];
2319 } else {
2320 $this->re_space['p'] = '[\s]';
2322 // set pattern modifiers
2323 if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2324 $this->re_space['m'] = $re_parts[2];
2325 } else {
2326 $this->re_space['m'] = '';
2331 * Enable or disable Right-To-Left language mode
2332 * @param $enable (Boolean) if true enable Right-To-Left language mode.
2333 * @param $resetx (Boolean) if true reset the X position on direction change.
2334 * @public
2335 * @since 2.0.000 (2008-01-03)
2337 public function setRTL($enable, $resetx=true) {
2338 $enable = $enable ? true : false;
2339 $resetx = ($resetx AND ($enable != $this->rtl));
2340 $this->rtl = $enable;
2341 $this->tmprtl = false;
2342 if ($resetx) {
2343 $this->Ln(0);
2348 * Return the RTL status
2349 * @return boolean
2350 * @public
2351 * @since 4.0.012 (2008-07-24)
2353 public function getRTL() {
2354 return $this->rtl;
2358 * Force temporary RTL language direction
2359 * @param $mode (mixed) can be false, 'L' for LTR or 'R' for RTL
2360 * @public
2361 * @since 2.1.000 (2008-01-09)
2363 public function setTempRTL($mode) {
2364 $newmode = false;
2365 switch (strtoupper($mode)) {
2366 case 'LTR':
2367 case 'L': {
2368 if ($this->rtl) {
2369 $newmode = 'L';
2371 break;
2373 case 'RTL':
2374 case 'R': {
2375 if (!$this->rtl) {
2376 $newmode = 'R';
2378 break;
2380 case false:
2381 default: {
2382 $newmode = false;
2383 break;
2386 $this->tmprtl = $newmode;
2390 * Return the current temporary RTL status
2391 * @return boolean
2392 * @public
2393 * @since 4.8.014 (2009-11-04)
2395 public function isRTLTextDir() {
2396 return ($this->rtl OR ($this->tmprtl == 'R'));
2400 * Set the last cell height.
2401 * @param $h (float) cell height.
2402 * @author Nicola Asuni
2403 * @public
2404 * @since 1.53.0.TC034
2406 public function setLastH($h) {
2407 $this->lasth = $h;
2411 * Return the cell height
2412 * @param $fontsize (int) Font size in internal units
2413 * @param $padding (boolean) If true add cell padding
2414 * @public
2416 public function getCellHeight($fontsize, $padding=TRUE) {
2417 $height = ($fontsize * $this->cell_height_ratio);
2418 if ($padding) {
2419 $height += ($this->cell_padding['T'] + $this->cell_padding['B']);
2421 return round($height, 6);
2425 * Reset the last cell height.
2426 * @public
2427 * @since 5.9.000 (2010-10-03)
2429 public function resetLastH() {
2430 $this->lasth = $this->getCellHeight($this->FontSize);
2434 * Get the last cell height.
2435 * @return last cell height
2436 * @public
2437 * @since 4.0.017 (2008-08-05)
2439 public function getLastH() {
2440 return $this->lasth;
2444 * Set the adjusting factor to convert pixels to user units.
2445 * @param $scale (float) adjusting factor to convert pixels to user units.
2446 * @author Nicola Asuni
2447 * @public
2448 * @since 1.5.2
2450 public function setImageScale($scale) {
2451 $this->imgscale = $scale;
2455 * Returns the adjusting factor to convert pixels to user units.
2456 * @return float adjusting factor to convert pixels to user units.
2457 * @author Nicola Asuni
2458 * @public
2459 * @since 1.5.2
2461 public function getImageScale() {
2462 return $this->imgscale;
2466 * Returns an array of page dimensions:
2467 * <ul><li>$this->pagedim[$this->page]['w'] = page width in points</li><li>$this->pagedim[$this->page]['h'] = height in points</li><li>$this->pagedim[$this->page]['wk'] = page width in user units</li><li>$this->pagedim[$this->page]['hk'] = page height in user units</li><li>$this->pagedim[$this->page]['tm'] = top margin</li><li>$this->pagedim[$this->page]['bm'] = bottom margin</li><li>$this->pagedim[$this->page]['lm'] = left margin</li><li>$this->pagedim[$this->page]['rm'] = right margin</li><li>$this->pagedim[$this->page]['pb'] = auto page break</li><li>$this->pagedim[$this->page]['or'] = page orientation</li><li>$this->pagedim[$this->page]['olm'] = original left margin</li><li>$this->pagedim[$this->page]['orm'] = original right margin</li><li>$this->pagedim[$this->page]['Rotate'] = The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.</li><li>$this->pagedim[$this->page]['PZ'] = The page's preferred zoom (magnification) factor.</li><li>$this->pagedim[$this->page]['trans'] : the style and duration of the visual transition to use when moving from another page to the given page during a presentation<ul><li>$this->pagedim[$this->page]['trans']['Dur'] = The page's display duration (also called its advance timing): the maximum length of time, in seconds, that the page shall be displayed during presentations before the viewer application shall automatically advance to the next page.</li><li>$this->pagedim[$this->page]['trans']['S'] = transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li><li>$this->pagedim[$this->page]['trans']['D'] = The duration of the transition effect, in seconds.</li><li>$this->pagedim[$this->page]['trans']['Dm'] = (Split and Blinds transition styles only) The dimension in which the specified transition effect shall occur: H = Horizontal, V = Vertical. Default value: H.</li><li>$this->pagedim[$this->page]['trans']['M'] = (Split, Box and Fly transition styles only) The direction of motion for the specified transition effect: I = Inward from the edges of the page, O = Outward from the center of the pageDefault value: I.</li><li>$this->pagedim[$this->page]['trans']['Di'] = (Wipe, Glitter, Fly, Cover, Uncover and Push transition styles only) The direction in which the specified transition effect shall moves, expressed in degrees counterclockwise starting from a left-to-right direction. If the value is a number, it shall be one of: 0 = Left to right, 90 = Bottom to top (Wipe only), 180 = Right to left (Wipe only), 270 = Top to bottom, 315 = Top-left to bottom-right (Glitter only). If the value is a name, it shall be None, which is relevant only for the Fly transition when the value of SS is not 1.0. Default value: 0.</li><li>$this->pagedim[$this->page]['trans']['SS'] = (Fly transition style only) The starting or ending scale at which the changes shall be drawn. If M specifies an inward transition, the scale of the changes drawn shall progress from SS to 1.0 over the course of the transition. If M specifies an outward transition, the scale of the changes drawn shall progress from 1.0 to SS over the course of the transition. Default: 1.0. </li><li>$this->pagedim[$this->page]['trans']['B'] = (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li></ul></li><li>$this->pagedim[$this->page]['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed<ul><li>$this->pagedim[$this->page]['MediaBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['CropBox'] : the visible region of default user space<ul><li>$this->pagedim[$this->page]['CropBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment<ul><li>$this->pagedim[$this->page]['BleedBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['TrimBox'] : the intended dimensions of the finished page after trimming<ul><li>$this->pagedim[$this->page]['TrimBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['ArtBox'] : the extent of the page's meaningful content<ul><li>$this->pagedim[$this->page]['ArtBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['ury'] = upper-right y coordinate in points</li></ul></li></ul>
2468 * @param $pagenum (int) page number (empty = current page)
2469 * @return array of page dimensions.
2470 * @author Nicola Asuni
2471 * @public
2472 * @since 4.5.027 (2009-03-16)
2474 public function getPageDimensions($pagenum='') {
2475 if (empty($pagenum)) {
2476 $pagenum = $this->page;
2478 return $this->pagedim[$pagenum];
2482 * Returns the page width in units.
2483 * @param $pagenum (int) page number (empty = current page)
2484 * @return int page width.
2485 * @author Nicola Asuni
2486 * @public
2487 * @since 1.5.2
2488 * @see getPageDimensions()
2490 public function getPageWidth($pagenum='') {
2491 if (empty($pagenum)) {
2492 return $this->w;
2494 return $this->pagedim[$pagenum]['w'];
2498 * Returns the page height in units.
2499 * @param $pagenum (int) page number (empty = current page)
2500 * @return int page height.
2501 * @author Nicola Asuni
2502 * @public
2503 * @since 1.5.2
2504 * @see getPageDimensions()
2506 public function getPageHeight($pagenum='') {
2507 if (empty($pagenum)) {
2508 return $this->h;
2510 return $this->pagedim[$pagenum]['h'];
2514 * Returns the page break margin.
2515 * @param $pagenum (int) page number (empty = current page)
2516 * @return int page break margin.
2517 * @author Nicola Asuni
2518 * @public
2519 * @since 1.5.2
2520 * @see getPageDimensions()
2522 public function getBreakMargin($pagenum='') {
2523 if (empty($pagenum)) {
2524 return $this->bMargin;
2526 return $this->pagedim[$pagenum]['bm'];
2530 * Returns the scale factor (number of points in user unit).
2531 * @return int scale factor.
2532 * @author Nicola Asuni
2533 * @public
2534 * @since 1.5.2
2536 public function getScaleFactor() {
2537 return $this->k;
2541 * Defines the left, top and right margins.
2542 * @param $left (float) Left margin.
2543 * @param $top (float) Top margin.
2544 * @param $right (float) Right margin. Default value is the left one.
2545 * @param $keepmargins (boolean) if true overwrites the default page margins
2546 * @public
2547 * @since 1.0
2548 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2550 public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
2551 //Set left, top and right margins
2552 $this->lMargin = $left;
2553 $this->tMargin = $top;
2554 if ($right == -1) {
2555 $right = $left;
2557 $this->rMargin = $right;
2558 if ($keepmargins) {
2559 // overwrite original values
2560 $this->original_lMargin = $this->lMargin;
2561 $this->original_rMargin = $this->rMargin;
2566 * Defines the left margin. The method can be called before creating the first page. If the current abscissa gets out of page, it is brought back to the margin.
2567 * @param $margin (float) The margin.
2568 * @public
2569 * @since 1.4
2570 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2572 public function SetLeftMargin($margin) {
2573 //Set left margin
2574 $this->lMargin = $margin;
2575 if (($this->page > 0) AND ($this->x < $margin)) {
2576 $this->x = $margin;
2581 * Defines the top margin. The method can be called before creating the first page.
2582 * @param $margin (float) The margin.
2583 * @public
2584 * @since 1.5
2585 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2587 public function SetTopMargin($margin) {
2588 //Set top margin
2589 $this->tMargin = $margin;
2590 if (($this->page > 0) AND ($this->y < $margin)) {
2591 $this->y = $margin;
2596 * Defines the right margin. The method can be called before creating the first page.
2597 * @param $margin (float) The margin.
2598 * @public
2599 * @since 1.5
2600 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2602 public function SetRightMargin($margin) {
2603 $this->rMargin = $margin;
2604 if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
2605 $this->x = $this->w - $margin;
2610 * Set the same internal Cell padding for top, right, bottom, left-
2611 * @param $pad (float) internal padding.
2612 * @public
2613 * @since 2.1.000 (2008-01-09)
2614 * @see getCellPaddings(), setCellPaddings()
2616 public function SetCellPadding($pad) {
2617 if ($pad >= 0) {
2618 $this->cell_padding['L'] = $pad;
2619 $this->cell_padding['T'] = $pad;
2620 $this->cell_padding['R'] = $pad;
2621 $this->cell_padding['B'] = $pad;
2626 * Set the internal Cell paddings.
2627 * @param $left (float) left padding
2628 * @param $top (float) top padding
2629 * @param $right (float) right padding
2630 * @param $bottom (float) bottom padding
2631 * @public
2632 * @since 5.9.000 (2010-10-03)
2633 * @see getCellPaddings(), SetCellPadding()
2635 public function setCellPaddings($left='', $top='', $right='', $bottom='') {
2636 if (($left !== '') AND ($left >= 0)) {
2637 $this->cell_padding['L'] = $left;
2639 if (($top !== '') AND ($top >= 0)) {
2640 $this->cell_padding['T'] = $top;
2642 if (($right !== '') AND ($right >= 0)) {
2643 $this->cell_padding['R'] = $right;
2645 if (($bottom !== '') AND ($bottom >= 0)) {
2646 $this->cell_padding['B'] = $bottom;
2651 * Get the internal Cell padding array.
2652 * @return array of padding values
2653 * @public
2654 * @since 5.9.000 (2010-10-03)
2655 * @see setCellPaddings(), SetCellPadding()
2657 public function getCellPaddings() {
2658 return $this->cell_padding;
2662 * Set the internal Cell margins.
2663 * @param $left (float) left margin
2664 * @param $top (float) top margin
2665 * @param $right (float) right margin
2666 * @param $bottom (float) bottom margin
2667 * @public
2668 * @since 5.9.000 (2010-10-03)
2669 * @see getCellMargins()
2671 public function setCellMargins($left='', $top='', $right='', $bottom='') {
2672 if (($left !== '') AND ($left >= 0)) {
2673 $this->cell_margin['L'] = $left;
2675 if (($top !== '') AND ($top >= 0)) {
2676 $this->cell_margin['T'] = $top;
2678 if (($right !== '') AND ($right >= 0)) {
2679 $this->cell_margin['R'] = $right;
2681 if (($bottom !== '') AND ($bottom >= 0)) {
2682 $this->cell_margin['B'] = $bottom;
2687 * Get the internal Cell margin array.
2688 * @return array of margin values
2689 * @public
2690 * @since 5.9.000 (2010-10-03)
2691 * @see setCellMargins()
2693 public function getCellMargins() {
2694 return $this->cell_margin;
2698 * Adjust the internal Cell padding array to take account of the line width.
2699 * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
2700 * @return array of adjustments
2701 * @public
2702 * @since 5.9.000 (2010-10-03)
2704 protected function adjustCellPadding($brd=0) {
2705 if (empty($brd)) {
2706 return;
2708 if (is_string($brd)) {
2709 // convert string to array
2710 $slen = strlen($brd);
2711 $newbrd = array();
2712 for ($i = 0; $i < $slen; ++$i) {
2713 $newbrd[$brd[$i]] = true;
2715 $brd = $newbrd;
2716 } elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
2717 $brd = array('LRTB' => true);
2719 if (!is_array($brd)) {
2720 return;
2722 // store current cell padding
2723 $cp = $this->cell_padding;
2724 // select border mode
2725 if (isset($brd['mode'])) {
2726 $mode = $brd['mode'];
2727 unset($brd['mode']);
2728 } else {
2729 $mode = 'normal';
2731 // process borders
2732 foreach ($brd as $border => $style) {
2733 $line_width = $this->LineWidth;
2734 if (is_array($style) AND isset($style['width'])) {
2735 // get border width
2736 $line_width = $style['width'];
2738 $adj = 0; // line width inside the cell
2739 switch ($mode) {
2740 case 'ext': {
2741 $adj = 0;
2742 break;
2744 case 'int': {
2745 $adj = $line_width;
2746 break;
2748 case 'normal':
2749 default: {
2750 $adj = ($line_width / 2);
2751 break;
2754 // correct internal cell padding if required to avoid overlap between text and lines
2755 if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
2756 $this->cell_padding['T'] = $adj;
2758 if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
2759 $this->cell_padding['R'] = $adj;
2761 if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
2762 $this->cell_padding['B'] = $adj;
2764 if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
2765 $this->cell_padding['L'] = $adj;
2768 return array('T' => ($this->cell_padding['T'] - $cp['T']), 'R' => ($this->cell_padding['R'] - $cp['R']), 'B' => ($this->cell_padding['B'] - $cp['B']), 'L' => ($this->cell_padding['L'] - $cp['L']));
2772 * Enables or disables the automatic page breaking mode. When enabling, the second parameter is the distance from the bottom of the page that defines the triggering limit. By default, the mode is on and the margin is 2 cm.
2773 * @param $auto (boolean) Boolean indicating if mode should be on or off.
2774 * @param $margin (float) Distance from the bottom of the page.
2775 * @public
2776 * @since 1.0
2777 * @see Cell(), MultiCell(), AcceptPageBreak()
2779 public function SetAutoPageBreak($auto, $margin=0) {
2780 $this->AutoPageBreak = $auto ? true : false;
2781 $this->bMargin = $margin;
2782 $this->PageBreakTrigger = $this->h - $margin;
2786 * Return the auto-page-break mode (true or false).
2787 * @return boolean auto-page-break mode
2788 * @public
2789 * @since 5.9.088
2791 public function getAutoPageBreak() {
2792 return $this->AutoPageBreak;
2796 * Defines the way the document is to be displayed by the viewer.
2797 * @param $zoom (mixed) The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
2798 * @param $layout (string) The page layout. Possible values are:<ul><li>SinglePage Display one page at a time</li><li>OneColumn Display the pages in one column</li><li>TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left</li><li>TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right</li><li>TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left</li><li>TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right</li></ul>
2799 * @param $mode (string) A name object specifying how the document should be displayed when opened:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible</li><li>UseOC (PDF 1.5) Optional content group panel visible</li><li>UseAttachments (PDF 1.6) Attachments panel visible</li></ul>
2800 * @public
2801 * @since 1.2
2803 public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2804 if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2805 $this->ZoomMode = $zoom;
2806 } else {
2807 $this->Error('Incorrect zoom display mode: '.$zoom);
2809 $this->LayoutMode = TCPDF_STATIC::getPageLayoutMode($layout);
2810 $this->PageMode = TCPDF_STATIC::getPageMode($mode);
2814 * Activates or deactivates page compression. When activated, the internal representation of each page is compressed, which leads to a compression ratio of about 2 for the resulting document. Compression is on by default.
2815 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2816 * @param $compress (boolean) Boolean indicating if compression must be enabled.
2817 * @public
2818 * @since 1.4
2820 public function SetCompression($compress=true) {
2821 if (function_exists('gzcompress')) {
2822 $this->compress = $compress ? true : false;
2823 } else {
2824 $this->compress = false;
2829 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2830 * @param $mode (boolean) If true force sRGB output intent.
2831 * @public
2832 * @since 5.9.121 (2011-09-28)
2834 public function setSRGBmode($mode=false) {
2835 $this->force_srgb = $mode ? true : false;
2839 * Turn on/off Unicode mode for document information dictionary (meta tags).
2840 * This has effect only when unicode mode is set to false.
2841 * @param $unicode (boolean) if true set the meta information in Unicode
2842 * @since 5.9.027 (2010-12-01)
2843 * @public
2845 public function SetDocInfoUnicode($unicode=true) {
2846 $this->docinfounicode = $unicode ? true : false;
2850 * Defines the title of the document.
2851 * @param $title (string) The title.
2852 * @public
2853 * @since 1.2
2854 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2856 public function SetTitle($title) {
2857 $this->title = $title;
2861 * Defines the subject of the document.
2862 * @param $subject (string) The subject.
2863 * @public
2864 * @since 1.2
2865 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2867 public function SetSubject($subject) {
2868 $this->subject = $subject;
2872 * Defines the author of the document.
2873 * @param $author (string) The name of the author.
2874 * @public
2875 * @since 1.2
2876 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2878 public function SetAuthor($author) {
2879 $this->author = $author;
2883 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2884 * @param $keywords (string) The list of keywords.
2885 * @public
2886 * @since 1.2
2887 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2889 public function SetKeywords($keywords) {
2890 $this->keywords = $keywords;
2894 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2895 * @param $creator (string) The name of the creator.
2896 * @public
2897 * @since 1.2
2898 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2900 public function SetCreator($creator) {
2901 $this->creator = $creator;
2905 * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
2906 * @param $msg (string) The error message
2907 * @public
2908 * @since 1.0
2910 public function Error($msg) {
2911 // unset all class variables
2912 $this->_destroy(true);
2913 if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR) {
2914 die('<strong>TCPDF ERROR: </strong>'.$msg);
2915 } else {
2916 throw new Exception('TCPDF ERROR: '.$msg);
2921 * This method begins the generation of the PDF document.
2922 * It is not necessary to call it explicitly because AddPage() does it automatically.
2923 * Note: no page is created by this method
2924 * @public
2925 * @since 1.0
2926 * @see AddPage(), Close()
2928 public function Open() {
2929 $this->state = 1;
2933 * Terminates the PDF document.
2934 * It is not necessary to call this method explicitly because Output() does it automatically.
2935 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
2936 * @public
2937 * @since 1.0
2938 * @see Open(), Output()
2940 public function Close() {
2941 if ($this->state == 3) {
2942 return;
2944 if ($this->page == 0) {
2945 $this->AddPage();
2947 $this->endLayer();
2948 if ($this->tcpdflink) {
2949 // save current graphic settings
2950 $gvars = $this->getGraphicVars();
2951 $this->setEqualColumns();
2952 $this->lastpage(true);
2953 $this->SetAutoPageBreak(false);
2954 $this->x = 0;
2955 $this->y = $this->h - (1 / $this->k);
2956 $this->lMargin = 0;
2957 $this->_outSaveGraphicsState();
2958 $font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN:'helvetica';
2959 $this->SetFont($font, '', 1);
2960 $this->setTextRenderingMode(0, false, false);
2961 $msg = "\x50\x6f\x77\x65\x72\x65\x64\x20\x62\x79\x20\x54\x43\x50\x44\x46\x20\x28\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67\x29";
2962 $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
2963 $this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
2964 $this->_outRestoreGraphicsState();
2965 // restore graphic settings
2966 $this->setGraphicVars($gvars);
2968 // close page
2969 $this->endPage();
2970 // close document
2971 $this->_enddoc();
2972 // unset all class variables (except critical ones)
2973 $this->_destroy(false);
2977 * Move pointer at the specified document page and update page dimensions.
2978 * @param $pnum (int) page number (1 ... numpages)
2979 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
2980 * @public
2981 * @since 2.1.000 (2008-01-07)
2982 * @see getPage(), lastpage(), getNumPages()
2984 public function setPage($pnum, $resetmargins=false) {
2985 if (($pnum == $this->page) AND ($this->state == 2)) {
2986 return;
2988 if (($pnum > 0) AND ($pnum <= $this->numpages)) {
2989 $this->state = 2;
2990 // save current graphic settings
2991 //$gvars = $this->getGraphicVars();
2992 $oldpage = $this->page;
2993 $this->page = $pnum;
2994 $this->wPt = $this->pagedim[$this->page]['w'];
2995 $this->hPt = $this->pagedim[$this->page]['h'];
2996 $this->w = $this->pagedim[$this->page]['wk'];
2997 $this->h = $this->pagedim[$this->page]['hk'];
2998 $this->tMargin = $this->pagedim[$this->page]['tm'];
2999 $this->bMargin = $this->pagedim[$this->page]['bm'];
3000 $this->original_lMargin = $this->pagedim[$this->page]['olm'];
3001 $this->original_rMargin = $this->pagedim[$this->page]['orm'];
3002 $this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
3003 $this->CurOrientation = $this->pagedim[$this->page]['or'];
3004 $this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
3005 // restore graphic settings
3006 //$this->setGraphicVars($gvars);
3007 if ($resetmargins) {
3008 $this->lMargin = $this->pagedim[$this->page]['olm'];
3009 $this->rMargin = $this->pagedim[$this->page]['orm'];
3010 $this->SetY($this->tMargin);
3011 } else {
3012 // account for booklet mode
3013 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
3014 $deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
3015 $this->lMargin += $deltam;
3016 $this->rMargin -= $deltam;
3019 } else {
3020 $this->Error('Wrong page number on setPage() function: '.$pnum);
3025 * Reset pointer to the last document page.
3026 * @param $resetmargins (boolean) if true reset left, right, top margins and Y position.
3027 * @public
3028 * @since 2.0.000 (2008-01-04)
3029 * @see setPage(), getPage(), getNumPages()
3031 public function lastPage($resetmargins=false) {
3032 $this->setPage($this->getNumPages(), $resetmargins);
3036 * Get current document page number.
3037 * @return int page number
3038 * @public
3039 * @since 2.1.000 (2008-01-07)
3040 * @see setPage(), lastpage(), getNumPages()
3042 public function getPage() {
3043 return $this->page;
3047 * Get the total number of insered pages.
3048 * @return int number of pages
3049 * @public
3050 * @since 2.1.000 (2008-01-07)
3051 * @see setPage(), getPage(), lastpage()
3053 public function getNumPages() {
3054 return $this->numpages;
3058 * Adds a new TOC (Table Of Content) page to the document.
3059 * @param $orientation (string) page orientation.
3060 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3061 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3062 * @public
3063 * @since 5.0.001 (2010-05-06)
3064 * @see AddPage(), startPage(), endPage(), endTOCPage()
3066 public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3067 $this->AddPage($orientation, $format, $keepmargins, true);
3071 * Terminate the current TOC (Table Of Content) page
3072 * @public
3073 * @since 5.0.001 (2010-05-06)
3074 * @see AddPage(), startPage(), endPage(), addTOCPage()
3076 public function endTOCPage() {
3077 $this->endPage(true);
3081 * Adds a new page to the document. If a page is already present, the Footer() method is called first to output the footer (if enabled). Then the page is added, the current position set to the top-left corner according to the left and top margins (or top-right if in RTL mode), and Header() is called to display the header (if enabled).
3082 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3083 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3084 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3085 * @param $keepmargins (boolean) if true overwrites the default page margins with the current margins
3086 * @param $tocpage (boolean) if true set the tocpage state to true (the added page will be used to display Table Of Content).
3087 * @public
3088 * @since 1.0
3089 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3091 public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3092 if ($this->inxobj) {
3093 // we are inside an XObject template
3094 return;
3096 if (!isset($this->original_lMargin) OR $keepmargins) {
3097 $this->original_lMargin = $this->lMargin;
3099 if (!isset($this->original_rMargin) OR $keepmargins) {
3100 $this->original_rMargin = $this->rMargin;
3102 // terminate previous page
3103 $this->endPage();
3104 // start new page
3105 $this->startPage($orientation, $format, $tocpage);
3109 * Terminate the current page
3110 * @param $tocpage (boolean) if true set the tocpage state to false (end the page used to display Table Of Content).
3111 * @public
3112 * @since 4.2.010 (2008-11-14)
3113 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3115 public function endPage($tocpage=false) {
3116 // check if page is already closed
3117 if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
3118 return;
3120 // print page footer
3121 $this->setFooter();
3122 // close page
3123 $this->_endpage();
3124 // mark page as closed
3125 $this->pageopen[$this->page] = false;
3126 if ($tocpage) {
3127 $this->tocpage = false;
3132 * Starts a new page to the document. The page must be closed using the endPage() function.
3133 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3134 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3135 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
3136 * @param $tocpage (boolean) if true the page is designated to contain the Table-Of-Content.
3137 * @since 4.2.010 (2008-11-14)
3138 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3139 * @public
3141 public function startPage($orientation='', $format='', $tocpage=false) {
3142 if ($tocpage) {
3143 $this->tocpage = true;
3145 // move page numbers of documents to be attached
3146 if ($this->tocpage) {
3147 // move reference to unexistent pages (used for page attachments)
3148 // adjust outlines
3149 $tmpoutlines = $this->outlines;
3150 foreach ($tmpoutlines as $key => $outline) {
3151 if (!$outline['f'] AND ($outline['p'] > $this->numpages)) {
3152 $this->outlines[$key]['p'] = ($outline['p'] + 1);
3155 // adjust dests
3156 $tmpdests = $this->dests;
3157 foreach ($tmpdests as $key => $dest) {
3158 if (!$dest['f'] AND ($dest['p'] > $this->numpages)) {
3159 $this->dests[$key]['p'] = ($dest['p'] + 1);
3162 // adjust links
3163 $tmplinks = $this->links;
3164 foreach ($tmplinks as $key => $link) {
3165 if (!$link['f'] AND ($link['p'] > $this->numpages)) {
3166 $this->links[$key]['p'] = ($link['p'] + 1);
3170 if ($this->numpages > $this->page) {
3171 // this page has been already added
3172 $this->setPage($this->page + 1);
3173 $this->SetY($this->tMargin);
3174 return;
3176 // start a new page
3177 if ($this->state == 0) {
3178 $this->Open();
3180 ++$this->numpages;
3181 $this->swapMargins($this->booklet);
3182 // save current graphic settings
3183 $gvars = $this->getGraphicVars();
3184 // start new page
3185 $this->_beginpage($orientation, $format);
3186 // mark page as open
3187 $this->pageopen[$this->page] = true;
3188 // restore graphic settings
3189 $this->setGraphicVars($gvars);
3190 // mark this point
3191 $this->setPageMark();
3192 // print page header
3193 $this->setHeader();
3194 // restore graphic settings
3195 $this->setGraphicVars($gvars);
3196 // mark this point
3197 $this->setPageMark();
3198 // print table header (if any)
3199 $this->setTableHeader();
3200 // set mark for empty page check
3201 $this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
3205 * Set start-writing mark on current page stream used to put borders and fills.
3206 * Borders and fills are always created after content and inserted on the position marked by this method.
3207 * This function must be called after calling Image() function for a background image.
3208 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3209 * @public
3210 * @since 4.0.016 (2008-07-30)
3212 public function setPageMark() {
3213 $this->intmrk[$this->page] = $this->pagelen[$this->page];
3214 $this->bordermrk[$this->page] = $this->intmrk[$this->page];
3215 $this->setContentMark();
3219 * Set start-writing mark on selected page.
3220 * Borders and fills are always created after content and inserted on the position marked by this method.
3221 * @param $page (int) page number (default is the current page)
3222 * @protected
3223 * @since 4.6.021 (2009-07-20)
3225 protected function setContentMark($page=0) {
3226 if ($page <= 0) {
3227 $page = $this->page;
3229 if (isset($this->footerlen[$page])) {
3230 $this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3231 } else {
3232 $this->cntmrk[$page] = $this->pagelen[$page];
3237 * Set header data.
3238 * @param $ln (string) header image logo
3239 * @param $lw (string) header image logo width in mm
3240 * @param $ht (string) string to print as title on document header
3241 * @param $hs (string) string to print on document header
3242 * @param $tc (array) RGB array color for text.
3243 * @param $lc (array) RGB array color for line.
3244 * @public
3246 public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3247 $this->header_logo = $ln;
3248 $this->header_logo_width = $lw;
3249 $this->header_title = $ht;
3250 $this->header_string = $hs;
3251 $this->header_text_color = $tc;
3252 $this->header_line_color = $lc;
3256 * Set footer data.
3257 * @param $tc (array) RGB array color for text.
3258 * @param $lc (array) RGB array color for line.
3259 * @public
3261 public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3262 $this->footer_text_color = $tc;
3263 $this->footer_line_color = $lc;
3267 * Returns header data:
3268 * <ul><li>$ret['logo'] = logo image</li><li>$ret['logo_width'] = width of the image logo in user units</li><li>$ret['title'] = header title</li><li>$ret['string'] = header description string</li></ul>
3269 * @return array()
3270 * @public
3271 * @since 4.0.012 (2008-07-24)
3273 public function getHeaderData() {
3274 $ret = array();
3275 $ret['logo'] = $this->header_logo;
3276 $ret['logo_width'] = $this->header_logo_width;
3277 $ret['title'] = $this->header_title;
3278 $ret['string'] = $this->header_string;
3279 $ret['text_color'] = $this->header_text_color;
3280 $ret['line_color'] = $this->header_line_color;
3281 return $ret;
3285 * Set header margin.
3286 * (minimum distance between header and top page margin)
3287 * @param $hm (int) distance in user units
3288 * @public
3290 public function setHeaderMargin($hm=10) {
3291 $this->header_margin = $hm;
3295 * Returns header margin in user units.
3296 * @return float
3297 * @since 4.0.012 (2008-07-24)
3298 * @public
3300 public function getHeaderMargin() {
3301 return $this->header_margin;
3305 * Set footer margin.
3306 * (minimum distance between footer and bottom page margin)
3307 * @param $fm (int) distance in user units
3308 * @public
3310 public function setFooterMargin($fm=10) {
3311 $this->footer_margin = $fm;
3315 * Returns footer margin in user units.
3316 * @return float
3317 * @since 4.0.012 (2008-07-24)
3318 * @public
3320 public function getFooterMargin() {
3321 return $this->footer_margin;
3324 * Set a flag to print page header.
3325 * @param $val (boolean) set to true to print the page header (default), false otherwise.
3326 * @public
3328 public function setPrintHeader($val=true) {
3329 $this->print_header = $val ? true : false;
3333 * Set a flag to print page footer.
3334 * @param $val (boolean) set to true to print the page footer (default), false otherwise.
3335 * @public
3337 public function setPrintFooter($val=true) {
3338 $this->print_footer = $val ? true : false;
3342 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3343 * @return float
3344 * @public
3346 public function getImageRBX() {
3347 return $this->img_rb_x;
3351 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3352 * @return float
3353 * @public
3355 public function getImageRBY() {
3356 return $this->img_rb_y;
3360 * Reset the xobject template used by Header() method.
3361 * @public
3363 public function resetHeaderTemplate() {
3364 $this->header_xobjid = false;
3368 * Set a flag to automatically reset the xobject template used by Header() method at each page.
3369 * @param $val (boolean) set to true to reset Header xobject template at each page, false otherwise.
3370 * @public
3372 public function setHeaderTemplateAutoreset($val=true) {
3373 $this->header_xobj_autoreset = $val ? true : false;
3377 * This method is used to render the page header.
3378 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3379 * @public
3381 public function Header() {
3382 if ($this->header_xobjid === false) {
3383 // start a new XObject Template
3384 $this->header_xobjid = $this->startTemplate($this->w, $this->tMargin);
3385 $headerfont = $this->getHeaderFont();
3386 $headerdata = $this->getHeaderData();
3387 $this->y = $this->header_margin;
3388 if ($this->rtl) {
3389 $this->x = $this->w - $this->original_rMargin;
3390 } else {
3391 $this->x = $this->original_lMargin;
3393 if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
3394 $imgtype = TCPDF_IMAGES::getImageFileType(K_PATH_IMAGES.$headerdata['logo']);
3395 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3396 $this->ImageEps(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3397 } elseif ($imgtype == 'svg') {
3398 $this->ImageSVG(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3399 } else {
3400 $this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3402 $imgy = $this->getImageRBY();
3403 } else {
3404 $imgy = $this->y;
3406 $cell_height = $this->getCellHeight($headerfont[2] / $this->k);
3407 // set starting margin for text data cell
3408 if ($this->getRTL()) {
3409 $header_x = $this->original_rMargin + ($headerdata['logo_width'] * 1.1);
3410 } else {
3411 $header_x = $this->original_lMargin + ($headerdata['logo_width'] * 1.1);
3413 $cw = $this->w - $this->original_lMargin - $this->original_rMargin - ($headerdata['logo_width'] * 1.1);
3414 $this->SetTextColorArray($this->header_text_color);
3415 // header title
3416 $this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
3417 $this->SetX($header_x);
3418 $this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3419 // header string
3420 $this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
3421 $this->SetX($header_x);
3422 $this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3423 // print an ending header line
3424 $this->SetLineStyle(array('width' => 0.85 / $this->k, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3425 $this->SetY((2.835 / $this->k) + max($imgy, $this->y));
3426 if ($this->rtl) {
3427 $this->SetX($this->original_rMargin);
3428 } else {
3429 $this->SetX($this->original_lMargin);
3431 $this->Cell(($this->w - $this->original_lMargin - $this->original_rMargin), 0, '', 'T', 0, 'C');
3432 $this->endTemplate();
3434 // print header template
3435 $x = 0;
3436 $dx = 0;
3437 if (!$this->header_xobj_autoreset AND $this->booklet AND (($this->page % 2) == 0)) {
3438 // adjust margins for booklet mode
3439 $dx = ($this->original_lMargin - $this->original_rMargin);
3441 if ($this->rtl) {
3442 $x = $this->w + $dx;
3443 } else {
3444 $x = 0 + $dx;
3446 $this->printTemplate($this->header_xobjid, $x, 0, 0, 0, '', '', false);
3447 if ($this->header_xobj_autoreset) {
3448 // reset header xobject template at each page
3449 $this->header_xobjid = false;
3454 * This method is used to render the page footer.
3455 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3456 * @public
3458 public function Footer() {
3459 $cur_y = $this->y;
3460 $this->SetTextColorArray($this->footer_text_color);
3461 //set style for cell border
3462 $line_width = (0.85 / $this->k);
3463 $this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color));
3464 //print document barcode
3465 $barcode = $this->getBarcode();
3466 if (!empty($barcode)) {
3467 $this->Ln($line_width);
3468 $barcode_width = round(($this->w - $this->original_lMargin - $this->original_rMargin) / 3);
3469 $style = array(
3470 'position' => $this->rtl?'R':'L',
3471 'align' => $this->rtl?'R':'L',
3472 'stretch' => false,
3473 'fitwidth' => true,
3474 'cellfitalign' => '',
3475 'border' => false,
3476 'padding' => 0,
3477 'fgcolor' => array(0,0,0),
3478 'bgcolor' => false,
3479 'text' => false
3481 $this->write1DBarcode($barcode, 'C128', '', $cur_y + $line_width, '', (($this->footer_margin / 3) - $line_width), 0.3, $style, '');
3483 $w_page = isset($this->l['w_page']) ? $this->l['w_page'].' ' : '';
3484 if (empty($this->pagegroups)) {
3485 $pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3486 } else {
3487 $pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3489 $this->SetY($cur_y);
3490 //Print page number
3491 if ($this->getRTL()) {
3492 $this->SetX($this->original_rMargin);
3493 $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3494 } else {
3495 $this->SetX($this->original_lMargin);
3496 $this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3501 * This method is used to render the page header.
3502 * @protected
3503 * @since 4.0.012 (2008-07-24)
3505 protected function setHeader() {
3506 if (!$this->print_header OR ($this->state != 2)) {
3507 return;
3509 $this->InHeader = true;
3510 $this->setGraphicVars($this->default_graphic_vars);
3511 $temp_thead = $this->thead;
3512 $temp_theadMargins = $this->theadMargins;
3513 $lasth = $this->lasth;
3514 $newline = $this->newline;
3515 $this->_outSaveGraphicsState();
3516 $this->rMargin = $this->original_rMargin;
3517 $this->lMargin = $this->original_lMargin;
3518 $this->SetCellPadding(0);
3519 //set current position
3520 if ($this->rtl) {
3521 $this->SetXY($this->original_rMargin, $this->header_margin);
3522 } else {
3523 $this->SetXY($this->original_lMargin, $this->header_margin);
3525 $this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3526 $this->Header();
3527 //restore position
3528 if ($this->rtl) {
3529 $this->SetXY($this->original_rMargin, $this->tMargin);
3530 } else {
3531 $this->SetXY($this->original_lMargin, $this->tMargin);
3533 $this->_outRestoreGraphicsState();
3534 $this->lasth = $lasth;
3535 $this->thead = $temp_thead;
3536 $this->theadMargins = $temp_theadMargins;
3537 $this->newline = $newline;
3538 $this->InHeader = false;
3542 * This method is used to render the page footer.
3543 * @protected
3544 * @since 4.0.012 (2008-07-24)
3546 protected function setFooter() {
3547 if ($this->state != 2) {
3548 return;
3550 $this->InFooter = true;
3551 // save current graphic settings
3552 $gvars = $this->getGraphicVars();
3553 // mark this point
3554 $this->footerpos[$this->page] = $this->pagelen[$this->page];
3555 $this->_out("\n");
3556 if ($this->print_footer) {
3557 $this->setGraphicVars($this->default_graphic_vars);
3558 $this->current_column = 0;
3559 $this->num_columns = 1;
3560 $temp_thead = $this->thead;
3561 $temp_theadMargins = $this->theadMargins;
3562 $lasth = $this->lasth;
3563 $this->_outSaveGraphicsState();
3564 $this->rMargin = $this->original_rMargin;
3565 $this->lMargin = $this->original_lMargin;
3566 $this->SetCellPadding(0);
3567 //set current position
3568 $footer_y = $this->h - $this->footer_margin;
3569 if ($this->rtl) {
3570 $this->SetXY($this->original_rMargin, $footer_y);
3571 } else {
3572 $this->SetXY($this->original_lMargin, $footer_y);
3574 $this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3575 $this->Footer();
3576 //restore position
3577 if ($this->rtl) {
3578 $this->SetXY($this->original_rMargin, $this->tMargin);
3579 } else {
3580 $this->SetXY($this->original_lMargin, $this->tMargin);
3582 $this->_outRestoreGraphicsState();
3583 $this->lasth = $lasth;
3584 $this->thead = $temp_thead;
3585 $this->theadMargins = $temp_theadMargins;
3587 // restore graphic settings
3588 $this->setGraphicVars($gvars);
3589 $this->current_column = $gvars['current_column'];
3590 $this->num_columns = $gvars['num_columns'];
3591 // calculate footer length
3592 $this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
3593 $this->InFooter = false;
3597 * Check if we are on the page body (excluding page header and footer).
3598 * @return true if we are not in page header nor in page footer, false otherwise.
3599 * @protected
3600 * @since 5.9.091 (2011-06-15)
3602 protected function inPageBody() {
3603 return (($this->InHeader === false) AND ($this->InFooter === false));
3607 * This method is used to render the table header on new page (if any).
3608 * @protected
3609 * @since 4.5.030 (2009-03-25)
3611 protected function setTableHeader() {
3612 if ($this->num_columns > 1) {
3613 // multi column mode
3614 return;
3616 if (isset($this->theadMargins['top'])) {
3617 // restore the original top-margin
3618 $this->tMargin = $this->theadMargins['top'];
3619 $this->pagedim[$this->page]['tm'] = $this->tMargin;
3620 $this->y = $this->tMargin;
3622 if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
3623 // set margins
3624 $prev_lMargin = $this->lMargin;
3625 $prev_rMargin = $this->rMargin;
3626 $prev_cell_padding = $this->cell_padding;
3627 $this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
3628 $this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
3629 $this->cell_padding = $this->theadMargins['cell_padding'];
3630 if ($this->rtl) {
3631 $this->x = $this->w - $this->rMargin;
3632 } else {
3633 $this->x = $this->lMargin;
3635 // account for special "cell" mode
3636 if ($this->theadMargins['cell']) {
3637 if ($this->rtl) {
3638 $this->x -= $this->cell_padding['R'];
3639 } else {
3640 $this->x += $this->cell_padding['L'];
3643 $gvars = $this->getGraphicVars();
3644 if (!empty($this->theadMargins['gvars'])) {
3645 // set the correct graphic style
3646 $this->setGraphicVars($this->theadMargins['gvars']);
3647 $this->rMargin = $gvars['rMargin'];
3648 $this->lMargin = $gvars['lMargin'];
3650 // print table header
3651 $this->writeHTML($this->thead, false, false, false, false, '');
3652 $this->setGraphicVars($gvars);
3653 // set new top margin to skip the table headers
3654 if (!isset($this->theadMargins['top'])) {
3655 $this->theadMargins['top'] = $this->tMargin;
3657 // store end of header position
3658 if (!isset($this->columns[0]['th'])) {
3659 $this->columns[0]['th'] = array();
3661 $this->columns[0]['th']['\''.$this->page.'\''] = $this->y;
3662 $this->tMargin = $this->y;
3663 $this->pagedim[$this->page]['tm'] = $this->tMargin;
3664 $this->lasth = 0;
3665 $this->lMargin = $prev_lMargin;
3666 $this->rMargin = $prev_rMargin;
3667 $this->cell_padding = $prev_cell_padding;
3672 * Returns the current page number.
3673 * @return int page number
3674 * @public
3675 * @since 1.0
3676 * @see getAliasNbPages()
3678 public function PageNo() {
3679 return $this->page;
3683 * Returns the array of spot colors.
3684 * @return (array) Spot colors array.
3685 * @public
3686 * @since 6.0.038 (2013-09-30)
3688 public function getAllSpotColors() {
3689 return $this->spot_colors;
3693 * Defines a new spot color.
3694 * It can be expressed in RGB components or gray scale.
3695 * The method can be called before the first page is created and the value is retained from page to page.
3696 * @param $name (string) Full name of the spot color.
3697 * @param $c (float) Cyan color for CMYK. Value between 0 and 100.
3698 * @param $m (float) Magenta color for CMYK. Value between 0 and 100.
3699 * @param $y (float) Yellow color for CMYK. Value between 0 and 100.
3700 * @param $k (float) Key (Black) color for CMYK. Value between 0 and 100.
3701 * @public
3702 * @since 4.0.024 (2008-09-12)
3703 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3705 public function AddSpotColor($name, $c, $m, $y, $k) {
3706 if (!isset($this->spot_colors[$name])) {
3707 $i = (1 + count($this->spot_colors));
3708 $this->spot_colors[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3713 * Set the spot color for the specified type ('draw', 'fill', 'text').
3714 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3715 * @param $name (string) Name of the spot color.
3716 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3717 * @return (string) PDF color command.
3718 * @public
3719 * @since 5.9.125 (2011-10-03)
3721 public function setSpotColor($type, $name, $tint=100) {
3722 $spotcolor = TCPDF_COLORS::getSpotColor($name, $this->spot_colors);
3723 if ($spotcolor === false) {
3724 $this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3726 $tint = (max(0, min(100, $tint)) / 100);
3727 $pdfcolor = sprintf('/CS%d ', $this->spot_colors[$name]['i']);
3728 switch ($type) {
3729 case 'draw': {
3730 $pdfcolor .= sprintf('CS %F SCN', $tint);
3731 $this->DrawColor = $pdfcolor;
3732 $this->strokecolor = $spotcolor;
3733 break;
3735 case 'fill': {
3736 $pdfcolor .= sprintf('cs %F scn', $tint);
3737 $this->FillColor = $pdfcolor;
3738 $this->bgcolor = $spotcolor;
3739 break;
3741 case 'text': {
3742 $pdfcolor .= sprintf('cs %F scn', $tint);
3743 $this->TextColor = $pdfcolor;
3744 $this->fgcolor = $spotcolor;
3745 break;
3748 $this->ColorFlag = ($this->FillColor != $this->TextColor);
3749 if ($this->state == 2) {
3750 $this->_out($pdfcolor);
3752 if ($this->inxobj) {
3753 // we are inside an XObject template
3754 $this->xobjects[$this->xobjid]['spot_colors'][$name] = $this->spot_colors[$name];
3756 return $pdfcolor;
3760 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3761 * @param $name (string) Name of the spot color.
3762 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3763 * @public
3764 * @since 4.0.024 (2008-09-12)
3765 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3767 public function SetDrawSpotColor($name, $tint=100) {
3768 $this->setSpotColor('draw', $name, $tint);
3772 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
3773 * @param $name (string) Name of the spot color.
3774 * @param $tint (float) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3775 * @public
3776 * @since 4.0.024 (2008-09-12)
3777 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
3779 public function SetFillSpotColor($name, $tint=100) {
3780 $this->setSpotColor('fill', $name, $tint);
3784 * Defines the spot color used for text.
3785 * @param $name (string) Name of the spot color.
3786 * @param $tint (int) Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3787 * @public
3788 * @since 4.0.024 (2008-09-12)
3789 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
3791 public function SetTextSpotColor($name, $tint=100) {
3792 $this->setSpotColor('text', $name, $tint);
3796 * Set the color array for the specified type ('draw', 'fill', 'text').
3797 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3798 * The method can be called before the first page is created and the value is retained from page to page.
3799 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3800 * @param $color (array) Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3801 * @param $ret (boolean) If true do not send the PDF command.
3802 * @return (string) The PDF command or empty string.
3803 * @public
3804 * @since 3.1.000 (2008-06-11)
3806 public function setColorArray($type, $color, $ret=false) {
3807 if (is_array($color)) {
3808 $color = array_values($color);
3809 // component: grey, RGB red or CMYK cyan
3810 $c = isset($color[0]) ? $color[0] : -1;
3811 // component: RGB green or CMYK magenta
3812 $m = isset($color[1]) ? $color[1] : -1;
3813 // component: RGB blue or CMYK yellow
3814 $y = isset($color[2]) ? $color[2] : -1;
3815 // component: CMYK black
3816 $k = isset($color[3]) ? $color[3] : -1;
3817 // color name
3818 $name = isset($color[4]) ? $color[4] : '';
3819 if ($c >= 0) {
3820 return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3823 return '';
3827 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
3828 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3829 * The method can be called before the first page is created and the value is retained from page to page.
3830 * @param $color (array) Array of colors (1, 3 or 4 values).
3831 * @param $ret (boolean) If true do not send the PDF command.
3832 * @return string the PDF command
3833 * @public
3834 * @since 3.1.000 (2008-06-11)
3835 * @see SetDrawColor()
3837 public function SetDrawColorArray($color, $ret=false) {
3838 return $this->setColorArray('draw', $color, $ret);
3842 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3843 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3844 * The method can be called before the first page is created and the value is retained from page to page.
3845 * @param $color (array) Array of colors (1, 3 or 4 values).
3846 * @param $ret (boolean) If true do not send the PDF command.
3847 * @public
3848 * @since 3.1.000 (2008-6-11)
3849 * @see SetFillColor()
3851 public function SetFillColorArray($color, $ret=false) {
3852 return $this->setColorArray('fill', $color, $ret);
3856 * Defines the color used for text. It can be expressed in RGB components or gray scale.
3857 * The method can be called before the first page is created and the value is retained from page to page.
3858 * @param $color (array) Array of colors (1, 3 or 4 values).
3859 * @param $ret (boolean) If true do not send the PDF command.
3860 * @public
3861 * @since 3.1.000 (2008-6-11)
3862 * @see SetFillColor()
3864 public function SetTextColorArray($color, $ret=false) {
3865 return $this->setColorArray('text', $color, $ret);
3869 * Defines the color used by the specified type ('draw', 'fill', 'text').
3870 * @param $type (string) Type of object affected by this color: ('draw', 'fill', 'text').
3871 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3872 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3873 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3874 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3875 * @param $ret (boolean) If true do not send the command.
3876 * @param $name (string) spot color name (if any)
3877 * @return (string) The PDF command or empty string.
3878 * @public
3879 * @since 5.9.125 (2011-10-03)
3881 public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3882 // set default values
3883 if (!is_numeric($col1)) {
3884 $col1 = 0;
3886 if (!is_numeric($col2)) {
3887 $col2 = -1;
3889 if (!is_numeric($col3)) {
3890 $col3 = -1;
3892 if (!is_numeric($col4)) {
3893 $col4 = -1;
3895 // set color by case
3896 $suffix = '';
3897 if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3898 // Grey scale
3899 $col1 = max(0, min(255, $col1));
3900 $intcolor = array('G' => $col1);
3901 $pdfcolor = sprintf('%F ', ($col1 / 255));
3902 $suffix = 'g';
3903 } elseif ($col4 == -1) {
3904 // RGB
3905 $col1 = max(0, min(255, $col1));
3906 $col2 = max(0, min(255, $col2));
3907 $col3 = max(0, min(255, $col3));
3908 $intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
3909 $pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
3910 $suffix = 'rg';
3911 } else {
3912 $col1 = max(0, min(100, $col1));
3913 $col2 = max(0, min(100, $col2));
3914 $col3 = max(0, min(100, $col3));
3915 $col4 = max(0, min(100, $col4));
3916 if (empty($name)) {
3917 // CMYK
3918 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
3919 $pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
3920 $suffix = 'k';
3921 } else {
3922 // SPOT COLOR
3923 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
3924 $this->AddSpotColor($name, $col1, $col2, $col3, $col4);
3925 $pdfcolor = $this->setSpotColor($type, $name, 100);
3928 switch ($type) {
3929 case 'draw': {
3930 $pdfcolor .= strtoupper($suffix);
3931 $this->DrawColor = $pdfcolor;
3932 $this->strokecolor = $intcolor;
3933 break;
3935 case 'fill': {
3936 $pdfcolor .= $suffix;
3937 $this->FillColor = $pdfcolor;
3938 $this->bgcolor = $intcolor;
3939 break;
3941 case 'text': {
3942 $pdfcolor .= $suffix;
3943 $this->TextColor = $pdfcolor;
3944 $this->fgcolor = $intcolor;
3945 break;
3948 $this->ColorFlag = ($this->FillColor != $this->TextColor);
3949 if (($type != 'text') AND ($this->state == 2)) {
3950 if (!$ret) {
3951 $this->_out($pdfcolor);
3953 return $pdfcolor;
3955 return '';
3959 * Defines the color used for all drawing operations (lines, rectangles and cell borders). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
3960 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3961 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3962 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3963 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3964 * @param $ret (boolean) If true do not send the command.
3965 * @param $name (string) spot color name (if any)
3966 * @return string the PDF command
3967 * @public
3968 * @since 1.3
3969 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
3971 public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3972 return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
3976 * Defines the color used for all filling operations (filled rectangles and cell backgrounds). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
3977 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3978 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3979 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3980 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3981 * @param $ret (boolean) If true do not send the command.
3982 * @param $name (string) Spot color name (if any).
3983 * @return (string) The PDF command.
3984 * @public
3985 * @since 1.3
3986 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
3988 public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3989 return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
3993 * Defines the color used for text. It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
3994 * @param $col1 (float) GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3995 * @param $col2 (float) GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3996 * @param $col3 (float) BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3997 * @param $col4 (float) KEY (BLACK) color for CMYK (0-100).
3998 * @param $ret (boolean) If true do not send the command.
3999 * @param $name (string) Spot color name (if any).
4000 * @return (string) Empty string.
4001 * @public
4002 * @since 1.3
4003 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4005 public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4006 return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4010 * Returns the length of a string in user unit. A font must be selected.<br>
4011 * @param $s (string) The string whose length is to be computed
4012 * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4013 * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line-through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4014 * @param $fontsize (float) Font size in points. The default value is the current size.
4015 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4016 * @return mixed int total string length or array of characted widths
4017 * @author Nicola Asuni
4018 * @public
4019 * @since 1.2
4021 public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4022 return $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont), $s, $this->tmprtl, $this->isunicode, $this->CurrentFont), $fontname, $fontstyle, $fontsize, $getarray);
4026 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4027 * @param $sa (string) The array of chars whose total length is to be computed
4028 * @param $fontname (string) Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
4029 * @param $fontstyle (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular.
4030 * @param $fontsize (float) Font size in points. The default value is the current size.
4031 * @param $getarray (boolean) if true returns an array of characters widths, if false returns the total length.
4032 * @return mixed int total string length or array of characted widths
4033 * @author Nicola Asuni
4034 * @public
4035 * @since 2.4.000 (2008-03-06)
4037 public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4038 // store current values
4039 if (!TCPDF_STATIC::empty_string($fontname)) {
4040 $prev_FontFamily = $this->FontFamily;
4041 $prev_FontStyle = $this->FontStyle;
4042 $prev_FontSizePt = $this->FontSizePt;
4043 $this->SetFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4045 // convert UTF-8 array to Latin1 if required
4046 if ($this->isunicode AND (!$this->isUnicodeFont())) {
4047 $sa = TCPDF_FONTS::UTF8ArrToLatin1Arr($sa);
4049 $w = 0; // total width
4050 $wa = array(); // array of characters widths
4051 foreach ($sa as $ck => $char) {
4052 // character width
4053 $cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4054 $wa[] = $cw;
4055 $w += $cw;
4057 // restore previous values
4058 if (!TCPDF_STATIC::empty_string($fontname)) {
4059 $this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4061 if ($getarray) {
4062 return $wa;
4064 return $w;
4068 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4069 * @param $char (int) The char code whose length is to be returned
4070 * @param $notlast (boolean) If false ignore the font-spacing.
4071 * @return float char width
4072 * @author Nicola Asuni
4073 * @public
4074 * @since 2.4.000 (2008-03-06)
4076 public function GetCharWidth($char, $notlast=true) {
4077 // get raw width
4078 $chw = $this->getRawCharWidth($char);
4079 if (($this->font_spacing < 0) OR (($this->font_spacing > 0) AND $notlast)) {
4080 // increase/decrease font spacing
4081 $chw += $this->font_spacing;
4083 if ($this->font_stretching != 100) {
4084 // fixed stretching mode
4085 $chw *= ($this->font_stretching / 100);
4087 return $chw;
4091 * Returns the length of the char in user unit for the current font.
4092 * @param $char (int) The char code whose length is to be returned
4093 * @return float char width
4094 * @author Nicola Asuni
4095 * @public
4096 * @since 5.9.000 (2010-09-28)
4098 public function getRawCharWidth($char) {
4099 if ($char == 173) {
4100 // SHY character will not be printed
4101 return (0);
4103 if (isset($this->CurrentFont['cw'][$char])) {
4104 $w = $this->CurrentFont['cw'][$char];
4105 } elseif (isset($this->CurrentFont['dw'])) {
4106 // default width
4107 $w = $this->CurrentFont['dw'];
4108 } elseif (isset($this->CurrentFont['cw'][32])) {
4109 // default width
4110 $w = $this->CurrentFont['cw'][32];
4111 } else {
4112 $w = 600;
4114 return $this->getAbsFontMeasure($w);
4118 * Returns the numbero of characters in a string.
4119 * @param $s (string) The input string.
4120 * @return int number of characters
4121 * @public
4122 * @since 2.0.0001 (2008-01-07)
4124 public function GetNumChars($s) {
4125 if ($this->isUnicodeFont()) {
4126 return count(TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont));
4128 return strlen($s);
4132 * Fill the list of available fonts ($this->fontlist).
4133 * @protected
4134 * @since 4.0.013 (2008-07-28)
4136 protected function getFontsList() {
4137 if (($fontsdir = opendir(TCPDF_FONTS::_getfontpath())) !== false) {
4138 while (($file = readdir($fontsdir)) !== false) {
4139 if (substr($file, -4) == '.php') {
4140 array_push($this->fontlist, strtolower(basename($file, '.php')));
4143 closedir($fontsdir);
4148 * Returns the unicode caracter specified by the value
4149 * @param $c (int) UTF-8 value
4150 * @return Returns the specified character.
4151 * @since 2.3.000 (2008-03-05)
4152 * @public
4153 * @deprecated
4155 public function unichr($c) {
4156 return TCPDF_FONTS::unichr($c, $this->isunicode);
4160 * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable).
4161 * @param $fontfile (string) Font file (full path).
4162 * @param $fonttype (string) Font type. Leave empty for autodetect mode. Valid values are: TrueTypeUnicode, TrueType, Type1, CID0JP = CID-0 Japanese, CID0KR = CID-0 Korean, CID0CS = CID-0 Chinese Simplified, CID0CT = CID-0 Chinese Traditional.
4163 * @param $enc (string) Name of the encoding table to use. Leave empty for default mode. Omit this parameter for TrueType Unicode and symbolic fonts like Symbol or ZapfDingBats.
4164 * @param $flags (int) Unsigned 32-bit integer containing flags specifying various characteristics of the font (PDF32000:2008 - 9.8.2 Font Descriptor Flags): +1 for fixed font; +4 for symbol or +32 for non-symbol; +64 for italic. Fixed and Italic mode are generally autodetected so you have to set it to 32 = non-symbolic font (default) or 4 = symbolic font.
4165 * @param $outpath (string) Output path for generated font files (must be writeable by the web server). Leave empty for default font folder.
4166 * @param $platid (int) Platform ID for CMAP table to extract (when building a Unicode font for Windows this value should be 3, for Macintosh should be 1).
4167 * @param $encid (int) Encoding ID for CMAP table to extract (when building a Unicode font for Windows this value should be 1, for Macintosh should be 0). When Platform ID is 3, legal values for Encoding ID are: 0=Symbol, 1=Unicode, 2=ShiftJIS, 3=PRC, 4=Big5, 5=Wansung, 6=Johab, 7=Reserved, 8=Reserved, 9=Reserved, 10=UCS-4.
4168 * @param $addcbbox (boolean) If true includes the character bounding box information on the php font file.
4169 * @return (string) TCPDF font name.
4170 * @author Nicola Asuni
4171 * @since 5.9.123 (2010-09-30)
4172 * @public
4173 * @deprecated
4175 public function addTTFfont($fontfile, $fonttype='', $enc='', $flags=32, $outpath='', $platid=3, $encid=1, $addcbbox=false) {
4176 return TCPDF_FONTS::addTTFfont($fontfile, $fonttype, $enc, $flags, $outpath, $platid, $encid, $addcbbox);
4180 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4181 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4182 * The definition file (and the font file itself when embedding) must be present either in the current directory or in the one indicated by K_PATH_FONTS if the constant is defined. If it could not be found, the error "Could not include font definition file" is generated.
4183 * @param $family (string) Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4184 * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
4185 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4186 * @return array containing the font data, or false in case of error.
4187 * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
4188 * @public
4189 * @since 1.5
4190 * @see SetFont(), setFontSubsetting()
4192 public function AddFont($family, $style='', $fontfile='', $subset='default') {
4193 if ($subset === 'default') {
4194 $subset = $this->font_subsetting;
4196 if ($this->pdfa_mode) {
4197 $subset = false;
4199 if (TCPDF_STATIC::empty_string($family)) {
4200 if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4201 $family = $this->FontFamily;
4202 } else {
4203 $this->Error('Empty font family');
4206 // move embedded styles on $style
4207 if (substr($family, -1) == 'I') {
4208 $style .= 'I';
4209 $family = substr($family, 0, -1);
4211 if (substr($family, -1) == 'B') {
4212 $style .= 'B';
4213 $family = substr($family, 0, -1);
4215 // normalize family name
4216 $family = strtolower($family);
4217 if ((!$this->isunicode) AND ($family == 'arial')) {
4218 $family = 'helvetica';
4220 if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4221 $style = '';
4223 if ($this->pdfa_mode AND (isset($this->CoreFonts[$family]))) {
4224 // all fonts must be embedded
4225 $family = 'pdfa'.$family;
4227 $tempstyle = strtoupper($style);
4228 $style = '';
4229 // underline
4230 if (strpos($tempstyle, 'U') !== false) {
4231 $this->underline = true;
4232 } else {
4233 $this->underline = false;
4235 // line-through (deleted)
4236 if (strpos($tempstyle, 'D') !== false) {
4237 $this->linethrough = true;
4238 } else {
4239 $this->linethrough = false;
4241 // overline
4242 if (strpos($tempstyle, 'O') !== false) {
4243 $this->overline = true;
4244 } else {
4245 $this->overline = false;
4247 // bold
4248 if (strpos($tempstyle, 'B') !== false) {
4249 $style .= 'B';
4251 // oblique
4252 if (strpos($tempstyle, 'I') !== false) {
4253 $style .= 'I';
4255 $bistyle = $style;
4256 $fontkey = $family.$style;
4257 $font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
4258 $fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4259 // check if the font has been already added
4260 $fb = $this->getFontBuffer($fontkey);
4261 if ($fb !== false) {
4262 if ($this->inxobj) {
4263 // we are inside an XObject template
4264 $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
4266 return $fontdata;
4268 // get specified font directory (if any)
4269 $fontdir = false;
4270 if (!TCPDF_STATIC::empty_string($fontfile)) {
4271 $fontdir = dirname($fontfile);
4272 if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4273 $fontdir = '';
4274 } else {
4275 $fontdir .= '/';
4278 // true when the font style variation is missing
4279 $missing_style = false;
4280 // search and include font file
4281 if (TCPDF_STATIC::empty_string($fontfile) OR (!@file_exists($fontfile))) {
4282 // build a standard filenames for specified font
4283 $tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4284 $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4285 if (TCPDF_STATIC::empty_string($fontfile)) {
4286 $missing_style = true;
4287 // try to remove the style part
4288 $tmp_fontfile = str_replace(' ', '', $family).'.php';
4289 $fontfile = TCPDF_FONTS::getFontFullPath($tmp_fontfile, $fontdir);
4292 // include font file
4293 if (!TCPDF_STATIC::empty_string($fontfile) AND (@file_exists($fontfile))) {
4294 include($fontfile);
4295 } else {
4296 $this->Error('Could not include font definition file: '.$family.'');
4298 // check font parameters
4299 if ((!isset($type)) OR (!isset($cw))) {
4300 $this->Error('The font definition file has a bad format: '.$fontfile.'');
4302 // SET default parameters
4303 if (!isset($file) OR TCPDF_STATIC::empty_string($file)) {
4304 $file = '';
4306 if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4307 $enc = '';
4309 if (!isset($cidinfo) OR TCPDF_STATIC::empty_string($cidinfo)) {
4310 $cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4311 $cidinfo['uni2cid'] = array();
4313 if (!isset($ctg) OR TCPDF_STATIC::empty_string($ctg)) {
4314 $ctg = '';
4316 if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4317 $desc = array();
4319 if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4320 $up = -100;
4322 if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4323 $ut = 50;
4325 if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4326 $cw = array();
4328 if (!isset($dw) OR TCPDF_STATIC::empty_string($dw)) {
4329 // set default width
4330 if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4331 $dw = $desc['MissingWidth'];
4332 } elseif (isset($cw[32])) {
4333 $dw = $cw[32];
4334 } else {
4335 $dw = 600;
4338 ++$this->numfonts;
4339 if ($type == 'core') {
4340 $name = $this->CoreFonts[$fontkey];
4341 $subset = false;
4342 } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4343 $subset = false;
4344 } elseif ($type == 'TrueTypeUnicode') {
4345 $enc = 'Identity-H';
4346 } elseif ($type == 'cidfont0') {
4347 if ($this->pdfa_mode) {
4348 $this->Error('All fonts must be embedded in PDF/A mode!');
4350 } else {
4351 $this->Error('Unknow font type: '.$type.'');
4353 // set name if unset
4354 if (!isset($name) OR empty($name)) {
4355 $name = $fontkey;
4357 // create artificial font style variations if missing (only works with non-embedded fonts)
4358 if (($type != 'core') AND $missing_style) {
4359 // style variations
4360 $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4361 $name .= $styles[$bistyle];
4362 // artificial bold
4363 if (strpos($bistyle, 'B') !== false) {
4364 if (isset($desc['StemV'])) {
4365 // from normal to bold
4366 $desc['StemV'] = round($desc['StemV'] * 1.75);
4367 } else {
4368 // bold
4369 $desc['StemV'] = 123;
4372 // artificial italic
4373 if (strpos($bistyle, 'I') !== false) {
4374 if (isset($desc['ItalicAngle'])) {
4375 $desc['ItalicAngle'] -= 11;
4376 } else {
4377 $desc['ItalicAngle'] = -11;
4379 if (isset($desc['Flags'])) {
4380 $desc['Flags'] |= 64; //bit 7
4381 } else {
4382 $desc['Flags'] = 64;
4386 // check if the array of characters bounding boxes is defined
4387 if (!isset($cbbox)) {
4388 $cbbox = array();
4390 // initialize subsetchars
4391 $subsetchars = array_fill(0, 255, true);
4392 $this->setFontBuffer($fontkey, array('fontkey' => $fontkey, 'i' => $this->numfonts, 'type' => $type, 'name' => $name, 'desc' => $desc, 'up' => $up, 'ut' => $ut, 'cw' => $cw, 'cbbox' => $cbbox, 'dw' => $dw, 'enc' => $enc, 'cidinfo' => $cidinfo, 'file' => $file, 'ctg' => $ctg, 'subset' => $subset, 'subsetchars' => $subsetchars));
4393 if ($this->inxobj) {
4394 // we are inside an XObject template
4395 $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
4397 if (isset($diff) AND (!empty($diff))) {
4398 //Search existing encodings
4399 $d = 0;
4400 $nb = count($this->diffs);
4401 for ($i=1; $i <= $nb; ++$i) {
4402 if ($this->diffs[$i] == $diff) {
4403 $d = $i;
4404 break;
4407 if ($d == 0) {
4408 $d = $nb + 1;
4409 $this->diffs[$d] = $diff;
4411 $this->setFontSubBuffer($fontkey, 'diff', $d);
4413 if (!TCPDF_STATIC::empty_string($file)) {
4414 if (!isset($this->FontFiles[$file])) {
4415 if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4416 $this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4417 } elseif ($type != 'core') {
4418 $this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4420 } else {
4421 // update fontkeys that are sharing this font file
4422 $this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
4423 if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
4424 $this->FontFiles[$file]['fontkeys'][] = $fontkey;
4428 return $fontdata;
4432 * Sets the font used to print character strings.
4433 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4434 * The method can be called before the first page is created and the font is retained from page to page.
4435 * If you just wish to change the current font size, it is simpler to call SetFontSize().
4436 * Note: for the standard fonts, the font metric files must be accessible. There are three possibilities for this:<ul><li>They are in the current directory (the one where the running script lies)</li><li>They are in one of the directories defined by the include_path parameter</li><li>They are in the directory defined by the K_PATH_FONTS constant</li></ul><br />
4437 * @param $family (string) Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive):<ul><li>times (Times-Roman)</li><li>timesb (Times-Bold)</li><li>timesi (Times-Italic)</li><li>timesbi (Times-BoldItalic)</li><li>helvetica (Helvetica)</li><li>helveticab (Helvetica-Bold)</li><li>helveticai (Helvetica-Oblique)</li><li>helveticabi (Helvetica-BoldOblique)</li><li>courier (Courier)</li><li>courierb (Courier-Bold)</li><li>courieri (Courier-Oblique)</li><li>courierbi (Courier-BoldOblique)</li><li>symbol (Symbol)</li><li>zapfdingbats (ZapfDingbats)</li></ul> It is also possible to pass an empty string. In that case, the current family is retained.
4438 * @param $style (string) Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line through</li><li>O: overline</li></ul> or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined.
4439 * @param $size (float) Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
4440 * @param $fontfile (string) The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4441 * @param $subset (mixed) if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
4442 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4443 * @author Nicola Asuni
4444 * @public
4445 * @since 1.0
4446 * @see AddFont(), SetFontSize()
4448 public function SetFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4449 //Select a font; size given in points
4450 if ($size === null) {
4451 $size = $this->FontSizePt;
4453 if ($size < 0) {
4454 $size = 0;
4456 // try to add font (if not already added)
4457 $fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4458 $this->FontFamily = $fontdata['family'];
4459 $this->FontStyle = $fontdata['style'];
4460 if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
4461 // save subset chars of the previous font
4462 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
4464 $this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
4465 $this->SetFontSize($size, $out);
4469 * Defines the size of the current font.
4470 * @param $size (float) The font size in points.
4471 * @param $out (boolean) if true output the font size command, otherwise only set the font properties.
4472 * @public
4473 * @since 1.0
4474 * @see SetFont()
4476 public function SetFontSize($size, $out=true) {
4477 // font size in points
4478 $this->FontSizePt = $size;
4479 // font size in user units
4480 $this->FontSize = $size / $this->k;
4481 // calculate some font metrics
4482 if (isset($this->CurrentFont['desc']['FontBBox'])) {
4483 $bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4484 $font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4485 } else {
4486 $font_height = $size * 1.219;
4488 if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
4489 $font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
4491 if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
4492 $font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
4494 if (!isset($font_ascent) AND !isset($font_descent)) {
4495 // core font
4496 $font_ascent = 0.76 * $font_height;
4497 $font_descent = $font_height - $font_ascent;
4498 } elseif (!isset($font_descent)) {
4499 $font_descent = $font_height - $font_ascent;
4500 } elseif (!isset($font_ascent)) {
4501 $font_ascent = $font_height - $font_descent;
4503 $this->FontAscent = ($font_ascent / $this->k);
4504 $this->FontDescent = ($font_descent / $this->k);
4505 if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i'])) AND ($this->state == 2)) {
4506 $this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
4511 * Returns the bounding box of the current font in user units.
4512 * @return array
4513 * @public
4514 * @since 5.9.152 (2012-03-23)
4516 public function getFontBBox() {
4517 $fbbox = array();
4518 if (isset($this->CurrentFont['desc']['FontBBox'])) {
4519 $tmpbbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
4520 $fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4521 } else {
4522 // Find max width
4523 if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4524 $maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4525 } else {
4526 $maxw = 0;
4527 if (isset($this->CurrentFont['desc']['MissingWidth'])) {
4528 $maxw = max($maxw, $this->CurrentFont['desc']['MissingWidth']);
4530 if (isset($this->CurrentFont['desc']['AvgWidth'])) {
4531 $maxw = max($maxw, $this->CurrentFont['desc']['AvgWidth']);
4533 if (isset($this->CurrentFont['dw'])) {
4534 $maxw = max($maxw, $this->CurrentFont['dw']);
4536 foreach ($this->CurrentFont['cw'] as $char => $w) {
4537 $maxw = max($maxw, $w);
4539 if ($maxw == 0) {
4540 $maxw = 600;
4542 $maxw = $this->getAbsFontMeasure($maxw);
4544 $fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4546 return $fbbox;
4550 * Convert a relative font measure into absolute value.
4551 * @param $s (int) Font measure.
4552 * @return float Absolute measure.
4553 * @since 5.9.186 (2012-09-13)
4555 public function getAbsFontMeasure($s) {
4556 return ($s * $this->FontSize / 1000);
4560 * Returns the glyph bounding box of the specified character in the current font in user units.
4561 * @param $char (int) Input character code.
4562 * @return mixed array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4563 * @since 5.9.186 (2012-09-13)
4565 public function getCharBBox($char) {
4566 if (isset($this->CurrentFont['cbbox'][$char])) {
4567 return array_map(array($this,'getAbsFontMeasure'), $this->CurrentFont['cbbox'][intval($char)]);
4569 return false;
4573 * Return the font descent value
4574 * @param $font (string) font name
4575 * @param $style (string) font style
4576 * @param $size (float) The size (in points)
4577 * @return int font descent
4578 * @public
4579 * @author Nicola Asuni
4580 * @since 4.9.003 (2010-03-30)
4582 public function getFontDescent($font, $style='', $size=0) {
4583 $fontdata = $this->AddFont($font, $style);
4584 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4585 if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4586 $descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4587 } else {
4588 $descent = (1.219 * 0.24 * $size);
4590 return ($descent / $this->k);
4594 * Return the font ascent value.
4595 * @param $font (string) font name
4596 * @param $style (string) font style
4597 * @param $size (float) The size (in points)
4598 * @return int font ascent
4599 * @public
4600 * @author Nicola Asuni
4601 * @since 4.9.003 (2010-03-30)
4603 public function getFontAscent($font, $style='', $size=0) {
4604 $fontdata = $this->AddFont($font, $style);
4605 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4606 if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4607 $ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4608 } else {
4609 $ascent = 1.219 * 0.76 * $size;
4611 return ($ascent / $this->k);
4615 * Return true in the character is present in the specified font.
4616 * @param $char (mixed) Character to check (integer value or string)
4617 * @param $font (string) Font name (family name).
4618 * @param $style (string) Font style.
4619 * @return (boolean) true if the char is defined, false otherwise.
4620 * @public
4621 * @since 5.9.153 (2012-03-28)
4623 public function isCharDefined($char, $font='', $style='') {
4624 if (is_string($char)) {
4625 // get character code
4626 $char = TCPDF_FONTS::UTF8StringToArray($char, $this->isunicode, $this->CurrentFont);
4627 $char = $char[0];
4629 if (TCPDF_STATIC::empty_string($font)) {
4630 if (TCPDF_STATIC::empty_string($style)) {
4631 return (isset($this->CurrentFont['cw'][intval($char)]));
4633 $font = $this->FontFamily;
4635 $fontdata = $this->AddFont($font, $style);
4636 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4637 return (isset($fontinfo['cw'][intval($char)]));
4641 * Replace missing font characters on selected font with specified substitutions.
4642 * @param $text (string) Text to process.
4643 * @param $font (string) Font name (family name).
4644 * @param $style (string) Font style.
4645 * @param $subs (array) Array of possible character substitutions. The key is the character to check (integer value) and the value is a single intege value or an array of possible substitutes.
4646 * @return (string) Processed text.
4647 * @public
4648 * @since 5.9.153 (2012-03-28)
4650 public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4651 if (empty($subs)) {
4652 return $text;
4654 if (TCPDF_STATIC::empty_string($font)) {
4655 $font = $this->FontFamily;
4657 $fontdata = $this->AddFont($font, $style);
4658 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4659 $uniarr = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
4660 foreach ($uniarr as $k => $chr) {
4661 if (!isset($fontinfo['cw'][$chr])) {
4662 // this character is missing on the selected font
4663 if (isset($subs[$chr])) {
4664 // we have available substitutions
4665 if (is_array($subs[$chr])) {
4666 foreach($subs[$chr] as $s) {
4667 if (isset($fontinfo['cw'][$s])) {
4668 $uniarr[$k] = $s;
4669 break;
4672 } elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4673 $uniarr[$k] = $subs[$chr];
4678 return TCPDF_FONTS::UniArrSubString(TCPDF_FONTS::UTF8ArrayToUniArray($uniarr, $this->isunicode));
4682 * Defines the default monospaced font.
4683 * @param $font (string) Font name.
4684 * @public
4685 * @since 4.5.025
4687 public function SetDefaultMonospacedFont($font) {
4688 $this->default_monospaced_font = $font;
4692 * Creates a new internal link and returns its identifier. An internal link is a clickable area which directs to another place within the document.<br />
4693 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4694 * @public
4695 * @since 1.5
4696 * @see Cell(), Write(), Image(), Link(), SetLink()
4698 public function AddLink() {
4699 // create a new internal link
4700 $n = count($this->links) + 1;
4701 $this->links[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4702 return $n;
4706 * Defines the page and position a link points to.
4707 * @param $link (int) The link identifier returned by AddLink()
4708 * @param $y (float) Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4709 * @param $page (int) Number of target page; -1 indicates the current page (default value). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
4710 * @public
4711 * @since 1.5
4712 * @see AddLink()
4714 public function SetLink($link, $y=0, $page=-1) {
4715 $fixed = false;
4716 if (!empty($page) AND ($page[0] == '*')) {
4717 $page = intval(substr($page, 1));
4718 // this page number will not be changed when moving/add/deleting pages
4719 $fixed = true;
4721 if ($page < 0) {
4722 $page = $this->page;
4724 if ($y == -1) {
4725 $y = $this->y;
4727 $this->links[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4731 * Puts a link on a rectangular area of the page.
4732 * Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image.
4733 * @param $x (float) Abscissa of the upper-left corner of the rectangle
4734 * @param $y (float) Ordinate of the upper-left corner of the rectangle
4735 * @param $w (float) Width of the rectangle
4736 * @param $h (float) Height of the rectangle
4737 * @param $link (mixed) URL or identifier returned by AddLink()
4738 * @param $spaces (int) number of spaces on the text to link
4739 * @public
4740 * @since 1.5
4741 * @see AddLink(), Annotation(), Cell(), Write(), Image()
4743 public function Link($x, $y, $w, $h, $link, $spaces=0) {
4744 $this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4748 * Puts a markup annotation on a rectangular area of the page.
4749 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4750 * @param $x (float) Abscissa of the upper-left corner of the rectangle
4751 * @param $y (float) Ordinate of the upper-left corner of the rectangle
4752 * @param $w (float) Width of the rectangle
4753 * @param $h (float) Height of the rectangle
4754 * @param $text (string) annotation text or alternate content
4755 * @param $opt (array) array of options (see section 8.4 of PDF reference 1.7).
4756 * @param $spaces (int) number of spaces on the text to link
4757 * @public
4758 * @since 4.0.018 (2008-08-06)
4760 public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4761 if ($this->inxobj) {
4762 // store parameters for later use on template
4763 $this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4764 return;
4766 if ($x === '') {
4767 $x = $this->x;
4769 if ($y === '') {
4770 $y = $this->y;
4772 // check page for no-write regions and adapt page margins if necessary
4773 list($x, $y) = $this->checkPageRegions($h, $x, $y);
4774 // recalculate coordinates to account for graphic transformations
4775 if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
4776 for ($i=$this->transfmatrix_key; $i > 0; --$i) {
4777 $maxid = count($this->transfmatrix[$i]) - 1;
4778 for ($j=$maxid; $j >= 0; --$j) {
4779 $ctm = $this->transfmatrix[$i][$j];
4780 if (isset($ctm['a'])) {
4781 $x = $x * $this->k;
4782 $y = ($this->h - $y) * $this->k;
4783 $w = $w * $this->k;
4784 $h = $h * $this->k;
4785 // top left
4786 $xt = $x;
4787 $yt = $y;
4788 $x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4789 $y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4790 // top right
4791 $xt = $x + $w;
4792 $yt = $y;
4793 $x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4794 $y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4795 // bottom left
4796 $xt = $x;
4797 $yt = $y - $h;
4798 $x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4799 $y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4800 // bottom right
4801 $xt = $x + $w;
4802 $yt = $y - $h;
4803 $x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4804 $y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4805 // new coordinates (rectangle area)
4806 $x = min($x1, $x2, $x3, $x4);
4807 $y = max($y1, $y2, $y3, $y4);
4808 $w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
4809 $h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
4810 $x = $x / $this->k;
4811 $y = $this->h - ($y / $this->k);
4816 if ($this->page <= 0) {
4817 $page = 1;
4818 } else {
4819 $page = $this->page;
4821 if (!isset($this->PageAnnots[$page])) {
4822 $this->PageAnnots[$page] = array();
4824 $this->PageAnnots[$page][] = array('n' => ++$this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4825 if (!$this->pdfa_mode) {
4826 if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC::empty_string($opt['FS']))
4827 AND (@file_exists($opt['FS']) OR TCPDF_STATIC::isValidURL($opt['FS']))
4828 AND (!isset($this->embeddedfiles[basename($opt['FS'])]))) {
4829 $this->embeddedfiles[basename($opt['FS'])] = array('f' => ++$this->n, 'n' => ++$this->n, 'file' => $opt['FS']);
4832 // Add widgets annotation's icons
4833 if (isset($opt['mk']['i']) AND @file_exists($opt['mk']['i'])) {
4834 $this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4836 if (isset($opt['mk']['ri']) AND @file_exists($opt['mk']['ri'])) {
4837 $this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4839 if (isset($opt['mk']['ix']) AND @file_exists($opt['mk']['ix'])) {
4840 $this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4845 * Embedd the attached files.
4846 * @since 4.4.000 (2008-12-07)
4847 * @protected
4848 * @see Annotation()
4850 protected function _putEmbeddedFiles() {
4851 if ($this->pdfa_mode) {
4852 // embedded files are not allowed in PDF/A mode
4853 return;
4855 reset($this->embeddedfiles);
4856 foreach ($this->embeddedfiles as $filename => $filedata) {
4857 $data = TCPDF_STATIC::fileGetContents($filedata['file']);
4858 if ($data !== FALSE) {
4859 $rawsize = strlen($data);
4860 if ($rawsize > 0) {
4861 // update name tree
4862 $this->efnames[$filename] = $filedata['f'].' 0 R';
4863 // embedded file specification object
4864 $out = $this->_getobj($filedata['f'])."\n";
4865 $out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']).' /EF <</F '.$filedata['n'].' 0 R>> >>';
4866 $out .= "\n".'endobj';
4867 $this->_out($out);
4868 // embedded file object
4869 $filter = '';
4870 if ($this->compress) {
4871 $data = gzcompress($data);
4872 $filter = ' /Filter /FlateDecode';
4874 $stream = $this->_getrawstream($data, $filedata['n']);
4875 $out = $this->_getobj($filedata['n'])."\n";
4876 $out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
4877 $out .= ' stream'."\n".$stream."\n".'endstream';
4878 $out .= "\n".'endobj';
4879 $this->_out($out);
4886 * Prints a text cell at the specified position.
4887 * This method allows to place a string precisely on the page.
4888 * @param $x (float) Abscissa of the cell origin
4889 * @param $y (float) Ordinate of the cell origin
4890 * @param $txt (string) String to print
4891 * @param $fstroke (int) outline size in user units (false = disable)
4892 * @param $fclip (boolean) if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
4893 * @param $ffill (boolean) if true fills the text
4894 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
4895 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
4896 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
4897 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
4898 * @param $link (mixed) URL or identifier returned by AddLink().
4899 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
4900 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
4901 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li><li>B : cell bottom</li></ul>
4902 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
4903 * @param $rtloff (boolean) if true uses the page top-left corner as origin of axis for $x and $y initial position.
4904 * @public
4905 * @since 1.0
4906 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
4908 public function Text($x, $y, $txt, $fstroke=false, $fclip=false, $ffill=true, $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M', $rtloff=false) {
4909 $textrendermode = $this->textrendermode;
4910 $textstrokewidth = $this->textstrokewidth;
4911 $this->setTextRenderingMode($fstroke, $ffill, $fclip);
4912 $this->SetXY($x, $y, $rtloff);
4913 $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
4914 // restore previous rendering mode
4915 $this->textrendermode = $textrendermode;
4916 $this->textstrokewidth = $textstrokewidth;
4920 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
4921 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
4922 * This method is called automatically and should not be called directly by the application.
4923 * @return boolean
4924 * @public
4925 * @since 1.4
4926 * @see SetAutoPageBreak()
4928 public function AcceptPageBreak() {
4929 if ($this->num_columns > 1) {
4930 // multi column mode
4931 if ($this->current_column < ($this->num_columns - 1)) {
4932 // go to next column
4933 $this->selectColumn($this->current_column + 1);
4934 } elseif ($this->AutoPageBreak) {
4935 // add a new page
4936 $this->AddPage();
4937 // set first column
4938 $this->selectColumn(0);
4940 // avoid page breaking from checkPageBreak()
4941 return false;
4943 return $this->AutoPageBreak;
4947 * Add page if needed.
4948 * @param $h (float) Cell height. Default value: 0.
4949 * @param $y (mixed) starting y position, leave empty for current position.
4950 * @param $addpage (boolean) if true add a page, otherwise only return the true/false state
4951 * @return boolean true in case of page break, false otherwise.
4952 * @since 3.2.000 (2008-07-01)
4953 * @protected
4955 protected function checkPageBreak($h=0, $y='', $addpage=true) {
4956 if (TCPDF_STATIC::empty_string($y)) {
4957 $y = $this->y;
4959 $current_page = $this->page;
4960 if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
4961 if ($addpage) {
4962 //Automatic page break
4963 $x = $this->x;
4964 $this->AddPage($this->CurOrientation);
4965 $this->y = $this->tMargin;
4966 $oldpage = $this->page - 1;
4967 if ($this->rtl) {
4968 if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
4969 $this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
4970 } else {
4971 $this->x = $x;
4973 } else {
4974 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
4975 $this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
4976 } else {
4977 $this->x = $x;
4981 return true;
4983 if ($current_page != $this->page) {
4984 // account for columns mode
4985 return true;
4987 return false;
4991 * Prints a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
4992 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
4993 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
4994 * @param $h (float) Cell height. Default value: 0.
4995 * @param $txt (string) String to print. Default value: empty string.
4996 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
4997 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
4998 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
4999 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5000 * @param $link (mixed) URL or identifier returned by AddLink().
5001 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5002 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5003 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5004 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
5005 * @public
5006 * @since 1.0
5007 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
5009 public function Cell($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M') {
5010 $prev_cell_margin = $this->cell_margin;
5011 $prev_cell_padding = $this->cell_padding;
5012 $this->adjustCellPadding($border);
5013 if (!$ignore_min_height) {
5014 $min_cell_height = $this->getCellHeight($this->FontSize);
5015 if ($h < $min_cell_height) {
5016 $h = $min_cell_height;
5019 $this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
5020 // apply text shadow if enabled
5021 if ($this->txtshadow['enabled']) {
5022 // save data
5023 $x = $this->x;
5024 $y = $this->y;
5025 $bc = $this->bgcolor;
5026 $fc = $this->fgcolor;
5027 $sc = $this->strokecolor;
5028 $alpha = $this->alpha;
5029 // print shadow
5030 $this->x += $this->txtshadow['depth_w'];
5031 $this->y += $this->txtshadow['depth_h'];
5032 $this->SetFillColorArray($this->txtshadow['color']);
5033 $this->SetTextColorArray($this->txtshadow['color']);
5034 $this->SetDrawColorArray($this->txtshadow['color']);
5035 if ($this->txtshadow['opacity'] != $alpha['CA']) {
5036 $this->setAlpha($this->txtshadow['opacity'], $this->txtshadow['blend_mode']);
5038 if ($this->state == 2) {
5039 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5041 //restore data
5042 $this->x = $x;
5043 $this->y = $y;
5044 $this->SetFillColorArray($bc);
5045 $this->SetTextColorArray($fc);
5046 $this->SetDrawColorArray($sc);
5047 if ($this->txtshadow['opacity'] != $alpha['CA']) {
5048 $this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5051 if ($this->state == 2) {
5052 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5054 $this->cell_padding = $prev_cell_padding;
5055 $this->cell_margin = $prev_cell_margin;
5059 * Returns the PDF string code to print a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
5060 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5061 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
5062 * @param $h (float) Cell height. Default value: 0.
5063 * @param $txt (string) String to print. Default value: empty string.
5064 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5065 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
5066 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
5067 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5068 * @param $link (mixed) URL or identifier returned by AddLink().
5069 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5070 * @param $ignore_min_height (boolean) if true ignore automatic minimum height value.
5071 * @param $calign (string) cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
5072 * @param $valign (string) text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
5073 * @return string containing cell code
5074 * @protected
5075 * @since 1.0
5076 * @see Cell()
5078 protected function getCellCode($w, $h=0, $txt='', $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M') {
5079 // replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5080 $txt = str_replace(TCPDF_FONTS::unichr(160, $this->isunicode), ' ', $txt);
5081 $prev_cell_margin = $this->cell_margin;
5082 $prev_cell_padding = $this->cell_padding;
5083 $txt = TCPDF_STATIC::removeSHY($txt, $this->isunicode);
5084 $rs = ''; //string to be returned
5085 $this->adjustCellPadding($border);
5086 if (!$ignore_min_height) {
5087 $min_cell_height = $this->getCellHeight($this->FontSize);
5088 if ($h < $min_cell_height) {
5089 $h = $min_cell_height;
5092 $k = $this->k;
5093 // check page for no-write regions and adapt page margins if necessary
5094 list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
5095 if ($this->rtl) {
5096 $x = $this->x - $this->cell_margin['R'];
5097 } else {
5098 $x = $this->x + $this->cell_margin['L'];
5100 $y = $this->y + $this->cell_margin['T'];
5101 $prev_font_stretching = $this->font_stretching;
5102 $prev_font_spacing = $this->font_spacing;
5103 // cell vertical alignment
5104 switch ($calign) {
5105 case 'A': {
5106 // font top
5107 switch ($valign) {
5108 case 'T': {
5109 // top
5110 $y -= $this->cell_padding['T'];
5111 break;
5113 case 'B': {
5114 // bottom
5115 $y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5116 break;
5118 default:
5119 case 'C':
5120 case 'M': {
5121 // center
5122 $y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5123 break;
5126 break;
5128 case 'L': {
5129 // font baseline
5130 switch ($valign) {
5131 case 'T': {
5132 // top
5133 $y -= ($this->cell_padding['T'] + $this->FontAscent);
5134 break;
5136 case 'B': {
5137 // bottom
5138 $y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5139 break;
5141 default:
5142 case 'C':
5143 case 'M': {
5144 // center
5145 $y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5146 break;
5149 break;
5151 case 'D': {
5152 // font bottom
5153 switch ($valign) {
5154 case 'T': {
5155 // top
5156 $y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5157 break;
5159 case 'B': {
5160 // bottom
5161 $y -= ($h - $this->cell_padding['B']);
5162 break;
5164 default:
5165 case 'C':
5166 case 'M': {
5167 // center
5168 $y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5169 break;
5172 break;
5174 case 'B': {
5175 // cell bottom
5176 $y -= $h;
5177 break;
5179 case 'C':
5180 case 'M': {
5181 // cell center
5182 $y -= ($h / 2);
5183 break;
5185 default:
5186 case 'T': {
5187 // cell top
5188 break;
5191 // text vertical alignment
5192 switch ($valign) {
5193 case 'T': {
5194 // top
5195 $yt = $y + $this->cell_padding['T'];
5196 break;
5198 case 'B': {
5199 // bottom
5200 $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5201 break;
5203 default:
5204 case 'C':
5205 case 'M': {
5206 // center
5207 $yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5208 break;
5211 $basefonty = $yt + $this->FontAscent;
5212 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5213 if ($this->rtl) {
5214 $w = $x - $this->lMargin;
5215 } else {
5216 $w = $this->w - $this->rMargin - $x;
5219 $s = '';
5220 // fill and borders
5221 if (is_string($border) AND (strlen($border) == 4)) {
5222 // full border
5223 $border = 1;
5225 if ($fill OR ($border == 1)) {
5226 if ($fill) {
5227 $op = ($border == 1) ? 'B' : 'f';
5228 } else {
5229 $op = 'S';
5231 if ($this->rtl) {
5232 $xk = (($x - $w) * $k);
5233 } else {
5234 $xk = ($x * $k);
5236 $s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5238 // draw borders
5239 $s .= $this->getCellBorder($x, $y, $w, $h, $border);
5240 if ($txt != '') {
5241 $txt2 = $txt;
5242 if ($this->isunicode) {
5243 if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
5244 $txt2 = TCPDF_FONTS::UTF8ToLatin1($txt2, $this->isunicode, $this->CurrentFont);
5245 } else {
5246 $unicode = TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont); // array of UTF-8 unicode values
5247 $unicode = TCPDF_FONTS::utf8Bidi($unicode, '', $this->tmprtl, $this->isunicode, $this->CurrentFont);
5248 // replace thai chars (if any)
5249 if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
5250 // number of chars
5251 $numchars = count($unicode);
5252 // po pla, for far, for fan
5253 $longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5254 // do chada, to patak
5255 $lowtail = array(0x0e0e, 0x0e0f);
5256 // mai hun arkad, sara i, sara ii, sara ue, sara uee
5257 $upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5258 // mai ek, mai tho, mai tri, mai chattawa, karan
5259 $tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5260 // sara u, sara uu, pinthu
5261 $lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5262 $output = array();
5263 for ($i = 0; $i < $numchars; $i++) {
5264 if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5265 $ch0 = $unicode[$i];
5266 $ch1 = ($i > 0) ? $unicode[($i - 1)] : 0;
5267 $ch2 = ($i > 1) ? $unicode[($i - 2)] : 0;
5268 $chn = ($i < ($numchars - 1)) ? $unicode[($i + 1)] : 0;
5269 if (in_array($ch0, $tonemark)) {
5270 if ($chn == 0x0e33) {
5271 // sara um
5272 if (in_array($ch1, $longtail)) {
5273 // tonemark at upper left
5274 $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5275 } else {
5276 // tonemark at upper right (normal position)
5277 $output[] = $ch0;
5279 } elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5280 // tonemark at lower left
5281 $output[] = $this->replaceChar($ch0, (0xf705 + $ch0 - 0x0e48));
5282 } elseif (in_array($ch1, $upvowel)) {
5283 if (in_array($ch2, $longtail)) {
5284 // tonemark at upper left
5285 $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5286 } else {
5287 // tonemark at upper right (normal position)
5288 $output[] = $ch0;
5290 } else {
5291 // tonemark at lower right
5292 $output[] = $this->replaceChar($ch0, (0xf70a + $ch0 - 0x0e48));
5294 } elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5295 // add lower left nikhahit and sara aa
5296 if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5297 $output[] = 0xf711;
5298 $this->CurrentFont['subsetchars'][0xf711] = true;
5299 $output[] = 0x0e32;
5300 $this->CurrentFont['subsetchars'][0x0e32] = true;
5301 } else {
5302 $output[] = $ch0;
5304 } elseif (in_array($ch1, $longtail)) {
5305 if ($ch0 == 0x0e31) {
5306 // lower left mai hun arkad
5307 $output[] = $this->replaceChar($ch0, 0xf710);
5308 } elseif (in_array($ch0, $upvowel)) {
5309 // lower left
5310 $output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5311 } elseif ($ch0 == 0x0e47) {
5312 // lower left mai tai koo
5313 $output[] = $this->replaceChar($ch0, 0xf712);
5314 } else {
5315 // normal character
5316 $output[] = $ch0;
5318 } elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5319 // lower vowel
5320 $output[] = $this->replaceChar($ch0, (0xf718 + $ch0 - 0x0e38));
5321 } elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5322 // yo ying without lower part
5323 $output[] = $this->replaceChar($ch0, 0xf70f);
5324 } elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5325 // tho santan without lower part
5326 $output[] = $this->replaceChar($ch0, 0xf700);
5327 } else {
5328 $output[] = $ch0;
5330 } else {
5331 // non-thai character
5332 $output[] = $unicode[$i];
5335 $unicode = $output;
5336 // update font subsetchars
5337 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
5338 } // end of K_THAI_TOPCHARS
5339 $txt2 = TCPDF_FONTS::arrUTF8ToUTF16BE($unicode, false);
5342 $txt2 = TCPDF_STATIC::_escape($txt2);
5343 // get current text width (considering general font stretching and spacing)
5344 $txwidth = $this->GetStringWidth($txt);
5345 $width = $txwidth;
5346 // check for stretch mode
5347 if ($stretch > 0) {
5348 // calculate ratio between cell width and text width
5349 if ($width <= 0) {
5350 $ratio = 1;
5351 } else {
5352 $ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
5354 // check if stretching is required
5355 if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
5356 // the text will be stretched to fit cell width
5357 if ($stretch > 2) {
5358 // set new character spacing
5359 $this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
5360 } else {
5361 // set new horizontal stretching
5362 $this->font_stretching *= $ratio;
5364 // recalculate text width (the text fills the entire cell)
5365 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5366 // reset alignment
5367 $align = '';
5370 if ($this->font_stretching != 100) {
5371 // apply font stretching
5372 $rs .= sprintf('BT %F Tz ET ', $this->font_stretching);
5374 if ($this->font_spacing != 0) {
5375 // increase/decrease font spacing
5376 $rs .= sprintf('BT %F Tc ET ', ($this->font_spacing * $this->k));
5378 if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5379 $s .= 'q '.$this->TextColor.' ';
5381 // rendering mode
5382 $s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode, ($this->textstrokewidth * $this->k));
5383 // count number of spaces
5384 $ns = substr_count($txt, chr(32));
5385 // Justification
5386 $spacewidth = 0;
5387 if (($align == 'J') AND ($ns > 0)) {
5388 if ($this->isUnicodeFont()) {
5389 // get string width without spaces
5390 $width = $this->GetStringWidth(str_replace(' ', '', $txt));
5391 // calculate average space width
5392 $spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / ($this->FontSize?$this->FontSize:1);
5393 if ($this->font_stretching != 100) {
5394 // word spacing is affected by stretching
5395 $spacewidth /= ($this->font_stretching / 100);
5397 // set word position to be used with TJ operator
5398 $txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5399 $unicode_justification = true;
5400 } else {
5401 // get string width
5402 $width = $txwidth;
5403 // new space width
5404 $spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
5405 if ($this->font_stretching != 100) {
5406 // word spacing (Tw) is affected by stretching
5407 $spacewidth /= ($this->font_stretching / 100);
5409 // set word spacing
5410 $rs .= sprintf('BT %F Tw ET ', $spacewidth);
5412 $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
5414 // replace carriage return characters
5415 $txt2 = str_replace("\r", ' ', $txt2);
5416 switch ($align) {
5417 case 'C': {
5418 $dx = ($w - $width) / 2;
5419 break;
5421 case 'R': {
5422 if ($this->rtl) {
5423 $dx = $this->cell_padding['R'];
5424 } else {
5425 $dx = $w - $width - $this->cell_padding['R'];
5427 break;
5429 case 'L': {
5430 if ($this->rtl) {
5431 $dx = $w - $width - $this->cell_padding['L'];
5432 } else {
5433 $dx = $this->cell_padding['L'];
5435 break;
5437 case 'J':
5438 default: {
5439 if ($this->rtl) {
5440 $dx = $this->cell_padding['R'];
5441 } else {
5442 $dx = $this->cell_padding['L'];
5444 break;
5447 if ($this->rtl) {
5448 $xdx = $x - $dx - $width;
5449 } else {
5450 $xdx = $x + $dx;
5452 $xdk = $xdx * $k;
5453 // print text
5454 $s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
5455 if (isset($uniblock)) {
5456 // print overlapping characters as separate string
5457 $xshift = 0; // horizontal shift
5458 $ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
5459 $spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
5460 foreach ($uniblock as $uk => $uniarr) {
5461 if (($uk % 2) == 0) {
5462 // x space to skip
5463 if ($spacewidth != 0) {
5464 // justification shift
5465 $xshift += (count(array_keys($uniarr, 32)) * $spw);
5467 $xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5468 } else {
5469 // character to print
5470 $topchr = TCPDF_FONTS::arrUTF8ToUTF16BE($uniarr, false);
5471 $topchr = TCPDF_STATIC::_escape($topchr);
5472 $s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
5476 if ($this->underline) {
5477 $s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5479 if ($this->linethrough) {
5480 $s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5482 if ($this->overline) {
5483 $s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5485 if ($this->ColorFlag AND ($this->textrendermode < 4)) {
5486 $s .= ' Q';
5488 if ($link) {
5489 $this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5492 // output cell
5493 if ($s) {
5494 // output cell
5495 $rs .= $s;
5496 if ($this->font_spacing != 0) {
5497 // reset font spacing mode
5498 $rs .= ' BT 0 Tc ET';
5500 if ($this->font_stretching != 100) {
5501 // reset font stretching mode
5502 $rs .= ' BT 100 Tz ET';
5505 // reset word spacing
5506 if (!$this->isUnicodeFont() AND ($align == 'J')) {
5507 $rs .= ' BT 0 Tw ET';
5509 // reset stretching and spacing
5510 $this->font_stretching = $prev_font_stretching;
5511 $this->font_spacing = $prev_font_spacing;
5512 $this->lasth = $h;
5513 if ($ln > 0) {
5514 //Go to the beginning of the next line
5515 $this->y = $y + $h + $this->cell_margin['B'];
5516 if ($ln == 1) {
5517 if ($this->rtl) {
5518 $this->x = $this->w - $this->rMargin;
5519 } else {
5520 $this->x = $this->lMargin;
5523 } else {
5524 // go left or right by case
5525 if ($this->rtl) {
5526 $this->x = $x - $w - $this->cell_margin['L'];
5527 } else {
5528 $this->x = $x + $w + $this->cell_margin['R'];
5531 $gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5532 $rs = $gstyles.$rs;
5533 $this->cell_padding = $prev_cell_padding;
5534 $this->cell_margin = $prev_cell_margin;
5535 return $rs;
5539 * Replace a char if is defined on the current font.
5540 * @param $oldchar (int) Integer code (unicode) of the character to replace.
5541 * @param $newchar (int) Integer code (unicode) of the new character.
5542 * @return int the replaced char or the old char in case the new char i not defined
5543 * @protected
5544 * @since 5.9.167 (2012-06-22)
5546 protected function replaceChar($oldchar, $newchar) {
5547 if ($this->isCharDefined($newchar)) {
5548 // add the new char on the subset list
5549 $this->CurrentFont['subsetchars'][$newchar] = true;
5550 // return the new character
5551 return $newchar;
5553 // return the old char
5554 return $oldchar;
5558 * Returns the code to draw the cell border
5559 * @param $x (float) X coordinate.
5560 * @param $y (float) Y coordinate.
5561 * @param $w (float) Cell width.
5562 * @param $h (float) Cell height.
5563 * @param $brd (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5564 * @return string containing cell border code
5565 * @protected
5566 * @see SetLineStyle()
5567 * @since 5.7.000 (2010-08-02)
5569 protected function getCellBorder($x, $y, $w, $h, $brd) {
5570 $s = ''; // string to be returned
5571 if (empty($brd)) {
5572 return $s;
5574 if ($brd == 1) {
5575 $brd = array('LRTB' => true);
5577 // calculate coordinates for border
5578 $k = $this->k;
5579 if ($this->rtl) {
5580 $xeL = ($x - $w) * $k;
5581 $xeR = $x * $k;
5582 } else {
5583 $xeL = $x * $k;
5584 $xeR = ($x + $w) * $k;
5586 $yeL = (($this->h - ($y + $h)) * $k);
5587 $yeT = (($this->h - $y) * $k);
5588 $xeT = $xeL;
5589 $xeB = $xeR;
5590 $yeR = $yeT;
5591 $yeB = $yeL;
5592 if (is_string($brd)) {
5593 // convert string to array
5594 $slen = strlen($brd);
5595 $newbrd = array();
5596 for ($i = 0; $i < $slen; ++$i) {
5597 $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5599 $brd = $newbrd;
5601 if (isset($brd['mode'])) {
5602 $mode = $brd['mode'];
5603 unset($brd['mode']);
5604 } else {
5605 $mode = 'normal';
5607 foreach ($brd as $border => $style) {
5608 if (is_array($style) AND !empty($style)) {
5609 // apply border style
5610 $prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
5611 $s .= $this->SetLineStyle($style, true)."\n";
5613 switch ($mode) {
5614 case 'ext': {
5615 $off = (($this->LineWidth / 2) * $k);
5616 $xL = $xeL - $off;
5617 $xR = $xeR + $off;
5618 $yT = $yeT + $off;
5619 $yL = $yeL - $off;
5620 $xT = $xL;
5621 $xB = $xR;
5622 $yR = $yT;
5623 $yB = $yL;
5624 $w += $this->LineWidth;
5625 $h += $this->LineWidth;
5626 break;
5628 case 'int': {
5629 $off = ($this->LineWidth / 2) * $k;
5630 $xL = $xeL + $off;
5631 $xR = $xeR - $off;
5632 $yT = $yeT - $off;
5633 $yL = $yeL + $off;
5634 $xT = $xL;
5635 $xB = $xR;
5636 $yR = $yT;
5637 $yB = $yL;
5638 $w -= $this->LineWidth;
5639 $h -= $this->LineWidth;
5640 break;
5642 case 'normal':
5643 default: {
5644 $xL = $xeL;
5645 $xT = $xeT;
5646 $xB = $xeB;
5647 $xR = $xeR;
5648 $yL = $yeL;
5649 $yT = $yeT;
5650 $yB = $yeB;
5651 $yR = $yeR;
5652 break;
5655 // draw borders by case
5656 if (strlen($border) == 4) {
5657 $s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5658 } elseif (strlen($border) == 3) {
5659 if (strpos($border,'B') === false) { // LTR
5660 $s .= sprintf('%F %F m ', $xL, $yL);
5661 $s .= sprintf('%F %F l ', $xT, $yT);
5662 $s .= sprintf('%F %F l ', $xR, $yR);
5663 $s .= sprintf('%F %F l ', $xB, $yB);
5664 $s .= 'S ';
5665 } elseif (strpos($border,'L') === false) { // TRB
5666 $s .= sprintf('%F %F m ', $xT, $yT);
5667 $s .= sprintf('%F %F l ', $xR, $yR);
5668 $s .= sprintf('%F %F l ', $xB, $yB);
5669 $s .= sprintf('%F %F l ', $xL, $yL);
5670 $s .= 'S ';
5671 } elseif (strpos($border,'T') === false) { // RBL
5672 $s .= sprintf('%F %F m ', $xR, $yR);
5673 $s .= sprintf('%F %F l ', $xB, $yB);
5674 $s .= sprintf('%F %F l ', $xL, $yL);
5675 $s .= sprintf('%F %F l ', $xT, $yT);
5676 $s .= 'S ';
5677 } elseif (strpos($border,'R') === false) { // BLT
5678 $s .= sprintf('%F %F m ', $xB, $yB);
5679 $s .= sprintf('%F %F l ', $xL, $yL);
5680 $s .= sprintf('%F %F l ', $xT, $yT);
5681 $s .= sprintf('%F %F l ', $xR, $yR);
5682 $s .= 'S ';
5684 } elseif (strlen($border) == 2) {
5685 if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5686 $s .= sprintf('%F %F m ', $xL, $yL);
5687 $s .= sprintf('%F %F l ', $xT, $yT);
5688 $s .= sprintf('%F %F l ', $xR, $yR);
5689 $s .= 'S ';
5690 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5691 $s .= sprintf('%F %F m ', $xT, $yT);
5692 $s .= sprintf('%F %F l ', $xR, $yR);
5693 $s .= sprintf('%F %F l ', $xB, $yB);
5694 $s .= 'S ';
5695 } elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5696 $s .= sprintf('%F %F m ', $xR, $yR);
5697 $s .= sprintf('%F %F l ', $xB, $yB);
5698 $s .= sprintf('%F %F l ', $xL, $yL);
5699 $s .= 'S ';
5700 } elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5701 $s .= sprintf('%F %F m ', $xB, $yB);
5702 $s .= sprintf('%F %F l ', $xL, $yL);
5703 $s .= sprintf('%F %F l ', $xT, $yT);
5704 $s .= 'S ';
5705 } elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5706 $s .= sprintf('%F %F m ', $xL, $yL);
5707 $s .= sprintf('%F %F l ', $xT, $yT);
5708 $s .= 'S ';
5709 $s .= sprintf('%F %F m ', $xR, $yR);
5710 $s .= sprintf('%F %F l ', $xB, $yB);
5711 $s .= 'S ';
5712 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5713 $s .= sprintf('%F %F m ', $xT, $yT);
5714 $s .= sprintf('%F %F l ', $xR, $yR);
5715 $s .= 'S ';
5716 $s .= sprintf('%F %F m ', $xB, $yB);
5717 $s .= sprintf('%F %F l ', $xL, $yL);
5718 $s .= 'S ';
5720 } else { // strlen($border) == 1
5721 if (strpos($border,'L') !== false) { // L
5722 $s .= sprintf('%F %F m ', $xL, $yL);
5723 $s .= sprintf('%F %F l ', $xT, $yT);
5724 $s .= 'S ';
5725 } elseif (strpos($border,'T') !== false) { // T
5726 $s .= sprintf('%F %F m ', $xT, $yT);
5727 $s .= sprintf('%F %F l ', $xR, $yR);
5728 $s .= 'S ';
5729 } elseif (strpos($border,'R') !== false) { // R
5730 $s .= sprintf('%F %F m ', $xR, $yR);
5731 $s .= sprintf('%F %F l ', $xB, $yB);
5732 $s .= 'S ';
5733 } elseif (strpos($border,'B') !== false) { // B
5734 $s .= sprintf('%F %F m ', $xB, $yB);
5735 $s .= sprintf('%F %F l ', $xL, $yL);
5736 $s .= 'S ';
5739 if (is_array($style) AND !empty($style)) {
5740 // reset border style to previous value
5741 $s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
5744 return $s;
5748 * This method allows printing text with line breaks.
5749 * They can be automatic (as soon as the text reaches the right border of the cell) or explicit (via the \n character). As many cells as necessary are output, one below the other.<br />
5750 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5751 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
5752 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
5753 * @param $txt (string) String to print
5754 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
5755 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value when $ishtml=false)</li></ul>
5756 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
5757 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
5758 * @param $x (float) x position in user units
5759 * @param $y (float) y position in user units
5760 * @param $reseth (boolean) if true reset the last cell height (default true).
5761 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
5762 * @param $ishtml (boolean) INTERNAL USE ONLY -- set to true if $txt is HTML content (default = false). Never set this parameter to true, use instead writeHTMLCell() or writeHTML() methods.
5763 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
5764 * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false.
5765 * @param $valign (string) Vertical alignment of text (requires $maxh = $h > 0). Possible values are:<ul><li>T: TOP</li><li>M: middle</li><li>B: bottom</li></ul>. This feature works only when $ishtml=false and the cell must fit in a single page.
5766 * @param $fitcell (boolean) if true attempt to fit all the text within the cell by reducing the font size (do not work in HTML mode). $maxh must be greater than 0 and wqual to $h.
5767 * @return int Return the number of cells or 1 for html mode.
5768 * @public
5769 * @since 1.3
5770 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5772 public function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0, $valign='T', $fitcell=false) {
5773 $prev_cell_margin = $this->cell_margin;
5774 $prev_cell_padding = $this->cell_padding;
5775 // adjust internal padding
5776 $this->adjustCellPadding($border);
5777 $mc_padding = $this->cell_padding;
5778 $mc_margin = $this->cell_margin;
5779 $this->cell_padding['T'] = 0;
5780 $this->cell_padding['B'] = 0;
5781 $this->setCellMargins(0, 0, 0, 0);
5782 if (TCPDF_STATIC::empty_string($this->lasth) OR $reseth) {
5783 // reset row height
5784 $this->resetLastH();
5786 if (!TCPDF_STATIC::empty_string($y)) {
5787 $this->SetY($y);
5788 } else {
5789 $y = $this->GetY();
5791 $resth = 0;
5792 if (($h > 0) AND $this->inPageBody() AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
5793 // spit cell in more pages/columns
5794 $newh = ($this->PageBreakTrigger - $y);
5795 $resth = ($h - $newh); // cell to be printed on the next page/column
5796 $h = $newh;
5798 // get current page number
5799 $startpage = $this->page;
5800 // get current column
5801 $startcolumn = $this->current_column;
5802 if (!TCPDF_STATIC::empty_string($x)) {
5803 $this->SetX($x);
5804 } else {
5805 $x = $this->GetX();
5807 // check page for no-write regions and adapt page margins if necessary
5808 list($x, $y) = $this->checkPageRegions(0, $x, $y);
5809 // apply margins
5810 $oy = $y + $mc_margin['T'];
5811 if ($this->rtl) {
5812 $ox = ($this->w - $x - $mc_margin['R']);
5813 } else {
5814 $ox = ($x + $mc_margin['L']);
5816 $this->x = $ox;
5817 $this->y = $oy;
5818 // set width
5819 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5820 if ($this->rtl) {
5821 $w = ($this->x - $this->lMargin - $mc_margin['L']);
5822 } else {
5823 $w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5826 // store original margin values
5827 $lMargin = $this->lMargin;
5828 $rMargin = $this->rMargin;
5829 if ($this->rtl) {
5830 $this->rMargin = ($this->w - $this->x);
5831 $this->lMargin = ($this->x - $w);
5832 } else {
5833 $this->lMargin = ($this->x);
5834 $this->rMargin = ($this->w - $this->x - $w);
5836 $this->clMargin = $this->lMargin;
5837 $this->crMargin = $this->rMargin;
5838 if ($autopadding) {
5839 // add top padding
5840 $this->y += $mc_padding['T'];
5842 if ($ishtml) { // ******* Write HTML text
5843 $this->writeHTML($txt, true, false, $reseth, true, $align);
5844 $nl = 1;
5845 } else { // ******* Write simple text
5846 $prev_FontSizePt = $this->FontSizePt;
5847 if ($fitcell) {
5848 // ajust height values
5849 $tobottom = ($this->h - $this->y - $this->bMargin - $this->cell_padding['T'] - $this->cell_padding['B']);
5850 $h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5852 // vertical alignment
5853 if ($maxh > 0) {
5854 // get text height
5855 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5856 if ($fitcell) {
5857 // try to reduce font size to fit text on cell (use a quick search algorithm)
5858 $fmin = 1;
5859 $fmax = $this->FontSizePt;
5860 $diff_epsilon = (1 / $this->k); // one point (min resolution)
5861 $maxit = ($fmax - $fmin); // max number of iterations
5862 while ($maxit > 0) {
5863 $fmid = (($fmax + $fmin) / 2);
5864 $this->SetFontSize($fmid, false);
5865 $this->resetLastH();
5866 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5867 $diff = ($maxh - $text_height);
5868 if ($diff >= 0) {
5869 if ($diff < $diff_epsilon) {
5870 break;
5871 } else {
5872 $fmin = $fmid;
5874 } else {
5875 $fmax = $fmid;
5877 --$maxit;
5879 $this->SetFontSize($this->FontSizePt);
5881 if ($text_height < $maxh) {
5882 if ($valign == 'M') {
5883 // text vertically centered
5884 $this->y += (($maxh - $text_height) / 2);
5885 } elseif ($valign == 'B') {
5886 // text vertically aligned on bottom
5887 $this->y += ($maxh - $text_height);
5891 $nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
5892 if ($fitcell) {
5893 // restore font size
5894 $this->SetFontSize($prev_FontSizePt);
5897 if ($autopadding) {
5898 // add bottom padding
5899 $this->y += $mc_padding['B'];
5901 // Get end-of-text Y position
5902 $currentY = $this->y;
5903 // get latest page number
5904 $endpage = $this->page;
5905 if ($resth > 0) {
5906 $skip = ($endpage - $startpage);
5907 $tmpresth = $resth;
5908 while ($tmpresth > 0) {
5909 if ($skip <= 0) {
5910 // add a page (or trig AcceptPageBreak() for multicolumn mode)
5911 $this->checkPageBreak($this->PageBreakTrigger + 1);
5913 if ($this->num_columns > 1) {
5914 $tmpresth -= ($this->h - $this->y - $this->bMargin);
5915 } else {
5916 $tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
5918 --$skip;
5920 $currentY = $this->y;
5921 $endpage = $this->page;
5923 // get latest column
5924 $endcolumn = $this->current_column;
5925 if ($this->num_columns == 0) {
5926 $this->num_columns = 1;
5928 // disable page regions check
5929 $check_page_regions = $this->check_page_regions;
5930 $this->check_page_regions = false;
5931 // get border modes
5932 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
5933 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
5934 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
5935 // design borders around HTML cells.
5936 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
5937 $ccode = '';
5938 $this->setPage($page);
5939 if ($this->num_columns < 2) {
5940 // single-column mode
5941 $this->SetX($x);
5942 $this->y = $this->tMargin;
5944 // account for margin changes
5945 if ($page > $startpage) {
5946 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
5947 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
5948 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
5949 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
5952 if ($startpage == $endpage) {
5953 // single page
5954 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
5955 $this->selectColumn($column);
5956 if ($this->rtl) {
5957 $this->x -= $mc_margin['R'];
5958 } else {
5959 $this->x += $mc_margin['L'];
5961 if ($startcolumn == $endcolumn) { // single column
5962 $cborder = $border;
5963 $h = max($h, ($currentY - $oy));
5964 $this->y = $oy;
5965 } elseif ($column == $startcolumn) { // first column
5966 $cborder = $border_start;
5967 $this->y = $oy;
5968 $h = $this->h - $this->y - $this->bMargin;
5969 } elseif ($column == $endcolumn) { // end column
5970 $cborder = $border_end;
5971 $h = $currentY - $this->y;
5972 if ($resth > $h) {
5973 $h = $resth;
5975 } else { // middle column
5976 $cborder = $border_middle;
5977 $h = $this->h - $this->y - $this->bMargin;
5978 $resth -= $h;
5980 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
5981 } // end for each column
5982 } elseif ($page == $startpage) { // first page
5983 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
5984 $this->selectColumn($column);
5985 if ($this->rtl) {
5986 $this->x -= $mc_margin['R'];
5987 } else {
5988 $this->x += $mc_margin['L'];
5990 if ($column == $startcolumn) { // first column
5991 $cborder = $border_start;
5992 $this->y = $oy;
5993 $h = $this->h - $this->y - $this->bMargin;
5994 } else { // middle column
5995 $cborder = $border_middle;
5996 $h = $this->h - $this->y - $this->bMargin;
5997 $resth -= $h;
5999 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6000 } // end for each column
6001 } elseif ($page == $endpage) { // last page
6002 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
6003 $this->selectColumn($column);
6004 if ($this->rtl) {
6005 $this->x -= $mc_margin['R'];
6006 } else {
6007 $this->x += $mc_margin['L'];
6009 if ($column == $endcolumn) {
6010 // end column
6011 $cborder = $border_end;
6012 $h = $currentY - $this->y;
6013 if ($resth > $h) {
6014 $h = $resth;
6016 } else {
6017 // middle column
6018 $cborder = $border_middle;
6019 $h = $this->h - $this->y - $this->bMargin;
6020 $resth -= $h;
6022 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6023 } // end for each column
6024 } else { // middle page
6025 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
6026 $this->selectColumn($column);
6027 if ($this->rtl) {
6028 $this->x -= $mc_margin['R'];
6029 } else {
6030 $this->x += $mc_margin['L'];
6032 $cborder = $border_middle;
6033 $h = $this->h - $this->y - $this->bMargin;
6034 $resth -= $h;
6035 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6036 } // end for each column
6038 if ($cborder OR $fill) {
6039 $offsetlen = strlen($ccode);
6040 // draw border and fill
6041 if ($this->inxobj) {
6042 // we are inside an XObject template
6043 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
6044 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
6045 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
6046 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
6047 } else {
6048 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
6049 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
6051 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
6052 $pstart = substr($pagebuff, 0, $pagemark);
6053 $pend = substr($pagebuff, $pagemark);
6054 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
6055 } else {
6056 if (end($this->transfmrk[$this->page]) !== false) {
6057 $pagemarkkey = key($this->transfmrk[$this->page]);
6058 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
6059 $this->transfmrk[$this->page][$pagemarkkey] += $offsetlen;
6060 } elseif ($this->InFooter) {
6061 $pagemark = $this->footerpos[$this->page];
6062 $this->footerpos[$this->page] += $offsetlen;
6063 } else {
6064 $pagemark = $this->intmrk[$this->page];
6065 $this->intmrk[$this->page] += $offsetlen;
6067 $pagebuff = $this->getPageBuffer($this->page);
6068 $pstart = substr($pagebuff, 0, $pagemark);
6069 $pend = substr($pagebuff, $pagemark);
6070 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
6073 } // end for each page
6074 // restore page regions check
6075 $this->check_page_regions = $check_page_regions;
6076 // Get end-of-cell Y position
6077 $currentY = $this->GetY();
6078 // restore previous values
6079 if ($this->num_columns > 1) {
6080 $this->selectColumn();
6081 } else {
6082 // restore original margins
6083 $this->lMargin = $lMargin;
6084 $this->rMargin = $rMargin;
6085 if ($this->page > $startpage) {
6086 // check for margin variations between pages (i.e. booklet mode)
6087 $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$startpage]['olm']);
6088 $dr = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$startpage]['orm']);
6089 if (($dl != 0) OR ($dr != 0)) {
6090 $this->lMargin += $dl;
6091 $this->rMargin += $dr;
6095 if ($ln > 0) {
6096 //Go to the beginning of the next line
6097 $this->SetY($currentY + $mc_margin['B']);
6098 if ($ln == 2) {
6099 $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6101 } else {
6102 // go left or right by case
6103 $this->setPage($startpage);
6104 $this->y = $y;
6105 $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6107 $this->setContentMark();
6108 $this->cell_padding = $prev_cell_padding;
6109 $this->cell_margin = $prev_cell_margin;
6110 $this->clMargin = $this->lMargin;
6111 $this->crMargin = $this->rMargin;
6112 return $nl;
6116 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6117 * @param $txt (string) String for calculating his height
6118 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6119 * @param $reseth (boolean) if true reset the last cell height (default false).
6120 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6121 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6122 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6123 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6124 * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
6125 * @public
6126 * @since 4.5.011
6128 public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6129 if ($txt === NULL) {
6130 return 0;
6132 if ($txt === '') {
6133 // empty string
6134 return 1;
6136 // adjust internal padding
6137 $prev_cell_padding = $this->cell_padding;
6138 $prev_lasth = $this->lasth;
6139 if (is_array($cellpadding)) {
6140 $this->cell_padding = $cellpadding;
6142 $this->adjustCellPadding($border);
6143 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
6144 if ($this->rtl) {
6145 $w = $this->x - $this->lMargin;
6146 } else {
6147 $w = $this->w - $this->rMargin - $this->x;
6150 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6151 if ($reseth) {
6152 // reset row height
6153 $this->resetLastH();
6155 $lines = 1;
6156 $sum = 0;
6157 $chars = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($txt, $this->isunicode, $this->CurrentFont), $txt, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6158 $charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6159 $length = count($chars);
6160 $lastSeparator = -1;
6161 for ($i = 0; $i < $length; ++$i) {
6162 $c = $chars[$i];
6163 $charWidth = $charsWidth[$i];
6164 if (($c != 160)
6165 AND (($c == 173)
6166 OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6167 OR (($c == 45)
6168 AND ($i > 0) AND ($i < ($length - 1))
6169 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i - 1)], $this->isunicode))
6170 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6174 $lastSeparator = $i;
6176 if ((($sum + $charWidth) > $wmax) OR ($c == 10)) {
6177 ++$lines;
6178 if ($c == 10) {
6179 $lastSeparator = -1;
6180 $sum = 0;
6181 } elseif ($lastSeparator != -1) {
6182 $i = $lastSeparator;
6183 $lastSeparator = -1;
6184 $sum = 0;
6185 } else {
6186 $sum = $charWidth;
6188 } else {
6189 $sum += $charWidth;
6192 if ($chars[($length - 1)] == 10) {
6193 --$lines;
6195 $this->cell_padding = $prev_cell_padding;
6196 $this->lasth = $prev_lasth;
6197 return $lines;
6201 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6202 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6203 * @pre
6204 * // store current object
6205 * $pdf->startTransaction();
6206 * // store starting values
6207 * $start_y = $pdf->GetY();
6208 * $start_page = $pdf->getPage();
6209 * // call your printing functions with your parameters
6210 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6211 * $pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0);
6212 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6213 * // get the new Y
6214 * $end_y = $pdf->GetY();
6215 * $end_page = $pdf->getPage();
6216 * // calculate height
6217 * $height = 0;
6218 * if ($end_page == $start_page) {
6219 * $height = $end_y - $start_y;
6220 * } else {
6221 * for ($page=$start_page; $page <= $end_page; ++$page) {
6222 * $this->setPage($page);
6223 * if ($page == $start_page) {
6224 * // first page
6225 * $height = $this->h - $start_y - $this->bMargin;
6226 * } elseif ($page == $end_page) {
6227 * // last page
6228 * $height = $end_y - $this->tMargin;
6229 * } else {
6230 * $height = $this->h - $this->tMargin - $this->bMargin;
6234 * // restore previous object
6235 * $pdf = $pdf->rollbackTransaction();
6237 * @param $w (float) Width of cells. If 0, they extend up to the right margin of the page.
6238 * @param $txt (string) String for calculating his height
6239 * @param $reseth (boolean) if true reset the last cell height (default false).
6240 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width (default true).
6241 * @param $cellpadding (float) Internal cell padding, if empty uses default cell padding.
6242 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6243 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6244 * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6245 * @public
6247 public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
6248 // adjust internal padding
6249 $prev_cell_padding = $this->cell_padding;
6250 $prev_lasth = $this->lasth;
6251 if (is_array($cellpadding)) {
6252 $this->cell_padding = $cellpadding;
6254 $this->adjustCellPadding($border);
6255 $lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6256 $height = $this->getCellHeight(($lines * $this->FontSize), $autopadding);
6257 $this->cell_padding = $prev_cell_padding;
6258 $this->lasth = $prev_lasth;
6259 return $height;
6263 * This method prints text from the current position.<br />
6264 * @param $h (float) Line height
6265 * @param $txt (string) String to print
6266 * @param $link (mixed) URL or identifier returned by AddLink()
6267 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
6268 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
6269 * @param $ln (boolean) if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6270 * @param $stretch (int) font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
6271 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
6272 * @param $firstblock (boolean) if true the string is the starting of a line.
6273 * @param $maxh (float) maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature.
6274 * @param $wadj (float) first line width will be reduced by this amount (used in HTML mode).
6275 * @param $margin (array) margin array of the parent container
6276 * @return mixed Return the number of cells or the remaining string if $firstline = true.
6277 * @public
6278 * @since 1.5
6280 public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
6281 // check page for no-write regions and adapt page margins if necessary
6282 list($this->x, $this->y) = $this->checkPageRegions($h, $this->x, $this->y);
6283 if (strlen($txt) == 0) {
6284 // fix empty text
6285 $txt = ' ';
6287 if ($margin === '') {
6288 // set default margins
6289 $margin = $this->cell_margin;
6291 // remove carriage returns
6292 $s = str_replace("\r", '', $txt);
6293 // check if string contains arabic text
6294 if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $s)) {
6295 $arabic = true;
6296 } else {
6297 $arabic = false;
6299 // check if string contains RTL text
6300 if ($arabic OR ($this->tmprtl == 'R') OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $s)) {
6301 $rtlmode = true;
6302 } else {
6303 $rtlmode = false;
6305 // get a char width
6306 $chrwidth = $this->GetCharWidth(46); // dot character
6307 // get array of unicode values
6308 $chars = TCPDF_FONTS::UTF8StringToArray($s, $this->isunicode, $this->CurrentFont);
6309 // calculate maximum width for a single character on string
6310 $chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6311 array_walk($chrw, array($this, 'getRawCharWidth'));
6312 $maxchwidth = max($chrw);
6313 // get array of chars
6314 $uchars = TCPDF_FONTS::UTF8ArrayToUniArray($chars, $this->isunicode);
6315 // get the number of characters
6316 $nb = count($chars);
6317 // replacement for SHY character (minus symbol)
6318 $shy_replacement = 45;
6319 $shy_replacement_char = TCPDF_FONTS::unichr($shy_replacement, $this->isunicode);
6320 // widht for SHY replacement
6321 $shy_replacement_width = $this->GetCharWidth($shy_replacement);
6322 // max Y
6323 $maxy = $this->y + $maxh - $h - $this->cell_padding['T'] - $this->cell_padding['B'];
6324 // page width
6325 $pw = $w = $this->w - $this->lMargin - $this->rMargin;
6326 // calculate remaining line width ($w)
6327 if ($this->rtl) {
6328 $w = $this->x - $this->lMargin;
6329 } else {
6330 $w = $this->w - $this->rMargin - $this->x;
6332 // max column width
6333 $wmax = ($w - $wadj);
6334 if (!$firstline) {
6335 $wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
6337 if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6338 // the maximum width character do not fit on column
6339 return '';
6341 // minimum row height
6342 $row_height = max($h, $this->getCellHeight($this->FontSize));
6343 $start_page = $this->page;
6344 $i = 0; // character position
6345 $j = 0; // current starting position
6346 $sep = -1; // position of the last blank space
6347 $shy = false; // true if the last blank is a soft hypen (SHY)
6348 $l = 0; // current string length
6349 $nl = 0; //number of lines
6350 $linebreak = false;
6351 $pc = 0; // previous character
6352 // for each character
6353 while ($i < $nb) {
6354 if (($maxh > 0) AND ($this->y >= $maxy) ) {
6355 break;
6357 //Get the current character
6358 $c = $chars[$i];
6359 if ($c == 10) { // 10 = "\n" = new line
6360 //Explicit line break
6361 if ($align == 'J') {
6362 if ($this->rtl) {
6363 $talign = 'R';
6364 } else {
6365 $talign = 'L';
6367 } else {
6368 $talign = $align;
6370 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6371 if ($firstline) {
6372 $startx = $this->x;
6373 $tmparr = array_slice($chars, $j, ($i - $j));
6374 if ($rtlmode) {
6375 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6377 $linew = $this->GetArrStringWidth($tmparr);
6378 unset($tmparr);
6379 if ($this->rtl) {
6380 $this->endlinex = $startx - $linew;
6381 } else {
6382 $this->endlinex = $startx + $linew;
6384 $w = $linew;
6385 $tmpcellpadding = $this->cell_padding;
6386 if ($maxh == 0) {
6387 $this->SetCellPadding(0);
6390 if ($firstblock AND $this->isRTLTextDir()) {
6391 $tmpstr = $this->stringRightTrim($tmpstr);
6393 // Skip newlines at the begining of a page or column
6394 if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
6395 $this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6397 unset($tmpstr);
6398 if ($firstline) {
6399 $this->cell_padding = $tmpcellpadding;
6400 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6402 ++$nl;
6403 $j = $i + 1;
6404 $l = 0;
6405 $sep = -1;
6406 $shy = false;
6407 // account for margin changes
6408 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6409 $this->AcceptPageBreak();
6410 if ($this->rtl) {
6411 $this->x -= $margin['R'];
6412 } else {
6413 $this->x += $margin['L'];
6415 $this->lMargin += $margin['L'];
6416 $this->rMargin += $margin['R'];
6418 $w = $this->getRemainingWidth();
6419 $wmax = ($w - $this->cell_padding['L'] - $this->cell_padding['R']);
6420 } else {
6421 // 160 is the non-breaking space.
6422 // 173 is SHY (Soft Hypen).
6423 // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6424 // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6425 // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6426 if (($c != 160)
6427 AND (($c == 173)
6428 OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6429 OR (($c == 45)
6430 AND ($i < ($nb - 1))
6431 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($pc, $this->isunicode))
6432 AND @preg_match('/[\p{L}]/'.$this->re_space['m'], TCPDF_FONTS::unichr($chars[($i + 1)], $this->isunicode))
6436 // update last blank space position
6437 $sep = $i;
6438 // check if is a SHY
6439 if (($c == 173) OR ($c == 45)) {
6440 $shy = true;
6441 if ($pc == 45) {
6442 $tmp_shy_replacement_width = 0;
6443 $tmp_shy_replacement_char = '';
6444 } else {
6445 $tmp_shy_replacement_width = $shy_replacement_width;
6446 $tmp_shy_replacement_char = $shy_replacement_char;
6448 } else {
6449 $shy = false;
6452 // update string length
6453 if ($this->isUnicodeFont() AND ($arabic)) {
6454 // with bidirectional algorithm some chars may be changed affecting the line length
6455 // *** very slow ***
6456 $l = $this->GetArrStringWidth(TCPDF_FONTS::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl, $this->isunicode, $this->CurrentFont));
6457 } else {
6458 $l += $this->GetCharWidth($c);
6460 if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) ) {
6461 // we have reached the end of column
6462 if ($sep == -1) {
6463 // check if the line was already started
6464 if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $this->cell_padding['R'] - $margin['R'] - $chrwidth)))
6465 OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $this->cell_padding['L'] + $margin['L'] + $chrwidth)))) {
6466 // print a void cell and go to next line
6467 $this->Cell($w, $h, '', 0, 1);
6468 $linebreak = true;
6469 if ($firstline) {
6470 return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6472 } else {
6473 // truncate the word because do not fit on column
6474 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6475 if ($firstline) {
6476 $startx = $this->x;
6477 $tmparr = array_slice($chars, $j, ($i - $j));
6478 if ($rtlmode) {
6479 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6481 $linew = $this->GetArrStringWidth($tmparr);
6482 unset($tmparr);
6483 if ($this->rtl) {
6484 $this->endlinex = $startx - $linew;
6485 } else {
6486 $this->endlinex = $startx + $linew;
6488 $w = $linew;
6489 $tmpcellpadding = $this->cell_padding;
6490 if ($maxh == 0) {
6491 $this->SetCellPadding(0);
6494 if ($firstblock AND $this->isRTLTextDir()) {
6495 $tmpstr = $this->stringRightTrim($tmpstr);
6497 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6498 unset($tmpstr);
6499 if ($firstline) {
6500 $this->cell_padding = $tmpcellpadding;
6501 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6503 $j = $i;
6504 --$i;
6506 } else {
6507 // word wrapping
6508 if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6509 $endspace = 1;
6510 } else {
6511 $endspace = 0;
6513 // check the length of the next string
6514 $strrest = TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace));
6515 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $this->stringTrim($strrest));
6516 if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6517 // truncate the word because do not fit on a full page width
6518 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6519 if ($firstline) {
6520 $startx = $this->x;
6521 $tmparr = array_slice($chars, $j, ($i - $j));
6522 if ($rtlmode) {
6523 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6525 $linew = $this->GetArrStringWidth($tmparr);
6526 unset($tmparr);
6527 if ($this->rtl) {
6528 $this->endlinex = ($startx - $linew);
6529 } else {
6530 $this->endlinex = ($startx + $linew);
6532 $w = $linew;
6533 $tmpcellpadding = $this->cell_padding;
6534 if ($maxh == 0) {
6535 $this->SetCellPadding(0);
6538 if ($firstblock AND $this->isRTLTextDir()) {
6539 $tmpstr = $this->stringRightTrim($tmpstr);
6541 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6542 unset($tmpstr);
6543 if ($firstline) {
6544 $this->cell_padding = $tmpcellpadding;
6545 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6547 $j = $i;
6548 --$i;
6549 } else {
6550 // word wrapping
6551 if ($shy) {
6552 // add hypen (minus symbol) at the end of the line
6553 $shy_width = $tmp_shy_replacement_width;
6554 if ($this->rtl) {
6555 $shy_char_left = $tmp_shy_replacement_char;
6556 $shy_char_right = '';
6557 } else {
6558 $shy_char_left = '';
6559 $shy_char_right = $tmp_shy_replacement_char;
6561 } else {
6562 $shy_width = 0;
6563 $shy_char_left = '';
6564 $shy_char_right = '';
6566 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6567 if ($firstline) {
6568 $startx = $this->x;
6569 $tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6570 if ($rtlmode) {
6571 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6573 $linew = $this->GetArrStringWidth($tmparr);
6574 unset($tmparr);
6575 if ($this->rtl) {
6576 $this->endlinex = $startx - $linew - $shy_width;
6577 } else {
6578 $this->endlinex = $startx + $linew + $shy_width;
6580 $w = $linew;
6581 $tmpcellpadding = $this->cell_padding;
6582 if ($maxh == 0) {
6583 $this->SetCellPadding(0);
6586 // print the line
6587 if ($firstblock AND $this->isRTLTextDir()) {
6588 $tmpstr = $this->stringRightTrim($tmpstr);
6590 $this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6591 unset($tmpstr);
6592 if ($firstline) {
6593 if ($chars[$sep] == 45) {
6594 $endspace += 1;
6596 // return the remaining text
6597 $this->cell_padding = $tmpcellpadding;
6598 return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6600 $i = $sep;
6601 $sep = -1;
6602 $shy = false;
6603 $j = ($i + 1);
6606 // account for margin changes
6607 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6608 $this->AcceptPageBreak();
6609 if ($this->rtl) {
6610 $this->x -= $margin['R'];
6611 } else {
6612 $this->x += $margin['L'];
6614 $this->lMargin += $margin['L'];
6615 $this->rMargin += $margin['R'];
6617 $w = $this->getRemainingWidth();
6618 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6619 if ($linebreak) {
6620 $linebreak = false;
6621 } else {
6622 ++$nl;
6623 $l = 0;
6627 // save last character
6628 $pc = $c;
6629 ++$i;
6630 } // end while i < nb
6631 // print last substring (if any)
6632 if ($l > 0) {
6633 switch ($align) {
6634 case 'J':
6635 case 'C': {
6636 $w = $w;
6637 break;
6639 case 'L': {
6640 if ($this->rtl) {
6641 $w = $w;
6642 } else {
6643 $w = $l;
6645 break;
6647 case 'R': {
6648 if ($this->rtl) {
6649 $w = $l;
6650 } else {
6651 $w = $w;
6653 break;
6655 default: {
6656 $w = $l;
6657 break;
6660 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6661 if ($firstline) {
6662 $startx = $this->x;
6663 $tmparr = array_slice($chars, $j, ($nb - $j));
6664 if ($rtlmode) {
6665 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6667 $linew = $this->GetArrStringWidth($tmparr);
6668 unset($tmparr);
6669 if ($this->rtl) {
6670 $this->endlinex = $startx - $linew;
6671 } else {
6672 $this->endlinex = $startx + $linew;
6674 $w = $linew;
6675 $tmpcellpadding = $this->cell_padding;
6676 if ($maxh == 0) {
6677 $this->SetCellPadding(0);
6680 if ($firstblock AND $this->isRTLTextDir()) {
6681 $tmpstr = $this->stringRightTrim($tmpstr);
6683 $this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6684 unset($tmpstr);
6685 if ($firstline) {
6686 $this->cell_padding = $tmpcellpadding;
6687 return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6689 ++$nl;
6691 if ($firstline) {
6692 return '';
6694 return $nl;
6698 * Returns the remaining width between the current position and margins.
6699 * @return int Return the remaining width
6700 * @protected
6702 protected function getRemainingWidth() {
6703 list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6704 if ($this->rtl) {
6705 return ($this->x - $this->lMargin);
6706 } else {
6707 return ($this->w - $this->rMargin - $this->x);
6712 * Set the block dimensions accounting for page breaks and page/column fitting
6713 * @param $w (float) width
6714 * @param $h (float) height
6715 * @param $x (float) X coordinate
6716 * @param $y (float) Y coodiante
6717 * @param $fitonpage (boolean) if true the block is resized to not exceed page dimensions.
6718 * @return array($w, $h, $x, $y)
6719 * @protected
6720 * @since 5.5.009 (2010-07-05)
6722 protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6723 if ($w <= 0) {
6724 // set maximum width
6725 $w = ($this->w - $this->lMargin - $this->rMargin);
6726 if ($w <= 0) {
6727 $w = 1;
6730 if ($h <= 0) {
6731 // set maximum height
6732 $h = ($this->PageBreakTrigger - $this->tMargin);
6733 if ($h <= 0) {
6734 $h = 1;
6737 // resize the block to be vertically contained on a single page or single column
6738 if ($fitonpage OR $this->AutoPageBreak) {
6739 $ratio_wh = ($w / $h);
6740 if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
6741 $h = $this->PageBreakTrigger - $this->tMargin;
6742 $w = ($h * $ratio_wh);
6744 // resize the block to be horizontally contained on a single page or single column
6745 if ($fitonpage) {
6746 $maxw = ($this->w - $this->lMargin - $this->rMargin);
6747 if ($w > $maxw) {
6748 $w = $maxw;
6749 $h = ($w / $ratio_wh);
6753 // Check whether we need a new page or new column first as this does not fit
6754 $prev_x = $this->x;
6755 $prev_y = $this->y;
6756 if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6757 $y = $this->y;
6758 if ($this->rtl) {
6759 $x += ($prev_x - $this->x);
6760 } else {
6761 $x += ($this->x - $prev_x);
6763 $this->newline = true;
6765 // resize the block to be contained on the remaining available page or column space
6766 if ($fitonpage) {
6767 $ratio_wh = ($w / $h);
6768 if (($y + $h) > $this->PageBreakTrigger) {
6769 $h = $this->PageBreakTrigger - $y;
6770 $w = ($h * $ratio_wh);
6772 if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
6773 $w = $this->w - $this->rMargin - $x;
6774 $h = ($w / $ratio_wh);
6775 } elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
6776 $w = $x - $this->lMargin;
6777 $h = ($w / $ratio_wh);
6780 return array($w, $h, $x, $y);
6784 * Puts an image in the page.
6785 * The upper-left corner must be given.
6786 * The dimensions can be specified in different ways:<ul>
6787 * <li>explicit width and height (expressed in user unit)</li>
6788 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6789 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6790 * Supported formats are JPEG and PNG images whitout GD library and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;
6791 * The format can be specified explicitly or inferred from the file extension.<br />
6792 * It is possible to put a link on the image.<br />
6793 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6794 * @param $file (string) Name of the file containing the image or a '@' character followed by the image data string. To link an image without embedding it on the document, set an asterisk character before the URL (i.e.: '*http://www.example.com/image.jpg').
6795 * @param $x (float) Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6796 * @param $y (float) Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6797 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6798 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6799 * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
6800 * @param $link (mixed) URL or identifier returned by AddLink().
6801 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
6802 * @param $resize (mixed) If true resize (reduce) the image to fit $w and $h (requires GD or ImageMagick library); if false do not resize; if 2 force resize in all cases (upscaling and downscaling).
6803 * @param $dpi (int) dot-per-inch resolution used on resize
6804 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
6805 * @param $ismask (boolean) true if this image is a mask, false otherwise
6806 * @param $imgmask (mixed) image object returned by this function or false
6807 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
6808 * @param $fitbox (mixed) If not false scale image dimensions proportionally to fit within the ($w, $h) box. $fitbox can be true or a 2 characters string indicating the image alignment inside the box. The first character indicate the horizontal alignment (L = left, C = center, R = right) the second character indicate the vertical algnment (T = top, M = middle, B = bottom).
6809 * @param $hidden (boolean) If true do not display the image.
6810 * @param $fitonpage (boolean) If true the image is resized to not exceed page dimensions.
6811 * @param $alt (boolean) If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6812 * @param $altimgs (array) Array of alternate images IDs. Each alternative image must be an array with two values: an integer representing the image ID (the value returned by the Image method) and a boolean value to indicate if the image is the default for printing.
6813 * @return image information
6814 * @public
6815 * @since 1.1
6817 public function Image($file, $x='', $y='', $w=0, $h=0, $type='', $link='', $align='', $resize=false, $dpi=300, $palign='', $ismask=false, $imgmask=false, $border=0, $fitbox=false, $hidden=false, $fitonpage=false, $alt=false, $altimgs=array()) {
6818 if ($this->state != 2) {
6819 return;
6821 if ($x === '') {
6822 $x = $this->x;
6824 if ($y === '') {
6825 $y = $this->y;
6827 // check page for no-write regions and adapt page margins if necessary
6828 list($x, $y) = $this->checkPageRegions($h, $x, $y);
6829 $exurl = ''; // external streams
6830 $imsize = FALSE;
6831 // check if we are passing an image as file or string
6832 if ($file[0] === '@') {
6833 // image from string
6834 $imgdata = substr($file, 1);
6835 } else { // image file
6836 if ($file[0] === '*') {
6837 // image as external stream
6838 $file = substr($file, 1);
6839 $exurl = $file;
6841 // check if is a local file
6842 if (!@file_exists($file)) {
6843 // try to encode spaces on filename
6844 $tfile = str_replace(' ', '%20', $file);
6845 if (@file_exists($tfile)) {
6846 $file = $tfile;
6849 if (($imsize = @getimagesize($file)) === FALSE) {
6850 if (in_array($file, $this->imagekeys)) {
6851 // get existing image data
6852 $info = $this->getImageBuffer($file);
6853 $imsize = array($info['w'], $info['h']);
6854 } elseif (strpos($file, '__tcpdf_img') === FALSE) {
6855 $imgdata = TCPDF_STATIC::fileGetContents($file);
6859 if (!empty($imgdata)) {
6860 // copy image to cache
6861 $original_file = $file;
6862 $file = TCPDF_STATIC::getObjFilename('img');
6863 $fp = fopen($file, 'w');
6864 fwrite($fp, $imgdata);
6865 fclose($fp);
6866 unset($imgdata);
6867 $imsize = @getimagesize($file);
6868 if ($imsize === FALSE) {
6869 unlink($file);
6870 $file = $original_file;
6871 } else {
6872 $this->cached_files[] = $file;
6875 if ($imsize === FALSE) {
6876 if (($w > 0) AND ($h > 0)) {
6877 // get measures from specified data
6878 $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6879 $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
6880 $imsize = array($pw, $ph);
6881 } else {
6882 $this->Error('[Image] Unable to get the size of the image: '.$file);
6885 // file hash
6886 $filehash = md5($this->file_id.$file);
6887 // get original image width and height in pixels
6888 list($pixw, $pixh) = $imsize;
6889 // calculate image width and height on document
6890 if (($w <= 0) AND ($h <= 0)) {
6891 // convert image size to document unit
6892 $w = $this->pixelsToUnits($pixw);
6893 $h = $this->pixelsToUnits($pixh);
6894 } elseif ($w <= 0) {
6895 $w = $h * $pixw / $pixh;
6896 } elseif ($h <= 0) {
6897 $h = $w * $pixh / $pixw;
6898 } elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
6899 if (strlen($fitbox) !== 2) {
6900 // set default alignment
6901 $fitbox = '--';
6903 // scale image dimensions proportionally to fit within the ($w, $h) box
6904 if ((($w * $pixh) / ($h * $pixw)) < 1) {
6905 // store current height
6906 $oldh = $h;
6907 // calculate new height
6908 $h = $w * $pixh / $pixw;
6909 // height difference
6910 $hdiff = ($oldh - $h);
6911 // vertical alignment
6912 switch (strtoupper($fitbox[1])) {
6913 case 'T': {
6914 break;
6916 case 'M': {
6917 $y += ($hdiff / 2);
6918 break;
6920 case 'B': {
6921 $y += $hdiff;
6922 break;
6925 } else {
6926 // store current width
6927 $oldw = $w;
6928 // calculate new width
6929 $w = $h * $pixw / $pixh;
6930 // width difference
6931 $wdiff = ($oldw - $w);
6932 // horizontal alignment
6933 switch (strtoupper($fitbox[0])) {
6934 case 'L': {
6935 if ($this->rtl) {
6936 $x -= $wdiff;
6938 break;
6940 case 'C': {
6941 if ($this->rtl) {
6942 $x -= ($wdiff / 2);
6943 } else {
6944 $x += ($wdiff / 2);
6946 break;
6948 case 'R': {
6949 if (!$this->rtl) {
6950 $x += $wdiff;
6952 break;
6957 // fit the image on available space
6958 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
6959 // calculate new minimum dimensions in pixels
6960 $neww = round($w * $this->k * $dpi / $this->dpi);
6961 $newh = round($h * $this->k * $dpi / $this->dpi);
6962 // check if resize is necessary (resize is used only to reduce the image)
6963 $newsize = ($neww * $newh);
6964 $pixsize = ($pixw * $pixh);
6965 if (intval($resize) == 2) {
6966 $resize = true;
6967 } elseif ($newsize >= $pixsize) {
6968 $resize = false;
6970 // check if image has been already added on document
6971 $newimage = true;
6972 if (in_array($file, $this->imagekeys)) {
6973 $newimage = false;
6974 // get existing image data
6975 $info = $this->getImageBuffer($file);
6976 if (strpos($file, '__tcpdf_imgmask_') === FALSE) {
6977 // check if the newer image is larger
6978 $oldsize = ($info['w'] * $info['h']);
6979 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
6980 $newimage = true;
6983 } elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_imgmask_') === FALSE)) {
6984 // create temp image file (without alpha channel)
6985 $tempfile_plain = K_PATH_CACHE.'__tcpdf_imgmask_plain_'.$filehash;
6986 // create temp alpha file
6987 $tempfile_alpha = K_PATH_CACHE.'__tcpdf_imgmask_alpha_'.$filehash;
6988 // check for cached images
6989 if (in_array($tempfile_plain, $this->imagekeys)) {
6990 // get existing image data
6991 $info = $this->getImageBuffer($tempfile_plain);
6992 // check if the newer image is larger
6993 $oldsize = ($info['w'] * $info['h']);
6994 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
6995 $newimage = true;
6996 } else {
6997 $newimage = false;
6998 // embed mask image
6999 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7000 // embed image, masked with previously embedded mask
7001 return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7005 if ($newimage) {
7006 //First use of image, get info
7007 $type = strtolower($type);
7008 if ($type == '') {
7009 $type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7010 } elseif ($type == 'jpg') {
7011 $type = 'jpeg';
7013 $mqr = TCPDF_STATIC::get_mqr();
7014 TCPDF_STATIC::set_mqr(false);
7015 // Specific image handlers (defined on TCPDF_IMAGES CLASS)
7016 $mtd = '_parse'.$type;
7017 // GD image handler function
7018 $gdfunction = 'imagecreatefrom'.$type;
7019 $info = false;
7020 if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7021 // TCPDF image functions
7022 $info = TCPDF_IMAGES::$mtd($file);
7023 if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_imgmask_') === FALSE)
7024 AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7025 return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7028 if (($info === false) AND function_exists($gdfunction)) {
7029 try {
7030 // GD library
7031 $img = $gdfunction($file);
7032 if ($img !== false) {
7033 if ($resize) {
7034 $imgr = imagecreatetruecolor($neww, $newh);
7035 if (($type == 'gif') OR ($type == 'png')) {
7036 $imgr = TCPDF_IMAGES::setGDImageTransparency($imgr, $img);
7038 imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7039 $img = $imgr;
7041 if (($type == 'gif') OR ($type == 'png')) {
7042 $info = TCPDF_IMAGES::_toPNG($img);
7043 } else {
7044 $info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality);
7047 } catch(Exception $e) {
7048 $info = false;
7051 if (($info === false) AND extension_loaded('imagick')) {
7052 try {
7053 // ImageMagick library
7054 $img = new Imagick();
7055 if ($type == 'SVG') {
7056 // get SVG file content
7057 $svgimg = TCPDF_STATIC::fileGetContents($file);
7058 if ($svgimg !== FALSE) {
7059 // get width and height
7060 $regs = array();
7061 if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7062 $svgtag = $regs[1];
7063 $tmp = array();
7064 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7065 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7066 $owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit;
7067 $svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7068 } else {
7069 $ow = $w;
7071 $tmp = array();
7072 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7073 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
7074 $ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit;
7075 $svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7076 } else {
7077 $oh = $h;
7079 $tmp = array();
7080 if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7081 $vbw = ($ow * $this->imgscale * $this->k);
7082 $vbh = ($oh * $this->imgscale * $this->k);
7083 $vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7084 $svgtag = $vbox.$svgtag;
7086 $svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7088 $img->readImageBlob($svgimg);
7090 } else {
7091 $img->readImage($file);
7093 if ($resize) {
7094 $img->resizeImage($neww, $newh, 10, 1, false);
7096 $img->setCompressionQuality($this->jpeg_quality);
7097 $img->setImageFormat('jpeg');
7098 $tempname = TCPDF_STATIC::getObjFilename('img');
7099 $img->writeImage($tempname);
7100 $info = TCPDF_IMAGES::_parsejpeg($tempname);
7101 unlink($tempname);
7102 $img->destroy();
7103 } catch(Exception $e) {
7104 $info = false;
7107 if ($info === false) {
7108 // unable to process image
7109 return;
7111 TCPDF_STATIC::set_mqr($mqr);
7112 if ($ismask) {
7113 // force grayscale
7114 $info['cs'] = 'DeviceGray';
7116 if ($imgmask !== false) {
7117 $info['masked'] = $imgmask;
7119 if (!empty($exurl)) {
7120 $info['exurl'] = $exurl;
7122 // array of alternative images
7123 $info['altimgs'] = $altimgs;
7124 // add image to document
7125 $info['i'] = $this->setImageBuffer($file, $info);
7127 // set alignment
7128 $this->img_rb_y = $y + $h;
7129 // set alignment
7130 if ($this->rtl) {
7131 if ($palign == 'L') {
7132 $ximg = $this->lMargin;
7133 } elseif ($palign == 'C') {
7134 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7135 } elseif ($palign == 'R') {
7136 $ximg = $this->w - $this->rMargin - $w;
7137 } else {
7138 $ximg = $x - $w;
7140 $this->img_rb_x = $ximg;
7141 } else {
7142 if ($palign == 'L') {
7143 $ximg = $this->lMargin;
7144 } elseif ($palign == 'C') {
7145 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
7146 } elseif ($palign == 'R') {
7147 $ximg = $this->w - $this->rMargin - $w;
7148 } else {
7149 $ximg = $x;
7151 $this->img_rb_x = $ximg + $w;
7153 if ($ismask OR $hidden) {
7154 // image is not displayed
7155 return $info['i'];
7157 $xkimg = $ximg * $this->k;
7158 if (!$alt) {
7159 // only non-alternative immages will be set
7160 $this->_out(sprintf('q %F 0 0 %F %F %F cm /I%u Do Q', ($w * $this->k), ($h * $this->k), $xkimg, (($this->h - ($y + $h)) * $this->k), $info['i']));
7162 if (!empty($border)) {
7163 $bx = $this->x;
7164 $by = $this->y;
7165 $this->x = $ximg;
7166 if ($this->rtl) {
7167 $this->x += $w;
7169 $this->y = $y;
7170 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7171 $this->x = $bx;
7172 $this->y = $by;
7174 if ($link) {
7175 $this->Link($ximg, $y, $w, $h, $link, 0);
7177 // set pointer to align the next text/objects
7178 switch($align) {
7179 case 'T': {
7180 $this->y = $y;
7181 $this->x = $this->img_rb_x;
7182 break;
7184 case 'M': {
7185 $this->y = $y + round($h/2);
7186 $this->x = $this->img_rb_x;
7187 break;
7189 case 'B': {
7190 $this->y = $this->img_rb_y;
7191 $this->x = $this->img_rb_x;
7192 break;
7194 case 'N': {
7195 $this->SetY($this->img_rb_y);
7196 break;
7198 default:{
7199 break;
7202 $this->endlinex = $this->img_rb_x;
7203 if ($this->inxobj) {
7204 // we are inside an XObject template
7205 $this->xobjects[$this->xobjid]['images'][] = $info['i'];
7207 return $info['i'];
7211 * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7212 * @param $file (string) Name of the file containing the image.
7213 * @param $x (float) Abscissa of the upper-left corner.
7214 * @param $y (float) Ordinate of the upper-left corner.
7215 * @param $wpx (float) Original width of the image in pixels.
7216 * @param $hpx (float) original height of the image in pixels.
7217 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7218 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7219 * @param $type (string) Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
7220 * @param $link (mixed) URL or identifier returned by AddLink().
7221 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
7222 * @param $resize (boolean) If true resize (reduce) the image to fit $w and $h (requires GD library).
7223 * @param $dpi (int) dot-per-inch resolution used on resize
7224 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
7225 * @param $filehash (string) File hash used to build unique file names.
7226 * @author Nicola Asuni
7227 * @protected
7228 * @since 4.3.007 (2008-12-04)
7229 * @see Image()
7231 protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7232 // create temp images
7233 if (empty($filehash)) {
7234 $filehash = md5($this->file_id.$file);
7236 // create temp image file (without alpha channel)
7237 $tempfile_plain = K_PATH_CACHE.'__tcpdf_imgmask_plain_'.$filehash;
7238 // create temp alpha file
7239 $tempfile_alpha = K_PATH_CACHE.'__tcpdf_imgmask_alpha_'.$filehash;
7240 $parsed = false;
7241 $parse_error = '';
7242 // ImageMagick extension
7243 if (($parsed === false) AND extension_loaded('imagick')) {
7244 try {
7245 // ImageMagick library
7246 $img = new Imagick();
7247 $img->readImage($file);
7248 // clone image object
7249 $imga = TCPDF_STATIC::objclone($img);
7250 // extract alpha channel
7251 if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7252 $img->setImageAlphaChannel(Imagick::ALPHACHANNEL_EXTRACT);
7253 } else {
7254 $img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7255 $img->negateImage(true);
7257 $img->setImageFormat('png');
7258 $img->writeImage($tempfile_alpha);
7259 // remove alpha channel
7260 if (method_exists($imga, 'setImageMatte')) {
7261 $imga->setImageMatte(false);
7262 } else {
7263 $imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7265 $imga->setImageFormat('png');
7266 $imga->writeImage($tempfile_plain);
7267 $parsed = true;
7268 } catch (Exception $e) {
7269 // Imagemagick fails, try with GD
7270 $parse_error = 'Imagick library error: '.$e->getMessage();
7273 // GD extension
7274 if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7275 try {
7276 // generate images
7277 $img = imagecreatefrompng($file);
7278 $imgalpha = imagecreate($wpx, $hpx);
7279 // generate gray scale palette (0 -> 255)
7280 for ($c = 0; $c < 256; ++$c) {
7281 ImageColorAllocate($imgalpha, $c, $c, $c);
7283 // extract alpha channel
7284 for ($xpx = 0; $xpx < $wpx; ++$xpx) {
7285 for ($ypx = 0; $ypx < $hpx; ++$ypx) {
7286 $color = imagecolorat($img, $xpx, $ypx);
7287 // get and correct gamma color
7288 $alpha = $this->getGDgamma($img, $color);
7289 imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
7292 imagepng($imgalpha, $tempfile_alpha);
7293 imagedestroy($imgalpha);
7294 // extract image without alpha channel
7295 $imgplain = imagecreatetruecolor($wpx, $hpx);
7296 imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7297 imagepng($imgplain, $tempfile_plain);
7298 imagedestroy($imgplain);
7299 $parsed = true;
7300 } catch (Exception $e) {
7301 // GD fails
7302 $parse_error = 'GD library error: '.$e->getMessage();
7305 if ($parsed === false) {
7306 if (empty($parse_error)) {
7307 $this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7308 } else {
7309 $this->Error($parse_error);
7312 // embed mask image
7313 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7314 // embed image, masked with previously embedded mask
7315 $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7316 // remove temp files
7317 unlink($tempfile_alpha);
7318 unlink($tempfile_plain);
7322 * Get the GD-corrected PNG gamma value from alpha color
7323 * @param $img (int) GD image Resource ID.
7324 * @param $c (int) alpha color
7325 * @protected
7326 * @since 4.3.007 (2008-12-04)
7328 protected function getGDgamma($img, $c) {
7329 if (!isset($this->gdgammacache['#'.$c])) {
7330 $colors = imagecolorsforindex($img, $c);
7331 // GD alpha is only 7 bit (0 -> 127)
7332 $this->gdgammacache['#'.$c] = (((127 - $colors['alpha']) / 127) * 255);
7333 // correct gamma
7334 $this->gdgammacache['#'.$c] = (pow(($this->gdgammacache['#'.$c] / 255), 2.2) * 255);
7335 // store the latest values on cache to improve performances
7336 if (count($this->gdgammacache) > 8) {
7337 // remove one element from the cache array
7338 array_shift($this->gdgammacache);
7341 return $this->gdgammacache['#'.$c];
7345 * Performs a line break.
7346 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7347 * @param $h (float) The height of the break. By default, the value equals the height of the last printed cell.
7348 * @param $cell (boolean) if true add the current left (or right o for RTL) padding to the X coordinate
7349 * @public
7350 * @since 1.0
7351 * @see Cell()
7353 public function Ln($h='', $cell=false) {
7354 if (($this->num_columns > 1) AND ($this->y == $this->columns[$this->current_column]['y']) AND isset($this->columns[$this->current_column]['x']) AND ($this->x == $this->columns[$this->current_column]['x'])) {
7355 // revove vertical space from the top of the column
7356 return;
7358 if ($cell) {
7359 if ($this->rtl) {
7360 $cellpadding = $this->cell_padding['R'];
7361 } else {
7362 $cellpadding = $this->cell_padding['L'];
7364 } else {
7365 $cellpadding = 0;
7367 if ($this->rtl) {
7368 $this->x = $this->w - $this->rMargin - $cellpadding;
7369 } else {
7370 $this->x = $this->lMargin + $cellpadding;
7372 if (is_string($h)) {
7373 $this->y += $this->lasth;
7374 } else {
7375 $this->y += $h;
7377 $this->newline = true;
7381 * Returns the relative X value of current position.
7382 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7383 * @return float
7384 * @public
7385 * @since 1.2
7386 * @see SetX(), GetY(), SetY()
7388 public function GetX() {
7389 //Get x position
7390 if ($this->rtl) {
7391 return ($this->w - $this->x);
7392 } else {
7393 return $this->x;
7398 * Returns the absolute X value of current position.
7399 * @return float
7400 * @public
7401 * @since 1.2
7402 * @see SetX(), GetY(), SetY()
7404 public function GetAbsX() {
7405 return $this->x;
7409 * Returns the ordinate of the current position.
7410 * @return float
7411 * @public
7412 * @since 1.0
7413 * @see SetY(), GetX(), SetX()
7415 public function GetY() {
7416 return $this->y;
7420 * Defines the abscissa of the current position.
7421 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7422 * @param $x (float) The value of the abscissa in user units.
7423 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7424 * @public
7425 * @since 1.2
7426 * @see GetX(), GetY(), SetY(), SetXY()
7428 public function SetX($x, $rtloff=false) {
7429 $x = floatval($x);
7430 if (!$rtloff AND $this->rtl) {
7431 if ($x >= 0) {
7432 $this->x = $this->w - $x;
7433 } else {
7434 $this->x = abs($x);
7436 } else {
7437 if ($x >= 0) {
7438 $this->x = $x;
7439 } else {
7440 $this->x = $this->w + $x;
7443 if ($this->x < 0) {
7444 $this->x = 0;
7446 if ($this->x > $this->w) {
7447 $this->x = $this->w;
7452 * Moves the current abscissa back to the left margin and sets the ordinate.
7453 * If the passed value is negative, it is relative to the bottom of the page.
7454 * @param $y (float) The value of the ordinate in user units.
7455 * @param $resetx (bool) if true (default) reset the X position.
7456 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7457 * @public
7458 * @since 1.0
7459 * @see GetX(), GetY(), SetY(), SetXY()
7461 public function SetY($y, $resetx=true, $rtloff=false) {
7462 $y = floatval($y);
7463 if ($resetx) {
7464 //reset x
7465 if (!$rtloff AND $this->rtl) {
7466 $this->x = $this->w - $this->rMargin;
7467 } else {
7468 $this->x = $this->lMargin;
7471 if ($y >= 0) {
7472 $this->y = $y;
7473 } else {
7474 $this->y = $this->h + $y;
7476 if ($this->y < 0) {
7477 $this->y = 0;
7479 if ($this->y > $this->h) {
7480 $this->y = $this->h;
7485 * Defines the abscissa and ordinate of the current position.
7486 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7487 * @param $x (float) The value of the abscissa.
7488 * @param $y (float) The value of the ordinate.
7489 * @param $rtloff (boolean) if true always uses the page top-left corner as origin of axis.
7490 * @public
7491 * @since 1.2
7492 * @see SetX(), SetY()
7494 public function SetXY($x, $y, $rtloff=false) {
7495 $this->SetY($y, false, $rtloff);
7496 $this->SetX($x, $rtloff);
7500 * Set the absolute X coordinate of the current pointer.
7501 * @param $x (float) The value of the abscissa in user units.
7502 * @public
7503 * @since 5.9.186 (2012-09-13)
7504 * @see setAbsX(), setAbsY(), SetAbsXY()
7506 public function SetAbsX($x) {
7507 $this->x = floatval($x);
7511 * Set the absolute Y coordinate of the current pointer.
7512 * @param $y (float) (float) The value of the ordinate in user units.
7513 * @public
7514 * @since 5.9.186 (2012-09-13)
7515 * @see setAbsX(), setAbsY(), SetAbsXY()
7517 public function SetAbsY($y) {
7518 $this->y = floatval($y);
7522 * Set the absolute X and Y coordinates of the current pointer.
7523 * @param $x (float) The value of the abscissa in user units.
7524 * @param $y (float) (float) The value of the ordinate in user units.
7525 * @public
7526 * @since 5.9.186 (2012-09-13)
7527 * @see setAbsX(), setAbsY(), SetAbsXY()
7529 public function SetAbsXY($x, $y) {
7530 $this->SetAbsX($x);
7531 $this->SetAbsY($y);
7535 * Send the document to a given destination: string, local file or browser.
7536 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7537 * The method first calls Close() if necessary to terminate the document.
7538 * @param $name (string) The name of the file when saved. Note that special characters are removed and blanks characters are replaced with the underscore character.
7539 * @param $dest (string) Destination where to send the document. It can take one of the following values:<ul><li>I: send the file inline to the browser (default). The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.</li><li>D: send to the browser and force a file download with the name given by name.</li><li>F: save to a local server file with the name given by name.</li><li>S: return the document as a string (name is ignored).</li><li>FI: equivalent to F + I option</li><li>FD: equivalent to F + D option</li><li>E: return the document as base64 mime multi-part email attachment (RFC 2045)</li></ul>
7540 * @public
7541 * @since 1.0
7542 * @see Close()
7544 public function Output($name='doc.pdf', $dest='I') {
7545 //Output PDF to some destination
7546 //Finish document if necessary
7547 if ($this->state < 3) {
7548 $this->Close();
7550 //Normalize parameters
7551 if (is_bool($dest)) {
7552 $dest = $dest ? 'D' : 'F';
7554 $dest = strtoupper($dest);
7555 if ($dest[0] != 'F') {
7556 $name = preg_replace('/[\s]+/', '_', $name);
7557 $name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
7559 if ($this->sign) {
7560 // *** apply digital signature to the document ***
7561 // get the document content
7562 $pdfdoc = $this->getBuffer();
7563 // remove last newline
7564 $pdfdoc = substr($pdfdoc, 0, -1);
7565 // Remove the original buffer
7566 if (isset($this->diskcache) AND $this->diskcache) {
7567 // remove buffer file from cache
7568 unlink($this->buffer);
7570 unset($this->buffer);
7571 // remove filler space
7572 $byterange_string_len = strlen(TCPDF_STATIC::$byterange_string);
7573 // define the ByteRange
7574 $byte_range = array();
7575 $byte_range[0] = 0;
7576 $byte_range[1] = strpos($pdfdoc, TCPDF_STATIC::$byterange_string) + $byterange_string_len + 10;
7577 $byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
7578 $byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7579 $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7580 // replace the ByteRange
7581 $byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7582 $byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7583 $pdfdoc = str_replace(TCPDF_STATIC::$byterange_string, $byterange, $pdfdoc);
7584 // write the document to a temporary folder
7585 $tempdoc = TCPDF_STATIC::getObjFilename('doc');
7586 $f = fopen($tempdoc, 'wb');
7587 if (!$f) {
7588 $this->Error('Unable to create temporary file: '.$tempdoc);
7590 $pdfdoc_length = strlen($pdfdoc);
7591 fwrite($f, $pdfdoc, $pdfdoc_length);
7592 fclose($f);
7593 // get digital signature via openssl library
7594 $tempsign = TCPDF_STATIC::getObjFilename('sig');
7595 if (empty($this->signature_data['extracerts'])) {
7596 openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
7597 } else {
7598 openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED, $this->signature_data['extracerts']);
7600 unlink($tempdoc);
7601 // read signature
7602 $signature = file_get_contents($tempsign);
7603 unlink($tempsign);
7604 // extract signature
7605 $signature = substr($signature, $pdfdoc_length);
7606 $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
7607 $tmparr = explode("\n\n", $signature);
7608 $signature = $tmparr[1];
7609 unset($tmparr);
7610 // decode signature
7611 $signature = base64_decode(trim($signature));
7612 // convert signature to hex
7613 $signature = current(unpack('H*', $signature));
7614 $signature = str_pad($signature, $this->signature_max_length, '0');
7615 // disable disk caching
7616 $this->diskcache = false;
7617 // Add signature to the document
7618 $this->buffer = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7619 $this->bufferlen = strlen($this->buffer);
7621 switch($dest) {
7622 case 'I': {
7623 // Send PDF to the standard output
7624 if (ob_get_contents()) {
7625 $this->Error('Some data has already been output, can\'t send PDF file');
7627 if (php_sapi_name() != 'cli') {
7628 // send output to a browser
7629 header('Content-Type: application/pdf');
7630 if (headers_sent()) {
7631 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7633 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7634 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7635 header('Pragma: public');
7636 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7637 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7638 header('Content-Disposition: inline; filename="'.basename($name).'"');
7639 TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7640 } else {
7641 echo $this->getBuffer();
7643 break;
7645 case 'D': {
7646 // download PDF as file
7647 if (ob_get_contents()) {
7648 $this->Error('Some data has already been output, can\'t send PDF file');
7650 header('Content-Description: File Transfer');
7651 if (headers_sent()) {
7652 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7654 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7655 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7656 header('Pragma: public');
7657 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7658 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7659 // force download dialog
7660 if (strpos(php_sapi_name(), 'cgi') === false) {
7661 header('Content-Type: application/force-download');
7662 header('Content-Type: application/octet-stream', false);
7663 header('Content-Type: application/download', false);
7664 header('Content-Type: application/pdf', false);
7665 } else {
7666 header('Content-Type: application/pdf');
7668 // use the Content-Disposition header to supply a recommended filename
7669 header('Content-Disposition: attachment; filename="'.basename($name).'"');
7670 header('Content-Transfer-Encoding: binary');
7671 TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
7672 break;
7674 case 'F':
7675 case 'FI':
7676 case 'FD': {
7677 // save PDF to a local file
7678 if ($this->diskcache) {
7679 copy($this->buffer, $name);
7680 } else {
7681 $f = fopen($name, 'wb');
7682 if (!$f) {
7683 $this->Error('Unable to create output file: '.$name);
7685 fwrite($f, $this->getBuffer(), $this->bufferlen);
7686 fclose($f);
7688 if ($dest == 'FI') {
7689 // send headers to browser
7690 header('Content-Type: application/pdf');
7691 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7692 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7693 header('Pragma: public');
7694 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7695 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7696 header('Content-Disposition: inline; filename="'.basename($name).'"');
7697 TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7698 } elseif ($dest == 'FD') {
7699 // send headers to browser
7700 if (ob_get_contents()) {
7701 $this->Error('Some data has already been output, can\'t send PDF file');
7703 header('Content-Description: File Transfer');
7704 if (headers_sent()) {
7705 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7707 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7708 header('Pragma: public');
7709 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7710 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7711 // force download dialog
7712 if (strpos(php_sapi_name(), 'cgi') === false) {
7713 header('Content-Type: application/force-download');
7714 header('Content-Type: application/octet-stream', false);
7715 header('Content-Type: application/download', false);
7716 header('Content-Type: application/pdf', false);
7717 } else {
7718 header('Content-Type: application/pdf');
7720 // use the Content-Disposition header to supply a recommended filename
7721 header('Content-Disposition: attachment; filename="'.basename($name).'"');
7722 header('Content-Transfer-Encoding: binary');
7723 TCPDF_STATIC::sendOutputData(file_get_contents($name), filesize($name));
7725 break;
7727 case 'E': {
7728 // return PDF as base64 mime multi-part email attachment (RFC 2045)
7729 $retval = 'Content-Type: application/pdf;'."\r\n";
7730 $retval .= ' name="'.$name.'"'."\r\n";
7731 $retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7732 $retval .= 'Content-Disposition: attachment;'."\r\n";
7733 $retval .= ' filename="'.$name.'"'."\r\n\r\n";
7734 $retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7735 return $retval;
7737 case 'S': {
7738 // returns PDF as a string
7739 return $this->getBuffer();
7741 default: {
7742 $this->Error('Incorrect output destination: '.$dest);
7745 return '';
7749 * Unset all class variables except the following critical variables.
7750 * @param $destroyall (boolean) if true destroys all class variables, otherwise preserves critical variables.
7751 * @param $preserve_objcopy (boolean) if true preserves the objcopy variable
7752 * @public
7753 * @since 4.5.016 (2009-02-24)
7755 public function _destroy($destroyall=false, $preserve_objcopy=false) {
7756 if ($destroyall AND isset($this->diskcache) AND $this->diskcache AND (!$preserve_objcopy) AND (!TCPDF_STATIC::empty_string($this->buffer))) {
7757 // remove buffer file from cache
7758 unlink($this->buffer);
7760 if ($destroyall AND !empty($this->cached_files)) {
7761 // remove cached files
7762 foreach ($this->cached_files as $cachefile) {
7763 if (is_file($cachefile)) {
7764 unlink($cachefile);
7767 unset($this->cached_files);
7769 foreach (array_keys(get_object_vars($this)) as $val) {
7770 if ($destroyall OR (
7771 ($val != 'internal_encoding')
7772 AND ($val != 'state')
7773 AND ($val != 'bufferlen')
7774 AND ($val != 'buffer')
7775 AND ($val != 'diskcache')
7776 AND ($val != 'cached_files')
7777 AND ($val != 'sign')
7778 AND ($val != 'signature_data')
7779 AND ($val != 'signature_max_length')
7780 AND ($val != 'byterange_string')
7781 )) {
7782 if ((!$preserve_objcopy OR ($val != 'objcopy')) AND isset($this->$val)) {
7783 unset($this->$val);
7790 * Check for locale-related bug
7791 * @protected
7793 protected function _dochecks() {
7794 //Check for locale-related bug
7795 if (1.1 == 1) {
7796 $this->Error('Don\'t alter the locale before including class file');
7798 //Check for decimal separator
7799 if (sprintf('%.1F', 1.0) != '1.0') {
7800 setlocale(LC_NUMERIC, 'C');
7805 * Return an array containing variations for the basic page number alias.
7806 * @param $a (string) Base alias.
7807 * @return array of page number aliases
7808 * @protected
7810 protected function getInternalPageNumberAliases($a= '') {
7811 $alias = array();
7812 // build array of Unicode + ASCII variants (the order is important)
7813 $alias = array('u' => array(), 'a' => array());
7814 $u = '{'.$a.'}';
7815 $alias['u'][] = TCPDF_STATIC::_escape($u);
7816 if ($this->isunicode) {
7817 $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($u, $this->isunicode, $this->CurrentFont));
7818 $alias['u'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($u, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7819 $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::UTF8ToLatin1($a, $this->isunicode, $this->CurrentFont));
7820 $alias['a'][] = TCPDF_STATIC::_escape(TCPDF_FONTS::utf8StrRev($a, false, $this->tmprtl, $this->isunicode, $this->CurrentFont));
7822 $alias['a'][] = TCPDF_STATIC::_escape($a);
7823 return $alias;
7827 * Return an array containing all internal page aliases.
7828 * @return array of page number aliases
7829 * @protected
7831 protected function getAllInternalPageNumberAliases() {
7832 $basic_alias = array(TCPDF_STATIC::$alias_tot_pages, TCPDF_STATIC::$alias_num_page, TCPDF_STATIC::$alias_group_tot_pages, TCPDF_STATIC::$alias_group_num_page, TCPDF_STATIC::$alias_right_shift);
7833 $pnalias = array();
7834 foreach($basic_alias as $k => $a) {
7835 $pnalias[$k] = $this->getInternalPageNumberAliases($a);
7837 return $pnalias;
7841 * Replace right shift page number aliases with spaces to correct right alignment.
7842 * This works perfectly only when using monospaced fonts.
7843 * @param $page (string) Page content.
7844 * @param $aliases (array) Array of page aliases.
7845 * @param $diff (int) initial difference to add.
7846 * @return replaced page content.
7847 * @protected
7849 protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7850 foreach ($aliases as $type => $alias) {
7851 foreach ($alias as $a) {
7852 // find position of compensation factor
7853 $startnum = (strpos($a, ':') + 1);
7854 $a = substr($a, 0, $startnum);
7855 if (($pos = strpos($page, $a)) !== false) {
7856 // end of alias
7857 $endnum = strpos($page, '}', $pos);
7858 // string to be replaced
7859 $aa = substr($page, $pos, ($endnum - $pos + 1));
7860 // get compensation factor
7861 $ratio = substr($page, ($pos + $startnum), ($endnum - $pos - $startnum));
7862 $ratio = preg_replace('/[^0-9\.]/', '', $ratio);
7863 $ratio = floatval($ratio);
7864 if ($type == 'u') {
7865 $chrdiff = floor(($diff + 12) * $ratio);
7866 $shift = str_repeat(' ', $chrdiff);
7867 $shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
7868 } else {
7869 $chrdiff = floor(($diff + 11) * $ratio);
7870 $shift = str_repeat(' ', $chrdiff);
7872 $page = str_replace($aa, $shift, $page);
7876 return $page;
7880 * Set page boxes to be included on page descriptions.
7881 * @param $boxes (array) Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
7882 * @protected
7884 protected function setPageBoxTypes($boxes) {
7885 $this->page_boxes = array();
7886 foreach ($boxes as $box) {
7887 if (in_array($box, TCPDF_STATIC::$pageboxes)) {
7888 $this->page_boxes[] = $box;
7894 * Output pages (and replace page number aliases).
7895 * @protected
7897 protected function _putpages() {
7898 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
7899 // get internal aliases for page numbers
7900 $pnalias = $this->getAllInternalPageNumberAliases();
7901 $num_pages = $this->numpages;
7902 $ptpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $num_pages - 1));
7903 $ptpu = TCPDF_FONTS::UTF8ToUTF16BE($ptpa, false, $this->isunicode, $this->CurrentFont);
7904 $ptp_num_chars = $this->GetNumChars($ptpa);
7905 $pagegroupnum = 0;
7906 $groupnum = 0;
7907 $ptgu = 1;
7908 $ptga = 1;
7909 $ptg_num_chars = 1;
7910 for ($n = 1; $n <= $num_pages; ++$n) {
7911 // get current page
7912 $temppage = $this->getPageBuffer($n);
7913 $pagelen = strlen($temppage);
7914 // set replacements for total pages number
7915 $pnpa = TCPDF_STATIC::formatPageNumber(($this->starting_page_number + $n - 1));
7916 $pnpu = TCPDF_FONTS::UTF8ToUTF16BE($pnpa, false, $this->isunicode, $this->CurrentFont);
7917 $pnp_num_chars = $this->GetNumChars($pnpa);
7918 $pdiff = 0; // difference used for right shift alignment of page numbers
7919 $gdiff = 0; // difference used for right shift alignment of page group numbers
7920 if (!empty($this->pagegroups)) {
7921 if (isset($this->newpagegroup[$n])) {
7922 $pagegroupnum = 0;
7923 ++$groupnum;
7924 $ptga = TCPDF_STATIC::formatPageNumber($this->pagegroups[$groupnum]);
7925 $ptgu = TCPDF_FONTS::UTF8ToUTF16BE($ptga, false, $this->isunicode, $this->CurrentFont);
7926 $ptg_num_chars = $this->GetNumChars($ptga);
7928 ++$pagegroupnum;
7929 $pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
7930 $pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
7931 $png_num_chars = $this->GetNumChars($pnga);
7932 // replace page numbers
7933 $replace = array();
7934 $replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
7935 $replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
7936 $replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
7937 $replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
7938 list($temppage, $gdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $gdiff);
7940 // replace page numbers
7941 $replace = array();
7942 $replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
7943 $replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
7944 $replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
7945 $replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
7946 list($temppage, $pdiff) = TCPDF_STATIC::replacePageNumAliases($temppage, $replace, $pdiff);
7947 // replace right shift alias
7948 $temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
7949 // replace EPS marker
7950 $temppage = str_replace($this->epsmarker, '', $temppage);
7951 //Page
7952 $this->page_obj_id[$n] = $this->_newobj();
7953 $out = '<<';
7954 $out .= ' /Type /Page';
7955 $out .= ' /Parent 1 0 R';
7956 $out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp);
7957 $out .= ' /Resources 2 0 R';
7958 foreach ($this->page_boxes as $box) {
7959 $out .= ' /'.$box;
7960 $out .= sprintf(' [%F %F %F %F]', $this->pagedim[$n][$box]['llx'], $this->pagedim[$n][$box]['lly'], $this->pagedim[$n][$box]['urx'], $this->pagedim[$n][$box]['ury']);
7962 if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
7963 $out .= ' /BoxColorInfo <<';
7964 foreach ($this->page_boxes as $box) {
7965 if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
7966 $out .= ' /'.$box.' <<';
7967 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
7968 $color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
7969 $out .= ' /C [';
7970 $out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
7971 $out .= ' ]';
7973 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
7974 $out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
7976 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
7977 $out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
7979 if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
7980 $dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
7981 $out .= ' /D [';
7982 foreach ($dashes as $dash) {
7983 $out .= sprintf(' %F', ($dash * $this->k));
7985 $out .= ' ]';
7987 $out .= ' >>';
7990 $out .= ' >>';
7992 $out .= ' /Contents '.($this->n + 1).' 0 R';
7993 $out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
7994 if (!$this->pdfa_mode) {
7995 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
7997 if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
7998 // page transitions
7999 if (isset($this->pagedim[$n]['trans']['Dur'])) {
8000 $out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
8002 $out .= ' /Trans <<';
8003 $out .= ' /Type /Trans';
8004 if (isset($this->pagedim[$n]['trans']['S'])) {
8005 $out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
8007 if (isset($this->pagedim[$n]['trans']['D'])) {
8008 $out .= ' /D '.$this->pagedim[$n]['trans']['D'];
8010 if (isset($this->pagedim[$n]['trans']['Dm'])) {
8011 $out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
8013 if (isset($this->pagedim[$n]['trans']['M'])) {
8014 $out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
8016 if (isset($this->pagedim[$n]['trans']['Di'])) {
8017 $out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
8019 if (isset($this->pagedim[$n]['trans']['SS'])) {
8020 $out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
8022 if (isset($this->pagedim[$n]['trans']['B'])) {
8023 $out .= ' /B '.$this->pagedim[$n]['trans']['B'];
8025 $out .= ' >>';
8027 $out .= $this->_getannotsrefs($n);
8028 $out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8029 $out .= ' >>';
8030 $out .= "\n".'endobj';
8031 $this->_out($out);
8032 //Page content
8033 $p = ($this->compress) ? gzcompress($temppage) : $temppage;
8034 $this->_newobj();
8035 $p = $this->_getrawstream($p);
8036 $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8037 if ($this->diskcache) {
8038 // remove temporary files
8039 unlink($this->pages[$n]);
8042 //Pages root
8043 $out = $this->_getobj(1)."\n";
8044 $out .= '<< /Type /Pages /Kids [';
8045 foreach($this->page_obj_id as $page_obj) {
8046 $out .= ' '.$page_obj.' 0 R';
8048 $out .= ' ] /Count '.$num_pages.' >>';
8049 $out .= "\n".'endobj';
8050 $this->_out($out);
8054 * Output references to page annotations
8055 * @param $n (int) page number
8056 * @protected
8057 * @author Nicola Asuni
8058 * @since 4.7.000 (2008-08-29)
8059 * @deprecated
8061 protected function _putannotsrefs($n) {
8062 $this->_out($this->_getannotsrefs($n));
8066 * Get references to page annotations.
8067 * @param $n (int) page number
8068 * @return string
8069 * @protected
8070 * @author Nicola Asuni
8071 * @since 5.0.010 (2010-05-17)
8073 protected function _getannotsrefs($n) {
8074 if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
8075 return '';
8077 $out = ' /Annots [';
8078 if (isset($this->PageAnnots[$n])) {
8079 foreach ($this->PageAnnots[$n] as $key => $val) {
8080 if (!in_array($val['n'], $this->radio_groups)) {
8081 $out .= ' '.$val['n'].' 0 R';
8084 // add radiobutton groups
8085 if (isset($this->radiobutton_groups[$n])) {
8086 foreach ($this->radiobutton_groups[$n] as $key => $data) {
8087 if (isset($data['n'])) {
8088 $out .= ' '.$data['n'].' 0 R';
8093 if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
8094 // set reference for signature object
8095 $out .= ' '.$this->sig_obj_id.' 0 R';
8097 if (!empty($this->empty_signature_appearance)) {
8098 foreach ($this->empty_signature_appearance as $esa) {
8099 if ($esa['page'] == $n) {
8100 // set reference for empty signature objects
8101 $out .= ' '.$esa['objid'].' 0 R';
8105 $out .= ' ]';
8106 return $out;
8110 * Output annotations objects for all pages.
8111 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8112 * See section 12.5 of PDF 32000_2008 reference.
8113 * @protected
8114 * @author Nicola Asuni
8115 * @since 4.0.018 (2008-08-06)
8117 protected function _putannotsobjs() {
8118 // reset object counter
8119 for ($n=1; $n <= $this->numpages; ++$n) {
8120 if (isset($this->PageAnnots[$n])) {
8121 // set page annotations
8122 foreach ($this->PageAnnots[$n] as $key => $pl) {
8123 $annot_obj_id = $this->PageAnnots[$n][$key]['n'];
8124 // create annotation object for grouping radiobuttons
8125 if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
8126 $radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
8127 $annots = '<<';
8128 $annots .= ' /Type /Annot';
8129 $annots .= ' /Subtype /Widget';
8130 $annots .= ' /Rect [0 0 0 0]';
8131 if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8132 // read only
8133 $annots .= ' /F 68';
8134 $annots .= ' /Ff 49153';
8135 } else {
8136 $annots .= ' /F 4'; // default print for PDF/A
8137 $annots .= ' /Ff 49152';
8139 $annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8140 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8141 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8143 $annots .= ' /FT /Btn';
8144 $annots .= ' /Kids [';
8145 $defval = '';
8146 foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
8147 if (isset($data['kid'])) {
8148 $annots .= ' '.$data['kid'].' 0 R';
8149 if ($data['def'] !== 'Off') {
8150 $defval = $data['def'];
8154 $annots .= ' ]';
8155 if (!empty($defval)) {
8156 $annots .= ' /V /'.$defval;
8158 $annots .= ' >>';
8159 $this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8160 $this->form_obj_id[] = $radio_button_obj_id;
8161 // store object id to be used on Parent entry of Kids
8162 $this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
8164 $formfield = false;
8165 $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
8166 $a = $pl['x'] * $this->k;
8167 $b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
8168 $c = $pl['w'] * $this->k;
8169 $d = $pl['h'] * $this->k;
8170 $rect = sprintf('%F %F %F %F', $a, $b, $a+$c, $b+$d);
8171 // create new annotation object
8172 $annots = '<</Type /Annot';
8173 $annots .= ' /Subtype /'.$pl['opt']['subtype'];
8174 $annots .= ' /Rect ['.$rect.']';
8175 $ft = array('Btn', 'Tx', 'Ch', 'Sig');
8176 if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8177 $annots .= ' /FT /'.$pl['opt']['ft'];
8178 $formfield = true;
8180 $annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8181 $annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
8182 $annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8183 $annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp);
8184 if (isset($pl['opt']['f'])) {
8185 $fval = 0;
8186 if (is_array($pl['opt']['f'])) {
8187 foreach ($pl['opt']['f'] as $f) {
8188 switch (strtolower($f)) {
8189 case 'invisible': {
8190 $fval += 1 << 0;
8191 break;
8193 case 'hidden': {
8194 $fval += 1 << 1;
8195 break;
8197 case 'print': {
8198 $fval += 1 << 2;
8199 break;
8201 case 'nozoom': {
8202 $fval += 1 << 3;
8203 break;
8205 case 'norotate': {
8206 $fval += 1 << 4;
8207 break;
8209 case 'noview': {
8210 $fval += 1 << 5;
8211 break;
8213 case 'readonly': {
8214 $fval += 1 << 6;
8215 break;
8217 case 'locked': {
8218 $fval += 1 << 8;
8219 break;
8221 case 'togglenoview': {
8222 $fval += 1 << 9;
8223 break;
8225 case 'lockedcontents': {
8226 $fval += 1 << 10;
8227 break;
8229 default: {
8230 break;
8234 } else {
8235 $fval = intval($pl['opt']['f']);
8237 } else {
8238 $fval = 4;
8240 if ($this->pdfa_mode) {
8241 // force print flag for PDF/A mode
8242 $fval |= 4;
8244 $annots .= ' /F '.intval($fval);
8245 if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8246 $annots .= ' /AS /'.$pl['opt']['as'];
8248 if (isset($pl['opt']['ap'])) {
8249 // appearance stream
8250 $annots .= ' /AP <<';
8251 if (is_array($pl['opt']['ap'])) {
8252 foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8253 // $apmode can be: n = normal; r = rollover; d = down;
8254 $annots .= ' /'.strtoupper($apmode);
8255 if (is_array($apdef)) {
8256 $annots .= ' <<';
8257 foreach ($apdef as $apstate => $stream) {
8258 // reference to XObject that define the appearance for this mode-state
8259 $apsobjid = $this->_putAPXObject($c, $d, $stream);
8260 $annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8262 $annots .= ' >>';
8263 } else {
8264 // reference to XObject that define the appearance for this mode
8265 $apsobjid = $this->_putAPXObject($c, $d, $apdef);
8266 $annots .= ' '.$apsobjid.' 0 R';
8269 } else {
8270 $annots .= $pl['opt']['ap'];
8272 $annots .= ' >>';
8274 if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8275 $annots .= ' /BS <<';
8276 $annots .= ' /Type /Border';
8277 if (isset($pl['opt']['bs']['w'])) {
8278 $annots .= ' /W '.intval($pl['opt']['bs']['w']);
8280 $bstyles = array('S', 'D', 'B', 'I', 'U');
8281 if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8282 $annots .= ' /S /'.$pl['opt']['bs']['s'];
8284 if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8285 $annots .= ' /D [';
8286 foreach ($pl['opt']['bs']['d'] as $cord) {
8287 $annots .= ' '.intval($cord);
8289 $annots .= ']';
8291 $annots .= ' >>';
8292 } else {
8293 $annots .= ' /Border [';
8294 if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8295 $annots .= intval($pl['opt']['border'][0]).' ';
8296 $annots .= intval($pl['opt']['border'][1]).' ';
8297 $annots .= intval($pl['opt']['border'][2]);
8298 if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8299 $annots .= ' [';
8300 foreach ($pl['opt']['border'][3] as $dash) {
8301 $annots .= intval($dash).' ';
8303 $annots .= ']';
8305 } else {
8306 $annots .= '0 0 0';
8308 $annots .= ']';
8310 if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8311 $annots .= ' /BE <<';
8312 $bstyles = array('S', 'C');
8313 if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8314 $annots .= ' /S /'.$pl['opt']['bs']['s'];
8315 } else {
8316 $annots .= ' /S /S';
8318 if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8319 $annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8321 $annots .= '>>';
8323 if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8324 $annots .= ' /C '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['c']);
8326 //$annots .= ' /StructParent ';
8327 //$annots .= ' /OC ';
8328 $markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8329 if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8330 // this is a markup type
8331 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8332 $annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8334 //$annots .= ' /Popup ';
8335 if (isset($pl['opt']['ca'])) {
8336 $annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8338 if (isset($pl['opt']['rc'])) {
8339 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8341 $annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp);
8342 //$annots .= ' /IRT ';
8343 if (isset($pl['opt']['subj'])) {
8344 $annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8346 //$annots .= ' /RT ';
8347 //$annots .= ' /IT ';
8348 //$annots .= ' /ExData ';
8350 $lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8351 // Annotation types
8352 switch (strtolower($pl['opt']['subtype'])) {
8353 case 'text': {
8354 if (isset($pl['opt']['open'])) {
8355 $annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
8357 $iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8358 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8359 $annots .= ' /Name /'.$pl['opt']['name'];
8360 } else {
8361 $annots .= ' /Name /Note';
8363 $statemodels = array('Marked', 'Review');
8364 if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
8365 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8366 } else {
8367 $pl['opt']['statemodel'] = 'Marked';
8368 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8370 if ($pl['opt']['statemodel'] == 'Marked') {
8371 $states = array('Accepted', 'Unmarked');
8372 } else {
8373 $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8375 if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
8376 $annots .= ' /State /'.$pl['opt']['state'];
8377 } else {
8378 if ($pl['opt']['statemodel'] == 'Marked') {
8379 $annots .= ' /State /Unmarked';
8380 } else {
8381 $annots .= ' /State /None';
8384 break;
8386 case 'link': {
8387 if (is_string($pl['txt'])) {
8388 if ($pl['txt'][0] == '#') {
8389 // internal destination
8390 $annots .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($pl['txt'], 1));
8391 } elseif ($pl['txt'][0] == '%') {
8392 // embedded PDF file
8393 $filename = basename(substr($pl['txt'], 1));
8394 $annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
8395 } elseif ($pl['txt'][0] == '*') {
8396 // embedded generic file
8397 $filename = basename(substr($pl['txt'], 1));
8398 $jsa = 'var D=event.target.doc;var MyData=D.dataObjects;for (var i in MyData) if (MyData[i].path=="'.$filename.'") D.exportDataObject( { cName : MyData[i].name, nLaunch : 2});';
8399 $annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8400 } else {
8401 // external URI link
8402 $annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8404 } elseif (isset($this->links[$pl['txt']])) {
8405 // internal link ID
8406 $l = $this->links[$pl['txt']];
8407 if (isset($this->page_obj_id[($l['p'])])) {
8408 $annots .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l['p'])], ($this->pagedim[$l['p']]['h'] - ($l['y'] * $this->k)));
8411 $hmodes = array('N', 'I', 'O', 'P');
8412 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8413 $annots .= ' /H /'.$pl['opt']['h'];
8414 } else {
8415 $annots .= ' /H /I';
8417 //$annots .= ' /PA ';
8418 //$annots .= ' /Quadpoints ';
8419 break;
8421 case 'freetext': {
8422 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8423 $annots .= ' /DA ('.$pl['opt']['da'].')';
8425 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8426 $annots .= ' /Q '.intval($pl['opt']['q']);
8428 if (isset($pl['opt']['rc'])) {
8429 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8431 if (isset($pl['opt']['ds'])) {
8432 $annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8434 if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8435 $annots .= ' /CL [';
8436 foreach ($pl['opt']['cl'] as $cl) {
8437 $annots .= sprintf('%F ', $cl * $this->k);
8439 $annots .= ']';
8441 $tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8442 if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8443 $annots .= ' /IT /'.$pl['opt']['it'];
8445 if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8446 $l = $pl['opt']['rd'][0] * $this->k;
8447 $r = $pl['opt']['rd'][1] * $this->k;
8448 $t = $pl['opt']['rd'][2] * $this->k;
8449 $b = $pl['opt']['rd'][3] * $this->k;
8450 $annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8452 if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8453 $annots .= ' /LE /'.$pl['opt']['le'];
8455 break;
8457 case 'line': {
8458 break;
8460 case 'square': {
8461 break;
8463 case 'circle': {
8464 break;
8466 case 'polygon': {
8467 break;
8469 case 'polyline': {
8470 break;
8472 case 'highlight': {
8473 break;
8475 case 'underline': {
8476 break;
8478 case 'squiggly': {
8479 break;
8481 case 'strikeout': {
8482 break;
8484 case 'stamp': {
8485 break;
8487 case 'caret': {
8488 break;
8490 case 'ink': {
8491 break;
8493 case 'popup': {
8494 break;
8496 case 'fileattachment': {
8497 if ($this->pdfa_mode) {
8498 // embedded files are not allowed in PDF/A mode
8499 break;
8501 if (!isset($pl['opt']['fs'])) {
8502 break;
8504 $filename = basename($pl['opt']['fs']);
8505 if (isset($this->embeddedfiles[$filename]['f'])) {
8506 $annots .= ' /FS '.$this->embeddedfiles[$filename]['f'].' 0 R';
8507 $iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8508 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8509 $annots .= ' /Name /'.$pl['opt']['name'];
8510 } else {
8511 $annots .= ' /Name /PushPin';
8513 // index (zero-based) of the annotation in the Annots array of this page
8514 $this->embeddedfiles[$filename]['a'] = $key;
8516 break;
8518 case 'sound': {
8519 if (!isset($pl['opt']['fs'])) {
8520 break;
8522 $filename = basename($pl['opt']['fs']);
8523 if (isset($this->embeddedfiles[$filename]['f'])) {
8524 // ... TO BE COMPLETED ...
8525 // /R /C /B /E /CO /CP
8526 $annots .= ' /Sound '.$this->embeddedfiles[$filename]['f'].' 0 R';
8527 $iconsapp = array('Speaker', 'Mic');
8528 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8529 $annots .= ' /Name /'.$pl['opt']['name'];
8530 } else {
8531 $annots .= ' /Name /Speaker';
8534 break;
8536 case 'movie': {
8537 break;
8539 case 'widget': {
8540 $hmode = array('N', 'I', 'O', 'P', 'T');
8541 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8542 $annots .= ' /H /'.$pl['opt']['h'];
8544 if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8545 $annots .= ' /MK <<';
8546 if (isset($pl['opt']['mk']['r'])) {
8547 $annots .= ' /R '.$pl['opt']['mk']['r'];
8549 if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8550 $annots .= ' /BC '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bc']);
8552 if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8553 $annots .= ' /BG '.TCPDF_COLORS::getColorStringFromArray($pl['opt']['mk']['bg']);
8555 if (isset($pl['opt']['mk']['ca'])) {
8556 $annots .= ' /CA '.$pl['opt']['mk']['ca'];
8558 if (isset($pl['opt']['mk']['rc'])) {
8559 $annots .= ' /RC '.$pl['opt']['mk']['rc'];
8561 if (isset($pl['opt']['mk']['ac'])) {
8562 $annots .= ' /AC '.$pl['opt']['mk']['ac'];
8564 if (isset($pl['opt']['mk']['i'])) {
8565 $info = $this->getImageBuffer($pl['opt']['mk']['i']);
8566 if ($info !== false) {
8567 $annots .= ' /I '.$info['n'].' 0 R';
8570 if (isset($pl['opt']['mk']['ri'])) {
8571 $info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8572 if ($info !== false) {
8573 $annots .= ' /RI '.$info['n'].' 0 R';
8576 if (isset($pl['opt']['mk']['ix'])) {
8577 $info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8578 if ($info !== false) {
8579 $annots .= ' /IX '.$info['n'].' 0 R';
8582 if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8583 $annots .= ' /IF <<';
8584 $if_sw = array('A', 'B', 'S', 'N');
8585 if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8586 $annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8588 $if_s = array('A', 'P');
8589 if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8590 $annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8592 if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8593 $annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8595 if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8596 $annots .= ' /FB true';
8598 $annots .= '>>';
8600 if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8601 $annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8603 $annots .= '>>';
8604 } // end MK
8605 // --- Entries for field dictionaries ---
8606 if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8607 // set parent
8608 $annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
8610 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8611 $annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8613 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8614 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8616 if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8617 $annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8619 if (isset($pl['opt']['ff'])) {
8620 if (is_array($pl['opt']['ff'])) {
8621 // array of bit settings
8622 $flag = 0;
8623 foreach($pl['opt']['ff'] as $val) {
8624 $flag += 1 << ($val - 1);
8626 } else {
8627 $flag = intval($pl['opt']['ff']);
8629 $annots .= ' /Ff '.$flag;
8631 if (isset($pl['opt']['maxlen'])) {
8632 $annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8634 if (isset($pl['opt']['v'])) {
8635 $annots .= ' /V';
8636 if (is_array($pl['opt']['v'])) {
8637 foreach ($pl['opt']['v'] AS $optval) {
8638 if (is_float($optval)) {
8639 $optval = sprintf('%F', $optval);
8641 $annots .= ' '.$optval;
8643 } else {
8644 $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8647 if (isset($pl['opt']['dv'])) {
8648 $annots .= ' /DV';
8649 if (is_array($pl['opt']['dv'])) {
8650 foreach ($pl['opt']['dv'] AS $optval) {
8651 if (is_float($optval)) {
8652 $optval = sprintf('%F', $optval);
8654 $annots .= ' '.$optval;
8656 } else {
8657 $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8660 if (isset($pl['opt']['rv'])) {
8661 $annots .= ' /RV';
8662 if (is_array($pl['opt']['rv'])) {
8663 foreach ($pl['opt']['rv'] AS $optval) {
8664 if (is_float($optval)) {
8665 $optval = sprintf('%F', $optval);
8667 $annots .= ' '.$optval;
8669 } else {
8670 $annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8673 if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8674 $annots .= ' /A << '.$pl['opt']['a'].' >>';
8676 if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8677 $annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8679 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8680 $annots .= ' /DA ('.$pl['opt']['da'].')';
8682 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8683 $annots .= ' /Q '.intval($pl['opt']['q']);
8685 if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8686 $annots .= ' /Opt [';
8687 foreach($pl['opt']['opt'] AS $copt) {
8688 if (is_array($copt)) {
8689 $annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8690 } else {
8691 $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8694 $annots .= ']';
8696 if (isset($pl['opt']['ti'])) {
8697 $annots .= ' /TI '.intval($pl['opt']['ti']);
8699 if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8700 $annots .= ' /I [';
8701 foreach($pl['opt']['i'] AS $copt) {
8702 $annots .= intval($copt).' ';
8704 $annots .= ']';
8706 break;
8708 case 'screen': {
8709 break;
8711 case 'printermark': {
8712 break;
8714 case 'trapnet': {
8715 break;
8717 case 'watermark': {
8718 break;
8720 case '3d': {
8721 break;
8723 default: {
8724 break;
8727 $annots .= '>>';
8728 // create new annotation object
8729 $this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8730 if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
8731 // store reference of form object
8732 $this->form_obj_id[] = $annot_obj_id;
8736 } // end for each page
8740 * Put appearance streams XObject used to define annotation's appearance states.
8741 * @param $w (int) annotation width
8742 * @param $h (int) annotation height
8743 * @param $stream (string) appearance stream
8744 * @return int object ID
8745 * @protected
8746 * @since 4.8.001 (2009-09-09)
8748 protected function _putAPXObject($w=0, $h=0, $stream='') {
8749 $stream = trim($stream);
8750 $out = $this->_getobj()."\n";
8751 $this->xobjects['AX'.$this->n] = array('n' => $this->n);
8752 $out .= '<<';
8753 $out .= ' /Type /XObject';
8754 $out .= ' /Subtype /Form';
8755 $out .= ' /FormType 1';
8756 if ($this->compress) {
8757 $stream = gzcompress($stream);
8758 $out .= ' /Filter /FlateDecode';
8760 $rect = sprintf('%F %F', $w, $h);
8761 $out .= ' /BBox [0 0 '.$rect.']';
8762 $out .= ' /Matrix [1 0 0 1 0 0]';
8763 $out .= ' /Resources 2 0 R';
8764 $stream = $this->_getrawstream($stream);
8765 $out .= ' /Length '.strlen($stream);
8766 $out .= ' >>';
8767 $out .= ' stream'."\n".$stream."\n".'endstream';
8768 $out .= "\n".'endobj';
8769 $this->_out($out);
8770 return $this->n;
8774 * Output fonts.
8775 * @author Nicola Asuni
8776 * @protected
8778 protected function _putfonts() {
8779 $nf = $this->n;
8780 foreach ($this->diffs as $diff) {
8781 //Encodings
8782 $this->_newobj();
8783 $this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8785 $mqr = TCPDF_STATIC::get_mqr();
8786 TCPDF_STATIC::set_mqr(false);
8787 foreach ($this->FontFiles as $file => $info) {
8788 // search and get font file to embedd
8789 $fontfile = TCPDF_FONTS::getFontFullPath($file, $info['fontdir']);
8790 if (!TCPDF_STATIC::empty_string($fontfile)) {
8791 $font = file_get_contents($fontfile);
8792 $compressed = (substr($file, -2) == '.z');
8793 if ((!$compressed) AND (isset($info['length2']))) {
8794 $header = (ord($font[0]) == 128);
8795 if ($header) {
8796 // strip first binary header
8797 $font = substr($font, 6);
8799 if ($header AND (ord($font[$info['length1']]) == 128)) {
8800 // strip second binary header
8801 $font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
8803 } elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8804 if ($compressed) {
8805 // uncompress font
8806 $font = gzuncompress($font);
8808 // merge subset characters
8809 $subsetchars = array(); // used chars
8810 foreach ($info['fontkeys'] as $fontkey) {
8811 $fontinfo = $this->getFontBuffer($fontkey);
8812 $subsetchars += $fontinfo['subsetchars'];
8814 // rebuild a font subset
8815 $font = TCPDF_FONTS::_getTrueTypeFontSubset($font, $subsetchars);
8816 // calculate new font length
8817 $info['length1'] = strlen($font);
8818 if ($compressed) {
8819 // recompress font
8820 $font = gzcompress($font);
8823 $this->_newobj();
8824 $this->FontFiles[$file]['n'] = $this->n;
8825 $stream = $this->_getrawstream($font);
8826 $out = '<< /Length '.strlen($stream);
8827 if ($compressed) {
8828 $out .= ' /Filter /FlateDecode';
8830 $out .= ' /Length1 '.$info['length1'];
8831 if (isset($info['length2'])) {
8832 $out .= ' /Length2 '.$info['length2'].' /Length3 0';
8834 $out .= ' >>';
8835 $out .= ' stream'."\n".$stream."\n".'endstream';
8836 $out .= "\n".'endobj';
8837 $this->_out($out);
8840 TCPDF_STATIC::set_mqr($mqr);
8841 foreach ($this->fontkeys as $k) {
8842 //Font objects
8843 $font = $this->getFontBuffer($k);
8844 $type = $font['type'];
8845 $name = $font['name'];
8846 if ($type == 'core') {
8847 // standard core font
8848 $out = $this->_getobj($this->font_obj_ids[$k])."\n";
8849 $out .= '<</Type /Font';
8850 $out .= ' /Subtype /Type1';
8851 $out .= ' /BaseFont /'.$name;
8852 $out .= ' /Name /F'.$font['i'];
8853 if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
8854 $out .= ' /Encoding /WinAnsiEncoding';
8856 if ($k == 'helvetica') {
8857 // add default font for annotations
8858 $this->annotation_fonts[$k] = $font['i'];
8860 $out .= ' >>';
8861 $out .= "\n".'endobj';
8862 $this->_out($out);
8863 } elseif (($type == 'Type1') OR ($type == 'TrueType')) {
8864 // additional Type1 or TrueType font
8865 $out = $this->_getobj($this->font_obj_ids[$k])."\n";
8866 $out .= '<</Type /Font';
8867 $out .= ' /Subtype /'.$type;
8868 $out .= ' /BaseFont /'.$name;
8869 $out .= ' /Name /F'.$font['i'];
8870 $out .= ' /FirstChar 32 /LastChar 255';
8871 $out .= ' /Widths '.($this->n + 1).' 0 R';
8872 $out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
8873 if ($font['enc']) {
8874 if (isset($font['diff'])) {
8875 $out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
8876 } else {
8877 $out .= ' /Encoding /WinAnsiEncoding';
8880 $out .= ' >>';
8881 $out .= "\n".'endobj';
8882 $this->_out($out);
8883 // Widths
8884 $this->_newobj();
8885 $s = '[';
8886 for ($i = 32; $i < 256; ++$i) {
8887 if (isset($font['cw'][$i])) {
8888 $s .= $font['cw'][$i].' ';
8889 } else {
8890 $s .= $font['dw'].' ';
8893 $s .= ']';
8894 $s .= "\n".'endobj';
8895 $this->_out($s);
8896 //Descriptor
8897 $this->_newobj();
8898 $s = '<</Type /FontDescriptor /FontName /'.$name;
8899 foreach ($font['desc'] as $fdk => $fdv) {
8900 if (is_float($fdv)) {
8901 $fdv = sprintf('%F', $fdv);
8903 $s .= ' /'.$fdk.' '.$fdv.'';
8905 if (!TCPDF_STATIC::empty_string($font['file'])) {
8906 $s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
8908 $s .= '>>';
8909 $s .= "\n".'endobj';
8910 $this->_out($s);
8911 } else {
8912 // additional types
8913 $mtd = '_put'.strtolower($type);
8914 if (!method_exists($this, $mtd)) {
8915 $this->Error('Unsupported font type: '.$type);
8917 $this->$mtd($font);
8923 * Adds unicode fonts.<br>
8924 * Based on PDF Reference 1.3 (section 5)
8925 * @param $font (array) font data
8926 * @protected
8927 * @author Nicola Asuni
8928 * @since 1.52.0.TC005 (2005-01-05)
8930 protected function _puttruetypeunicode($font) {
8931 $fontname = '';
8932 if ($font['subset']) {
8933 // change name for font subsetting
8934 $subtag = sprintf('%06u', $font['i']);
8935 $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
8936 $fontname .= $subtag.'+';
8938 $fontname .= $font['name'];
8939 // Type0 Font
8940 // A composite font composed of other fonts, organized hierarchically
8941 $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
8942 $out .= '<< /Type /Font';
8943 $out .= ' /Subtype /Type0';
8944 $out .= ' /BaseFont /'.$fontname;
8945 $out .= ' /Name /F'.$font['i'];
8946 $out .= ' /Encoding /'.$font['enc'];
8947 $out .= ' /ToUnicode '.($this->n + 1).' 0 R';
8948 $out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
8949 $out .= ' >>';
8950 $out .= "\n".'endobj';
8951 $this->_out($out);
8952 // ToUnicode map for Identity-H
8953 $stream = TCPDF_FONT_DATA::$uni_identity_h;
8954 // ToUnicode Object
8955 $this->_newobj();
8956 $stream = ($this->compress) ? gzcompress($stream) : $stream;
8957 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
8958 $stream = $this->_getrawstream($stream);
8959 $this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
8960 // CIDFontType2
8961 // A CIDFont whose glyph descriptions are based on TrueType font technology
8962 $oid = $this->_newobj();
8963 $out = '<< /Type /Font';
8964 $out .= ' /Subtype /CIDFontType2';
8965 $out .= ' /BaseFont /'.$fontname;
8966 // A dictionary containing entries that define the character collection of the CIDFont.
8967 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
8968 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
8969 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
8970 $out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
8971 $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
8972 $out .= ' /DW '.$font['dw']; // default width
8973 $out .= "\n".TCPDF_FONTS::_putfontwidths($font, 0);
8974 if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
8975 $out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
8977 $out .= ' >>';
8978 $out .= "\n".'endobj';
8979 $this->_out($out);
8980 // Font descriptor
8981 // A font descriptor describing the CIDFont default metrics other than its glyph widths
8982 $this->_newobj();
8983 $out = '<< /Type /FontDescriptor';
8984 $out .= ' /FontName /'.$fontname;
8985 foreach ($font['desc'] as $key => $value) {
8986 if (is_float($value)) {
8987 $value = sprintf('%F', $value);
8989 $out .= ' /'.$key.' '.$value;
8991 $fontdir = false;
8992 if (!TCPDF_STATIC::empty_string($font['file'])) {
8993 // A stream containing a TrueType font
8994 $out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
8995 $fontdir = $this->FontFiles[$font['file']]['fontdir'];
8997 $out .= ' >>';
8998 $out .= "\n".'endobj';
8999 $this->_out($out);
9000 if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9001 $this->_newobj();
9002 // Embed CIDToGIDMap
9003 // A specification of the mapping from CIDs to glyph indices
9004 // search and get CTG font file to embedd
9005 $ctgfile = strtolower($font['ctg']);
9006 // search and get ctg font file to embedd
9007 $fontfile = TCPDF_FONTS::getFontFullPath($ctgfile, $fontdir);
9008 if (TCPDF_STATIC::empty_string($fontfile)) {
9009 $this->Error('Font file not found: '.$ctgfile);
9011 $stream = $this->_getrawstream(file_get_contents($fontfile));
9012 $out = '<< /Length '.strlen($stream).'';
9013 if (substr($fontfile, -2) == '.z') { // check file extension
9014 // Decompresses data encoded using the public-domain
9015 // zlib/deflate compression method, reproducing the
9016 // original text or binary data
9017 $out .= ' /Filter /FlateDecode';
9019 $out .= ' >>';
9020 $out .= ' stream'."\n".$stream."\n".'endstream';
9021 $out .= "\n".'endobj';
9022 $this->_out($out);
9027 * Output CID-0 fonts.
9028 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9029 * @param $font (array) font data
9030 * @protected
9031 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9032 * @since 3.2.000 (2008-06-23)
9034 protected function _putcidfont0($font) {
9035 $cidoffset = 0;
9036 if (!isset($font['cw'][1])) {
9037 $cidoffset = 31;
9039 if (isset($font['cidinfo']['uni2cid'])) {
9040 // convert unicode to cid.
9041 $uni2cid = $font['cidinfo']['uni2cid'];
9042 $cw = array();
9043 foreach ($font['cw'] as $uni => $width) {
9044 if (isset($uni2cid[$uni])) {
9045 $cw[($uni2cid[$uni] + $cidoffset)] = $width;
9046 } elseif ($uni < 256) {
9047 $cw[$uni] = $width;
9048 } // else unknown character
9050 $font = array_merge($font, array('cw' => $cw));
9052 $name = $font['name'];
9053 $enc = $font['enc'];
9054 if ($enc) {
9055 $longname = $name.'-'.$enc;
9056 } else {
9057 $longname = $name;
9059 $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
9060 $out .= '<</Type /Font';
9061 $out .= ' /Subtype /Type0';
9062 $out .= ' /BaseFont /'.$longname;
9063 $out .= ' /Name /F'.$font['i'];
9064 if ($enc) {
9065 $out .= ' /Encoding /'.$enc;
9067 $out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9068 $out .= ' >>';
9069 $out .= "\n".'endobj';
9070 $this->_out($out);
9071 $oid = $this->_newobj();
9072 $out = '<</Type /Font';
9073 $out .= ' /Subtype /CIDFontType0';
9074 $out .= ' /BaseFont /'.$name;
9075 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9076 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9077 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9078 $out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9079 $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
9080 $out .= ' /DW '.$font['dw'];
9081 $out .= "\n".TCPDF_FONTS::_putfontwidths($font, $cidoffset);
9082 $out .= ' >>';
9083 $out .= "\n".'endobj';
9084 $this->_out($out);
9085 $this->_newobj();
9086 $s = '<</Type /FontDescriptor /FontName /'.$name;
9087 foreach ($font['desc'] as $k => $v) {
9088 if ($k != 'Style') {
9089 if (is_float($v)) {
9090 $v = sprintf('%F', $v);
9092 $s .= ' /'.$k.' '.$v.'';
9095 $s .= '>>';
9096 $s .= "\n".'endobj';
9097 $this->_out($s);
9101 * Output images.
9102 * @protected
9104 protected function _putimages() {
9105 $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
9106 foreach ($this->imagekeys as $file) {
9107 $info = $this->getImageBuffer($file);
9108 // set object for alternate images array
9109 if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9110 $altoid = $this->_newobj();
9111 $out = '[';
9112 foreach ($info['altimgs'] as $altimage) {
9113 if (isset($this->xobjects['I'.$altimage[0]]['n'])) {
9114 $out .= ' << /Image '.$this->xobjects['I'.$altimage[0]]['n'].' 0 R';
9115 $out .= ' /DefaultForPrinting';
9116 if ($altimage[1] === true) {
9117 $out .= ' true';
9118 } else {
9119 $out .= ' false';
9121 $out .= ' >>';
9124 $out .= ' ]';
9125 $out .= "\n".'endobj';
9126 $this->_out($out);
9128 // set image object
9129 $oid = $this->_newobj();
9130 $this->xobjects['I'.$info['i']] = array('n' => $oid);
9131 $this->setImageSubBuffer($file, 'n', $this->n);
9132 $out = '<</Type /XObject';
9133 $out .= ' /Subtype /Image';
9134 $out .= ' /Width '.$info['w'];
9135 $out .= ' /Height '.$info['h'];
9136 if (array_key_exists('masked', $info)) {
9137 $out .= ' /SMask '.($this->n - 1).' 0 R';
9139 // set color space
9140 $icc = false;
9141 if (isset($info['icc']) AND ($info['icc'] !== false)) {
9142 // ICC Colour Space
9143 $icc = true;
9144 $out .= ' /ColorSpace [/ICCBased '.($this->n + 1).' 0 R]';
9145 } elseif ($info['cs'] == 'Indexed') {
9146 // Indexed Colour Space
9147 $out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
9148 } else {
9149 // Device Colour Space
9150 $out .= ' /ColorSpace /'.$info['cs'];
9152 if ($info['cs'] == 'DeviceCMYK') {
9153 $out .= ' /Decode [1 0 1 0 1 0 1 0]';
9155 $out .= ' /BitsPerComponent '.$info['bpc'];
9156 if (isset($altoid) AND ($altoid > 0)) {
9157 // reference to alternate images dictionary
9158 $out .= ' /Alternates '.$altoid.' 0 R';
9160 if (isset($info['exurl']) AND !empty($info['exurl'])) {
9161 // external stream
9162 $out .= ' /Length 0';
9163 $out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9164 if (isset($info['f'])) {
9165 $out .= ' /FFilter /'.$info['f'];
9167 $out .= ' >>';
9168 $out .= ' stream'."\n".'endstream';
9169 } else {
9170 if (isset($info['f'])) {
9171 $out .= ' /Filter /'.$info['f'];
9173 if (isset($info['parms'])) {
9174 $out .= ' '.$info['parms'];
9176 if (isset($info['trns']) AND is_array($info['trns'])) {
9177 $trns = '';
9178 $count_info = count($info['trns']);
9179 for ($i=0; $i < $count_info; ++$i) {
9180 $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9182 $out .= ' /Mask ['.$trns.']';
9184 $stream = $this->_getrawstream($info['data']);
9185 $out .= ' /Length '.strlen($stream).' >>';
9186 $out .= ' stream'."\n".$stream."\n".'endstream';
9188 $out .= "\n".'endobj';
9189 $this->_out($out);
9190 if ($icc) {
9191 // ICC colour profile
9192 $this->_newobj();
9193 $icc = ($this->compress) ? gzcompress($info['icc']) : $info['icc'];
9194 $icc = $this->_getrawstream($icc);
9195 $this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9196 } elseif ($info['cs'] == 'Indexed') {
9197 // colour palette
9198 $this->_newobj();
9199 $pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
9200 $pal = $this->_getrawstream($pal);
9201 $this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9207 * Output Form XObjects Templates.
9208 * @author Nicola Asuni
9209 * @since 5.8.017 (2010-08-24)
9210 * @protected
9211 * @see startTemplate(), endTemplate(), printTemplate()
9213 protected function _putxobjects() {
9214 foreach ($this->xobjects as $key => $data) {
9215 if (isset($data['outdata'])) {
9216 $stream = str_replace($this->epsmarker, '', trim($data['outdata']));
9217 $out = $this->_getobj($data['n'])."\n";
9218 $out .= '<<';
9219 $out .= ' /Type /XObject';
9220 $out .= ' /Subtype /Form';
9221 $out .= ' /FormType 1';
9222 if ($this->compress) {
9223 $stream = gzcompress($stream);
9224 $out .= ' /Filter /FlateDecode';
9226 $out .= sprintf(' /BBox [%F %F %F %F]', ($data['x'] * $this->k), (-$data['y'] * $this->k), (($data['w'] + $data['x']) * $this->k), (($data['h'] - $data['y']) * $this->k));
9227 $out .= ' /Matrix [1 0 0 1 0 0]';
9228 $out .= ' /Resources <<';
9229 $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9230 if (!$this->pdfa_mode) {
9231 // transparency
9232 if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9233 $out .= ' /ExtGState <<';
9234 foreach ($data['extgstates'] as $k => $extgstate) {
9235 if (isset($this->extgstates[$k]['name'])) {
9236 $out .= ' /'.$this->extgstates[$k]['name'];
9237 } else {
9238 $out .= ' /GS'.$k;
9240 $out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9242 $out .= ' >>';
9244 if (isset($data['gradients']) AND !empty($data['gradients'])) {
9245 $gp = '';
9246 $gs = '';
9247 foreach ($data['gradients'] as $id => $grad) {
9248 // gradient patterns
9249 $gp .= ' /p'.$id.' '.$this->gradients[$id]['pattern'].' 0 R';
9250 // gradient shadings
9251 $gs .= ' /Sh'.$id.' '.$this->gradients[$id]['id'].' 0 R';
9253 $out .= ' /Pattern <<'.$gp.' >>';
9254 $out .= ' /Shading <<'.$gs.' >>';
9257 // spot colors
9258 if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9259 $out .= ' /ColorSpace <<';
9260 foreach ($data['spot_colors'] as $name => $color) {
9261 $out .= ' /CS'.$color['i'].' '.$this->spot_colors[$name]['n'].' 0 R';
9263 $out .= ' >>';
9265 // fonts
9266 if (!empty($data['fonts'])) {
9267 $out .= ' /Font <<';
9268 foreach ($data['fonts'] as $fontkey => $fontid) {
9269 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9271 $out .= ' >>';
9273 // images or nested xobjects
9274 if (!empty($data['images']) OR !empty($data['xobjects'])) {
9275 $out .= ' /XObject <<';
9276 foreach ($data['images'] as $imgid) {
9277 $out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
9279 foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9280 $out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9282 $out .= ' >>';
9284 $out .= ' >>'; //end resources
9285 if (isset($data['group']) AND ($data['group'] !== false)) {
9286 // set transparency group
9287 $out .= ' /Group << /Type /Group /S /Transparency';
9288 if (is_array($data['group'])) {
9289 if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9290 $out .= ' /CS /'.$data['group']['CS'];
9292 if (isset($data['group']['I'])) {
9293 $out .= ' /I /'.($data['group']['I']===true?'true':'false');
9295 if (isset($data['group']['K'])) {
9296 $out .= ' /K /'.($data['group']['K']===true?'true':'false');
9299 $out .= ' >>';
9301 $stream = $this->_getrawstream($stream, $data['n']);
9302 $out .= ' /Length '.strlen($stream);
9303 $out .= ' >>';
9304 $out .= ' stream'."\n".$stream."\n".'endstream';
9305 $out .= "\n".'endobj';
9306 $this->_out($out);
9312 * Output Spot Colors Resources.
9313 * @protected
9314 * @since 4.0.024 (2008-09-12)
9316 protected function _putspotcolors() {
9317 foreach ($this->spot_colors as $name => $color) {
9318 $this->_newobj();
9319 $this->spot_colors[$name]['n'] = $this->n;
9320 $out = '[/Separation /'.str_replace(' ', '#20', $name);
9321 $out .= ' /DeviceCMYK <<';
9322 $out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9323 $out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9324 $out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9325 $out .= "\n".'endobj';
9326 $this->_out($out);
9331 * Return XObjects Dictionary.
9332 * @return string XObjects dictionary
9333 * @protected
9334 * @since 5.8.014 (2010-08-23)
9336 protected function _getxobjectdict() {
9337 $out = '';
9338 foreach ($this->xobjects as $id => $objid) {
9339 $out .= ' /'.$id.' '.$objid['n'].' 0 R';
9341 return $out;
9345 * Output Resources Dictionary.
9346 * @protected
9348 protected function _putresourcedict() {
9349 $out = $this->_getobj(2)."\n";
9350 $out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9351 $out .= ' /Font <<';
9352 foreach ($this->fontkeys as $fontkey) {
9353 $font = $this->getFontBuffer($fontkey);
9354 $out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9356 $out .= ' >>';
9357 $out .= ' /XObject <<';
9358 $out .= $this->_getxobjectdict();
9359 $out .= ' >>';
9360 // layers
9361 if (!empty($this->pdflayers)) {
9362 $out .= ' /Properties <<';
9363 foreach ($this->pdflayers as $layer) {
9364 $out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9366 $out .= ' >>';
9368 if (!$this->pdfa_mode) {
9369 // transparency
9370 if (isset($this->extgstates) AND !empty($this->extgstates)) {
9371 $out .= ' /ExtGState <<';
9372 foreach ($this->extgstates as $k => $extgstate) {
9373 if (isset($extgstate['name'])) {
9374 $out .= ' /'.$extgstate['name'];
9375 } else {
9376 $out .= ' /GS'.$k;
9378 $out .= ' '.$extgstate['n'].' 0 R';
9380 $out .= ' >>';
9382 if (isset($this->gradients) AND !empty($this->gradients)) {
9383 $gp = '';
9384 $gs = '';
9385 foreach ($this->gradients as $id => $grad) {
9386 // gradient patterns
9387 $gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9388 // gradient shadings
9389 $gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9391 $out .= ' /Pattern <<'.$gp.' >>';
9392 $out .= ' /Shading <<'.$gs.' >>';
9395 // spot colors
9396 if (isset($this->spot_colors) AND !empty($this->spot_colors)) {
9397 $out .= ' /ColorSpace <<';
9398 foreach ($this->spot_colors as $color) {
9399 $out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9401 $out .= ' >>';
9403 $out .= ' >>';
9404 $out .= "\n".'endobj';
9405 $this->_out($out);
9409 * Output Resources.
9410 * @protected
9412 protected function _putresources() {
9413 $this->_putextgstates();
9414 $this->_putocg();
9415 $this->_putfonts();
9416 $this->_putimages();
9417 $this->_putspotcolors();
9418 $this->_putshaders();
9419 $this->_putxobjects();
9420 $this->_putresourcedict();
9421 $this->_putdests();
9422 $this->_putEmbeddedFiles();
9423 $this->_putannotsobjs();
9424 $this->_putjavascript();
9425 $this->_putbookmarks();
9426 $this->_putencryption();
9430 * Adds some Metadata information (Document Information Dictionary)
9431 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9432 * @return int object id
9433 * @protected
9435 protected function _putinfo() {
9436 $oid = $this->_newobj();
9437 $out = '<<';
9438 // store current isunicode value
9439 $prev_isunicode = $this->isunicode;
9440 if ($this->docinfounicode) {
9441 $this->isunicode = true;
9443 if (!TCPDF_STATIC::empty_string($this->title)) {
9444 // The document's title.
9445 $out .= ' /Title '.$this->_textstring($this->title, $oid);
9447 if (!TCPDF_STATIC::empty_string($this->author)) {
9448 // The name of the person who created the document.
9449 $out .= ' /Author '.$this->_textstring($this->author, $oid);
9451 if (!TCPDF_STATIC::empty_string($this->subject)) {
9452 // The subject of the document.
9453 $out .= ' /Subject '.$this->_textstring($this->subject, $oid);
9455 if (!TCPDF_STATIC::empty_string($this->keywords)) {
9456 // Keywords associated with the document.
9457 $out .= ' /Keywords '.$this->_textstring($this->keywords, $oid);
9459 if (!TCPDF_STATIC::empty_string($this->creator)) {
9460 // If the document was converted to PDF from another format, the name of the conforming product that created the original document from which it was converted.
9461 $out .= ' /Creator '.$this->_textstring($this->creator, $oid);
9463 // restore previous isunicode value
9464 $this->isunicode = $prev_isunicode;
9465 // default producer
9466 $out .= ' /Producer '.$this->_textstring(TCPDF_STATIC::getTCPDFProducer(), $oid);
9467 // The date and time the document was created, in human-readable form
9468 $out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp);
9469 // The date and time the document was most recently modified, in human-readable form
9470 $out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp);
9471 // A name object indicating whether the document has been modified to include trapping information
9472 $out .= ' /Trapped /False';
9473 $out .= ' >>';
9474 $out .= "\n".'endobj';
9475 $this->_out($out);
9476 return $oid;
9480 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9481 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9482 * @param $xmp (string) Custom XMP data.
9483 * @since 5.9.128 (2011-10-06)
9484 * @public
9486 public function setExtraXMP($xmp) {
9487 $this->custom_xmp = $xmp;
9491 * Put XMP data object and return ID.
9492 * @return (int) The object ID.
9493 * @since 5.9.121 (2011-09-28)
9494 * @protected
9496 protected function _putXMP() {
9497 $oid = $this->_newobj();
9498 // store current isunicode value
9499 $prev_isunicode = $this->isunicode;
9500 $this->isunicode = true;
9501 $prev_encrypted = $this->encrypted;
9502 $this->encrypted = false;
9503 // set XMP data
9504 $xmp = '<?xpacket begin="'.TCPDF_FONTS::unichr(0xfeff, $this->isunicode).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9505 $xmp .= '<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 4.2.1-c043 52.372728, 2009/01/18-15:08:04">'."\n";
9506 $xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9507 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9508 $xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9509 $xmp .= "\t\t\t".'<dc:title>'."\n";
9510 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9511 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->title).'</rdf:li>'."\n";
9512 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9513 $xmp .= "\t\t\t".'</dc:title>'."\n";
9514 $xmp .= "\t\t\t".'<dc:creator>'."\n";
9515 $xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9516 $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->author).'</rdf:li>'."\n";
9517 $xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9518 $xmp .= "\t\t\t".'</dc:creator>'."\n";
9519 $xmp .= "\t\t\t".'<dc:description>'."\n";
9520 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9521 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC::_escapeXML($this->subject).'</rdf:li>'."\n";
9522 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9523 $xmp .= "\t\t\t".'</dc:description>'."\n";
9524 $xmp .= "\t\t\t".'<dc:subject>'."\n";
9525 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9526 $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC::_escapeXML($this->keywords).'</rdf:li>'."\n";
9527 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9528 $xmp .= "\t\t\t".'</dc:subject>'."\n";
9529 $xmp .= "\t\t".'</rdf:Description>'."\n";
9530 // convert doc creation date format
9531 $dcdate = TCPDF_STATIC::getFormattedDate($this->doc_creation_timestamp);
9532 $doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9533 $doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9534 $doccreationdate .= '+'.substr($dcdate, 15, 2).':'.substr($dcdate, 18, 2);
9535 $doccreationdate = TCPDF_STATIC::_escapeXML($doccreationdate);
9536 // convert doc modification date format
9537 $dmdate = TCPDF_STATIC::getFormattedDate($this->doc_modification_timestamp);
9538 $docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9539 $docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9540 $docmoddate .= '+'.substr($dmdate, 15, 2).':'.substr($dmdate, 18, 2);
9541 $docmoddate = TCPDF_STATIC::_escapeXML($docmoddate);
9542 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9543 $xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9544 $xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator.'</xmp:CreatorTool>'."\n";
9545 $xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9546 $xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9547 $xmp .= "\t\t".'</rdf:Description>'."\n";
9548 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9549 $xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC::_escapeXML($this->keywords).'</pdf:Keywords>'."\n";
9550 $xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC::_escapeXML(TCPDF_STATIC::getTCPDFProducer()).'</pdf:Producer>'."\n";
9551 $xmp .= "\t\t".'</rdf:Description>'."\n";
9552 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9553 $uuid = 'uuid:'.substr($this->file_id, 0, 8).'-'.substr($this->file_id, 8, 4).'-'.substr($this->file_id, 12, 4).'-'.substr($this->file_id, 16, 4).'-'.substr($this->file_id, 20, 12);
9554 $xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9555 $xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9556 $xmp .= "\t\t".'</rdf:Description>'."\n";
9557 if ($this->pdfa_mode) {
9558 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9559 $xmp .= "\t\t\t".'<pdfaid:part>1</pdfaid:part>'."\n";
9560 $xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9561 $xmp .= "\t\t".'</rdf:Description>'."\n";
9563 // XMP extension schemas
9564 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/" xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#" xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">'."\n";
9565 $xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9566 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9567 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9568 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9569 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9570 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9571 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9572 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9573 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9574 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9575 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9576 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9577 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9578 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9579 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9580 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9581 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9582 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9583 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9584 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9585 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9586 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9587 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9588 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9589 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9590 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9591 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9592 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9593 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9594 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9595 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9596 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9597 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9598 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9599 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9600 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9601 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9602 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9603 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9604 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9605 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9606 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9607 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9608 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9609 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9610 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9611 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9612 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9613 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9614 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9615 $xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9616 $xmp .= "\t\t".'</rdf:Description>'."\n";
9617 $xmp .= "\t".'</rdf:RDF>'."\n";
9618 $xmp .= $this->custom_xmp;
9619 $xmp .= '</x:xmpmeta>'."\n";
9620 $xmp .= '<?xpacket end="w"?>';
9621 $out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9622 // restore previous isunicode value
9623 $this->isunicode = $prev_isunicode;
9624 $this->encrypted = $prev_encrypted;
9625 $this->_out($out);
9626 return $oid;
9630 * Output Catalog.
9631 * @return int object id
9632 * @protected
9634 protected function _putcatalog() {
9635 // put XMP
9636 $xmpobj = $this->_putXMP();
9637 // if required, add standard sRGB_IEC61966-2.1 blackscaled ICC colour profile
9638 if ($this->pdfa_mode OR $this->force_srgb) {
9639 $iccobj = $this->_newobj();
9640 $icc = file_get_contents(dirname(__FILE__).'/include/sRGB.icc');
9641 $filter = '';
9642 if ($this->compress) {
9643 $filter = ' /Filter /FlateDecode';
9644 $icc = gzcompress($icc);
9646 $icc = $this->_getrawstream($icc);
9647 $this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9649 // start catalog
9650 $oid = $this->_newobj();
9651 $out = '<< /Type /Catalog';
9652 $out .= ' /Version /'.$this->PDFVersion;
9653 //$out .= ' /Extensions <<>>';
9654 $out .= ' /Pages 1 0 R';
9655 //$out .= ' /PageLabels ' //...;
9656 $out .= ' /Names <<';
9657 if ((!$this->pdfa_mode) AND !empty($this->n_js)) {
9658 $out .= ' /JavaScript '.$this->n_js;
9660 if (!empty($this->efnames)) {
9661 $out .= ' /EmbeddedFiles <</Names [';
9662 foreach ($this->efnames AS $fn => $fref) {
9663 $out .= ' '.$this->_datastring($fn).' '.$fref;
9665 $out .= ' ]>>';
9667 $out .= ' >>';
9668 if (!empty($this->dests)) {
9669 $out .= ' /Dests '.($this->n_dests).' 0 R';
9671 $out .= $this->_putviewerpreferences();
9672 if (isset($this->LayoutMode) AND (!TCPDF_STATIC::empty_string($this->LayoutMode))) {
9673 $out .= ' /PageLayout /'.$this->LayoutMode;
9675 if (isset($this->PageMode) AND (!TCPDF_STATIC::empty_string($this->PageMode))) {
9676 $out .= ' /PageMode /'.$this->PageMode;
9678 if (count($this->outlines) > 0) {
9679 $out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
9680 $out .= ' /PageMode /UseOutlines';
9682 //$out .= ' /Threads []';
9683 if ($this->ZoomMode == 'fullpage') {
9684 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
9685 } elseif ($this->ZoomMode == 'fullwidth') {
9686 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
9687 } elseif ($this->ZoomMode == 'real') {
9688 $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
9689 } elseif (!is_string($this->ZoomMode)) {
9690 $out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %F]', ($this->ZoomMode / 100));
9692 //$out .= ' /AA <<>>';
9693 //$out .= ' /URI <<>>';
9694 $out .= ' /Metadata '.$xmpobj.' 0 R';
9695 //$out .= ' /StructTreeRoot <<>>';
9696 //$out .= ' /MarkInfo <<>>';
9697 if (isset($this->l['a_meta_language'])) {
9698 $out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
9700 //$out .= ' /SpiderInfo <<>>';
9701 // set OutputIntent to sRGB IEC61966-2.1 if required
9702 if ($this->pdfa_mode OR $this->force_srgb) {
9703 $out .= ' /OutputIntents [<<';
9704 $out .= ' /Type /OutputIntent';
9705 $out .= ' /S /GTS_PDFA1';
9706 $out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9707 $out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9708 $out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9709 $out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9710 $out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9711 $out .= ' >>]';
9713 //$out .= ' /PieceInfo <<>>';
9714 if (!empty($this->pdflayers)) {
9715 $lyrobjs = '';
9716 $lyrobjs_print = '';
9717 $lyrobjs_view = '';
9718 $lyrobjs_lock = '';
9719 foreach ($this->pdflayers as $layer) {
9720 $layer_obj_ref = ' '.$layer['objid'].' 0 R';
9721 $lyrobjs .= $layer_obj_ref;
9722 if ($layer['print']) {
9723 $lyrobjs_print .= $layer_obj_ref;
9725 if ($layer['view']) {
9726 $lyrobjs_view .= $layer_obj_ref;
9728 if ($layer['lock']) {
9729 $lyrobjs_lock .= $layer_obj_ref;
9732 $out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9733 $out .= ' /D <<';
9734 $out .= ' /Name '.$this->_textstring('Layers', $oid);
9735 $out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9736 $out .= ' /BaseState /ON';
9737 $out .= ' /ON ['.$lyrobjs_print.']';
9738 $out .= ' /OFF ['.$lyrobjs_view.']';
9739 $out .= ' /Locked ['.$lyrobjs_lock.']';
9740 $out .= ' /Intent /View';
9741 $out .= ' /AS [';
9742 $out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9743 $out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9744 $out .= ' ]';
9745 $out .= ' /Order ['.$lyrobjs.']';
9746 $out .= ' /ListMode /AllPages';
9747 //$out .= ' /RBGroups ['..']';
9748 //$out .= ' /Locked ['..']';
9749 $out .= ' >>';
9750 $out .= ' >>';
9752 // AcroForm
9753 if (!empty($this->form_obj_id)
9754 OR ($this->sign AND isset($this->signature_data['cert_type']))
9755 OR !empty($this->empty_signature_appearance)) {
9756 $out .= ' /AcroForm <<';
9757 $objrefs = '';
9758 if ($this->sign AND isset($this->signature_data['cert_type'])) {
9759 // set reference for signature object
9760 $objrefs .= $this->sig_obj_id.' 0 R';
9762 if (!empty($this->empty_signature_appearance)) {
9763 foreach ($this->empty_signature_appearance as $esa) {
9764 // set reference for empty signature objects
9765 $objrefs .= ' '.$esa['objid'].' 0 R';
9768 if (!empty($this->form_obj_id)) {
9769 foreach($this->form_obj_id as $objid) {
9770 $objrefs .= ' '.$objid.' 0 R';
9773 $out .= ' /Fields ['.$objrefs.']';
9774 // It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9775 $out .= ' /NeedAppearances false';
9776 if ($this->sign AND isset($this->signature_data['cert_type'])) {
9777 if ($this->signature_data['cert_type'] > 0) {
9778 $out .= ' /SigFlags 3';
9779 } else {
9780 $out .= ' /SigFlags 1';
9783 //$out .= ' /CO ';
9784 if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9785 $out .= ' /DR <<';
9786 $out .= ' /Font <<';
9787 foreach ($this->annotation_fonts as $fontkey => $fontid) {
9788 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9790 $out .= ' >> >>';
9792 $font = $this->getFontBuffer('helvetica');
9793 $out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
9794 $out .= ' /Q '.(($this->rtl)?'2':'0');
9795 //$out .= ' /XFA ';
9796 $out .= ' >>';
9797 // signatures
9798 if ($this->sign AND isset($this->signature_data['cert_type'])) {
9799 if ($this->signature_data['cert_type'] > 0) {
9800 $out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
9801 } else {
9802 $out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
9806 //$out .= ' /Legal <<>>';
9807 //$out .= ' /Requirements []';
9808 //$out .= ' /Collection <<>>';
9809 //$out .= ' /NeedsRendering true';
9810 $out .= ' >>';
9811 $out .= "\n".'endobj';
9812 $this->_out($out);
9813 return $oid;
9817 * Output viewer preferences.
9818 * @return string for viewer preferences
9819 * @author Nicola asuni
9820 * @since 3.1.000 (2008-06-09)
9821 * @protected
9823 protected function _putviewerpreferences() {
9824 $vp = $this->viewer_preferences;
9825 $out = ' /ViewerPreferences <<';
9826 if ($this->rtl) {
9827 $out .= ' /Direction /R2L';
9828 } else {
9829 $out .= ' /Direction /L2R';
9831 if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
9832 $out .= ' /HideToolbar true';
9834 if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
9835 $out .= ' /HideMenubar true';
9837 if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
9838 $out .= ' /HideWindowUI true';
9840 if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
9841 $out .= ' /FitWindow true';
9843 if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
9844 $out .= ' /CenterWindow true';
9846 if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
9847 $out .= ' /DisplayDocTitle true';
9849 if (isset($vp['NonFullScreenPageMode'])) {
9850 $out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
9852 if (isset($vp['ViewArea'])) {
9853 $out .= ' /ViewArea /'.$vp['ViewArea'];
9855 if (isset($vp['ViewClip'])) {
9856 $out .= ' /ViewClip /'.$vp['ViewClip'];
9858 if (isset($vp['PrintArea'])) {
9859 $out .= ' /PrintArea /'.$vp['PrintArea'];
9861 if (isset($vp['PrintClip'])) {
9862 $out .= ' /PrintClip /'.$vp['PrintClip'];
9864 if (isset($vp['PrintScaling'])) {
9865 $out .= ' /PrintScaling /'.$vp['PrintScaling'];
9867 if (isset($vp['Duplex']) AND (!TCPDF_STATIC::empty_string($vp['Duplex']))) {
9868 $out .= ' /Duplex /'.$vp['Duplex'];
9870 if (isset($vp['PickTrayByPDFSize'])) {
9871 if ($vp['PickTrayByPDFSize']) {
9872 $out .= ' /PickTrayByPDFSize true';
9873 } else {
9874 $out .= ' /PickTrayByPDFSize false';
9877 if (isset($vp['PrintPageRange'])) {
9878 $PrintPageRangeNum = '';
9879 foreach ($vp['PrintPageRange'] as $k => $v) {
9880 $PrintPageRangeNum .= ' '.($v - 1).'';
9882 $out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
9884 if (isset($vp['NumCopies'])) {
9885 $out .= ' /NumCopies '.intval($vp['NumCopies']);
9887 $out .= ' >>';
9888 return $out;
9892 * Output PDF File Header (7.5.2).
9893 * @protected
9895 protected function _putheader() {
9896 $this->_out('%PDF-'.$this->PDFVersion);
9897 $this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
9901 * Output end of document (EOF).
9902 * @protected
9904 protected function _enddoc() {
9905 if (isset($this->CurrentFont['fontkey']) AND isset($this->CurrentFont['subsetchars'])) {
9906 // save subset chars of the previous font
9907 $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
9909 $this->state = 1;
9910 $this->_putheader();
9911 $this->_putpages();
9912 $this->_putresources();
9913 // empty signature fields
9914 if (!empty($this->empty_signature_appearance)) {
9915 foreach ($this->empty_signature_appearance as $key => $esa) {
9916 // widget annotation for empty signature
9917 $out = $this->_getobj($esa['objid'])."\n";
9918 $out .= '<< /Type /Annot';
9919 $out .= ' /Subtype /Widget';
9920 $out .= ' /Rect ['.$esa['rect'].']';
9921 $out .= ' /P '.$this->page_obj_id[($esa['page'])].' 0 R'; // link to signature appearance page
9922 $out .= ' /F 4';
9923 $out .= ' /FT /Sig';
9924 $signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
9925 $out .= ' /T '.$this->_textstring($signame, $esa['objid']);
9926 $out .= ' /Ff 0';
9927 $out .= ' >>';
9928 $out .= "\n".'endobj';
9929 $this->_out($out);
9932 // Signature
9933 if ($this->sign AND isset($this->signature_data['cert_type'])) {
9934 // widget annotation for signature
9935 $out = $this->_getobj($this->sig_obj_id)."\n";
9936 $out .= '<< /Type /Annot';
9937 $out .= ' /Subtype /Widget';
9938 $out .= ' /Rect ['.$this->signature_appearance['rect'].']';
9939 $out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
9940 $out .= ' /F 4';
9941 $out .= ' /FT /Sig';
9942 $out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
9943 $out .= ' /Ff 0';
9944 $out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
9945 $out .= ' >>';
9946 $out .= "\n".'endobj';
9947 $this->_out($out);
9948 // signature
9949 $this->_putsignature();
9951 // Info
9952 $objid_info = $this->_putinfo();
9953 // Catalog
9954 $objid_catalog = $this->_putcatalog();
9955 // Cross-ref
9956 $o = $this->bufferlen;
9957 // XREF section
9958 $this->_out('xref');
9959 $this->_out('0 '.($this->n + 1));
9960 $this->_out('0000000000 65535 f ');
9961 $freegen = ($this->n + 2);
9962 for ($i=1; $i <= $this->n; ++$i) {
9963 if (!isset($this->offsets[$i]) AND ($i > 1)) {
9964 $this->_out(sprintf('0000000000 %05d f ', $freegen));
9965 ++$freegen;
9966 } else {
9967 $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
9970 // TRAILER
9971 $out = 'trailer'."\n";
9972 $out .= '<<';
9973 $out .= ' /Size '.($this->n + 1);
9974 $out .= ' /Root '.$objid_catalog.' 0 R';
9975 $out .= ' /Info '.$objid_info.' 0 R';
9976 if ($this->encrypted) {
9977 $out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
9979 $out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
9980 $out .= ' >>';
9981 $this->_out($out);
9982 $this->_out('startxref');
9983 $this->_out($o);
9984 $this->_out('%%EOF');
9985 $this->state = 3; // end-of-doc
9986 if ($this->diskcache) {
9987 // remove temporary files used for images
9988 foreach ($this->imagekeys as $key) {
9989 // remove temporary files
9990 unlink($this->images[$key]);
9992 foreach ($this->fontkeys as $key) {
9993 // remove temporary files
9994 unlink($this->fonts[$key]);
10000 * Initialize a new page.
10001 * @param $orientation (string) page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10002 * @param $format (mixed) The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
10003 * @protected
10004 * @see getPageSizeFromFormat(), setPageFormat()
10006 protected function _beginpage($orientation='', $format='') {
10007 ++$this->page;
10008 $this->pageobjects[$this->page] = array();
10009 $this->setPageBuffer($this->page, '');
10010 // initialize array for graphics tranformation positions inside a page buffer
10011 $this->transfmrk[$this->page] = array();
10012 $this->state = 2;
10013 if (TCPDF_STATIC::empty_string($orientation)) {
10014 if (isset($this->CurOrientation)) {
10015 $orientation = $this->CurOrientation;
10016 } elseif ($this->fwPt > $this->fhPt) {
10017 // landscape
10018 $orientation = 'L';
10019 } else {
10020 // portrait
10021 $orientation = 'P';
10024 if (TCPDF_STATIC::empty_string($format)) {
10025 $this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10026 $this->setPageOrientation($orientation);
10027 } else {
10028 $this->setPageFormat($format, $orientation);
10030 if ($this->rtl) {
10031 $this->x = $this->w - $this->rMargin;
10032 } else {
10033 $this->x = $this->lMargin;
10035 $this->y = $this->tMargin;
10036 if (isset($this->newpagegroup[$this->page])) {
10037 // start a new group
10038 $this->currpagegroup = $this->newpagegroup[$this->page];
10039 $this->pagegroups[$this->currpagegroup] = 1;
10040 } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
10041 ++$this->pagegroups[$this->currpagegroup];
10046 * Mark end of page.
10047 * @protected
10049 protected function _endpage() {
10050 $this->setVisibility('all');
10051 $this->state = 1;
10055 * Begin a new object and return the object number.
10056 * @return int object number
10057 * @protected
10059 protected function _newobj() {
10060 $this->_out($this->_getobj());
10061 return $this->n;
10065 * Return the starting object string for the selected object ID.
10066 * @param $objid (int) Object ID (leave empty to get a new ID).
10067 * @return string the starting object string
10068 * @protected
10069 * @since 5.8.009 (2010-08-20)
10071 protected function _getobj($objid='') {
10072 if ($objid === '') {
10073 ++$this->n;
10074 $objid = $this->n;
10076 $this->offsets[$objid] = $this->bufferlen;
10077 $this->pageobjects[$this->page][] = $objid;
10078 return $objid.' 0 obj';
10082 * Underline text.
10083 * @param $x (int) X coordinate
10084 * @param $y (int) Y coordinate
10085 * @param $txt (string) text to underline
10086 * @protected
10088 protected function _dounderline($x, $y, $txt) {
10089 $w = $this->GetStringWidth($txt);
10090 return $this->_dounderlinew($x, $y, $w);
10094 * Underline for rectangular text area.
10095 * @param $x (int) X coordinate
10096 * @param $y (int) Y coordinate
10097 * @param $w (int) width to underline
10098 * @protected
10099 * @since 4.8.008 (2009-09-29)
10101 protected function _dounderlinew($x, $y, $w) {
10102 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10103 return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
10107 * Line through text.
10108 * @param $x (int) X coordinate
10109 * @param $y (int) Y coordinate
10110 * @param $txt (string) text to linethrough
10111 * @protected
10113 protected function _dolinethrough($x, $y, $txt) {
10114 $w = $this->GetStringWidth($txt);
10115 return $this->_dolinethroughw($x, $y, $w);
10119 * Line through for rectangular text area.
10120 * @param $x (int) X coordinate
10121 * @param $y (int) Y coordinate
10122 * @param $w (int) line length (width)
10123 * @protected
10124 * @since 4.9.008 (2009-09-29)
10126 protected function _dolinethroughw($x, $y, $w) {
10127 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10128 return sprintf('%F %F %F %F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
10132 * Overline text.
10133 * @param $x (int) X coordinate
10134 * @param $y (int) Y coordinate
10135 * @param $txt (string) text to overline
10136 * @protected
10137 * @since 4.9.015 (2010-04-19)
10139 protected function _dooverline($x, $y, $txt) {
10140 $w = $this->GetStringWidth($txt);
10141 return $this->_dooverlinew($x, $y, $w);
10145 * Overline for rectangular text area.
10146 * @param $x (int) X coordinate
10147 * @param $y (int) Y coordinate
10148 * @param $w (int) width to overline
10149 * @protected
10150 * @since 4.9.015 (2010-04-19)
10152 protected function _dooverlinew($x, $y, $w) {
10153 $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
10154 return sprintf('%F %F %F %F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
10159 * Format a data string for meta information
10160 * @param $s (string) data string to escape.
10161 * @param $n (int) object ID
10162 * @return string escaped string.
10163 * @protected
10165 protected function _datastring($s, $n=0) {
10166 if ($n == 0) {
10167 $n = $this->n;
10169 $s = $this->_encrypt_data($n, $s);
10170 return '('. TCPDF_STATIC::_escape($s).')';
10174 * Set the document creation timestamp
10175 * @param $time (mixed) Document creation timestamp in seconds or date-time string.
10176 * @public
10177 * @since 5.9.152 (2012-03-23)
10179 public function setDocCreationTimestamp($time) {
10180 if (is_string($time)) {
10181 $time = TCPDF_STATIC::getTimestamp($time);
10183 $this->doc_creation_timestamp = intval($time);
10187 * Set the document modification timestamp
10188 * @param $time (mixed) Document modification timestamp in seconds or date-time string.
10189 * @public
10190 * @since 5.9.152 (2012-03-23)
10192 public function setDocModificationTimestamp($time) {
10193 if (is_string($time)) {
10194 $time = TCPDF_STATIC::getTimestamp($time);
10196 $this->doc_modification_timestamp = intval($time);
10200 * Returns document creation timestamp in seconds.
10201 * @return (int) Creation timestamp in seconds.
10202 * @public
10203 * @since 5.9.152 (2012-03-23)
10205 public function getDocCreationTimestamp() {
10206 return $this->doc_creation_timestamp;
10210 * Returns document modification timestamp in seconds.
10211 * @return (int) Modfication timestamp in seconds.
10212 * @public
10213 * @since 5.9.152 (2012-03-23)
10215 public function getDocModificationTimestamp() {
10216 return $this->doc_modification_timestamp;
10220 * Returns a formatted date for meta information
10221 * @param $n (int) Object ID.
10222 * @param $timestamp (int) Timestamp to convert.
10223 * @return string escaped date string.
10224 * @protected
10225 * @since 4.6.028 (2009-08-25)
10227 protected function _datestring($n=0, $timestamp=0) {
10228 if ((empty($timestamp)) OR ($timestamp < 0)) {
10229 $timestamp = $this->doc_creation_timestamp;
10231 return $this->_datastring('D:'.TCPDF_STATIC::getFormattedDate($timestamp), $n);
10235 * Format a text string for meta information
10236 * @param $s (string) string to escape.
10237 * @param $n (int) object ID
10238 * @return string escaped string.
10239 * @protected
10241 protected function _textstring($s, $n=0) {
10242 if ($this->isunicode) {
10243 //Convert string to UTF-16BE
10244 $s = TCPDF_FONTS::UTF8ToUTF16BE($s, true, $this->isunicode, $this->CurrentFont);
10246 return $this->_datastring($s, $n);
10250 * THIS METHOD IS DEPRECATED
10251 * Format a text string
10252 * @param $s (string) string to escape.
10253 * @return string escaped string.
10254 * @protected
10255 * @deprecated
10257 protected function _escapetext($s) {
10258 if ($this->isunicode) {
10259 if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
10260 $s = TCPDF_FONTS::UTF8ToLatin1($s, $this->isunicode, $this->CurrentFont);
10261 } else {
10262 //Convert string to UTF-16BE and reverse RTL language
10263 $s = TCPDF_FONTS::utf8StrRev($s, false, $this->tmprtl, $this->isunicode, $this->CurrentFont);
10266 return TCPDF_STATIC::_escape($s);
10270 * get raw output stream.
10271 * @param $s (string) string to output.
10272 * @param $n (int) object reference for encryption mode
10273 * @protected
10274 * @author Nicola Asuni
10275 * @since 5.5.000 (2010-06-22)
10277 protected function _getrawstream($s, $n=0) {
10278 if ($n <= 0) {
10279 // default to current object
10280 $n = $this->n;
10282 return $this->_encrypt_data($n, $s);
10286 * Format output stream (DEPRECATED).
10287 * @param $s (string) string to output.
10288 * @param $n (int) object reference for encryption mode
10289 * @protected
10290 * @deprecated
10292 protected function _getstream($s, $n=0) {
10293 return 'stream'."\n".$this->_getrawstream($s, $n)."\n".'endstream';
10297 * Output a stream (DEPRECATED).
10298 * @param $s (string) string to output.
10299 * @param $n (int) object reference for encryption mode
10300 * @protected
10301 * @deprecated
10303 protected function _putstream($s, $n=0) {
10304 $this->_out($this->_getstream($s, $n));
10308 * Output a string to the document.
10309 * @param $s (string) string to output.
10310 * @protected
10312 protected function _out($s) {
10313 if ($this->state == 2) {
10314 if ($this->inxobj) {
10315 // we are inside an XObject template
10316 $this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
10317 } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
10318 // puts data before page footer
10319 $pagebuff = $this->getPageBuffer($this->page);
10320 $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
10321 $footer = substr($pagebuff, -$this->footerlen[$this->page]);
10322 $this->setPageBuffer($this->page, $page.$s."\n".$footer);
10323 // update footer position
10324 $this->footerpos[$this->page] += strlen($s."\n");
10325 } else {
10326 // set page data
10327 $this->setPageBuffer($this->page, $s."\n", true);
10329 } elseif ($this->state > 0) {
10330 // set general data
10331 $this->setBuffer($s."\n");
10336 * Set header font.
10337 * @param $font (array) Array describing the basic font parameters: (family, style, size).
10338 * @public
10339 * @since 1.1
10341 public function setHeaderFont($font) {
10342 $this->header_font = $font;
10346 * Get header font.
10347 * @return array() Array describing the basic font parameters: (family, style, size).
10348 * @public
10349 * @since 4.0.012 (2008-07-24)
10351 public function getHeaderFont() {
10352 return $this->header_font;
10356 * Set footer font.
10357 * @param $font (array) Array describing the basic font parameters: (family, style, size).
10358 * @public
10359 * @since 1.1
10361 public function setFooterFont($font) {
10362 $this->footer_font = $font;
10366 * Get Footer font.
10367 * @return array() Array describing the basic font parameters: (family, style, size).
10368 * @public
10369 * @since 4.0.012 (2008-07-24)
10371 public function getFooterFont() {
10372 return $this->footer_font;
10376 * Set language array.
10377 * @param $language (array)
10378 * @public
10379 * @since 1.1
10381 public function setLanguageArray($language) {
10382 $this->l = $language;
10383 if (isset($this->l['a_meta_dir'])) {
10384 $this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
10385 } else {
10386 $this->rtl = false;
10391 * Returns the PDF data.
10392 * @public
10394 public function getPDFData() {
10395 if ($this->state < 3) {
10396 $this->Close();
10398 return $this->buffer;
10402 * Output anchor link.
10403 * @param $url (string) link URL or internal link (i.e.: &lt;a href="#23,4.5"&gt;link to page 23 at 4.5 Y position&lt;/a&gt;)
10404 * @param $name (string) link name
10405 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
10406 * @param $firstline (boolean) if true prints only the first line and return the remaining string.
10407 * @param $color (array) array of RGB text color
10408 * @param $style (string) font style (U, D, B, I)
10409 * @param $firstblock (boolean) if true the string is the starting of a line.
10410 * @return the number of cells used or the remaining text if $firstline = true;
10411 * @public
10413 public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
10414 if (isset($url[1]) AND ($url[0] == '#')) {
10415 // convert url to internal link
10416 $lnkdata = explode(',', $url);
10417 if (isset($lnkdata[0]) ) {
10418 $page = substr($lnkdata[0], 1);
10419 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10420 $lnky = floatval($lnkdata[1]);
10421 } else {
10422 $lnky = 0;
10424 $url = $this->AddLink();
10425 $this->SetLink($url, $lnky, $page);
10428 // store current settings
10429 $prevcolor = $this->fgcolor;
10430 $prevstyle = $this->FontStyle;
10431 if (empty($color)) {
10432 $this->SetTextColorArray($this->htmlLinkColorArray);
10433 } else {
10434 $this->SetTextColorArray($color);
10436 if ($style == -1) {
10437 $this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10438 } else {
10439 $this->SetFont('', $this->FontStyle.$style);
10441 $ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10442 // restore settings
10443 $this->SetFont('', $prevstyle);
10444 $this->SetTextColorArray($prevcolor);
10445 return $ret;
10449 * Converts pixels to User's Units.
10450 * @param $px (int) pixels
10451 * @return float value in user's unit
10452 * @public
10453 * @see setImageScale(), getImageScale()
10455 public function pixelsToUnits($px) {
10456 return ($px / ($this->imgscale * $this->k));
10460 * Reverse function for htmlentities.
10461 * Convert entities in UTF-8.
10462 * @param $text_to_convert (string) Text to convert.
10463 * @return string converted text string
10464 * @public
10466 public function unhtmlentities($text_to_convert) {
10467 return @html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
10470 // ENCRYPTION METHODS ----------------------------------
10473 * Compute encryption key depending on object number where the encrypted data is stored.
10474 * This is used for all strings and streams without crypt filter specifier.
10475 * @param $n (int) object number
10476 * @return int object key
10477 * @protected
10478 * @author Nicola Asuni
10479 * @since 2.0.000 (2008-01-02)
10481 protected function _objectkey($n) {
10482 $objkey = $this->encryptdata['key'].pack('VXxx', $n);
10483 if ($this->encryptdata['mode'] == 2) { // AES-128
10484 // AES padding
10485 $objkey .= "\x73\x41\x6C\x54"; // sAlT
10487 $objkey = substr(TCPDF_STATIC::_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
10488 $objkey = substr($objkey, 0, 16);
10489 return $objkey;
10493 * Encrypt the input string.
10494 * @param $n (int) object number
10495 * @param $s (string) data string to encrypt
10496 * @return encrypted string
10497 * @protected
10498 * @author Nicola Asuni
10499 * @since 5.0.005 (2010-05-11)
10501 protected function _encrypt_data($n, $s) {
10502 if (!$this->encrypted) {
10503 return $s;
10505 switch ($this->encryptdata['mode']) {
10506 case 0: // RC4-40
10507 case 1: { // RC4-128
10508 $s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10509 break;
10511 case 2: { // AES-128
10512 $s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10513 break;
10515 case 3: { // AES-256
10516 $s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10517 break;
10520 return $s;
10524 * Put encryption on PDF document.
10525 * @protected
10526 * @author Nicola Asuni
10527 * @since 2.0.000 (2008-01-02)
10529 protected function _putencryption() {
10530 if (!$this->encrypted) {
10531 return;
10533 $this->encryptdata['objid'] = $this->_newobj();
10534 $out = '<<';
10535 if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
10536 $this->encryptdata['Filter'] = 'Standard';
10538 $out .= ' /Filter /'.$this->encryptdata['Filter'];
10539 if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
10540 $out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
10542 if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
10543 $this->encryptdata['V'] = 1;
10545 // V is a code specifying the algorithm to be used in encrypting and decrypting the document
10546 $out .= ' /V '.$this->encryptdata['V'];
10547 if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
10548 // The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10549 $out .= ' /Length '.$this->encryptdata['Length'];
10550 } else {
10551 $out .= ' /Length 40';
10553 if ($this->encryptdata['V'] >= 4) {
10554 if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
10555 $this->encryptdata['StmF'] = 'Identity';
10557 if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
10558 // The name of the crypt filter that shall be used when decrypting all strings in the document.
10559 $this->encryptdata['StrF'] = 'Identity';
10561 // A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10562 if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
10563 $out .= ' /CF <<';
10564 $out .= ' /'.$this->encryptdata['StmF'].' <<';
10565 $out .= ' /Type /CryptFilter';
10566 if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10567 // The method used
10568 $out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
10569 if ($this->encryptdata['pubkey']) {
10570 $out .= ' /Recipients [';
10571 foreach ($this->encryptdata['Recipients'] as $rec) {
10572 $out .= ' <'.$rec.'>';
10574 $out .= ' ]';
10575 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10576 $out .= ' /EncryptMetadata false';
10577 } else {
10578 $out .= ' /EncryptMetadata true';
10581 } else {
10582 $out .= ' /CFM /None';
10584 if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
10585 // The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10586 $out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
10587 } else {
10588 $out .= ' /AuthEvent /DocOpen';
10590 if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
10591 // The bit length of the encryption key.
10592 $out .= ' /Length '.$this->encryptdata['CF']['Length'];
10594 $out .= ' >> >>';
10596 // The name of the crypt filter that shall be used by default when decrypting streams.
10597 $out .= ' /StmF /'.$this->encryptdata['StmF'];
10598 // The name of the crypt filter that shall be used when decrypting all strings in the document.
10599 $out .= ' /StrF /'.$this->encryptdata['StrF'];
10600 if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
10601 // The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10602 $out .= ' /EFF /'.$this->encryptdata[''];
10605 // Additional encryption dictionary entries for the standard security handler
10606 if ($this->encryptdata['pubkey']) {
10607 if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
10608 $out .= ' /Recipients [';
10609 foreach ($this->encryptdata['Recipients'] as $rec) {
10610 $out .= ' <'.$rec.'>';
10612 $out .= ' ]';
10614 } else {
10615 $out .= ' /R';
10616 if ($this->encryptdata['V'] == 5) { // AES-256
10617 $out .= ' 5';
10618 $out .= ' /OE ('.TCPDF_STATIC::_escape($this->encryptdata['OE']).')';
10619 $out .= ' /UE ('.TCPDF_STATIC::_escape($this->encryptdata['UE']).')';
10620 $out .= ' /Perms ('.TCPDF_STATIC::_escape($this->encryptdata['perms']).')';
10621 } elseif ($this->encryptdata['V'] == 4) { // AES-128
10622 $out .= ' 4';
10623 } elseif ($this->encryptdata['V'] < 2) { // RC-40
10624 $out .= ' 2';
10625 } else { // RC-128
10626 $out .= ' 3';
10628 $out .= ' /O ('.TCPDF_STATIC::_escape($this->encryptdata['O']).')';
10629 $out .= ' /U ('.TCPDF_STATIC::_escape($this->encryptdata['U']).')';
10630 $out .= ' /P '.$this->encryptdata['P'];
10631 if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
10632 $out .= ' /EncryptMetadata false';
10633 } else {
10634 $out .= ' /EncryptMetadata true';
10637 $out .= ' >>';
10638 $out .= "\n".'endobj';
10639 $this->_out($out);
10643 * Compute U value (used for encryption)
10644 * @return string U value
10645 * @protected
10646 * @since 2.0.000 (2008-01-02)
10647 * @author Nicola Asuni
10649 protected function _Uvalue() {
10650 if ($this->encryptdata['mode'] == 0) { // RC4-40
10651 return TCPDF_STATIC::_RC4($this->encryptdata['key'], TCPDF_STATIC::$enc_padding, $this->last_enc_key, $this->last_enc_key_c);
10652 } elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
10653 $tmp = TCPDF_STATIC::_md5_16(TCPDF_STATIC::$enc_padding.$this->encryptdata['fileid']);
10654 $enc = TCPDF_STATIC::_RC4($this->encryptdata['key'], $tmp, $this->last_enc_key, $this->last_enc_key_c);
10655 $len = strlen($tmp);
10656 for ($i = 1; $i <= 19; ++$i) {
10657 $ek = '';
10658 for ($j = 0; $j < $len; ++$j) {
10659 $ek .= chr(ord($this->encryptdata['key'][$j]) ^ $i);
10661 $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10663 $enc .= str_repeat("\x00", 16);
10664 return substr($enc, 0, 32);
10665 } elseif ($this->encryptdata['mode'] == 3) { // AES-256
10666 $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10667 // User Validation Salt
10668 $this->encryptdata['UVS'] = substr($seed, 0, 8);
10669 // User Key Salt
10670 $this->encryptdata['UKS'] = substr($seed, 8, 16);
10671 return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
10676 * Compute UE value (used for encryption)
10677 * @return string UE value
10678 * @protected
10679 * @since 5.9.006 (2010-10-19)
10680 * @author Nicola Asuni
10682 protected function _UEvalue() {
10683 $hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
10684 $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
10685 return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
10689 * Compute O value (used for encryption)
10690 * @return string O value
10691 * @protected
10692 * @since 2.0.000 (2008-01-02)
10693 * @author Nicola Asuni
10695 protected function _Ovalue() {
10696 if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
10697 $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['owner_password']);
10698 if ($this->encryptdata['mode'] > 0) {
10699 for ($i = 0; $i < 50; ++$i) {
10700 $tmp = TCPDF_STATIC::_md5_16($tmp);
10703 $owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
10704 $enc = TCPDF_STATIC::_RC4($owner_key, $this->encryptdata['user_password'], $this->last_enc_key, $this->last_enc_key_c);
10705 if ($this->encryptdata['mode'] > 0) {
10706 $len = strlen($owner_key);
10707 for ($i = 1; $i <= 19; ++$i) {
10708 $ek = '';
10709 for ($j = 0; $j < $len; ++$j) {
10710 $ek .= chr(ord($owner_key[$j]) ^ $i);
10712 $enc = TCPDF_STATIC::_RC4($ek, $enc, $this->last_enc_key, $this->last_enc_key_c);
10715 return $enc;
10716 } elseif ($this->encryptdata['mode'] == 3) { // AES-256
10717 $seed = TCPDF_STATIC::_md5_16(TCPDF_STATIC::getRandomSeed());
10718 // Owner Validation Salt
10719 $this->encryptdata['OVS'] = substr($seed, 0, 8);
10720 // Owner Key Salt
10721 $this->encryptdata['OKS'] = substr($seed, 8, 16);
10722 return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
10727 * Compute OE value (used for encryption)
10728 * @return string OE value
10729 * @protected
10730 * @since 5.9.006 (2010-10-19)
10731 * @author Nicola Asuni
10733 protected function _OEvalue() {
10734 $hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
10735 $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
10736 return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
10740 * Convert password for AES-256 encryption mode
10741 * @param $password (string) password
10742 * @return string password
10743 * @protected
10744 * @since 5.9.006 (2010-10-19)
10745 * @author Nicola Asuni
10747 protected function _fixAES256Password($password) {
10748 $psw = ''; // password to be returned
10749 $psw_array = TCPDF_FONTS::utf8Bidi(TCPDF_FONTS::UTF8StringToArray($password, $this->isunicode, $this->CurrentFont), $password, $this->rtl, $this->isunicode, $this->CurrentFont);
10750 foreach ($psw_array as $c) {
10751 $psw .= TCPDF_FONTS::unichr($c, $this->isunicode);
10753 return substr($psw, 0, 127);
10757 * Compute encryption key
10758 * @protected
10759 * @since 2.0.000 (2008-01-02)
10760 * @author Nicola Asuni
10762 protected function _generateencryptionkey() {
10763 $keybytelen = ($this->encryptdata['Length'] / 8);
10764 if (!$this->encryptdata['pubkey']) { // standard mode
10765 if ($this->encryptdata['mode'] == 3) { // AES-256
10766 // generate 256 bit random key
10767 $this->encryptdata['key'] = substr(hash('sha256', TCPDF_STATIC::getRandomSeed(), true), 0, $keybytelen);
10768 // truncate passwords
10769 $this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
10770 $this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
10771 // Compute U value
10772 $this->encryptdata['U'] = $this->_Uvalue();
10773 // Compute UE value
10774 $this->encryptdata['UE'] = $this->_UEvalue();
10775 // Compute O value
10776 $this->encryptdata['O'] = $this->_Ovalue();
10777 // Compute OE value
10778 $this->encryptdata['OE'] = $this->_OEvalue();
10779 // Compute P value
10780 $this->encryptdata['P'] = $this->encryptdata['protection'];
10781 // Computing the encryption dictionary's Perms (permissions) value
10782 $perms = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
10783 $perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10784 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
10785 $perms .= 'F';
10786 } else {
10787 $perms .= 'T';
10789 $perms .= 'adb'; // bytes 9-11
10790 $perms .= 'nick'; // bytes 12-15
10791 $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB));
10792 $this->encryptdata['perms'] = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->encryptdata['key'], $perms, MCRYPT_MODE_ECB, $iv);
10793 } else { // RC4-40, RC4-128, AES-128
10794 // Pad passwords
10795 $this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10796 $this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].TCPDF_STATIC::$enc_padding, 0, 32);
10797 // Compute O value
10798 $this->encryptdata['O'] = $this->_Ovalue();
10799 // get default permissions (reverse byte order)
10800 $permissions = TCPDF_STATIC::getEncPermissionsString($this->encryptdata['protection']);
10801 // Compute encryption key
10802 $tmp = TCPDF_STATIC::_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
10803 if ($this->encryptdata['mode'] > 0) {
10804 for ($i = 0; $i < 50; ++$i) {
10805 $tmp = TCPDF_STATIC::_md5_16(substr($tmp, 0, $keybytelen));
10808 $this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
10809 // Compute U value
10810 $this->encryptdata['U'] = $this->_Uvalue();
10811 // Compute P value
10812 $this->encryptdata['P'] = $this->encryptdata['protection'];
10814 } else { // Public-Key mode
10815 // random 20-byte seed
10816 $seed = sha1(TCPDF_STATIC::getRandomSeed(), true);
10817 $recipient_bytes = '';
10818 foreach ($this->encryptdata['pubkeys'] as $pubkey) {
10819 // for each public certificate
10820 if (isset($pubkey['p'])) {
10821 $pkprotection = TCPDF_STATIC::getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
10822 } else {
10823 $pkprotection = $this->encryptdata['protection'];
10825 // get default permissions (reverse byte order)
10826 $pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10827 // envelope data
10828 $envelope = $seed.$pkpermissions;
10829 // write the envelope data to a temporary file
10830 $tempkeyfile = TCPDF_STATIC::getObjFilename('key');
10831 $f = fopen($tempkeyfile, 'wb');
10832 if (!$f) {
10833 $this->Error('Unable to create temporary key file: '.$tempkeyfile);
10835 $envelope_length = strlen($envelope);
10836 fwrite($f, $envelope, $envelope_length);
10837 fclose($f);
10838 $tempencfile = TCPDF_STATIC::getObjFilename('enc');
10839 if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED)) {
10840 $this->Error('Unable to encrypt the file: '.$tempkeyfile);
10842 unlink($tempkeyfile);
10843 // read encryption signature
10844 $signature = file_get_contents($tempencfile, false, null, $envelope_length);
10845 unlink($tempencfile);
10846 // extract signature
10847 $signature = substr($signature, strpos($signature, 'Content-Disposition'));
10848 $tmparr = explode("\n\n", $signature);
10849 $signature = trim($tmparr[1]);
10850 unset($tmparr);
10851 // decode signature
10852 $signature = base64_decode($signature);
10853 // convert signature to hex
10854 $hexsignature = current(unpack('H*', $signature));
10855 // store signature on recipients array
10856 $this->encryptdata['Recipients'][] = $hexsignature;
10857 // The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
10858 $recipient_bytes .= $signature;
10860 // calculate encryption key
10861 if ($this->encryptdata['mode'] == 3) { // AES-256
10862 $this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
10863 } else { // RC4-40, RC4-128, AES-128
10864 $this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
10870 * Set document protection
10871 * Remark: the protection against modification is for people who have the full Acrobat product.
10872 * If you don't set any password, the document will open as usual. If you set a user password, the PDF viewer will ask for it before displaying the document. The master password, if different from the user one, can be used to get full access.
10873 * Note: protecting a document requires to encrypt it, which increases the processing time a lot. This can cause a PHP time-out in some cases, especially if the document contains images or fonts.
10874 * @param $permissions (Array) the set of permissions (specify the ones you want to block):<ul><li>print : Print the document;</li><li>modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';</li><li>copy : Copy or otherwise extract text and graphics from the document;</li><li>annot-forms : Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);</li><li>fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;</li><li>extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);</li><li>assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;</li><li>print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.</li><li>owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.</li></ul>
10875 * @param $user_pass (String) user password. Empty by default.
10876 * @param $owner_pass (String) owner password. If not specified, a random value is used.
10877 * @param $mode (int) encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
10878 * @param $pubkeys (String) array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../examples/data/cert/tcpdf.crt', 'p' => array('print')))
10879 * @public
10880 * @since 2.0.000 (2008-01-02)
10881 * @author Nicola Asuni
10883 public function SetProtection($permissions=array('print', 'modify', 'copy', 'annot-forms', 'fill-forms', 'extract', 'assemble', 'print-high'), $user_pass='', $owner_pass=null, $mode=0, $pubkeys=null) {
10884 if ($this->pdfa_mode) {
10885 // encryption is not allowed in PDF/A mode
10886 return;
10888 $this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
10889 if (($pubkeys !== null) AND (is_array($pubkeys))) {
10890 // public-key mode
10891 $this->encryptdata['pubkeys'] = $pubkeys;
10892 if ($mode == 0) {
10893 // public-Key Security requires at least 128 bit
10894 $mode = 1;
10896 if (!function_exists('openssl_pkcs7_encrypt')) {
10897 $this->Error('Public-Key Security requires openssl library.');
10899 // Set Public-Key filter (availabe are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
10900 $this->encryptdata['pubkey'] = true;
10901 $this->encryptdata['Filter'] = 'Adobe.PubSec';
10902 $this->encryptdata['StmF'] = 'DefaultCryptFilter';
10903 $this->encryptdata['StrF'] = 'DefaultCryptFilter';
10904 } else {
10905 // standard mode (password mode)
10906 $this->encryptdata['pubkey'] = false;
10907 $this->encryptdata['Filter'] = 'Standard';
10908 $this->encryptdata['StmF'] = 'StdCF';
10909 $this->encryptdata['StrF'] = 'StdCF';
10911 if ($mode > 1) { // AES
10912 if (!extension_loaded('mcrypt')) {
10913 $this->Error('AES encryption requires mcrypt library (http://www.php.net/manual/en/mcrypt.requirements.php).');
10915 if (mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
10916 $this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
10918 if (($mode == 3) AND !function_exists('hash')) {
10919 // the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
10920 $this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
10923 if ($owner_pass === null) {
10924 $owner_pass = md5(TCPDF_STATIC::getRandomSeed());
10926 $this->encryptdata['user_password'] = $user_pass;
10927 $this->encryptdata['owner_password'] = $owner_pass;
10928 $this->encryptdata['mode'] = $mode;
10929 switch ($mode) {
10930 case 0: { // RC4 40 bit
10931 $this->encryptdata['V'] = 1;
10932 $this->encryptdata['Length'] = 40;
10933 $this->encryptdata['CF']['CFM'] = 'V2';
10934 break;
10936 case 1: { // RC4 128 bit
10937 $this->encryptdata['V'] = 2;
10938 $this->encryptdata['Length'] = 128;
10939 $this->encryptdata['CF']['CFM'] = 'V2';
10940 if ($this->encryptdata['pubkey']) {
10941 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
10942 $this->encryptdata['Recipients'] = array();
10944 break;
10946 case 2: { // AES 128 bit
10947 $this->encryptdata['V'] = 4;
10948 $this->encryptdata['Length'] = 128;
10949 $this->encryptdata['CF']['CFM'] = 'AESV2';
10950 $this->encryptdata['CF']['Length'] = 128;
10951 if ($this->encryptdata['pubkey']) {
10952 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
10953 $this->encryptdata['Recipients'] = array();
10955 break;
10957 case 3: { // AES 256 bit
10958 $this->encryptdata['V'] = 5;
10959 $this->encryptdata['Length'] = 256;
10960 $this->encryptdata['CF']['CFM'] = 'AESV3';
10961 $this->encryptdata['CF']['Length'] = 256;
10962 if ($this->encryptdata['pubkey']) {
10963 $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
10964 $this->encryptdata['Recipients'] = array();
10966 break;
10969 $this->encrypted = true;
10970 $this->encryptdata['fileid'] = TCPDF_STATIC::convertHexStringToString($this->file_id);
10971 $this->_generateencryptionkey();
10974 // END OF ENCRYPTION FUNCTIONS -------------------------
10976 // START TRANSFORMATIONS SECTION -----------------------
10979 * Starts a 2D tranformation saving current graphic state.
10980 * This function must be called before scaling, mirroring, translation, rotation and skewing.
10981 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
10982 * @public
10983 * @since 2.1.000 (2008-01-07)
10984 * @see StartTransform(), StopTransform()
10986 public function StartTransform() {
10987 if ($this->state != 2) {
10988 return;
10990 $this->_outSaveGraphicsState();
10991 if ($this->inxobj) {
10992 // we are inside an XObject template
10993 $this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
10994 } else {
10995 $this->transfmrk[$this->page][] = $this->pagelen[$this->page];
10997 ++$this->transfmatrix_key;
10998 $this->transfmatrix[$this->transfmatrix_key] = array();
11002 * Stops a 2D tranformation restoring previous graphic state.
11003 * This function must be called after scaling, mirroring, translation, rotation and skewing.
11004 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11005 * @public
11006 * @since 2.1.000 (2008-01-07)
11007 * @see StartTransform(), StopTransform()
11009 public function StopTransform() {
11010 if ($this->state != 2) {
11011 return;
11013 $this->_outRestoreGraphicsState();
11014 if (isset($this->transfmatrix[$this->transfmatrix_key])) {
11015 array_pop($this->transfmatrix[$this->transfmatrix_key]);
11016 --$this->transfmatrix_key;
11018 if ($this->inxobj) {
11019 // we are inside an XObject template
11020 array_pop($this->xobjects[$this->xobjid]['transfmrk']);
11021 } else {
11022 array_pop($this->transfmrk[$this->page]);
11026 * Horizontal Scaling.
11027 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
11028 * @param $x (int) abscissa of the scaling center. Default is current x position
11029 * @param $y (int) ordinate of the scaling center. Default is current y position
11030 * @public
11031 * @since 2.1.000 (2008-01-07)
11032 * @see StartTransform(), StopTransform()
11034 public function ScaleX($s_x, $x='', $y='') {
11035 $this->Scale($s_x, 100, $x, $y);
11039 * Vertical Scaling.
11040 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11041 * @param $x (int) abscissa of the scaling center. Default is current x position
11042 * @param $y (int) ordinate of the scaling center. Default is current y position
11043 * @public
11044 * @since 2.1.000 (2008-01-07)
11045 * @see StartTransform(), StopTransform()
11047 public function ScaleY($s_y, $x='', $y='') {
11048 $this->Scale(100, $s_y, $x, $y);
11052 * Vertical and horizontal proportional Scaling.
11053 * @param $s (float) scaling factor for width and height as percent. 0 is not allowed.
11054 * @param $x (int) abscissa of the scaling center. Default is current x position
11055 * @param $y (int) ordinate of the scaling center. Default is current y position
11056 * @public
11057 * @since 2.1.000 (2008-01-07)
11058 * @see StartTransform(), StopTransform()
11060 public function ScaleXY($s, $x='', $y='') {
11061 $this->Scale($s, $s, $x, $y);
11065 * Vertical and horizontal non-proportional Scaling.
11066 * @param $s_x (float) scaling factor for width as percent. 0 is not allowed.
11067 * @param $s_y (float) scaling factor for height as percent. 0 is not allowed.
11068 * @param $x (int) abscissa of the scaling center. Default is current x position
11069 * @param $y (int) ordinate of the scaling center. Default is current y position
11070 * @public
11071 * @since 2.1.000 (2008-01-07)
11072 * @see StartTransform(), StopTransform()
11074 public function Scale($s_x, $s_y, $x='', $y='') {
11075 if ($x === '') {
11076 $x = $this->x;
11078 if ($y === '') {
11079 $y = $this->y;
11081 if (($s_x == 0) OR ($s_y == 0)) {
11082 $this->Error('Please do not use values equal to zero for scaling');
11084 $y = ($this->h - $y) * $this->k;
11085 $x *= $this->k;
11086 //calculate elements of transformation matrix
11087 $s_x /= 100;
11088 $s_y /= 100;
11089 $tm = array();
11090 $tm[0] = $s_x;
11091 $tm[1] = 0;
11092 $tm[2] = 0;
11093 $tm[3] = $s_y;
11094 $tm[4] = $x * (1 - $s_x);
11095 $tm[5] = $y * (1 - $s_y);
11096 //scale the coordinate system
11097 $this->Transform($tm);
11101 * Horizontal Mirroring.
11102 * @param $x (int) abscissa of the point. Default is current x position
11103 * @public
11104 * @since 2.1.000 (2008-01-07)
11105 * @see StartTransform(), StopTransform()
11107 public function MirrorH($x='') {
11108 $this->Scale(-100, 100, $x);
11112 * Verical Mirroring.
11113 * @param $y (int) ordinate of the point. Default is current y position
11114 * @public
11115 * @since 2.1.000 (2008-01-07)
11116 * @see StartTransform(), StopTransform()
11118 public function MirrorV($y='') {
11119 $this->Scale(100, -100, '', $y);
11123 * Point reflection mirroring.
11124 * @param $x (int) abscissa of the point. Default is current x position
11125 * @param $y (int) ordinate of the point. Default is current y position
11126 * @public
11127 * @since 2.1.000 (2008-01-07)
11128 * @see StartTransform(), StopTransform()
11130 public function MirrorP($x='',$y='') {
11131 $this->Scale(-100, -100, $x, $y);
11135 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11136 * @param $angle (float) gradient angle of the straight line. Default is 0 (horizontal line).
11137 * @param $x (int) abscissa of the point. Default is current x position
11138 * @param $y (int) ordinate of the point. Default is current y position
11139 * @public
11140 * @since 2.1.000 (2008-01-07)
11141 * @see StartTransform(), StopTransform()
11143 public function MirrorL($angle=0, $x='',$y='') {
11144 $this->Scale(-100, 100, $x, $y);
11145 $this->Rotate(-2*($angle-90), $x, $y);
11149 * Translate graphic object horizontally.
11150 * @param $t_x (int) movement to the right (or left for RTL)
11151 * @public
11152 * @since 2.1.000 (2008-01-07)
11153 * @see StartTransform(), StopTransform()
11155 public function TranslateX($t_x) {
11156 $this->Translate($t_x, 0);
11160 * Translate graphic object vertically.
11161 * @param $t_y (int) movement to the bottom
11162 * @public
11163 * @since 2.1.000 (2008-01-07)
11164 * @see StartTransform(), StopTransform()
11166 public function TranslateY($t_y) {
11167 $this->Translate(0, $t_y);
11171 * Translate graphic object horizontally and vertically.
11172 * @param $t_x (int) movement to the right
11173 * @param $t_y (int) movement to the bottom
11174 * @public
11175 * @since 2.1.000 (2008-01-07)
11176 * @see StartTransform(), StopTransform()
11178 public function Translate($t_x, $t_y) {
11179 //calculate elements of transformation matrix
11180 $tm = array();
11181 $tm[0] = 1;
11182 $tm[1] = 0;
11183 $tm[2] = 0;
11184 $tm[3] = 1;
11185 $tm[4] = $t_x * $this->k;
11186 $tm[5] = -$t_y * $this->k;
11187 //translate the coordinate system
11188 $this->Transform($tm);
11192 * Rotate object.
11193 * @param $angle (float) angle in degrees for counter-clockwise rotation
11194 * @param $x (int) abscissa of the rotation center. Default is current x position
11195 * @param $y (int) ordinate of the rotation center. Default is current y position
11196 * @public
11197 * @since 2.1.000 (2008-01-07)
11198 * @see StartTransform(), StopTransform()
11200 public function Rotate($angle, $x='', $y='') {
11201 if ($x === '') {
11202 $x = $this->x;
11204 if ($y === '') {
11205 $y = $this->y;
11207 $y = ($this->h - $y) * $this->k;
11208 $x *= $this->k;
11209 //calculate elements of transformation matrix
11210 $tm = array();
11211 $tm[0] = cos(deg2rad($angle));
11212 $tm[1] = sin(deg2rad($angle));
11213 $tm[2] = -$tm[1];
11214 $tm[3] = $tm[0];
11215 $tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
11216 $tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11217 //rotate the coordinate system around ($x,$y)
11218 $this->Transform($tm);
11222 * Skew horizontally.
11223 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11224 * @param $x (int) abscissa of the skewing center. default is current x position
11225 * @param $y (int) ordinate of the skewing center. default is current y position
11226 * @public
11227 * @since 2.1.000 (2008-01-07)
11228 * @see StartTransform(), StopTransform()
11230 public function SkewX($angle_x, $x='', $y='') {
11231 $this->Skew($angle_x, 0, $x, $y);
11235 * Skew vertically.
11236 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11237 * @param $x (int) abscissa of the skewing center. default is current x position
11238 * @param $y (int) ordinate of the skewing center. default is current y position
11239 * @public
11240 * @since 2.1.000 (2008-01-07)
11241 * @see StartTransform(), StopTransform()
11243 public function SkewY($angle_y, $x='', $y='') {
11244 $this->Skew(0, $angle_y, $x, $y);
11248 * Skew.
11249 * @param $angle_x (float) angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11250 * @param $angle_y (float) angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11251 * @param $x (int) abscissa of the skewing center. default is current x position
11252 * @param $y (int) ordinate of the skewing center. default is current y position
11253 * @public
11254 * @since 2.1.000 (2008-01-07)
11255 * @see StartTransform(), StopTransform()
11257 public function Skew($angle_x, $angle_y, $x='', $y='') {
11258 if ($x === '') {
11259 $x = $this->x;
11261 if ($y === '') {
11262 $y = $this->y;
11264 if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11265 $this->Error('Please use values between -90 and +90 degrees for Skewing.');
11267 $x *= $this->k;
11268 $y = ($this->h - $y) * $this->k;
11269 //calculate elements of transformation matrix
11270 $tm = array();
11271 $tm[0] = 1;
11272 $tm[1] = tan(deg2rad($angle_y));
11273 $tm[2] = tan(deg2rad($angle_x));
11274 $tm[3] = 1;
11275 $tm[4] = -$tm[2] * $y;
11276 $tm[5] = -$tm[1] * $x;
11277 //skew the coordinate system
11278 $this->Transform($tm);
11282 * Apply graphic transformations.
11283 * @param $tm (array) transformation matrix
11284 * @protected
11285 * @since 2.1.000 (2008-01-07)
11286 * @see StartTransform(), StopTransform()
11288 protected function Transform($tm) {
11289 if ($this->state != 2) {
11290 return;
11292 $this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11293 // add tranformation matrix
11294 $this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11295 // update transformation mark
11296 if ($this->inxobj) {
11297 // we are inside an XObject template
11298 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
11299 $key = key($this->xobjects[$this->xobjid]['transfmrk']);
11300 $this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
11302 } elseif (end($this->transfmrk[$this->page]) !== false) {
11303 $key = key($this->transfmrk[$this->page]);
11304 $this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
11308 // END TRANSFORMATIONS SECTION -------------------------
11310 // START GRAPHIC FUNCTIONS SECTION ---------------------
11311 // The following section is based on the code provided by David Hernandez Sanz
11314 * Defines the line width. By default, the value equals 0.2 mm. The method can be called before the first page is created and the value is retained from page to page.
11315 * @param $width (float) The width.
11316 * @public
11317 * @since 1.0
11318 * @see Line(), Rect(), Cell(), MultiCell()
11320 public function SetLineWidth($width) {
11321 //Set line width
11322 $this->LineWidth = $width;
11323 $this->linestyleWidth = sprintf('%F w', ($width * $this->k));
11324 if ($this->state == 2) {
11325 $this->_out($this->linestyleWidth);
11330 * Returns the current the line width.
11331 * @return int Line width
11332 * @public
11333 * @since 2.1.000 (2008-01-07)
11334 * @see Line(), SetLineWidth()
11336 public function GetLineWidth() {
11337 return $this->LineWidth;
11341 * Set line style.
11342 * @param $style (array) Line style. Array with keys among the following:
11343 * <ul>
11344 * <li>width (float): Width of the line in user units.</li>
11345 * <li>cap (string): Type of cap to put on the line. Possible values are:
11346 * butt, round, square. The difference between "square" and "butt" is that
11347 * "square" projects a flat end past the end of the line.</li>
11348 * <li>join (string): Type of join. Possible values are: miter, round,
11349 * bevel.</li>
11350 * <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11351 * series of length values, which are the lengths of the on and off dashes.
11352 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11353 * 1 off, 2 on, 1 off, ...</li>
11354 * <li>phase (integer): Modifier on the dash pattern which is used to shift
11355 * the point at which the pattern starts.</li>
11356 * <li>color (array): Draw color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName).</li>
11357 * </ul>
11358 * @param $ret (boolean) if true do not send the command.
11359 * @return string the PDF command
11360 * @public
11361 * @since 2.1.000 (2008-01-08)
11363 public function SetLineStyle($style, $ret=false) {
11364 $s = ''; // string to be returned
11365 if (!is_array($style)) {
11366 return;
11368 if (isset($style['width'])) {
11369 $this->LineWidth = $style['width'];
11370 $this->linestyleWidth = sprintf('%F w', ($style['width'] * $this->k));
11371 $s .= $this->linestyleWidth.' ';
11373 if (isset($style['cap'])) {
11374 $ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11375 if (isset($ca[$style['cap']])) {
11376 $this->linestyleCap = $ca[$style['cap']].' J';
11377 $s .= $this->linestyleCap.' ';
11380 if (isset($style['join'])) {
11381 $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11382 if (isset($ja[$style['join']])) {
11383 $this->linestyleJoin = $ja[$style['join']].' j';
11384 $s .= $this->linestyleJoin.' ';
11387 if (isset($style['dash'])) {
11388 $dash_string = '';
11389 if ($style['dash']) {
11390 if (preg_match('/^.+,/', $style['dash']) > 0) {
11391 $tab = explode(',', $style['dash']);
11392 } else {
11393 $tab = array($style['dash']);
11395 $dash_string = '';
11396 foreach ($tab as $i => $v) {
11397 if ($i) {
11398 $dash_string .= ' ';
11400 $dash_string .= sprintf('%F', $v);
11403 if (!isset($style['phase']) OR !$style['dash']) {
11404 $style['phase'] = 0;
11406 $this->linestyleDash = sprintf('[%s] %F d', $dash_string, $style['phase']);
11407 $s .= $this->linestyleDash.' ';
11409 if (isset($style['color'])) {
11410 $s .= $this->SetDrawColorArray($style['color'], true).' ';
11412 if (!$ret AND ($this->state == 2)) {
11413 $this->_out($s);
11415 return $s;
11419 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11420 * @param $x (float) Abscissa of point.
11421 * @param $y (float) Ordinate of point.
11422 * @protected
11423 * @since 2.1.000 (2008-01-08)
11425 protected function _outPoint($x, $y) {
11426 if ($this->state == 2) {
11427 $this->_out(sprintf('%F %F m', ($x * $this->k), (($this->h - $y) * $this->k)));
11432 * Append a straight line segment from the current point to the point (x, y).
11433 * The new current point shall be (x, y).
11434 * @param $x (float) Abscissa of end point.
11435 * @param $y (float) Ordinate of end point.
11436 * @protected
11437 * @since 2.1.000 (2008-01-08)
11439 protected function _outLine($x, $y) {
11440 if ($this->state == 2) {
11441 $this->_out(sprintf('%F %F l', ($x * $this->k), (($this->h - $y) * $this->k)));
11446 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11447 * @param $x (float) Abscissa of upper-left corner.
11448 * @param $y (float) Ordinate of upper-left corner.
11449 * @param $w (float) Width.
11450 * @param $h (float) Height.
11451 * @param $op (string) options
11452 * @protected
11453 * @since 2.1.000 (2008-01-08)
11455 protected function _outRect($x, $y, $w, $h, $op) {
11456 if ($this->state == 2) {
11457 $this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k), (($this->h - $y) * $this->k), ($w * $this->k), (-$h * $this->k), $op));
11462 * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x2, y2) as the Bezier control points.
11463 * The new current point shall be (x3, y3).
11464 * @param $x1 (float) Abscissa of control point 1.
11465 * @param $y1 (float) Ordinate of control point 1.
11466 * @param $x2 (float) Abscissa of control point 2.
11467 * @param $y2 (float) Ordinate of control point 2.
11468 * @param $x3 (float) Abscissa of end point.
11469 * @param $y3 (float) Ordinate of end point.
11470 * @protected
11471 * @since 2.1.000 (2008-01-08)
11473 protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11474 if ($this->state == 2) {
11475 $this->_out(sprintf('%F %F %F %F %F %F c', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11480 * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using the current point and (x2, y2) as the Bezier control points.
11481 * The new current point shall be (x3, y3).
11482 * @param $x2 (float) Abscissa of control point 2.
11483 * @param $y2 (float) Ordinate of control point 2.
11484 * @param $x3 (float) Abscissa of end point.
11485 * @param $y3 (float) Ordinate of end point.
11486 * @protected
11487 * @since 4.9.019 (2010-04-26)
11489 protected function _outCurveV($x2, $y2, $x3, $y3) {
11490 if ($this->state == 2) {
11491 $this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k), (($this->h - $y2) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11496 * Append a cubic Bezier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x3, y3) as the Bezier control points.
11497 * The new current point shall be (x3, y3).
11498 * @param $x1 (float) Abscissa of control point 1.
11499 * @param $y1 (float) Ordinate of control point 1.
11500 * @param $x3 (float) Abscissa of end point.
11501 * @param $y3 (float) Ordinate of end point.
11502 * @protected
11503 * @since 2.1.000 (2008-01-08)
11505 protected function _outCurveY($x1, $y1, $x3, $y3) {
11506 if ($this->state == 2) {
11507 $this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k), (($this->h - $y1) * $this->k), ($x3 * $this->k), (($this->h - $y3) * $this->k)));
11512 * Draws a line between two points.
11513 * @param $x1 (float) Abscissa of first point.
11514 * @param $y1 (float) Ordinate of first point.
11515 * @param $x2 (float) Abscissa of second point.
11516 * @param $y2 (float) Ordinate of second point.
11517 * @param $style (array) Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11518 * @public
11519 * @since 1.0
11520 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11522 public function Line($x1, $y1, $x2, $y2, $style=array()) {
11523 if ($this->state != 2) {
11524 return;
11526 if (is_array($style)) {
11527 $this->SetLineStyle($style);
11529 $this->_outPoint($x1, $y1);
11530 $this->_outLine($x2, $y2);
11531 $this->_out('S');
11535 * Draws a rectangle.
11536 * @param $x (float) Abscissa of upper-left corner.
11537 * @param $y (float) Ordinate of upper-left corner.
11538 * @param $w (float) Width.
11539 * @param $h (float) Height.
11540 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11541 * @param $border_style (array) Border style of rectangle. Array with keys among the following:
11542 * <ul>
11543 * <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11544 * <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11545 * </ul>
11546 * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11547 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11548 * @public
11549 * @since 1.0
11550 * @see SetLineStyle()
11552 public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11553 if ($this->state != 2) {
11554 return;
11556 if (empty($style)) {
11557 $style = 'S';
11559 if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11560 // set background color
11561 $this->SetFillColorArray($fill_color);
11563 if (!empty($border_style)) {
11564 if (isset($border_style['all']) AND !empty($border_style['all'])) {
11565 //set global style for border
11566 $this->SetLineStyle($border_style['all']);
11567 $border_style = array();
11568 } else {
11569 // remove stroke operator from style
11570 $opnostroke = array('S' => '', 'D' => '', 's' => '', 'd' => '', 'B' => 'F', 'FD' => 'F', 'DF' => 'F', 'B*' => 'F*', 'F*D' => 'F*', 'DF*' => 'F*', 'b' => 'f', 'fd' => 'f', 'df' => 'f', 'b*' => 'f*', 'f*d' => 'f*', 'df*' => 'f*' );
11571 if (isset($opnostroke[$style])) {
11572 $style = $opnostroke[$style];
11576 if (!empty($style)) {
11577 $op = TCPDF_STATIC::getPathPaintOperator($style);
11578 $this->_outRect($x, $y, $w, $h, $op);
11580 if (!empty($border_style)) {
11581 $border_style2 = array();
11582 foreach ($border_style as $line => $value) {
11583 $length = strlen($line);
11584 for ($i = 0; $i < $length; ++$i) {
11585 $border_style2[$line[$i]] = $value;
11588 $border_style = $border_style2;
11589 if (isset($border_style['L']) AND $border_style['L']) {
11590 $this->Line($x, $y, $x, $y + $h, $border_style['L']);
11592 if (isset($border_style['T']) AND $border_style['T']) {
11593 $this->Line($x, $y, $x + $w, $y, $border_style['T']);
11595 if (isset($border_style['R']) AND $border_style['R']) {
11596 $this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
11598 if (isset($border_style['B']) AND $border_style['B']) {
11599 $this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
11605 * Draws a Bezier curve.
11606 * The Bezier curve is a tangent to the line between the control points at
11607 * either end of the curve.
11608 * @param $x0 (float) Abscissa of start point.
11609 * @param $y0 (float) Ordinate of start point.
11610 * @param $x1 (float) Abscissa of control point 1.
11611 * @param $y1 (float) Ordinate of control point 1.
11612 * @param $x2 (float) Abscissa of control point 2.
11613 * @param $y2 (float) Ordinate of control point 2.
11614 * @param $x3 (float) Abscissa of end point.
11615 * @param $y3 (float) Ordinate of end point.
11616 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11617 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11618 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11619 * @public
11620 * @see SetLineStyle()
11621 * @since 2.1.000 (2008-01-08)
11623 public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11624 if ($this->state != 2) {
11625 return;
11627 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11628 $this->SetFillColorArray($fill_color);
11630 $op = TCPDF_STATIC::getPathPaintOperator($style);
11631 if ($line_style) {
11632 $this->SetLineStyle($line_style);
11634 $this->_outPoint($x0, $y0);
11635 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11636 $this->_out($op);
11640 * Draws a poly-Bezier curve.
11641 * Each Bezier curve segment is a tangent to the line between the control points at
11642 * either end of the curve.
11643 * @param $x0 (float) Abscissa of start point.
11644 * @param $y0 (float) Ordinate of start point.
11645 * @param $segments (float) An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11646 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11647 * @param $line_style (array) Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11648 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11649 * @public
11650 * @see SetLineStyle()
11651 * @since 3.0008 (2008-05-12)
11653 public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11654 if ($this->state != 2) {
11655 return;
11657 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11658 $this->SetFillColorArray($fill_color);
11660 $op = TCPDF_STATIC::getPathPaintOperator($style);
11661 if ($op == 'f') {
11662 $line_style = array();
11664 if ($line_style) {
11665 $this->SetLineStyle($line_style);
11667 $this->_outPoint($x0, $y0);
11668 foreach ($segments as $segment) {
11669 list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11670 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11672 $this->_out($op);
11676 * Draws an ellipse.
11677 * An ellipse is formed from n Bezier curves.
11678 * @param $x0 (float) Abscissa of center point.
11679 * @param $y0 (float) Ordinate of center point.
11680 * @param $rx (float) Horizontal radius.
11681 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11682 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
11683 * @param $astart: (float) Angle start of draw line. Default value: 0.
11684 * @param $afinish: (float) Angle finish of draw line. Default value: 360.
11685 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11686 * @param $line_style (array) Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11687 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11688 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11689 * @author Nicola Asuni
11690 * @public
11691 * @since 2.1.000 (2008-01-08)
11693 public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11694 if ($this->state != 2) {
11695 return;
11697 if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11698 $ry = $rx;
11700 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11701 $this->SetFillColorArray($fill_color);
11703 $op = TCPDF_STATIC::getPathPaintOperator($style);
11704 if ($op == 'f') {
11705 $line_style = array();
11707 if ($line_style) {
11708 $this->SetLineStyle($line_style);
11710 $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11711 $this->_out($op);
11715 * Append an elliptical arc to the current path.
11716 * An ellipse is formed from n Bezier curves.
11717 * @param $xc (float) Abscissa of center point.
11718 * @param $yc (float) Ordinate of center point.
11719 * @param $rx (float) Horizontal radius.
11720 * @param $ry (float) Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11721 * @param $xang: (float) Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11722 * @param $angs: (float) Angle start of draw line. Default value: 0.
11723 * @param $angf: (float) Angle finish of draw line. Default value: 360.
11724 * @param $pie (boolean) if true do not mark the border point (used to draw pie sectors).
11725 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of ellipse.
11726 * @param $startpoint (boolean) if true output a starting point.
11727 * @param $ccw (boolean) if true draws in counter-clockwise.
11728 * @param $svg (boolean) if true the angles are in svg mode (already calculated).
11729 * @return array bounding box coordinates (x min, y min, x max, y max)
11730 * @author Nicola Asuni
11731 * @protected
11732 * @since 4.9.019 (2010-04-26)
11734 protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11735 if (($rx <= 0) OR ($ry < 0)) {
11736 return;
11738 $k = $this->k;
11739 if ($nc < 2) {
11740 $nc = 2;
11742 $xmin = 2147483647;
11743 $ymin = 2147483647;
11744 $xmax = 0;
11745 $ymax = 0;
11746 if ($pie) {
11747 // center of the arc
11748 $this->_outPoint($xc, $yc);
11750 $xang = deg2rad((float) $xang);
11751 $angs = deg2rad((float) $angs);
11752 $angf = deg2rad((float) $angf);
11753 if ($svg) {
11754 $as = $angs;
11755 $af = $angf;
11756 } else {
11757 $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11758 $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11760 if ($as < 0) {
11761 $as += (2 * M_PI);
11763 if ($af < 0) {
11764 $af += (2 * M_PI);
11766 if ($ccw AND ($as > $af)) {
11767 // reverse rotation
11768 $as -= (2 * M_PI);
11769 } elseif (!$ccw AND ($as < $af)) {
11770 // reverse rotation
11771 $af -= (2 * M_PI);
11773 $total_angle = ($af - $as);
11774 if ($nc < 2) {
11775 $nc = 2;
11777 // total arcs to draw
11778 $nc *= (2 * abs($total_angle) / M_PI);
11779 $nc = round($nc) + 1;
11780 // angle of each arc
11781 $arcang = ($total_angle / $nc);
11782 // center point in PDF coordinates
11783 $x0 = $xc;
11784 $y0 = ($this->h - $yc);
11785 // starting angle
11786 $ang = $as;
11787 $alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11788 $cos_xang = cos($xang);
11789 $sin_xang = sin($xang);
11790 $cos_ang = cos($ang);
11791 $sin_ang = sin($ang);
11792 // first arc point
11793 $px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11794 $py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11795 // first Bezier control point
11796 $qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11797 $qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11798 if ($pie) {
11799 // line from center to arc starting point
11800 $this->_outLine($px1, $this->h - $py1);
11801 } elseif ($startpoint) {
11802 // arc starting point
11803 $this->_outPoint($px1, $this->h - $py1);
11805 // draw arcs
11806 for ($i = 1; $i <= $nc; ++$i) {
11807 // starting angle
11808 $ang = $as + ($i * $arcang);
11809 if ($i == $nc) {
11810 $ang = $af;
11812 $cos_ang = cos($ang);
11813 $sin_ang = sin($ang);
11814 // second arc point
11815 $px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11816 $py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
11817 // second Bezier control point
11818 $qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11819 $qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
11820 // draw arc
11821 $cx1 = ($px1 + $qx1);
11822 $cy1 = ($this->h - ($py1 + $qy1));
11823 $cx2 = ($px2 - $qx2);
11824 $cy2 = ($this->h - ($py2 - $qy2));
11825 $cx3 = $px2;
11826 $cy3 = ($this->h - $py2);
11827 $this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11828 // get bounding box coordinates
11829 $xmin = min($xmin, $cx1, $cx2, $cx3);
11830 $ymin = min($ymin, $cy1, $cy2, $cy3);
11831 $xmax = max($xmax, $cx1, $cx2, $cx3);
11832 $ymax = max($ymax, $cy1, $cy2, $cy3);
11833 // move to next point
11834 $px1 = $px2;
11835 $py1 = $py2;
11836 $qx1 = $qx2;
11837 $qy1 = $qy2;
11839 if ($pie) {
11840 $this->_outLine($xc, $yc);
11841 // get bounding box coordinates
11842 $xmin = min($xmin, $xc);
11843 $ymin = min($ymin, $yc);
11844 $xmax = max($xmax, $xc);
11845 $ymax = max($ymax, $yc);
11847 return array($xmin, $ymin, $xmax, $ymax);
11851 * Draws a circle.
11852 * A circle is formed from n Bezier curves.
11853 * @param $x0 (float) Abscissa of center point.
11854 * @param $y0 (float) Ordinate of center point.
11855 * @param $r (float) Radius.
11856 * @param $angstr: (float) Angle start of draw line. Default value: 0.
11857 * @param $angend: (float) Angle finish of draw line. Default value: 360.
11858 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11859 * @param $line_style (array) Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
11860 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11861 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of circle.
11862 * @public
11863 * @since 2.1.000 (2008-01-08)
11865 public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11866 $this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
11870 * Draws a polygonal line
11871 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11872 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11873 * @param $line_style (array) Line style of polygon. Array with keys among the following:
11874 * <ul>
11875 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11876 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11877 * </ul>
11878 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11879 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11880 * @since 4.8.003 (2009-09-15)
11881 * @public
11883 public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
11884 $this->Polygon($p, $style, $line_style, $fill_color, false);
11888 * Draws a polygon.
11889 * @param $p (array) Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11890 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11891 * @param $line_style (array) Line style of polygon. Array with keys among the following:
11892 * <ul>
11893 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11894 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11895 * </ul>
11896 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11897 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
11898 * @param $closed (boolean) if true the polygon is closes, otherwise will remain open
11899 * @public
11900 * @since 2.1.000 (2008-01-08)
11902 public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
11903 if ($this->state != 2) {
11904 return;
11906 $nc = count($p); // number of coordinates
11907 $np = $nc / 2; // number of points
11908 if ($closed) {
11909 // close polygon by adding the first 2 points at the end (one line)
11910 for ($i = 0; $i < 4; ++$i) {
11911 $p[$nc + $i] = $p[$i];
11913 // copy style for the last added line
11914 if (isset($line_style[0])) {
11915 $line_style[$np] = $line_style[0];
11917 $nc += 4;
11919 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
11920 $this->SetFillColorArray($fill_color);
11922 $op = TCPDF_STATIC::getPathPaintOperator($style);
11923 if ($op == 'f') {
11924 $line_style = array();
11926 $draw = true;
11927 if ($line_style) {
11928 if (isset($line_style['all'])) {
11929 $this->SetLineStyle($line_style['all']);
11930 } else {
11931 $draw = false;
11932 if ($op == 'B') {
11933 // draw fill
11934 $op = 'f';
11935 $this->_outPoint($p[0], $p[1]);
11936 for ($i = 2; $i < $nc; $i = $i + 2) {
11937 $this->_outLine($p[$i], $p[$i + 1]);
11939 $this->_out($op);
11941 // draw outline
11942 $this->_outPoint($p[0], $p[1]);
11943 for ($i = 2; $i < $nc; $i = $i + 2) {
11944 $line_num = ($i / 2) - 1;
11945 if (isset($line_style[$line_num])) {
11946 if ($line_style[$line_num] != 0) {
11947 if (is_array($line_style[$line_num])) {
11948 $this->_out('S');
11949 $this->SetLineStyle($line_style[$line_num]);
11950 $this->_outPoint($p[$i - 2], $p[$i - 1]);
11951 $this->_outLine($p[$i], $p[$i + 1]);
11952 $this->_out('S');
11953 $this->_outPoint($p[$i], $p[$i + 1]);
11954 } else {
11955 $this->_outLine($p[$i], $p[$i + 1]);
11958 } else {
11959 $this->_outLine($p[$i], $p[$i + 1]);
11962 $this->_out($op);
11965 if ($draw) {
11966 $this->_outPoint($p[0], $p[1]);
11967 for ($i = 2; $i < $nc; $i = $i + 2) {
11968 $this->_outLine($p[$i], $p[$i + 1]);
11970 $this->_out($op);
11975 * Draws a regular polygon.
11976 * @param $x0 (float) Abscissa of center point.
11977 * @param $y0 (float) Ordinate of center point.
11978 * @param $r: (float) Radius of inscribed circle.
11979 * @param $ns (integer) Number of sides.
11980 * @param $angle (float) Angle oriented (anti-clockwise). Default value: 0.
11981 * @param $draw_circle (boolean) Draw inscribed circle or not. Default value: false.
11982 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
11983 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
11984 * <ul>
11985 * <li>all: Line style of all sides. Array like for SetLineStyle().</li>
11986 * <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
11987 * </ul>
11988 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
11989 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11990 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
11991 * <ul>
11992 * <li>D or empty string: Draw (default).</li>
11993 * <li>F: Fill.</li>
11994 * <li>DF or FD: Draw and fill.</li>
11995 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
11996 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
11997 * </ul>
11998 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
11999 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12000 * @public
12001 * @since 2.1.000 (2008-01-08)
12003 public function RegularPolygon($x0, $y0, $r, $ns, $angle=0, $draw_circle=false, $style='', $line_style=array(), $fill_color=array(), $circle_style='', $circle_outLine_style=array(), $circle_fill_color=array()) {
12004 if (3 > $ns) {
12005 $ns = 3;
12007 if ($draw_circle) {
12008 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12010 $p = array();
12011 for ($i = 0; $i < $ns; ++$i) {
12012 $a = $angle + ($i * 360 / $ns);
12013 $a_rad = deg2rad((float) $a);
12014 $p[] = $x0 + ($r * sin($a_rad));
12015 $p[] = $y0 + ($r * cos($a_rad));
12017 $this->Polygon($p, $style, $line_style, $fill_color);
12021 * Draws a star polygon
12022 * @param $x0 (float) Abscissa of center point.
12023 * @param $y0 (float) Ordinate of center point.
12024 * @param $r (float) Radius of inscribed circle.
12025 * @param $nv (integer) Number of vertices.
12026 * @param $ng (integer) Number of gap (if ($ng % $nv = 1) then is a regular polygon).
12027 * @param $angle: (float) Angle oriented (anti-clockwise). Default value: 0.
12028 * @param $draw_circle: (boolean) Draw inscribed circle or not. Default value is false.
12029 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12030 * @param $line_style (array) Line style of polygon sides. Array with keys among the following:
12031 * <ul>
12032 * <li>all: Line style of all sides. Array like for
12033 * SetLineStyle().</li>
12034 * <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
12035 * </ul>
12036 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12037 * @param $fill_color (array) Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12038 * @param $circle_style (string) Style of rendering of inscribed circle (if draws). Possible values are:
12039 * <ul>
12040 * <li>D or empty string: Draw (default).</li>
12041 * <li>F: Fill.</li>
12042 * <li>DF or FD: Draw and fill.</li>
12043 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12044 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12045 * </ul>
12046 * @param $circle_outLine_style (array) Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12047 * @param $circle_fill_color (array) Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12048 * @public
12049 * @since 2.1.000 (2008-01-08)
12051 public function StarPolygon($x0, $y0, $r, $nv, $ng, $angle=0, $draw_circle=false, $style='', $line_style=array(), $fill_color=array(), $circle_style='', $circle_outLine_style=array(), $circle_fill_color=array()) {
12052 if ($nv < 2) {
12053 $nv = 2;
12055 if ($draw_circle) {
12056 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12058 $p2 = array();
12059 $visited = array();
12060 for ($i = 0; $i < $nv; ++$i) {
12061 $a = $angle + ($i * 360 / $nv);
12062 $a_rad = deg2rad((float) $a);
12063 $p2[] = $x0 + ($r * sin($a_rad));
12064 $p2[] = $y0 + ($r * cos($a_rad));
12065 $visited[] = false;
12067 $p = array();
12068 $i = 0;
12069 do {
12070 $p[] = $p2[$i * 2];
12071 $p[] = $p2[($i * 2) + 1];
12072 $visited[$i] = true;
12073 $i += $ng;
12074 $i %= $nv;
12075 } while (!$visited[$i]);
12076 $this->Polygon($p, $style, $line_style, $fill_color);
12080 * Draws a rounded rectangle.
12081 * @param $x (float) Abscissa of upper-left corner.
12082 * @param $y (float) Ordinate of upper-left corner.
12083 * @param $w (float) Width.
12084 * @param $h (float) Height.
12085 * @param $r (float) the radius of the circle used to round off the corners of the rectangle.
12086 * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
12087 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12088 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12089 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12090 * @public
12091 * @since 2.1.000 (2008-01-08)
12093 public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12094 $this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12098 * Draws a rounded rectangle.
12099 * @param $x (float) Abscissa of upper-left corner.
12100 * @param $y (float) Ordinate of upper-left corner.
12101 * @param $w (float) Width.
12102 * @param $h (float) Height.
12103 * @param $rx (float) the x-axis radius of the ellipse used to round off the corners of the rectangle.
12104 * @param $ry (float) the y-axis radius of the ellipse used to round off the corners of the rectangle.
12105 * @param $round_corner (string) Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top right, bottom right, bottom left and top left. Default value: all rounded corner ("1111").
12106 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
12107 * @param $border_style (array) Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12108 * @param $fill_color (array) Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K) or array(C,M,Y,K,SpotColorName). Default value: default color (empty array).
12109 * @public
12110 * @since 4.9.019 (2010-04-22)
12112 public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12113 if ($this->state != 2) {
12114 return;
12116 if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12117 // Not rounded
12118 $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12119 return;
12121 // Rounded
12122 if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
12123 $this->SetFillColorArray($fill_color);
12125 $op = TCPDF_STATIC::getPathPaintOperator($style);
12126 if ($op == 'f') {
12127 $border_style = array();
12129 if ($border_style) {
12130 $this->SetLineStyle($border_style);
12132 $MyArc = 4 / 3 * (sqrt(2) - 1);
12133 $this->_outPoint($x + $rx, $y);
12134 $xc = $x + $w - $rx;
12135 $yc = $y + $ry;
12136 $this->_outLine($xc, $y);
12137 if ($round_corner[0]) {
12138 $this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
12139 } else {
12140 $this->_outLine($x + $w, $y);
12142 $xc = $x + $w - $rx;
12143 $yc = $y + $h - $ry;
12144 $this->_outLine($x + $w, $yc);
12145 if ($round_corner[1]) {
12146 $this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
12147 } else {
12148 $this->_outLine($x + $w, $y + $h);
12150 $xc = $x + $rx;
12151 $yc = $y + $h - $ry;
12152 $this->_outLine($xc, $y + $h);
12153 if ($round_corner[2]) {
12154 $this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
12155 } else {
12156 $this->_outLine($x, $y + $h);
12158 $xc = $x + $rx;
12159 $yc = $y + $ry;
12160 $this->_outLine($x, $yc);
12161 if ($round_corner[3]) {
12162 $this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12163 } else {
12164 $this->_outLine($x, $y);
12165 $this->_outLine($x + $rx, $y);
12167 $this->_out($op);
12171 * Draws a grahic arrow.
12172 * @param $x0 (float) Abscissa of first point.
12173 * @param $y0 (float) Ordinate of first point.
12174 * @param $x1 (float) Abscissa of second point.
12175 * @param $y1 (float) Ordinate of second point.
12176 * @param $head_style (int) (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12177 * @param $arm_size (float) length of arrowhead arms
12178 * @param $arm_angle (int) angle between an arm and the shaft
12179 * @author Piotr Galecki, Nicola Asuni, Andy Meier
12180 * @since 4.6.018 (2009-07-10)
12182 public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12183 // getting arrow direction angle
12184 // 0 deg angle is when both arms go along X axis. angle grows clockwise.
12185 $dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12186 if ($dir_angle < 0) {
12187 $dir_angle += (2 * M_PI);
12189 $arm_angle = deg2rad($arm_angle);
12190 $sx1 = $x1;
12191 $sy1 = $y1;
12192 if ($head_style > 0) {
12193 // calculate the stopping point for the arrow shaft
12194 $sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
12195 $sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
12197 // main arrow line / shaft
12198 $this->Line($x0, $y0, $sx1, $sy1);
12199 // left arrowhead arm tip
12200 $x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
12201 $y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
12202 // right arrowhead arm tip
12203 $x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
12204 $y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
12205 $mode = 'D';
12206 $style = array();
12207 switch ($head_style) {
12208 case 0: {
12209 // draw only arrowhead arms
12210 $mode = 'D';
12211 $style = array(1, 1, 0);
12212 break;
12214 case 1: {
12215 // draw closed arrowhead, but no fill
12216 $mode = 'D';
12217 break;
12219 case 2: {
12220 // closed and filled arrowhead
12221 $mode = 'DF';
12222 break;
12224 case 3: {
12225 // filled arrowhead
12226 $mode = 'F';
12227 break;
12230 $this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12233 // END GRAPHIC FUNCTIONS SECTION -----------------------
12236 * Add a Named Destination.
12237 * NOTE: destination names are unique, so only last entry will be saved.
12238 * @param $name (string) Destination name.
12239 * @param $y (float) Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12240 * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12241 * @param $x (float) X position in user units of the destiantion on the selected page (default = -1 = current position;).
12242 * @return (string) Stripped named destination identifier or false in case of error.
12243 * @public
12244 * @author Christian Deligant, Nicola Asuni
12245 * @since 5.9.097 (2011-06-23)
12247 public function setDestination($name, $y=-1, $page='', $x=-1) {
12248 // remove unsupported characters
12249 $name = TCPDF_STATIC::encodeNameObject($name);
12250 if (TCPDF_STATIC::empty_string($name)) {
12251 return false;
12253 if ($y == -1) {
12254 $y = $this->GetY();
12255 } elseif ($y < 0) {
12256 $y = 0;
12257 } elseif ($y > $this->h) {
12258 $y = $this->h;
12260 if ($x == -1) {
12261 $x = $this->GetX();
12262 } elseif ($x < 0) {
12263 $x = 0;
12264 } elseif ($x > $this->w) {
12265 $x = $this->w;
12267 $fixed = false;
12268 if (!empty($page) AND ($page[0] == '*')) {
12269 $page = intval(substr($page, 1));
12270 // this page number will not be changed when moving/add/deleting pages
12271 $fixed = true;
12273 if (empty($page)) {
12274 $page = $this->PageNo();
12275 if (empty($page)) {
12276 return;
12279 $this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12280 return $name;
12284 * Return the Named Destination array.
12285 * @return (array) Named Destination array.
12286 * @public
12287 * @author Nicola Asuni
12288 * @since 5.9.097 (2011-06-23)
12290 public function getDestination() {
12291 return $this->dests;
12295 * Insert Named Destinations.
12296 * @protected
12297 * @author Johannes G\FCntert, Nicola Asuni
12298 * @since 5.9.098 (2011-06-23)
12300 protected function _putdests() {
12301 if (empty($this->dests)) {
12302 return;
12304 $this->n_dests = $this->_newobj();
12305 $out = ' <<';
12306 foreach($this->dests as $name => $o) {
12307 $out .= ' /'.$name.' '.sprintf('[%u 0 R /XYZ %F %F null]', $this->page_obj_id[($o['p'])], ($o['x'] * $this->k), ($this->pagedim[$o['p']]['h'] - ($o['y'] * $this->k)));
12309 $out .= ' >>';
12310 $out .= "\n".'endobj';
12311 $this->_out($out);
12315 * Adds a bookmark - alias for Bookmark().
12316 * @param $txt (string) Bookmark description.
12317 * @param $level (int) Bookmark level (minimum value is 0).
12318 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12319 * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12320 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12321 * @param $color (array) RGB color array (values from 0 to 255).
12322 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12323 * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12324 * @public
12326 public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12327 $this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12331 * Adds a bookmark.
12332 * @param $txt (string) Bookmark description.
12333 * @param $level (int) Bookmark level (minimum value is 0).
12334 * @param $y (float) Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12335 * @param $page (int|string) Target page number (leave empty for current page). If you prefix a page number with the * character, then this page will not be changed when adding/deleting/moving pages.
12336 * @param $style (string) Font style: B = Bold, I = Italic, BI = Bold + Italic.
12337 * @param $color (array) RGB color array (values from 0 to 255).
12338 * @param $x (float) X position in user units of the bookmark on the selected page (default = -1 = current position;).
12339 * @param $link (mixed) URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12340 * @public
12341 * @since 2.1.002 (2008-02-12)
12343 public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12344 if ($level < 0) {
12345 $level = 0;
12347 if (isset($this->outlines[0])) {
12348 $lastoutline = end($this->outlines);
12349 $maxlevel = $lastoutline['l'] + 1;
12350 } else {
12351 $maxlevel = 0;
12353 if ($level > $maxlevel) {
12354 $level = $maxlevel;
12356 if ($y == -1) {
12357 $y = $this->GetY();
12358 } elseif ($y < 0) {
12359 $y = 0;
12360 } elseif ($y > $this->h) {
12361 $y = $this->h;
12363 if ($x == -1) {
12364 $x = $this->GetX();
12365 } elseif ($x < 0) {
12366 $x = 0;
12367 } elseif ($x > $this->w) {
12368 $x = $this->w;
12370 $fixed = false;
12371 if (!empty($page) AND ($page[0] == '*')) {
12372 $page = intval(substr($page, 1));
12373 // this page number will not be changed when moving/add/deleting pages
12374 $fixed = true;
12376 if (empty($page)) {
12377 $page = $this->PageNo();
12378 if (empty($page)) {
12379 return;
12382 $this->outlines[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12386 * Sort bookmarks for page and key.
12387 * @protected
12388 * @since 5.9.119 (2011-09-19)
12390 protected function sortBookmarks() {
12391 // get sorting columns
12392 $outline_p = array();
12393 $outline_y = array();
12394 foreach ($this->outlines as $key => $row) {
12395 $outline_p[$key] = $row['p'];
12396 $outline_k[$key] = $key;
12398 // sort outlines by page and original position
12399 array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
12403 * Create a bookmark PDF string.
12404 * @protected
12405 * @author Olivier Plathey, Nicola Asuni
12406 * @since 2.1.002 (2008-02-12)
12408 protected function _putbookmarks() {
12409 $nb = count($this->outlines);
12410 if ($nb == 0) {
12411 return;
12413 // sort bookmarks
12414 $this->sortBookmarks();
12415 $lru = array();
12416 $level = 0;
12417 foreach ($this->outlines as $i => $o) {
12418 if ($o['l'] > 0) {
12419 $parent = $lru[($o['l'] - 1)];
12420 //Set parent and last pointers
12421 $this->outlines[$i]['parent'] = $parent;
12422 $this->outlines[$parent]['last'] = $i;
12423 if ($o['l'] > $level) {
12424 //Level increasing: set first pointer
12425 $this->outlines[$parent]['first'] = $i;
12427 } else {
12428 $this->outlines[$i]['parent'] = $nb;
12430 if (($o['l'] <= $level) AND ($i > 0)) {
12431 //Set prev and next pointers
12432 $prev = $lru[$o['l']];
12433 $this->outlines[$prev]['next'] = $i;
12434 $this->outlines[$i]['prev'] = $prev;
12436 $lru[$o['l']] = $i;
12437 $level = $o['l'];
12439 //Outline items
12440 $n = $this->n + 1;
12441 $nltags = '/<br[\s]?\/>|<\/(blockquote|dd|dl|div|dt|h1|h2|h3|h4|h5|h6|hr|li|ol|p|pre|ul|tcpdf|table|tr|td)>/si';
12442 foreach ($this->outlines as $i => $o) {
12443 $oid = $this->_newobj();
12444 // covert HTML title to string
12445 $title = preg_replace($nltags, "\n", $o['t']);
12446 $title = preg_replace("/[\r]+/si", '', $title);
12447 $title = preg_replace("/[\n]+/si", "\n", $title);
12448 $title = strip_tags($title);
12449 $title = $this->stringTrim($title);
12450 $out = '<</Title '.$this->_textstring($title, $oid);
12451 $out .= ' /Parent '.($n + $o['parent']).' 0 R';
12452 if (isset($o['prev'])) {
12453 $out .= ' /Prev '.($n + $o['prev']).' 0 R';
12455 if (isset($o['next'])) {
12456 $out .= ' /Next '.($n + $o['next']).' 0 R';
12458 if (isset($o['first'])) {
12459 $out .= ' /First '.($n + $o['first']).' 0 R';
12461 if (isset($o['last'])) {
12462 $out .= ' /Last '.($n + $o['last']).' 0 R';
12464 if (isset($o['u']) AND !empty($o['u'])) {
12465 // link
12466 if (is_string($o['u'])) {
12467 if ($o['u'][0] == '#') {
12468 // internal destination
12469 $out .= ' /Dest /'.TCPDF_STATIC::encodeNameObject(substr($o['u'], 1));
12470 } elseif ($o['u'][0] == '%') {
12471 // embedded PDF file
12472 $filename = basename(substr($o['u'], 1));
12473 $out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles[$filename]['a'].' >> >>';
12474 } elseif ($o['u'][0] == '*') {
12475 // embedded generic file
12476 $filename = basename(substr($o['u'], 1));
12477 $jsa = 'var D=event.target.doc;var MyData=D.dataObjects;for (var i in MyData) if (MyData[i].path=="'.$filename.'") D.exportDataObject( { cName : MyData[i].name, nLaunch : 2});';
12478 $out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12479 } else {
12480 // external URI link
12481 $out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12483 } elseif (isset($this->links[$o['u']])) {
12484 // internal link ID
12485 $l = $this->links[$o['u']];
12486 if (isset($this->page_obj_id[($l['p'])])) {
12487 $out .= sprintf(' /Dest [%u 0 R /XYZ 0 %F null]', $this->page_obj_id[($l['p'])], ($this->pagedim[$l['p']]['h'] - ($l['y'] * $this->k)));
12490 } elseif (isset($this->page_obj_id[($o['p'])])) {
12491 // link to a page
12492 $out .= ' '.sprintf('/Dest [%u 0 R /XYZ %F %F null]', $this->page_obj_id[($o['p'])], ($o['x'] * $this->k), ($this->pagedim[$o['p']]['h'] - ($o['y'] * $this->k)));
12494 // set font style
12495 $style = 0;
12496 if (!empty($o['s'])) {
12497 // bold
12498 if (strpos($o['s'], 'B') !== false) {
12499 $style |= 2;
12501 // oblique
12502 if (strpos($o['s'], 'I') !== false) {
12503 $style |= 1;
12506 $out .= sprintf(' /F %d', $style);
12507 // set bookmark color
12508 if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12509 $color = array_values($o['c']);
12510 $out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12511 } else {
12512 // black
12513 $out .= ' /C [0.0 0.0 0.0]';
12515 $out .= ' /Count 0'; // normally closed item
12516 $out .= ' >>';
12517 $out .= "\n".'endobj';
12518 $this->_out($out);
12520 //Outline root
12521 $this->OutlineRoot = $this->_newobj();
12522 $this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
12525 // --- JAVASCRIPT ------------------------------------------------------
12528 * Adds a javascript
12529 * @param $script (string) Javascript code
12530 * @public
12531 * @author Johannes G\FCntert, Nicola Asuni
12532 * @since 2.1.002 (2008-02-12)
12534 public function IncludeJS($script) {
12535 $this->javascript .= $script;
12539 * Adds a javascript object and return object ID
12540 * @param $script (string) Javascript code
12541 * @param $onload (boolean) if true executes this object when opening the document
12542 * @return int internal object ID
12543 * @public
12544 * @author Nicola Asuni
12545 * @since 4.8.000 (2009-09-07)
12547 public function addJavascriptObject($script, $onload=false) {
12548 if ($this->pdfa_mode) {
12549 // javascript is not allowed in PDF/A mode
12550 return false;
12552 ++$this->n;
12553 $this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12554 return $this->n;
12558 * Create a javascript PDF string.
12559 * @protected
12560 * @author Johannes G\FCntert, Nicola Asuni
12561 * @since 2.1.002 (2008-02-12)
12563 protected function _putjavascript() {
12564 if ($this->pdfa_mode OR (empty($this->javascript) AND empty($this->js_objects))) {
12565 return;
12567 if (strpos($this->javascript, 'this.addField') > 0) {
12568 if (!$this->ur['enabled']) {
12569 //$this->setUserRights();
12571 // the following two lines are used to avoid form fields duplication after saving
12572 // The addField method only works when releasing user rights (UR3)
12573 $jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12574 $jsb = "getField('tcpdfdocsaved').value='saved';";
12575 $this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
12577 // name tree for javascript
12578 $this->n_js = '<< /Names [';
12579 if (!empty($this->javascript)) {
12580 $this->n_js .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
12582 if (!empty($this->js_objects)) {
12583 foreach ($this->js_objects as $key => $val) {
12584 if ($val['onload']) {
12585 $this->n_js .= ' (JS'.$key.') '.$key.' 0 R';
12589 $this->n_js .= ' ] >>';
12590 // default Javascript object
12591 if (!empty($this->javascript)) {
12592 $obj_id = $this->_newobj();
12593 $out = '<< /S /JavaScript';
12594 $out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
12595 $out .= ' >>';
12596 $out .= "\n".'endobj';
12597 $this->_out($out);
12599 // additional Javascript objects
12600 if (!empty($this->js_objects)) {
12601 foreach ($this->js_objects as $key => $val) {
12602 $out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12603 $this->_out($out);
12609 * Adds a javascript form field.
12610 * @param $type (string) field type
12611 * @param $name (string) field name
12612 * @param $x (int) horizontal position
12613 * @param $y (int) vertical position
12614 * @param $w (int) width
12615 * @param $h (int) height
12616 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12617 * @protected
12618 * @author Denis Van Nuffelen, Nicola Asuni
12619 * @since 2.1.002 (2008-02-12)
12621 protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12622 if ($this->rtl) {
12623 $x = $x - $w;
12625 // the followind avoid fields duplication after saving the document
12626 $this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12627 $k = $this->k;
12628 $this->javascript .= sprintf("f".$name."=this.addField('%s','%s',%u,[%F,%F,%F,%F]);", $name, $type, $this->PageNo()-1, $x*$k, ($this->h-$y)*$k+1, ($x+$w)*$k, ($this->h-$y-$h)*$k+1)."\n";
12629 $this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
12630 while (list($key, $val) = each($prop)) {
12631 if (strcmp(substr($key, -5), 'Color') == 0) {
12632 $val = TCPDF_COLORS::_JScolor($val);
12633 } else {
12634 $val = "'".$val."'";
12636 $this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12638 if ($this->rtl) {
12639 $this->x -= $w;
12640 } else {
12641 $this->x += $w;
12643 $this->javascript .= '}';
12646 // --- FORM FIELDS -----------------------------------------------------
12651 * Set default properties for form fields.
12652 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12653 * @public
12654 * @author Nicola Asuni
12655 * @since 4.8.000 (2009-09-06)
12657 public function setFormDefaultProp($prop=array()) {
12658 $this->default_form_prop = $prop;
12662 * Return the default properties for form fields.
12663 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12664 * @public
12665 * @author Nicola Asuni
12666 * @since 4.8.000 (2009-09-06)
12668 public function getFormDefaultProp() {
12669 return $this->default_form_prop;
12673 * Creates a text field
12674 * @param $name (string) field name
12675 * @param $w (float) Width of the rectangle
12676 * @param $h (float) Height of the rectangle
12677 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12678 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12679 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12680 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12681 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12682 * @public
12683 * @author Nicola Asuni
12684 * @since 4.8.000 (2009-09-07)
12686 public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12687 if ($x === '') {
12688 $x = $this->x;
12690 if ($y === '') {
12691 $y = $this->y;
12693 // check page for no-write regions and adapt page margins if necessary
12694 list($x, $y) = $this->checkPageRegions($h, $x, $y);
12695 if ($js) {
12696 $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12697 return;
12699 // get default style
12700 $prop = array_merge($this->getFormDefaultProp(), $prop);
12701 // get annotation data
12702 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12703 // set default appearance stream
12704 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12705 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12706 $popt['da'] = $fontstyle;
12707 // build appearance stream
12708 $popt['ap'] = array();
12709 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12710 $text = '';
12711 if (isset($prop['value']) AND !empty($prop['value'])) {
12712 $text = $prop['value'];
12713 } elseif (isset($opt['v']) AND !empty($opt['v'])) {
12714 $text = $opt['v'];
12716 $tmpid = $this->startTemplate($w, $h, false);
12717 $align = '';
12718 if (isset($popt['q'])) {
12719 switch ($popt['q']) {
12720 case 0: {
12721 $align = 'L';
12722 break;
12724 case 1: {
12725 $align = 'C';
12726 break;
12728 case 2: {
12729 $align = 'R';
12730 break;
12732 default: {
12733 $align = '';
12734 break;
12738 $this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12739 $this->endTemplate();
12740 --$this->n;
12741 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12742 unset($this->xobjects[$tmpid]);
12743 $popt['ap']['n'] .= 'Q EMC';
12744 // merge options
12745 $opt = array_merge($popt, $opt);
12746 // remove some conflicting options
12747 unset($opt['bs']);
12748 // set remaining annotation data
12749 $opt['Subtype'] = 'Widget';
12750 $opt['ft'] = 'Tx';
12751 $opt['t'] = $name;
12752 // Additional annotation's parameters (check _putannotsobj() method):
12753 //$opt['f']
12754 //$opt['as']
12755 //$opt['bs']
12756 //$opt['be']
12757 //$opt['c']
12758 //$opt['border']
12759 //$opt['h']
12760 //$opt['mk'];
12761 //$opt['mk']['r']
12762 //$opt['mk']['bc'];
12763 //$opt['mk']['bg'];
12764 unset($opt['mk']['ca']);
12765 unset($opt['mk']['rc']);
12766 unset($opt['mk']['ac']);
12767 unset($opt['mk']['i']);
12768 unset($opt['mk']['ri']);
12769 unset($opt['mk']['ix']);
12770 unset($opt['mk']['if']);
12771 //$opt['mk']['if']['sw'];
12772 //$opt['mk']['if']['s'];
12773 //$opt['mk']['if']['a'];
12774 //$opt['mk']['if']['fb'];
12775 unset($opt['mk']['tp']);
12776 //$opt['tu']
12777 //$opt['tm']
12778 //$opt['ff']
12779 //$opt['v']
12780 //$opt['dv']
12781 //$opt['a']
12782 //$opt['aa']
12783 //$opt['q']
12784 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12785 if ($this->rtl) {
12786 $this->x -= $w;
12787 } else {
12788 $this->x += $w;
12793 * Creates a RadioButton field.
12794 * @param $name (string) Field name.
12795 * @param $w (int) Width of the radio button.
12796 * @param $prop (array) Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12797 * @param $opt (array) Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12798 * @param $onvalue (string) Value to be returned if selected.
12799 * @param $checked (boolean) Define the initial state.
12800 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12801 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12802 * @param $js (boolean) If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12803 * @public
12804 * @author Nicola Asuni
12805 * @since 4.8.000 (2009-09-07)
12807 public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
12808 if ($x === '') {
12809 $x = $this->x;
12811 if ($y === '') {
12812 $y = $this->y;
12814 // check page for no-write regions and adapt page margins if necessary
12815 list($x, $y) = $this->checkPageRegions($w, $x, $y);
12816 if ($js) {
12817 $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12818 return;
12820 if (TCPDF_STATIC::empty_string($onvalue)) {
12821 $onvalue = 'On';
12823 if ($checked) {
12824 $defval = $onvalue;
12825 } else {
12826 $defval = 'Off';
12828 // set font
12829 $font = 'zapfdingbats';
12830 if ($this->pdfa_mode) {
12831 // all fonts must be embedded
12832 $font = 'pdfa'.$font;
12834 $this->AddFont($font);
12835 $tmpfont = $this->getFontBuffer($font);
12836 // set data for parent group
12837 if (!isset($this->radiobutton_groups[$this->page])) {
12838 $this->radiobutton_groups[$this->page] = array();
12840 if (!isset($this->radiobutton_groups[$this->page][$name])) {
12841 $this->radiobutton_groups[$this->page][$name] = array();
12842 ++$this->n;
12843 $this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
12844 $this->radio_groups[] = $this->n;
12846 $kid = ($this->n + 1);
12847 // save object ID to be added on Kids entry on parent object
12848 $this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
12849 // get default style
12850 $prop = array_merge($this->getFormDefaultProp(), $prop);
12851 $prop['NoToggleToOff'] = 'true';
12852 $prop['Radio'] = 'true';
12853 $prop['borderStyle'] = 'inset';
12854 // get annotation data
12855 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12856 // set additional default options
12857 $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
12858 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
12859 $popt['da'] = $fontstyle;
12860 // build appearance stream
12861 $popt['ap'] = array();
12862 $popt['ap']['n'] = array();
12863 $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k);
12864 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
12865 $popt['ap']['n'][$onvalue] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(108).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
12866 $popt['ap']['n']['Off'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(109).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
12867 if (!isset($popt['mk'])) {
12868 $popt['mk'] = array();
12870 $popt['mk']['ca'] = '(l)';
12871 // merge options
12872 $opt = array_merge($popt, $opt);
12873 // set remaining annotation data
12874 $opt['Subtype'] = 'Widget';
12875 $opt['ft'] = 'Btn';
12876 if ($checked) {
12877 $opt['v'] = array('/'.$onvalue);
12878 $opt['as'] = $onvalue;
12879 } else {
12880 $opt['as'] = 'Off';
12882 // store readonly flag
12883 if (!isset($this->radiobutton_groups[$this->page][$name]['#readonly#'])) {
12884 $this->radiobutton_groups[$this->page][$name]['#readonly#'] = false;
12886 $this->radiobutton_groups[$this->page][$name]['#readonly#'] |= ($opt['f'] & 64);
12887 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
12888 if ($this->rtl) {
12889 $this->x -= $w;
12890 } else {
12891 $this->x += $w;
12896 * Creates a List-box field
12897 * @param $name (string) field name
12898 * @param $w (int) width
12899 * @param $h (int) height
12900 * @param $values (array) array containing the list of values.
12901 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12902 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12903 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12904 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12905 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12906 * @public
12907 * @author Nicola Asuni
12908 * @since 4.8.000 (2009-09-07)
12910 public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12911 if ($x === '') {
12912 $x = $this->x;
12914 if ($y === '') {
12915 $y = $this->y;
12917 // check page for no-write regions and adapt page margins if necessary
12918 list($x, $y) = $this->checkPageRegions($h, $x, $y);
12919 if ($js) {
12920 $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
12921 $s = '';
12922 foreach ($values as $value) {
12923 if (is_array($value)) {
12924 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
12925 } else {
12926 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
12929 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
12930 return;
12932 // get default style
12933 $prop = array_merge($this->getFormDefaultProp(), $prop);
12934 // get annotation data
12935 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
12936 // set additional default values
12937 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
12938 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
12939 $popt['da'] = $fontstyle;
12940 // build appearance stream
12941 $popt['ap'] = array();
12942 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12943 $text = '';
12944 foreach($values as $item) {
12945 if (is_array($item)) {
12946 $text .= $item[1]."\n";
12947 } else {
12948 $text .= $item."\n";
12951 $tmpid = $this->startTemplate($w, $h, false);
12952 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12953 $this->endTemplate();
12954 --$this->n;
12955 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12956 unset($this->xobjects[$tmpid]);
12957 $popt['ap']['n'] .= 'Q EMC';
12958 // merge options
12959 $opt = array_merge($popt, $opt);
12960 // set remaining annotation data
12961 $opt['Subtype'] = 'Widget';
12962 $opt['ft'] = 'Ch';
12963 $opt['t'] = $name;
12964 $opt['opt'] = $values;
12965 unset($opt['mk']['ca']);
12966 unset($opt['mk']['rc']);
12967 unset($opt['mk']['ac']);
12968 unset($opt['mk']['i']);
12969 unset($opt['mk']['ri']);
12970 unset($opt['mk']['ix']);
12971 unset($opt['mk']['if']);
12972 unset($opt['mk']['tp']);
12973 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12974 if ($this->rtl) {
12975 $this->x -= $w;
12976 } else {
12977 $this->x += $w;
12982 * Creates a Combo-box field
12983 * @param $name (string) field name
12984 * @param $w (int) width
12985 * @param $h (int) height
12986 * @param $values (array) array containing the list of values.
12987 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12988 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
12989 * @param $x (float) Abscissa of the upper-left corner of the rectangle
12990 * @param $y (float) Ordinate of the upper-left corner of the rectangle
12991 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12992 * @public
12993 * @author Nicola Asuni
12994 * @since 4.8.000 (2009-09-07)
12996 public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
12997 if ($x === '') {
12998 $x = $this->x;
13000 if ($y === '') {
13001 $y = $this->y;
13003 // check page for no-write regions and adapt page margins if necessary
13004 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13005 if ($js) {
13006 $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
13007 $s = '';
13008 foreach ($values as $value) {
13009 if (is_array($value)) {
13010 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13011 } else {
13012 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13015 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13016 return;
13018 // get default style
13019 $prop = array_merge($this->getFormDefaultProp(), $prop);
13020 $prop['Combo'] = true;
13021 // get annotation data
13022 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13023 // set additional default options
13024 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13025 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13026 $popt['da'] = $fontstyle;
13027 // build appearance stream
13028 $popt['ap'] = array();
13029 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13030 $text = '';
13031 foreach($values as $item) {
13032 if (is_array($item)) {
13033 $text .= $item[1]."\n";
13034 } else {
13035 $text .= $item."\n";
13038 $tmpid = $this->startTemplate($w, $h, false);
13039 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13040 $this->endTemplate();
13041 --$this->n;
13042 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13043 unset($this->xobjects[$tmpid]);
13044 $popt['ap']['n'] .= 'Q EMC';
13045 // merge options
13046 $opt = array_merge($popt, $opt);
13047 // set remaining annotation data
13048 $opt['Subtype'] = 'Widget';
13049 $opt['ft'] = 'Ch';
13050 $opt['t'] = $name;
13051 $opt['opt'] = $values;
13052 unset($opt['mk']['ca']);
13053 unset($opt['mk']['rc']);
13054 unset($opt['mk']['ac']);
13055 unset($opt['mk']['i']);
13056 unset($opt['mk']['ri']);
13057 unset($opt['mk']['ix']);
13058 unset($opt['mk']['if']);
13059 unset($opt['mk']['tp']);
13060 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13061 if ($this->rtl) {
13062 $this->x -= $w;
13063 } else {
13064 $this->x += $w;
13069 * Creates a CheckBox field
13070 * @param $name (string) field name
13071 * @param $w (int) width
13072 * @param $checked (boolean) define the initial state.
13073 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13074 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13075 * @param $onvalue (string) value to be returned if selected.
13076 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13077 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13078 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13079 * @public
13080 * @author Nicola Asuni
13081 * @since 4.8.000 (2009-09-07)
13083 public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
13084 if ($x === '') {
13085 $x = $this->x;
13087 if ($y === '') {
13088 $y = $this->y;
13090 // check page for no-write regions and adapt page margins if necessary
13091 list($x, $y) = $this->checkPageRegions($w, $x, $y);
13092 if ($js) {
13093 $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13094 return;
13096 if (!isset($prop['value'])) {
13097 $prop['value'] = array('Yes');
13099 // get default style
13100 $prop = array_merge($this->getFormDefaultProp(), $prop);
13101 $prop['borderStyle'] = 'inset';
13102 // get annotation data
13103 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13104 // set additional default options
13105 $font = 'zapfdingbats';
13106 if ($this->pdfa_mode) {
13107 // all fonts must be embedded
13108 $font = 'pdfa'.$font;
13110 $this->AddFont($font);
13111 $tmpfont = $this->getFontBuffer($font);
13112 $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
13113 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
13114 $popt['da'] = $fontstyle;
13115 // build appearance stream
13116 $popt['ap'] = array();
13117 $popt['ap']['n'] = array();
13118 $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k);
13119 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt / 1000) / $this->k)) * $this->k);
13120 $popt['ap']['n']['Yes'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(110).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
13121 $popt['ap']['n']['Off'] = sprintf('q %s BT /F%d %F Tf %F %F Td ('.chr(111).') Tj ET Q', $this->TextColor, $tmpfont['i'], $this->FontSizePt, $fx, $fy);
13122 // merge options
13123 $opt = array_merge($popt, $opt);
13124 // set remaining annotation data
13125 $opt['Subtype'] = 'Widget';
13126 $opt['ft'] = 'Btn';
13127 $opt['t'] = $name;
13128 if (TCPDF_STATIC::empty_string($onvalue)) {
13129 $onvalue = 'Yes';
13131 $opt['opt'] = array($onvalue);
13132 if ($checked) {
13133 $opt['v'] = array('/Yes');
13134 $opt['as'] = 'Yes';
13135 } else {
13136 $opt['v'] = array('/Off');
13137 $opt['as'] = 'Off';
13139 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13140 if ($this->rtl) {
13141 $this->x -= $w;
13142 } else {
13143 $this->x += $w;
13148 * Creates a button field
13149 * @param $name (string) field name
13150 * @param $w (int) width
13151 * @param $h (int) height
13152 * @param $caption (string) caption.
13153 * @param $action (mixed) action triggered by pressing the button. Use a string to specify a javascript action. Use an array to specify a form action options as on section 12.7.5 of PDF32000_2008.
13154 * @param $prop (array) javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13155 * @param $opt (array) annotation parameters. Possible values are described on official PDF32000_2008 reference.
13156 * @param $x (float) Abscissa of the upper-left corner of the rectangle
13157 * @param $y (float) Ordinate of the upper-left corner of the rectangle
13158 * @param $js (boolean) if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13159 * @public
13160 * @author Nicola Asuni
13161 * @since 4.8.000 (2009-09-07)
13163 public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
13164 if ($x === '') {
13165 $x = $this->x;
13167 if ($y === '') {
13168 $y = $this->y;
13170 // check page for no-write regions and adapt page margins if necessary
13171 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13172 if ($js) {
13173 $this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
13174 $this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13175 $this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13176 $this->javascript .= 'f'.$name.".highlight='push';\n";
13177 $this->javascript .= 'f'.$name.".print=false;\n";
13178 return;
13180 // get default style
13181 $prop = array_merge($this->getFormDefaultProp(), $prop);
13182 $prop['Pushbutton'] = 'true';
13183 $prop['highlight'] = 'push';
13184 $prop['display'] = 'display.noPrint';
13185 // get annotation data
13186 $popt = TCPDF_STATIC::getAnnotOptFromJSProp($prop, $this->spot_colors, $this->rtl);
13187 $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
13188 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
13189 $popt['da'] = $fontstyle;
13190 // build appearance stream
13191 $popt['ap'] = array();
13192 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13193 $tmpid = $this->startTemplate($w, $h, false);
13194 $bw = (2 / $this->k); // border width
13195 $border = array(
13196 'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13197 'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13198 'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13199 'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13200 $this->SetFillColor(204);
13201 $this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13202 $this->endTemplate();
13203 --$this->n;
13204 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13205 unset($this->xobjects[$tmpid]);
13206 $popt['ap']['n'] .= 'Q EMC';
13207 // set additional default options
13208 if (!isset($popt['mk'])) {
13209 $popt['mk'] = array();
13211 $ann_obj_id = ($this->n + 1);
13212 if (!empty($action) AND !is_array($action)) {
13213 $ann_obj_id = ($this->n + 2);
13215 $popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13216 $popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13217 $popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13218 // merge options
13219 $opt = array_merge($popt, $opt);
13220 // set remaining annotation data
13221 $opt['Subtype'] = 'Widget';
13222 $opt['ft'] = 'Btn';
13223 $opt['t'] = $caption;
13224 $opt['v'] = $name;
13225 if (!empty($action)) {
13226 if (is_array($action)) {
13227 // form action options as on section 12.7.5 of PDF32000_2008.
13228 $opt['aa'] = '/D <<';
13229 $bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13230 foreach ($action AS $key => $val) {
13231 if (($key == 'S') AND in_array($val, $bmode)) {
13232 $opt['aa'] .= ' /S /'.$val;
13233 } elseif (($key == 'F') AND (!empty($val))) {
13234 $opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13235 } elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13236 $opt['aa'] .= ' /Fields [';
13237 foreach ($val AS $field) {
13238 $opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13240 $opt['aa'] .= ']';
13241 } elseif (($key == 'Flags')) {
13242 $ff = 0;
13243 if (is_array($val)) {
13244 foreach ($val AS $flag) {
13245 switch ($flag) {
13246 case 'Include/Exclude': {
13247 $ff += 1 << 0;
13248 break;
13250 case 'IncludeNoValueFields': {
13251 $ff += 1 << 1;
13252 break;
13254 case 'ExportFormat': {
13255 $ff += 1 << 2;
13256 break;
13258 case 'GetMethod': {
13259 $ff += 1 << 3;
13260 break;
13262 case 'SubmitCoordinates': {
13263 $ff += 1 << 4;
13264 break;
13266 case 'XFDF': {
13267 $ff += 1 << 5;
13268 break;
13270 case 'IncludeAppendSaves': {
13271 $ff += 1 << 6;
13272 break;
13274 case 'IncludeAnnotations': {
13275 $ff += 1 << 7;
13276 break;
13278 case 'SubmitPDF': {
13279 $ff += 1 << 8;
13280 break;
13282 case 'CanonicalFormat': {
13283 $ff += 1 << 9;
13284 break;
13286 case 'ExclNonUserAnnots': {
13287 $ff += 1 << 10;
13288 break;
13290 case 'ExclFKey': {
13291 $ff += 1 << 11;
13292 break;
13294 case 'EmbedForm': {
13295 $ff += 1 << 13;
13296 break;
13300 } else {
13301 $ff = intval($val);
13303 $opt['aa'] .= ' /Flags '.$ff;
13306 $opt['aa'] .= ' >>';
13307 } else {
13308 // Javascript action or raw action command
13309 $js_obj_id = $this->addJavascriptObject($action);
13310 $opt['aa'] = '/D '.$js_obj_id.' 0 R';
13313 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13314 if ($this->rtl) {
13315 $this->x -= $w;
13316 } else {
13317 $this->x += $w;
13321 // --- END FORMS FIELDS ------------------------------------------------
13324 * Add certification signature (DocMDP or UR3)
13325 * You can set only one signature type
13326 * @protected
13327 * @author Nicola Asuni
13328 * @since 4.6.008 (2009-05-07)
13330 protected function _putsignature() {
13331 if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
13332 return;
13334 $sigobjid = ($this->sig_obj_id + 1);
13335 $out = $this->_getobj($sigobjid)."\n";
13336 $out .= '<< /Type /Sig';
13337 $out .= ' /Filter /Adobe.PPKLite';
13338 $out .= ' /SubFilter /adbe.pkcs7.detached';
13339 $out .= ' '.TCPDF_STATIC::$byterange_string;
13340 $out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
13341 $out .= ' /Reference ['; // array of signature reference dictionaries
13342 $out .= ' << /Type /SigRef';
13343 if ($this->signature_data['cert_type'] > 0) {
13344 $out .= ' /TransformMethod /DocMDP';
13345 $out .= ' /TransformParams <<';
13346 $out .= ' /Type /TransformParams';
13347 $out .= ' /P '.$this->signature_data['cert_type'];
13348 $out .= ' /V /1.2';
13349 } else {
13350 $out .= ' /TransformMethod /UR3';
13351 $out .= ' /TransformParams <<';
13352 $out .= ' /Type /TransformParams';
13353 $out .= ' /V /2.2';
13354 if (!TCPDF_STATIC::empty_string($this->ur['document'])) {
13355 $out .= ' /Document['.$this->ur['document'].']';
13357 if (!TCPDF_STATIC::empty_string($this->ur['form'])) {
13358 $out .= ' /Form['.$this->ur['form'].']';
13360 if (!TCPDF_STATIC::empty_string($this->ur['signature'])) {
13361 $out .= ' /Signature['.$this->ur['signature'].']';
13363 if (!TCPDF_STATIC::empty_string($this->ur['annots'])) {
13364 $out .= ' /Annots['.$this->ur['annots'].']';
13366 if (!TCPDF_STATIC::empty_string($this->ur['ef'])) {
13367 $out .= ' /EF['.$this->ur['ef'].']';
13369 if (!TCPDF_STATIC::empty_string($this->ur['formex'])) {
13370 $out .= ' /FormEX['.$this->ur['formex'].']';
13373 $out .= ' >>'; // close TransformParams
13374 // optional digest data (values must be calculated and replaced later)
13375 //$out .= ' /Data ********** 0 R';
13376 //$out .= ' /DigestMethod/MD5';
13377 //$out .= ' /DigestLocation[********** 34]';
13378 //$out .= ' /DigestValue<********************************>';
13379 $out .= ' >>';
13380 $out .= ' ]'; // end of reference
13381 if (isset($this->signature_data['info']['Name']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Name'])) {
13382 $out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name'], $sigobjid);
13384 if (isset($this->signature_data['info']['Location']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Location'])) {
13385 $out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location'], $sigobjid);
13387 if (isset($this->signature_data['info']['Reason']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['Reason'])) {
13388 $out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason'], $sigobjid);
13390 if (isset($this->signature_data['info']['ContactInfo']) AND !TCPDF_STATIC::empty_string($this->signature_data['info']['ContactInfo'])) {
13391 $out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo'], $sigobjid);
13393 $out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp);
13394 $out .= ' >>';
13395 $out .= "\n".'endobj';
13396 $this->_out($out);
13400 * Set User's Rights for PDF Reader
13401 * WARNING: This is experimental and currently do not work.
13402 * Check the PDF Reference 8.7.1 Transform Methods,
13403 * Table 8.105 Entries in the UR transform parameters dictionary
13404 * @param $enable (boolean) if true enable user's rights on PDF reader
13405 * @param $document (string) Names specifying additional document-wide usage rights for the document. The only defined value is "/FullSave", which permits a user to save the document along with modified form and/or annotation data.
13406 * @param $annots (string) Names specifying additional annotation-related usage rights for the document. Valid names in PDF 1.5 and later are /Create/Delete/Modify/Copy/Import/Export, which permit the user to perform the named operation on annotations.
13407 * @param $form (string) Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13408 * @param $signature (string) Names specifying additional signature-related usage rights for the document. The only defined value is /Modify, which permits a user to apply a digital signature to an existing signature form field or clear a signed signature form field.
13409 * @param $ef (string) Names specifying additional usage rights for named embedded files in the document. Valid names are /Create/Delete/Modify/Import, which permit the user to perform the named operation on named embedded files
13410 Names specifying additional embedded-files-related usage rights for the document.
13411 * @param $formex (string) Names specifying additional form-field-related usage rights. The only valid name is BarcodePlaintext, which permits text form field data to be encoded as a plaintext two-dimensional barcode.
13412 * @public
13413 * @author Nicola Asuni
13414 * @since 2.9.000 (2008-03-26)
13416 public function setUserRights(
13417 $enable=true,
13418 $document='/FullSave',
13419 $annots='/Create/Delete/Modify/Copy/Import/Export',
13420 $form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13421 $signature='/Modify',
13422 $ef='/Create/Delete/Modify/Import',
13423 $formex='') {
13424 $this->ur['enabled'] = $enable;
13425 $this->ur['document'] = $document;
13426 $this->ur['annots'] = $annots;
13427 $this->ur['form'] = $form;
13428 $this->ur['signature'] = $signature;
13429 $this->ur['ef'] = $ef;
13430 $this->ur['formex'] = $formex;
13431 if (!$this->sign) {
13432 $this->setSignature('', '', '', '', 0, array());
13437 * Enable document signature (requires the OpenSSL Library).
13438 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13439 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13440 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13441 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13442 * @param $signing_cert (mixed) signing certificate (string or filename prefixed with 'file://')
13443 * @param $private_key (mixed) private key (string or filename prefixed with 'file://')
13444 * @param $private_key_password (string) password
13445 * @param $extracerts (string) specifies the name of a file containing a bunch of extra certificates to include in the signature which can for example be used to help the recipient to verify the certificate that you used.
13446 * @param $cert_type (int) The access permissions granted for this document. Valid values shall be: 1 = No changes to the document shall be permitted; any change to the document shall invalidate the signature; 2 = Permitted changes shall be filling in forms, instantiating page templates, and signing; other changes shall invalidate the signature; 3 = Permitted changes shall be the same as for 2, as well as annotation creation, deletion, and modification; other changes shall invalidate the signature.
13447 * @param $info (array) array of option information: Name, Location, Reason, ContactInfo.
13448 * @public
13449 * @author Nicola Asuni
13450 * @since 4.6.005 (2009-04-24)
13452 public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array()) {
13453 // to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13454 // to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13455 // to convert pfx certificate to pem: openssl
13456 // OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13457 $this->sign = true;
13458 ++$this->n;
13459 $this->sig_obj_id = $this->n; // signature widget
13460 ++$this->n; // signature object ($this->sig_obj_id + 1)
13461 $this->signature_data = array();
13462 if (strlen($signing_cert) == 0) {
13463 $this->Error('Please provide a certificate file and password!');
13465 if (strlen($private_key) == 0) {
13466 $private_key = $signing_cert;
13468 $this->signature_data['signcert'] = $signing_cert;
13469 $this->signature_data['privkey'] = $private_key;
13470 $this->signature_data['password'] = $private_key_password;
13471 $this->signature_data['extracerts'] = $extracerts;
13472 $this->signature_data['cert_type'] = $cert_type;
13473 $this->signature_data['info'] = $info;
13477 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13478 * @param $x (float) Abscissa of the upper-left corner.
13479 * @param $y (float) Ordinate of the upper-left corner.
13480 * @param $w (float) Width of the signature area.
13481 * @param $h (float) Height of the signature area.
13482 * @param $page (int) option page number (if < 0 the current page is used).
13483 * @param $name (string) Name of the signature.
13484 * @public
13485 * @author Nicola Asuni
13486 * @since 5.3.011 (2010-06-17)
13488 public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13489 $this->signature_appearance = $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13493 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13494 * @param $x (float) Abscissa of the upper-left corner.
13495 * @param $y (float) Ordinate of the upper-left corner.
13496 * @param $w (float) Width of the signature area.
13497 * @param $h (float) Height of the signature area.
13498 * @param $page (int) option page number (if < 0 the current page is used).
13499 * @param $name (string) Name of the signature.
13500 * @public
13501 * @author Nicola Asuni
13502 * @since 5.9.101 (2011-07-06)
13504 public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13505 ++$this->n;
13506 $this->empty_signature_appearance[] = array('objid' => $this->n) + $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13510 * Get the array that defines the signature appearance (page and rectangle coordinates).
13511 * @param $x (float) Abscissa of the upper-left corner.
13512 * @param $y (float) Ordinate of the upper-left corner.
13513 * @param $w (float) Width of the signature area.
13514 * @param $h (float) Height of the signature area.
13515 * @param $page (int) option page number (if < 0 the current page is used).
13516 * @param $name (string) Name of the signature.
13517 * @return (array) Array defining page and rectangle coordinates of signature appearance.
13518 * @protected
13519 * @author Nicola Asuni
13520 * @since 5.9.101 (2011-07-06)
13522 protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13523 $sigapp = array();
13524 if (($page < 1) OR ($page > $this->numpages)) {
13525 $sigapp['page'] = $this->page;
13526 } else {
13527 $sigapp['page'] = intval($page);
13529 if (empty($name)) {
13530 $sigapp['name'] = 'Signature';
13531 } else {
13532 $sigapp['name'] = $name;
13534 $a = $x * $this->k;
13535 $b = $this->pagedim[($sigapp['page'])]['h'] - (($y + $h) * $this->k);
13536 $c = $w * $this->k;
13537 $d = $h * $this->k;
13538 $sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a + $c), ($b + $d));
13539 return $sigapp;
13543 * Create a new page group.
13544 * NOTE: call this function before calling AddPage()
13545 * @param $page (int) starting group page (leave empty for next page).
13546 * @public
13547 * @since 3.0.000 (2008-03-27)
13549 public function startPageGroup($page='') {
13550 if (empty($page)) {
13551 $page = $this->page + 1;
13553 $this->newpagegroup[$page] = sizeof($this->newpagegroup) + 1;
13557 * Set the starting page number.
13558 * @param $num (int) Starting page number.
13559 * @since 5.9.093 (2011-06-16)
13560 * @public
13562 public function setStartingPageNumber($num=1) {
13563 $this->starting_page_number = max(0, intval($num));
13567 * Returns the string alias used right align page numbers.
13568 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13569 * @return string
13570 * @since 5.9.099 (2011-06-27)
13571 * @public
13573 public function getAliasRightShift() {
13574 // calculate aproximatively the ratio between widths of aliases and replacements.
13575 $ref = '{'.TCPDF_STATIC::$alias_right_shift.'}{'.TCPDF_STATIC::$alias_tot_pages.'}{'.TCPDF_STATIC::$alias_num_page.'}';
13576 $rep = str_repeat(' ', $this->GetNumChars($ref));
13577 $wrep = $this->GetStringWidth($rep);
13578 if ($wrep > 0) {
13579 $wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13580 } else {
13581 $wdiff = 1;
13583 $sdiff = sprintf('%F', $wdiff);
13584 $alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13585 if ($this->isUnicodeFont()) {
13586 $alias = '{'.$alias;
13588 return $alias;
13592 * Returns the string alias used for the total number of pages.
13593 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13594 * This alias will be replaced by the total number of pages in the document.
13595 * @return string
13596 * @since 4.0.018 (2008-08-08)
13597 * @public
13599 public function getAliasNbPages() {
13600 if ($this->isUnicodeFont()) {
13601 return '{'.TCPDF_STATIC::$alias_tot_pages.'}';
13603 return TCPDF_STATIC::$alias_tot_pages;
13607 * Returns the string alias used for the page number.
13608 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13609 * This alias will be replaced by the page number.
13610 * @return string
13611 * @since 4.5.000 (2009-01-02)
13612 * @public
13614 public function getAliasNumPage() {
13615 if ($this->isUnicodeFont()) {
13616 return '{'.TCPDF_STATIC::$alias_num_page.'}';
13618 return TCPDF_STATIC::$alias_num_page;
13622 * Return the alias for the total number of pages in the current page group.
13623 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13624 * This alias will be replaced by the total number of pages in this group.
13625 * @return alias of the current page group
13626 * @public
13627 * @since 3.0.000 (2008-03-27)
13629 public function getPageGroupAlias() {
13630 if ($this->isUnicodeFont()) {
13631 return '{'.TCPDF_STATIC::$alias_group_tot_pages.'}';
13633 return TCPDF_STATIC::$alias_group_tot_pages;
13637 * Return the alias for the page number on the current page group.
13638 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13639 * This alias will be replaced by the page number (relative to the belonging group).
13640 * @return alias of the current page group
13641 * @public
13642 * @since 4.5.000 (2009-01-02)
13644 public function getPageNumGroupAlias() {
13645 if ($this->isUnicodeFont()) {
13646 return '{'.TCPDF_STATIC::$alias_group_num_page.'}';
13648 return TCPDF_STATIC::$alias_group_num_page;
13652 * Return the current page in the group.
13653 * @return current page in the group
13654 * @public
13655 * @since 3.0.000 (2008-03-27)
13657 public function getGroupPageNo() {
13658 return $this->pagegroups[$this->currpagegroup];
13662 * Returns the current group page number formatted as a string.
13663 * @public
13664 * @since 4.3.003 (2008-11-18)
13665 * @see PaneNo(), formatPageNumber()
13667 public function getGroupPageNoFormatted() {
13668 return TCPDF_STATIC::formatPageNumber($this->getGroupPageNo());
13672 * Returns the current page number formatted as a string.
13673 * @public
13674 * @since 4.2.005 (2008-11-06)
13675 * @see PaneNo(), formatPageNumber()
13677 public function PageNoFormatted() {
13678 return TCPDF_STATIC::formatPageNumber($this->PageNo());
13682 * Put pdf layers.
13683 * @protected
13684 * @since 3.0.000 (2008-03-27)
13686 protected function _putocg() {
13687 if (empty($this->pdflayers)) {
13688 return;
13690 foreach ($this->pdflayers as $key => $layer) {
13691 $this->pdflayers[$key]['objid'] = $this->_newobj();
13692 $out = '<< /Type /OCG';
13693 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers[$key]['objid']);
13694 $out .= ' /Usage <<';
13695 if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13696 $out .= ' /Print <</PrintState /'.($layer['print']?'ON':'OFF').'>>';
13698 $out .= ' /View <</ViewState /'.($layer['view']?'ON':'OFF').'>>';
13699 $out .= ' >> >>';
13700 $out .= "\n".'endobj';
13701 $this->_out($out);
13706 * Start a new pdf layer.
13707 * @param $name (string) Layer name (only a-z letters and numbers). Leave empty for automatic name.
13708 * @param $print (boolean|null) Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13709 * @param $view (boolean) Set to true to view this layer.
13710 * @param $lock (boolean) If true lock the layer
13711 * @public
13712 * @since 5.9.102 (2011-07-13)
13714 public function startLayer($name='', $print=true, $view=true, $lock=true) {
13715 if ($this->state != 2) {
13716 return;
13718 $layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13719 if (empty($name)) {
13720 $name = $layer;
13721 } else {
13722 $name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13724 $this->pdflayers[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13725 $this->openMarkedContent = true;
13726 $this->_out('/OC /'.$layer.' BDC');
13730 * End the current PDF layer.
13731 * @public
13732 * @since 5.9.102 (2011-07-13)
13734 public function endLayer() {
13735 if ($this->state != 2) {
13736 return;
13738 if ($this->openMarkedContent) {
13739 // close existing open marked-content layer
13740 $this->_out('EMC');
13741 $this->openMarkedContent = false;
13746 * Set the visibility of the successive elements.
13747 * This can be useful, for instance, to put a background
13748 * image or color that will show on screen but won't print.
13749 * @param $v (string) visibility mode. Legal values are: all, print, screen or view.
13750 * @public
13751 * @since 3.0.000 (2008-03-27)
13753 public function setVisibility($v) {
13754 if ($this->state != 2) {
13755 return;
13757 $this->endLayer();
13758 switch($v) {
13759 case 'print': {
13760 $this->startLayer('Print', true, false);
13761 break;
13763 case 'view':
13764 case 'screen': {
13765 $this->startLayer('View', false, true);
13766 break;
13768 case 'all': {
13769 $this->_out('');
13770 break;
13772 default: {
13773 $this->Error('Incorrect visibility: '.$v);
13774 break;
13780 * Add transparency parameters to the current extgstate
13781 * @param $parms (array) parameters
13782 * @return the number of extgstates
13783 * @protected
13784 * @since 3.0.000 (2008-03-27)
13786 protected function addExtGState($parms) {
13787 if ($this->pdfa_mode) {
13788 // transparencies are not allowed in PDF/A mode
13789 return;
13791 // check if this ExtGState already exist
13792 foreach ($this->extgstates as $i => $ext) {
13793 if ($ext['parms'] == $parms) {
13794 if ($this->inxobj) {
13795 // we are inside an XObject template
13796 $this->xobjects[$this->xobjid]['extgstates'][$i] = $ext;
13798 // return reference to existing ExtGState
13799 return $i;
13802 $n = (count($this->extgstates) + 1);
13803 $this->extgstates[$n] = array('parms' => $parms);
13804 if ($this->inxobj) {
13805 // we are inside an XObject template
13806 $this->xobjects[$this->xobjid]['extgstates'][$n] = $this->extgstates[$n];
13808 return $n;
13812 * Add an extgstate
13813 * @param $gs (array) extgstate
13814 * @protected
13815 * @since 3.0.000 (2008-03-27)
13817 protected function setExtGState($gs) {
13818 if ($this->pdfa_mode OR ($this->state != 2)) {
13819 // transparency is not allowed in PDF/A mode
13820 return;
13822 $this->_out(sprintf('/GS%d gs', $gs));
13826 * Put extgstates for object transparency
13827 * @protected
13828 * @since 3.0.000 (2008-03-27)
13830 protected function _putextgstates() {
13831 foreach ($this->extgstates as $i => $ext) {
13832 $this->extgstates[$i]['n'] = $this->_newobj();
13833 $out = '<< /Type /ExtGState';
13834 foreach ($ext['parms'] as $k => $v) {
13835 if (is_float($v)) {
13836 $v = sprintf('%F', $v);
13837 } elseif ($v === true) {
13838 $v = 'true';
13839 } elseif ($v === false) {
13840 $v = 'false';
13842 $out .= ' /'.$k.' '.$v;
13844 $out .= ' >>';
13845 $out .= "\n".'endobj';
13846 $this->_out($out);
13851 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
13852 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13853 * @param $stroking (boolean) If true apply overprint for stroking operations.
13854 * @param $nonstroking (boolean) If true apply overprint for painting operations other than stroking.
13855 * @param $mode (integer) Overprint mode: (0 = each source colour component value replaces the value previously painted for the corresponding device colorant; 1 = a tint value of 0.0 for a source colour component shall leave the corresponding component of the previously painted colour unchanged).
13856 * @public
13857 * @since 5.9.152 (2012-03-23)
13859 public function setOverprint($stroking=true, $nonstroking='', $mode=0) {
13860 if ($this->state != 2) {
13861 return;
13863 $stroking = $stroking ? true : false;
13864 if (TCPDF_STATIC::empty_string($nonstroking)) {
13865 // default value if not set
13866 $nonstroking = $stroking;
13867 } else {
13868 $nonstroking = $nonstroking ? true : false;
13870 if (($mode != 0) AND ($mode != 1)) {
13871 $mode = 0;
13873 $this->overprint = array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
13874 $gs = $this->addExtGState($this->overprint);
13875 $this->setExtGState($gs);
13879 * Get the overprint mode array (OP, op, OPM).
13880 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13881 * @return array.
13882 * @public
13883 * @since 5.9.152 (2012-03-23)
13885 public function getOverprint() {
13886 return $this->overprint;
13890 * Set alpha for stroking (CA) and non-stroking (ca) operations.
13891 * @param $stroking (float) Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
13892 * @param $bm (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity
13893 * @param $nonstroking (float) Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
13894 * @param $ais (boolean)
13895 * @public
13896 * @since 3.0.000 (2008-03-27)
13898 public function setAlpha($stroking=1, $bm='Normal', $nonstroking='', $ais=false) {
13899 if ($this->pdfa_mode) {
13900 // transparency is not allowed in PDF/A mode
13901 return;
13903 $stroking = floatval($stroking);
13904 if (TCPDF_STATIC::empty_string($nonstroking)) {
13905 // default value if not set
13906 $nonstroking = $stroking;
13907 } else {
13908 $nonstroking = floatval($nonstroking);
13910 if ($bm[0] == '/') {
13911 // remove trailing slash
13912 $bm = substr($bm, 1);
13914 if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
13915 $bm = 'Normal';
13917 $ais = $ais ? true : false;
13918 $this->alpha = array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
13919 $gs = $this->addExtGState($this->alpha);
13920 $this->setExtGState($gs);
13924 * Get the alpha mode array (CA, ca, BM, AIS).
13925 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13926 * @return array.
13927 * @public
13928 * @since 5.9.152 (2012-03-23)
13930 public function getAlpha() {
13931 return $this->alpha;
13935 * Set the default JPEG compression quality (1-100)
13936 * @param $quality (int) JPEG quality, integer between 1 and 100
13937 * @public
13938 * @since 3.0.000 (2008-03-27)
13940 public function setJPEGQuality($quality) {
13941 if (($quality < 1) OR ($quality > 100)) {
13942 $quality = 75;
13944 $this->jpeg_quality = intval($quality);
13948 * Set the default number of columns in a row for HTML tables.
13949 * @param $cols (int) number of columns
13950 * @public
13951 * @since 3.0.014 (2008-06-04)
13953 public function setDefaultTableColumns($cols=4) {
13954 $this->default_table_columns = intval($cols);
13958 * Set the height of the cell (line height) respect the font height.
13959 * @param $h (int) cell proportion respect font height (typical value = 1.25).
13960 * @public
13961 * @since 3.0.014 (2008-06-04)
13963 public function setCellHeightRatio($h) {
13964 $this->cell_height_ratio = $h;
13968 * return the height of cell repect font height.
13969 * @public
13970 * @since 4.0.012 (2008-07-24)
13972 public function getCellHeightRatio() {
13973 return $this->cell_height_ratio;
13977 * Set the PDF version (check PDF reference for valid values).
13978 * @param $version (string) PDF document version.
13979 * @public
13980 * @since 3.1.000 (2008-06-09)
13982 public function setPDFVersion($version='1.7') {
13983 if ($this->pdfa_mode) {
13984 // PDF/A mode
13985 $this->PDFVersion = '1.4';
13986 } else {
13987 $this->PDFVersion = $version;
13992 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
13993 * (see Section 8.1 of PDF reference, "Viewer Preferences").
13994 * <ul><li>HideToolbar boolean (Optional) A flag specifying whether to hide the viewer application's tool bars when the document is active. Default value: false.</li><li>HideMenubar boolean (Optional) A flag specifying whether to hide the viewer application's menu bar when the document is active. Default value: false.</li><li>HideWindowUI boolean (Optional) A flag specifying whether to hide user interface elements in the document's window (such as scroll bars and navigation controls), leaving only the document's contents displayed. Default value: false.</li><li>FitWindow boolean (Optional) A flag specifying whether to resize the document's window to fit the size of the first displayed page. Default value: false.</li><li>CenterWindow boolean (Optional) A flag specifying whether to position the document's window in the center of the screen. Default value: false.</li><li>DisplayDocTitle boolean (Optional; PDF 1.4) A flag specifying whether the window's title bar should display the document title taken from the Title entry of the document information dictionary (see Section 10.2.1, "Document Information Dictionary"). If false, the title bar should instead display the name of the PDF file containing the document. Default value: false.</li><li>NonFullScreenPageMode name (Optional) The document's page mode, specifying how to display the document on exiting full-screen mode:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>UseOC Optional content group panel visible</li></ul>This entry is meaningful only if the value of the PageMode entry in the catalog dictionary (see Section 3.6.1, "Document Catalog") is FullScreen; it is ignored otherwise. Default value: UseNone.</li><li>ViewArea name (Optional; PDF 1.4) The name of the page boundary representing the area of a page to be displayed when viewing the document on the screen. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>ViewClip name (Optional; PDF 1.4) The name of the page boundary to which the contents of a page are to be clipped when viewing the document on the screen. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>PrintArea name (Optional; PDF 1.4) The name of the page boundary representing the area of a page to be rendered when printing the document. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>PrintClip name (Optional; PDF 1.4) The name of the page boundary to which the contents of a page are to be clipped when printing the document. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>PrintScaling name (Optional; PDF 1.6) The page scaling option to be selected when a print dialog is displayed for this document. Valid values are: <ul><li>None, which indicates that the print dialog should reflect no page scaling</li><li>AppDefault (default), which indicates that applications should use the current print scaling</li></ul></li><li>Duplex name (Optional; PDF 1.7) The paper handling option to use when printing the file from the print dialog. The following values are valid:<ul><li>Simplex - Print single-sided</li><li>DuplexFlipShortEdge - Duplex and flip on the short edge of the sheet</li><li>DuplexFlipLongEdge - Duplex and flip on the long edge of the sheet</li></ul>Default value: none</li><li>PickTrayByPDFSize boolean (Optional; PDF 1.7) A flag specifying whether the PDF page size is used to select the input paper tray. This setting influences only the preset values used to populate the print dialog presented by a PDF viewer application. If PickTrayByPDFSize is true, the check box in the print dialog associated with input paper tray is checked. Note: This setting has no effect on Mac OS systems, which do not provide the ability to pick the input tray by size.</li><li>PrintPageRange array (Optional; PDF 1.7) The page numbers used to initialize the print dialog box when the file is printed. The first page of the PDF file is denoted by 1. Each pair consists of the first and last pages in the sub-range. An odd number of integers causes this entry to be ignored. Negative numbers cause the entire array to be ignored. Default value: as defined by PDF viewer application</li><li>NumCopies integer (Optional; PDF 1.7) The number of copies to be printed when the print dialog is opened for this file. Supported values are the integers 2 through 5. Values outside this range are ignored. Default value: as defined by PDF viewer application, but typically 1</li></ul>
13995 * @param $preferences (array) array of options.
13996 * @author Nicola Asuni
13997 * @public
13998 * @since 3.1.000 (2008-06-09)
14000 public function setViewerPreferences($preferences) {
14001 $this->viewer_preferences = $preferences;
14005 * Paints color transition registration bars
14006 * @param $x (float) abscissa of the top left corner of the rectangle.
14007 * @param $y (float) ordinate of the top left corner of the rectangle.
14008 * @param $w (float) width of the rectangle.
14009 * @param $h (float) height of the rectangle.
14010 * @param $transition (boolean) if true prints tcolor transitions to white.
14011 * @param $vertical (boolean) if true prints bar vertically.
14012 * @param $colors (string) colors to print separated by comma. Valid values are: A,W,R,G,B,C,M,Y,K,RGB,CMYK,ALL,ALLSPOT,<SPOT_COLOR_NAME>. Where: A = grayscale black, W = grayscale white, R = RGB red, G RGB green, B RGB blue, C = CMYK cyan, M = CMYK magenta, Y = CMYK yellow, K = CMYK key/black, RGB = RGB registration color, CMYK = CMYK registration color, ALL = Spot registration color, ALLSPOT = print all defined spot colors, <SPOT_COLOR_NAME> = name of the spot color to print.
14013 * @author Nicola Asuni
14014 * @since 4.9.000 (2010-03-26)
14015 * @public
14017 public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14018 if (strpos($colors, 'ALLSPOT') !== false) {
14019 // expand spot colors
14020 $spot_colors = '';
14021 foreach ($this->spot_colors as $spot_color_name => $v) {
14022 $spot_colors .= ','.$spot_color_name;
14024 if (!empty($spot_colors)) {
14025 $spot_colors = substr($spot_colors, 1);
14026 $colors = str_replace('ALLSPOT', $spot_colors, $colors);
14027 } else {
14028 $colors = str_replace('ALLSPOT', 'NONE', $colors);
14031 $bars = explode(',', $colors);
14032 $numbars = count($bars); // number of bars to print
14033 if ($numbars <= 0) {
14034 return;
14036 // set bar measures
14037 if ($vertical) {
14038 $coords = array(0, 0, 0, 1);
14039 $wb = $w / $numbars; // bar width
14040 $hb = $h; // bar height
14041 $xd = $wb; // delta x
14042 $yd = 0; // delta y
14043 } else {
14044 $coords = array(1, 0, 0, 0);
14045 $wb = $w; // bar width
14046 $hb = $h / $numbars; // bar height
14047 $xd = 0; // delta x
14048 $yd = $hb; // delta y
14050 $xb = $x;
14051 $yb = $y;
14052 foreach ($bars as $col) {
14053 switch ($col) {
14054 // set transition colors
14055 case 'A': { // BLACK (GRAYSCALE)
14056 $col_a = array(255);
14057 $col_b = array(0);
14058 break;
14060 case 'W': { // WHITE (GRAYSCALE)
14061 $col_a = array(0);
14062 $col_b = array(255);
14063 break;
14065 case 'R': { // RED (RGB)
14066 $col_a = array(255,255,255);
14067 $col_b = array(255,0,0);
14068 break;
14070 case 'G': { // GREEN (RGB)
14071 $col_a = array(255,255,255);
14072 $col_b = array(0,255,0);
14073 break;
14075 case 'B': { // BLUE (RGB)
14076 $col_a = array(255,255,255);
14077 $col_b = array(0,0,255);
14078 break;
14080 case 'C': { // CYAN (CMYK)
14081 $col_a = array(0,0,0,0);
14082 $col_b = array(100,0,0,0);
14083 break;
14085 case 'M': { // MAGENTA (CMYK)
14086 $col_a = array(0,0,0,0);
14087 $col_b = array(0,100,0,0);
14088 break;
14090 case 'Y': { // YELLOW (CMYK)
14091 $col_a = array(0,0,0,0);
14092 $col_b = array(0,0,100,0);
14093 break;
14095 case 'K': { // KEY - BLACK (CMYK)
14096 $col_a = array(0,0,0,0);
14097 $col_b = array(0,0,0,100);
14098 break;
14100 case 'RGB': { // BLACK REGISTRATION (RGB)
14101 $col_a = array(255,255,255);
14102 $col_b = array(0,0,0);
14103 break;
14105 case 'CMYK': { // BLACK REGISTRATION (CMYK)
14106 $col_a = array(0,0,0,0);
14107 $col_b = array(100,100,100,100);
14108 break;
14110 case 'ALL': { // SPOT COLOR REGISTRATION
14111 $col_a = array(0,0,0,0,'None');
14112 $col_b = array(100,100,100,100,'All');
14113 break;
14115 case 'NONE': { // SKIP THIS COLOR
14116 $col_a = array(0,0,0,0,'None');
14117 $col_b = array(0,0,0,0,'None');
14118 break;
14120 default: { // SPECIFIC SPOT COLOR NAME
14121 $col_a = array(0,0,0,0,'None');
14122 $col_b = TCPDF_COLORS::getSpotColor($col, $this->spot_colors);
14123 if ($col_b === false) {
14124 // in case of error defaults to the registration color
14125 $col_b = array(100,100,100,100,'All');
14127 break;
14130 if ($col != 'NONE') {
14131 if ($transition) {
14132 // color gradient
14133 $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14134 } else {
14135 $this->SetFillColorArray($col_b);
14136 // colored rectangle
14137 $this->Rect($xb, $yb, $wb, $hb, 'F', array());
14139 $xb += $xd;
14140 $yb += $yd;
14146 * Paints crop marks.
14147 * @param $x (float) abscissa of the crop mark center.
14148 * @param $y (float) ordinate of the crop mark center.
14149 * @param $w (float) width of the crop mark.
14150 * @param $h (float) height of the crop mark.
14151 * @param $type (string) type of crop mark, one symbol per type separated by comma: T = TOP, F = BOTTOM, L = LEFT, R = RIGHT, TL = A = TOP-LEFT, TR = B = TOP-RIGHT, BL = C = BOTTOM-LEFT, BR = D = BOTTOM-RIGHT.
14152 * @param $color (array) crop mark color (default spot registration color).
14153 * @author Nicola Asuni
14154 * @since 4.9.000 (2010-03-26)
14155 * @public
14157 public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14158 $this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14159 $type = strtoupper($type);
14160 $type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14161 // split type in single components
14162 $type = str_replace('-', ',', $type);
14163 $type = str_replace('TL', 'T,L', $type);
14164 $type = str_replace('TR', 'T,R', $type);
14165 $type = str_replace('BL', 'F,L', $type);
14166 $type = str_replace('BR', 'F,R', $type);
14167 $type = str_replace('A', 'T,L', $type);
14168 $type = str_replace('B', 'T,R', $type);
14169 $type = str_replace('T,RO', 'BO', $type);
14170 $type = str_replace('C', 'F,L', $type);
14171 $type = str_replace('D', 'F,R', $type);
14172 $crops = explode(',', strtoupper($type));
14173 // remove duplicates
14174 $crops = array_unique($crops);
14175 $dw = ($w / 4); // horizontal space to leave before the intersection point
14176 $dh = ($h / 4); // vertical space to leave before the intersection point
14177 foreach ($crops as $crop) {
14178 switch ($crop) {
14179 case 'T':
14180 case 'TOP': {
14181 $x1 = $x;
14182 $y1 = ($y - $h);
14183 $x2 = $x;
14184 $y2 = ($y - $dh);
14185 break;
14187 case 'F':
14188 case 'BOTTOM': {
14189 $x1 = $x;
14190 $y1 = ($y + $dh);
14191 $x2 = $x;
14192 $y2 = ($y + $h);
14193 break;
14195 case 'L':
14196 case 'LEFT': {
14197 $x1 = ($x - $w);
14198 $y1 = $y;
14199 $x2 = ($x - $dw);
14200 $y2 = $y;
14201 break;
14203 case 'R':
14204 case 'RIGHT': {
14205 $x1 = ($x + $dw);
14206 $y1 = $y;
14207 $x2 = ($x + $w);
14208 $y2 = $y;
14209 break;
14212 $this->Line($x1, $y1, $x2, $y2);
14217 * Paints a registration mark
14218 * @param $x (float) abscissa of the registration mark center.
14219 * @param $y (float) ordinate of the registration mark center.
14220 * @param $r (float) radius of the crop mark.
14221 * @param $double (boolean) if true print two concentric crop marks.
14222 * @param $cola (array) crop mark color (default spot registration color 'All').
14223 * @param $colb (array) second crop mark color (default spot registration color 'None').
14224 * @author Nicola Asuni
14225 * @since 4.9.000 (2010-03-26)
14226 * @public
14228 public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14229 $line_style = array('width' => max((0.5 / $this->k),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14230 $this->SetFillColorArray($cola);
14231 $this->PieSector($x, $y, $r, 90, 180, 'F');
14232 $this->PieSector($x, $y, $r, 270, 360, 'F');
14233 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14234 if ($double) {
14235 $ri = $r * 0.5;
14236 $this->SetFillColorArray($colb);
14237 $this->PieSector($x, $y, $ri, 90, 180, 'F');
14238 $this->PieSector($x, $y, $ri, 270, 360, 'F');
14239 $this->SetFillColorArray($cola);
14240 $this->PieSector($x, $y, $ri, 0, 90, 'F');
14241 $this->PieSector($x, $y, $ri, 180, 270, 'F');
14242 $this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14247 * Paints a CMYK registration mark
14248 * @param $x (float) abscissa of the registration mark center.
14249 * @param $y (float) ordinate of the registration mark center.
14250 * @param $r (float) radius of the crop mark.
14251 * @author Nicola Asuni
14252 * @since 6.0.038 (2013-09-30)
14253 * @public
14255 public function registrationMarkCMYK($x, $y, $r) {
14256 // line width
14257 $lw = max((0.5 / $this->k),($r / 8));
14258 // internal radius
14259 $ri = ($r * 0.6);
14260 // external radius
14261 $re = ($r * 1.3);
14262 // Cyan
14263 $this->SetFillColorArray(array(100,0,0,0));
14264 $this->PieSector($x, $y, $ri, 270, 360, 'F');
14265 // Magenta
14266 $this->SetFillColorArray(array(0,100,0,0));
14267 $this->PieSector($x, $y, $ri, 0, 90, 'F');
14268 // Yellow
14269 $this->SetFillColorArray(array(0,0,100,0));
14270 $this->PieSector($x, $y, $ri, 90, 180, 'F');
14271 // Key - black
14272 $this->SetFillColorArray(array(0,0,0,100));
14273 $this->PieSector($x, $y, $ri, 180, 270, 'F');
14274 // registration color
14275 $line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14276 $this->SetFillColorArray(array(100,100,100,100,'All'));
14277 // external circle
14278 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14279 // cross lines
14280 $this->Line($x, ($y - $re), $x, ($y - $ri));
14281 $this->Line($x, ($y + $ri), $x, ($y + $re));
14282 $this->Line(($x - $re), $y, ($x - $ri), $y);
14283 $this->Line(($x + $ri), $y, ($x + $re), $y);
14287 * Paints a linear colour gradient.
14288 * @param $x (float) abscissa of the top left corner of the rectangle.
14289 * @param $y (float) ordinate of the top left corner of the rectangle.
14290 * @param $w (float) width of the rectangle.
14291 * @param $h (float) height of the rectangle.
14292 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14293 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14294 * @param $coords (array) array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). The default value is from left to right (x1=0, y1=0, x2=1, y2=0).
14295 * @author Andreas W\FCrmser, Nicola Asuni
14296 * @since 3.1.000 (2008-06-09)
14297 * @public
14299 public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14300 $this->Clip($x, $y, $w, $h);
14301 $this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14305 * Paints a radial colour gradient.
14306 * @param $x (float) abscissa of the top left corner of the rectangle.
14307 * @param $y (float) ordinate of the top left corner of the rectangle.
14308 * @param $w (float) width of the rectangle.
14309 * @param $h (float) height of the rectangle.
14310 * @param $col1 (array) first color (Grayscale, RGB or CMYK components).
14311 * @param $col2 (array) second color (Grayscale, RGB or CMYK components).
14312 * @param $coords (array) array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). (fx, fy) should be inside the circle, otherwise some areas will not be defined.
14313 * @author Andreas W\FCrmser, Nicola Asuni
14314 * @since 3.1.000 (2008-06-09)
14315 * @public
14317 public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14318 $this->Clip($x, $y, $w, $h);
14319 $this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14323 * Paints a coons patch mesh.
14324 * @param $x (float) abscissa of the top left corner of the rectangle.
14325 * @param $y (float) ordinate of the top left corner of the rectangle.
14326 * @param $w (float) width of the rectangle.
14327 * @param $h (float) height of the rectangle.
14328 * @param $col1 (array) first color (lower left corner) (RGB components).
14329 * @param $col2 (array) second color (lower right corner) (RGB components).
14330 * @param $col3 (array) third color (upper right corner) (RGB components).
14331 * @param $col4 (array) fourth color (upper left corner) (RGB components).
14332 * @param $coords (array) <ul><li>for one patch mesh: array(float x1, float y1, .... float x12, float y12): 12 pairs of coordinates (normally from 0 to 1) which specify the Bezier control points that define the patch. First pair is the lower left edge point, next is its right control point (control point 2). Then the other points are defined in the order: control point 1, edge point, control point 2 going counter-clockwise around the patch. Last (x12, y12) is the first edge point's left control point (control point 1).</li><li>for two or more patch meshes: array[number of patches]: arrays with the following keys for each patch: f: where to put that patch (0 = first patch, 1, 2, 3 = right, top and left of precedent patch - I didn't figure this out completely - just try and error ;-) points: 12 pairs of coordinates of the Bezier control points as above for the first patch, 8 pairs of coordinates for the following patches, ignoring the coordinates already defined by the precedent patch (I also didn't figure out the order of these - also: try and see what's happening) colors: must be 4 colors for the first patch, 2 colors for the following patches</li></ul>
14333 * @param $coords_min (array) minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0
14334 * @param $coords_max (array) maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1
14335 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14336 * @author Andreas W\FCrmser, Nicola Asuni
14337 * @since 3.1.000 (2008-06-09)
14338 * @public
14340 public function CoonsPatchMesh($x, $y, $w, $h, $col1=array(), $col2=array(), $col3=array(), $col4=array(), $coords=array(0.00,0.0,0.33,0.00,0.67,0.00,1.00,0.00,1.00,0.33,1.00,0.67,1.00,1.00,0.67,1.00,0.33,1.00,0.00,1.00,0.00,0.67,0.00,0.33), $coords_min=0, $coords_max=1, $antialias=false) {
14341 if ($this->pdfa_mode OR ($this->state != 2)) {
14342 return;
14344 $this->Clip($x, $y, $w, $h);
14345 $n = count($this->gradients) + 1;
14346 $this->gradients[$n] = array();
14347 $this->gradients[$n]['type'] = 6; //coons patch mesh
14348 $this->gradients[$n]['coords'] = array();
14349 $this->gradients[$n]['antialias'] = $antialias;
14350 $this->gradients[$n]['colors'] = array();
14351 $this->gradients[$n]['transparency'] = false;
14352 //check the coords array if it is the simple array or the multi patch array
14353 if (!isset($coords[0]['f'])) {
14354 //simple array -> convert to multi patch array
14355 if (!isset($col1[1])) {
14356 $col1[1] = $col1[2] = $col1[0];
14358 if (!isset($col2[1])) {
14359 $col2[1] = $col2[2] = $col2[0];
14361 if (!isset($col3[1])) {
14362 $col3[1] = $col3[2] = $col3[0];
14364 if (!isset($col4[1])) {
14365 $col4[1] = $col4[2] = $col4[0];
14367 $patch_array[0]['f'] = 0;
14368 $patch_array[0]['points'] = $coords;
14369 $patch_array[0]['colors'][0]['r'] = $col1[0];
14370 $patch_array[0]['colors'][0]['g'] = $col1[1];
14371 $patch_array[0]['colors'][0]['b'] = $col1[2];
14372 $patch_array[0]['colors'][1]['r'] = $col2[0];
14373 $patch_array[0]['colors'][1]['g'] = $col2[1];
14374 $patch_array[0]['colors'][1]['b'] = $col2[2];
14375 $patch_array[0]['colors'][2]['r'] = $col3[0];
14376 $patch_array[0]['colors'][2]['g'] = $col3[1];
14377 $patch_array[0]['colors'][2]['b'] = $col3[2];
14378 $patch_array[0]['colors'][3]['r'] = $col4[0];
14379 $patch_array[0]['colors'][3]['g'] = $col4[1];
14380 $patch_array[0]['colors'][3]['b'] = $col4[2];
14381 } else {
14382 //multi patch array
14383 $patch_array = $coords;
14385 $bpcd = 65535; //16 bits per coordinate
14386 //build the data stream
14387 $this->gradients[$n]['stream'] = '';
14388 $count_patch = count($patch_array);
14389 for ($i=0; $i < $count_patch; ++$i) {
14390 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14391 $count_points = count($patch_array[$i]['points']);
14392 for ($j=0; $j < $count_points; ++$j) {
14393 //each point as 16 bit
14394 $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14395 if ($patch_array[$i]['points'][$j] < 0) {
14396 $patch_array[$i]['points'][$j] = 0;
14398 if ($patch_array[$i]['points'][$j] > $bpcd) {
14399 $patch_array[$i]['points'][$j] = $bpcd;
14401 $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
14402 $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
14404 $count_cols = count($patch_array[$i]['colors']);
14405 for ($j=0; $j < $count_cols; ++$j) {
14406 //each color component as 8 bit
14407 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14408 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14409 $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14412 //paint the gradient
14413 $this->_out('/Sh'.$n.' sh');
14414 //restore previous Graphic State
14415 $this->_outRestoreGraphicsState();
14416 if ($this->inxobj) {
14417 // we are inside an XObject template
14418 $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14423 * Set a rectangular clipping area.
14424 * @param $x (float) abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
14425 * @param $y (float) ordinate of the top left corner of the rectangle.
14426 * @param $w (float) width of the rectangle.
14427 * @param $h (float) height of the rectangle.
14428 * @author Andreas W\FCrmser, Nicola Asuni
14429 * @since 3.1.000 (2008-06-09)
14430 * @protected
14432 protected function Clip($x, $y, $w, $h) {
14433 if ($this->state != 2) {
14434 return;
14436 if ($this->rtl) {
14437 $x = $this->w - $x - $w;
14439 //save current Graphic State
14440 $s = 'q';
14441 //set clipping area
14442 $s .= sprintf(' %F %F %F %F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
14443 //set up transformation matrix for gradient
14444 $s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
14445 $this->_out($s);
14449 * Output gradient.
14450 * @param $type (int) type of gradient (1 Function-based shading; 2 Axial shading; 3 Radial shading; 4 Free-form Gouraud-shaded triangle mesh; 5 Lattice-form Gouraud-shaded triangle mesh; 6 Coons patch mesh; 7 Tensor-product patch mesh). (Not all types are currently supported)
14451 * @param $coords (array) array of coordinates.
14452 * @param $stops (array) array gradient color components: color = array of GRAY, RGB or CMYK color components; offset = (0 to 1) represents a location along the gradient vector; exponent = exponent of the exponential interpolation function (default = 1).
14453 * @param $background (array) An array of colour components appropriate to the colour space, specifying a single background colour value.
14454 * @param $antialias (boolean) A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14455 * @author Nicola Asuni
14456 * @since 3.1.000 (2008-06-09)
14457 * @public
14459 public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14460 if ($this->pdfa_mode OR ($this->state != 2)) {
14461 return;
14463 $n = count($this->gradients) + 1;
14464 $this->gradients[$n] = array();
14465 $this->gradients[$n]['type'] = $type;
14466 $this->gradients[$n]['coords'] = $coords;
14467 $this->gradients[$n]['antialias'] = $antialias;
14468 $this->gradients[$n]['colors'] = array();
14469 $this->gradients[$n]['transparency'] = false;
14470 // color space
14471 $numcolspace = count($stops[0]['color']);
14472 $bcolor = array_values($background);
14473 switch($numcolspace) {
14474 case 5: // SPOT
14475 case 4: { // CMYK
14476 $this->gradients[$n]['colspace'] = 'DeviceCMYK';
14477 if (!empty($background)) {
14478 $this->gradients[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14480 break;
14482 case 3: { // RGB
14483 $this->gradients[$n]['colspace'] = 'DeviceRGB';
14484 if (!empty($background)) {
14485 $this->gradients[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14487 break;
14489 case 1: { // GRAY SCALE
14490 $this->gradients[$n]['colspace'] = 'DeviceGray';
14491 if (!empty($background)) {
14492 $this->gradients[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14494 break;
14497 $num_stops = count($stops);
14498 $last_stop_id = $num_stops - 1;
14499 foreach ($stops as $key => $stop) {
14500 $this->gradients[$n]['colors'][$key] = array();
14501 // offset represents a location along the gradient vector
14502 if (isset($stop['offset'])) {
14503 $this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
14504 } else {
14505 if ($key == 0) {
14506 $this->gradients[$n]['colors'][$key]['offset'] = 0;
14507 } elseif ($key == $last_stop_id) {
14508 $this->gradients[$n]['colors'][$key]['offset'] = 1;
14509 } else {
14510 $offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14511 $this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
14514 if (isset($stop['opacity'])) {
14515 $this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14516 if ((!$this->pdfa_mode) AND ($stop['opacity'] < 1)) {
14517 $this->gradients[$n]['transparency'] = true;
14519 } else {
14520 $this->gradients[$n]['colors'][$key]['opacity'] = 1;
14522 // exponent for the exponential interpolation function
14523 if (isset($stop['exponent'])) {
14524 $this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14525 } else {
14526 $this->gradients[$n]['colors'][$key]['exponent'] = 1;
14528 // set colors
14529 $color = array_values($stop['color']);
14530 switch($numcolspace) {
14531 case 5: // SPOT
14532 case 4: { // CMYK
14533 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14534 break;
14536 case 3: { // RGB
14537 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14538 break;
14540 case 1: { // GRAY SCALE
14541 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14542 break;
14546 if ($this->gradients[$n]['transparency']) {
14547 // paint luminosity gradient
14548 $this->_out('/TGS'.$n.' gs');
14550 //paint the gradient
14551 $this->_out('/Sh'.$n.' sh');
14552 //restore previous Graphic State
14553 $this->_outRestoreGraphicsState();
14554 if ($this->inxobj) {
14555 // we are inside an XObject template
14556 $this->xobjects[$this->xobjid]['gradients'][$n] = $this->gradients[$n];
14561 * Output gradient shaders.
14562 * @author Nicola Asuni
14563 * @since 3.1.000 (2008-06-09)
14564 * @protected
14566 function _putshaders() {
14567 if ($this->pdfa_mode) {
14568 return;
14570 $idt = count($this->gradients); //index for transparency gradients
14571 foreach ($this->gradients as $id => $grad) {
14572 if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14573 $fc = $this->_newobj();
14574 $out = '<<';
14575 $out .= ' /FunctionType 3';
14576 $out .= ' /Domain [0 1]';
14577 $functions = '';
14578 $bounds = '';
14579 $encode = '';
14580 $i = 1;
14581 $num_cols = count($grad['colors']);
14582 $lastcols = $num_cols - 1;
14583 for ($i = 1; $i < $num_cols; ++$i) {
14584 $functions .= ($fc + $i).' 0 R ';
14585 if ($i < $lastcols) {
14586 $bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14588 $encode .= '0 1 ';
14590 $out .= ' /Functions ['.trim($functions).']';
14591 $out .= ' /Bounds ['.trim($bounds).']';
14592 $out .= ' /Encode ['.trim($encode).']';
14593 $out .= ' >>';
14594 $out .= "\n".'endobj';
14595 $this->_out($out);
14596 for ($i = 1; $i < $num_cols; ++$i) {
14597 $this->_newobj();
14598 $out = '<<';
14599 $out .= ' /FunctionType 2';
14600 $out .= ' /Domain [0 1]';
14601 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14602 $out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14603 $out .= ' /N '.$grad['colors'][$i]['exponent'];
14604 $out .= ' >>';
14605 $out .= "\n".'endobj';
14606 $this->_out($out);
14608 // set transparency fuctions
14609 if ($grad['transparency']) {
14610 $ft = $this->_newobj();
14611 $out = '<<';
14612 $out .= ' /FunctionType 3';
14613 $out .= ' /Domain [0 1]';
14614 $functions = '';
14615 $i = 1;
14616 $num_cols = count($grad['colors']);
14617 for ($i = 1; $i < $num_cols; ++$i) {
14618 $functions .= ($ft + $i).' 0 R ';
14620 $out .= ' /Functions ['.trim($functions).']';
14621 $out .= ' /Bounds ['.trim($bounds).']';
14622 $out .= ' /Encode ['.trim($encode).']';
14623 $out .= ' >>';
14624 $out .= "\n".'endobj';
14625 $this->_out($out);
14626 for ($i = 1; $i < $num_cols; ++$i) {
14627 $this->_newobj();
14628 $out = '<<';
14629 $out .= ' /FunctionType 2';
14630 $out .= ' /Domain [0 1]';
14631 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14632 $out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14633 $out .= ' /N '.$grad['colors'][$i]['exponent'];
14634 $out .= ' >>';
14635 $out .= "\n".'endobj';
14636 $this->_out($out);
14640 // set shading object
14641 $this->_newobj();
14642 $out = '<< /ShadingType '.$grad['type'];
14643 if (isset($grad['colspace'])) {
14644 $out .= ' /ColorSpace /'.$grad['colspace'];
14645 } else {
14646 $out .= ' /ColorSpace /DeviceRGB';
14648 if (isset($grad['background']) AND !empty($grad['background'])) {
14649 $out .= ' /Background ['.$grad['background'].']';
14651 if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14652 $out .= ' /AntiAlias true';
14654 if ($grad['type'] == 2) {
14655 $out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14656 $out .= ' /Domain [0 1]';
14657 $out .= ' /Function '.$fc.' 0 R';
14658 $out .= ' /Extend [true true]';
14659 $out .= ' >>';
14660 } elseif ($grad['type'] == 3) {
14661 //x0, y0, r0, x1, y1, r1
14662 //at this this time radius of inner circle is 0
14663 $out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14664 $out .= ' /Domain [0 1]';
14665 $out .= ' /Function '.$fc.' 0 R';
14666 $out .= ' /Extend [true true]';
14667 $out .= ' >>';
14668 } elseif ($grad['type'] == 6) {
14669 $out .= ' /BitsPerCoordinate 16';
14670 $out .= ' /BitsPerComponent 8';
14671 $out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14672 $out .= ' /BitsPerFlag 8';
14673 $stream = $this->_getrawstream($grad['stream']);
14674 $out .= ' /Length '.strlen($stream);
14675 $out .= ' >>';
14676 $out .= ' stream'."\n".$stream."\n".'endstream';
14678 $out .= "\n".'endobj';
14679 $this->_out($out);
14680 if ($grad['transparency']) {
14681 $shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14682 $shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14684 $this->gradients[$id]['id'] = $this->n;
14685 // set pattern object
14686 $this->_newobj();
14687 $out = '<< /Type /Pattern /PatternType 2';
14688 $out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14689 $out .= ' >>';
14690 $out .= "\n".'endobj';
14691 $this->_out($out);
14692 $this->gradients[$id]['pattern'] = $this->n;
14693 // set shading and pattern for transparency mask
14694 if ($grad['transparency']) {
14695 // luminosity pattern
14696 $idgs = $id + $idt;
14697 $this->_newobj();
14698 $this->_out($shading_transparency);
14699 $this->gradients[$idgs]['id'] = $this->n;
14700 $this->_newobj();
14701 $out = '<< /Type /Pattern /PatternType 2';
14702 $out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14703 $out .= ' >>';
14704 $out .= "\n".'endobj';
14705 $this->_out($out);
14706 $this->gradients[$idgs]['pattern'] = $this->n;
14707 // luminosity XObject
14708 $oid = $this->_newobj();
14709 $this->xobjects['LX'.$oid] = array('n' => $oid);
14710 $filter = '';
14711 $stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
14712 if ($this->compress) {
14713 $filter = ' /Filter /FlateDecode';
14714 $stream = gzcompress($stream);
14716 $stream = $this->_getrawstream($stream);
14717 $out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14718 $out .= ' /Length '.strlen($stream);
14719 $rect = sprintf('%F %F', $this->wPt, $this->hPt);
14720 $out .= ' /BBox [0 0 '.$rect.']';
14721 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14722 $out .= ' /Resources <<';
14723 $out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14724 $out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
14725 $out .= ' >>';
14726 $out .= ' >> ';
14727 $out .= ' stream'."\n".$stream."\n".'endstream';
14728 $out .= "\n".'endobj';
14729 $this->_out($out);
14730 // SMask
14731 $this->_newobj();
14732 $out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14733 $this->_out($out);
14734 // ExtGState
14735 $this->_newobj();
14736 $out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14737 $this->_out($out);
14738 $this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
14744 * Draw the sector of a circle.
14745 * It can be used for instance to render pie charts.
14746 * @param $xc (float) abscissa of the center.
14747 * @param $yc (float) ordinate of the center.
14748 * @param $r (float) radius.
14749 * @param $a (float) start angle (in degrees).
14750 * @param $b (float) end angle (in degrees).
14751 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14752 * @param $cw: (float) indicates whether to go clockwise (default: true).
14753 * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90.
14754 * @author Maxime Delorme, Nicola Asuni
14755 * @since 3.1.000 (2008-06-09)
14756 * @public
14758 public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14759 $this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14763 * Draw the sector of an ellipse.
14764 * It can be used for instance to render pie charts.
14765 * @param $xc (float) abscissa of the center.
14766 * @param $yc (float) ordinate of the center.
14767 * @param $rx (float) the x-axis radius.
14768 * @param $ry (float) the y-axis radius.
14769 * @param $a (float) start angle (in degrees).
14770 * @param $b (float) end angle (in degrees).
14771 * @param $style (string) Style of rendering. See the getPathPaintOperator() function for more information.
14772 * @param $cw: (float) indicates whether to go clockwise.
14773 * @param $o: (float) origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14774 * @param $nc (integer) Number of curves used to draw a 90 degrees portion of arc.
14775 * @author Maxime Delorme, Nicola Asuni
14776 * @since 3.1.000 (2008-06-09)
14777 * @public
14779 public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14780 if ($this->state != 2) {
14781 return;
14783 if ($this->rtl) {
14784 $xc = ($this->w - $xc);
14786 $op = TCPDF_STATIC::getPathPaintOperator($style);
14787 if ($op == 'f') {
14788 $line_style = array();
14790 if ($cw) {
14791 $d = $b;
14792 $b = (360 - $a + $o);
14793 $a = (360 - $d + $o);
14794 } else {
14795 $b += $o;
14796 $a += $o;
14798 $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
14799 $this->_out($op);
14803 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
14804 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
14805 * Only vector drawing is supported, not text or bitmap.
14806 * Although the script was successfully tested with various AI format versions, best results are probably achieved with files that were exported in the AI3 format (tested with Illustrator CS2, Freehand MX and Photoshop CS2).
14807 * @param $file (string) Name of the file containing the image or a '@' character followed by the EPS/AI data string.
14808 * @param $x (float) Abscissa of the upper-left corner.
14809 * @param $y (float) Ordinate of the upper-left corner.
14810 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
14811 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
14812 * @param $link (mixed) URL or identifier returned by AddLink().
14813 * @param $useBoundingBox (boolean) specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
14814 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
14815 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
14816 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
14817 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
14818 * @param $fixoutvals (boolean) if true remove values outside the bounding box.
14819 * @author Valentin Schmidt, Nicola Asuni
14820 * @since 3.1.000 (2008-06-09)
14821 * @public
14823 public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
14824 if ($this->state != 2) {
14825 return;
14827 if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
14828 // convert EPS to raster image using GD or ImageMagick libraries
14829 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14831 if ($x === '') {
14832 $x = $this->x;
14834 if ($y === '') {
14835 $y = $this->y;
14837 // check page for no-write regions and adapt page margins if necessary
14838 list($x, $y) = $this->checkPageRegions($h, $x, $y);
14839 $k = $this->k;
14840 if ($file[0] === '@') { // image from string
14841 $data = substr($file, 1);
14842 } else { // EPS/AI file
14843 $data = TCPDF_STATIC::fileGetContents($file);
14845 if ($data === FALSE) {
14846 $this->Error('EPS file not found: '.$file);
14848 $regs = array();
14849 // EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
14850 preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
14851 if (count($regs) > 1) {
14852 $version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
14853 if (strpos($version_str, 'Adobe Illustrator') !== false) {
14854 $versexp = explode(' ', $version_str);
14855 $version = (float)array_pop($versexp);
14856 if ($version >= 9) {
14857 $this->Error('This version of Adobe Illustrator file is not supported: '.$file);
14861 // strip binary bytes in front of PS-header
14862 $start = strpos($data, '%!PS-Adobe');
14863 if ($start > 0) {
14864 $data = substr($data, $start);
14866 // find BoundingBox params
14867 preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
14868 if (count($regs) > 1) {
14869 list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
14870 } else {
14871 $this->Error('No BoundingBox found in EPS/AI file: '.$file);
14873 $start = strpos($data, '%%EndSetup');
14874 if ($start === false) {
14875 $start = strpos($data, '%%EndProlog');
14877 if ($start === false) {
14878 $start = strpos($data, '%%BoundingBox');
14880 $data = substr($data, $start);
14881 $end = strpos($data, '%%PageTrailer');
14882 if ($end===false) {
14883 $end = strpos($data, 'showpage');
14885 if ($end) {
14886 $data = substr($data, 0, $end);
14888 // calculate image width and height on document
14889 if (($w <= 0) AND ($h <= 0)) {
14890 $w = ($x2 - $x1) / $k;
14891 $h = ($y2 - $y1) / $k;
14892 } elseif ($w <= 0) {
14893 $w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
14894 } elseif ($h <= 0) {
14895 $h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
14897 // fit the image on available space
14898 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
14899 if ($this->rasterize_vector_images) {
14900 // convert EPS to raster image using GD or ImageMagick libraries
14901 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14903 // set scaling factors
14904 $scale_x = $w / (($x2 - $x1) / $k);
14905 $scale_y = $h / (($y2 - $y1) / $k);
14906 // set alignment
14907 $this->img_rb_y = $y + $h;
14908 // set alignment
14909 if ($this->rtl) {
14910 if ($palign == 'L') {
14911 $ximg = $this->lMargin;
14912 } elseif ($palign == 'C') {
14913 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
14914 } elseif ($palign == 'R') {
14915 $ximg = $this->w - $this->rMargin - $w;
14916 } else {
14917 $ximg = $x - $w;
14919 $this->img_rb_x = $ximg;
14920 } else {
14921 if ($palign == 'L') {
14922 $ximg = $this->lMargin;
14923 } elseif ($palign == 'C') {
14924 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
14925 } elseif ($palign == 'R') {
14926 $ximg = $this->w - $this->rMargin - $w;
14927 } else {
14928 $ximg = $x;
14930 $this->img_rb_x = $ximg + $w;
14932 if ($useBoundingBox) {
14933 $dx = $ximg * $k - $x1;
14934 $dy = $y * $k - $y1;
14935 } else {
14936 $dx = $ximg * $k;
14937 $dy = $y * $k;
14939 // save the current graphic state
14940 $this->_out('q'.$this->epsmarker);
14941 // translate
14942 $this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
14943 // scale
14944 if (isset($scale_x)) {
14945 $this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
14947 // handle pc/unix/mac line endings
14948 $lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY);
14949 $u=0;
14950 $cnt = count($lines);
14951 for ($i=0; $i < $cnt; ++$i) {
14952 $line = $lines[$i];
14953 if (($line == '') OR ($line[0] == '%')) {
14954 continue;
14956 $len = strlen($line);
14957 // check for spot color names
14958 $color_name = '';
14959 if (strcasecmp('x', substr(trim($line), -1)) == 0) {
14960 if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
14961 // extract spot color name
14962 $color_name = $matches[0];
14963 // remove color name from string
14964 $line = str_replace(' '.$color_name, '', $line);
14965 // remove pharentesis from color name
14966 $color_name = substr($color_name, 1, -1);
14969 $chunks = explode(' ', $line);
14970 $cmd = trim(array_pop($chunks));
14971 // RGB
14972 if (($cmd == 'Xa') OR ($cmd == 'XA')) {
14973 $b = array_pop($chunks);
14974 $g = array_pop($chunks);
14975 $r = array_pop($chunks);
14976 $this->_out(''.$r.' '.$g.' '.$b.' '.($cmd=='Xa'?'rg':'RG')); //substr($line, 0, -2).'rg' -> in EPS (AI8): c m y k r g b rg!
14977 continue;
14979 $skip = false;
14980 if ($fixoutvals) {
14981 // check for values outside the bounding box
14982 switch ($cmd) {
14983 case 'm':
14984 case 'l':
14985 case 'L': {
14986 // skip values outside bounding box
14987 foreach ($chunks as $key => $val) {
14988 if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
14989 $skip = true;
14990 } elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
14991 $skip = true;
14997 switch ($cmd) {
14998 case 'm':
14999 case 'l':
15000 case 'v':
15001 case 'y':
15002 case 'c':
15003 case 'k':
15004 case 'K':
15005 case 'g':
15006 case 'G':
15007 case 's':
15008 case 'S':
15009 case 'J':
15010 case 'j':
15011 case 'w':
15012 case 'M':
15013 case 'd':
15014 case 'n': {
15015 if ($skip) {
15016 break;
15018 $this->_out($line);
15019 break;
15021 case 'x': {// custom fill color
15022 if (empty($color_name)) {
15023 // CMYK color
15024 list($col_c, $col_m, $col_y, $col_k) = $chunks;
15025 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15026 } else {
15027 // Spot Color (CMYK + tint)
15028 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15029 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15030 $color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15031 $this->_out($color_cmd);
15033 break;
15035 case 'X': { // custom stroke color
15036 if (empty($color_name)) {
15037 // CMYK color
15038 list($col_c, $col_m, $col_y, $col_k) = $chunks;
15039 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15040 } else {
15041 // Spot Color (CMYK + tint)
15042 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15043 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15044 $color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors[$color_name]['i'], (1 - $col_t));
15045 $this->_out($color_cmd);
15047 break;
15049 case 'Y':
15050 case 'N':
15051 case 'V':
15052 case 'L':
15053 case 'C': {
15054 if ($skip) {
15055 break;
15057 $line[($len - 1)] = strtolower($cmd);
15058 $this->_out($line);
15059 break;
15061 case 'b':
15062 case 'B': {
15063 $this->_out($cmd . '*');
15064 break;
15066 case 'f':
15067 case 'F': {
15068 if ($u > 0) {
15069 $isU = false;
15070 $max = min(($i + 5), $cnt);
15071 for ($j = ($i + 1); $j < $max; ++$j) {
15072 $isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15074 if ($isU) {
15075 $this->_out('f*');
15077 } else {
15078 $this->_out('f*');
15080 break;
15082 case '*u': {
15083 ++$u;
15084 break;
15086 case '*U': {
15087 --$u;
15088 break;
15092 // restore previous graphic state
15093 $this->_out($this->epsmarker.'Q');
15094 if (!empty($border)) {
15095 $bx = $this->x;
15096 $by = $this->y;
15097 $this->x = $ximg;
15098 if ($this->rtl) {
15099 $this->x += $w;
15101 $this->y = $y;
15102 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15103 $this->x = $bx;
15104 $this->y = $by;
15106 if ($link) {
15107 $this->Link($ximg, $y, $w, $h, $link, 0);
15109 // set pointer to align the next text/objects
15110 switch($align) {
15111 case 'T':{
15112 $this->y = $y;
15113 $this->x = $this->img_rb_x;
15114 break;
15116 case 'M':{
15117 $this->y = $y + round($h/2);
15118 $this->x = $this->img_rb_x;
15119 break;
15121 case 'B':{
15122 $this->y = $this->img_rb_y;
15123 $this->x = $this->img_rb_x;
15124 break;
15126 case 'N':{
15127 $this->SetY($this->img_rb_y);
15128 break;
15130 default:{
15131 break;
15134 $this->endlinex = $this->img_rb_x;
15138 * Set document barcode.
15139 * @param $bc (string) barcode
15140 * @public
15142 public function setBarcode($bc='') {
15143 $this->barcode = $bc;
15147 * Get current barcode.
15148 * @return string
15149 * @public
15150 * @since 4.0.012 (2008-07-24)
15152 public function getBarcode() {
15153 return $this->barcode;
15157 * Print a Linear Barcode.
15158 * @param $code (string) code to print
15159 * @param $type (string) type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15160 * @param $x (int) x position in user units (empty string = current x position)
15161 * @param $y (int) y position in user units (empty string = current y position)
15162 * @param $w (int) width in user units (empty string = remaining page width)
15163 * @param $h (int) height in user units (empty string = remaining page height)
15164 * @param $xres (float) width of the smallest bar in user units (empty string = default value = 0.4mm)
15165 * @param $style (array) array of options:<ul>
15166 * <li>boolean $style['border'] if true prints a border</li>
15167 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15168 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15169 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15170 * <li>array $style['fgcolor'] color array for bars and text</li>
15171 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15172 * <li>boolean $style['text'] if true prints text below the barcode</li>
15173 * <li>string $style['label'] override default label</li>
15174 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15175 * <li>int $style['stretchtext']: 0 = disabled; 1 = horizontal scaling only if necessary; 2 = forced horizontal scaling; 3 = character spacing only if necessary; 4 = forced character spacing.</li>
15176 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15177 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15178 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15179 * <li>string $style['fitwidth'] if true reduce the width to fit the barcode width + padding. When this option is enabled the 'stretch' option is automatically disabled.</li>
15180 * <li>string $style['cellfitalign'] this option works only when 'fitwidth' is true and 'position' is unset or empty. Set the horizontal position of the containing barcode cell inside the specified rectangle: L = left; C = center; R = right.</li></ul>
15181 * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
15182 * @author Nicola Asuni
15183 * @since 3.1.000 (2008-06-09)
15184 * @public
15186 public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style='', $align='') {
15187 if (TCPDF_STATIC::empty_string(trim($code))) {
15188 return;
15190 require_once(dirname(__FILE__).'/tcpdf_barcodes_1d.php');
15191 // save current graphic settings
15192 $gvars = $this->getGraphicVars();
15193 // create new barcode object
15194 $barcodeobj = new TCPDFBarcode($code, $type);
15195 $arrcode = $barcodeobj->getBarcodeArray();
15196 if (($arrcode === false) OR empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15197 $this->Error('Error in 1D barcode string');
15199 if ($arrcode['maxh'] <= 0) {
15200 $arrcode['maxh'] = 1;
15202 // set default values
15203 if (!isset($style['position'])) {
15204 $style['position'] = '';
15205 } elseif ($style['position'] == 'S') {
15206 // keep this for backward compatibility
15207 $style['position'] = '';
15208 $style['stretch'] = true;
15210 if (!isset($style['fitwidth'])) {
15211 if (!isset($style['stretch'])) {
15212 $style['fitwidth'] = true;
15213 } else {
15214 $style['fitwidth'] = false;
15217 if ($style['fitwidth']) {
15218 // disable stretch
15219 $style['stretch'] = false;
15221 if (!isset($style['stretch'])) {
15222 if (($w === '') OR ($w <= 0)) {
15223 $style['stretch'] = false;
15224 } else {
15225 $style['stretch'] = true;
15228 if (!isset($style['fgcolor'])) {
15229 $style['fgcolor'] = array(0,0,0); // default black
15231 if (!isset($style['bgcolor'])) {
15232 $style['bgcolor'] = false; // default transparent
15234 if (!isset($style['border'])) {
15235 $style['border'] = false;
15237 $fontsize = 0;
15238 if (!isset($style['text'])) {
15239 $style['text'] = false;
15241 if ($style['text'] AND isset($style['font'])) {
15242 if (isset($style['fontsize'])) {
15243 $fontsize = $style['fontsize'];
15245 $this->SetFont($style['font'], '', $fontsize);
15247 if (!isset($style['stretchtext'])) {
15248 $style['stretchtext'] = 4;
15250 if ($x === '') {
15251 $x = $this->x;
15253 if ($y === '') {
15254 $y = $this->y;
15256 // check page for no-write regions and adapt page margins if necessary
15257 list($x, $y) = $this->checkPageRegions($h, $x, $y);
15258 if (($w === '') OR ($w <= 0)) {
15259 if ($this->rtl) {
15260 $w = $x - $this->lMargin;
15261 } else {
15262 $w = $this->w - $this->rMargin - $x;
15265 // padding
15266 if (!isset($style['padding'])) {
15267 $padding = 0;
15268 } elseif ($style['padding'] === 'auto') {
15269 $padding = 10 * ($w / ($arrcode['maxw'] + 20));
15270 } else {
15271 $padding = floatval($style['padding']);
15273 // horizontal padding
15274 if (!isset($style['hpadding'])) {
15275 $hpadding = $padding;
15276 } elseif ($style['hpadding'] === 'auto') {
15277 $hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
15278 } else {
15279 $hpadding = floatval($style['hpadding']);
15281 // vertical padding
15282 if (!isset($style['vpadding'])) {
15283 $vpadding = $padding;
15284 } elseif ($style['vpadding'] === 'auto') {
15285 $vpadding = ($hpadding / 2);
15286 } else {
15287 $vpadding = floatval($style['vpadding']);
15289 // calculate xres (single bar width)
15290 $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15291 if ($style['stretch']) {
15292 $xres = $max_xres;
15293 } else {
15294 if (TCPDF_STATIC::empty_string($xres)) {
15295 $xres = (0.141 * $this->k); // default bar width = 0.4 mm
15297 if ($xres > $max_xres) {
15298 // correct xres to fit on $w
15299 $xres = $max_xres;
15301 if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15302 OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15303 $hpadding = 10 * $xres;
15304 if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15305 $vpadding = ($hpadding / 2);
15309 if ($style['fitwidth']) {
15310 $wold = $w;
15311 $w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15312 if (isset($style['cellfitalign'])) {
15313 switch ($style['cellfitalign']) {
15314 case 'L': {
15315 if ($this->rtl) {
15316 $x -= ($wold - $w);
15318 break;
15320 case 'R': {
15321 if (!$this->rtl) {
15322 $x += ($wold - $w);
15324 break;
15326 case 'C': {
15327 if ($this->rtl) {
15328 $x -= (($wold - $w) / 2);
15329 } else {
15330 $x += (($wold - $w) / 2);
15332 break;
15334 default : {
15335 break;
15340 $text_height = $this->getCellHeight($fontsize / $this->k);
15341 // height
15342 if (($h === '') OR ($h <= 0)) {
15343 // set default height
15344 $h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
15346 $barh = $h - $text_height - (2 * $vpadding);
15347 if ($barh <=0) {
15348 // try to reduce font or padding to fit barcode on available height
15349 if ($text_height > $h) {
15350 $fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
15351 $text_height = $this->getCellHeight($fontsize / $this->k);
15352 $this->SetFont($style['font'], '', $fontsize);
15354 if ($vpadding > 0) {
15355 $vpadding = (($h - $text_height) / 4);
15357 $barh = $h - $text_height - (2 * $vpadding);
15359 // fit the barcode on available space
15360 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15361 // set alignment
15362 $this->img_rb_y = $y + $h;
15363 // set alignment
15364 if ($this->rtl) {
15365 if ($style['position'] == 'L') {
15366 $xpos = $this->lMargin;
15367 } elseif ($style['position'] == 'C') {
15368 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15369 } elseif ($style['position'] == 'R') {
15370 $xpos = $this->w - $this->rMargin - $w;
15371 } else {
15372 $xpos = $x - $w;
15374 $this->img_rb_x = $xpos;
15375 } else {
15376 if ($style['position'] == 'L') {
15377 $xpos = $this->lMargin;
15378 } elseif ($style['position'] == 'C') {
15379 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15380 } elseif ($style['position'] == 'R') {
15381 $xpos = $this->w - $this->rMargin - $w;
15382 } else {
15383 $xpos = $x;
15385 $this->img_rb_x = $xpos + $w;
15387 $xpos_rect = $xpos;
15388 if (!isset($style['align'])) {
15389 $style['align'] = 'C';
15391 switch ($style['align']) {
15392 case 'L': {
15393 $xpos = $xpos_rect + $hpadding;
15394 break;
15396 case 'R': {
15397 $xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15398 break;
15400 case 'C':
15401 default : {
15402 $xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15403 break;
15406 $xpos_text = $xpos;
15407 // barcode is always printed in LTR direction
15408 $tempRTL = $this->rtl;
15409 $this->rtl = false;
15410 // print background color
15411 if ($style['bgcolor']) {
15412 $this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15413 } elseif ($style['border']) {
15414 $this->Rect($xpos_rect, $y, $w, $h, 'D');
15416 // set foreground color
15417 $this->SetDrawColorArray($style['fgcolor']);
15418 $this->SetTextColorArray($style['fgcolor']);
15419 // print bars
15420 foreach ($arrcode['bcode'] as $k => $v) {
15421 $bw = ($v['w'] * $xres);
15422 if ($v['t']) {
15423 // draw a vertical bar
15424 $ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
15425 $this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15427 $xpos += $bw;
15429 // print text
15430 if ($style['text']) {
15431 if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15432 $label = $style['label'];
15433 } else {
15434 $label = $code;
15436 $txtwidth = ($arrcode['maxw'] * $xres);
15437 if ($this->GetStringWidth($label) > $txtwidth) {
15438 $style['stretchtext'] = 2;
15440 // print text
15441 $this->x = $xpos_text;
15442 $this->y = $y + $vpadding + $barh;
15443 $cellpadding = $this->cell_padding;
15444 $this->SetCellPadding(0);
15445 $this->Cell($txtwidth, '', $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15446 $this->cell_padding = $cellpadding;
15448 // restore original direction
15449 $this->rtl = $tempRTL;
15450 // restore previous settings
15451 $this->setGraphicVars($gvars);
15452 // set pointer to align the next text/objects
15453 switch($align) {
15454 case 'T':{
15455 $this->y = $y;
15456 $this->x = $this->img_rb_x;
15457 break;
15459 case 'M':{
15460 $this->y = $y + round($h / 2);
15461 $this->x = $this->img_rb_x;
15462 break;
15464 case 'B':{
15465 $this->y = $this->img_rb_y;
15466 $this->x = $this->img_rb_x;
15467 break;
15469 case 'N':{
15470 $this->SetY($this->img_rb_y);
15471 break;
15473 default:{
15474 break;
15477 $this->endlinex = $this->img_rb_x;
15481 * Print 2D Barcode.
15482 * @param $code (string) code to print
15483 * @param $type (string) type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15484 * @param $x (int) x position in user units
15485 * @param $y (int) y position in user units
15486 * @param $w (int) width in user units
15487 * @param $h (int) height in user units
15488 * @param $style (array) array of options:<ul>
15489 * <li>boolean $style['border'] if true prints a border around the barcode</li>
15490 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15491 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15492 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15493 * <li>int $style['module_width'] width of a single module in points</li>
15494 * <li>int $style['module_height'] height of a single module in points</li>
15495 * <li>array $style['fgcolor'] color array for bars and text</li>
15496 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15497 * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li><li>$style['module_width'] width of a single module in points</li>
15498 * <li>$style['module_height'] height of a single module in points</li></ul>
15499 * @param $align (string) Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
15500 * @param $distort (boolean) if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15501 * @author Nicola Asuni
15502 * @since 4.5.037 (2009-04-07)
15503 * @public
15505 public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style='', $align='', $distort=false) {
15506 if (TCPDF_STATIC::empty_string(trim($code))) {
15507 return;
15509 require_once(dirname(__FILE__).'/tcpdf_barcodes_2d.php');
15510 // save current graphic settings
15511 $gvars = $this->getGraphicVars();
15512 // create new barcode object
15513 $barcodeobj = new TCPDF2DBarcode($code, $type);
15514 $arrcode = $barcodeobj->getBarcodeArray();
15515 if (($arrcode === false) OR empty($arrcode) OR !isset($arrcode['num_rows']) OR ($arrcode['num_rows'] == 0) OR !isset($arrcode['num_cols']) OR ($arrcode['num_cols'] == 0)) {
15516 $this->Error('Error in 2D barcode string');
15518 // set default values
15519 if (!isset($style['position'])) {
15520 $style['position'] = '';
15522 if (!isset($style['fgcolor'])) {
15523 $style['fgcolor'] = array(0,0,0); // default black
15525 if (!isset($style['bgcolor'])) {
15526 $style['bgcolor'] = false; // default transparent
15528 if (!isset($style['border'])) {
15529 $style['border'] = false;
15531 // padding
15532 if (!isset($style['padding'])) {
15533 $style['padding'] = 0;
15534 } elseif ($style['padding'] === 'auto') {
15535 $style['padding'] = 4;
15537 if (!isset($style['hpadding'])) {
15538 $style['hpadding'] = $style['padding'];
15539 } elseif ($style['hpadding'] === 'auto') {
15540 $style['hpadding'] = 4;
15542 if (!isset($style['vpadding'])) {
15543 $style['vpadding'] = $style['padding'];
15544 } elseif ($style['vpadding'] === 'auto') {
15545 $style['vpadding'] = 4;
15547 $hpad = (2 * $style['hpadding']);
15548 $vpad = (2 * $style['vpadding']);
15549 // cell (module) dimension
15550 if (!isset($style['module_width'])) {
15551 $style['module_width'] = 1; // width of a single module in points
15553 if (!isset($style['module_height'])) {
15554 $style['module_height'] = 1; // height of a single module in points
15556 if ($x === '') {
15557 $x = $this->x;
15559 if ($y === '') {
15560 $y = $this->y;
15562 // check page for no-write regions and adapt page margins if necessary
15563 list($x, $y) = $this->checkPageRegions($h, $x, $y);
15564 // number of barcode columns and rows
15565 $rows = $arrcode['num_rows'];
15566 $cols = $arrcode['num_cols'];
15567 if (($rows <= 0) || ($cols <= 0)){
15568 $this->Error('Error in 2D barcode string');
15570 // module width and height
15571 $mw = $style['module_width'];
15572 $mh = $style['module_height'];
15573 if (($mw <= 0) OR ($mh <= 0)) {
15574 $this->Error('Error in 2D barcode string');
15576 // get max dimensions
15577 if ($this->rtl) {
15578 $maxw = $x - $this->lMargin;
15579 } else {
15580 $maxw = $this->w - $this->rMargin - $x;
15582 $maxh = ($this->h - $this->tMargin - $this->bMargin);
15583 $ratioHW = ((($rows * $mh) + $hpad) / (($cols * $mw) + $vpad));
15584 $ratioWH = ((($cols * $mw) + $vpad) / (($rows * $mh) + $hpad));
15585 if (!$distort) {
15586 if (($maxw * $ratioHW) > $maxh) {
15587 $maxw = $maxh * $ratioWH;
15589 if (($maxh * $ratioWH) > $maxw) {
15590 $maxh = $maxw * $ratioHW;
15593 // set maximum dimesions
15594 if ($w > $maxw) {
15595 $w = $maxw;
15597 if ($h > $maxh) {
15598 $h = $maxh;
15600 // set dimensions
15601 if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
15602 $w = ($cols + $hpad) * ($mw / $this->k);
15603 $h = ($rows + $vpad) * ($mh / $this->k);
15604 } elseif (($w === '') OR ($w <= 0)) {
15605 $w = $h * $ratioWH;
15606 } elseif (($h === '') OR ($h <= 0)) {
15607 $h = $w * $ratioHW;
15609 // barcode size (excluding padding)
15610 $bw = ($w * $cols) / ($cols + $hpad);
15611 $bh = ($h * $rows) / ($rows + $vpad);
15612 // dimension of single barcode cell unit
15613 $cw = $bw / $cols;
15614 $ch = $bh / $rows;
15615 if (!$distort) {
15616 if (($cw / $ch) > ($mw / $mh)) {
15617 // correct horizontal distortion
15618 $cw = $ch * $mw / $mh;
15619 $bw = $cw * $cols;
15620 $style['hpadding'] = ($w - $bw) / (2 * $cw);
15621 } else {
15622 // correct vertical distortion
15623 $ch = $cw * $mh / $mw;
15624 $bh = $ch * $rows;
15625 $style['vpadding'] = ($h - $bh) / (2 * $ch);
15628 // fit the barcode on available space
15629 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15630 // set alignment
15631 $this->img_rb_y = $y + $h;
15632 // set alignment
15633 if ($this->rtl) {
15634 if ($style['position'] == 'L') {
15635 $xpos = $this->lMargin;
15636 } elseif ($style['position'] == 'C') {
15637 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15638 } elseif ($style['position'] == 'R') {
15639 $xpos = $this->w - $this->rMargin - $w;
15640 } else {
15641 $xpos = $x - $w;
15643 $this->img_rb_x = $xpos;
15644 } else {
15645 if ($style['position'] == 'L') {
15646 $xpos = $this->lMargin;
15647 } elseif ($style['position'] == 'C') {
15648 $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
15649 } elseif ($style['position'] == 'R') {
15650 $xpos = $this->w - $this->rMargin - $w;
15651 } else {
15652 $xpos = $x;
15654 $this->img_rb_x = $xpos + $w;
15656 $xstart = $xpos + ($style['hpadding'] * $cw);
15657 $ystart = $y + ($style['vpadding'] * $ch);
15658 // barcode is always printed in LTR direction
15659 $tempRTL = $this->rtl;
15660 $this->rtl = false;
15661 // print background color
15662 if ($style['bgcolor']) {
15663 $this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
15664 } elseif ($style['border']) {
15665 $this->Rect($xpos, $y, $w, $h, 'D');
15667 // set foreground color
15668 $this->SetDrawColorArray($style['fgcolor']);
15669 // print barcode cells
15670 // for each row
15671 for ($r = 0; $r < $rows; ++$r) {
15672 $xr = $xstart;
15673 // for each column
15674 for ($c = 0; $c < $cols; ++$c) {
15675 if ($arrcode['bcode'][$r][$c] == 1) {
15676 // draw a single barcode cell
15677 $this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15679 $xr += $cw;
15681 $ystart += $ch;
15683 // restore original direction
15684 $this->rtl = $tempRTL;
15685 // restore previous settings
15686 $this->setGraphicVars($gvars);
15687 // set pointer to align the next text/objects
15688 switch($align) {
15689 case 'T':{
15690 $this->y = $y;
15691 $this->x = $this->img_rb_x;
15692 break;
15694 case 'M':{
15695 $this->y = $y + round($h/2);
15696 $this->x = $this->img_rb_x;
15697 break;
15699 case 'B':{
15700 $this->y = $this->img_rb_y;
15701 $this->x = $this->img_rb_x;
15702 break;
15704 case 'N':{
15705 $this->SetY($this->img_rb_y);
15706 break;
15708 default:{
15709 break;
15712 $this->endlinex = $this->img_rb_x;
15716 * Returns an array containing current margins:
15717 * <ul>
15718 <li>$ret['left'] = left margin</li>
15719 <li>$ret['right'] = right margin</li>
15720 <li>$ret['top'] = top margin</li>
15721 <li>$ret['bottom'] = bottom margin</li>
15722 <li>$ret['header'] = header margin</li>
15723 <li>$ret['footer'] = footer margin</li>
15724 <li>$ret['cell'] = cell padding array</li>
15725 <li>$ret['padding_left'] = cell left padding</li>
15726 <li>$ret['padding_top'] = cell top padding</li>
15727 <li>$ret['padding_right'] = cell right padding</li>
15728 <li>$ret['padding_bottom'] = cell bottom padding</li>
15729 * </ul>
15730 * @return array containing all margins measures
15731 * @public
15732 * @since 3.2.000 (2008-06-23)
15734 public function getMargins() {
15735 $ret = array(
15736 'left' => $this->lMargin,
15737 'right' => $this->rMargin,
15738 'top' => $this->tMargin,
15739 'bottom' => $this->bMargin,
15740 'header' => $this->header_margin,
15741 'footer' => $this->footer_margin,
15742 'cell' => $this->cell_padding,
15743 'padding_left' => $this->cell_padding['L'],
15744 'padding_top' => $this->cell_padding['T'],
15745 'padding_right' => $this->cell_padding['R'],
15746 'padding_bottom' => $this->cell_padding['B']
15748 return $ret;
15752 * Returns an array containing original margins:
15753 * <ul>
15754 <li>$ret['left'] = left margin</li>
15755 <li>$ret['right'] = right margin</li>
15756 * </ul>
15757 * @return array containing all margins measures
15758 * @public
15759 * @since 4.0.012 (2008-07-24)
15761 public function getOriginalMargins() {
15762 $ret = array(
15763 'left' => $this->original_lMargin,
15764 'right' => $this->original_rMargin
15766 return $ret;
15770 * Returns the current font size.
15771 * @return current font size
15772 * @public
15773 * @since 3.2.000 (2008-06-23)
15775 public function getFontSize() {
15776 return $this->FontSize;
15780 * Returns the current font size in points unit.
15781 * @return current font size in points unit
15782 * @public
15783 * @since 3.2.000 (2008-06-23)
15785 public function getFontSizePt() {
15786 return $this->FontSizePt;
15790 * Returns the current font family name.
15791 * @return string current font family name
15792 * @public
15793 * @since 4.3.008 (2008-12-05)
15795 public function getFontFamily() {
15796 return $this->FontFamily;
15800 * Returns the current font style.
15801 * @return string current font style
15802 * @public
15803 * @since 4.3.008 (2008-12-05)
15805 public function getFontStyle() {
15806 return $this->FontStyle;
15810 * Cleanup HTML code (requires HTML Tidy library).
15811 * @param $html (string) htmlcode to fix
15812 * @param $default_css (string) CSS commands to add
15813 * @param $tagvs (array) parameters for setHtmlVSpace method
15814 * @param $tidy_options (array) options for tidy_parse_string function
15815 * @return string XHTML code cleaned up
15816 * @author Nicola Asuni
15817 * @public
15818 * @since 5.9.017 (2010-11-16)
15819 * @see setHtmlVSpace()
15821 public function fixHTMLCode($html, $default_css='', $tagvs='', $tidy_options='') {
15822 return TCPDF_STATIC::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces);
15826 * Returns the border width from CSS property
15827 * @param $width (string) border width
15828 * @return int with in user units
15829 * @protected
15830 * @since 5.7.000 (2010-08-02)
15832 protected function getCSSBorderWidth($width) {
15833 if ($width == 'thin') {
15834 $width = (2 / $this->k);
15835 } elseif ($width == 'medium') {
15836 $width = (4 / $this->k);
15837 } elseif ($width == 'thick') {
15838 $width = (6 / $this->k);
15839 } else {
15840 $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
15842 return $width;
15846 * Returns the border dash style from CSS property
15847 * @param $style (string) border style to convert
15848 * @return int sash style (return -1 in case of none or hidden border)
15849 * @protected
15850 * @since 5.7.000 (2010-08-02)
15852 protected function getCSSBorderDashStyle($style) {
15853 switch (strtolower($style)) {
15854 case 'none':
15855 case 'hidden': {
15856 $dash = -1;
15857 break;
15859 case 'dotted': {
15860 $dash = 1;
15861 break;
15863 case 'dashed': {
15864 $dash = 3;
15865 break;
15867 case 'double':
15868 case 'groove':
15869 case 'ridge':
15870 case 'inset':
15871 case 'outset':
15872 case 'solid':
15873 default: {
15874 $dash = 0;
15875 break;
15878 return $dash;
15882 * Returns the border style array from CSS border properties
15883 * @param $cssborder (string) border properties
15884 * @return array containing border properties
15885 * @protected
15886 * @since 5.7.000 (2010-08-02)
15888 protected function getCSSBorderStyle($cssborder) {
15889 $bprop = preg_split('/[\s]+/', trim($cssborder));
15890 $border = array(); // value to be returned
15891 switch (count($bprop)) {
15892 case 3: {
15893 $width = $bprop[0];
15894 $style = $bprop[1];
15895 $color = $bprop[2];
15896 break;
15898 case 2: {
15899 $width = 'medium';
15900 $style = $bprop[0];
15901 $color = $bprop[1];
15902 break;
15904 case 1: {
15905 $width = 'medium';
15906 $style = $bprop[0];
15907 $color = 'black';
15908 break;
15910 default: {
15911 $width = 'medium';
15912 $style = 'solid';
15913 $color = 'black';
15914 break;
15917 if ($style == 'none') {
15918 return array();
15920 $border['cap'] = 'square';
15921 $border['join'] = 'miter';
15922 $border['dash'] = $this->getCSSBorderDashStyle($style);
15923 if ($border['dash'] < 0) {
15924 return array();
15926 $border['width'] = $this->getCSSBorderWidth($width);
15927 $border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
15928 return $border;
15932 * Get the internal Cell padding from CSS attribute.
15933 * @param $csspadding (string) padding properties
15934 * @param $width (float) width of the containing element
15935 * @return array of cell paddings
15936 * @public
15937 * @since 5.9.000 (2010-10-04)
15939 public function getCSSPadding($csspadding, $width=0) {
15940 $padding = preg_split('/[\s]+/', trim($csspadding));
15941 $cell_padding = array(); // value to be returned
15942 switch (count($padding)) {
15943 case 4: {
15944 $cell_padding['T'] = $padding[0];
15945 $cell_padding['R'] = $padding[1];
15946 $cell_padding['B'] = $padding[2];
15947 $cell_padding['L'] = $padding[3];
15948 break;
15950 case 3: {
15951 $cell_padding['T'] = $padding[0];
15952 $cell_padding['R'] = $padding[1];
15953 $cell_padding['B'] = $padding[2];
15954 $cell_padding['L'] = $padding[1];
15955 break;
15957 case 2: {
15958 $cell_padding['T'] = $padding[0];
15959 $cell_padding['R'] = $padding[1];
15960 $cell_padding['B'] = $padding[0];
15961 $cell_padding['L'] = $padding[1];
15962 break;
15964 case 1: {
15965 $cell_padding['T'] = $padding[0];
15966 $cell_padding['R'] = $padding[0];
15967 $cell_padding['B'] = $padding[0];
15968 $cell_padding['L'] = $padding[0];
15969 break;
15971 default: {
15972 return $this->cell_padding;
15975 if ($width == 0) {
15976 $width = $this->w - $this->lMargin - $this->rMargin;
15978 $cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
15979 $cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
15980 $cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
15981 $cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
15982 return $cell_padding;
15986 * Get the internal Cell margin from CSS attribute.
15987 * @param $cssmargin (string) margin properties
15988 * @param $width (float) width of the containing element
15989 * @return array of cell margins
15990 * @public
15991 * @since 5.9.000 (2010-10-04)
15993 public function getCSSMargin($cssmargin, $width=0) {
15994 $margin = preg_split('/[\s]+/', trim($cssmargin));
15995 $cell_margin = array(); // value to be returned
15996 switch (count($margin)) {
15997 case 4: {
15998 $cell_margin['T'] = $margin[0];
15999 $cell_margin['R'] = $margin[1];
16000 $cell_margin['B'] = $margin[2];
16001 $cell_margin['L'] = $margin[3];
16002 break;
16004 case 3: {
16005 $cell_margin['T'] = $margin[0];
16006 $cell_margin['R'] = $margin[1];
16007 $cell_margin['B'] = $margin[2];
16008 $cell_margin['L'] = $margin[1];
16009 break;
16011 case 2: {
16012 $cell_margin['T'] = $margin[0];
16013 $cell_margin['R'] = $margin[1];
16014 $cell_margin['B'] = $margin[0];
16015 $cell_margin['L'] = $margin[1];
16016 break;
16018 case 1: {
16019 $cell_margin['T'] = $margin[0];
16020 $cell_margin['R'] = $margin[0];
16021 $cell_margin['B'] = $margin[0];
16022 $cell_margin['L'] = $margin[0];
16023 break;
16025 default: {
16026 return $this->cell_margin;
16029 if ($width == 0) {
16030 $width = $this->w - $this->lMargin - $this->rMargin;
16032 $cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16033 $cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16034 $cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16035 $cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16036 return $cell_margin;
16040 * Get the border-spacing from CSS attribute.
16041 * @param $cssbspace (string) border-spacing CSS properties
16042 * @param $width (float) width of the containing element
16043 * @return array of border spacings
16044 * @public
16045 * @since 5.9.010 (2010-10-27)
16047 public function getCSSBorderMargin($cssbspace, $width=0) {
16048 $space = preg_split('/[\s]+/', trim($cssbspace));
16049 $border_spacing = array(); // value to be returned
16050 switch (count($space)) {
16051 case 2: {
16052 $border_spacing['H'] = $space[0];
16053 $border_spacing['V'] = $space[1];
16054 break;
16056 case 1: {
16057 $border_spacing['H'] = $space[0];
16058 $border_spacing['V'] = $space[0];
16059 break;
16061 default: {
16062 return array('H' => 0, 'V' => 0);
16065 if ($width == 0) {
16066 $width = $this->w - $this->lMargin - $this->rMargin;
16068 $border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16069 $border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16070 return $border_spacing;
16074 * Returns the letter-spacing value from CSS value
16075 * @param $spacing (string) letter-spacing value
16076 * @param $parent (float) font spacing (tracking) value of the parent element
16077 * @return float quantity to increases or decreases the space between characters in a text.
16078 * @protected
16079 * @since 5.9.000 (2010-10-02)
16081 protected function getCSSFontSpacing($spacing, $parent=0) {
16082 $val = 0; // value to be returned
16083 $spacing = trim($spacing);
16084 switch ($spacing) {
16085 case 'normal': {
16086 $val = 0;
16087 break;
16089 case 'inherit': {
16090 if ($parent == 'normal') {
16091 $val = 0;
16092 } else {
16093 $val = $parent;
16095 break;
16097 default: {
16098 $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16101 return $val;
16105 * Returns the percentage of font stretching from CSS value
16106 * @param $stretch (string) stretch mode
16107 * @param $parent (float) stretch value of the parent element
16108 * @return float font stretching percentage
16109 * @protected
16110 * @since 5.9.000 (2010-10-02)
16112 protected function getCSSFontStretching($stretch, $parent=100) {
16113 $val = 100; // value to be returned
16114 $stretch = trim($stretch);
16115 switch ($stretch) {
16116 case 'ultra-condensed': {
16117 $val = 40;
16118 break;
16120 case 'extra-condensed': {
16121 $val = 55;
16122 break;
16124 case 'condensed': {
16125 $val = 70;
16126 break;
16128 case 'semi-condensed': {
16129 $val = 85;
16130 break;
16132 case 'normal': {
16133 $val = 100;
16134 break;
16136 case 'semi-expanded': {
16137 $val = 115;
16138 break;
16140 case 'expanded': {
16141 $val = 130;
16142 break;
16144 case 'extra-expanded': {
16145 $val = 145;
16146 break;
16148 case 'ultra-expanded': {
16149 $val = 160;
16150 break;
16152 case 'wider': {
16153 $val = ($parent + 10);
16154 break;
16156 case 'narrower': {
16157 $val = ($parent - 10);
16158 break;
16160 case 'inherit': {
16161 if ($parent == 'normal') {
16162 $val = 100;
16163 } else {
16164 $val = $parent;
16166 break;
16168 default: {
16169 $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16172 return $val;
16176 * Convert HTML string containing font size value to points
16177 * @param $val (string) String containing font size value and unit.
16178 * @param $refsize (float) Reference font size in points.
16179 * @param $parent_size (float) Parent font size in points.
16180 * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16181 * @return float value in points
16182 * @public
16184 public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16185 $refsize = TCPDF_FONTS::getFontRefSize($refsize);
16186 $parent_size = TCPDF_FONTS::getFontRefSize($parent_size, $refsize);
16187 switch ($val) {
16188 case 'xx-small': {
16189 $size = ($refsize - 4);
16190 break;
16192 case 'x-small': {
16193 $size = ($refsize - 3);
16194 break;
16196 case 'small': {
16197 $size = ($refsize - 2);
16198 break;
16200 case 'medium': {
16201 $size = $refsize;
16202 break;
16204 case 'large': {
16205 $size = ($refsize + 2);
16206 break;
16208 case 'x-large': {
16209 $size = ($refsize + 4);
16210 break;
16212 case 'xx-large': {
16213 $size = ($refsize + 6);
16214 break;
16216 case 'smaller': {
16217 $size = ($parent_size - 3);
16218 break;
16220 case 'larger': {
16221 $size = ($parent_size + 3);
16222 break;
16224 default: {
16225 $size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16228 return $size;
16232 * Returns the HTML DOM array.
16233 * @param $html (string) html code
16234 * @return array
16235 * @protected
16236 * @since 3.2.000 (2008-06-20)
16238 protected function getHtmlDomArray($html) {
16239 // array of CSS styles ( selector => properties).
16240 $css = array();
16241 // get CSS array defined at previous call
16242 $matches = array();
16243 if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
16244 if (isset($matches[1][0])) {
16245 $css = array_merge($css, unserialize($this->unhtmlentities($matches[1][0])));
16247 $html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
16249 // extract external CSS files
16250 $matches = array();
16251 if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
16252 foreach ($matches[1] as $key => $link) {
16253 $type = array();
16254 if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16255 $type = array();
16256 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16257 // get 'all' and 'print' media, other media types are discarded
16258 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16259 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16260 $type = array();
16261 if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16262 // read CSS data file
16263 $cssdata = TCPDF_STATIC::fileGetContents(trim($type[1]));
16264 if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16265 $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16272 // extract style tags
16273 $matches = array();
16274 if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
16275 foreach ($matches[1] as $key => $media) {
16276 $type = array();
16277 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16278 // get 'all' and 'print' media, other media types are discarded
16279 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16280 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16281 $cssdata = $matches[2][$key];
16282 $css = array_merge($css, TCPDF_STATIC::extractCSSproperties($cssdata));
16286 // create a special tag to contain the CSS array (used for table content)
16287 $csstagarray = '<cssarray>'.htmlentities(serialize($css)).'</cssarray>';
16288 // remove head and style blocks
16289 $html = preg_replace('/<head([^\>]*)>(.*?)<\/head>/siU', '', $html);
16290 $html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
16291 // define block tags
16292 $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16293 // define self-closing tags
16294 $selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16295 // remove all unsupported tags (the line below lists all supported tags)
16296 $html = strip_tags($html, '<marker/><a><b><blockquote><body><br><br/><dd><del><div><dl><dt><em><font><form><h1><h2><h3><h4><h5><h6><hr><hr/><i><img><input><label><li><ol><option><p><pre><s><select><small><span><strike><strong><sub><sup><table><tablehead><tcpdf><td><textarea><th><thead><tr><tt><u><ul>');
16297 //replace some blank characters
16298 $html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16299 $html = preg_replace('/<(table|tr|td|th|tcpdf|blockquote|dd|div|dl|dt|form|h1|h2|h3|h4|h5|h6|br|hr|li|ol|ul|p)([^\>]*)>[\n\r\t]+/', '<\\1\\2>', $html);
16300 $html = preg_replace('@(\r\n|\r)@', "\n", $html);
16301 $repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16302 $html = strtr($html, $repTable);
16303 $offset = 0;
16304 while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16305 $html_a = substr($html, 0, $offset);
16306 $html_b = substr($html, $offset, ($pos - $offset + 6));
16307 while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16308 // preserve newlines on <pre> tag
16309 $html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16311 while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
16312 // preserve spaces on <pre> tag
16313 $html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
16315 $html = $html_a.$html_b.substr($html, $pos + 6);
16316 $offset = strlen($html_a.$html_b);
16318 $offset = 0;
16319 while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16320 $html_a = substr($html, 0, $offset);
16321 $html_b = substr($html, $offset, ($pos - $offset + 11));
16322 while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16323 // preserve newlines on <textarea> tag
16324 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16325 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16327 $html = $html_a.$html_b.substr($html, $pos + 11);
16328 $offset = strlen($html_a.$html_b);
16330 $html = preg_replace('/([\s]*)<option/si', '<option', $html);
16331 $html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16332 $offset = 0;
16333 while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16334 $html_a = substr($html, 0, $offset);
16335 $html_b = substr($html, $offset, ($pos - $offset + 9));
16336 while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16337 $html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16338 $html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16340 $html = $html_a.$html_b.substr($html, $pos + 9);
16341 $offset = strlen($html_a.$html_b);
16343 if (preg_match("'</select'si", $html)) {
16344 $html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16345 $html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16347 $html = str_replace("\n", ' ', $html);
16348 // restore textarea newlines
16349 $html = str_replace('<TBR>', "\n", $html);
16350 // remove extra spaces from code
16351 $html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16352 $html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
16353 $html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16354 $html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
16355 $html = preg_replace('/<\/(table|tr|td|th|blockquote|dd|dt|dl|div|dt|h1|h2|h3|h4|h5|h6|hr|li|ol|ul|p)>[\s]+</', '</\\1><', $html);
16356 $html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16357 $html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16358 $html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
16359 $html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1>&nbsp;\\2', $html);
16360 $html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16361 $html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16362 $html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16363 $html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
16364 $html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16365 $html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16366 $html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
16367 $html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16368 $html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16369 $html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
16370 // trim string
16371 $html = $this->stringTrim($html);
16372 // fix br tag after li
16373 $html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16374 // fix first image tag alignment
16375 $html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16376 // pattern for generic tag
16377 $tagpattern = '/(<[^>]+>)/';
16378 // explodes the string
16379 $a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
16380 // count elements
16381 $maxel = count($a);
16382 $elkey = 0;
16383 $key = 0;
16384 // create an array of elements
16385 $dom = array();
16386 $dom[$key] = array();
16387 // set inheritable properties fot the first void element
16388 // possible inheritable properties are: azimuth, border-collapse, border-spacing, caption-side, color, cursor, direction, empty-cells, font, font-family, font-stretch, font-size, font-size-adjust, font-style, font-variant, font-weight, letter-spacing, line-height, list-style, list-style-image, list-style-position, list-style-type, orphans, page, page-break-inside, quotes, speak, speak-header, text-align, text-indent, text-transform, volume, white-space, widows, word-spacing
16389 $dom[$key]['tag'] = false;
16390 $dom[$key]['block'] = false;
16391 $dom[$key]['value'] = '';
16392 $dom[$key]['parent'] = 0;
16393 $dom[$key]['hide'] = false;
16394 $dom[$key]['fontname'] = $this->FontFamily;
16395 $dom[$key]['fontstyle'] = $this->FontStyle;
16396 $dom[$key]['fontsize'] = $this->FontSizePt;
16397 $dom[$key]['font-stretch'] = $this->font_stretching;
16398 $dom[$key]['letter-spacing'] = $this->font_spacing;
16399 $dom[$key]['stroke'] = $this->textstrokewidth;
16400 $dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
16401 $dom[$key]['clip'] = ($this->textrendermode > 3);
16402 $dom[$key]['line-height'] = $this->cell_height_ratio;
16403 $dom[$key]['bgcolor'] = false;
16404 $dom[$key]['fgcolor'] = $this->fgcolor; // color
16405 $dom[$key]['strokecolor'] = $this->strokecolor;
16406 $dom[$key]['align'] = '';
16407 $dom[$key]['listtype'] = '';
16408 $dom[$key]['text-indent'] = 0;
16409 $dom[$key]['text-transform'] = '';
16410 $dom[$key]['border'] = array();
16411 $dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
16412 $thead = false; // true when we are inside the THEAD tag
16413 ++$key;
16414 $level = array();
16415 array_push($level, 0); // root
16416 while ($elkey < $maxel) {
16417 $dom[$key] = array();
16418 $element = $a[$elkey];
16419 $dom[$key]['elkey'] = $elkey;
16420 if (preg_match($tagpattern, $element)) {
16421 // html tag
16422 $element = substr($element, 1, -1);
16423 // get tag name
16424 preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16425 $tagname = strtolower($tag[1]);
16426 // check if we are inside a table header
16427 if ($tagname == 'thead') {
16428 if ($element[0] == '/') {
16429 $thead = false;
16430 } else {
16431 $thead = true;
16433 ++$elkey;
16434 continue;
16436 $dom[$key]['tag'] = true;
16437 $dom[$key]['value'] = $tagname;
16438 if (in_array($dom[$key]['value'], $blocktags)) {
16439 $dom[$key]['block'] = true;
16440 } else {
16441 $dom[$key]['block'] = false;
16443 if ($element[0] == '/') {
16444 // *** closing html tag
16445 $dom[$key]['opening'] = false;
16446 $dom[$key]['parent'] = end($level);
16447 array_pop($level);
16448 $dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16449 $dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16450 $dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16451 $dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16452 $dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16453 $dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16454 $dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16455 $dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16456 $dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16457 $dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16458 $dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16459 $dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16460 $dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16461 $dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16462 $dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16463 $dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16464 if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16465 $dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16467 // set the number of columns in table tag
16468 if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16469 $dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16471 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16472 $dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16473 for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
16474 $dom[($dom[$key]['parent'])]['content'] .= $a[$dom[$i]['elkey']];
16476 $key = $i;
16477 // mark nested tables
16478 $dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16479 // remove thead sections from nested tables
16480 $dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16481 $dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16483 // store header rows on a new table
16484 if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
16485 if (TCPDF_STATIC::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16486 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16488 for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
16489 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16491 if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16492 $dom[($dom[$key]['parent'])]['attribute'] = array();
16494 // header elements must be always contained in a single page
16495 $dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16497 if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16498 // remove the nobr attributes from the table header
16499 $dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16500 $dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16502 } else {
16503 // *** opening or self-closing html tag
16504 $dom[$key]['opening'] = true;
16505 $dom[$key]['parent'] = end($level);
16506 if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16507 // self-closing tag
16508 $dom[$key]['self'] = true;
16509 } else {
16510 // opening tag
16511 array_push($level, $key);
16512 $dom[$key]['self'] = false;
16514 // copy some values from parent
16515 $parentkey = 0;
16516 if ($key > 0) {
16517 $parentkey = $dom[$key]['parent'];
16518 $dom[$key]['hide'] = $dom[$parentkey]['hide'];
16519 $dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16520 $dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16521 $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16522 $dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16523 $dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16524 $dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16525 $dom[$key]['fill'] = $dom[$parentkey]['fill'];
16526 $dom[$key]['clip'] = $dom[$parentkey]['clip'];
16527 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16528 $dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16529 $dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16530 $dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16531 $dom[$key]['align'] = $dom[$parentkey]['align'];
16532 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16533 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16534 $dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16535 $dom[$key]['border'] = array();
16536 $dom[$key]['dir'] = $dom[$parentkey]['dir'];
16538 // get attributes
16539 preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
16540 $dom[$key]['attribute'] = array(); // reset attribute array
16541 while (list($id, $name) = each($attr_array[1])) {
16542 $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16544 if (!empty($css)) {
16545 // merge CSS style to current style
16546 list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC::getCSSdataArray($dom, $key, $css);
16547 $dom[$key]['attribute']['style'] = TCPDF_STATIC::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16549 // split style attributes
16550 if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16551 // get style attributes
16552 preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
16553 $dom[$key]['style'] = array(); // reset style attribute array
16554 while (list($id, $name) = each($style_array[1])) {
16555 // in case of duplicate attribute the last replace the previous
16556 $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16558 // --- get some style attributes ---
16559 // text direction
16560 if (isset($dom[$key]['style']['direction'])) {
16561 $dom[$key]['dir'] = $dom[$key]['style']['direction'];
16563 // display
16564 if (isset($dom[$key]['style']['display'])) {
16565 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16567 // font family
16568 if (isset($dom[$key]['style']['font-family'])) {
16569 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16571 // list-style-type
16572 if (isset($dom[$key]['style']['list-style-type'])) {
16573 $dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16574 if ($dom[$key]['listtype'] == 'inherit') {
16575 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16578 // text-indent
16579 if (isset($dom[$key]['style']['text-indent'])) {
16580 $dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16581 if ($dom[$key]['text-indent'] == 'inherit') {
16582 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16585 // text-transform
16586 if (isset($dom[$key]['style']['text-transform'])) {
16587 $dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16589 // font size
16590 if (isset($dom[$key]['style']['font-size'])) {
16591 $fsize = trim($dom[$key]['style']['font-size']);
16592 $dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16594 // font-stretch
16595 if (isset($dom[$key]['style']['font-stretch'])) {
16596 $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16598 // letter-spacing
16599 if (isset($dom[$key]['style']['letter-spacing'])) {
16600 $dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16602 // line-height (internally is the cell height ratio)
16603 if (isset($dom[$key]['style']['line-height'])) {
16604 $lineheight = trim($dom[$key]['style']['line-height']);
16605 switch ($lineheight) {
16606 // A normal line height. This is default
16607 case 'normal': {
16608 $dom[$key]['line-height'] = $dom[0]['line-height'];
16609 break;
16611 case 'inherit': {
16612 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16614 default: {
16615 if (is_numeric($lineheight)) {
16616 // convert to percentage of font height
16617 $lineheight = ($lineheight * 100).'%';
16619 $dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16620 if (substr($lineheight, -1) !== '%') {
16621 $dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16626 // font style
16627 if (isset($dom[$key]['style']['font-weight'])) {
16628 if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16629 if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16630 $dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16632 } elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16633 $dom[$key]['fontstyle'] .= 'B';
16636 if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16637 $dom[$key]['fontstyle'] .= 'I';
16639 // font color
16640 if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['color']))) {
16641 $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors);
16642 } elseif ($dom[$key]['value'] == 'a') {
16643 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16645 // background color
16646 if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['style']['background-color']))) {
16647 $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors);
16649 // text-decoration
16650 if (isset($dom[$key]['style']['text-decoration'])) {
16651 $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16652 foreach ($decors as $dec) {
16653 $dec = trim($dec);
16654 if (!TCPDF_STATIC::empty_string($dec)) {
16655 if ($dec[0] == 'u') {
16656 // underline
16657 $dom[$key]['fontstyle'] .= 'U';
16658 } elseif ($dec[0] == 'l') {
16659 // line-through
16660 $dom[$key]['fontstyle'] .= 'D';
16661 } elseif ($dec[0] == 'o') {
16662 // overline
16663 $dom[$key]['fontstyle'] .= 'O';
16667 } elseif ($dom[$key]['value'] == 'a') {
16668 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16670 // check for width attribute
16671 if (isset($dom[$key]['style']['width'])) {
16672 $dom[$key]['width'] = $dom[$key]['style']['width'];
16674 // check for height attribute
16675 if (isset($dom[$key]['style']['height'])) {
16676 $dom[$key]['height'] = $dom[$key]['style']['height'];
16678 // check for text alignment
16679 if (isset($dom[$key]['style']['text-align'])) {
16680 $dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16682 // check for CSS border properties
16683 if (isset($dom[$key]['style']['border'])) {
16684 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16685 if (!empty($borderstyle)) {
16686 $dom[$key]['border']['LTRB'] = $borderstyle;
16689 if (isset($dom[$key]['style']['border-color'])) {
16690 $brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16691 if (isset($brd_colors[3])) {
16692 $dom[$key]['border']['L']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[3], $this->spot_colors);
16694 if (isset($brd_colors[1])) {
16695 $dom[$key]['border']['R']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[1], $this->spot_colors);
16697 if (isset($brd_colors[0])) {
16698 $dom[$key]['border']['T']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[0], $this->spot_colors);
16700 if (isset($brd_colors[2])) {
16701 $dom[$key]['border']['B']['color'] = TCPDF_COLORS::convertHTMLColorToDec($brd_colors[2], $this->spot_colors);
16704 if (isset($dom[$key]['style']['border-width'])) {
16705 $brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16706 if (isset($brd_widths[3])) {
16707 $dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16709 if (isset($brd_widths[1])) {
16710 $dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16712 if (isset($brd_widths[0])) {
16713 $dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16715 if (isset($brd_widths[2])) {
16716 $dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16719 if (isset($dom[$key]['style']['border-style'])) {
16720 $brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16721 if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16722 $dom[$key]['border']['L']['cap'] = 'square';
16723 $dom[$key]['border']['L']['join'] = 'miter';
16724 $dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16725 if ($dom[$key]['border']['L']['dash'] < 0) {
16726 $dom[$key]['border']['L'] = array();
16729 if (isset($brd_styles[1])) {
16730 $dom[$key]['border']['R']['cap'] = 'square';
16731 $dom[$key]['border']['R']['join'] = 'miter';
16732 $dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16733 if ($dom[$key]['border']['R']['dash'] < 0) {
16734 $dom[$key]['border']['R'] = array();
16737 if (isset($brd_styles[0])) {
16738 $dom[$key]['border']['T']['cap'] = 'square';
16739 $dom[$key]['border']['T']['join'] = 'miter';
16740 $dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16741 if ($dom[$key]['border']['T']['dash'] < 0) {
16742 $dom[$key]['border']['T'] = array();
16745 if (isset($brd_styles[2])) {
16746 $dom[$key]['border']['B']['cap'] = 'square';
16747 $dom[$key]['border']['B']['join'] = 'miter';
16748 $dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16749 if ($dom[$key]['border']['B']['dash'] < 0) {
16750 $dom[$key]['border']['B'] = array();
16754 $cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16755 foreach ($cellside as $bsk => $bsv) {
16756 if (isset($dom[$key]['style']['border-'.$bsv])) {
16757 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
16758 if (!empty($borderstyle)) {
16759 $dom[$key]['border'][$bsk] = $borderstyle;
16762 if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
16763 $dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors);
16765 if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
16766 $dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
16768 if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
16769 $dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
16770 if ($dom[$key]['border'][$bsk]['dash'] < 0) {
16771 $dom[$key]['border'][$bsk] = array();
16775 // check for CSS padding properties
16776 if (isset($dom[$key]['style']['padding'])) {
16777 $dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
16778 } else {
16779 $dom[$key]['padding'] = $this->cell_padding;
16781 foreach ($cellside as $psk => $psv) {
16782 if (isset($dom[$key]['style']['padding-'.$psv])) {
16783 $dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
16786 // check for CSS margin properties
16787 if (isset($dom[$key]['style']['margin'])) {
16788 $dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
16789 } else {
16790 $dom[$key]['margin'] = $this->cell_margin;
16792 foreach ($cellside as $psk => $psv) {
16793 if (isset($dom[$key]['style']['margin-'.$psv])) {
16794 $dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
16797 // check for CSS border-spacing properties
16798 if (isset($dom[$key]['style']['border-spacing'])) {
16799 $dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
16801 // page-break-inside
16802 if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
16803 $dom[$key]['attribute']['nobr'] = 'true';
16805 // page-break-before
16806 if (isset($dom[$key]['style']['page-break-before'])) {
16807 if ($dom[$key]['style']['page-break-before'] == 'always') {
16808 $dom[$key]['attribute']['pagebreak'] = 'true';
16809 } elseif ($dom[$key]['style']['page-break-before'] == 'left') {
16810 $dom[$key]['attribute']['pagebreak'] = 'left';
16811 } elseif ($dom[$key]['style']['page-break-before'] == 'right') {
16812 $dom[$key]['attribute']['pagebreak'] = 'right';
16815 // page-break-after
16816 if (isset($dom[$key]['style']['page-break-after'])) {
16817 if ($dom[$key]['style']['page-break-after'] == 'always') {
16818 $dom[$key]['attribute']['pagebreakafter'] = 'true';
16819 } elseif ($dom[$key]['style']['page-break-after'] == 'left') {
16820 $dom[$key]['attribute']['pagebreakafter'] = 'left';
16821 } elseif ($dom[$key]['style']['page-break-after'] == 'right') {
16822 $dom[$key]['attribute']['pagebreakafter'] = 'right';
16826 if (isset($dom[$key]['attribute']['display'])) {
16827 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
16829 if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
16830 $borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
16831 if (!empty($borderstyle)) {
16832 $dom[$key]['border']['LTRB'] = $borderstyle;
16835 // check for font tag
16836 if ($dom[$key]['value'] == 'font') {
16837 // font family
16838 if (isset($dom[$key]['attribute']['face'])) {
16839 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
16841 // font size
16842 if (isset($dom[$key]['attribute']['size'])) {
16843 if ($key > 0) {
16844 if ($dom[$key]['attribute']['size'][0] == '+') {
16845 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
16846 } elseif ($dom[$key]['attribute']['size'][0] == '-') {
16847 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
16848 } else {
16849 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16851 } else {
16852 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
16856 // force natural alignment for lists
16857 if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
16858 AND (!isset($dom[$key]['align']) OR TCPDF_STATIC::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
16859 if ($this->rtl) {
16860 $dom[$key]['align'] = 'R';
16861 } else {
16862 $dom[$key]['align'] = 'L';
16865 if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
16866 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
16867 $dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
16870 if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
16871 $dom[$key]['fontstyle'] .= 'B';
16873 if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
16874 $dom[$key]['fontstyle'] .= 'I';
16876 if ($dom[$key]['value'] == 'u') {
16877 $dom[$key]['fontstyle'] .= 'U';
16879 if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
16880 $dom[$key]['fontstyle'] .= 'D';
16882 if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
16883 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
16885 if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
16886 $dom[$key]['fontname'] = $this->default_monospaced_font;
16888 if (!empty($dom[$key]['value']) AND ($dom[$key]['value'][0] == 'h') AND (intval($dom[$key]['value']{1}) > 0) AND (intval($dom[$key]['value']{1}) < 7)) {
16889 // headings h1, h2, h3, h4, h5, h6
16890 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
16891 $headsize = (4 - intval($dom[$key]['value']{1})) * 2;
16892 $dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
16894 if (!isset($dom[$key]['style']['font-weight'])) {
16895 $dom[$key]['fontstyle'] .= 'B';
16898 if (($dom[$key]['value'] == 'table')) {
16899 $dom[$key]['rows'] = 0; // number of rows
16900 $dom[$key]['trids'] = array(); // IDs of TR elements
16901 $dom[$key]['thead'] = ''; // table header rows
16903 if (($dom[$key]['value'] == 'tr')) {
16904 $dom[$key]['cols'] = 0;
16905 if ($thead) {
16906 $dom[$key]['thead'] = true;
16907 // rows on thead block are printed as a separate table
16908 } else {
16909 $dom[$key]['thead'] = false;
16910 // store the number of rows on table element
16911 ++$dom[($dom[$key]['parent'])]['rows'];
16912 // store the TR elements IDs on table element
16913 array_push($dom[($dom[$key]['parent'])]['trids'], $key);
16916 if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
16917 if (isset($dom[$key]['attribute']['colspan'])) {
16918 $colspan = intval($dom[$key]['attribute']['colspan']);
16919 } else {
16920 $colspan = 1;
16922 $dom[$key]['attribute']['colspan'] = $colspan;
16923 $dom[($dom[$key]['parent'])]['cols'] += $colspan;
16925 // text direction
16926 if (isset($dom[$key]['attribute']['dir'])) {
16927 $dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
16929 // set foreground color attribute
16930 if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['color']))) {
16931 $dom[$key]['fgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors);
16932 } elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
16933 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
16935 // set background color attribute
16936 if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['bgcolor']))) {
16937 $dom[$key]['bgcolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors);
16939 // set stroke color attribute
16940 if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['strokecolor']))) {
16941 $dom[$key]['strokecolor'] = TCPDF_COLORS::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors);
16943 // check for width attribute
16944 if (isset($dom[$key]['attribute']['width'])) {
16945 $dom[$key]['width'] = $dom[$key]['attribute']['width'];
16947 // check for height attribute
16948 if (isset($dom[$key]['attribute']['height'])) {
16949 $dom[$key]['height'] = $dom[$key]['attribute']['height'];
16951 // check for text alignment
16952 if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
16953 $dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
16955 // check for text rendering mode (the following attributes do not exist in HTML)
16956 if (isset($dom[$key]['attribute']['stroke'])) {
16957 // font stroke width
16958 $dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
16960 if (isset($dom[$key]['attribute']['fill'])) {
16961 // font fill
16962 if ($dom[$key]['attribute']['fill'] == 'true') {
16963 $dom[$key]['fill'] = true;
16964 } else {
16965 $dom[$key]['fill'] = false;
16968 if (isset($dom[$key]['attribute']['clip'])) {
16969 // clipping mode
16970 if ($dom[$key]['attribute']['clip'] == 'true') {
16971 $dom[$key]['clip'] = true;
16972 } else {
16973 $dom[$key]['clip'] = false;
16976 } // end opening tag
16977 } else {
16978 // text
16979 $dom[$key]['tag'] = false;
16980 $dom[$key]['block'] = false;
16981 $dom[$key]['parent'] = end($level);
16982 $dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
16983 if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
16984 // text-transform for unicode requires mb_convert_case (Multibyte String Functions)
16985 if (function_exists('mb_convert_case')) {
16986 $ttm = array('capitalize' => MB_CASE_TITLE, 'uppercase' => MB_CASE_UPPER, 'lowercase' => MB_CASE_LOWER);
16987 if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
16988 $element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding);
16990 } elseif (!$this->isunicode) {
16991 switch ($dom[$dom[$key]['parent']]['text-transform']) {
16992 case 'capitalize': {
16993 $element = ucwords(strtolower($element));
16994 break;
16996 case 'uppercase': {
16997 $element = strtoupper($element);
16998 break;
17000 case 'lowercase': {
17001 $element = strtolower($element);
17002 break;
17007 $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17009 ++$elkey;
17010 ++$key;
17012 return $dom;
17016 * Returns the string used to find spaces
17017 * @return string
17018 * @protected
17019 * @author Nicola Asuni
17020 * @since 4.8.024 (2010-01-15)
17022 protected function getSpaceString() {
17023 $spacestr = chr(32);
17024 if ($this->isUnicodeFont()) {
17025 $spacestr = chr(0).chr(32);
17027 return $spacestr;
17031 * Serialize an array of parameters to be used with TCPDF tag in HTML code.
17032 * @param $pararray (array) parameters array
17033 * @return sting containing serialized data
17034 * @since 4.9.006 (2010-04-02)
17035 * @public
17036 * @deprecated
17038 public function serializeTCPDFtagParameters($pararray) {
17039 return TCPDF_STATIC::serializeTCPDFtagParameters($pararray);
17043 * Prints a cell (rectangular area) with optional borders, background color and html text string.
17044 * The upper-left corner of the cell corresponds to the current position. After the call, the current position moves to the right or to the next line.<br />
17045 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17046 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17047 * Supported tags are: a, b, blockquote, br, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, img, li, ol, p, pre, small, span, strong, sub, sup, table, tcpdf, td, th, thead, tr, tt, u, ul
17048 * NOTE: all the HTML attributes must be enclosed in double-quote.
17049 * @param $w (float) Cell width. If 0, the cell extends up to the right margin.
17050 * @param $h (float) Cell minimum height. The cell extends automatically if needed.
17051 * @param $x (float) upper-left corner X coordinate
17052 * @param $y (float) upper-left corner Y coordinate
17053 * @param $html (string) html text to print. Default value: empty string.
17054 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
17055 * @param $ln (int) Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL language)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
17056 Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
17057 * @param $fill (boolean) Indicates if the cell background must be painted (true) or transparent (false).
17058 * @param $reseth (boolean) if true reset the last cell height (default true).
17059 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
17060 * @param $autopadding (boolean) if true, uses internal padding and automatically adjust it to account for line width.
17061 * @see Multicell(), writeHTML()
17062 * @public
17064 public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17065 return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17069 * Allows to preserve some HTML formatting (limited support).<br />
17070 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17071 * Supported tags are: a, b, blockquote, br, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, img, li, ol, p, pre, small, span, strong, sub, sup, table, tcpdf, td, th, thead, tr, tt, u, ul
17072 * NOTE: all the HTML attributes must be enclosed in double-quote.
17073 * @param $html (string) text to display
17074 * @param $ln (boolean) if true add a new line after text (default = true)
17075 * @param $fill (boolean) Indicates if the background must be painted (true) or transparent (false).
17076 * @param $reseth (boolean) if true reset the last cell height (default false).
17077 * @param $cell (boolean) if true add the current left (or right for RTL) padding to each Write (default false).
17078 * @param $align (string) Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
17079 * @public
17081 public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17082 $gvars = $this->getGraphicVars();
17083 // store current values
17084 $prev_cell_margin = $this->cell_margin;
17085 $prev_cell_padding = $this->cell_padding;
17086 $prevPage = $this->page;
17087 $prevlMargin = $this->lMargin;
17088 $prevrMargin = $this->rMargin;
17089 $curfontname = $this->FontFamily;
17090 $curfontstyle = $this->FontStyle;
17091 $curfontsize = $this->FontSizePt;
17092 $curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17093 $curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17094 $curfontstretcing = $this->font_stretching;
17095 $curfonttracking = $this->font_spacing;
17096 $this->newline = true;
17097 $newline = true;
17098 $startlinepage = $this->page;
17099 $minstartliney = $this->y;
17100 $maxbottomliney = 0;
17101 $startlinex = $this->x;
17102 $startliney = $this->y;
17103 $yshift = 0;
17104 $loop = 0;
17105 $curpos = 0;
17106 $this_method_vars = array();
17107 $undo = false;
17108 $fontaligned = false;
17109 $reverse_dir = false; // true when the text direction is reversed
17110 $this->premode = false;
17111 if ($this->inxobj) {
17112 // we are inside an XObject template
17113 $pask = count($this->xobjects[$this->xobjid]['annotations']);
17114 } elseif (isset($this->PageAnnots[$this->page])) {
17115 $pask = count($this->PageAnnots[$this->page]);
17116 } else {
17117 $pask = 0;
17119 if ($this->inxobj) {
17120 // we are inside an XObject template
17121 $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17122 } elseif (!$this->InFooter) {
17123 if (isset($this->footerlen[$this->page])) {
17124 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17125 } else {
17126 $this->footerpos[$this->page] = $this->pagelen[$this->page];
17128 $startlinepos = $this->footerpos[$this->page];
17129 } else {
17130 // we are inside the footer
17131 $startlinepos = $this->pagelen[$this->page];
17133 $lalign = $align;
17134 $plalign = $align;
17135 if ($this->rtl) {
17136 $w = $this->x - $this->lMargin;
17137 } else {
17138 $w = $this->w - $this->rMargin - $this->x;
17140 $w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17141 if ($cell) {
17142 if ($this->rtl) {
17143 $this->x -= $this->cell_padding['R'];
17144 $this->lMargin += $this->cell_padding['R'];
17145 } else {
17146 $this->x += $this->cell_padding['L'];
17147 $this->rMargin += $this->cell_padding['L'];
17150 if ($this->customlistindent >= 0) {
17151 $this->listindent = $this->customlistindent;
17152 } else {
17153 $this->listindent = $this->GetStringWidth('000000');
17155 $this->listindentlevel = 0;
17156 // save previous states
17157 $prev_cell_height_ratio = $this->cell_height_ratio;
17158 $prev_listnum = $this->listnum;
17159 $prev_listordered = $this->listordered;
17160 $prev_listcount = $this->listcount;
17161 $prev_lispacer = $this->lispacer;
17162 $this->listnum = 0;
17163 $this->listordered = array();
17164 $this->listcount = array();
17165 $this->lispacer = '';
17166 if ((TCPDF_STATIC::empty_string($this->lasth)) OR ($reseth)) {
17167 // reset row height
17168 $this->resetLastH();
17170 $dom = $this->getHtmlDomArray($html);
17171 $maxel = count($dom);
17172 $key = 0;
17173 while ($key < $maxel) {
17174 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17175 // store the node key
17176 $hidden_node_key = $key;
17177 if ($dom[$key]['self']) {
17178 // skip just this self-closing tag
17179 ++$key;
17180 } else {
17181 // skip this and all children tags
17182 while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17183 // skip hidden objects
17184 ++$key;
17186 ++$key;
17189 if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17190 // check for pagebreak
17191 if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17192 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17193 $this->checkPageBreak($this->PageBreakTrigger + 1);
17194 $this->htmlvspace = ($this->PageBreakTrigger + 1);
17196 if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
17197 OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
17198 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17199 $this->checkPageBreak($this->PageBreakTrigger + 1);
17200 $this->htmlvspace = ($this->PageBreakTrigger + 1);
17203 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17204 if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17205 $dom[$key]['attribute']['nobr'] = false;
17206 } else {
17207 // store current object
17208 $this->startTransaction();
17209 // save this method vars
17210 $this_method_vars['html'] = $html;
17211 $this_method_vars['ln'] = $ln;
17212 $this_method_vars['fill'] = $fill;
17213 $this_method_vars['reseth'] = $reseth;
17214 $this_method_vars['cell'] = $cell;
17215 $this_method_vars['align'] = $align;
17216 $this_method_vars['gvars'] = $gvars;
17217 $this_method_vars['prevPage'] = $prevPage;
17218 $this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17219 $this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17220 $this_method_vars['prevlMargin'] = $prevlMargin;
17221 $this_method_vars['prevrMargin'] = $prevrMargin;
17222 $this_method_vars['curfontname'] = $curfontname;
17223 $this_method_vars['curfontstyle'] = $curfontstyle;
17224 $this_method_vars['curfontsize'] = $curfontsize;
17225 $this_method_vars['curfontascent'] = $curfontascent;
17226 $this_method_vars['curfontdescent'] = $curfontdescent;
17227 $this_method_vars['curfontstretcing'] = $curfontstretcing;
17228 $this_method_vars['curfonttracking'] = $curfonttracking;
17229 $this_method_vars['minstartliney'] = $minstartliney;
17230 $this_method_vars['maxbottomliney'] = $maxbottomliney;
17231 $this_method_vars['yshift'] = $yshift;
17232 $this_method_vars['startlinepage'] = $startlinepage;
17233 $this_method_vars['startlinepos'] = $startlinepos;
17234 $this_method_vars['startlinex'] = $startlinex;
17235 $this_method_vars['startliney'] = $startliney;
17236 $this_method_vars['newline'] = $newline;
17237 $this_method_vars['loop'] = $loop;
17238 $this_method_vars['curpos'] = $curpos;
17239 $this_method_vars['pask'] = $pask;
17240 $this_method_vars['lalign'] = $lalign;
17241 $this_method_vars['plalign'] = $plalign;
17242 $this_method_vars['w'] = $w;
17243 $this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17244 $this_method_vars['prev_listnum'] = $prev_listnum;
17245 $this_method_vars['prev_listordered'] = $prev_listordered;
17246 $this_method_vars['prev_listcount'] = $prev_listcount;
17247 $this_method_vars['prev_lispacer'] = $prev_lispacer;
17248 $this_method_vars['fontaligned'] = $fontaligned;
17249 $this_method_vars['key'] = $key;
17250 $this_method_vars['dom'] = $dom;
17253 // print THEAD block
17254 if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17255 if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17256 $this->inthead = true;
17257 // print table header (thead)
17258 $this->writeHTML($this->thead, false, false, false, false, '');
17259 // check if we are on a new page or on a new column
17260 if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
17261 // we are on a new page or on a new column and the total object height is less than the available vertical space.
17262 // restore previous object
17263 $this->rollbackTransaction(true);
17264 // restore previous values
17265 foreach ($this_method_vars as $vkey => $vval) {
17266 $$vkey = $vval;
17268 // disable table header
17269 $tmp_thead = $this->thead;
17270 $this->thead = '';
17271 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17272 $pre_y = $this->y;
17273 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
17274 // fix for multicolumn mode
17275 $startliney = $this->y;
17277 $this->start_transaction_page = $this->page;
17278 $this->start_transaction_y = $this->y;
17279 // restore table header
17280 $this->thead = $tmp_thead;
17281 // fix table border properties
17282 if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17283 $tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17284 } elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17285 $tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17286 } else {
17287 $tmp_cellspacing = 0;
17289 $dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
17290 $dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
17291 $dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
17292 $xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
17293 $dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
17294 $dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
17295 // print table header (thead)
17296 $this->writeHTML($this->thead, false, false, false, false, '');
17299 // move $key index forward to skip THEAD block
17300 while ( ($key < $maxel) AND (!(
17301 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17302 OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17303 ++$key;
17306 if ($dom[$key]['tag'] OR ($key == 0)) {
17307 if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17308 $dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
17310 // vertically align image in line
17311 if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17312 // get image height
17313 $imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k), 'px');
17314 $autolinebreak = false;
17315 if (!empty($dom[$key]['width'])) {
17316 $imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k), 'px', false);
17317 if (($imgw <= ($this->w - $this->lMargin - $this->rMargin - $this->cell_padding['L'] - $this->cell_padding['R']))
17318 AND ((($this->rtl) AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
17319 OR ((!$this->rtl) AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R']))))) {
17320 // add automatic line break
17321 $autolinebreak = true;
17322 $this->Ln('', $cell);
17323 if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17324 // go back to evaluate this line break
17325 --$key;
17329 if (!$autolinebreak) {
17330 if ($this->inPageBody()) {
17331 $pre_y = $this->y;
17332 // check for page break
17333 if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
17334 // fix for multicolumn mode
17335 $startliney = $this->y;
17338 if ($this->page > $startlinepage) {
17339 // fix line splitted over two pages
17340 if (isset($this->footerlen[$startlinepage])) {
17341 $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17343 // line to be moved one page forward
17344 $pagebuff = $this->getPageBuffer($startlinepage);
17345 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17346 $tstart = substr($pagebuff, 0, $startlinepos);
17347 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17348 // remove line from previous page
17349 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17350 $pagebuff = $this->getPageBuffer($this->page);
17351 $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17352 $tend = substr($pagebuff, $this->cntmrk[$this->page]);
17353 // add line start to current page
17354 $yshift = ($minstartliney - $this->y);
17355 if ($fontaligned) {
17356 $yshift += ($curfontsize / $this->k);
17358 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17359 $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17360 // shift the annotations and links
17361 if (isset($this->PageAnnots[$this->page])) {
17362 $next_pask = count($this->PageAnnots[$this->page]);
17363 } else {
17364 $next_pask = 0;
17366 if (isset($this->PageAnnots[$startlinepage])) {
17367 foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17368 if ($pak >= $pask) {
17369 $this->PageAnnots[$this->page][] = $pac;
17370 unset($this->PageAnnots[$startlinepage][$pak]);
17371 $npak = count($this->PageAnnots[$this->page]) - 1;
17372 $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17376 $pask = $next_pask;
17377 $startlinepos = $this->cntmrk[$this->page];
17378 $startlinepage = $this->page;
17379 $startliney = $this->y;
17380 $this->newline = false;
17382 $this->y += ($this->getCellHeight($curfontsize / $this->k) - ($curfontdescent * $this->cell_height_ratio) - $imgh);
17383 $minstartliney = min($this->y, $minstartliney);
17384 $maxbottomliney = ($startliney + $this->getCellHeight($curfontsize / $this->k));
17386 } elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17387 // account for different font size
17388 $pfontname = $curfontname;
17389 $pfontstyle = $curfontstyle;
17390 $pfontsize = $curfontsize;
17391 $fontname = (isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname);
17392 $fontstyle = (isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle);
17393 $fontsize = (isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize);
17394 $fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17395 $fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17396 if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17397 OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17398 OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17399 if (($key < ($maxel - 1)) AND (
17400 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17401 OR ($this->cell_height_ratio != $dom[$key]['line-height'])
17402 OR (!$this->newline AND is_numeric($fontsize) AND is_numeric($curfontsize) AND ($fontsize >= 0) AND ($curfontsize >= 0) AND ($fontsize != $curfontsize))
17403 )) {
17404 if ($this->page > $startlinepage) {
17405 // fix lines splitted over two pages
17406 if (isset($this->footerlen[$startlinepage])) {
17407 $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17409 // line to be moved one page forward
17410 $pagebuff = $this->getPageBuffer($startlinepage);
17411 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17412 $tstart = substr($pagebuff, 0, $startlinepos);
17413 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17414 // remove line start from previous page
17415 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17416 $pagebuff = $this->getPageBuffer($this->page);
17417 $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
17418 $tend = substr($pagebuff, $this->cntmrk[$this->page]);
17419 // add line start to current page
17420 $yshift = ($minstartliney - $this->y);
17421 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k));
17422 $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17423 // shift the annotations and links
17424 if (isset($this->PageAnnots[$this->page])) {
17425 $next_pask = count($this->PageAnnots[$this->page]);
17426 } else {
17427 $next_pask = 0;
17429 if (isset($this->PageAnnots[$startlinepage])) {
17430 foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
17431 if ($pak >= $pask) {
17432 $this->PageAnnots[$this->page][] = $pac;
17433 unset($this->PageAnnots[$startlinepage][$pak]);
17434 $npak = count($this->PageAnnots[$this->page]) - 1;
17435 $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
17439 $pask = $next_pask;
17440 $startlinepos = $this->cntmrk[$this->page];
17441 $startlinepage = $this->page;
17442 $startliney = $this->y;
17444 if (!isset($dom[$key]['line-height'])) {
17445 $dom[$key]['line-height'] = $this->cell_height_ratio;
17447 if (!$dom[$key]['block']) {
17448 if (!(isset($dom[($key + 1)]) AND $dom[($key + 1)]['tag'] AND (!$dom[($key + 1)]['opening']) AND ($dom[($key + 1)]['value'] != 'li') AND $dom[$key]['tag'] AND (!$dom[$key]['opening']))) {
17449 $this->y += (((($curfontsize * $this->cell_height_ratio) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
17451 if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17452 $current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17453 if (isset($line_align_data) AND (($line_align_data[0] == ($key - 1)) OR (($line_align_data[0] == ($key - 2)) AND (isset($dom[($key - 1)])) AND (preg_match('/^([\s]+)$/', $dom[($key - 1)]['value']) > 0)))) {
17454 $minstartliney = min($this->y, $line_align_data[1]);
17455 $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $line_align_data[2]);
17456 } else {
17457 $minstartliney = min($this->y, $minstartliney);
17458 $maxbottomliney = max(($this->y + $this->getCellHeight($fontsize / $this->k)), $maxbottomliney);
17460 $line_align_data = $current_line_align_data;
17463 $this->cell_height_ratio = $dom[$key]['line-height'];
17464 $fontaligned = true;
17466 $this->SetFont($fontname, $fontstyle, $fontsize);
17467 // reset row height
17468 $this->resetLastH();
17469 $curfontname = $fontname;
17470 $curfontstyle = $fontstyle;
17471 $curfontsize = $fontsize;
17472 $curfontascent = $fontascent;
17473 $curfontdescent = $fontdescent;
17476 // set text rendering mode
17477 $textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
17478 $textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
17479 $textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
17480 $this->setTextRenderingMode($textstroke, $textfill, $textclip);
17481 if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17482 $this->setFontStretching($dom[$key]['font-stretch']);
17484 if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17485 $this->setFontSpacing($dom[$key]['letter-spacing']);
17487 if (($plalign == 'J') AND $dom[$key]['block']) {
17488 $plalign = '';
17490 // get current position on page buffer
17491 $curpos = $this->pagelen[$startlinepage];
17492 if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17493 $this->SetFillColorArray($dom[$key]['bgcolor']);
17494 $wfill = true;
17495 } else {
17496 $wfill = $fill | false;
17498 if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17499 $this->SetTextColorArray($dom[$key]['fgcolor']);
17501 if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17502 $this->SetDrawColorArray($dom[$key]['strokecolor']);
17504 if (isset($dom[$key]['align'])) {
17505 $lalign = $dom[$key]['align'];
17507 if (TCPDF_STATIC::empty_string($lalign)) {
17508 $lalign = $align;
17511 // align lines
17512 if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17513 $newline = true;
17514 $fontaligned = false;
17515 // we are at the beginning of a new line
17516 if (isset($startlinex)) {
17517 $yshift = ($minstartliney - $startliney);
17518 if (($yshift > 0) OR ($this->page > $startlinepage)) {
17519 $yshift = 0;
17521 $t_x = 0;
17522 // the last line must be shifted to be aligned as requested
17523 $linew = abs($this->endlinex - $startlinex);
17524 if ($this->inxobj) {
17525 // we are inside an XObject template
17526 $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
17527 if (isset($opentagpos)) {
17528 $midpos = $opentagpos;
17529 } else {
17530 $midpos = 0;
17532 if ($midpos > 0) {
17533 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17534 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17535 } else {
17536 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17537 $pend = '';
17539 } else {
17540 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17541 if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17542 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17543 $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
17544 } elseif (isset($opentagpos)) {
17545 $midpos = $opentagpos;
17546 } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
17547 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
17548 $midpos = $this->footerpos[$startlinepage];
17549 } else {
17550 $midpos = 0;
17552 if ($midpos > 0) {
17553 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17554 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
17555 } else {
17556 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17557 $pend = '';
17560 if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17561 // calculate shifting amount
17562 $tw = $w;
17563 if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
17564 $tw += $this->cell_padding['R'];
17566 if ($this->lMargin != $prevlMargin) {
17567 $tw += ($prevlMargin - $this->lMargin);
17569 if ($this->rMargin != $prevrMargin) {
17570 $tw += ($prevrMargin - $this->rMargin);
17572 $one_space_width = $this->GetStringWidth(chr(32));
17573 $no = 0; // number of spaces on a line contained on a single block
17574 if ($this->isRTLTextDir()) { // RTL
17575 // remove left space if exist
17576 $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
17577 if ($pos1 > 0) {
17578 $pos1 = intval($pos1);
17579 if ($this->isUnicodeFont()) {
17580 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17581 $spacelen = 2;
17582 } else {
17583 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17584 $spacelen = 1;
17586 if ($pos1 == $pos2) {
17587 $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
17588 if (substr($pmid, $pos1, 4) == '[()]') {
17589 $linew -= $one_space_width;
17590 } elseif ($pos1 == strpos($pmid, '[(')) {
17591 $no = 1;
17595 } else { // LTR
17596 // remove right space if exist
17597 $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17598 if ($pos1 > 0) {
17599 $pos1 = intval($pos1);
17600 if ($this->isUnicodeFont()) {
17601 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17602 $spacelen = 2;
17603 } else {
17604 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17605 $spacelen = 1;
17607 if ($pos1 == $pos2) {
17608 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17609 $linew -= $one_space_width;
17613 $mdiff = ($tw - $linew);
17614 if ($plalign == 'C') {
17615 if ($this->rtl) {
17616 $t_x = -($mdiff / 2);
17617 } else {
17618 $t_x = ($mdiff / 2);
17620 } elseif ($plalign == 'R') {
17621 // right alignment on LTR document
17622 $t_x = $mdiff;
17623 } elseif ($plalign == 'L') {
17624 // left alignment on RTL document
17625 $t_x = -$mdiff;
17626 } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17627 // Justification
17628 if ($this->isRTLTextDir()) {
17629 // align text on the left
17630 $t_x = -$mdiff;
17632 $ns = 0; // number of spaces
17633 $pmidtemp = $pmid;
17634 // escape special characters
17635 $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17636 $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17637 // search spaces
17638 if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
17639 $spacestr = $this->getSpaceString();
17640 $maxkk = count($lnstring[1]) - 1;
17641 for ($kk=0; $kk <= $maxkk; ++$kk) {
17642 // restore special characters
17643 $lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17644 $lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17645 // store number of spaces on the strings
17646 $lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17647 // count total spaces on line
17648 $ns += $lnstring[2][$kk];
17649 $lnstring[3][$kk] = $ns;
17651 if ($ns == 0) {
17652 $ns = 1;
17654 // calculate additional space to add to each existing space
17655 $spacewidth = ($mdiff / ($ns - $no)) * $this->k;
17656 if ($this->FontSize <= 0) {
17657 $this->FontSize = 1;
17659 $spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
17660 if ($this->font_spacing != 0) {
17661 // fixed spacing mode
17662 $osw = -1000 * $this->font_spacing / $this->FontSize;
17663 $spacewidthu += $osw;
17665 $nsmax = $ns;
17666 $ns = 0;
17667 reset($lnstring);
17668 $offset = 0;
17669 $strcount = 0;
17670 $prev_epsposbeg = 0;
17671 $textpos = 0;
17672 if ($this->isRTLTextDir()) {
17673 $textpos = $this->wPt;
17675 while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
17676 // check if we are inside a string section '[( ... )]'
17677 $stroffset = strpos($pmid, '[(', $offset);
17678 if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17679 // set offset to the end of string section
17680 $offset = strpos($pmid, ')]', $stroffset);
17681 while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17682 $offset = strpos($pmid, ')]', ($offset + 1));
17684 if ($offset === false) {
17685 $this->Error('HTML Justification: malformed PDF code.');
17687 continue;
17689 if ($this->isRTLTextDir()) {
17690 $spacew = ($spacewidth * ($nsmax - $ns));
17691 } else {
17692 $spacew = ($spacewidth * $ns);
17694 $offset = $strpiece[2][1] + strlen($strpiece[2][0]);
17695 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
17696 $epsposend = strpos($pmid, $this->epsmarker.'Q', $offset) + strlen($this->epsmarker.'Q');
17697 if ((($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend))
17698 OR (($epsposbeg === false) AND ($epsposend > 0) AND ($offset < $epsposend))) {
17699 // shift EPS images
17700 $trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
17701 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
17702 $pmid_b = substr($pmid, 0, $epsposbeg);
17703 $pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
17704 $pmid_e = substr($pmid, $epsposend);
17705 $pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
17706 $offset = $epsposend;
17707 continue;
17710 $prev_epsposbeg = $epsposbeg;
17711 $currentxpos = 0;
17712 // shift blocks of code
17713 switch ($strpiece[2][0]) {
17714 case 'Td':
17715 case 'cm':
17716 case 'm':
17717 case 'l': {
17718 // get current X position
17719 preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
17720 if (!isset($xmatches[1])) {
17721 break;
17723 $currentxpos = $xmatches[1];
17724 $textpos = $currentxpos;
17725 if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
17726 $ns = $lnstring[3][$strcount];
17727 if ($this->isRTLTextDir()) {
17728 $spacew = ($spacewidth * ($nsmax - $ns));
17730 ++$strcount;
17732 // justify block
17733 if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
17734 $newpmid = sprintf('%F',(floatval($pmatch[1]) + $spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
17735 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17736 unset($pmatch, $newpmid);
17738 break;
17740 case 're': {
17741 // justify block
17742 if (!TCPDF_STATIC::empty_string($this->lispacer)) {
17743 $this->lispacer = '';
17744 continue;
17746 preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
17747 if (!isset($xmatches[1])) {
17748 break;
17750 $currentxpos = $xmatches[1];
17751 $x_diff = 0;
17752 $w_diff = 0;
17753 if ($this->isRTLTextDir()) { // RTL
17754 if ($currentxpos < $textpos) {
17755 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
17756 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17757 } else {
17758 if ($strcount > 0) {
17759 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
17760 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17763 } else { // LTR
17764 if ($currentxpos > $textpos) {
17765 if ($strcount > 0) {
17766 $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
17768 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17769 } else {
17770 if ($strcount > 1) {
17771 $x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
17773 if ($strcount > 0) {
17774 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17778 if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
17779 $newx = sprintf('%F',(floatval($pmatch[1]) + $x_diff));
17780 $neww = sprintf('%F',(floatval($pmatch[3]) + $w_diff));
17781 $newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
17782 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17783 unset($pmatch, $newpmid, $newx, $neww);
17785 break;
17787 case 'c': {
17788 // get current X position
17789 preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](c)([\s]*)/x', $pmid, $xmatches);
17790 if (!isset($xmatches[1])) {
17791 break;
17793 $currentxpos = $xmatches[1];
17794 // justify block
17795 if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$xmatches[4].')[\s]('.$xmatches[5].')[\s]('.$strpiece[1][0].')[\s](c)([\s]*)/x', $pmid, $pmatch) == 1) {
17796 $newx1 = sprintf('%F',(floatval($pmatch[1]) + $spacew));
17797 $newx2 = sprintf('%F',(floatval($pmatch[3]) + $spacew));
17798 $newx3 = sprintf('%F',(floatval($pmatch[5]) + $spacew));
17799 $newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
17800 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17801 unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
17803 break;
17806 // shift the annotations and links
17807 $cxpos = ($currentxpos / $this->k);
17808 $lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
17809 if ($this->inxobj) {
17810 // we are inside an XObject template
17811 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17812 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17813 if ($cxpos > $lmpos) {
17814 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
17815 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17816 } else {
17817 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17819 break;
17822 } elseif (isset($this->PageAnnots[$this->page])) {
17823 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17824 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
17825 if ($cxpos > $lmpos) {
17826 $this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
17827 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17828 } else {
17829 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
17831 break;
17835 } // end of while
17836 // remove markers
17837 $pmid = str_replace('x*#!#*x', '', $pmid);
17838 if ($this->isUnicodeFont()) {
17839 // multibyte characters
17840 $spacew = $spacewidthu;
17841 if ($this->font_stretching != 100) {
17842 // word spacing is affected by stretching
17843 $spacew /= ($this->font_stretching / 100);
17845 // escape special characters
17846 $pos = 0;
17847 $pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
17848 $pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
17849 if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
17850 foreach($pamatch[0] as $pk => $pmatch) {
17851 $replace = $pamatch[1][$pk];
17852 $replace = str_replace('#!#OP#!#', '(', $replace);
17853 $replace = str_replace('#!#CP#!#', ')', $replace);
17854 $newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
17855 $pos = strpos($pmid, $pmatch, $pos);
17856 if ($pos !== FALSE) {
17857 $pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
17859 ++$pos;
17861 unset($pamatch);
17863 if ($this->inxobj) {
17864 // we are inside an XObject template
17865 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
17866 } else {
17867 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
17869 $endlinepos = strlen($pstart."\n".$pmid."\n");
17870 } else {
17871 // non-unicode (single-byte characters)
17872 if ($this->font_stretching != 100) {
17873 // word spacing (Tw) is affected by stretching
17874 $spacewidth /= ($this->font_stretching / 100);
17876 $rs = sprintf('%F Tw', $spacewidth);
17877 $pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
17878 if ($this->inxobj) {
17879 // we are inside an XObject template
17880 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
17881 } else {
17882 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
17884 $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
17887 } // end of J
17888 } // end if $startlinex
17889 if (($t_x != 0) OR ($yshift < 0)) {
17890 // shift the line
17891 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
17892 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
17893 $endlinepos = strlen($pstart);
17894 if ($this->inxobj) {
17895 // we are inside an XObject template
17896 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
17897 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
17898 if ($pak >= $pask) {
17899 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
17900 $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
17903 } else {
17904 $this->setPageBuffer($startlinepage, $pstart.$pend);
17905 // shift the annotations and links
17906 if (isset($this->PageAnnots[$this->page])) {
17907 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
17908 if ($pak >= $pask) {
17909 $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
17910 $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
17915 $this->y -= $yshift;
17918 $pbrk = $this->checkPageBreak($this->lasth);
17919 $this->newline = false;
17920 $startlinex = $this->x;
17921 $startliney = $this->y;
17922 if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
17923 $startliney -= ((0.3 * $this->FontSizePt) / $this->k);
17924 } elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
17925 $startliney -= (($this->FontSizePt / 0.7) / $this->k);
17926 } else {
17927 $minstartliney = $startliney;
17928 $maxbottomliney = ($this->y + $this->getCellHeight($fontsize / $this->k));
17930 $startlinepage = $this->page;
17931 if (isset($endlinepos) AND (!$pbrk)) {
17932 $startlinepos = $endlinepos;
17933 } else {
17934 if ($this->inxobj) {
17935 // we are inside an XObject template
17936 $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
17937 } elseif (!$this->InFooter) {
17938 if (isset($this->footerlen[$this->page])) {
17939 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
17940 } else {
17941 $this->footerpos[$this->page] = $this->pagelen[$this->page];
17943 $startlinepos = $this->footerpos[$this->page];
17944 } else {
17945 $startlinepos = $this->pagelen[$this->page];
17948 unset($endlinepos);
17949 $plalign = $lalign;
17950 if (isset($this->PageAnnots[$this->page])) {
17951 $pask = count($this->PageAnnots[$this->page]);
17952 } else {
17953 $pask = 0;
17955 if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
17956 AND (isset($this->emptypagemrk[$this->page]))
17957 AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
17958 $this->SetFont($fontname, $fontstyle, $fontsize);
17959 if ($wfill) {
17960 $this->SetFillColorArray($this->bgcolor);
17963 } // end newline
17964 if (isset($opentagpos)) {
17965 unset($opentagpos);
17967 if ($dom[$key]['tag']) {
17968 if ($dom[$key]['opening']) {
17969 // get text indentation (if any)
17970 if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
17971 $this->textindent = $dom[$key]['text-indent'];
17972 $this->newline = true;
17974 // table
17975 if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
17976 // available page width
17977 if ($this->rtl) {
17978 $wtmp = $this->x - $this->lMargin;
17979 } else {
17980 $wtmp = $this->w - $this->rMargin - $this->x;
17982 // get cell spacing
17983 if (isset($dom[$key]['attribute']['cellspacing'])) {
17984 $clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
17985 $cellspacing = array('H' => $clsp, 'V' => $clsp);
17986 } elseif (isset($dom[$key]['border-spacing'])) {
17987 $cellspacing = $dom[$key]['border-spacing'];
17988 } else {
17989 $cellspacing = array('H' => 0, 'V' => 0);
17991 // table width
17992 if (isset($dom[$key]['width'])) {
17993 $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
17994 } else {
17995 $table_width = $wtmp;
17997 $table_width -= (2 * $cellspacing['H']);
17998 if (!$this->inthead) {
17999 $this->y += $cellspacing['V'];
18001 if ($this->rtl) {
18002 $cellspacingx = -$cellspacing['H'];
18003 } else {
18004 $cellspacingx = $cellspacing['H'];
18006 // total table width without cellspaces
18007 $table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18008 // minimum column width
18009 $table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18010 // array of custom column widths
18011 $table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18013 // table row
18014 if ($dom[$key]['value'] == 'tr') {
18015 // reset column counter
18016 $colid = 0;
18018 // table cell
18019 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18020 $trid = $dom[$key]['parent'];
18021 $table_el = $dom[$trid]['parent'];
18022 if (!isset($dom[$table_el]['cols'])) {
18023 $dom[$table_el]['cols'] = $dom[$trid]['cols'];
18025 // store border info
18026 $tdborder = 0;
18027 if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18028 $tdborder = $dom[$key]['border'];
18030 $colspan = intval($dom[$key]['attribute']['colspan']);
18031 if ($colspan <= 0) {
18032 $colspan = 1;
18034 $old_cell_padding = $this->cell_padding;
18035 if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18036 $crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18037 $current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18038 } elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18039 $current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18040 } else {
18041 $current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18043 $this->cell_padding = $current_cell_padding;
18044 if (isset($dom[$key]['height'])) {
18045 // minimum cell height
18046 $cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18047 } else {
18048 $cellh = 0;
18050 if (isset($dom[$key]['content'])) {
18051 $cell_content = stripslashes($dom[$key]['content']);
18052 } else {
18053 $cell_content = '&nbsp;';
18055 $tagtype = $dom[$key]['value'];
18056 $parentid = $key;
18057 while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18058 // move $key index forward
18059 ++$key;
18061 if (!isset($dom[$trid]['startpage'])) {
18062 $dom[$trid]['startpage'] = $this->page;
18063 } else {
18064 $this->setPage($dom[$trid]['startpage']);
18066 if (!isset($dom[$trid]['startcolumn'])) {
18067 $dom[$trid]['startcolumn'] = $this->current_column;
18068 } elseif ($this->current_column != $dom[$trid]['startcolumn']) {
18069 $tmpx = $this->x;
18070 $this->selectColumn($dom[$trid]['startcolumn']);
18071 $this->x = $tmpx;
18073 if (!isset($dom[$trid]['starty'])) {
18074 $dom[$trid]['starty'] = $this->y;
18075 } else {
18076 $this->y = $dom[$trid]['starty'];
18078 if (!isset($dom[$trid]['startx'])) {
18079 $dom[$trid]['startx'] = $this->x;
18080 $this->x += $cellspacingx;
18081 } else {
18082 $this->x += ($cellspacingx / 2);
18084 if (isset($dom[$parentid]['attribute']['rowspan'])) {
18085 $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18086 } else {
18087 $rowspan = 1;
18089 // skip row-spanned cells started on the previous rows
18090 if (isset($dom[$table_el]['rowspans'])) {
18091 $rsk = 0;
18092 $rskmax = count($dom[$table_el]['rowspans']);
18093 while ($rsk < $rskmax) {
18094 $trwsp = $dom[$table_el]['rowspans'][$rsk];
18095 $rsstartx = $trwsp['startx'];
18096 $rsendx = $trwsp['endx'];
18097 // account for margin changes
18098 if ($trwsp['startpage'] < $this->page) {
18099 if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
18100 $dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
18101 $rsstartx -= $dl;
18102 $rsendx -= $dl;
18103 } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
18104 $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
18105 $rsstartx += $dl;
18106 $rsendx += $dl;
18109 if (($trwsp['rowspan'] > 0)
18110 AND ($rsstartx > ($this->x - $cellspacing['H'] - $current_cell_padding['L'] - $this->feps))
18111 AND ($rsstartx < ($this->x + $cellspacing['H'] + $current_cell_padding['R'] + $this->feps))
18112 AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
18113 // set the starting X position of the current cell
18114 $this->x = $rsendx + $cellspacingx;
18115 // increment column indicator
18116 $colid += $trwsp['colspan'];
18117 if (($trwsp['rowspan'] == 1)
18118 AND (isset($dom[$trid]['endy']))
18119 AND (isset($dom[$trid]['endpage']))
18120 AND (isset($dom[$trid]['endcolumn']))
18121 AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18122 AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18123 // set ending Y position for row
18124 $dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18125 $dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18127 $rsk = 0;
18128 } else {
18129 ++$rsk;
18133 if (isset($dom[$parentid]['width'])) {
18134 // user specified width
18135 $cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18136 $tmpcw = ($cellw / $colspan);
18137 for ($i = 0; $i < $colspan; ++$i) {
18138 $table_colwidths[($colid + $i)] = $tmpcw;
18140 } else {
18141 // inherit column width
18142 $cellw = 0;
18143 for ($i = 0; $i < $colspan; ++$i) {
18144 $cellw += $table_colwidths[($colid + $i)];
18147 $cellw += (($colspan - 1) * $cellspacing['H']);
18148 // increment column indicator
18149 $colid += $colspan;
18150 // add rowspan information to table element
18151 if ($rowspan > 1) {
18152 $trsid = array_push($dom[$table_el]['rowspans'], array('trid' => $trid, 'rowspan' => $rowspan, 'mrowspan' => $rowspan, 'colspan' => $colspan, 'startpage' => $this->page, 'startcolumn' => $this->current_column, 'startx' => $this->x, 'starty' => $this->y));
18154 $cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
18155 if ($rowspan > 1) {
18156 $dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18158 // push background colors
18159 if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18160 $dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18162 // store border info
18163 if (isset($tdborder) AND !empty($tdborder)) {
18164 $dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18166 $prevLastH = $this->lasth;
18167 // store some info for multicolumn mode
18168 if ($this->rtl) {
18169 $this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18170 } else {
18171 $this->colxshift['x'] = $this->x - $this->lMargin;
18173 $this->colxshift['s'] = $cellspacing;
18174 $this->colxshift['p'] = $current_cell_padding;
18175 // ****** write the cell content ******
18176 $this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18177 // restore some values
18178 $this->colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18179 $this->lasth = $prevLastH;
18180 $this->cell_padding = $old_cell_padding;
18181 $dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
18182 // update the end of row position
18183 if ($rowspan <= 1) {
18184 if (isset($dom[$trid]['endy'])) {
18185 if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
18186 $dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
18187 } elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
18188 $dom[$trid]['endy'] = $this->y;
18190 } else {
18191 $dom[$trid]['endy'] = $this->y;
18193 if (isset($dom[$trid]['endpage'])) {
18194 $dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18195 } else {
18196 $dom[$trid]['endpage'] = $this->page;
18198 if (isset($dom[$trid]['endcolumn'])) {
18199 $dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18200 } else {
18201 $dom[$trid]['endcolumn'] = $this->current_column;
18203 } else {
18204 // account for row-spanned cells
18205 $dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
18206 $dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
18207 $dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
18208 $dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
18210 if (isset($dom[$table_el]['rowspans'])) {
18211 // update endy and endpage on rowspanned cells
18212 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18213 if ($trwsp['rowspan'] > 0) {
18214 if (isset($dom[$trid]['endpage'])) {
18215 if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18216 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18217 } elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18218 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18219 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18220 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18221 } else {
18222 $dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18228 $this->x += ($cellspacingx / 2);
18229 } else {
18230 // opening tag (or self-closing tag)
18231 if (!isset($opentagpos)) {
18232 if ($this->inxobj) {
18233 // we are inside an XObject template
18234 $opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
18235 } elseif (!$this->InFooter) {
18236 if (isset($this->footerlen[$this->page])) {
18237 $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
18238 } else {
18239 $this->footerpos[$this->page] = $this->pagelen[$this->page];
18241 $opentagpos = $this->footerpos[$this->page];
18244 $dom = $this->openHTMLTagHandler($dom, $key, $cell);
18246 } else { // closing tag
18247 $prev_numpages = $this->numpages;
18248 $old_bordermrk = $this->bordermrk[$this->page];
18249 $dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18250 if ($this->bordermrk[$this->page] > $old_bordermrk) {
18251 $startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
18253 if ($prev_numpages > $this->numpages) {
18254 $startlinepage = $this->page;
18257 } elseif (strlen($dom[$key]['value']) > 0) {
18258 // print list-item
18259 if (!TCPDF_STATIC::empty_string($this->lispacer) AND ($this->lispacer != '^')) {
18260 $this->SetFont($pfontname, $pfontstyle, $pfontsize);
18261 $this->resetLastH();
18262 $minstartliney = $this->y;
18263 $maxbottomliney = ($startliney + $this->getCellHeight($this->FontSize));
18264 if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18265 $this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
18267 $this->SetFont($curfontname, $curfontstyle, $curfontsize);
18268 $this->resetLastH();
18269 if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18270 $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18271 $pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18272 $this->y += ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
18273 $minstartliney = min($this->y, $minstartliney);
18274 $maxbottomliney = max(($this->y + $this->getCellHeight($pfontsize / $this->k)), $maxbottomliney);
18277 // text
18278 $this->htmlvspace = 0;
18279 if ((!$this->premode) AND $this->isRTLTextDir()) {
18280 // reverse spaces order
18281 $lsp = ''; // left spaces
18282 $rsp = ''; // right spaces
18283 if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18284 $lsp = $matches[1];
18286 if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
18287 $rsp = $matches[1];
18289 $dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18291 if ($newline) {
18292 if (!$this->premode) {
18293 $prelen = strlen($dom[$key]['value']);
18294 if ($this->isRTLTextDir()) {
18295 // right trim except non-breaking space
18296 $dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18297 } else {
18298 // left trim except non-breaking space
18299 $dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18301 $postlen = strlen($dom[$key]['value']);
18302 if (($postlen == 0) AND ($prelen > 0)) {
18303 $dom[$key]['trimmed_space'] = true;
18306 $newline = false;
18307 $firstblock = true;
18308 } else {
18309 $firstblock = false;
18310 // replace empty multiple spaces string with a single space
18311 $dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
18313 $strrest = '';
18314 if ($this->rtl) {
18315 $this->x -= $this->textindent;
18316 } else {
18317 $this->x += $this->textindent;
18319 if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18320 $strlinelen = $this->GetStringWidth($dom[$key]['value']);
18321 if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
18322 // HTML <a> Link
18323 $hrefcolor = '';
18324 if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18325 $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18327 $hrefstyle = -1;
18328 if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18329 $hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18331 $strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18332 } else {
18333 $wadj = 0; // space to leave for block continuity
18334 if ($this->rtl) {
18335 $cwa = ($this->x - $this->lMargin);
18336 } else {
18337 $cwa = ($this->w - $this->rMargin - $this->x);
18339 if (($strlinelen < $cwa) AND (isset($dom[($key + 1)])) AND ($dom[($key + 1)]['tag']) AND (!$dom[($key + 1)]['block'])) {
18340 // check the next text blocks for continuity
18341 $nkey = ($key + 1);
18342 $write_block = true;
18343 $same_textdir = true;
18344 $tmp_fontname = $this->FontFamily;
18345 $tmp_fontstyle = $this->FontStyle;
18346 $tmp_fontsize = $this->FontSizePt;
18347 while ($write_block AND isset($dom[$nkey])) {
18348 if ($dom[$nkey]['tag']) {
18349 if ($dom[$nkey]['block']) {
18350 // end of block
18351 $write_block = false;
18353 $tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
18354 $tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
18355 $tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
18356 $same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18357 } else {
18358 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'+/', $this->re_space['m'], $dom[$nkey]['value']);
18359 if (isset($nextstr[0]) AND $same_textdir) {
18360 $wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18361 if (isset($nextstr[1])) {
18362 $write_block = false;
18366 ++$nkey;
18369 if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18370 $wadj = 0;
18371 $nextstr = TCPDF_STATIC::pregSplit('/'.$this->re_space['p'].'/', $this->re_space['m'], $dom[$key]['value']);
18372 $numblks = count($nextstr);
18373 if ($numblks > 1) {
18374 // try to split on blank spaces
18375 $wadj = ($cwa - $strlinelen + $this->GetStringWidth($nextstr[($numblks - 1)]));
18376 } else {
18377 // set the entire block on new line
18378 $wadj = $this->GetStringWidth($nextstr[0]);
18381 // check for reversed text direction
18382 if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl === 'L')) OR (!$this->rtl AND ($this->tmprtl === 'R')))) {
18383 // LTR text on RTL direction or RTL text on LTR direction
18384 $reverse_dir = true;
18385 $this->rtl = !$this->rtl;
18386 $revshift = ($strlinelen + $wadj + 0.000001); // add little quantity for rounding problems
18387 if ($this->rtl) {
18388 $this->x += $revshift;
18389 } else {
18390 $this->x -= $revshift;
18392 $xws = $this->x;
18394 // ****** write only until the end of the line and get the rest ******
18395 $strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18396 // restore default direction
18397 if ($reverse_dir AND ($wadj == 0)) {
18398 $this->x = $xws;
18399 $this->rtl = !$this->rtl;
18400 $reverse_dir = false;
18404 $this->textindent = 0;
18405 if (strlen($strrest) > 0) {
18406 // store the remaining string on the previous $key position
18407 $this->newline = true;
18408 if ($strrest == $dom[$key]['value']) {
18409 // used to avoid infinite loop
18410 ++$loop;
18411 } else {
18412 $loop = 0;
18414 $dom[$key]['value'] = $strrest;
18415 if ($cell) {
18416 if ($this->rtl) {
18417 $this->x -= $this->cell_padding['R'];
18418 } else {
18419 $this->x += $this->cell_padding['L'];
18422 if ($loop < 3) {
18423 --$key;
18425 } else {
18426 $loop = 0;
18427 // add the positive font spacing of the last character (if any)
18428 if ($this->font_spacing > 0) {
18429 if ($this->rtl) {
18430 $this->x -= $this->font_spacing;
18431 } else {
18432 $this->x += $this->font_spacing;
18437 ++$key;
18438 if (isset($dom[$key]['tag']) AND $dom[$key]['tag'] AND (!isset($dom[$key]['opening']) OR !$dom[$key]['opening']) AND isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
18439 // check if we are on a new page or on a new column
18440 if ((!$undo) AND (($this->y < $this->start_transaction_y) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y)))) {
18441 // we are on a new page or on a new column and the total object height is less than the available vertical space.
18442 // restore previous object
18443 $this->rollbackTransaction(true);
18444 // restore previous values
18445 foreach ($this_method_vars as $vkey => $vval) {
18446 $$vkey = $vval;
18448 // add a page (or trig AcceptPageBreak() for multicolumn mode)
18449 $pre_y = $this->y;
18450 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18451 $startliney = $this->y;
18453 $undo = true; // avoid infinite loop
18454 } else {
18455 $undo = false;
18458 } // end for each $key
18459 // align the last line
18460 if (isset($startlinex)) {
18461 $yshift = ($minstartliney - $startliney);
18462 if (($yshift > 0) OR ($this->page > $startlinepage)) {
18463 $yshift = 0;
18465 $t_x = 0;
18466 // the last line must be shifted to be aligned as requested
18467 $linew = abs($this->endlinex - $startlinex);
18468 if ($this->inxobj) {
18469 // we are inside an XObject template
18470 $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
18471 if (isset($opentagpos)) {
18472 $midpos = $opentagpos;
18473 } else {
18474 $midpos = 0;
18476 if ($midpos > 0) {
18477 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18478 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18479 } else {
18480 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18481 $pend = '';
18483 } else {
18484 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18485 if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18486 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18487 $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
18488 } elseif (isset($opentagpos)) {
18489 $midpos = $opentagpos;
18490 } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
18491 $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
18492 $midpos = $this->footerpos[$startlinepage];
18493 } else {
18494 $midpos = 0;
18496 if ($midpos > 0) {
18497 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18498 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
18499 } else {
18500 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18501 $pend = '';
18504 if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18505 // calculate shifting amount
18506 $tw = $w;
18507 if ($this->lMargin != $prevlMargin) {
18508 $tw += ($prevlMargin - $this->lMargin);
18510 if ($this->rMargin != $prevrMargin) {
18511 $tw += ($prevrMargin - $this->rMargin);
18513 $one_space_width = $this->GetStringWidth(chr(32));
18514 $no = 0; // number of spaces on a line contained on a single block
18515 if ($this->isRTLTextDir()) { // RTL
18516 // remove left space if exist
18517 $pos1 = TCPDF_STATIC::revstrpos($pmid, '[(');
18518 if ($pos1 > 0) {
18519 $pos1 = intval($pos1);
18520 if ($this->isUnicodeFont()) {
18521 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18522 $spacelen = 2;
18523 } else {
18524 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18525 $spacelen = 1;
18527 if ($pos1 == $pos2) {
18528 $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
18529 if (substr($pmid, $pos1, 4) == '[()]') {
18530 $linew -= $one_space_width;
18531 } elseif ($pos1 == strpos($pmid, '[(')) {
18532 $no = 1;
18536 } else { // LTR
18537 // remove right space if exist
18538 $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18539 if ($pos1 > 0) {
18540 $pos1 = intval($pos1);
18541 if ($this->isUnicodeFont()) {
18542 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18543 $spacelen = 2;
18544 } else {
18545 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18546 $spacelen = 1;
18548 if ($pos1 == $pos2) {
18549 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18550 $linew -= $one_space_width;
18554 $mdiff = ($tw - $linew);
18555 if ($plalign == 'C') {
18556 if ($this->rtl) {
18557 $t_x = -($mdiff / 2);
18558 } else {
18559 $t_x = ($mdiff / 2);
18561 } elseif ($plalign == 'R') {
18562 // right alignment on LTR document
18563 $t_x = $mdiff;
18564 } elseif ($plalign == 'L') {
18565 // left alignment on RTL document
18566 $t_x = -$mdiff;
18568 } // end if startlinex
18569 if (($t_x != 0) OR ($yshift < 0)) {
18570 // shift the line
18571 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k), ($yshift * $this->k));
18572 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18573 $endlinepos = strlen($pstart);
18574 if ($this->inxobj) {
18575 // we are inside an XObject template
18576 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
18577 foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
18578 if ($pak >= $pask) {
18579 $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
18580 $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
18583 } else {
18584 $this->setPageBuffer($startlinepage, $pstart.$pend);
18585 // shift the annotations and links
18586 if (isset($this->PageAnnots[$this->page])) {
18587 foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
18588 if ($pak >= $pask) {
18589 $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
18590 $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
18595 $this->y -= $yshift;
18596 $yshift = 0;
18599 // restore previous values
18600 $this->setGraphicVars($gvars);
18601 if ($this->num_columns > 1) {
18602 $this->selectColumn();
18603 } elseif ($this->page > $prevPage) {
18604 $this->lMargin = $this->pagedim[$this->page]['olm'];
18605 $this->rMargin = $this->pagedim[$this->page]['orm'];
18607 // restore previous list state
18608 $this->cell_height_ratio = $prev_cell_height_ratio;
18609 $this->listnum = $prev_listnum;
18610 $this->listordered = $prev_listordered;
18611 $this->listcount = $prev_listcount;
18612 $this->lispacer = $prev_lispacer;
18613 if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18614 $this->Ln($this->lasth);
18615 if ($this->y < $maxbottomliney) {
18616 $this->y = $maxbottomliney;
18619 unset($dom);
18623 * Process opening tags.
18624 * @param $dom (array) html dom array
18625 * @param $key (int) current element id
18626 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
18627 * @return $dom array
18628 * @protected
18630 protected function openHTMLTagHandler($dom, $key, $cell) {
18631 $tag = $dom[$key];
18632 $parent = $dom[($dom[$key]['parent'])];
18633 $firsttag = ($key == 1);
18634 // check for text direction attribute
18635 if (isset($tag['dir'])) {
18636 $this->setTempRTL($tag['dir']);
18637 } else {
18638 $this->tmprtl = false;
18640 if ($tag['block']) {
18641 $hbz = 0; // distance from y to line bottom
18642 $hb = 0; // vertical space between block tags
18643 // calculate vertical space for block tags
18644 if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
18645 $cur_h = $this->tagvspaces[$tag['value']][0]['h'];
18646 } elseif (isset($tag['fontsize'])) {
18647 $cur_h = $this->getCellHeight($tag['fontsize'] / $this->k);
18648 } else {
18649 $cur_h = $this->getCellHeight($this->FontSize);
18651 if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
18652 $n = $this->tagvspaces[$tag['value']][0]['n'];
18653 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18654 $n = 0.6;
18655 } else {
18656 $n = 1;
18658 if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br')))) {
18659 $hb = 0;
18660 } else {
18661 $hb = ($n * $cur_h);
18663 if (($this->htmlvspace <= 0) AND ($n > 0)) {
18664 if (isset($parent['fontsize'])) {
18665 $hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
18666 } else {
18667 $hbz = $this->getCellHeight($this->FontSize);
18670 if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18671 // fix vertical space after table
18672 $hbz = 0;
18675 // Opening tag
18676 switch($tag['value']) {
18677 case 'table': {
18678 $cp = 0;
18679 $cs = 0;
18680 $dom[$key]['rowspans'] = array();
18681 if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
18682 $this->htmlvspace = 0;
18683 // set table header
18684 if (!TCPDF_STATIC::empty_string($dom[$key]['thead'])) {
18685 // set table header
18686 $this->thead = $dom[$key]['thead'];
18687 if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
18688 $this->theadMargins = array();
18689 $this->theadMargins['cell_padding'] = $this->cell_padding;
18690 $this->theadMargins['lmargin'] = $this->lMargin;
18691 $this->theadMargins['rmargin'] = $this->rMargin;
18692 $this->theadMargins['page'] = $this->page;
18693 $this->theadMargins['cell'] = $cell;
18694 $this->theadMargins['gvars'] = $this->getGraphicVars();
18698 // store current margins and page
18699 $dom[$key]['old_cell_padding'] = $this->cell_padding;
18700 if (isset($tag['attribute']['cellpadding'])) {
18701 $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
18702 $this->SetCellPadding($pad);
18703 } elseif (isset($tag['padding'])) {
18704 $this->cell_padding = $tag['padding'];
18706 if (isset($tag['attribute']['cellspacing'])) {
18707 $cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
18708 } elseif (isset($tag['border-spacing'])) {
18709 $cs = $tag['border-spacing']['V'];
18711 $prev_y = $this->y;
18712 if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
18713 $this->inthead = true;
18714 // add a page (or trig AcceptPageBreak() for multicolumn mode)
18715 $this->checkPageBreak($this->PageBreakTrigger + 1);
18717 break;
18719 case 'tr': {
18720 // array of columns positions
18721 $dom[$key]['cellpos'] = array();
18722 break;
18724 case 'hr': {
18725 if ((isset($tag['height'])) AND ($tag['height'] != '')) {
18726 $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
18727 } else {
18728 $hrHeight = $this->GetLineWidth();
18730 $this->addHTMLVertSpace($hbz, ($hrHeight / 2), $cell, $firsttag);
18731 $x = $this->GetX();
18732 $y = $this->GetY();
18733 $wtmp = $this->w - $this->lMargin - $this->rMargin;
18734 if ($cell) {
18735 $wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
18737 if ((isset($tag['width'])) AND ($tag['width'] != '')) {
18738 $hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
18739 } else {
18740 $hrWidth = $wtmp;
18742 $prevlinewidth = $this->GetLineWidth();
18743 $this->SetLineWidth($hrHeight);
18744 $this->Line($x, $y, $x + $hrWidth, $y);
18745 $this->SetLineWidth($prevlinewidth);
18746 $this->addHTMLVertSpace(($hrHeight / 2), 0, $cell, !isset($dom[($key + 1)]));
18747 break;
18749 case 'a': {
18750 if (array_key_exists('href', $tag['attribute'])) {
18751 $this->HREF['url'] = $tag['attribute']['href'];
18753 break;
18755 case 'img': {
18756 if (!empty($tag['attribute']['src'])) {
18757 if ($tag['attribute']['src'][0] === '@') {
18758 // data stream
18759 $tag['attribute']['src'] = '@'.base64_decode(substr($tag['attribute']['src'], 1));
18760 $type = '';
18761 } else {
18762 // get image type
18763 $type = TCPDF_IMAGES::getImageFileType($tag['attribute']['src']);
18765 if (!isset($tag['width'])) {
18766 $tag['width'] = 0;
18768 if (!isset($tag['height'])) {
18769 $tag['height'] = 0;
18771 //if (!isset($tag['attribute']['align'])) {
18772 // the only alignment supported is "bottom"
18773 // further development is required for other modes.
18774 $tag['attribute']['align'] = 'bottom';
18776 switch($tag['attribute']['align']) {
18777 case 'top': {
18778 $align = 'T';
18779 break;
18781 case 'middle': {
18782 $align = 'M';
18783 break;
18785 case 'bottom': {
18786 $align = 'B';
18787 break;
18789 default: {
18790 $align = 'B';
18791 break;
18794 $prevy = $this->y;
18795 $xpos = $this->x;
18796 $imglink = '';
18797 if (isset($this->HREF['url']) AND !TCPDF_STATIC::empty_string($this->HREF['url'])) {
18798 $imglink = $this->HREF['url'];
18799 if ($imglink[0] == '#') {
18800 // convert url to internal link
18801 $lnkdata = explode(',', $imglink);
18802 if (isset($lnkdata[0])) {
18803 $page = intval(substr($lnkdata[0], 1));
18804 if (empty($page) OR ($page <= 0)) {
18805 $page = $this->page;
18807 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
18808 $lnky = floatval($lnkdata[1]);
18809 } else {
18810 $lnky = 0;
18812 $imglink = $this->AddLink();
18813 $this->SetLink($imglink, $lnky, $page);
18817 $border = 0;
18818 if (isset($tag['border']) AND !empty($tag['border'])) {
18819 // currently only support 1 (frame) or a combination of 'LTRB'
18820 $border = $tag['border'];
18822 $iw = '';
18823 if (isset($tag['width'])) {
18824 $iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
18826 $ih = '';
18827 if (isset($tag['height'])) {
18828 $ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k), 'px', false);
18830 if (($type == 'eps') OR ($type == 'ai')) {
18831 $this->ImageEps($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
18832 } elseif ($type == 'svg') {
18833 $this->ImageSVG($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
18834 } else {
18835 $this->Image($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
18837 switch($align) {
18838 case 'T': {
18839 $this->y = $prevy;
18840 break;
18842 case 'M': {
18843 $this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
18844 break;
18846 case 'B': {
18847 $this->y = $this->img_rb_y - ($this->getCellHeight($tag['fontsize'] / $this->k) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio));
18848 break;
18852 break;
18854 case 'dl': {
18855 ++$this->listnum;
18856 if ($this->listnum == 1) {
18857 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18858 } else {
18859 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
18861 break;
18863 case 'dt': {
18864 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18865 break;
18867 case 'dd': {
18868 if ($this->rtl) {
18869 $this->rMargin += $this->listindent;
18870 } else {
18871 $this->lMargin += $this->listindent;
18873 ++$this->listindentlevel;
18874 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18875 break;
18877 case 'ul':
18878 case 'ol': {
18879 ++$this->listnum;
18880 if ($tag['value'] == 'ol') {
18881 $this->listordered[$this->listnum] = true;
18882 } else {
18883 $this->listordered[$this->listnum] = false;
18885 if (isset($tag['attribute']['start'])) {
18886 $this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
18887 } else {
18888 $this->listcount[$this->listnum] = 0;
18890 if ($this->rtl) {
18891 $this->rMargin += $this->listindent;
18892 $this->x -= $this->listindent;
18893 } else {
18894 $this->lMargin += $this->listindent;
18895 $this->x += $this->listindent;
18897 ++$this->listindentlevel;
18898 if ($this->listnum == 1) {
18899 if ($key > 1) {
18900 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18902 } else {
18903 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
18905 break;
18907 case 'li': {
18908 if ($key > 2) {
18909 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18911 if ($this->listordered[$this->listnum]) {
18912 // ordered item
18913 if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
18914 $this->lispacer = $parent['attribute']['type'];
18915 } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
18916 $this->lispacer = $parent['listtype'];
18917 } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
18918 $this->lispacer = $this->lisymbol;
18919 } else {
18920 $this->lispacer = '#';
18922 ++$this->listcount[$this->listnum];
18923 if (isset($tag['attribute']['value'])) {
18924 $this->listcount[$this->listnum] = intval($tag['attribute']['value']);
18926 } else {
18927 // unordered item
18928 if (isset($parent['attribute']['type']) AND !TCPDF_STATIC::empty_string($parent['attribute']['type'])) {
18929 $this->lispacer = $parent['attribute']['type'];
18930 } elseif (isset($parent['listtype']) AND !TCPDF_STATIC::empty_string($parent['listtype'])) {
18931 $this->lispacer = $parent['listtype'];
18932 } elseif (isset($this->lisymbol) AND !TCPDF_STATIC::empty_string($this->lisymbol)) {
18933 $this->lispacer = $this->lisymbol;
18934 } else {
18935 $this->lispacer = '!';
18938 break;
18940 case 'blockquote': {
18941 if ($this->rtl) {
18942 $this->rMargin += $this->listindent;
18943 } else {
18944 $this->lMargin += $this->listindent;
18946 ++$this->listindentlevel;
18947 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18948 break;
18950 case 'br': {
18951 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18952 break;
18954 case 'div': {
18955 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18956 break;
18958 case 'p': {
18959 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18960 break;
18962 case 'pre': {
18963 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18964 $this->premode = true;
18965 break;
18967 case 'sup': {
18968 $this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
18969 break;
18971 case 'sub': {
18972 $this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
18973 break;
18975 case 'h1':
18976 case 'h2':
18977 case 'h3':
18978 case 'h4':
18979 case 'h5':
18980 case 'h6': {
18981 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
18982 break;
18984 // Form fields (since 4.8.000 - 2009-09-07)
18985 case 'form': {
18986 if (isset($tag['attribute']['action'])) {
18987 $this->form_action = $tag['attribute']['action'];
18988 } else {
18989 $this->Error('Please explicitly set action attribute path!');
18991 if (isset($tag['attribute']['enctype'])) {
18992 $this->form_enctype = $tag['attribute']['enctype'];
18993 } else {
18994 $this->form_enctype = 'application/x-www-form-urlencoded';
18996 if (isset($tag['attribute']['method'])) {
18997 $this->form_mode = $tag['attribute']['method'];
18998 } else {
18999 $this->form_mode = 'post';
19001 break;
19003 case 'input': {
19004 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19005 $name = $tag['attribute']['name'];
19006 } else {
19007 break;
19009 $prop = array();
19010 $opt = array();
19011 if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19012 $prop['readonly'] = true;
19014 if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19015 $value = $tag['attribute']['value'];
19017 if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC::empty_string($tag['attribute']['maxlength'])) {
19018 $opt['maxlen'] = intval($tag['attribute']['maxlength']);
19020 $h = $this->getCellHeight($this->FontSize);
19021 if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19022 $w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19023 } else {
19024 $w = $h;
19026 if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19027 $checked = true;
19028 } else {
19029 $checked = false;
19031 if (isset($tag['align'])) {
19032 switch ($tag['align']) {
19033 case 'C': {
19034 $opt['q'] = 1;
19035 break;
19037 case 'R': {
19038 $opt['q'] = 2;
19039 break;
19041 case 'L':
19042 default: {
19043 break;
19047 switch ($tag['attribute']['type']) {
19048 case 'text': {
19049 if (isset($value)) {
19050 $opt['v'] = $value;
19052 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19053 break;
19055 case 'password': {
19056 if (isset($value)) {
19057 $opt['v'] = $value;
19059 $prop['password'] = 'true';
19060 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19061 break;
19063 case 'checkbox': {
19064 if (!isset($value)) {
19065 break;
19067 $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19068 break;
19070 case 'radio': {
19071 if (!isset($value)) {
19072 break;
19074 $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19075 break;
19077 case 'submit': {
19078 if (!isset($value)) {
19079 $value = 'submit';
19081 $w = $this->GetStringWidth($value) * 1.5;
19082 $h *= 1.6;
19083 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19084 $action = array();
19085 $action['S'] = 'SubmitForm';
19086 $action['F'] = $this->form_action;
19087 if ($this->form_enctype != 'FDF') {
19088 $action['Flags'] = array('ExportFormat');
19090 if ($this->form_mode == 'get') {
19091 $action['Flags'] = array('GetMethod');
19093 $this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19094 break;
19096 case 'reset': {
19097 if (!isset($value)) {
19098 $value = 'reset';
19100 $w = $this->GetStringWidth($value) * 1.5;
19101 $h *= 1.6;
19102 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19103 $this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19104 break;
19106 case 'file': {
19107 $prop['fileSelect'] = 'true';
19108 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19109 if (!isset($value)) {
19110 $value = '*';
19112 $w = $this->GetStringWidth($value) * 2;
19113 $h *= 1.2;
19114 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19115 $jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19116 $this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19117 break;
19119 case 'hidden': {
19120 if (isset($value)) {
19121 $opt['v'] = $value;
19123 $opt['f'] = array('invisible', 'hidden');
19124 $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19125 break;
19127 case 'image': {
19128 // THIS TYPE MUST BE FIXED
19129 if (isset($tag['attribute']['src']) AND !TCPDF_STATIC::empty_string($tag['attribute']['src'])) {
19130 $img = $tag['attribute']['src'];
19131 } else {
19132 break;
19134 $value = 'img';
19135 //$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19136 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19137 $jsaction = $tag['attribute']['onclick'];
19138 } else {
19139 $jsaction = '';
19141 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19142 break;
19144 case 'button': {
19145 if (!isset($value)) {
19146 $value = ' ';
19148 $w = $this->GetStringWidth($value) * 1.5;
19149 $h *= 1.6;
19150 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19151 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19152 $jsaction = $tag['attribute']['onclick'];
19153 } else {
19154 $jsaction = '';
19156 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19157 break;
19160 break;
19162 case 'textarea': {
19163 $prop = array();
19164 $opt = array();
19165 if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC::empty_string($tag['attribute']['readonly'])) {
19166 $prop['readonly'] = true;
19168 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19169 $name = $tag['attribute']['name'];
19170 } else {
19171 break;
19173 if (isset($tag['attribute']['value']) AND !TCPDF_STATIC::empty_string($tag['attribute']['value'])) {
19174 $opt['v'] = $tag['attribute']['value'];
19176 if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC::empty_string($tag['attribute']['cols'])) {
19177 $w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19178 } else {
19179 $w = 40;
19181 if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19182 $h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19183 } else {
19184 $h = 10;
19186 $prop['multiline'] = 'true';
19187 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19188 break;
19190 case 'select': {
19191 $h = $this->getCellHeight($this->FontSize);
19192 if (isset($tag['attribute']['size']) AND !TCPDF_STATIC::empty_string($tag['attribute']['size'])) {
19193 $h *= ($tag['attribute']['size'] + 1);
19195 $prop = array();
19196 $opt = array();
19197 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19198 $name = $tag['attribute']['name'];
19199 } else {
19200 break;
19202 $w = 0;
19203 if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19204 $options = explode('#!NwL!#', $tag['attribute']['opt']);
19205 $values = array();
19206 foreach ($options as $val) {
19207 if (strpos($val, '#!TaB!#') !== false) {
19208 $opts = explode('#!TaB!#', $val);
19209 $values[] = $opts;
19210 $w = max($w, $this->GetStringWidth($opts[1]));
19211 } else {
19212 $values[] = $val;
19213 $w = max($w, $this->GetStringWidth($val));
19216 } else {
19217 break;
19219 $w *= 2;
19220 if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19221 $prop['multipleSelection'] = 'true';
19222 $this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19223 } else {
19224 $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19226 break;
19228 case 'tcpdf': {
19229 if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
19230 // Special tag used to call TCPDF methods
19231 if (isset($tag['attribute']['method'])) {
19232 $tcpdf_method = $tag['attribute']['method'];
19233 if (method_exists($this, $tcpdf_method)) {
19234 if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
19235 $params = unserialize(urldecode($tag['attribute']['params']));
19236 call_user_func_array(array($this, $tcpdf_method), $params);
19237 } else {
19238 $this->$tcpdf_method();
19240 $this->newline = true;
19244 break;
19246 default: {
19247 break;
19250 // define tags that support borders and background colors
19251 $bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19252 if (in_array($tag['value'], $bordertags)) {
19253 // set border
19254 $dom[$key]['borderposition'] = $this->getBorderStartPosition();
19256 if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19257 $pba = $dom[$key]['attribute']['pagebreakafter'];
19258 // check for pagebreak
19259 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19260 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19261 $this->checkPageBreak($this->PageBreakTrigger + 1);
19263 if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19264 OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19265 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19266 $this->checkPageBreak($this->PageBreakTrigger + 1);
19269 return $dom;
19273 * Process closing tags.
19274 * @param $dom (array) html dom array
19275 * @param $key (int) current element id
19276 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19277 * @param $maxbottomliney (int) maximum y value of current line
19278 * @return $dom array
19279 * @protected
19281 protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19282 $tag = $dom[$key];
19283 $parent = $dom[($dom[$key]['parent'])];
19284 $lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
19285 $in_table_head = false;
19286 // maximum x position (used to draw borders)
19287 if ($this->rtl) {
19288 $xmax = $this->w;
19289 } else {
19290 $xmax = 0;
19292 if ($tag['block']) {
19293 $hbz = 0; // distance from y to line bottom
19294 $hb = 0; // vertical space between block tags
19295 // calculate vertical space for block tags
19296 if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
19297 $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
19298 } elseif (isset($parent['fontsize'])) {
19299 $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k);
19300 } else {
19301 $pre_h = $this->getCellHeight($this->FontSize);
19303 if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
19304 $n = $this->tagvspaces[$tag['value']][1]['n'];
19305 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19306 $n = 0.6;
19307 } else {
19308 $n = 1;
19310 if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19311 $hb = 0;
19312 } else {
19313 $hb = ($n * $pre_h);
19315 if ($maxbottomliney > $this->PageBreakTrigger) {
19316 $hbz = $this->getCellHeight($this->FontSize);
19317 } elseif ($this->y < $maxbottomliney) {
19318 $hbz = ($maxbottomliney - $this->y);
19321 // Closing tag
19322 switch($tag['value']) {
19323 case 'tr': {
19324 $table_el = $dom[($dom[$key]['parent'])]['parent'];
19325 if (!isset($parent['endy'])) {
19326 $dom[($dom[$key]['parent'])]['endy'] = $this->y;
19327 $parent['endy'] = $this->y;
19329 if (!isset($parent['endpage'])) {
19330 $dom[($dom[$key]['parent'])]['endpage'] = $this->page;
19331 $parent['endpage'] = $this->page;
19333 if (!isset($parent['endcolumn'])) {
19334 $dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
19335 $parent['endcolumn'] = $this->current_column;
19337 // update row-spanned cells
19338 if (isset($dom[$table_el]['rowspans'])) {
19339 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19340 $dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19341 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19342 if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19343 $dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19344 } elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19345 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19346 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19347 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19351 // report new endy and endpage to the rowspanned cells
19352 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19353 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19354 $dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19355 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19356 $dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19357 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19358 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19359 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19362 // update remaining rowspanned cells
19363 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19364 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19365 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19366 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19367 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19371 $this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19372 if ($this->num_columns > 1) {
19373 $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19375 $this->y = $dom[($dom[$key]['parent'])]['endy'];
19376 if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19377 $this->y += $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19378 } elseif (isset($dom[$table_el]['border-spacing'])) {
19379 $this->y += $dom[$table_el]['border-spacing']['V'];
19381 $this->Ln(0, $cell);
19382 if ($this->current_column == $parent['startcolumn']) {
19383 $this->x = $parent['startx'];
19385 // account for booklet mode
19386 if ($this->page > $parent['startpage']) {
19387 if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
19388 $this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
19389 } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
19390 $this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
19393 break;
19395 case 'tablehead':
19396 // closing tag used for the thead part
19397 $in_table_head = true;
19398 $this->inthead = false;
19399 case 'table': {
19400 $table_el = $parent;
19401 // set default border
19402 if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19403 // set default border
19404 $border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19405 } else {
19406 $border = 0;
19408 $default_border = $border;
19409 // fix bottom line alignment of last line before page break
19410 foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19411 // update row-spanned cells
19412 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19413 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19414 if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19415 $dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19417 if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19418 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19422 if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19423 $pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
19424 $dom[$prevtrkey]['endy'] = $pgendy;
19425 // update row-spanned cells
19426 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19427 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19428 if (($trwsp['trid'] == $trkey) AND ($trwsp['mrowspan'] > 1) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19429 $dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19430 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19435 $prevtrkey = $trkey;
19436 $table_el = $dom[($dom[$key]['parent'])];
19438 // for each row
19439 if (count($table_el['trids']) > 0) {
19440 unset($xmax);
19442 foreach ($table_el['trids'] as $j => $trkey) {
19443 $parent = $dom[$trkey];
19444 if (!isset($xmax)) {
19445 $xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19447 // for each cell on the row
19448 foreach ($parent['cellpos'] as $k => $cellpos) {
19449 if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19450 $cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19451 $cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19452 $endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19453 $startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19454 $endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19455 $startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19456 $endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19457 } else {
19458 $endy = $parent['endy'];
19459 $startpage = $parent['startpage'];
19460 $endpage = $parent['endpage'];
19461 $startcolumn = $parent['startcolumn'];
19462 $endcolumn = $parent['endcolumn'];
19464 if ($this->num_columns == 0) {
19465 $this->num_columns = 1;
19467 if (isset($cellpos['border'])) {
19468 $border = $cellpos['border'];
19470 if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19471 $this->SetFillColorArray($cellpos['bgcolor']);
19472 $fill = true;
19473 } else {
19474 $fill = false;
19476 $x = $cellpos['startx'];
19477 $y = $parent['starty'];
19478 $starty = $y;
19479 $w = abs($cellpos['endx'] - $cellpos['startx']);
19480 // get border modes
19481 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19482 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19483 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19484 // design borders around HTML cells.
19485 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19486 $ccode = '';
19487 $this->setPage($page);
19488 if ($this->num_columns < 2) {
19489 // single-column mode
19490 $this->x = $x;
19491 $this->y = $this->tMargin;
19493 // account for margin changes
19494 if ($page > $startpage) {
19495 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19496 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19497 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19498 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19501 if ($startpage == $endpage) { // single page
19502 $deltacol = 0;
19503 $deltath = 0;
19504 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19505 $this->selectColumn($column);
19506 if ($startcolumn == $endcolumn) { // single column
19507 $cborder = $border;
19508 $h = $endy - $parent['starty'];
19509 $this->y = $y;
19510 $this->x = $x;
19511 } elseif ($column == $startcolumn) { // first column
19512 $cborder = $border_start;
19513 $this->y = $starty;
19514 $this->x = $x;
19515 $h = $this->h - $this->y - $this->bMargin;
19516 if ($this->rtl) {
19517 $deltacol = $this->x + $this->rMargin - $this->w;
19518 } else {
19519 $deltacol = $this->x - $this->lMargin;
19521 } elseif ($column == $endcolumn) { // end column
19522 $cborder = $border_end;
19523 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19524 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19526 $this->x += $deltacol;
19527 $h = $endy - $this->y;
19528 } else { // middle column
19529 $cborder = $border_middle;
19530 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19531 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19533 $this->x += $deltacol;
19534 $h = $this->h - $this->y - $this->bMargin;
19536 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19537 } // end for each column
19538 } elseif ($page == $startpage) { // first page
19539 $deltacol = 0;
19540 $deltath = 0;
19541 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19542 $this->selectColumn($column);
19543 if ($column == $startcolumn) { // first column
19544 $cborder = $border_start;
19545 $this->y = $starty;
19546 $this->x = $x;
19547 $h = $this->h - $this->y - $this->bMargin;
19548 if ($this->rtl) {
19549 $deltacol = $this->x + $this->rMargin - $this->w;
19550 } else {
19551 $deltacol = $this->x - $this->lMargin;
19553 } else { // middle column
19554 $cborder = $border_middle;
19555 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19556 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19558 $this->x += $deltacol;
19559 $h = $this->h - $this->y - $this->bMargin;
19561 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19562 } // end for each column
19563 } elseif ($page == $endpage) { // last page
19564 $deltacol = 0;
19565 $deltath = 0;
19566 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19567 $this->selectColumn($column);
19568 if ($column == $endcolumn) { // end column
19569 $cborder = $border_end;
19570 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19571 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19573 $this->x += $deltacol;
19574 $h = $endy - $this->y;
19575 } else { // middle column
19576 $cborder = $border_middle;
19577 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19578 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19580 $this->x += $deltacol;
19581 $h = $this->h - $this->y - $this->bMargin;
19583 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19584 } // end for each column
19585 } else { // middle page
19586 $deltacol = 0;
19587 $deltath = 0;
19588 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
19589 $this->selectColumn($column);
19590 $cborder = $border_middle;
19591 if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
19592 $this->y = $this->columns[$column]['th']['\''.$page.'\''];
19594 $this->x += $deltacol;
19595 $h = $this->h - $this->y - $this->bMargin;
19596 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19597 } // end for each column
19599 if ($cborder OR $fill) {
19600 $offsetlen = strlen($ccode);
19601 // draw border and fill
19602 if ($this->inxobj) {
19603 // we are inside an XObject template
19604 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
19605 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
19606 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
19607 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
19608 } else {
19609 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
19610 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
19612 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
19613 $pstart = substr($pagebuff, 0, $pagemark);
19614 $pend = substr($pagebuff, $pagemark);
19615 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
19616 } else {
19617 // draw border and fill
19618 if (end($this->transfmrk[$this->page]) !== false) {
19619 $pagemarkkey = key($this->transfmrk[$this->page]);
19620 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
19621 } elseif ($this->InFooter) {
19622 $pagemark = $this->footerpos[$this->page];
19623 } else {
19624 $pagemark = $this->intmrk[$this->page];
19626 $pagebuff = $this->getPageBuffer($this->page);
19627 $pstart = substr($pagebuff, 0, $pagemark);
19628 $pend = substr($pagebuff, $pagemark);
19629 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
19632 } // end for each page
19633 // restore default border
19634 $border = $default_border;
19635 } // end for each cell on the row
19636 if (isset($table_el['attribute']['cellspacing'])) {
19637 $this->y += $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
19638 } elseif (isset($table_el['border-spacing'])) {
19639 $this->y += $table_el['border-spacing']['V'];
19641 $this->Ln(0, $cell);
19642 $this->x = $parent['startx'];
19643 if ($endpage > $startpage) {
19644 if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
19645 $this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
19646 } elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
19647 $this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
19651 if (!$in_table_head) { // we are not inside a thead section
19652 $this->cell_padding = $table_el['old_cell_padding'];
19653 // reset row height
19654 $this->resetLastH();
19655 if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages])) {
19656 $plendiff = ($this->pagelen[$this->numpages] - $this->emptypagemrk[$this->numpages]);
19657 if (($plendiff > 0) AND ($plendiff < 60)) {
19658 $pagediff = substr($this->getPageBuffer($this->numpages), $this->emptypagemrk[$this->numpages], $plendiff);
19659 if (substr($pagediff, 0, 5) == 'BT /F') {
19660 // the difference is only a font setting
19661 $plendiff = 0;
19664 if ($plendiff == 0) {
19665 // remove last blank page
19666 $this->deletePage($this->numpages);
19669 if (isset($this->theadMargins['top'])) {
19670 // restore top margin
19671 $this->tMargin = $this->theadMargins['top'];
19673 if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
19674 // reset main table header
19675 $this->thead = '';
19676 $this->theadMargins = array();
19677 $this->pagedim[$this->page]['tm'] = $this->tMargin;
19680 $parent = $table_el;
19681 break;
19683 case 'a': {
19684 $this->HREF = '';
19685 break;
19687 case 'sup': {
19688 $this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
19689 break;
19691 case 'sub': {
19692 $this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
19693 break;
19695 case 'div': {
19696 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19697 break;
19699 case 'blockquote': {
19700 if ($this->rtl) {
19701 $this->rMargin -= $this->listindent;
19702 } else {
19703 $this->lMargin -= $this->listindent;
19705 --$this->listindentlevel;
19706 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19707 break;
19709 case 'p': {
19710 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19711 break;
19713 case 'pre': {
19714 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19715 $this->premode = false;
19716 break;
19718 case 'dl': {
19719 --$this->listnum;
19720 if ($this->listnum <= 0) {
19721 $this->listnum = 0;
19722 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19723 } else {
19724 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19726 $this->resetLastH();
19727 break;
19729 case 'dt': {
19730 $this->lispacer = '';
19731 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19732 break;
19734 case 'dd': {
19735 $this->lispacer = '';
19736 if ($this->rtl) {
19737 $this->rMargin -= $this->listindent;
19738 } else {
19739 $this->lMargin -= $this->listindent;
19741 --$this->listindentlevel;
19742 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19743 break;
19745 case 'ul':
19746 case 'ol': {
19747 --$this->listnum;
19748 $this->lispacer = '';
19749 if ($this->rtl) {
19750 $this->rMargin -= $this->listindent;
19751 } else {
19752 $this->lMargin -= $this->listindent;
19754 --$this->listindentlevel;
19755 if ($this->listnum <= 0) {
19756 $this->listnum = 0;
19757 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19758 } else {
19759 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19761 $this->resetLastH();
19762 break;
19764 case 'li': {
19765 $this->lispacer = '';
19766 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
19767 break;
19769 case 'h1':
19770 case 'h2':
19771 case 'h3':
19772 case 'h4':
19773 case 'h5':
19774 case 'h6': {
19775 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19776 break;
19778 // Form fields (since 4.8.000 - 2009-09-07)
19779 case 'form': {
19780 $this->form_action = '';
19781 $this->form_enctype = 'application/x-www-form-urlencoded';
19782 break;
19784 default : {
19785 break;
19788 // draw border and background (if any)
19789 $this->drawHTMLTagBorder($parent, $xmax);
19790 if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
19791 $pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
19792 // check for pagebreak
19793 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19794 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19795 $this->checkPageBreak($this->PageBreakTrigger + 1);
19797 if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
19798 OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
19799 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19800 $this->checkPageBreak($this->PageBreakTrigger + 1);
19803 $this->tmprtl = false;
19804 return $dom;
19808 * Add vertical spaces if needed.
19809 * @param $hbz (string) Distance between current y and line bottom.
19810 * @param $hb (string) The height of the break.
19811 * @param $cell (boolean) if true add the default left (or right if RTL) padding to each new line (default false).
19812 * @param $firsttag (boolean) set to true when the tag is the first.
19813 * @param $lasttag (boolean) set to true when the tag is the last.
19814 * @protected
19816 protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
19817 if ($firsttag) {
19818 $this->Ln(0, $cell);
19819 $this->htmlvspace = 0;
19820 return;
19822 if ($lasttag) {
19823 $this->Ln($hbz, $cell);
19824 $this->htmlvspace = 0;
19825 return;
19827 if ($hb < $this->htmlvspace) {
19828 $hd = 0;
19829 } else {
19830 $hd = $hb - $this->htmlvspace;
19831 $this->htmlvspace = $hb;
19833 $this->Ln(($hbz + $hd), $cell);
19837 * Return the starting coordinates to draw an html border
19838 * @return array containing top-left border coordinates
19839 * @protected
19840 * @since 5.7.000 (2010-08-03)
19842 protected function getBorderStartPosition() {
19843 if ($this->rtl) {
19844 $xmax = $this->lMargin;
19845 } else {
19846 $xmax = $this->w - $this->rMargin;
19848 return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
19852 * Draw an HTML block border and fill
19853 * @param $tag (array) array of tag properties.
19854 * @param $xmax (int) end X coordinate for border.
19855 * @protected
19856 * @since 5.7.000 (2010-08-03)
19858 protected function drawHTMLTagBorder($tag, $xmax) {
19859 if (!isset($tag['borderposition'])) {
19860 // nothing to draw
19861 return;
19863 $prev_x = $this->x;
19864 $prev_y = $this->y;
19865 $prev_lasth = $this->lasth;
19866 $border = 0;
19867 $fill = false;
19868 $this->lasth = 0;
19869 if (isset($tag['border']) AND !empty($tag['border'])) {
19870 // get border style
19871 $border = $tag['border'];
19872 if (!TCPDF_STATIC::empty_string($this->thead) AND (!$this->inthead)) {
19873 // border for table header
19874 $border = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19877 if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
19878 // get background color
19879 $old_bgcolor = $this->bgcolor;
19880 $this->SetFillColorArray($tag['bgcolor']);
19881 $fill = true;
19883 if (!$border AND !$fill) {
19884 // nothing to draw
19885 return;
19887 if (isset($tag['attribute']['cellspacing'])) {
19888 $clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
19889 $cellspacing = array('H' => $clsp, 'V' => $clsp);
19890 } elseif (isset($tag['border-spacing'])) {
19891 $cellspacing = $tag['border-spacing'];
19892 } else {
19893 $cellspacing = array('H' => 0, 'V' => 0);
19895 if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
19896 // draw the border externally respect the sqare edge.
19897 $border['mode'] = 'ext';
19899 if ($this->rtl) {
19900 if ($xmax >= $tag['borderposition']['x']) {
19901 $xmax = $tag['borderposition']['xmax'];
19903 $w = ($tag['borderposition']['x'] - $xmax);
19904 } else {
19905 if ($xmax <= $tag['borderposition']['x']) {
19906 $xmax = $tag['borderposition']['xmax'];
19908 $w = ($xmax - $tag['borderposition']['x']);
19910 if ($w <= 0) {
19911 return;
19913 $w += $cellspacing['H'];
19914 $startpage = $tag['borderposition']['page'];
19915 $startcolumn = $tag['borderposition']['column'];
19916 $x = $tag['borderposition']['x'];
19917 $y = $tag['borderposition']['y'];
19918 $endpage = $this->page;
19919 $starty = $tag['borderposition']['y'] - $cellspacing['V'];
19920 $currentY = $this->y;
19921 $this->x = $x;
19922 // get latest column
19923 $endcolumn = $this->current_column;
19924 if ($this->num_columns == 0) {
19925 $this->num_columns = 1;
19927 // get border modes
19928 $border_start = TCPDF_STATIC::getBorderMode($border, $position='start', $this->opencell);
19929 $border_end = TCPDF_STATIC::getBorderMode($border, $position='end', $this->opencell);
19930 $border_middle = TCPDF_STATIC::getBorderMode($border, $position='middle', $this->opencell);
19931 // temporary disable page regions
19932 $temp_page_regions = $this->page_regions;
19933 $this->page_regions = array();
19934 // design borders around HTML cells.
19935 for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
19936 $ccode = '';
19937 $this->setPage($page);
19938 if ($this->num_columns < 2) {
19939 // single-column mode
19940 $this->x = $x;
19941 $this->y = $this->tMargin;
19943 // account for margin changes
19944 if ($page > $startpage) {
19945 if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
19946 $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
19947 } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
19948 $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
19951 if ($startpage == $endpage) {
19952 // single page
19953 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
19954 $this->selectColumn($column);
19955 if ($startcolumn == $endcolumn) { // single column
19956 $cborder = $border;
19957 $h = ($currentY - $y) + $cellspacing['V'];
19958 $this->y = $starty;
19959 } elseif ($column == $startcolumn) { // first column
19960 $cborder = $border_start;
19961 $this->y = $starty;
19962 $h = $this->h - $this->y - $this->bMargin;
19963 } elseif ($column == $endcolumn) { // end column
19964 $cborder = $border_end;
19965 $h = $currentY - $this->y;
19966 } else { // middle column
19967 $cborder = $border_middle;
19968 $h = $this->h - $this->y - $this->bMargin;
19970 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19971 } // end for each column
19972 } elseif ($page == $startpage) { // first page
19973 for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
19974 $this->selectColumn($column);
19975 if ($column == $startcolumn) { // first column
19976 $cborder = $border_start;
19977 $this->y = $starty;
19978 $h = $this->h - $this->y - $this->bMargin;
19979 } else { // middle column
19980 $cborder = $border_middle;
19981 $h = $this->h - $this->y - $this->bMargin;
19983 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19984 } // end for each column
19985 } elseif ($page == $endpage) { // last page
19986 for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
19987 $this->selectColumn($column);
19988 if ($column == $endcolumn) {
19989 // end column
19990 $cborder = $border_end;
19991 $h = $currentY - $this->y;
19992 } else {
19993 // middle column
19994 $cborder = $border_middle;
19995 $h = $this->h - $this->y - $this->bMargin;
19997 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19998 } // end for each column
19999 } else { // middle page
20000 for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
20001 $this->selectColumn($column);
20002 $cborder = $border_middle;
20003 $h = $this->h - $this->y - $this->bMargin;
20004 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20005 } // end for each column
20007 if ($cborder OR $fill) {
20008 $offsetlen = strlen($ccode);
20009 // draw border and fill
20010 if ($this->inxobj) {
20011 // we are inside an XObject template
20012 if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
20013 $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
20014 $pagemark = $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
20015 $this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey] += $offsetlen;
20016 } else {
20017 $pagemark = $this->xobjects[$this->xobjid]['intmrk'];
20018 $this->xobjects[$this->xobjid]['intmrk'] += $offsetlen;
20020 $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
20021 $pstart = substr($pagebuff, 0, $pagemark);
20022 $pend = substr($pagebuff, $pagemark);
20023 $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
20024 } else {
20025 if (end($this->transfmrk[$this->page]) !== false) {
20026 $pagemarkkey = key($this->transfmrk[$this->page]);
20027 $pagemark = $this->transfmrk[$this->page][$pagemarkkey];
20028 } elseif ($this->InFooter) {
20029 $pagemark = $this->footerpos[$this->page];
20030 } else {
20031 $pagemark = $this->intmrk[$this->page];
20033 $pagebuff = $this->getPageBuffer($this->page);
20034 $pstart = substr($pagebuff, 0, $pagemark);
20035 $pend = substr($pagebuff, $pagemark);
20036 $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
20037 $this->bordermrk[$this->page] += $offsetlen;
20038 $this->cntmrk[$this->page] += $offsetlen;
20041 } // end for each page
20042 // restore page regions
20043 $this->page_regions = $temp_page_regions;
20044 if (isset($old_bgcolor)) {
20045 // restore background color
20046 $this->SetFillColorArray($old_bgcolor);
20048 // restore pointer position
20049 $this->x = $prev_x;
20050 $this->y = $prev_y;
20051 $this->lasth = $prev_lasth;
20055 * Set the default bullet to be used as LI bullet symbol
20056 * @param $symbol (string) character or string to be used (legal values are: '' = automatic, '!' = auto bullet, '#' = auto numbering, 'disc', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek', 'img|type|width|height|image.ext')
20057 * @public
20058 * @since 4.0.028 (2008-09-26)
20060 public function setLIsymbol($symbol='!') {
20061 // check for custom image symbol
20062 if (substr($symbol, 0, 4) == 'img|') {
20063 $this->lisymbol = $symbol;
20064 return;
20066 $symbol = strtolower($symbol);
20067 $valid_symbols = array('!', '#', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek');
20068 if (in_array($symbol, $valid_symbols)) {
20069 $this->lisymbol = $symbol;
20070 } else {
20071 $this->lisymbol = '';
20076 * Set the booklet mode for double-sided pages.
20077 * @param $booklet (boolean) true set the booklet mode on, false otherwise.
20078 * @param $inner (float) Inner page margin.
20079 * @param $outer (float) Outer page margin.
20080 * @public
20081 * @since 4.2.000 (2008-10-29)
20083 public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
20084 $this->booklet = $booklet;
20085 if ($inner >= 0) {
20086 $this->lMargin = $inner;
20088 if ($outer >= 0) {
20089 $this->rMargin = $outer;
20094 * Swap the left and right margins.
20095 * @param $reverse (boolean) if true swap left and right margins.
20096 * @protected
20097 * @since 4.2.000 (2008-10-29)
20099 protected function swapMargins($reverse=true) {
20100 if ($reverse) {
20101 // swap left and right margins
20102 $mtemp = $this->original_lMargin;
20103 $this->original_lMargin = $this->original_rMargin;
20104 $this->original_rMargin = $mtemp;
20105 $deltam = $this->original_lMargin - $this->original_rMargin;
20106 $this->lMargin += $deltam;
20107 $this->rMargin -= $deltam;
20112 * Set the vertical spaces for HTML tags.
20113 * The array must have the following structure (example):
20114 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20115 * The first array level contains the tag names,
20116 * the second level contains 0 for opening tags or 1 for closing tags,
20117 * the third level contains the vertical space unit (h) and the number spaces to add (n).
20118 * If the h parameter is not specified, default values are used.
20119 * @param $tagvs (array) array of tags and relative vertical spaces.
20120 * @public
20121 * @since 4.2.001 (2008-10-30)
20123 public function setHtmlVSpace($tagvs) {
20124 $this->tagvspaces = $tagvs;
20128 * Set custom width for list indentation.
20129 * @param $width (float) width of the indentation. Use negative value to disable it.
20130 * @public
20131 * @since 4.2.007 (2008-11-12)
20133 public function setListIndentWidth($width) {
20134 return $this->customlistindent = floatval($width);
20138 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20139 * @param $isopen (boolean) if true keeps the top/bottom border open for the cell sides that cross the page.
20140 * @public
20141 * @since 4.2.010 (2008-11-14)
20143 public function setOpenCell($isopen) {
20144 $this->opencell = $isopen;
20148 * Set the color and font style for HTML links.
20149 * @param $color (array) RGB array of colors
20150 * @param $fontstyle (string) additional font styles to add
20151 * @public
20152 * @since 4.4.003 (2008-12-09)
20154 public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20155 $this->htmlLinkColorArray = $color;
20156 $this->htmlLinkFontStyle = $fontstyle;
20160 * Convert HTML string containing value and unit of measure to user's units or points.
20161 * @param $htmlval (string) String containing values and unit.
20162 * @param $refsize (string) Reference value in points.
20163 * @param $defaultunit (string) Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20164 * @param $points (boolean) If true returns points, otherwise returns value in user's units.
20165 * @return float value in user's unit or point if $points=true
20166 * @public
20167 * @since 4.4.004 (2008-12-10)
20169 public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20170 $supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20171 $retval = 0;
20172 $value = 0;
20173 $unit = 'px';
20174 if ($points) {
20175 $k = 1;
20176 } else {
20177 $k = $this->k;
20179 if (in_array($defaultunit, $supportedunits)) {
20180 $unit = $defaultunit;
20182 if (is_numeric($htmlval)) {
20183 $value = floatval($htmlval);
20184 } elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20185 $value = floatval($mnum[1]);
20186 if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20187 if (in_array($munit[1], $supportedunits)) {
20188 $unit = $munit[1];
20192 switch ($unit) {
20193 // percentage
20194 case '%': {
20195 $retval = (($value * $refsize) / 100);
20196 break;
20198 // relative-size
20199 case 'em': {
20200 $retval = ($value * $refsize);
20201 break;
20203 // height of lower case 'x' (about half the font-size)
20204 case 'ex': {
20205 $retval = ($value * ($refsize / 2));
20206 break;
20208 // absolute-size
20209 case 'in': {
20210 $retval = (($value * $this->dpi) / $k);
20211 break;
20213 // centimeters
20214 case 'cm': {
20215 $retval = (($value / 2.54 * $this->dpi) / $k);
20216 break;
20218 // millimeters
20219 case 'mm': {
20220 $retval = (($value / 25.4 * $this->dpi) / $k);
20221 break;
20223 // one pica is 12 points
20224 case 'pc': {
20225 $retval = (($value * 12) / $k);
20226 break;
20228 // points
20229 case 'pt': {
20230 $retval = ($value / $k);
20231 break;
20233 // pixels
20234 case 'px': {
20235 $retval = $this->pixelsToUnits($value);
20236 if ($points) {
20237 $retval *= $this->k;
20239 break;
20242 return $retval;
20246 * Output an HTML list bullet or ordered item symbol
20247 * @param $listdepth (int) list nesting level
20248 * @param $listtype (string) type of list
20249 * @param $size (float) current font size
20250 * @protected
20251 * @since 4.4.004 (2008-12-10)
20253 protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20254 if ($this->state != 2) {
20255 return;
20257 $size /= $this->k;
20258 $fill = '';
20259 $bgcolor = $this->bgcolor;
20260 $color = $this->fgcolor;
20261 $strokecolor = $this->strokecolor;
20262 $width = 0;
20263 $textitem = '';
20264 $tmpx = $this->x;
20265 $lspace = $this->GetStringWidth(' ');
20266 if ($listtype == '^') {
20267 // special symbol used for avoid justification of rect bullet
20268 $this->lispacer = '';
20269 return;
20270 } elseif ($listtype == '!') {
20271 // set default list type for unordered list
20272 $deftypes = array('disc', 'circle', 'square');
20273 $listtype = $deftypes[($listdepth - 1) % 3];
20274 } elseif ($listtype == '#') {
20275 // set default list type for ordered list
20276 $listtype = 'decimal';
20277 } elseif (substr($listtype, 0, 4) == 'img|') {
20278 // custom image type ('img|type|width|height|image.ext')
20279 $img = explode('|', $listtype);
20280 $listtype = 'img';
20282 switch ($listtype) {
20283 // unordered types
20284 case 'none': {
20285 break;
20287 case 'disc': {
20288 $r = $size / 6;
20289 $lspace += (2 * $r);
20290 if ($this->rtl) {
20291 $this->x += $lspace;
20292 } else {
20293 $this->x -= $lspace;
20295 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20296 break;
20298 case 'circle': {
20299 $r = $size / 6;
20300 $lspace += (2 * $r);
20301 if ($this->rtl) {
20302 $this->x += $lspace;
20303 } else {
20304 $this->x -= $lspace;
20306 $prev_line_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor;
20307 $new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20308 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20309 $this->_out($prev_line_style); // restore line settings
20310 break;
20312 case 'square': {
20313 $l = $size / 3;
20314 $lspace += $l;
20315 if ($this->rtl) {;
20316 $this->x += $lspace;
20317 } else {
20318 $this->x -= $lspace;
20320 $this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20321 break;
20323 case 'img': {
20324 // 1=>type, 2=>width, 3=>height, 4=>image.ext
20325 $lspace += $img[2];
20326 if ($this->rtl) {;
20327 $this->x += $lspace;
20328 } else {
20329 $this->x -= $lspace;
20331 $imgtype = strtolower($img[1]);
20332 $prev_y = $this->y;
20333 switch ($imgtype) {
20334 case 'svg': {
20335 $this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20336 break;
20338 case 'ai':
20339 case 'eps': {
20340 $this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20341 break;
20343 default: {
20344 $this->Image($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], $img[1], '', 'T', false, 300, '', false, false, 0, false, false, false);
20345 break;
20348 $this->y = $prev_y;
20349 break;
20351 // ordered types
20352 // $this->listcount[$this->listnum];
20353 // $textitem
20354 case '1':
20355 case 'decimal': {
20356 $textitem = $this->listcount[$this->listnum];
20357 break;
20359 case 'decimal-leading-zero': {
20360 $textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20361 break;
20363 case 'i':
20364 case 'lower-roman': {
20365 $textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20366 break;
20368 case 'I':
20369 case 'upper-roman': {
20370 $textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20371 break;
20373 case 'a':
20374 case 'lower-alpha':
20375 case 'lower-latin': {
20376 $textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20377 break;
20379 case 'A':
20380 case 'upper-alpha':
20381 case 'upper-latin': {
20382 $textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20383 break;
20385 case 'lower-greek': {
20386 $textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20387 break;
20390 // Types to be implemented (special handling)
20391 case 'hebrew': {
20392 break;
20394 case 'armenian': {
20395 break;
20397 case 'georgian': {
20398 break;
20400 case 'cjk-ideographic': {
20401 break;
20403 case 'hiragana': {
20404 break;
20406 case 'katakana': {
20407 break;
20409 case 'hiragana-iroha': {
20410 break;
20412 case 'katakana-iroha': {
20413 break;
20416 default: {
20417 $textitem = $this->listcount[$this->listnum];
20420 if (!TCPDF_STATIC::empty_string($textitem)) {
20421 // Check whether we need a new page or new column
20422 $prev_y = $this->y;
20423 $h = $this->getCellHeight($this->FontSize);
20424 if ($this->checkPageBreak($h) OR ($this->y < $prev_y)) {
20425 $tmpx = $this->x;
20427 // print ordered item
20428 if ($this->rtl) {
20429 $textitem = '.'.$textitem;
20430 } else {
20431 $textitem = $textitem.'.';
20433 $lspace += $this->GetStringWidth($textitem);
20434 if ($this->rtl) {
20435 $this->x += $lspace;
20436 } else {
20437 $this->x -= $lspace;
20439 $this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20441 $this->x = $tmpx;
20442 $this->lispacer = '^';
20443 // restore colors
20444 $this->SetFillColorArray($bgcolor);
20445 $this->SetDrawColorArray($strokecolor);
20446 $this->SettextColorArray($color);
20450 * Returns current graphic variables as array.
20451 * @return array of graphic variables
20452 * @protected
20453 * @since 4.2.010 (2008-11-14)
20455 protected function getGraphicVars() {
20456 $grapvars = array(
20457 'FontFamily' => $this->FontFamily,
20458 'FontStyle' => $this->FontStyle,
20459 'FontSizePt' => $this->FontSizePt,
20460 'rMargin' => $this->rMargin,
20461 'lMargin' => $this->lMargin,
20462 'cell_padding' => $this->cell_padding,
20463 'cell_margin' => $this->cell_margin,
20464 'LineWidth' => $this->LineWidth,
20465 'linestyleWidth' => $this->linestyleWidth,
20466 'linestyleCap' => $this->linestyleCap,
20467 'linestyleJoin' => $this->linestyleJoin,
20468 'linestyleDash' => $this->linestyleDash,
20469 'textrendermode' => $this->textrendermode,
20470 'textstrokewidth' => $this->textstrokewidth,
20471 'DrawColor' => $this->DrawColor,
20472 'FillColor' => $this->FillColor,
20473 'TextColor' => $this->TextColor,
20474 'ColorFlag' => $this->ColorFlag,
20475 'bgcolor' => $this->bgcolor,
20476 'fgcolor' => $this->fgcolor,
20477 'htmlvspace' => $this->htmlvspace,
20478 'listindent' => $this->listindent,
20479 'listindentlevel' => $this->listindentlevel,
20480 'listnum' => $this->listnum,
20481 'listordered' => $this->listordered,
20482 'listcount' => $this->listcount,
20483 'lispacer' => $this->lispacer,
20484 'cell_height_ratio' => $this->cell_height_ratio,
20485 'font_stretching' => $this->font_stretching,
20486 'font_spacing' => $this->font_spacing,
20487 'alpha' => $this->alpha,
20488 // extended
20489 'lasth' => $this->lasth,
20490 'tMargin' => $this->tMargin,
20491 'bMargin' => $this->bMargin,
20492 'AutoPageBreak' => $this->AutoPageBreak,
20493 'PageBreakTrigger' => $this->PageBreakTrigger,
20494 'x' => $this->x,
20495 'y' => $this->y,
20496 'w' => $this->w,
20497 'h' => $this->h,
20498 'wPt' => $this->wPt,
20499 'hPt' => $this->hPt,
20500 'fwPt' => $this->fwPt,
20501 'fhPt' => $this->fhPt,
20502 'page' => $this->page,
20503 'current_column' => $this->current_column,
20504 'num_columns' => $this->num_columns
20506 return $grapvars;
20510 * Set graphic variables.
20511 * @param $gvars (array) array of graphic variablesto restore
20512 * @param $extended (boolean) if true restore extended graphic variables
20513 * @protected
20514 * @since 4.2.010 (2008-11-14)
20516 protected function setGraphicVars($gvars, $extended=false) {
20517 if ($this->state != 2) {
20518 return;
20520 $this->FontFamily = $gvars['FontFamily'];
20521 $this->FontStyle = $gvars['FontStyle'];
20522 $this->FontSizePt = $gvars['FontSizePt'];
20523 $this->rMargin = $gvars['rMargin'];
20524 $this->lMargin = $gvars['lMargin'];
20525 $this->cell_padding = $gvars['cell_padding'];
20526 $this->cell_margin = $gvars['cell_margin'];
20527 $this->LineWidth = $gvars['LineWidth'];
20528 $this->linestyleWidth = $gvars['linestyleWidth'];
20529 $this->linestyleCap = $gvars['linestyleCap'];
20530 $this->linestyleJoin = $gvars['linestyleJoin'];
20531 $this->linestyleDash = $gvars['linestyleDash'];
20532 $this->textrendermode = $gvars['textrendermode'];
20533 $this->textstrokewidth = $gvars['textstrokewidth'];
20534 $this->DrawColor = $gvars['DrawColor'];
20535 $this->FillColor = $gvars['FillColor'];
20536 $this->TextColor = $gvars['TextColor'];
20537 $this->ColorFlag = $gvars['ColorFlag'];
20538 $this->bgcolor = $gvars['bgcolor'];
20539 $this->fgcolor = $gvars['fgcolor'];
20540 $this->htmlvspace = $gvars['htmlvspace'];
20541 $this->listindent = $gvars['listindent'];
20542 $this->listindentlevel = $gvars['listindentlevel'];
20543 $this->listnum = $gvars['listnum'];
20544 $this->listordered = $gvars['listordered'];
20545 $this->listcount = $gvars['listcount'];
20546 $this->lispacer = $gvars['lispacer'];
20547 $this->cell_height_ratio = $gvars['cell_height_ratio'];
20548 $this->font_stretching = $gvars['font_stretching'];
20549 $this->font_spacing = $gvars['font_spacing'];
20550 $this->alpha = $gvars['alpha'];
20551 if ($extended) {
20552 // restore extended values
20553 $this->lasth = $gvars['lasth'];
20554 $this->tMargin = $gvars['tMargin'];
20555 $this->bMargin = $gvars['bMargin'];
20556 $this->AutoPageBreak = $gvars['AutoPageBreak'];
20557 $this->PageBreakTrigger = $gvars['PageBreakTrigger'];
20558 $this->x = $gvars['x'];
20559 $this->y = $gvars['y'];
20560 $this->w = $gvars['w'];
20561 $this->h = $gvars['h'];
20562 $this->wPt = $gvars['wPt'];
20563 $this->hPt = $gvars['hPt'];
20564 $this->fwPt = $gvars['fwPt'];
20565 $this->fhPt = $gvars['fhPt'];
20566 $this->page = $gvars['page'];
20567 $this->current_column = $gvars['current_column'];
20568 $this->num_columns = $gvars['num_columns'];
20570 $this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
20571 if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
20572 $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
20577 * Outputs the "save graphics state" operator 'q'
20578 * @protected
20580 protected function _outSaveGraphicsState() {
20581 $this->_out('q');
20585 * Outputs the "restore graphics state" operator 'Q'
20586 * @protected
20588 protected function _outRestoreGraphicsState() {
20589 $this->_out('Q');
20593 * Writes data to a temporary file on filesystem.
20594 * @param $filename (string) file name
20595 * @param $data (mixed) data to write on file
20596 * @param $append (boolean) if true append data, false replace.
20597 * @since 4.5.000 (2008-12-31)
20598 * @protected
20600 protected function writeDiskCache($filename, $data, $append=false) {
20601 if ($append) {
20602 $fmode = 'ab+';
20603 } else {
20604 $fmode = 'wb+';
20606 $f = @fopen($filename, $fmode);
20607 if (!$f) {
20608 $this->Error('Unable to write cache file: '.$filename);
20609 } else {
20610 fwrite($f, $data);
20611 fclose($f);
20613 // update file length (needed for transactions)
20614 if (!isset($this->cache_file_length['_'.$filename])) {
20615 $this->cache_file_length['_'.$filename] = strlen($data);
20616 } else {
20617 $this->cache_file_length['_'.$filename] += strlen($data);
20622 * Read data from a temporary file on filesystem.
20623 * @param $filename (string) file name
20624 * @return mixed retrieved data
20625 * @since 4.5.000 (2008-12-31)
20626 * @protected
20628 protected function readDiskCache($filename) {
20629 return file_get_contents($filename);
20633 * Set buffer content (always append data).
20634 * @param $data (string) data
20635 * @protected
20636 * @since 4.5.000 (2009-01-02)
20638 protected function setBuffer($data) {
20639 $this->bufferlen += strlen($data);
20640 if ($this->diskcache) {
20641 if (!isset($this->buffer) OR TCPDF_STATIC::empty_string($this->buffer)) {
20642 $this->buffer = TCPDF_STATIC::getObjFilename('buf');
20644 $this->writeDiskCache($this->buffer, $data, true);
20645 } else {
20646 $this->buffer .= $data;
20651 * Replace the buffer content
20652 * @param $data (string) data
20653 * @protected
20654 * @since 5.5.000 (2010-06-22)
20656 protected function replaceBuffer($data) {
20657 $this->bufferlen = strlen($data);
20658 if ($this->diskcache) {
20659 if (!isset($this->buffer) OR TCPDF_STATIC::empty_string($this->buffer)) {
20660 $this->buffer = TCPDF_STATIC::getObjFilename('buf');
20662 $this->writeDiskCache($this->buffer, $data, false);
20663 } else {
20664 $this->buffer = $data;
20669 * Get buffer content.
20670 * @return string buffer content
20671 * @protected
20672 * @since 4.5.000 (2009-01-02)
20674 protected function getBuffer() {
20675 if ($this->diskcache) {
20676 return $this->readDiskCache($this->buffer);
20677 } else {
20678 return $this->buffer;
20683 * Set page buffer content.
20684 * @param $page (int) page number
20685 * @param $data (string) page data
20686 * @param $append (boolean) if true append data, false replace.
20687 * @protected
20688 * @since 4.5.000 (2008-12-31)
20690 protected function setPageBuffer($page, $data, $append=false) {
20691 if ($this->diskcache) {
20692 if (!isset($this->pages[$page])) {
20693 $this->pages[$page] = TCPDF_STATIC::getObjFilename('page');
20695 $this->writeDiskCache($this->pages[$page], $data, $append);
20696 } else {
20697 if ($append) {
20698 $this->pages[$page] .= $data;
20699 } else {
20700 $this->pages[$page] = $data;
20703 if ($append AND isset($this->pagelen[$page])) {
20704 $this->pagelen[$page] += strlen($data);
20705 } else {
20706 $this->pagelen[$page] = strlen($data);
20711 * Get page buffer content.
20712 * @param $page (int) page number
20713 * @return string page buffer content or false in case of error
20714 * @protected
20715 * @since 4.5.000 (2008-12-31)
20717 protected function getPageBuffer($page) {
20718 if ($this->diskcache) {
20719 return $this->readDiskCache($this->pages[$page]);
20720 } elseif (isset($this->pages[$page])) {
20721 return $this->pages[$page];
20723 return false;
20727 * Set image buffer content.
20728 * @param $image (string) image key
20729 * @param $data (array) image data
20730 * @return int image index number
20731 * @protected
20732 * @since 4.5.000 (2008-12-31)
20734 protected function setImageBuffer($image, $data) {
20735 if (($data['i'] = array_search($image, $this->imagekeys)) === FALSE) {
20736 $this->imagekeys[$this->numimages] = $image;
20737 $data['i'] = $this->numimages;
20738 ++$this->numimages;
20740 if ($this->diskcache) {
20741 if (!isset($this->images[$image])) {
20742 $this->images[$image] = TCPDF_STATIC::getObjFilename('img');
20744 $this->writeDiskCache($this->images[$image], serialize($data));
20745 } else {
20746 $this->images[$image] = $data;
20748 return $data['i'];
20752 * Set image buffer content for a specified sub-key.
20753 * @param $image (string) image key
20754 * @param $key (string) image sub-key
20755 * @param $data (array) image data
20756 * @protected
20757 * @since 4.5.000 (2008-12-31)
20759 protected function setImageSubBuffer($image, $key, $data) {
20760 if (!isset($this->images[$image])) {
20761 $this->setImageBuffer($image, array());
20763 if ($this->diskcache) {
20764 $tmpimg = $this->getImageBuffer($image);
20765 $tmpimg[$key] = $data;
20766 $this->writeDiskCache($this->images[$image], serialize($tmpimg));
20767 } else {
20768 $this->images[$image][$key] = $data;
20773 * Get image buffer content.
20774 * @param $image (string) image key
20775 * @return string image buffer content or false in case of error
20776 * @protected
20777 * @since 4.5.000 (2008-12-31)
20779 protected function getImageBuffer($image) {
20780 if ($this->diskcache AND isset($this->images[$image])) {
20781 return unserialize($this->readDiskCache($this->images[$image]));
20782 } elseif (isset($this->images[$image])) {
20783 return $this->images[$image];
20785 return false;
20789 * Set font buffer content.
20790 * @param $font (string) font key
20791 * @param $data (array) font data
20792 * @protected
20793 * @since 4.5.000 (2009-01-02)
20795 protected function setFontBuffer($font, $data) {
20796 if ($this->diskcache) {
20797 if (!isset($this->fonts[$font])) {
20798 $this->fonts[$font] = TCPDF_STATIC::getObjFilename('font');
20800 $this->writeDiskCache($this->fonts[$font], serialize($data));
20801 } else {
20802 $this->fonts[$font] = $data;
20804 if (!in_array($font, $this->fontkeys)) {
20805 $this->fontkeys[] = $font;
20806 // store object ID for current font
20807 ++$this->n;
20808 $this->font_obj_ids[$font] = $this->n;
20809 $this->setFontSubBuffer($font, 'n', $this->n);
20814 * Set font buffer content.
20815 * @param $font (string) font key
20816 * @param $key (string) font sub-key
20817 * @param $data (array) font data
20818 * @protected
20819 * @since 4.5.000 (2009-01-02)
20821 protected function setFontSubBuffer($font, $key, $data) {
20822 if (!isset($this->fonts[$font])) {
20823 $this->setFontBuffer($font, array());
20825 if ($this->diskcache) {
20826 $tmpfont = $this->getFontBuffer($font);
20827 $tmpfont[$key] = $data;
20828 $this->writeDiskCache($this->fonts[$font], serialize($tmpfont));
20829 } else {
20830 $this->fonts[$font][$key] = $data;
20835 * Get font buffer content.
20836 * @param $font (string) font key
20837 * @return string font buffer content or false in case of error
20838 * @protected
20839 * @since 4.5.000 (2009-01-02)
20841 protected function getFontBuffer($font) {
20842 if ($this->diskcache AND isset($this->fonts[$font])) {
20843 return unserialize($this->readDiskCache($this->fonts[$font]));
20844 } elseif (isset($this->fonts[$font])) {
20845 return $this->fonts[$font];
20847 return false;
20851 * Move a page to a previous position.
20852 * @param $frompage (int) number of the source page
20853 * @param $topage (int) number of the destination page (must be less than $frompage)
20854 * @return true in case of success, false in case of error.
20855 * @public
20856 * @since 4.5.000 (2009-01-02)
20858 public function movePage($frompage, $topage) {
20859 if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
20860 return false;
20862 if ($frompage == $this->page) {
20863 // close the page before moving it
20864 $this->endPage();
20866 // move all page-related states
20867 $tmppage = $this->getPageBuffer($frompage);
20868 $tmppagedim = $this->pagedim[$frompage];
20869 $tmppagelen = $this->pagelen[$frompage];
20870 $tmpintmrk = $this->intmrk[$frompage];
20871 $tmpbordermrk = $this->bordermrk[$frompage];
20872 $tmpcntmrk = $this->cntmrk[$frompage];
20873 $tmppageobjects = $this->pageobjects[$frompage];
20874 if (isset($this->footerpos[$frompage])) {
20875 $tmpfooterpos = $this->footerpos[$frompage];
20877 if (isset($this->footerlen[$frompage])) {
20878 $tmpfooterlen = $this->footerlen[$frompage];
20880 if (isset($this->transfmrk[$frompage])) {
20881 $tmptransfmrk = $this->transfmrk[$frompage];
20883 if (isset($this->PageAnnots[$frompage])) {
20884 $tmpannots = $this->PageAnnots[$frompage];
20886 if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
20887 for ($i = $frompage; $i > $topage; --$i) {
20888 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $frompage)) {
20889 --$this->pagegroups[$this->newpagegroup[$i]];
20890 break;
20893 for ($i = $topage; $i > 0; --$i) {
20894 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $topage)) {
20895 ++$this->pagegroups[$this->newpagegroup[$i]];
20896 break;
20900 for ($i = $frompage; $i > $topage; --$i) {
20901 $j = $i - 1;
20902 // shift pages down
20903 $this->setPageBuffer($i, $this->getPageBuffer($j));
20904 $this->pagedim[$i] = $this->pagedim[$j];
20905 $this->pagelen[$i] = $this->pagelen[$j];
20906 $this->intmrk[$i] = $this->intmrk[$j];
20907 $this->bordermrk[$i] = $this->bordermrk[$j];
20908 $this->cntmrk[$i] = $this->cntmrk[$j];
20909 $this->pageobjects[$i] = $this->pageobjects[$j];
20910 if (isset($this->footerpos[$j])) {
20911 $this->footerpos[$i] = $this->footerpos[$j];
20912 } elseif (isset($this->footerpos[$i])) {
20913 unset($this->footerpos[$i]);
20915 if (isset($this->footerlen[$j])) {
20916 $this->footerlen[$i] = $this->footerlen[$j];
20917 } elseif (isset($this->footerlen[$i])) {
20918 unset($this->footerlen[$i]);
20920 if (isset($this->transfmrk[$j])) {
20921 $this->transfmrk[$i] = $this->transfmrk[$j];
20922 } elseif (isset($this->transfmrk[$i])) {
20923 unset($this->transfmrk[$i]);
20925 if (isset($this->PageAnnots[$j])) {
20926 $this->PageAnnots[$i] = $this->PageAnnots[$j];
20927 } elseif (isset($this->PageAnnots[$i])) {
20928 unset($this->PageAnnots[$i]);
20930 if (isset($this->newpagegroup[$j])) {
20931 $this->newpagegroup[$i] = $this->newpagegroup[$j];
20932 unset($this->newpagegroup[$j]);
20934 if ($this->currpagegroup == $j) {
20935 $this->currpagegroup = $i;
20938 $this->setPageBuffer($topage, $tmppage);
20939 $this->pagedim[$topage] = $tmppagedim;
20940 $this->pagelen[$topage] = $tmppagelen;
20941 $this->intmrk[$topage] = $tmpintmrk;
20942 $this->bordermrk[$topage] = $tmpbordermrk;
20943 $this->cntmrk[$topage] = $tmpcntmrk;
20944 $this->pageobjects[$topage] = $tmppageobjects;
20945 if (isset($tmpfooterpos)) {
20946 $this->footerpos[$topage] = $tmpfooterpos;
20947 } elseif (isset($this->footerpos[$topage])) {
20948 unset($this->footerpos[$topage]);
20950 if (isset($tmpfooterlen)) {
20951 $this->footerlen[$topage] = $tmpfooterlen;
20952 } elseif (isset($this->footerlen[$topage])) {
20953 unset($this->footerlen[$topage]);
20955 if (isset($tmptransfmrk)) {
20956 $this->transfmrk[$topage] = $tmptransfmrk;
20957 } elseif (isset($this->transfmrk[$topage])) {
20958 unset($this->transfmrk[$topage]);
20960 if (isset($tmpannots)) {
20961 $this->PageAnnots[$topage] = $tmpannots;
20962 } elseif (isset($this->PageAnnots[$topage])) {
20963 unset($this->PageAnnots[$topage]);
20965 // adjust outlines
20966 $tmpoutlines = $this->outlines;
20967 foreach ($tmpoutlines as $key => $outline) {
20968 if (!$outline['f']) {
20969 if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
20970 $this->outlines[$key]['p'] = ($outline['p'] + 1);
20971 } elseif ($outline['p'] == $frompage) {
20972 $this->outlines[$key]['p'] = $topage;
20976 // adjust dests
20977 $tmpdests = $this->dests;
20978 foreach ($tmpdests as $key => $dest) {
20979 if (!$dest['f']) {
20980 if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
20981 $this->dests[$key]['p'] = ($dest['p'] + 1);
20982 } elseif ($dest['p'] == $frompage) {
20983 $this->dests[$key]['p'] = $topage;
20987 // adjust links
20988 $tmplinks = $this->links;
20989 foreach ($tmplinks as $key => $link) {
20990 if (!$link['f']) {
20991 if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
20992 $this->links[$key]['p'] = ($link['p'] + 1);
20993 } elseif ($link['p'] == $frompage) {
20994 $this->links[$key]['p'] = $topage;
20998 // adjust javascript
20999 $jfrompage = $frompage;
21000 $jtopage = $topage;
21001 if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21002 foreach($pamatch[0] as $pk => $pmatch) {
21003 $pagenum = intval($pamatch[3][$pk]) + 1;
21004 if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
21005 $newpage = ($pagenum + 1);
21006 } elseif ($pagenum == $jfrompage) {
21007 $newpage = $jtopage;
21008 } else {
21009 $newpage = $pagenum;
21011 --$newpage;
21012 $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21013 $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21015 unset($pamatch);
21017 // return to last page
21018 $this->lastPage(true);
21019 return true;
21023 * Remove the specified page.
21024 * @param $page (int) page to remove
21025 * @return true in case of success, false in case of error.
21026 * @public
21027 * @since 4.6.004 (2009-04-23)
21029 public function deletePage($page) {
21030 if (($page < 1) OR ($page > $this->numpages)) {
21031 return false;
21033 // delete current page
21034 unset($this->pages[$page]);
21035 unset($this->pagedim[$page]);
21036 unset($this->pagelen[$page]);
21037 unset($this->intmrk[$page]);
21038 unset($this->bordermrk[$page]);
21039 unset($this->cntmrk[$page]);
21040 foreach ($this->pageobjects[$page] as $oid) {
21041 if (isset($this->offsets[$oid])){
21042 unset($this->offsets[$oid]);
21045 unset($this->pageobjects[$page]);
21046 if (isset($this->footerpos[$page])) {
21047 unset($this->footerpos[$page]);
21049 if (isset($this->footerlen[$page])) {
21050 unset($this->footerlen[$page]);
21052 if (isset($this->transfmrk[$page])) {
21053 unset($this->transfmrk[$page]);
21055 if (isset($this->PageAnnots[$page])) {
21056 unset($this->PageAnnots[$page]);
21058 if (isset($this->newpagegroup) AND !empty($this->newpagegroup)) {
21059 for ($i = $page; $i > 0; --$i) {
21060 if (isset($this->newpagegroup[$i]) AND (($i + $this->pagegroups[$this->newpagegroup[$i]]) > $page)) {
21061 --$this->pagegroups[$this->newpagegroup[$i]];
21062 break;
21066 if (isset($this->pageopen[$page])) {
21067 unset($this->pageopen[$page]);
21069 if ($page < $this->numpages) {
21070 // update remaining pages
21071 for ($i = $page; $i < $this->numpages; ++$i) {
21072 $j = $i + 1;
21073 // shift pages
21074 $this->setPageBuffer($i, $this->getPageBuffer($j));
21075 $this->pagedim[$i] = $this->pagedim[$j];
21076 $this->pagelen[$i] = $this->pagelen[$j];
21077 $this->intmrk[$i] = $this->intmrk[$j];
21078 $this->bordermrk[$i] = $this->bordermrk[$j];
21079 $this->cntmrk[$i] = $this->cntmrk[$j];
21080 $this->pageobjects[$i] = $this->pageobjects[$j];
21081 if (isset($this->footerpos[$j])) {
21082 $this->footerpos[$i] = $this->footerpos[$j];
21083 } elseif (isset($this->footerpos[$i])) {
21084 unset($this->footerpos[$i]);
21086 if (isset($this->footerlen[$j])) {
21087 $this->footerlen[$i] = $this->footerlen[$j];
21088 } elseif (isset($this->footerlen[$i])) {
21089 unset($this->footerlen[$i]);
21091 if (isset($this->transfmrk[$j])) {
21092 $this->transfmrk[$i] = $this->transfmrk[$j];
21093 } elseif (isset($this->transfmrk[$i])) {
21094 unset($this->transfmrk[$i]);
21096 if (isset($this->PageAnnots[$j])) {
21097 $this->PageAnnots[$i] = $this->PageAnnots[$j];
21098 } elseif (isset($this->PageAnnots[$i])) {
21099 unset($this->PageAnnots[$i]);
21101 if (isset($this->newpagegroup[$j])) {
21102 $this->newpagegroup[$i] = $this->newpagegroup[$j];
21103 unset($this->newpagegroup[$j]);
21105 if ($this->currpagegroup == $j) {
21106 $this->currpagegroup = $i;
21108 if (isset($this->pageopen[$j])) {
21109 $this->pageopen[$i] = $this->pageopen[$j];
21110 } elseif (isset($this->pageopen[$i])) {
21111 unset($this->pageopen[$i]);
21114 // remove last page
21115 unset($this->pages[$this->numpages]);
21116 unset($this->pagedim[$this->numpages]);
21117 unset($this->pagelen[$this->numpages]);
21118 unset($this->intmrk[$this->numpages]);
21119 unset($this->bordermrk[$this->numpages]);
21120 unset($this->cntmrk[$this->numpages]);
21121 foreach ($this->pageobjects[$this->numpages] as $oid) {
21122 if (isset($this->offsets[$oid])){
21123 unset($this->offsets[$oid]);
21126 unset($this->pageobjects[$this->numpages]);
21127 if (isset($this->footerpos[$this->numpages])) {
21128 unset($this->footerpos[$this->numpages]);
21130 if (isset($this->footerlen[$this->numpages])) {
21131 unset($this->footerlen[$this->numpages]);
21133 if (isset($this->transfmrk[$this->numpages])) {
21134 unset($this->transfmrk[$this->numpages]);
21136 if (isset($this->PageAnnots[$this->numpages])) {
21137 unset($this->PageAnnots[$this->numpages]);
21139 if (isset($this->newpagegroup[$this->numpages])) {
21140 unset($this->newpagegroup[$this->numpages]);
21142 if ($this->currpagegroup == $this->numpages) {
21143 $this->currpagegroup = ($this->numpages - 1);
21145 if (isset($this->pagegroups[$this->numpages])) {
21146 unset($this->pagegroups[$this->numpages]);
21148 if (isset($this->pageopen[$this->numpages])) {
21149 unset($this->pageopen[$this->numpages]);
21152 --$this->numpages;
21153 $this->page = $this->numpages;
21154 // adjust outlines
21155 $tmpoutlines = $this->outlines;
21156 foreach ($tmpoutlines as $key => $outline) {
21157 if (!$outline['f']) {
21158 if ($outline['p'] > $page) {
21159 $this->outlines[$key]['p'] = $outline['p'] - 1;
21160 } elseif ($outline['p'] == $page) {
21161 unset($this->outlines[$key]);
21165 // adjust dests
21166 $tmpdests = $this->dests;
21167 foreach ($tmpdests as $key => $dest) {
21168 if (!$dest['f']) {
21169 if ($dest['p'] > $page) {
21170 $this->dests[$key]['p'] = $dest['p'] - 1;
21171 } elseif ($dest['p'] == $page) {
21172 unset($this->dests[$key]);
21176 // adjust links
21177 $tmplinks = $this->links;
21178 foreach ($tmplinks as $key => $link) {
21179 if (!$link['f']) {
21180 if ($link['p'] > $page) {
21181 $this->links[$key]['p'] = $link['p'] - 1;
21182 } elseif ($link['p'] == $page) {
21183 unset($this->links[$key]);
21187 // adjust javascript
21188 $jpage = $page;
21189 if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript, $pamatch) > 0) {
21190 foreach($pamatch[0] as $pk => $pmatch) {
21191 $pagenum = intval($pamatch[3][$pk]) + 1;
21192 if ($pagenum >= $jpage) {
21193 $newpage = ($pagenum - 1);
21194 } elseif ($pagenum == $jpage) {
21195 $newpage = 1;
21196 } else {
21197 $newpage = $pagenum;
21199 --$newpage;
21200 $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21201 $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21203 unset($pamatch);
21205 // return to last page
21206 if ($this->numpages > 0) {
21207 $this->lastPage(true);
21209 return true;
21213 * Clone the specified page to a new page.
21214 * @param $page (int) number of page to copy (0 = current page)
21215 * @return true in case of success, false in case of error.
21216 * @public
21217 * @since 4.9.015 (2010-04-20)
21219 public function copyPage($page=0) {
21220 if ($page == 0) {
21221 // default value
21222 $page = $this->page;
21224 if (($page < 1) OR ($page > $this->numpages)) {
21225 return false;
21227 // close the last page
21228 $this->endPage();
21229 // copy all page-related states
21230 ++$this->numpages;
21231 $this->page = $this->numpages;
21232 $this->setPageBuffer($this->page, $this->getPageBuffer($page));
21233 $this->pagedim[$this->page] = $this->pagedim[$page];
21234 $this->pagelen[$this->page] = $this->pagelen[$page];
21235 $this->intmrk[$this->page] = $this->intmrk[$page];
21236 $this->bordermrk[$this->page] = $this->bordermrk[$page];
21237 $this->cntmrk[$this->page] = $this->cntmrk[$page];
21238 $this->pageobjects[$this->page] = $this->pageobjects[$page];
21239 $this->pageopen[$this->page] = false;
21240 if (isset($this->footerpos[$page])) {
21241 $this->footerpos[$this->page] = $this->footerpos[$page];
21243 if (isset($this->footerlen[$page])) {
21244 $this->footerlen[$this->page] = $this->footerlen[$page];
21246 if (isset($this->transfmrk[$page])) {
21247 $this->transfmrk[$this->page] = $this->transfmrk[$page];
21249 if (isset($this->PageAnnots[$page])) {
21250 $this->PageAnnots[$this->page] = $this->PageAnnots[$page];
21252 if (isset($this->newpagegroup[$page])) {
21253 // start a new group
21254 $this->newpagegroup[$this->page] = sizeof($this->newpagegroup) + 1;
21255 $this->currpagegroup = $this->newpagegroup[$this->page];
21256 $this->pagegroups[$this->currpagegroup] = 1;
21257 } elseif (isset($this->currpagegroup) AND ($this->currpagegroup > 0)) {
21258 ++$this->pagegroups[$this->currpagegroup];
21260 // copy outlines
21261 $tmpoutlines = $this->outlines;
21262 foreach ($tmpoutlines as $key => $outline) {
21263 if ($outline['p'] == $page) {
21264 $this->outlines[] = array('t' => $outline['t'], 'l' => $outline['l'], 'x' => $outline['x'], 'y' => $outline['y'], 'p' => $this->page, 'f' => $outline['f'], 's' => $outline['s'], 'c' => $outline['c']);
21267 // copy links
21268 $tmplinks = $this->links;
21269 foreach ($tmplinks as $key => $link) {
21270 if ($link['p'] == $page) {
21271 $this->links[] = array('p' => $this->page, 'y' => $link['y'], 'f' => $link['f']);
21274 // return to last page
21275 $this->lastPage(true);
21276 return true;
21280 * Output a Table of Content Index (TOC).
21281 * This method must be called after all Bookmarks were set.
21282 * Before calling this method you have to open the page using the addTOCPage() method.
21283 * After calling this method you have to call endTOCPage() to close the TOC page.
21284 * You can override this method to achieve different styles.
21285 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21286 * @param $numbersfont (string) set the font for page numbers (please use monospaced font for better alignment).
21287 * @param $filler (string) string used to fill the space between text and page number.
21288 * @param $toc_name (string) name to use for TOC bookmark.
21289 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21290 * @param $color (array) RGB color array for bookmark title (values from 0 to 255).
21291 * @public
21292 * @author Nicola Asuni
21293 * @since 4.5.000 (2009-01-02)
21294 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21296 public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21297 $fontsize = $this->FontSizePt;
21298 $fontfamily = $this->FontFamily;
21299 $fontstyle = $this->FontStyle;
21300 $w = $this->w - $this->lMargin - $this->rMargin;
21301 $spacer = $this->GetStringWidth(chr(32)) * 4;
21302 $lmargin = $this->lMargin;
21303 $rmargin = $this->rMargin;
21304 $x_start = $this->GetX();
21305 $page_first = $this->page;
21306 $current_page = $this->page;
21307 $page_fill_start = false;
21308 $page_fill_end = false;
21309 $current_column = $this->current_column;
21310 if (TCPDF_STATIC::empty_string($numbersfont)) {
21311 $numbersfont = $this->default_monospaced_font;
21313 if (TCPDF_STATIC::empty_string($filler)) {
21314 $filler = ' ';
21316 if (TCPDF_STATIC::empty_string($page)) {
21317 $gap = ' ';
21318 } else {
21319 $gap = '';
21320 if ($page < 1) {
21321 $page = 1;
21324 $this->SetFont($numbersfont, $fontstyle, $fontsize);
21325 $numwidth = $this->GetStringWidth('00000');
21326 $maxpage = 0; //used for pages on attached documents
21327 foreach ($this->outlines as $key => $outline) {
21328 // check for extra pages (used for attachments)
21329 if (($this->page > $page_first) AND ($outline['p'] >= $this->numpages)) {
21330 $outline['p'] += ($this->page - $page_first);
21332 if ($this->rtl) {
21333 $aligntext = 'R';
21334 $alignnum = 'L';
21335 } else {
21336 $aligntext = 'L';
21337 $alignnum = 'R';
21339 if ($outline['l'] == 0) {
21340 $this->SetFont($fontfamily, $outline['s'].'B', $fontsize);
21341 } else {
21342 $this->SetFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21344 $this->SetTextColorArray($outline['c']);
21345 // check for page break
21346 $this->checkPageBreak(2 * $this->getCellHeight($this->FontSize));
21347 // set margins and X position
21348 if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
21349 $this->lMargin = $lmargin;
21350 $this->rMargin = $rmargin;
21351 } else {
21352 if ($this->current_column != $current_column) {
21353 if ($this->rtl) {
21354 $x_start = $this->w - $this->columns[$this->current_column]['x'];
21355 } else {
21356 $x_start = $this->columns[$this->current_column]['x'];
21359 $lmargin = $this->lMargin;
21360 $rmargin = $this->rMargin;
21361 $current_page = $this->page;
21362 $current_column = $this->current_column;
21364 $this->SetX($x_start);
21365 $indent = ($spacer * $outline['l']);
21366 if ($this->rtl) {
21367 $this->x -= $indent;
21368 $this->rMargin = $this->w - $this->x;
21369 } else {
21370 $this->x += $indent;
21371 $this->lMargin = $this->x;
21373 $link = $this->AddLink();
21374 $this->SetLink($link, $outline['y'], $outline['p']);
21375 // write the text
21376 if ($this->rtl) {
21377 $txt = ' '.$outline['t'];
21378 } else {
21379 $txt = $outline['t'].' ';
21381 $this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21382 if ($this->rtl) {
21383 $tw = $this->x - $this->lMargin;
21384 } else {
21385 $tw = $this->w - $this->rMargin - $this->x;
21387 $this->SetFont($numbersfont, $fontstyle, $fontsize);
21388 if (TCPDF_STATIC::empty_string($page)) {
21389 $pagenum = $outline['p'];
21390 } else {
21391 // placemark to be replaced with the correct number
21392 $pagenum = '{#'.($outline['p']).'}';
21393 if ($this->isUnicodeFont()) {
21394 $pagenum = '{'.$pagenum.'}';
21396 $maxpage = max($maxpage, $outline['p']);
21398 $fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21399 $wfiller = $this->GetStringWidth($filler);
21400 if ($wfiller > 0) {
21401 $numfills = floor($fw / $wfiller);
21402 } else {
21403 $numfills = 0;
21405 if ($numfills > 0) {
21406 $rowfill = str_repeat($filler, $numfills);
21407 } else {
21408 $rowfill = '';
21410 if ($this->rtl) {
21411 $pagenum = $pagenum.$gap.$rowfill;
21412 } else {
21413 $pagenum = $rowfill.$gap.$pagenum;
21415 // write the number
21416 $this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21418 $page_last = $this->getPage();
21419 $numpages = ($page_last - $page_first + 1);
21420 // account for booklet mode
21421 if ($this->booklet) {
21422 // check if a blank page is required before TOC
21423 $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21424 $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21425 if ($page_fill_start) {
21426 // add a page at the end (to be moved before TOC)
21427 $this->addPage();
21428 ++$page_last;
21429 ++$numpages;
21431 if ($page_fill_end) {
21432 // add a page at the end
21433 $this->addPage();
21434 ++$page_last;
21435 ++$numpages;
21438 $maxpage = max($maxpage, $page_last);
21439 if (!TCPDF_STATIC::empty_string($page)) {
21440 for ($p = $page_first; $p <= $page_last; ++$p) {
21441 // get page data
21442 $temppage = $this->getPageBuffer($p);
21443 for ($n = 1; $n <= $maxpage; ++$n) {
21444 // update page numbers
21445 $a = '{#'.$n.'}';
21446 // get page number aliases
21447 $pnalias = $this->getInternalPageNumberAliases($a);
21448 // calculate replacement number
21449 if (($n >= $page) AND ($n <= $this->numpages)) {
21450 $np = $n + $numpages;
21451 } else {
21452 $np = $n;
21454 $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21455 $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21456 // replace aliases with numbers
21457 foreach ($pnalias['u'] as $u) {
21458 $sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21459 if ($this->rtl) {
21460 $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21461 } else {
21462 $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21464 $temppage = str_replace($u, $nr, $temppage);
21466 foreach ($pnalias['a'] as $a) {
21467 $sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21468 if ($this->rtl) {
21469 $nr = $na.' '.$sfill;
21470 } else {
21471 $nr = $sfill.' '.$na;
21473 $temppage = str_replace($a, $nr, $temppage);
21476 // save changes
21477 $this->setPageBuffer($p, $temppage);
21479 // move pages
21480 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21481 if ($page_fill_start) {
21482 $this->movePage($page_last, $page_first);
21484 for ($i = 0; $i < $numpages; ++$i) {
21485 $this->movePage($page_last, $page);
21491 * Output a Table Of Content Index (TOC) using HTML templates.
21492 * This method must be called after all Bookmarks were set.
21493 * Before calling this method you have to open the page using the addTOCPage() method.
21494 * After calling this method you have to call endTOCPage() to close the TOC page.
21495 * @param $page (int) page number where this TOC should be inserted (leave empty for current page).
21496 * @param $toc_name (string) name to use for TOC bookmark.
21497 * @param $templates (array) array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21498 * @param $correct_align (boolean) if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL)
21499 * @param $style (string) Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21500 * @param $color (array) RGB color array for title (values from 0 to 255).
21501 * @public
21502 * @author Nicola Asuni
21503 * @since 5.0.001 (2010-05-06)
21504 * @see addTOCPage(), endTOCPage(), addTOC()
21506 public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21507 $filler = ' ';
21508 $prev_htmlLinkColorArray = $this->htmlLinkColorArray;
21509 $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
21510 // set new style for link
21511 $this->htmlLinkColorArray = array();
21512 $this->htmlLinkFontStyle = '';
21513 $page_first = $this->getPage();
21514 $page_fill_start = false;
21515 $page_fill_end = false;
21516 // get the font type used for numbers in each template
21517 $current_font = $this->FontFamily;
21518 foreach ($templates as $level => $html) {
21519 $dom = $this->getHtmlDomArray($html);
21520 foreach ($dom as $key => $value) {
21521 if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21522 $this->SetFont($dom[($key - 1)]['fontname']);
21523 $templates['F'.$level] = $this->isUnicodeFont();
21527 $this->SetFont($current_font);
21528 $maxpage = 0; //used for pages on attached documents
21529 foreach ($this->outlines as $key => $outline) {
21530 // get HTML template
21531 $row = $templates[$outline['l']];
21532 if (TCPDF_STATIC::empty_string($page)) {
21533 $pagenum = $outline['p'];
21534 } else {
21535 // placemark to be replaced with the correct number
21536 $pagenum = '{#'.($outline['p']).'}';
21537 if ($templates['F'.$outline['l']]) {
21538 $pagenum = '{'.$pagenum.'}';
21540 $maxpage = max($maxpage, $outline['p']);
21542 // replace templates with current values
21543 $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21544 $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21545 // add link to page
21546 $row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21547 // write bookmark entry
21548 $this->writeHTML($row, false, false, true, false, '');
21550 // restore link styles
21551 $this->htmlLinkColorArray = $prev_htmlLinkColorArray;
21552 $this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
21553 // move TOC page and replace numbers
21554 $page_last = $this->getPage();
21555 $numpages = ($page_last - $page_first + 1);
21556 // account for booklet mode
21557 if ($this->booklet) {
21558 // check if a blank page is required before TOC
21559 $page_fill_start = ((($page_first % 2) == 0) XOR (($page % 2) == 0));
21560 $page_fill_end = (!((($numpages % 2) == 0) XOR ($page_fill_start)));
21561 if ($page_fill_start) {
21562 // add a page at the end (to be moved before TOC)
21563 $this->addPage();
21564 ++$page_last;
21565 ++$numpages;
21567 if ($page_fill_end) {
21568 // add a page at the end
21569 $this->addPage();
21570 ++$page_last;
21571 ++$numpages;
21574 $maxpage = max($maxpage, $page_last);
21575 if (!TCPDF_STATIC::empty_string($page)) {
21576 for ($p = $page_first; $p <= $page_last; ++$p) {
21577 // get page data
21578 $temppage = $this->getPageBuffer($p);
21579 for ($n = 1; $n <= $maxpage; ++$n) {
21580 // update page numbers
21581 $a = '{#'.$n.'}';
21582 // get page number aliases
21583 $pnalias = $this->getInternalPageNumberAliases($a);
21584 // calculate replacement number
21585 if ($n >= $page) {
21586 $np = $n + $numpages;
21587 } else {
21588 $np = $n;
21590 $na = TCPDF_STATIC::formatTOCPageNumber(($this->starting_page_number + $np - 1));
21591 $nu = TCPDF_FONTS::UTF8ToUTF16BE($na, false, $this->isunicode, $this->CurrentFont);
21592 // replace aliases with numbers
21593 foreach ($pnalias['u'] as $u) {
21594 if ($correct_align) {
21595 $sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21596 if ($this->rtl) {
21597 $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21598 } else {
21599 $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21601 } else {
21602 $nr = $nu;
21604 $temppage = str_replace($u, $nr, $temppage);
21606 foreach ($pnalias['a'] as $a) {
21607 if ($correct_align) {
21608 $sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21609 if ($this->rtl) {
21610 $nr = $na.' '.$sfill;
21611 } else {
21612 $nr = $sfill.' '.$na;
21614 } else {
21615 $nr = $na;
21617 $temppage = str_replace($a, $nr, $temppage);
21620 // save changes
21621 $this->setPageBuffer($p, $temppage);
21623 // move pages
21624 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21625 if ($page_fill_start) {
21626 $this->movePage($page_last, $page_first);
21628 for ($i = 0; $i < $numpages; ++$i) {
21629 $this->movePage($page_last, $page);
21635 * Stores a copy of the current TCPDF object used for undo operation.
21636 * @public
21637 * @since 4.5.029 (2009-03-19)
21639 public function startTransaction() {
21640 if (isset($this->objcopy)) {
21641 // remove previous copy
21642 $this->commitTransaction();
21644 // record current page number and Y position
21645 $this->start_transaction_page = $this->page;
21646 $this->start_transaction_y = $this->y;
21647 // clone current object
21648 $this->objcopy = TCPDF_STATIC::objclone($this);
21652 * Delete the copy of the current TCPDF object used for undo operation.
21653 * @public
21654 * @since 4.5.029 (2009-03-19)
21656 public function commitTransaction() {
21657 if (isset($this->objcopy)) {
21658 $this->objcopy->_destroy(true, true);
21659 unset($this->objcopy);
21664 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21665 * @param $self (boolean) if true restores current class object to previous state without the need of reassignment via the returned value.
21666 * @return TCPDF object.
21667 * @public
21668 * @since 4.5.029 (2009-03-19)
21670 public function rollbackTransaction($self=false) {
21671 if (isset($this->objcopy)) {
21672 if (isset($this->objcopy->diskcache) AND $this->objcopy->diskcache) {
21673 // truncate files to previous values
21674 foreach ($this->objcopy->cache_file_length as $file => $length) {
21675 $file = substr($file, 1);
21676 $handle = fopen($file, 'r+');
21677 ftruncate($handle, $length);
21680 $this->_destroy(true, true);
21681 if ($self) {
21682 $objvars = get_object_vars($this->objcopy);
21683 foreach ($objvars as $key => $value) {
21684 $this->$key = $value;
21687 return $this->objcopy;
21689 return $this;
21692 // --- MULTI COLUMNS METHODS -----------------------
21695 * Set multiple columns of the same size
21696 * @param $numcols (int) number of columns (set to zero to disable columns mode)
21697 * @param $width (int) column width
21698 * @param $y (int) column starting Y position (leave empty for current Y position)
21699 * @public
21700 * @since 4.9.001 (2010-03-28)
21702 public function setEqualColumns($numcols=0, $width=0, $y='') {
21703 $this->columns = array();
21704 if ($numcols < 2) {
21705 $numcols = 0;
21706 $this->columns = array();
21707 } else {
21708 // maximum column width
21709 $maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
21710 if (($width == 0) OR ($width > $maxwidth)) {
21711 $width = $maxwidth;
21713 if (TCPDF_STATIC::empty_string($y)) {
21714 $y = $this->y;
21716 // space between columns
21717 $space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
21718 // fill the columns array (with, space, starting Y position)
21719 for ($i = 0; $i < $numcols; ++$i) {
21720 $this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
21723 $this->num_columns = $numcols;
21724 $this->current_column = 0;
21725 $this->column_start_page = $this->page;
21726 $this->selectColumn(0);
21730 * Remove columns and reset page margins.
21731 * @public
21732 * @since 5.9.072 (2011-04-26)
21734 public function resetColumns() {
21735 $this->lMargin = $this->original_lMargin;
21736 $this->rMargin = $this->original_rMargin;
21737 $this->setEqualColumns();
21741 * Set columns array.
21742 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
21743 * @param $columns (array)
21744 * @public
21745 * @since 4.9.001 (2010-03-28)
21747 public function setColumnsArray($columns) {
21748 $this->columns = $columns;
21749 $this->num_columns = count($columns);
21750 $this->current_column = 0;
21751 $this->column_start_page = $this->page;
21752 $this->selectColumn(0);
21756 * Set position at a given column
21757 * @param $col (int) column number (from 0 to getNumberOfColumns()-1); empty string = current column.
21758 * @public
21759 * @since 4.9.001 (2010-03-28)
21761 public function selectColumn($col='') {
21762 if (is_string($col)) {
21763 $col = $this->current_column;
21764 } elseif ($col >= $this->num_columns) {
21765 $col = 0;
21767 $xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
21768 $enable_thead = false;
21769 if ($this->num_columns > 1) {
21770 if ($col != $this->current_column) {
21771 // move Y pointer at the top of the column
21772 if ($this->column_start_page == $this->page) {
21773 $this->y = $this->columns[$col]['y'];
21774 } else {
21775 $this->y = $this->tMargin;
21777 // Avoid to write table headers more than once
21778 if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
21779 $enable_thead = true;
21780 $this->maxselcol['page'] = $this->page;
21781 $this->maxselcol['column'] = $col;
21784 $xshift = $this->colxshift;
21785 // set X position of the current column by case
21786 $listindent = ($this->listindentlevel * $this->listindent);
21787 // calculate column X position
21788 $colpos = 0;
21789 for ($i = 0; $i < $col; ++$i) {
21790 $colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
21792 if ($this->rtl) {
21793 $x = $this->w - $this->original_rMargin - $colpos;
21794 $this->rMargin = ($this->w - $x + $listindent);
21795 $this->lMargin = ($x - $this->columns[$col]['w']);
21796 $this->x = $x - $listindent;
21797 } else {
21798 $x = $this->original_lMargin + $colpos;
21799 $this->lMargin = ($x + $listindent);
21800 $this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
21801 $this->x = $x + $listindent;
21803 $this->columns[$col]['x'] = $x;
21805 $this->current_column = $col;
21806 // fix for HTML mode
21807 $this->newline = true;
21808 // print HTML table header (if any)
21809 if ((!TCPDF_STATIC::empty_string($this->thead)) AND (!$this->inthead)) {
21810 if ($enable_thead) {
21811 // print table header
21812 $this->writeHTML($this->thead, false, false, false, false, '');
21813 $this->y += $xshift['s']['V'];
21814 // store end of header position
21815 if (!isset($this->columns[$col]['th'])) {
21816 $this->columns[$col]['th'] = array();
21818 $this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
21819 $this->lasth = 0;
21820 } elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
21821 $this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
21824 // account for an html table cell over multiple columns
21825 if ($this->rtl) {
21826 $this->rMargin += $xshift['x'];
21827 $this->x -= ($xshift['x'] + $xshift['p']['R']);
21828 } else {
21829 $this->lMargin += $xshift['x'];
21830 $this->x += $xshift['x'] + $xshift['p']['L'];
21835 * Return the current column number
21836 * @return int current column number
21837 * @public
21838 * @since 5.5.011 (2010-07-08)
21840 public function getColumn() {
21841 return $this->current_column;
21845 * Return the current number of columns.
21846 * @return int number of columns
21847 * @public
21848 * @since 5.8.018 (2010-08-25)
21850 public function getNumberOfColumns() {
21851 return $this->num_columns;
21855 * Set Text rendering mode.
21856 * @param $stroke (int) outline size in user units (0 = disable).
21857 * @param $fill (boolean) if true fills the text (default).
21858 * @param $clip (boolean) if true activate clipping mode
21859 * @public
21860 * @since 4.9.008 (2009-04-02)
21862 public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
21863 // Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
21864 // convert text rendering parameters
21865 if ($stroke < 0) {
21866 $stroke = 0;
21868 if ($fill === true) {
21869 if ($stroke > 0) {
21870 if ($clip === true) {
21871 // Fill, then stroke text and add to path for clipping
21872 $textrendermode = 6;
21873 } else {
21874 // Fill, then stroke text
21875 $textrendermode = 2;
21877 $textstrokewidth = $stroke;
21878 } else {
21879 if ($clip === true) {
21880 // Fill text and add to path for clipping
21881 $textrendermode = 4;
21882 } else {
21883 // Fill text
21884 $textrendermode = 0;
21887 } else {
21888 if ($stroke > 0) {
21889 if ($clip === true) {
21890 // Stroke text and add to path for clipping
21891 $textrendermode = 5;
21892 } else {
21893 // Stroke text
21894 $textrendermode = 1;
21896 $textstrokewidth = $stroke;
21897 } else {
21898 if ($clip === true) {
21899 // Add text to path for clipping
21900 $textrendermode = 7;
21901 } else {
21902 // Neither fill nor stroke text (invisible)
21903 $textrendermode = 3;
21907 $this->textrendermode = $textrendermode;
21908 $this->textstrokewidth = $stroke;
21912 * Set parameters for drop shadow effect for text.
21913 * @param $params (array) Array of parameters: enabled (boolean) set to true to enable shadow; depth_w (float) shadow width in user units; depth_h (float) shadow height in user units; color (array) shadow color or false to use the stroke color; opacity (float) Alpha value: real value from 0 (transparent) to 1 (opaque); blend_mode (string) blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity.
21914 * @since 5.9.174 (2012-07-25)
21915 * @public
21917 public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
21918 if (isset($params['enabled'])) {
21919 $this->txtshadow['enabled'] = $params['enabled']?true:false;
21920 } else {
21921 $this->txtshadow['enabled'] = false;
21923 if (isset($params['depth_w'])) {
21924 $this->txtshadow['depth_w'] = floatval($params['depth_w']);
21925 } else {
21926 $this->txtshadow['depth_w'] = 0;
21928 if (isset($params['depth_h'])) {
21929 $this->txtshadow['depth_h'] = floatval($params['depth_h']);
21930 } else {
21931 $this->txtshadow['depth_h'] = 0;
21933 if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
21934 $this->txtshadow['color'] = $params['color'];
21935 } else {
21936 $this->txtshadow['color'] = $this->strokecolor;
21938 if (isset($params['opacity'])) {
21939 $this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
21940 } else {
21941 $this->txtshadow['opacity'] = 1;
21943 if (isset($params['blend_mode']) AND in_array($params['blend_mode'], array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
21944 $this->txtshadow['blend_mode'] = $params['blend_mode'];
21945 } else {
21946 $this->txtshadow['blend_mode'] = 'Normal';
21948 if ((($this->txtshadow['depth_w'] == 0) AND ($this->txtshadow['depth_h'] == 0)) OR ($this->txtshadow['opacity'] == 0)) {
21949 $this->txtshadow['enabled'] = false;
21954 * Return the text shadow parameters array.
21955 * @return Array of parameters.
21956 * @since 5.9.174 (2012-07-25)
21957 * @public
21959 public function getTextShadow() {
21960 return $this->txtshadow;
21964 * Returns an array of chars containing soft hyphens.
21965 * @param $word (array) array of chars
21966 * @param $patterns (array) Array of hypenation patterns.
21967 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algoritm.
21968 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
21969 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
21970 * @param $charmin (int) Minimum word length to apply the hyphenation algoritm.
21971 * @param $charmax (int) Maximum length of broken piece of word.
21972 * @return array text with soft hyphens
21973 * @author Nicola Asuni
21974 * @since 4.9.012 (2010-04-12)
21975 * @protected
21977 protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
21978 $hyphenword = array(); // hyphens positions
21979 $numchars = count($word);
21980 if ($numchars <= $charmin) {
21981 return $word;
21983 $word_string = TCPDF_FONTS::UTF8ArrSubString($word, '', '', $this->isunicode);
21984 // some words will be returned as-is
21985 $pattern = '/^([a-zA-Z0-9_\.\-]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/';
21986 if (preg_match($pattern, $word_string) > 0) {
21987 // email
21988 return $word;
21990 $pattern = '/(([a-zA-Z0-9\-]+\.)?)((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/';
21991 if (preg_match($pattern, $word_string) > 0) {
21992 // URL
21993 return $word;
21995 if (isset($dictionary[$word_string])) {
21996 return TCPDF_FONTS::UTF8StringToArray($dictionary[$word_string], $this->isunicode, $this->CurrentFont);
21998 // suround word with '_' characters
21999 $tmpword = array_merge(array(95), $word, array(95));
22000 $tmpnumchars = $numchars + 2;
22001 $maxpos = $tmpnumchars - $charmin;
22002 for ($pos = 0; $pos < $maxpos; ++$pos) {
22003 $imax = min(($tmpnumchars - $pos), $charmax);
22004 for ($i = $charmin; $i <= $imax; ++$i) {
22005 $subword = strtolower(TCPDF_FONTS::UTF8ArrSubString($tmpword, $pos, ($pos + $i), $this->isunicode));
22006 if (isset($patterns[$subword])) {
22007 $pattern = TCPDF_FONTS::UTF8StringToArray($patterns[$subword], $this->isunicode, $this->CurrentFont);
22008 $pattern_length = count($pattern);
22009 $digits = 1;
22010 for ($j = 0; $j < $pattern_length; ++$j) {
22011 // check if $pattern[$j] is a number
22012 if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
22013 if ($j == 0) {
22014 $zero = $pos - 1;
22015 } else {
22016 $zero = $pos + $j - $digits;
22018 if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] != $pattern[$j])) {
22019 $hyphenword[$zero] = TCPDF_FONTS::unichr($pattern[$j], $this->isunicode);
22021 ++$digits;
22027 $inserted = 0;
22028 $maxpos = $numchars - $rightmin;
22029 for ($i = $leftmin; $i <= $maxpos; ++$i) {
22030 if (isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
22031 // 173 = soft hyphen character
22032 array_splice($word, $i + $inserted, 0, 173);
22033 ++$inserted;
22036 return $word;
22040 * Returns text with soft hyphens.
22041 * @param $text (string) text to process
22042 * @param $patterns (mixed) Array of hypenation patterns or a TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
22043 * @param $dictionary (array) Array of words to be returned without applying the hyphenation algoritm.
22044 * @param $leftmin (int) Minimum number of character to leave on the left of the word without applying the hyphens.
22045 * @param $rightmin (int) Minimum number of character to leave on the right of the word without applying the hyphens.
22046 * @param $charmin (int) Minimum word length to apply the hyphenation algoritm.
22047 * @param $charmax (int) Maximum length of broken piece of word.
22048 * @return array text with soft hyphens
22049 * @author Nicola Asuni
22050 * @since 4.9.012 (2010-04-12)
22051 * @public
22053 public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22054 $text = $this->unhtmlentities($text);
22055 $word = array(); // last word
22056 $txtarr = array(); // text to be returned
22057 $intag = false; // true if we are inside an HTML tag
22058 if (!is_array($patterns)) {
22059 $patterns = TCPDF_STATIC::getHyphenPatternsFromTEX($patterns);
22061 // get array of characters
22062 $unichars = TCPDF_FONTS::UTF8StringToArray($text, $this->isunicode, $this->CurrentFont);
22063 // for each char
22064 foreach ($unichars as $char) {
22065 if ((!$intag) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22066 // letter character
22067 $word[] = $char;
22068 } else {
22069 // other type of character
22070 if (!TCPDF_STATIC::empty_string($word)) {
22071 // hypenate the word
22072 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22073 $word = array();
22075 $txtarr[] = $char;
22076 if (chr($char) == '<') {
22077 // we are inside an HTML tag
22078 $intag = true;
22079 } elseif ($intag AND (chr($char) == '>')) {
22080 // end of HTML tag
22081 $intag = false;
22085 if (!TCPDF_STATIC::empty_string($word)) {
22086 // hypenate the word
22087 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22089 // convert char array to string and return
22090 return TCPDF_FONTS::UTF8ArrSubString($txtarr, '', '', $this->isunicode);
22094 * Enable/disable rasterization of vector images using ImageMagick library.
22095 * @param $mode (boolean) if true enable rasterization, false otherwise.
22096 * @public
22097 * @since 5.0.000 (2010-04-27)
22099 public function setRasterizeVectorImages($mode) {
22100 $this->rasterize_vector_images = $mode;
22104 * Enable or disable default option for font subsetting.
22105 * @param $enable (boolean) if true enable font subsetting by default.
22106 * @author Nicola Asuni
22107 * @public
22108 * @since 5.3.002 (2010-06-07)
22110 public function setFontSubsetting($enable=true) {
22111 if ($this->pdfa_mode) {
22112 $this->font_subsetting = false;
22113 } else {
22114 $this->font_subsetting = $enable ? true : false;
22119 * Return the default option for font subsetting.
22120 * @return boolean default font subsetting state.
22121 * @author Nicola Asuni
22122 * @public
22123 * @since 5.3.002 (2010-06-07)
22125 public function getFontSubsetting() {
22126 return $this->font_subsetting;
22130 * Left trim the input string
22131 * @param $str (string) string to trim
22132 * @param $replace (string) string that replace spaces.
22133 * @return left trimmed string
22134 * @author Nicola Asuni
22135 * @public
22136 * @since 5.8.000 (2010-08-11)
22138 public function stringLeftTrim($str, $replace='') {
22139 return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
22143 * Right trim the input string
22144 * @param $str (string) string to trim
22145 * @param $replace (string) string that replace spaces.
22146 * @return right trimmed string
22147 * @author Nicola Asuni
22148 * @public
22149 * @since 5.8.000 (2010-08-11)
22151 public function stringRightTrim($str, $replace='') {
22152 return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
22156 * Trim the input string
22157 * @param $str (string) string to trim
22158 * @param $replace (string) string that replace spaces.
22159 * @return trimmed string
22160 * @author Nicola Asuni
22161 * @public
22162 * @since 5.8.000 (2010-08-11)
22164 public function stringTrim($str, $replace='') {
22165 $str = $this->stringLeftTrim($str, $replace);
22166 $str = $this->stringRightTrim($str, $replace);
22167 return $str;
22171 * Return true if the current font is unicode type.
22172 * @return true for unicode font, false otherwise.
22173 * @author Nicola Asuni
22174 * @public
22175 * @since 5.8.002 (2010-08-14)
22177 public function isUnicodeFont() {
22178 return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
22182 * Return normalized font name
22183 * @param $fontfamily (string) property string containing font family names
22184 * @return string normalized font name
22185 * @author Nicola Asuni
22186 * @public
22187 * @since 5.8.004 (2010-08-17)
22189 public function getFontFamilyName($fontfamily) {
22190 // remove spaces and symbols
22191 $fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22192 // extract all font names
22193 $fontslist = preg_split('/[,]/', $fontfamily);
22194 // find first valid font name
22195 foreach ($fontslist as $font) {
22196 // replace font variations
22197 $font = preg_replace('/italic$/', 'I', $font);
22198 $font = preg_replace('/oblique$/', 'I', $font);
22199 $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22200 // replace common family names and core fonts
22201 $pattern = array();
22202 $replacement = array();
22203 $pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22204 $replacement[] = 'times';
22205 $pattern[] = '/^sansserif/';
22206 $replacement[] = 'helvetica';
22207 $pattern[] = '/^monospace/';
22208 $replacement[] = 'courier';
22209 $font = preg_replace($pattern, $replacement, $font);
22210 if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
22211 return $font;
22214 // return current font as default
22215 return $this->CurrentFont['fontkey'];
22219 * Start a new XObject Template.
22220 * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images).
22221 * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked.
22222 * Note: X,Y coordinates will be reset to 0,0.
22223 * @param $w (int) Template width in user units (empty string or zero = page width less margins).
22224 * @param $h (int) Template height in user units (empty string or zero = page height less margins).
22225 * @param $group (mixed) Set transparency group. Can be a boolean value or an array specifying optional parameters: 'CS' (solour space name), 'I' (boolean flag to indicate isolated group) and 'K' (boolean flag to indicate knockout group).
22226 * @return int the XObject Template ID in case of success or false in case of error.
22227 * @author Nicola Asuni
22228 * @public
22229 * @since 5.8.017 (2010-08-24)
22230 * @see endTemplate(), printTemplate()
22232 public function startTemplate($w=0, $h=0, $group=false) {
22233 if ($this->inxobj) {
22234 // we are already inside an XObject template
22235 return false;
22237 $this->inxobj = true;
22238 ++$this->n;
22239 // XObject ID
22240 $this->xobjid = 'XT'.$this->n;
22241 // object ID
22242 $this->xobjects[$this->xobjid] = array('n' => $this->n);
22243 // store current graphic state
22244 $this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22245 // initialize data
22246 $this->xobjects[$this->xobjid]['intmrk'] = 0;
22247 $this->xobjects[$this->xobjid]['transfmrk'] = array();
22248 $this->xobjects[$this->xobjid]['outdata'] = '';
22249 $this->xobjects[$this->xobjid]['xobjects'] = array();
22250 $this->xobjects[$this->xobjid]['images'] = array();
22251 $this->xobjects[$this->xobjid]['fonts'] = array();
22252 $this->xobjects[$this->xobjid]['annotations'] = array();
22253 $this->xobjects[$this->xobjid]['extgstates'] = array();
22254 $this->xobjects[$this->xobjid]['gradients'] = array();
22255 $this->xobjects[$this->xobjid]['spot_colors'] = array();
22256 // set new environment
22257 $this->num_columns = 1;
22258 $this->current_column = 0;
22259 $this->SetAutoPageBreak(false);
22260 if (($w === '') OR ($w <= 0)) {
22261 $w = $this->w - $this->lMargin - $this->rMargin;
22263 if (($h === '') OR ($h <= 0)) {
22264 $h = $this->h - $this->tMargin - $this->bMargin;
22266 $this->xobjects[$this->xobjid]['x'] = 0;
22267 $this->xobjects[$this->xobjid]['y'] = 0;
22268 $this->xobjects[$this->xobjid]['w'] = $w;
22269 $this->xobjects[$this->xobjid]['h'] = $h;
22270 $this->w = $w;
22271 $this->h = $h;
22272 $this->wPt = $this->w * $this->k;
22273 $this->hPt = $this->h * $this->k;
22274 $this->fwPt = $this->wPt;
22275 $this->fhPt = $this->hPt;
22276 $this->x = 0;
22277 $this->y = 0;
22278 $this->lMargin = 0;
22279 $this->rMargin = 0;
22280 $this->tMargin = 0;
22281 $this->bMargin = 0;
22282 // set group mode
22283 $this->xobjects[$this->xobjid]['group'] = $group;
22284 return $this->xobjid;
22288 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22289 * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images).
22290 * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked.
22291 * @return int the XObject Template ID in case of success or false in case of error.
22292 * @author Nicola Asuni
22293 * @public
22294 * @since 5.8.017 (2010-08-24)
22295 * @see startTemplate(), printTemplate()
22297 public function endTemplate() {
22298 if (!$this->inxobj) {
22299 // we are not inside a template
22300 return false;
22302 $this->inxobj = false;
22303 // restore previous graphic state
22304 $this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
22305 return $this->xobjid;
22309 * Print an XObject Template.
22310 * You can print an XObject Template inside the currently opened Template.
22311 * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images).
22312 * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked.
22313 * @param $id (string) The ID of XObject Template to print.
22314 * @param $x (int) X position in user units (empty string = current x position)
22315 * @param $y (int) Y position in user units (empty string = current y position)
22316 * @param $w (int) Width in user units (zero = remaining page width)
22317 * @param $h (int) Height in user units (zero = remaining page height)
22318 * @param $align (string) Indicates the alignment of the pointer next to template insertion relative to template height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
22319 * @param $palign (string) Allows to center or align the template on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
22320 * @param $fitonpage (boolean) If true the template is resized to not exceed page dimensions.
22321 * @author Nicola Asuni
22322 * @public
22323 * @since 5.8.017 (2010-08-24)
22324 * @see startTemplate(), endTemplate()
22326 public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22327 if ($this->state != 2) {
22328 return;
22330 if (!isset($this->xobjects[$id])) {
22331 $this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22333 if ($this->inxobj) {
22334 if ($id == $this->xobjid) {
22335 // close current template
22336 $this->endTemplate();
22337 } else {
22338 // use the template as resource for the template currently opened
22339 $this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
22342 // set default values
22343 if ($x === '') {
22344 $x = $this->x;
22346 if ($y === '') {
22347 $y = $this->y;
22349 // check page for no-write regions and adapt page margins if necessary
22350 list($x, $y) = $this->checkPageRegions($h, $x, $y);
22351 $ow = $this->xobjects[$id]['w'];
22352 if ($ow <= 0) {
22353 $ow = 1;
22355 $oh = $this->xobjects[$id]['h'];
22356 if ($oh <= 0) {
22357 $oh = 1;
22359 // calculate template width and height on document
22360 if (($w <= 0) AND ($h <= 0)) {
22361 $w = $ow;
22362 $h = $oh;
22363 } elseif ($w <= 0) {
22364 $w = $h * $ow / $oh;
22365 } elseif ($h <= 0) {
22366 $h = $w * $oh / $ow;
22368 // fit the template on available space
22369 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22370 // set page alignment
22371 $rb_y = $y + $h;
22372 // set alignment
22373 if ($this->rtl) {
22374 if ($palign == 'L') {
22375 $xt = $this->lMargin;
22376 } elseif ($palign == 'C') {
22377 $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22378 } elseif ($palign == 'R') {
22379 $xt = $this->w - $this->rMargin - $w;
22380 } else {
22381 $xt = $x - $w;
22383 $rb_x = $xt;
22384 } else {
22385 if ($palign == 'L') {
22386 $xt = $this->lMargin;
22387 } elseif ($palign == 'C') {
22388 $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22389 } elseif ($palign == 'R') {
22390 $xt = $this->w - $this->rMargin - $w;
22391 } else {
22392 $xt = $x;
22394 $rb_x = $xt + $w;
22396 // print XObject Template + Transformation matrix
22397 $this->StartTransform();
22398 // translate and scale
22399 $sx = ($w / $ow);
22400 $sy = ($h / $oh);
22401 $tm = array();
22402 $tm[0] = $sx;
22403 $tm[1] = 0;
22404 $tm[2] = 0;
22405 $tm[3] = $sy;
22406 $tm[4] = $xt * $this->k;
22407 $tm[5] = ($this->h - $h - $y) * $this->k;
22408 $this->Transform($tm);
22409 // set object
22410 $this->_out('/'.$id.' Do');
22411 $this->StopTransform();
22412 // add annotations
22413 if (!empty($this->xobjects[$id]['annotations'])) {
22414 foreach ($this->xobjects[$id]['annotations'] as $annot) {
22415 // transform original coordinates
22416 $coordlt = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
22417 $ax = ($coordlt[4] / $this->k);
22418 $ay = ($this->h - $h - ($coordlt[5] / $this->k));
22419 $coordrb = TCPDF_STATIC::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
22420 $aw = ($coordrb[4] / $this->k) - $ax;
22421 $ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
22422 $this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22425 // set pointer to align the next text/objects
22426 switch($align) {
22427 case 'T': {
22428 $this->y = $y;
22429 $this->x = $rb_x;
22430 break;
22432 case 'M': {
22433 $this->y = $y + round($h/2);
22434 $this->x = $rb_x;
22435 break;
22437 case 'B': {
22438 $this->y = $rb_y;
22439 $this->x = $rb_x;
22440 break;
22442 case 'N': {
22443 $this->SetY($rb_y);
22444 break;
22446 default:{
22447 break;
22453 * Set the percentage of character stretching.
22454 * @param $perc (int) percentage of stretching (100 = no stretching)
22455 * @author Nicola Asuni
22456 * @public
22457 * @since 5.9.000 (2010-09-29)
22459 public function setFontStretching($perc=100) {
22460 $this->font_stretching = $perc;
22464 * Get the percentage of character stretching.
22465 * @return float stretching value
22466 * @author Nicola Asuni
22467 * @public
22468 * @since 5.9.000 (2010-09-29)
22470 public function getFontStretching() {
22471 return $this->font_stretching;
22475 * Set the amount to increase or decrease the space between characters in a text.
22476 * @param $spacing (float) amount to increase or decrease the space between characters in a text (0 = default spacing)
22477 * @author Nicola Asuni
22478 * @public
22479 * @since 5.9.000 (2010-09-29)
22481 public function setFontSpacing($spacing=0) {
22482 $this->font_spacing = $spacing;
22486 * Get the amount to increase or decrease the space between characters in a text.
22487 * @return int font spacing (tracking) value
22488 * @author Nicola Asuni
22489 * @public
22490 * @since 5.9.000 (2010-09-29)
22492 public function getFontSpacing() {
22493 return $this->font_spacing;
22497 * Return an array of no-write page regions
22498 * @return array of no-write page regions
22499 * @author Nicola Asuni
22500 * @public
22501 * @since 5.9.003 (2010-10-13)
22502 * @see setPageRegions(), addPageRegion()
22504 public function getPageRegions() {
22505 return $this->page_regions;
22509 * Set no-write regions on page.
22510 * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code.
22511 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22512 * You can set multiple regions for the same page.
22513 * @param $regions (array) array of no-write regions. For each region you can define an array as follow: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). Omit this parameter to remove all regions.
22514 * @author Nicola Asuni
22515 * @public
22516 * @since 5.9.003 (2010-10-13)
22517 * @see addPageRegion(), getPageRegions()
22519 public function setPageRegions($regions=array()) {
22520 // empty current regions array
22521 $this->page_regions = array();
22522 // add regions
22523 foreach ($regions as $data) {
22524 $this->addPageRegion($data);
22529 * Add a single no-write region on selected page.
22530 * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code.
22531 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22532 * You can set multiple regions for the same page.
22533 * @param $region (array) array of a single no-write region array: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right).
22534 * @author Nicola Asuni
22535 * @public
22536 * @since 5.9.003 (2010-10-13)
22537 * @see setPageRegions(), getPageRegions()
22539 public function addPageRegion($region) {
22540 if (!isset($region['page']) OR empty($region['page'])) {
22541 $region['page'] = $this->page;
22543 if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22544 AND isset($region['yt']) AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22545 AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22546 $this->page_regions[] = $region;
22551 * Remove a single no-write region.
22552 * @param $key (int) region key
22553 * @author Nicola Asuni
22554 * @public
22555 * @since 5.9.003 (2010-10-13)
22556 * @see setPageRegions(), getPageRegions()
22558 public function removePageRegion($key) {
22559 if (isset($this->page_regions[$key])) {
22560 unset($this->page_regions[$key]);
22565 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22566 * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code.
22567 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22568 * @param $h (float) height of the text/image/object to print in user units
22569 * @param $x (float) current X coordinate in user units
22570 * @param $y (float) current Y coordinate in user units
22571 * @return array($x, $y)
22572 * @author Nicola Asuni
22573 * @protected
22574 * @since 5.9.003 (2010-10-13)
22576 protected function checkPageRegions($h, $x, $y) {
22577 // set default values
22578 if ($x === '') {
22579 $x = $this->x;
22581 if ($y === '') {
22582 $y = $this->y;
22584 if (!$this->check_page_regions OR empty($this->page_regions)) {
22585 // no page regions defined
22586 return array($x, $y);
22588 if (empty($h)) {
22589 $h = $this->getCellHeight($this->FontSize);
22591 // check for page break
22592 if ($this->checkPageBreak($h, $y)) {
22593 // the content will be printed on a new page
22594 $x = $this->x;
22595 $y = $this->y;
22597 if ($this->num_columns > 1) {
22598 if ($this->rtl) {
22599 $this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22600 } else {
22601 $this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22603 } else {
22604 if ($this->rtl) {
22605 $this->lMargin = max($this->clMargin, $this->original_lMargin);
22606 } else {
22607 $this->rMargin = max($this->crMargin, $this->original_rMargin);
22610 // adjust coordinates and page margins
22611 foreach ($this->page_regions as $regid => $regdata) {
22612 if ($regdata['page'] == $this->page) {
22613 // check region boundaries
22614 if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22615 // Y is inside the region
22616 $minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22617 $yt = max($y, $regdata['yt']);
22618 $yb = min(($yt + $h), $regdata['yb']);
22619 $xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
22620 $xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
22621 if ($regdata['side'] == 'L') { // left side
22622 $new_margin = max($xt, $xb);
22623 if ($this->lMargin < $new_margin) {
22624 if ($this->rtl) {
22625 // adjust left page margin
22626 $this->lMargin = max(0, $new_margin);
22628 if ($x < $new_margin) {
22629 // adjust x position
22630 $x = $new_margin;
22631 if ($new_margin > ($this->w - $this->rMargin)) {
22632 // adjust y position
22633 $y = $regdata['yb'] - $h;
22637 } elseif ($regdata['side'] == 'R') { // right side
22638 $new_margin = min($xt, $xb);
22639 if (($this->w - $this->rMargin) > $new_margin) {
22640 if (!$this->rtl) {
22641 // adjust right page margin
22642 $this->rMargin = max(0, ($this->w - $new_margin));
22644 if ($x > $new_margin) {
22645 // adjust x position
22646 $x = $new_margin;
22647 if ($new_margin > $this->lMargin) {
22648 // adjust y position
22649 $y = $regdata['yb'] - $h;
22657 return array($x, $y);
22660 // --- SVG METHODS ---------------------------------------------------------
22663 * Embedd a Scalable Vector Graphics (SVG) image.
22664 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
22665 * @param $file (string) Name of the SVG file or a '@' character followed by the SVG data string.
22666 * @param $x (float) Abscissa of the upper-left corner.
22667 * @param $y (float) Ordinate of the upper-left corner.
22668 * @param $w (float) Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
22669 * @param $h (float) Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
22670 * @param $link (mixed) URL or identifier returned by AddLink().
22671 * @param $align (string) Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul> If the alignment is an empty string, then the pointer will be restored on the starting SVG position.
22672 * @param $palign (string) Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
22673 * @param $border (mixed) Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
22674 * @param $fitonpage (boolean) if true the image is resized to not exceed page dimensions.
22675 * @author Nicola Asuni
22676 * @since 5.0.000 (2010-05-02)
22677 * @public
22679 public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
22680 if ($this->state != 2) {
22681 return;
22683 // reseet SVG vars
22684 $this->svggradients = array();
22685 $this->svggradientid = 0;
22686 $this->svgdefsmode = false;
22687 $this->svgdefs = array();
22688 $this->svgclipmode = false;
22689 $this->svgclippaths = array();
22690 $this->svgcliptm = array();
22691 $this->svgclipid = 0;
22692 $this->svgtext = '';
22693 $this->svgtextmode = array();
22694 if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
22695 // convert SVG to raster image using GD or ImageMagick libraries
22696 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22698 if ($file[0] === '@') { // image from string
22699 $this->svgdir = '';
22700 $svgdata = substr($file, 1);
22701 } else { // SVG file
22702 $this->svgdir = dirname($file);
22703 $svgdata = TCPDF_STATIC::fileGetContents($file);
22705 if ($svgdata === FALSE) {
22706 $this->Error('SVG file not found: '.$file);
22708 if ($x === '') {
22709 $x = $this->x;
22711 if ($y === '') {
22712 $y = $this->y;
22714 // check page for no-write regions and adapt page margins if necessary
22715 list($x, $y) = $this->checkPageRegions($h, $x, $y);
22716 $k = $this->k;
22717 $ox = 0;
22718 $oy = 0;
22719 $ow = $w;
22720 $oh = $h;
22721 $aspect_ratio_align = 'xMidYMid';
22722 $aspect_ratio_ms = 'meet';
22723 $regs = array();
22724 // get original image width and height
22725 preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
22726 if (isset($regs[1]) AND !empty($regs[1])) {
22727 $tmp = array();
22728 if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22729 $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22731 $tmp = array();
22732 if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22733 $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22735 $tmp = array();
22736 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22737 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22739 $tmp = array();
22740 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22741 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22743 $tmp = array();
22744 $view_box = array();
22745 if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
22746 if (count($tmp) == 5) {
22747 array_shift($tmp);
22748 foreach ($tmp as $key => $val) {
22749 $view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
22751 $ox = $view_box[0];
22752 $oy = $view_box[1];
22754 // get aspect ratio
22755 $tmp = array();
22756 if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22757 $aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
22758 switch (count($aspect_ratio)) {
22759 case 3: {
22760 $aspect_ratio_align = $aspect_ratio[1];
22761 $aspect_ratio_ms = $aspect_ratio[2];
22762 break;
22764 case 2: {
22765 $aspect_ratio_align = $aspect_ratio[0];
22766 $aspect_ratio_ms = $aspect_ratio[1];
22767 break;
22769 case 1: {
22770 $aspect_ratio_align = $aspect_ratio[0];
22771 $aspect_ratio_ms = 'meet';
22772 break;
22778 if ($ow <= 0) {
22779 $ow = 1;
22781 if ($oh <= 0) {
22782 $oh = 1;
22784 // calculate image width and height on document
22785 if (($w <= 0) AND ($h <= 0)) {
22786 // convert image size to document unit
22787 $w = $ow;
22788 $h = $oh;
22789 } elseif ($w <= 0) {
22790 $w = $h * $ow / $oh;
22791 } elseif ($h <= 0) {
22792 $h = $w * $oh / $ow;
22794 // fit the image on available space
22795 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22796 if ($this->rasterize_vector_images) {
22797 // convert SVG to raster image using GD or ImageMagick libraries
22798 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22800 // set alignment
22801 $this->img_rb_y = $y + $h;
22802 // set alignment
22803 if ($this->rtl) {
22804 if ($palign == 'L') {
22805 $ximg = $this->lMargin;
22806 } elseif ($palign == 'C') {
22807 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22808 } elseif ($palign == 'R') {
22809 $ximg = $this->w - $this->rMargin - $w;
22810 } else {
22811 $ximg = $x - $w;
22813 $this->img_rb_x = $ximg;
22814 } else {
22815 if ($palign == 'L') {
22816 $ximg = $this->lMargin;
22817 } elseif ($palign == 'C') {
22818 $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
22819 } elseif ($palign == 'R') {
22820 $ximg = $this->w - $this->rMargin - $w;
22821 } else {
22822 $ximg = $x;
22824 $this->img_rb_x = $ximg + $w;
22826 // store current graphic vars
22827 $gvars = $this->getGraphicVars();
22828 // store SVG position and scale factors
22829 $svgoffset_x = ($ximg - $ox) * $this->k;
22830 $svgoffset_y = -($y - $oy) * $this->k;
22831 if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
22832 $ow = $view_box[2];
22833 $oh = $view_box[3];
22834 } else {
22835 if ($ow <= 0) {
22836 $ow = $w;
22838 if ($oh <= 0) {
22839 $oh = $h;
22842 $svgscale_x = $w / $ow;
22843 $svgscale_y = $h / $oh;
22844 // scaling and alignment
22845 if ($aspect_ratio_align != 'none') {
22846 // store current scaling values
22847 $svgscale_old_x = $svgscale_x;
22848 $svgscale_old_y = $svgscale_y;
22849 // force uniform scaling
22850 if ($aspect_ratio_ms == 'slice') {
22851 // the entire viewport is covered by the viewBox
22852 if ($svgscale_x > $svgscale_y) {
22853 $svgscale_y = $svgscale_x;
22854 } elseif ($svgscale_x < $svgscale_y) {
22855 $svgscale_x = $svgscale_y;
22857 } else { // meet
22858 // the entire viewBox is visible within the viewport
22859 if ($svgscale_x < $svgscale_y) {
22860 $svgscale_y = $svgscale_x;
22861 } elseif ($svgscale_x > $svgscale_y) {
22862 $svgscale_x = $svgscale_y;
22865 // correct X alignment
22866 switch (substr($aspect_ratio_align, 1, 3)) {
22867 case 'Min': {
22868 // do nothing
22869 break;
22871 case 'Max': {
22872 $svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
22873 break;
22875 default:
22876 case 'Mid': {
22877 $svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
22878 break;
22881 // correct Y alignment
22882 switch (substr($aspect_ratio_align, 5)) {
22883 case 'Min': {
22884 // do nothing
22885 break;
22887 case 'Max': {
22888 $svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
22889 break;
22891 default:
22892 case 'Mid': {
22893 $svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
22894 break;
22898 // store current page break mode
22899 $page_break_mode = $this->AutoPageBreak;
22900 $page_break_margin = $this->getBreakMargin();
22901 $cell_padding = $this->cell_padding;
22902 $this->SetCellPadding(0);
22903 $this->SetAutoPageBreak(false);
22904 // save the current graphic state
22905 $this->_out('q'.$this->epsmarker);
22906 // set initial clipping mask
22907 $this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
22908 // scale and translate
22909 $e = $ox * $this->k * (1 - $svgscale_x);
22910 $f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
22911 $this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e + $svgoffset_x), ($f + $svgoffset_y)));
22912 // creates a new XML parser to be used by the other XML functions
22913 $this->parser = xml_parser_create('UTF-8');
22914 // the following function allows to use parser inside object
22915 xml_set_object($this->parser, $this);
22916 // disable case-folding for this XML parser
22917 xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
22918 // sets the element handler functions for the XML parser
22919 xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
22920 // sets the character data handler function for the XML parser
22921 xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
22922 // start parsing an XML document
22923 if (!xml_parse($this->parser, $svgdata)) {
22924 $error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($this->parser)), xml_get_current_line_number($this->parser));
22925 $this->Error($error_message);
22927 // free this XML parser
22928 xml_parser_free($this->parser);
22929 // restore previous graphic state
22930 $this->_out($this->epsmarker.'Q');
22931 // restore graphic vars
22932 $this->setGraphicVars($gvars);
22933 $this->lasth = $gvars['lasth'];
22934 if (!empty($border)) {
22935 $bx = $this->x;
22936 $by = $this->y;
22937 $this->x = $ximg;
22938 if ($this->rtl) {
22939 $this->x += $w;
22941 $this->y = $y;
22942 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
22943 $this->x = $bx;
22944 $this->y = $by;
22946 if ($link) {
22947 $this->Link($ximg, $y, $w, $h, $link, 0);
22949 // set pointer to align the next text/objects
22950 switch($align) {
22951 case 'T':{
22952 $this->y = $y;
22953 $this->x = $this->img_rb_x;
22954 break;
22956 case 'M':{
22957 $this->y = $y + round($h/2);
22958 $this->x = $this->img_rb_x;
22959 break;
22961 case 'B':{
22962 $this->y = $this->img_rb_y;
22963 $this->x = $this->img_rb_x;
22964 break;
22966 case 'N':{
22967 $this->SetY($this->img_rb_y);
22968 break;
22970 default:{
22971 // restore pointer to starting position
22972 $this->x = $gvars['x'];
22973 $this->y = $gvars['y'];
22974 $this->page = $gvars['page'];
22975 $this->current_column = $gvars['current_column'];
22976 $this->tMargin = $gvars['tMargin'];
22977 $this->bMargin = $gvars['bMargin'];
22978 $this->w = $gvars['w'];
22979 $this->h = $gvars['h'];
22980 $this->wPt = $gvars['wPt'];
22981 $this->hPt = $gvars['hPt'];
22982 $this->fwPt = $gvars['fwPt'];
22983 $this->fhPt = $gvars['fhPt'];
22984 break;
22987 $this->endlinex = $this->img_rb_x;
22988 // restore page break
22989 $this->SetAutoPageBreak($page_break_mode, $page_break_margin);
22990 $this->cell_padding = $cell_padding;
22994 * Convert SVG transformation matrix to PDF.
22995 * @param $tm (array) original SVG transformation matrix
22996 * @return array transformation matrix
22997 * @protected
22998 * @since 5.0.000 (2010-05-02)
23000 protected function convertSVGtMatrix($tm) {
23001 $a = $tm[0];
23002 $b = -$tm[1];
23003 $c = -$tm[2];
23004 $d = $tm[3];
23005 $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
23006 $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
23007 $x = 0;
23008 $y = $this->h * $this->k;
23009 $e = ($x * (1 - $a)) - ($y * $c) + $e;
23010 $f = ($y * (1 - $d)) - ($x * $b) + $f;
23011 return array($a, $b, $c, $d, $e, $f);
23015 * Apply SVG graphic transformation matrix.
23016 * @param $tm (array) original SVG transformation matrix
23017 * @protected
23018 * @since 5.0.000 (2010-05-02)
23020 protected function SVGTransform($tm) {
23021 $this->Transform($this->convertSVGtMatrix($tm));
23025 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23026 * @param $svgstyle (array) array of SVG styles to apply
23027 * @param $prevsvgstyle (array) array of previous SVG style
23028 * @param $x (int) X origin of the bounding box
23029 * @param $y (int) Y origin of the bounding box
23030 * @param $w (int) width of the bounding box
23031 * @param $h (int) height of the bounding box
23032 * @param $clip_function (string) clip function
23033 * @param $clip_params (array) array of parameters for clipping function
23034 * @return object style
23035 * @author Nicola Asuni
23036 * @since 5.0.000 (2010-05-02)
23037 * @protected
23039 protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23040 if ($this->state != 2) {
23041 return;
23043 $objstyle = '';
23044 $minlen = (0.01 / $this->k); // minimum acceptable length
23045 if (!isset($svgstyle['opacity'])) {
23046 return $objstyle;
23048 // clip-path
23049 $regs = array();
23050 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23051 $clip_path = $this->svgclippaths[$regs[1]];
23052 foreach ($clip_path as $cp) {
23053 $this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23056 // opacity
23057 if ($svgstyle['opacity'] != 1) {
23058 $this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23060 // color
23061 $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23062 $this->SetFillColorArray($fill_color);
23063 // text color
23064 $text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23065 $this->SetTextColorArray($text_color);
23066 // clip
23067 if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
23068 $top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
23069 $right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
23070 $bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
23071 $left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
23072 $cx = $x + $left;
23073 $cy = $y + $top;
23074 $cw = $w - $left - $right;
23075 $ch = $h - $top - $bottom;
23076 if ($svgstyle['clip-rule'] == 'evenodd') {
23077 $clip_rule = 'CNZ';
23078 } else {
23079 $clip_rule = 'CEO';
23081 $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23083 // fill
23084 $regs = array();
23085 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23086 // gradient
23087 $gradient = $this->svggradients[$regs[1]];
23088 if (isset($gradient['xref'])) {
23089 // reference to another gradient definition
23090 $newgradient = $this->svggradients[$gradient['xref']];
23091 $newgradient['coords'] = $gradient['coords'];
23092 $newgradient['mode'] = $gradient['mode'];
23093 $newgradient['type'] = $gradient['type'];
23094 $newgradient['gradientUnits'] = $gradient['gradientUnits'];
23095 if (isset($gradient['gradientTransform'])) {
23096 $newgradient['gradientTransform'] = $gradient['gradientTransform'];
23098 $gradient = $newgradient;
23100 //save current Graphic State
23101 $this->_outSaveGraphicsState();
23102 //set clipping area
23103 if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23104 $bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23105 if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23106 list($x, $y, $w, $h) = $bbox;
23109 if ($gradient['mode'] == 'measure') {
23110 if (!isset($gradient['coords'][4])) {
23111 $gradient['coords'][4] = 0.5;
23113 if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23114 $gtm = $gradient['gradientTransform'];
23115 // apply transformation matrix
23116 $xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
23117 $ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
23118 $xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
23119 $yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
23120 $r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
23121 $gradient['coords'][0] = $xa;
23122 $gradient['coords'][1] = $ya;
23123 $gradient['coords'][2] = $xb;
23124 $gradient['coords'][3] = $yb;
23125 $gradient['coords'][4] = $r;
23127 // convert SVG coordinates to user units
23128 $gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
23129 $gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
23130 $gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
23131 $gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
23132 $gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
23133 if ($w <= $minlen) {
23134 $w = $minlen;
23136 if ($h <= $minlen) {
23137 $h = $minlen;
23139 // shift units
23140 if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23141 // convert to SVG coordinate system
23142 $gradient['coords'][0] += $x;
23143 $gradient['coords'][1] += $y;
23144 $gradient['coords'][2] += $x;
23145 $gradient['coords'][3] += $y;
23147 // calculate percentages
23148 $gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23149 $gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23150 $gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23151 $gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23152 $gradient['coords'][4] /= $w;
23153 } elseif ($gradient['mode'] == 'percentage') {
23154 foreach($gradient['coords'] as $key => $val) {
23155 $gradient['coords'][$key] = (intval($val) / 100);
23156 if ($val < 0) {
23157 $gradient['coords'][$key] = 0;
23158 } elseif ($val > 1) {
23159 $gradient['coords'][$key] = 1;
23163 if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23164 // single color (no shading)
23165 $gradient['coords'][0] = 1;
23166 $gradient['coords'][1] = 0;
23167 $gradient['coords'][2] = 0.999;
23168 $gradient['coords'][3] = 0;
23170 // swap Y coordinates
23171 $tmp = $gradient['coords'][1];
23172 $gradient['coords'][1] = $gradient['coords'][3];
23173 $gradient['coords'][3] = $tmp;
23174 // set transformation map for gradient
23175 $cy = ($this->h - $y);
23176 if ($gradient['type'] == 3) {
23177 // circular gradient
23178 $cy -= ($gradient['coords'][1] * ($w + $h));
23179 } else {
23180 $cy -= $h;
23182 $this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k), ($h * $this->k), ($x * $this->k), ($cy * $this->k)));
23183 if (count($gradient['stops']) > 1) {
23184 $this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
23186 } elseif ($svgstyle['fill'] != 'none') {
23187 $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors);
23188 if ($svgstyle['fill-opacity'] != 1) {
23189 $this->setAlpha($this->alpha['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23191 $this->SetFillColorArray($fill_color);
23192 if ($svgstyle['fill-rule'] == 'evenodd') {
23193 $objstyle .= 'F*';
23194 } else {
23195 $objstyle .= 'F';
23198 // stroke
23199 if ($svgstyle['stroke'] != 'none') {
23200 if ($svgstyle['stroke-opacity'] != 1) {
23201 $this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha['ca'], false);
23203 $stroke_style = array(
23204 'color' => TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors),
23205 'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
23206 'cap' => $svgstyle['stroke-linecap'],
23207 'join' => $svgstyle['stroke-linejoin']
23209 if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23210 $stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23212 $this->SetLineStyle($stroke_style);
23213 $objstyle .= 'D';
23215 // font
23216 $regs = array();
23217 if (!empty($svgstyle['font'])) {
23218 if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23219 $font_family = $this->getFontFamilyName($regs[1]);
23220 } else {
23221 $font_family = $svgstyle['font-family'];
23223 if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23224 $font_size = trim($regs[1]);
23225 } else {
23226 $font_size = $svgstyle['font-size'];
23228 if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23229 $font_style = trim($regs[1]);
23230 } else {
23231 $font_style = $svgstyle['font-style'];
23233 if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23234 $font_weight = trim($regs[1]);
23235 } else {
23236 $font_weight = $svgstyle['font-weight'];
23238 if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23239 $font_stretch = trim($regs[1]);
23240 } else {
23241 $font_stretch = $svgstyle['font-stretch'];
23243 if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23244 $font_spacing = trim($regs[1]);
23245 } else {
23246 $font_spacing = $svgstyle['letter-spacing'];
23248 } else {
23249 $font_family = $this->getFontFamilyName($svgstyle['font-family']);
23250 $font_size = $svgstyle['font-size'];
23251 $font_style = $svgstyle['font-style'];
23252 $font_weight = $svgstyle['font-weight'];
23253 $font_stretch = $svgstyle['font-stretch'];
23254 $font_spacing = $svgstyle['letter-spacing'];
23256 $font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit);
23257 $font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23258 $font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23259 switch ($font_style) {
23260 case 'italic': {
23261 $font_style = 'I';
23262 break;
23264 case 'oblique': {
23265 $font_style = 'I';
23266 break;
23268 default:
23269 case 'normal': {
23270 $font_style = '';
23271 break;
23274 switch ($font_weight) {
23275 case 'bold':
23276 case 'bolder': {
23277 $font_style .= 'B';
23278 break;
23281 switch ($svgstyle['text-decoration']) {
23282 case 'underline': {
23283 $font_style .= 'U';
23284 break;
23286 case 'overline': {
23287 $font_style .= 'O';
23288 break;
23290 case 'line-through': {
23291 $font_style .= 'D';
23292 break;
23294 default:
23295 case 'none': {
23296 break;
23299 $this->SetFont($font_family, $font_style, $font_size);
23300 $this->setFontStretching($font_stretch);
23301 $this->setFontSpacing($font_spacing);
23302 return $objstyle;
23306 * Draws an SVG path
23307 * @param $d (string) attribute d of the path SVG element
23308 * @param $style (string) Style of rendering. Possible values are:
23309 * <ul>
23310 * <li>D or empty string: Draw (default).</li>
23311 * <li>F: Fill.</li>
23312 * <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23313 * <li>DF or FD: Draw and fill.</li>
23314 * <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23315 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23316 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23317 * </ul>
23318 * @return array of container box measures (x, y, w, h)
23319 * @author Nicola Asuni
23320 * @since 5.0.000 (2010-05-02)
23321 * @protected
23323 protected function SVGPath($d, $style='') {
23324 if ($this->state != 2) {
23325 return;
23327 // set fill/stroke style
23328 $op = TCPDF_STATIC::getPathPaintOperator($style, '');
23329 if (empty($op)) {
23330 return;
23332 $paths = array();
23333 $d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23334 preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER);
23335 $x = 0;
23336 $y = 0;
23337 $x1 = 0;
23338 $y1 = 0;
23339 $x2 = 0;
23340 $y2 = 0;
23341 $xmin = 2147483647;
23342 $xmax = 0;
23343 $ymin = 2147483647;
23344 $ymax = 0;
23345 $relcoord = false;
23346 $minlen = (0.01 / $this->k); // minimum acceptable length (3 point)
23347 $firstcmd = true; // used to print first point
23348 // draw curve pieces
23349 foreach ($paths as $key => $val) {
23350 // get curve type
23351 $cmd = trim($val[1]);
23352 if (strtolower($cmd) == $cmd) {
23353 // use relative coordinated instead of absolute
23354 $relcoord = true;
23355 $xoffset = $x;
23356 $yoffset = $y;
23357 } else {
23358 $relcoord = false;
23359 $xoffset = 0;
23360 $yoffset = 0;
23362 $params = array();
23363 if (isset($val[2])) {
23364 // get curve parameters
23365 $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
23366 $params = array();
23367 foreach ($rawparams as $ck => $cp) {
23368 $params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
23369 if (abs($params[$ck]) < $minlen) {
23370 // aproximate little values to zero
23371 $params[$ck] = 0;
23375 // store current origin point
23376 $x0 = $x;
23377 $y0 = $y;
23378 switch (strtoupper($cmd)) {
23379 case 'M': { // moveto
23380 foreach ($params as $ck => $cp) {
23381 if (($ck % 2) == 0) {
23382 $x = $cp + $xoffset;
23383 } else {
23384 $y = $cp + $yoffset;
23385 if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23386 if ($ck == 1) {
23387 $this->_outPoint($x, $y);
23388 $firstcmd = false;
23389 } else {
23390 $this->_outLine($x, $y);
23392 $x0 = $x;
23393 $y0 = $y;
23395 $xmin = min($xmin, $x);
23396 $ymin = min($ymin, $y);
23397 $xmax = max($xmax, $x);
23398 $ymax = max($ymax, $y);
23399 if ($relcoord) {
23400 $xoffset = $x;
23401 $yoffset = $y;
23405 break;
23407 case 'L': { // lineto
23408 foreach ($params as $ck => $cp) {
23409 if (($ck % 2) == 0) {
23410 $x = $cp + $xoffset;
23411 } else {
23412 $y = $cp + $yoffset;
23413 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23414 $this->_outLine($x, $y);
23415 $x0 = $x;
23416 $y0 = $y;
23418 $xmin = min($xmin, $x);
23419 $ymin = min($ymin, $y);
23420 $xmax = max($xmax, $x);
23421 $ymax = max($ymax, $y);
23422 if ($relcoord) {
23423 $xoffset = $x;
23424 $yoffset = $y;
23428 break;
23430 case 'H': { // horizontal lineto
23431 foreach ($params as $ck => $cp) {
23432 $x = $cp + $xoffset;
23433 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23434 $this->_outLine($x, $y);
23435 $x0 = $x;
23436 $y0 = $y;
23438 $xmin = min($xmin, $x);
23439 $xmax = max($xmax, $x);
23440 if ($relcoord) {
23441 $xoffset = $x;
23444 break;
23446 case 'V': { // vertical lineto
23447 foreach ($params as $ck => $cp) {
23448 $y = $cp + $yoffset;
23449 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23450 $this->_outLine($x, $y);
23451 $x0 = $x;
23452 $y0 = $y;
23454 $ymin = min($ymin, $y);
23455 $ymax = max($ymax, $y);
23456 if ($relcoord) {
23457 $yoffset = $y;
23460 break;
23462 case 'C': { // curveto
23463 foreach ($params as $ck => $cp) {
23464 $params[$ck] = $cp;
23465 if ((($ck + 1) % 6) == 0) {
23466 $x1 = $params[($ck - 5)] + $xoffset;
23467 $y1 = $params[($ck - 4)] + $yoffset;
23468 $x2 = $params[($ck - 3)] + $xoffset;
23469 $y2 = $params[($ck - 2)] + $yoffset;
23470 $x = $params[($ck - 1)] + $xoffset;
23471 $y = $params[($ck)] + $yoffset;
23472 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23473 $xmin = min($xmin, $x, $x1, $x2);
23474 $ymin = min($ymin, $y, $y1, $y2);
23475 $xmax = max($xmax, $x, $x1, $x2);
23476 $ymax = max($ymax, $y, $y1, $y2);
23477 if ($relcoord) {
23478 $xoffset = $x;
23479 $yoffset = $y;
23483 break;
23485 case 'S': { // shorthand/smooth curveto
23486 foreach ($params as $ck => $cp) {
23487 $params[$ck] = $cp;
23488 if ((($ck + 1) % 4) == 0) {
23489 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23490 $x1 = (2 * $x) - $x2;
23491 $y1 = (2 * $y) - $y2;
23492 } else {
23493 $x1 = $x;
23494 $y1 = $y;
23496 $x2 = $params[($ck - 3)] + $xoffset;
23497 $y2 = $params[($ck - 2)] + $yoffset;
23498 $x = $params[($ck - 1)] + $xoffset;
23499 $y = $params[($ck)] + $yoffset;
23500 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23501 $xmin = min($xmin, $x, $x1, $x2);
23502 $ymin = min($ymin, $y, $y1, $y2);
23503 $xmax = max($xmax, $x, $x1, $x2);
23504 $ymax = max($ymax, $y, $y1, $y2);
23505 if ($relcoord) {
23506 $xoffset = $x;
23507 $yoffset = $y;
23511 break;
23513 case 'Q': { // quadratic Bezier curveto
23514 foreach ($params as $ck => $cp) {
23515 $params[$ck] = $cp;
23516 if ((($ck + 1) % 4) == 0) {
23517 // convert quadratic points to cubic points
23518 $x1 = $params[($ck - 3)] + $xoffset;
23519 $y1 = $params[($ck - 2)] + $yoffset;
23520 $xa = ($x + (2 * $x1)) / 3;
23521 $ya = ($y + (2 * $y1)) / 3;
23522 $x = $params[($ck - 1)] + $xoffset;
23523 $y = $params[($ck)] + $yoffset;
23524 $xb = ($x + (2 * $x1)) / 3;
23525 $yb = ($y + (2 * $y1)) / 3;
23526 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23527 $xmin = min($xmin, $x, $xa, $xb);
23528 $ymin = min($ymin, $y, $ya, $yb);
23529 $xmax = max($xmax, $x, $xa, $xb);
23530 $ymax = max($ymax, $y, $ya, $yb);
23531 if ($relcoord) {
23532 $xoffset = $x;
23533 $yoffset = $y;
23537 break;
23539 case 'T': { // shorthand/smooth quadratic Bezier curveto
23540 foreach ($params as $ck => $cp) {
23541 $params[$ck] = $cp;
23542 if (($ck % 2) != 0) {
23543 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23544 $x1 = (2 * $x) - $x1;
23545 $y1 = (2 * $y) - $y1;
23546 } else {
23547 $x1 = $x;
23548 $y1 = $y;
23550 // convert quadratic points to cubic points
23551 $xa = ($x + (2 * $x1)) / 3;
23552 $ya = ($y + (2 * $y1)) / 3;
23553 $x = $params[($ck - 1)] + $xoffset;
23554 $y = $params[($ck)] + $yoffset;
23555 $xb = ($x + (2 * $x1)) / 3;
23556 $yb = ($y + (2 * $y1)) / 3;
23557 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23558 $xmin = min($xmin, $x, $xa, $xb);
23559 $ymin = min($ymin, $y, $ya, $yb);
23560 $xmax = max($xmax, $x, $xa, $xb);
23561 $ymax = max($ymax, $y, $ya, $yb);
23562 if ($relcoord) {
23563 $xoffset = $x;
23564 $yoffset = $y;
23568 break;
23570 case 'A': { // elliptical arc
23571 foreach ($params as $ck => $cp) {
23572 $params[$ck] = $cp;
23573 if ((($ck + 1) % 7) == 0) {
23574 $x0 = $x;
23575 $y0 = $y;
23576 $rx = abs($params[($ck - 6)]);
23577 $ry = abs($params[($ck - 5)]);
23578 $ang = -$rawparams[($ck - 4)];
23579 $angle = deg2rad($ang);
23580 $fa = $rawparams[($ck - 3)]; // large-arc-flag
23581 $fs = $rawparams[($ck - 2)]; // sweep-flag
23582 $x = $params[($ck - 1)] + $xoffset;
23583 $y = $params[$ck] + $yoffset;
23584 if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23585 // endpoints are almost identical
23586 $xmin = min($xmin, $x);
23587 $ymin = min($ymin, $y);
23588 $xmax = max($xmax, $x);
23589 $ymax = max($ymax, $y);
23590 } else {
23591 $cos_ang = cos($angle);
23592 $sin_ang = sin($angle);
23593 $a = (($x0 - $x) / 2);
23594 $b = (($y0 - $y) / 2);
23595 $xa = ($a * $cos_ang) - ($b * $sin_ang);
23596 $ya = ($a * $sin_ang) + ($b * $cos_ang);
23597 $rx2 = $rx * $rx;
23598 $ry2 = $ry * $ry;
23599 $xa2 = $xa * $xa;
23600 $ya2 = $ya * $ya;
23601 $delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23602 if ($delta > 1) {
23603 $rx *= sqrt($delta);
23604 $ry *= sqrt($delta);
23605 $rx2 = $rx * $rx;
23606 $ry2 = $ry * $ry;
23608 $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23609 if ($numerator < 0) {
23610 $root = 0;
23611 } else {
23612 $root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23614 if ($fa == $fs){
23615 $root *= -1;
23617 $cax = $root * (($rx * $ya) / $ry);
23618 $cay = -$root * (($ry * $xa) / $rx);
23619 // coordinates of ellipse center
23620 $cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
23621 $cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
23622 // get angles
23623 $angs = TCPDF_STATIC::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23624 $dang = TCPDF_STATIC::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23625 if (($fs == 0) AND ($dang > 0)) {
23626 $dang -= (2 * M_PI);
23627 } elseif (($fs == 1) AND ($dang < 0)) {
23628 $dang += (2 * M_PI);
23630 $angf = $angs - $dang;
23631 if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
23632 // reverse angles
23633 $tmp = $angs;
23634 $angs = $angf;
23635 $angf = $tmp;
23637 $angs = round(rad2deg($angs), 6);
23638 $angf = round(rad2deg($angf), 6);
23639 // covent angles to positive values
23640 if (($angs < 0) AND ($angf < 0)) {
23641 $angs += 360;
23642 $angf += 360;
23644 $pie = false;
23645 if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
23646 $pie = true;
23648 list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
23649 $xmin = min($xmin, $x, $axmin);
23650 $ymin = min($ymin, $y, $aymin);
23651 $xmax = max($xmax, $x, $axmax);
23652 $ymax = max($ymax, $y, $aymax);
23654 if ($relcoord) {
23655 $xoffset = $x;
23656 $yoffset = $y;
23660 break;
23662 case 'Z': {
23663 $this->_out('h');
23664 break;
23667 $firstcmd = false;
23668 } // end foreach
23669 if (!empty($op)) {
23670 $this->_out($op);
23672 return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
23676 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
23677 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
23678 * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
23679 * @param $attribs (array) The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on.
23680 * @param $ctm (array) tranformation matrix for clipping mode (starting transformation matrix).
23681 * @author Nicola Asuni
23682 * @since 5.0.000 (2010-05-02)
23683 * @protected
23685 protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
23686 // check if we are in clip mode
23687 if ($this->svgclipmode) {
23688 $this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
23689 return;
23691 if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
23692 if (isset($attribs['id'])) {
23693 $attribs['child_elements'] = array();
23694 $this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23695 return;
23697 if (end($this->svgdefs) !== FALSE) {
23698 $last_svgdefs_id = key($this->svgdefs);
23699 if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
23700 $attribs['id'] = 'DF_'.(count($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements']) + 1);
23701 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23702 return;
23705 return;
23707 $clipping = false;
23708 if ($parser == 'clip-path') {
23709 // set clipping mode
23710 $clipping = true;
23712 // get styling properties
23713 $prev_svgstyle = $this->svgstyles[max(0,(count($this->svgstyles) - 1))]; // previous style
23714 $svgstyle = $this->svgstyles[0]; // set default style
23715 if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
23716 // default fill attribute for clipping
23717 $attribs['fill'] = 'none';
23719 if (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
23720 // fix style for regular expression
23721 $attribs['style'] = ';'.$attribs['style'];
23723 foreach ($prev_svgstyle as $key => $val) {
23724 if (in_array($key, TCPDF_IMAGES::$svginheritprop)) {
23725 // inherit previous value
23726 $svgstyle[$key] = $val;
23728 if (isset($attribs[$key]) AND !TCPDF_STATIC::empty_string($attribs[$key])) {
23729 // specific attribute settings
23730 if ($attribs[$key] == 'inherit') {
23731 $svgstyle[$key] = $val;
23732 } else {
23733 $svgstyle[$key] = $attribs[$key];
23735 } elseif (isset($attribs['style']) AND !TCPDF_STATIC::empty_string($attribs['style'])) {
23736 // CSS style syntax
23737 $attrval = array();
23738 if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
23739 if ($attrval[1] == 'inherit') {
23740 $svgstyle[$key] = $val;
23741 } else {
23742 $svgstyle[$key] = $attrval[1];
23747 // transformation matrix
23748 if (!empty($ctm)) {
23749 $tm = $ctm;
23750 } else {
23751 $tm = array(1,0,0,1,0,0);
23753 if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
23754 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, TCPDF_STATIC::getSVGTransformMatrix($attribs['transform']));
23756 $svgstyle['transfmatrix'] = $tm;
23757 $invisible = false;
23758 if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
23759 // the current graphics element is invisible (nothing is painted)
23760 $invisible = true;
23762 // process tag
23763 switch($name) {
23764 case 'defs': {
23765 $this->svgdefsmode = true;
23766 break;
23768 // clipPath
23769 case 'clipPath': {
23770 if ($invisible) {
23771 break;
23773 $this->svgclipmode = true;
23774 if (!isset($attribs['id'])) {
23775 $attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
23777 $this->svgclipid = $attribs['id'];
23778 $this->svgclippaths[$this->svgclipid] = array();
23779 $this->svgcliptm[$this->svgclipid] = $tm;
23780 break;
23782 case 'svg': {
23783 // start of SVG object
23784 break;
23786 case 'g': {
23787 // group together related graphics elements
23788 array_push($this->svgstyles, $svgstyle);
23789 $this->StartTransform();
23790 $x = (isset($attribs['x'])?$attribs['x']:0);
23791 $y = (isset($attribs['y'])?$attribs['y']:0);
23792 $w = 1;//(isset($attribs['width'])?$attribs['width']:1);
23793 $h = 1;//(isset($attribs['height'])?$attribs['height']:1);
23794 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
23795 $this->SVGTransform($tm);
23796 $this->setSVGStyles($svgstyle, $prev_svgstyle);
23797 break;
23799 case 'linearGradient': {
23800 if ($this->pdfa_mode) {
23801 break;
23803 if (!isset($attribs['id'])) {
23804 $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
23806 $this->svggradientid = $attribs['id'];
23807 $this->svggradients[$this->svggradientid] = array();
23808 $this->svggradients[$this->svggradientid]['type'] = 2;
23809 $this->svggradients[$this->svggradientid]['stops'] = array();
23810 if (isset($attribs['gradientUnits'])) {
23811 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
23812 } else {
23813 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
23815 //$attribs['spreadMethod']
23816 if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
23817 OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
23818 OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
23819 OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
23820 OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
23821 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
23822 } else {
23823 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
23825 $x1 = (isset($attribs['x1'])?$attribs['x1']:'0');
23826 $y1 = (isset($attribs['y1'])?$attribs['y1']:'0');
23827 $x2 = (isset($attribs['x2'])?$attribs['x2']:'100');
23828 $y2 = (isset($attribs['y2'])?$attribs['y2']:'0');
23829 if (isset($attribs['gradientTransform'])) {
23830 $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
23832 $this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
23833 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
23834 // gradient is defined on another place
23835 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
23837 break;
23839 case 'radialGradient': {
23840 if ($this->pdfa_mode) {
23841 break;
23843 if (!isset($attribs['id'])) {
23844 $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
23846 $this->svggradientid = $attribs['id'];
23847 $this->svggradients[$this->svggradientid] = array();
23848 $this->svggradients[$this->svggradientid]['type'] = 3;
23849 $this->svggradients[$this->svggradientid]['stops'] = array();
23850 if (isset($attribs['gradientUnits'])) {
23851 $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
23852 } else {
23853 $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
23855 //$attribs['spreadMethod']
23856 if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
23857 OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
23858 OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')) )) {
23859 $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
23860 } else {
23861 $this->svggradients[$this->svggradientid]['mode'] = 'measure';
23863 $cx = (isset($attribs['cx']) ? $attribs['cx'] : 0.5);
23864 $cy = (isset($attribs['cy']) ? $attribs['cy'] : 0.5);
23865 $fx = (isset($attribs['fx']) ? $attribs['fx'] : $cx);
23866 $fy = (isset($attribs['fy']) ? $attribs['fy'] : $cy);
23867 $r = (isset($attribs['r']) ? $attribs['r'] : 0.5);
23868 if (isset($attribs['gradientTransform'])) {
23869 $this->svggradients[$this->svggradientid]['gradientTransform'] = TCPDF_STATIC::getSVGTransformMatrix($attribs['gradientTransform']);
23871 $this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
23872 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
23873 // gradient is defined on another place
23874 $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
23876 break;
23878 case 'stop': {
23879 // gradient stops
23880 if (substr($attribs['offset'], -1) == '%') {
23881 $offset = floatval(substr($attribs['offset'], -1)) / 100;
23882 } else {
23883 $offset = floatval($attribs['offset']);
23884 if ($offset > 1) {
23885 $offset /= 100;
23888 $stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors):'black';
23889 $opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
23890 $this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
23891 break;
23893 // paths
23894 case 'path': {
23895 if ($invisible) {
23896 break;
23898 if (isset($attribs['d'])) {
23899 $d = trim($attribs['d']);
23900 if (!empty($d)) {
23901 $x = (isset($attribs['x'])?$attribs['x']:0);
23902 $y = (isset($attribs['y'])?$attribs['y']:0);
23903 $w = (isset($attribs['width'])?$attribs['width']:1);
23904 $h = (isset($attribs['height'])?$attribs['height']:1);
23905 $tm = TCPDF_STATIC::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
23906 if ($clipping) {
23907 $this->SVGTransform($tm);
23908 $this->SVGPath($d, 'CNZ');
23909 } else {
23910 $this->StartTransform();
23911 $this->SVGTransform($tm);
23912 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
23913 if (!empty($obstyle)) {
23914 $this->SVGPath($d, $obstyle);
23916 $this->StopTransform();
23920 break;
23922 // shapes
23923 case 'rect': {
23924 if ($invisible) {
23925 break;
23927 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
23928 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
23929 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
23930 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
23931 $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
23932 $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
23933 if ($clipping) {
23934 $this->SVGTransform($tm);
23935 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
23936 } else {
23937 $this->StartTransform();
23938 $this->SVGTransform($tm);
23939 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
23940 if (!empty($obstyle)) {
23941 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
23943 $this->StopTransform();
23945 break;
23947 case 'circle': {
23948 if ($invisible) {
23949 break;
23951 $r = (isset($attribs['r']) ? $this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false) : 0);
23952 $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
23953 $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
23954 $x = ($cx - $r);
23955 $y = ($cy - $r);
23956 $w = (2 * $r);
23957 $h = $w;
23958 if ($clipping) {
23959 $this->SVGTransform($tm);
23960 $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
23961 } else {
23962 $this->StartTransform();
23963 $this->SVGTransform($tm);
23964 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
23965 if (!empty($obstyle)) {
23966 $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
23968 $this->StopTransform();
23970 break;
23972 case 'ellipse': {
23973 if ($invisible) {
23974 break;
23976 $rx = (isset($attribs['rx']) ? $this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false) : 0);
23977 $ry = (isset($attribs['ry']) ? $this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false) : 0);
23978 $cx = (isset($attribs['cx']) ? $this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false) : (isset($attribs['x']) ? $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false) : 0));
23979 $cy = (isset($attribs['cy']) ? $this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false) : (isset($attribs['y']) ? $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false) : 0));
23980 $x = ($cx - $rx);
23981 $y = ($cy - $ry);
23982 $w = (2 * $rx);
23983 $h = (2 * $ry);
23984 if ($clipping) {
23985 $this->SVGTransform($tm);
23986 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
23987 } else {
23988 $this->StartTransform();
23989 $this->SVGTransform($tm);
23990 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
23991 if (!empty($obstyle)) {
23992 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
23994 $this->StopTransform();
23996 break;
23998 case 'line': {
23999 if ($invisible) {
24000 break;
24002 $x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
24003 $y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
24004 $x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
24005 $y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
24006 $x = $x1;
24007 $y = $y1;
24008 $w = abs($x2 - $x1);
24009 $h = abs($y2 - $y1);
24010 if (!$clipping) {
24011 $this->StartTransform();
24012 $this->SVGTransform($tm);
24013 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24014 $this->Line($x1, $y1, $x2, $y2);
24015 $this->StopTransform();
24017 break;
24019 case 'polyline':
24020 case 'polygon': {
24021 if ($invisible) {
24022 break;
24024 $points = (isset($attribs['points'])?$attribs['points']:'0 0');
24025 $points = trim($points);
24026 // note that point may use a complex syntax not covered here
24027 $points = preg_split('/[\,\s]+/si', $points);
24028 if (count($points) < 4) {
24029 break;
24031 $p = array();
24032 $xmin = 2147483647;
24033 $xmax = 0;
24034 $ymin = 2147483647;
24035 $ymax = 0;
24036 foreach ($points as $key => $val) {
24037 $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24038 if (($key % 2) == 0) {
24039 // X coordinate
24040 $xmin = min($xmin, $p[$key]);
24041 $xmax = max($xmax, $p[$key]);
24042 } else {
24043 // Y coordinate
24044 $ymin = min($ymin, $p[$key]);
24045 $ymax = max($ymax, $p[$key]);
24048 $x = $xmin;
24049 $y = $ymin;
24050 $w = ($xmax - $xmin);
24051 $h = ($ymax - $ymin);
24052 if ($name == 'polyline') {
24053 $this->StartTransform();
24054 $this->SVGTransform($tm);
24055 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24056 if (!empty($obstyle)) {
24057 $this->PolyLine($p, $obstyle, array(), array());
24059 $this->StopTransform();
24060 } else { // polygon
24061 if ($clipping) {
24062 $this->SVGTransform($tm);
24063 $this->Polygon($p, 'CNZ', array(), array(), true);
24064 } else {
24065 $this->StartTransform();
24066 $this->SVGTransform($tm);
24067 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24068 if (!empty($obstyle)) {
24069 $this->Polygon($p, $obstyle, array(), array(), true);
24071 $this->StopTransform();
24074 break;
24076 // image
24077 case 'image': {
24078 if ($invisible) {
24079 break;
24081 if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24082 break;
24084 $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
24085 $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
24086 $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
24087 $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
24088 $img = $attribs['xlink:href'];
24089 if (!$clipping) {
24090 $this->StartTransform();
24091 $this->SVGTransform($tm);
24092 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24093 if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24094 // embedded image encoded as base64
24095 $img = '@'.base64_decode(substr($img, strlen($m[0])));
24096 } else {
24097 // fix image path
24098 if (!TCPDF_STATIC::empty_string($this->svgdir) AND (($img[0] == '.') OR (basename($img) == $img))) {
24099 // replace relative path with full server path
24100 $img = $this->svgdir.'/'.$img;
24102 if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24103 $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24104 if (($findroot === false) OR ($findroot > 1)) {
24105 if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24106 $img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24107 } else {
24108 $img = $_SERVER['DOCUMENT_ROOT'].$img;
24112 $img = urldecode($img);
24113 $testscrtype = @parse_url($img);
24114 if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
24115 // convert URL to server path
24116 $img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
24119 // get image type
24120 $imgtype = TCPDF_IMAGES::getImageFileType($img);
24121 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24122 $this->ImageEps($img, $x, $y, $w, $h);
24123 } elseif ($imgtype == 'svg') {
24124 $this->ImageSVG($img, $x, $y, $w, $h);
24125 } else {
24126 $this->Image($img, $x, $y, $w, $h);
24128 $this->StopTransform();
24130 break;
24132 // text
24133 case 'text':
24134 case 'tspan': {
24135 // only basic support - advanced features must be implemented
24136 $this->svgtextmode['invisible'] = $invisible;
24137 if ($invisible) {
24138 break;
24140 array_push($this->svgstyles, $svgstyle);
24141 if (isset($attribs['x'])) {
24142 $x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false);
24143 } elseif ($name == 'tspan') {
24144 $x = $this->x;
24145 } else {
24146 $x = 0;
24148 if (isset($attribs['dx'])) {
24149 $x += $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit, false);
24151 if (isset($attribs['y'])) {
24152 $y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false);
24153 } elseif ($name == 'tspan') {
24154 $y = $this->y;
24155 } else {
24156 $y = 0;
24158 if (isset($attribs['dy'])) {
24159 $y += $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit, false);
24161 $svgstyle['text-color'] = $svgstyle['fill'];
24162 $this->svgtext = '';
24163 if (isset($svgstyle['text-anchor'])) {
24164 $this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
24165 } else {
24166 $this->svgtextmode['text-anchor'] = 'start';
24168 if (isset($svgstyle['direction'])) {
24169 if ($svgstyle['direction'] == 'rtl') {
24170 $this->svgtextmode['rtl'] = true;
24171 } else {
24172 $this->svgtextmode['rtl'] = false;
24174 } else {
24175 $this->svgtextmode['rtl'] = false;
24177 if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24178 $this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
24179 } else {
24180 $this->svgtextmode['stroke'] = false;
24182 $this->StartTransform();
24183 $this->SVGTransform($tm);
24184 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24185 $this->x = $x;
24186 $this->y = $y;
24187 break;
24189 // use
24190 case 'use': {
24191 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24192 $svgdefid = substr($attribs['xlink:href'], 1);
24193 if (isset($this->svgdefs[$svgdefid])) {
24194 $use = $this->svgdefs[$svgdefid];
24195 if (isset($attribs['xlink:href'])) {
24196 unset($attribs['xlink:href']);
24198 if (isset($attribs['id'])) {
24199 unset($attribs['id']);
24201 if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24202 $attribs['x'] += $use['attribs']['x'];
24204 if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24205 $attribs['y'] += $use['attribs']['y'];
24207 if (empty($attribs['style'])) {
24208 $attribs['style'] = '';
24210 if (!empty($use['attribs']['style'])) {
24211 // merge styles
24212 $attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24214 $attribs = array_merge($use['attribs'], $attribs);
24215 $this->startSVGElementHandler($parser, $use['name'], $attribs);
24216 return;
24219 break;
24221 default: {
24222 break;
24224 } // end of switch
24225 // process child elements
24226 if (!empty($attribs['child_elements'])) {
24227 $child_elements = $attribs['child_elements'];
24228 unset($attribs['child_elements']);
24229 foreach($child_elements as $child_element) {
24230 if (empty($child_element['attribs']['closing_tag'])) {
24231 $this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24232 } else {
24233 if (isset($child_element['attribs']['content'])) {
24234 $this->svgtext = $child_element['attribs']['content'];
24236 $this->endSVGElementHandler('child-tag', $child_element['name']);
24243 * Sets the closing SVG element handler function for the XML parser.
24244 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24245 * @param $name (string) The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
24246 * @author Nicola Asuni
24247 * @since 5.0.000 (2010-05-02)
24248 * @protected
24250 protected function endSVGElementHandler($parser, $name) {
24251 if ($this->svgdefsmode AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
24252 if (end($this->svgdefs) !== FALSE) {
24253 $last_svgdefs_id = key($this->svgdefs);
24254 if (isset($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'])) {
24255 foreach($this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24256 if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24257 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24258 return;
24261 if ($this->svgdefs[$last_svgdefs_id]['name'] == $name) {
24262 $this->svgdefs[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext));
24263 return;
24267 return;
24269 switch($name) {
24270 case 'defs': {
24271 $this->svgdefsmode = false;
24272 break;
24274 // clipPath
24275 case 'clipPath': {
24276 $this->svgclipmode = false;
24277 break;
24279 case 'g': {
24280 // ungroup: remove last style from array
24281 array_pop($this->svgstyles);
24282 $this->StopTransform();
24283 break;
24285 case 'text':
24286 case 'tspan': {
24287 if ($this->svgtextmode['invisible']) {
24288 // This implementation must be fixed to following the rule:
24289 // If the 'visibility' property is set to hidden on a 'tspan', 'tref' or 'altGlyph' element, then the text is invisible but still takes up space in text layout calculations.
24290 break;
24292 // print text
24293 $text = $this->svgtext;
24294 //$text = $this->stringTrim($text);
24295 $textlen = $this->GetStringWidth($text);
24296 if ($this->svgtextmode['text-anchor'] != 'start') {
24297 // check if string is RTL text
24298 if ($this->svgtextmode['text-anchor'] == 'end') {
24299 if ($this->svgtextmode['rtl']) {
24300 $this->x += $textlen;
24301 } else {
24302 $this->x -= $textlen;
24304 } elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24305 if ($this->svgtextmode['rtl']) {
24306 $this->x += ($textlen / 2);
24307 } else {
24308 $this->x -= ($textlen / 2);
24312 $textrendermode = $this->textrendermode;
24313 $textstrokewidth = $this->textstrokewidth;
24314 $this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
24315 if ($name == 'text') {
24316 // store current coordinates
24317 $tmpx = $this->x;
24318 $tmpy = $this->y;
24320 $this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24321 if ($name == 'text') {
24322 // restore coordinates
24323 $this->x = $tmpx;
24324 $this->y = $tmpy;
24326 // restore previous rendering mode
24327 $this->textrendermode = $textrendermode;
24328 $this->textstrokewidth = $textstrokewidth;
24329 $this->svgtext = '';
24330 $this->StopTransform();
24331 if (!$this->svgdefsmode) {
24332 array_pop($this->svgstyles);
24334 break;
24336 default: {
24337 break;
24343 * Sets the character data handler function for the XML parser.
24344 * @param $parser (resource) The first parameter, parser, is a reference to the XML parser calling the handler.
24345 * @param $data (string) The second parameter, data, contains the character data as a string.
24346 * @author Nicola Asuni
24347 * @since 5.0.000 (2010-05-02)
24348 * @protected
24350 protected function segSVGContentHandler($parser, $data) {
24351 $this->svgtext .= $data;
24354 // --- END SVG METHODS -----------------------------------------------------
24356 } // END OF TCPDF CLASS
24358 //============================================================+
24359 // END OF FILE
24360 //============================================================+