2 //============================================================+
3 // File name : tcpdf.php
6 // Last Update : 2023-09-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-2023 Nicola Asuni - Tecnick.com LTD
12 // This file is part of TCPDF software library.
14 // TCPDF is free software: you can redistribute 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 // -------------------------------------------------------------------
32 // This is a PHP class for generating PDF documents without requiring external extensions.
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.
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;
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 ImageMagick (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 Extension, 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.
68 //============================================================+
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>
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 ImageMagick (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 Extension, 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>
104 * Tools to encode your unicode fonts are on fonts/utils directory.</p>
105 * @package com.tecnick.tcpdf
106 * @author Nicola Asuni
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 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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.
132 * @author Nicola Asuni - info@tecnick.com
133 * @IgnoreAnnotation("protected")
134 * @IgnoreAnnotation("public")
135 * @IgnoreAnnotation("pre")
139 // Protected properties
142 * Current page number.
148 * Current object number.
154 * Array of object offsets.
157 protected $offsets = array();
160 * Array of object IDs for each page.
163 protected $pageobjects = array();
166 * Buffer holding in-memory PDF.
172 * Array containing pages.
175 protected $pages = array();
178 * Current document state.
190 * Current page orientation (P = Portrait, L = Landscape).
193 protected $CurOrientation;
199 protected $pagedim = array();
202 * Scale factor (number of points in user unit).
208 * Width of page format in points.
214 * Height of page format in points.
220 * Current width of page in points.
226 * Current height of page in points.
232 * Current width of page in user unit.
238 * Current height of page in user unit.
256 * Cell left margin (used by regions).
262 * Cell right margin (used by regions).
280 * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
281 * @since 5.9.000 (2010-10-03)
284 protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
287 * Array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
288 * @since 5.9.000 (2010-10-04)
291 protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
294 * Current horizontal position in user unit for cell positioning.
300 * Current vertical position in user unit for cell positioning.
306 * Height of last cell printed.
312 * Line width in user unit.
315 protected $LineWidth;
318 * Array of standard font names.
321 protected $CoreFonts;
324 * Array of used fonts.
327 protected $fonts = array();
330 * Array of font files.
333 protected $FontFiles = array();
336 * Array of encoding differences.
339 protected $diffs = array();
342 * Array of used images.
345 protected $images = array();
348 * Depth of the svg tag, to keep track if the svg tag is a subtag or the root tag.
351 protected $svg_tag_depth = 0;
354 * Array of Annotations in pages.
357 protected $PageAnnots = array();
360 * Array of internal links.
363 protected $links = array();
366 * Current font family.
369 protected $FontFamily;
372 * Current font style.
375 protected $FontStyle;
378 * Current font ascent (distance between font top and baseline).
380 * @since 2.8.000 (2007-03-29)
382 protected $FontAscent;
385 * Current font descent (distance between font bottom and baseline).
387 * @since 2.8.000 (2007-03-29)
389 protected $FontDescent;
395 protected $underline;
407 protected $CurrentFont;
410 * Current font size in points.
413 protected $FontSizePt;
416 * Current font size in user unit.
422 * Commands for drawing color.
425 protected $DrawColor;
428 * Commands for filling color.
431 protected $FillColor;
434 * Commands for text color.
437 protected $TextColor;
440 * Indicates whether fill and text colors are different.
443 protected $ColorFlag;
446 * Automatic page breaking.
449 protected $AutoPageBreak;
452 * Threshold used to trigger page breaks.
455 protected $PageBreakTrigger;
458 * Flag set when processing page header.
461 protected $InHeader = false;
464 * Flag set when processing page footer.
467 protected $InFooter = false;
476 * Layout display mode.
479 protected $LayoutMode;
482 * If true set the document information dictionary in Unicode.
485 protected $docinfounicode = true;
491 protected $title = '';
497 protected $subject = '';
503 protected $author = '';
509 protected $keywords = '';
515 protected $creator = '';
518 * Starting page number.
521 protected $starting_page_number = 1;
524 * The right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image.
526 * @author Nicola Asuni
532 * The right-bottom corner Y coordinate of last inserted image.
534 * @author Nicola Asuni
540 * Adjusting factor to convert pixels to user units.
542 * @author Nicola Asuni
545 protected $imgscale = 1;
548 * Boolean flag set to true when the input text is unicode (require unicode fonts).
550 * @author Nicola Asuni
553 protected $isunicode = false;
560 protected $PDFVersion = '1.7';
563 * ID of the stored default header template (-1 = not set).
566 protected $header_xobjid = false;
569 * If true reset the Header Xobject template at each page
572 protected $header_xobj_autoreset = false;
575 * Minimum distance between header and top page margin.
579 protected $header_margin;
582 * Minimum distance between footer and bottom page margin.
586 protected $footer_margin;
589 * Original left margin value.
591 * @since 1.53.0.TC013
593 protected $original_lMargin;
596 * Original right margin value.
598 * @since 1.53.0.TC013
600 protected $original_rMargin;
603 * Default font used on page header.
605 * @var array<int,string|float|null>
606 * @phpstan-var array{0: string, 1: string, 2: float|null}
608 protected $header_font;
611 * Default font used on page footer.
613 * @var array<int,string|float|null>
614 * @phpstan-var array{0: string, 1: string, 2: float|null}
616 protected $footer_font;
619 * Language templates.
625 * Barcode to print on page footer (only if set).
628 protected $barcode = false;
631 * Boolean flag to print/hide page header.
634 protected $print_header = true;
637 * Boolean flag to print/hide page footer.
640 protected $print_footer = true;
646 protected $header_logo = '';
649 * Width of header image logo in user units.
652 protected $header_logo_width = 30;
655 * Title to be printed on default page header.
658 protected $header_title = '';
661 * String to print on page header after title.
664 protected $header_string = '';
667 * Color for header text (RGB array).
668 * @since 5.9.174 (2012-07-25)
671 * @phpstan-var array{0: int, 1: int, 2: int}
673 protected $header_text_color = array(0,0,0);
676 * Color for header line (RGB array).
677 * @since 5.9.174 (2012-07-25)
680 * @phpstan-var array{0: int, 1: int, 2: int}
682 protected $header_line_color = array(0,0,0);
685 * Color for footer text (RGB array).
686 * @since 5.9.174 (2012-07-25)
689 * @phpstan-var array{0: int, 1: int, 2: int}
691 protected $footer_text_color = array(0,0,0);
694 * Color for footer line (RGB array).
695 * @since 5.9.174 (2012-07-25)
698 * @phpstan-var array{0: int, 1: int, 2: int}
700 protected $footer_line_color = array(0,0,0);
703 * Text shadow data array.
704 * @since 5.9.174 (2012-07-25)
707 protected $txtshadow = array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal');
710 * Default number of columns for html table.
713 protected $default_table_columns = 4;
715 // variables for html parser
718 * HTML PARSER: array to store current link and rendering styles.
721 protected $HREF = array();
724 * List of available fonts on filesystem.
727 protected $fontlist = array();
730 * Current foreground color.
736 * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
739 protected $listordered = array();
742 * HTML PARSER: array count list items on nested lists.
745 protected $listcount = array();
748 * HTML PARSER: current list nesting level.
751 protected $listnum = 0;
754 * HTML PARSER: indent amount for lists.
757 protected $listindent = 0;
760 * HTML PARSER: current list indententation level.
763 protected $listindentlevel = 0;
766 * Current background color.
772 * Temporary font size in points.
775 protected $tempfontsize = 10;
778 * Spacer string for LI tags.
781 protected $lispacer = '';
786 * @since 1.53.0.TC010
788 protected $encoding = 'UTF-8';
791 * Boolean flag to indicate if the document language is Right-To-Left.
795 protected $rtl = false;
798 * Boolean flag used to force RTL or LTR string direction.
802 protected $tmprtl = false;
804 // --- Variables used for document encryption:
807 * IBoolean flag indicating whether document is protected.
809 * @since 2.0.000 (2008-01-02)
811 protected $encrypted;
814 * Array containing encryption settings.
816 * @since 5.0.005 (2010-05-11)
818 protected $encryptdata = array();
821 * Last RC4 key encrypted (cached for optimisation).
823 * @since 2.0.000 (2008-01-02)
825 protected $last_enc_key;
828 * Last RC4 computed key.
830 * @since 2.0.000 (2008-01-02)
832 protected $last_enc_key_c;
835 * File ID (used on document trailer).
837 * @since 5.0.005 (2010-05-12)
844 * Outlines for bookmark.
846 * @since 2.1.002 (2008-02-12)
848 protected $outlines = array();
851 * Outline root for bookmark.
853 * @since 2.1.002 (2008-02-12)
855 protected $OutlineRoot;
857 // --- javascript and form ---
862 * @since 2.1.002 (2008-02-12)
864 protected $javascript = '';
867 * Javascript counter.
869 * @since 2.1.002 (2008-02-12)
876 * @since 2.8.000 (2008-03-19)
878 protected $linethrough;
881 * Array with additional document-wide usage rights for the document.
883 * @since 5.8.014 (2010-08-23)
885 protected $ur = array();
888 * DPI (Dot Per Inch) Document Resolution (do not change).
890 * @since 3.0.000 (2008-03-27)
895 * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
897 * @since 3.0.000 (2008-03-27)
899 protected $newpagegroup = array();
902 * Array that contains the number of pages in each page group.
904 * @since 3.0.000 (2008-03-27)
906 protected $pagegroups = array();
909 * Current page group number.
911 * @since 3.0.000 (2008-03-27)
913 protected $currpagegroup = 0;
916 * Array of transparency objects and parameters.
918 * @since 3.0.000 (2008-03-27)
920 protected $extgstates;
923 * Set the default JPEG compression quality (1-100).
925 * @since 3.0.000 (2008-03-27)
927 protected $jpeg_quality;
930 * Default cell height ratio.
932 * @since 3.0.014 (2008-05-23)
935 protected $cell_height_ratio = K_CELL_HEIGHT_RATIO
;
938 * PDF viewer preferences.
940 * @since 3.1.000 (2008-06-09)
942 protected $viewer_preferences;
945 * A name object specifying how the document should be displayed when opened.
947 * @since 3.1.000 (2008-06-09)
952 * Array for storing gradient information.
954 * @since 3.1.000 (2008-06-09)
956 protected $gradients = array();
959 * Array used to store positions inside the pages buffer (keys are the page numbers).
961 * @since 3.2.000 (2008-06-26)
963 protected $intmrk = array();
966 * Array used to store positions inside the pages buffer (keys are the page numbers).
968 * @since 5.7.000 (2010-08-03)
970 protected $bordermrk = array();
973 * Array used to store page positions to track empty pages (keys are the page numbers).
975 * @since 5.8.007 (2010-08-18)
977 protected $emptypagemrk = array();
980 * Array used to store content positions inside the pages buffer (keys are the page numbers).
982 * @since 4.6.021 (2009-07-20)
984 protected $cntmrk = array();
987 * Array used to store footer positions of each page.
989 * @since 3.2.000 (2008-07-01)
991 protected $footerpos = array();
994 * Array used to store footer length of each page.
996 * @since 4.0.014 (2008-07-29)
998 protected $footerlen = array();
1001 * Boolean flag to indicate if a new line is created.
1003 * @since 3.2.000 (2008-07-01)
1005 protected $newline = true;
1008 * End position of the latest inserted line.
1010 * @since 3.2.000 (2008-07-01)
1012 protected $endlinex = 0;
1015 * PDF string for width value of the last line.
1017 * @since 4.0.006 (2008-07-16)
1019 protected $linestyleWidth = '';
1022 * PDF string for CAP value of the last line.
1024 * @since 4.0.006 (2008-07-16)
1026 protected $linestyleCap = '0 J';
1029 * PDF string for join value of the last line.
1031 * @since 4.0.006 (2008-07-16)
1033 protected $linestyleJoin = '0 j';
1036 * PDF string for dash value of the last line.
1038 * @since 4.0.006 (2008-07-16)
1040 protected $linestyleDash = '[] 0 d';
1043 * Boolean flag to indicate if marked-content sequence is open.
1045 * @since 4.0.013 (2008-07-28)
1047 protected $openMarkedContent = false;
1050 * Count the latest inserted vertical spaces on HTML.
1052 * @since 4.0.021 (2008-08-24)
1054 protected $htmlvspace = 0;
1057 * Array of Spot colors.
1059 * @since 4.0.024 (2008-09-12)
1061 protected $spot_colors = array();
1064 * Symbol used for HTML unordered list items.
1066 * @since 4.0.028 (2008-09-26)
1068 protected $lisymbol = '';
1071 * String used to mark the beginning and end of EPS image blocks.
1073 * @since 4.1.000 (2008-10-18)
1075 protected $epsmarker = 'x#!#EPS#!#x';
1078 * Array of transformation matrix.
1080 * @since 4.2.000 (2008-10-29)
1082 protected $transfmatrix = array();
1085 * Current key for transformation matrix.
1087 * @since 4.8.005 (2009-09-17)
1089 protected $transfmatrix_key = 0;
1092 * Booklet mode for double-sided pages.
1094 * @since 4.2.000 (2008-10-29)
1096 protected $booklet = false;
1099 * Epsilon value used for float calculations.
1101 * @since 4.2.000 (2008-10-29)
1103 protected $feps = 0.005;
1106 * Array used for custom vertical spaces for HTML tags.
1108 * @since 4.2.001 (2008-10-30)
1110 protected $tagvspaces = array();
1113 * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1115 * @since 4.2.007 (2008-11-12)
1117 protected $customlistindent = -1;
1120 * Boolean flag to indicate if the border of the cell sides that cross the page should be removed.
1122 * @since 4.2.010 (2008-11-14)
1124 protected $opencell = true;
1127 * Array of files to embedd.
1129 * @since 4.4.000 (2008-12-07)
1131 protected $embeddedfiles = array();
1134 * Boolean flag to indicate if we are inside a PRE tag.
1136 * @since 4.4.001 (2008-12-08)
1138 protected $premode = false;
1141 * Array used to store positions of graphics transformation blocks inside the page buffer.
1142 * keys are the page numbers
1144 * @since 4.4.002 (2008-12-09)
1146 protected $transfmrk = array();
1149 * Default color for html links.
1151 * @since 4.4.003 (2008-12-09)
1153 protected $htmlLinkColorArray = array(0, 0, 255);
1156 * Default font style to add to html links.
1158 * @since 4.4.003 (2008-12-09)
1160 protected $htmlLinkFontStyle = 'U';
1163 * Counts the number of pages.
1165 * @since 4.5.000 (2008-12-31)
1167 protected $numpages = 0;
1170 * Array containing page lengths in bytes.
1172 * @since 4.5.000 (2008-12-31)
1174 protected $pagelen = array();
1177 * Counts the number of pages.
1179 * @since 4.5.000 (2008-12-31)
1181 protected $numimages = 0;
1184 * Store the image keys.
1186 * @since 4.5.000 (2008-12-31)
1188 protected $imagekeys = array();
1191 * Length of the buffer in bytes.
1193 * @since 4.5.000 (2008-12-31)
1195 protected $bufferlen = 0;
1198 * Counts the number of fonts.
1200 * @since 4.5.000 (2009-01-02)
1202 protected $numfonts = 0;
1205 * Store the font keys.
1207 * @since 4.5.000 (2009-01-02)
1209 protected $fontkeys = array();
1212 * Store the font object IDs.
1214 * @since 4.8.001 (2009-09-09)
1216 protected $font_obj_ids = array();
1219 * Store the fage status (true when opened, false when closed).
1221 * @since 4.5.000 (2009-01-02)
1223 protected $pageopen = array();
1226 * Default monospace font.
1228 * @since 4.5.025 (2009-03-10)
1230 protected $default_monospaced_font = 'courier';
1233 * Cloned copy of the current class object.
1235 * @since 4.5.029 (2009-03-19)
1240 * Array used to store the lengths of cache files.
1242 * @since 4.5.029 (2009-03-19)
1244 protected $cache_file_length = array();
1247 * Table header content to be repeated on each new page.
1249 * @since 4.5.030 (2009-03-20)
1251 protected $thead = '';
1254 * Margins used for table header.
1256 * @since 4.5.030 (2009-03-20)
1258 protected $theadMargins = array();
1261 * Boolean flag to enable document digital signature.
1263 * @since 4.6.005 (2009-04-24)
1265 protected $sign = false;
1268 * Digital signature data.
1270 * @since 4.6.005 (2009-04-24)
1272 protected $signature_data = array();
1275 * Digital signature max length.
1277 * @since 4.6.005 (2009-04-24)
1279 protected $signature_max_length = 11742;
1282 * Data for digital signature appearance.
1284 * @since 5.3.011 (2010-06-16)
1286 protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
1289 * Array of empty digital signature appearances.
1291 * @since 5.9.101 (2011-07-06)
1293 protected $empty_signature_appearance = array();
1296 * Boolean flag to enable document timestamping with TSA.
1298 * @since 6.0.085 (2014-06-19)
1300 protected $tsa_timestamp = false;
1303 * Timestamping data.
1305 * @since 6.0.085 (2014-06-19)
1307 protected $tsa_data = array();
1310 * Regular expression used to find blank characters (required for word-wrapping).
1312 * @since 4.6.006 (2009-04-28)
1314 protected $re_spaces = '/[^\S\xa0]/';
1317 * Array of $re_spaces parts.
1319 * @since 5.5.011 (2010-07-09)
1321 protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1324 * Digital signature object ID.
1326 * @since 4.6.022 (2009-06-23)
1328 protected $sig_obj_id = 0;
1331 * ID of page objects.
1333 * @since 4.7.000 (2009-08-29)
1335 protected $page_obj_id = array();
1338 * List of form annotations IDs.
1340 * @since 4.8.000 (2009-09-07)
1342 protected $form_obj_id = array();
1345 * 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.
1347 * @since 4.8.000 (2009-09-07)
1349 protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
1352 * Javascript objects array.
1354 * @since 4.8.000 (2009-09-07)
1356 protected $js_objects = array();
1359 * Current form action (used during XHTML rendering).
1361 * @since 4.8.000 (2009-09-07)
1363 protected $form_action = '';
1366 * Current form encryption type (used during XHTML rendering).
1368 * @since 4.8.000 (2009-09-07)
1370 protected $form_enctype = 'application/x-www-form-urlencoded';
1373 * Current method to submit forms.
1375 * @since 4.8.000 (2009-09-07)
1377 protected $form_mode = 'post';
1380 * List of fonts used on form fields (fontname => fontkey).
1382 * @since 4.8.001 (2009-09-09)
1384 protected $annotation_fonts = array();
1387 * List of radio buttons parent objects.
1389 * @since 4.8.001 (2009-09-09)
1391 protected $radiobutton_groups = array();
1394 * List of radio group objects IDs.
1396 * @since 4.8.001 (2009-09-09)
1398 protected $radio_groups = array();
1401 * Text indentation value (used for text-indent CSS attribute).
1403 * @since 4.8.006 (2009-09-23)
1405 protected $textindent = 0;
1408 * Store page number when startTransaction() is called.
1410 * @since 4.8.006 (2009-09-23)
1412 protected $start_transaction_page = 0;
1415 * Store Y position when startTransaction() is called.
1417 * @since 4.9.001 (2010-03-28)
1419 protected $start_transaction_y = 0;
1422 * True when we are printing the thead section on a new page.
1424 * @since 4.8.027 (2010-01-25)
1426 protected $inthead = false;
1429 * Array of column measures (width, space, starting Y position).
1431 * @since 4.9.001 (2010-03-28)
1433 protected $columns = array();
1438 * @since 4.9.001 (2010-03-28)
1440 protected $num_columns = 1;
1443 * Current column number.
1445 * @since 4.9.001 (2010-03-28)
1447 protected $current_column = 0;
1450 * Starting page for columns.
1452 * @since 4.9.001 (2010-03-28)
1454 protected $column_start_page = 0;
1457 * Maximum page and column selected.
1459 * @since 5.8.000 (2010-08-11)
1461 protected $maxselcol = array('page' => 0, 'column' => 0);
1464 * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding.
1466 * @since 5.8.000 (2010-08-11)
1468 protected $colxshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
1471 * 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.
1473 * @since 4.9.008 (2010-04-03)
1475 protected $textrendermode = 0;
1478 * Text stroke width in doc units.
1480 * @since 4.9.008 (2010-04-03)
1482 protected $textstrokewidth = 0;
1485 * Current stroke color.
1487 * @since 4.9.008 (2010-04-03)
1489 protected $strokecolor;
1492 * Default unit of measure for document.
1494 * @since 5.0.000 (2010-04-22)
1496 protected $pdfunit = 'mm';
1499 * Boolean flag true when we are on TOC (Table Of Content) page.
1502 protected $tocpage = false;
1505 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1507 * @since 5.0.000 (2010-04-26)
1509 protected $rasterize_vector_images = false;
1512 * Boolean flag: if true enables font subsetting by default.
1514 * @since 5.3.002 (2010-06-07)
1516 protected $font_subsetting = true;
1519 * Array of default graphic settings.
1521 * @since 5.5.008 (2010-07-02)
1523 protected $default_graphic_vars = array();
1526 * Array of XObjects.
1528 * @since 5.8.014 (2010-08-23)
1530 protected $xobjects = array();
1533 * Boolean value true when we are inside an XObject.
1535 * @since 5.8.017 (2010-08-24)
1537 protected $inxobj = false;
1540 * Current XObject ID.
1542 * @since 5.8.017 (2010-08-24)
1544 protected $xobjid = '';
1547 * Percentage of character stretching.
1549 * @since 5.9.000 (2010-09-29)
1551 protected $font_stretching = 100;
1554 * Increases or decreases the space between characters in a text by the specified amount (tracking).
1556 * @since 5.9.000 (2010-09-29)
1558 protected $font_spacing = 0;
1561 * Array of no-write regions.
1562 * ('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)
1564 * @since 5.9.003 (2010-10-14)
1566 protected $page_regions = array();
1569 * Boolean value true when page region check is active.
1572 protected $check_page_regions = true;
1575 * Array of PDF layers data.
1577 * @since 5.9.102 (2011-07-13)
1579 protected $pdflayers = array();
1582 * A dictionary of names and corresponding destinations (Dests key on document Catalog).
1584 * @since 5.9.097 (2011-06-23)
1586 protected $dests = array();
1589 * Object ID for Named Destinations
1591 * @since 5.9.097 (2011-06-23)
1596 * Embedded Files Names
1598 * @since 5.9.204 (2013-01-23)
1600 protected $efnames = array();
1603 * Directory used for the last SVG image.
1605 * @since 5.0.000 (2010-05-05)
1607 protected $svgdir = '';
1610 * Deafult unit of measure for SVG.
1612 * @since 5.0.000 (2010-05-02)
1614 protected $svgunit = 'px';
1617 * Array of SVG gradients.
1619 * @since 5.0.000 (2010-05-02)
1621 protected $svggradients = array();
1624 * ID of last SVG gradient.
1626 * @since 5.0.000 (2010-05-02)
1628 protected $svggradientid = 0;
1631 * Boolean value true when in SVG defs group.
1633 * @since 5.0.000 (2010-05-02)
1635 protected $svgdefsmode = false;
1638 * Array of SVG defs.
1640 * @since 5.0.000 (2010-05-02)
1642 protected $svgdefs = array();
1645 * Boolean value true when in SVG clipPath tag.
1647 * @since 5.0.000 (2010-04-26)
1649 protected $svgclipmode = false;
1652 * Array of SVG clipPath commands.
1654 * @since 5.0.000 (2010-05-02)
1656 protected $svgclippaths = array();
1659 * Array of SVG clipPath tranformation matrix.
1661 * @since 5.8.022 (2010-08-31)
1663 protected $svgcliptm = array();
1666 * ID of last SVG clipPath.
1668 * @since 5.0.000 (2010-05-02)
1670 protected $svgclipid = 0;
1675 * @since 5.0.000 (2010-05-02)
1677 protected $svgtext = '';
1680 * SVG text properties.
1682 * @since 5.8.013 (2010-08-23)
1684 protected $svgtextmode = array();
1687 * Array of SVG properties.
1689 * @since 5.0.000 (2010-05-02)
1691 protected $svgstyles = array(array(
1692 'alignment-baseline' => 'auto',
1693 'baseline-shift' => 'baseline',
1695 'clip-path' => 'none',
1696 'clip-rule' => 'nonzero',
1698 'color-interpolation' => 'sRGB',
1699 'color-interpolation-filters' => 'linearRGB',
1700 'color-profile' => 'auto',
1701 'color-rendering' => 'auto',
1703 'direction' => 'ltr',
1704 'display' => 'inline',
1705 'dominant-baseline' => 'auto',
1706 'enable-background' => 'accumulate',
1708 'fill-opacity' => 1,
1709 'fill-rule' => 'nonzero',
1711 'flood-color' => 'black',
1712 'flood-opacity' => 1,
1714 'font-family' => 'helvetica',
1715 'font-size' => 'medium',
1716 'font-size-adjust' => 'none',
1717 'font-stretch' => 'normal',
1718 'font-style' => 'normal',
1719 'font-variant' => 'normal',
1720 'font-weight' => 'normal',
1721 'glyph-orientation-horizontal' => '0deg',
1722 'glyph-orientation-vertical' => 'auto',
1723 'image-rendering' => 'auto',
1724 'kerning' => 'auto',
1725 'letter-spacing' => 'normal',
1726 'lighting-color' => 'white',
1728 'marker-end' => 'none',
1729 'marker-mid' => 'none',
1730 'marker-start' => 'none',
1733 'overflow' => 'auto',
1734 'pointer-events' => 'visiblePainted',
1735 'shape-rendering' => 'auto',
1736 'stop-color' => 'black',
1737 'stop-opacity' => 1,
1739 'stroke-dasharray' => 'none',
1740 'stroke-dashoffset' => 0,
1741 'stroke-linecap' => 'butt',
1742 'stroke-linejoin' => 'miter',
1743 'stroke-miterlimit' => 4,
1744 'stroke-opacity' => 1,
1745 'stroke-width' => 1,
1746 'text-anchor' => 'start',
1747 'text-decoration' => 'none',
1748 'text-rendering' => 'auto',
1749 'unicode-bidi' => 'normal',
1750 'visibility' => 'visible',
1751 'word-spacing' => 'normal',
1752 'writing-mode' => 'lr-tb',
1753 'text-color' => 'black',
1754 'transfmatrix' => array(1, 0, 0, 1, 0, 0)
1758 * If true force sRGB color profile for all document.
1760 * @since 5.9.121 (2011-09-28)
1762 protected $force_srgb = false;
1765 * If true set the document to PDF/A mode.
1767 * @since 5.9.121 (2011-09-27)
1769 protected $pdfa_mode = false;
1772 * version of PDF/A mode (1 - 3).
1774 * @since 6.2.26 (2019-03-12)
1776 protected $pdfa_version = 1;
1779 * Document creation date-time
1781 * @since 5.9.152 (2012-03-22)
1783 protected $doc_creation_timestamp;
1786 * Document modification date-time
1788 * @since 5.9.152 (2012-03-22)
1790 protected $doc_modification_timestamp;
1795 * @since 5.9.128 (2011-10-06)
1797 protected $custom_xmp = '';
1800 * Custom XMP RDF data.
1802 * @since 6.3.0 (2019-09-19)
1804 protected $custom_xmp_rdf = '';
1807 * Overprint mode array.
1808 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1810 * @since 5.9.152 (2012-03-23)
1811 * @var array<string,bool|int>
1813 protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1817 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1819 * @since 5.9.152 (2012-03-23)
1821 protected $alpha = array('CA' => 1, 'ca' => 1, 'BM' => '/Normal', 'AIS' => false);
1824 * Define the page boundaries boxes to be set on document.
1826 * @since 5.9.152 (2012-03-23)
1828 protected $page_boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
1831 * If true print TCPDF meta link.
1833 * @since 5.9.152 (2012-03-23)
1835 protected $tcpdflink = true;
1838 * Cache array for computed GD gamma values.
1840 * @since 5.9.1632 (2012-06-05)
1842 protected $gdgammacache = array();
1845 * Cache array for file content
1848 * @since 6.3.5 (2020-09-28)
1850 protected $fileContentCache = array();
1853 * Whether to allow local file path in image html tags, when prefixed with file://
1857 * @since 6.4 (2020-07-23)
1859 protected $allowLocalFiles = false;
1861 //------------------------------------------------------------
1863 //------------------------------------------------------------
1866 * This is the class constructor.
1867 * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
1869 * @param string $orientation 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>
1870 * @param string $unit 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.
1871 * @param mixed $format 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().
1872 * @param boolean $unicode TRUE means that the input text is unicode (default = true)
1873 * @param string $encoding Charset encoding (used only when converting back html entities); default is UTF-8.
1874 * @param boolean $diskcache DEPRECATED FEATURE
1875 * @param false|integer $pdfa If not false, set the document to PDF/A mode and the good version (1 or 3).
1877 * @see getPageSizeFromFormat(), setPageFormat()
1879 public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false, $pdfa=false) {
1880 // set file ID for trailer
1881 $serformat = (is_array($format) ?
json_encode($format) : $format);
1882 $this->file_id
= md5(TCPDF_STATIC
::getRandomSeed('TCPDF'.$orientation.$unit.$serformat.$encoding));
1883 $this->font_obj_ids
= array();
1884 $this->page_obj_id
= array();
1885 $this->form_obj_id
= array();
1888 if ($pdfa != false) {
1889 $this->pdfa_mode
= true;
1890 $this->pdfa_version
= $pdfa; // 1 or 3
1892 $this->pdfa_mode
= false;
1894 $this->force_srgb
= false;
1895 // set language direction
1897 $this->tmprtl
= false;
1900 // initialization of properties
1901 $this->isunicode
= $unicode;
1903 $this->transfmrk
[0] = array();
1904 $this->pagedim
= array();
1907 $this->pages
= array();
1909 $this->fonts
= array();
1910 $this->FontFiles
= array();
1911 $this->diffs
= array();
1912 $this->images
= array();
1913 $this->links
= array();
1914 $this->gradients
= array();
1915 $this->InFooter
= false;
1917 $this->FontFamily
= defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN
:'helvetica';
1918 $this->FontStyle
= '';
1919 $this->FontSizePt
= 12;
1920 $this->underline
= false;
1921 $this->overline
= false;
1922 $this->linethrough
= false;
1923 $this->DrawColor
= '0 G';
1924 $this->FillColor
= '0 g';
1925 $this->TextColor
= '0 g';
1926 $this->ColorFlag
= false;
1927 $this->pdflayers
= array();
1928 // encryption values
1929 $this->encrypted
= false;
1930 $this->last_enc_key
= '';
1931 // standard Unicode fonts
1932 $this->CoreFonts
= array(
1933 'courier'=>'Courier',
1934 'courierB'=>'Courier-Bold',
1935 'courierI'=>'Courier-Oblique',
1936 'courierBI'=>'Courier-BoldOblique',
1937 'helvetica'=>'Helvetica',
1938 'helveticaB'=>'Helvetica-Bold',
1939 'helveticaI'=>'Helvetica-Oblique',
1940 'helveticaBI'=>'Helvetica-BoldOblique',
1941 'times'=>'Times-Roman',
1942 'timesB'=>'Times-Bold',
1943 'timesI'=>'Times-Italic',
1944 'timesBI'=>'Times-BoldItalic',
1946 'zapfdingbats'=>'ZapfDingbats'
1949 $this->setPageUnit($unit);
1950 // set page format and orientation
1951 $this->setPageFormat($format, $orientation);
1952 // page margins (1 cm)
1953 $margin = 28.35 / $this->k
;
1954 $this->setMargins($margin, $margin);
1955 $this->clMargin
= $this->lMargin
;
1956 $this->crMargin
= $this->rMargin
;
1957 // internal cell padding
1958 $cpadding = $margin / 10;
1959 $this->setCellPaddings($cpadding, 0, $cpadding, 0);
1961 $this->setCellMargins(0, 0, 0, 0);
1962 // line width (0.2 mm)
1963 $this->LineWidth
= 0.57 / $this->k
;
1964 $this->linestyleWidth
= sprintf('%F w', ($this->LineWidth
* $this->k
));
1965 $this->linestyleCap
= '0 J';
1966 $this->linestyleJoin
= '0 j';
1967 $this->linestyleDash
= '[] 0 d';
1968 // automatic page break
1969 $this->setAutoPageBreak(true, (2 * $margin));
1970 // full width display mode
1971 $this->setDisplayMode('fullwidth');
1973 $this->setCompression();
1974 // set default PDF version number
1975 $this->setPDFVersion();
1976 $this->tcpdflink
= true;
1977 $this->encoding
= $encoding;
1978 $this->HREF
= array();
1979 $this->getFontsList();
1980 $this->fgcolor
= array('R' => 0, 'G' => 0, 'B' => 0);
1981 $this->strokecolor
= array('R' => 0, 'G' => 0, 'B' => 0);
1982 $this->bgcolor
= array('R' => 255, 'G' => 255, 'B' => 255);
1983 $this->extgstates
= array();
1984 $this->setTextShadow();
1986 $this->sign
= false;
1987 $this->tsa_timestamp
= false;
1988 $this->tsa_data
= array();
1989 $this->signature_appearance
= array('page' => 1, 'rect' => '0 0 0 0', 'name' => 'Signature');
1990 $this->empty_signature_appearance
= array();
1992 $this->ur
['enabled'] = false;
1993 $this->ur
['document'] = '/FullSave';
1994 $this->ur
['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
1995 $this->ur
['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
1996 $this->ur
['signature'] = '/Modify';
1997 $this->ur
['ef'] = '/Create/Delete/Modify/Import';
1998 $this->ur
['formex'] = '';
1999 // set default JPEG quality
2000 $this->jpeg_quality
= 75;
2001 // initialize some settings
2002 TCPDF_FONTS
::utf8Bidi(array(), '', false, $this->isunicode
, $this->CurrentFont
);
2004 $this->setFont($this->FontFamily
, $this->FontStyle
, $this->FontSizePt
);
2005 $this->setHeaderFont(array($this->FontFamily
, $this->FontStyle
, $this->FontSizePt
));
2006 $this->setFooterFont(array($this->FontFamily
, $this->FontStyle
, $this->FontSizePt
));
2007 // check if PCRE Unicode support is enabled
2008 if ($this->isunicode
AND (@preg_match
('/\pL/u', 'a') == 1)) {
2009 // PCRE unicode support is turned ON
2010 // \s : any whitespace character
2011 // \p{Z} : any separator
2012 // \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2013 // \xa0 : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2014 //$this->setSpacesRE('/(?!\xa0)[\s\p{Z}\p{Lo}]/u');
2015 $this->setSpacesRE('/(?!\xa0)[\s\p{Z}]/u');
2017 // PCRE unicode support is turned OFF
2018 $this->setSpacesRE('/[^\S\xa0]/');
2020 $this->default_form_prop
= array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
2021 // set document creation and modification timestamp
2022 $this->doc_creation_timestamp
= time();
2023 $this->doc_modification_timestamp
= $this->doc_creation_timestamp
;
2024 // get default graphic vars
2025 $this->default_graphic_vars
= $this->getGraphicVars();
2026 $this->header_xobj_autoreset
= false;
2027 $this->custom_xmp
= '';
2028 $this->custom_xmp_rdf
= '';
2032 * Default destructor.
2034 * @since 1.53.0.TC016
2036 public function __destruct() {
2038 $this->_destroy(true);
2042 * Set the units of measure for the document.
2043 * @param string $unit 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.
2045 * @since 3.0.015 (2008-06-06)
2047 public function setPageUnit($unit) {
2048 $unit = strtolower($unit);
2059 $this->k
= $this->dpi
/ 25.4;
2064 $this->k
= $this->dpi
/ 2.54;
2069 $this->k
= $this->dpi
;
2074 $this->Error('Incorrect unit: '.$unit);
2078 $this->pdfunit
= $unit;
2079 if (isset($this->CurOrientation
)) {
2080 $this->setPageOrientation($this->CurOrientation
);
2085 * Change the format of the current page
2086 * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numbers (width, height) or an array containing the following measures and options:<ul>
2087 * <li>['format'] = page format name (one of the above);</li>
2088 * <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>
2089 * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
2090 * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
2091 * <li>['MediaBox']['llx'] : lower-left x coordinate</li>
2092 * <li>['MediaBox']['lly'] : lower-left y coordinate</li>
2093 * <li>['MediaBox']['urx'] : upper-right x coordinate</li>
2094 * <li>['MediaBox']['ury'] : upper-right y coordinate</li>
2095 * <li>['CropBox'] : the visible region of default user space:</li>
2096 * <li>['CropBox']['llx'] : lower-left x coordinate</li>
2097 * <li>['CropBox']['lly'] : lower-left y coordinate</li>
2098 * <li>['CropBox']['urx'] : upper-right x coordinate</li>
2099 * <li>['CropBox']['ury'] : upper-right y coordinate</li>
2100 * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
2101 * <li>['BleedBox']['llx'] : lower-left x coordinate</li>
2102 * <li>['BleedBox']['lly'] : lower-left y coordinate</li>
2103 * <li>['BleedBox']['urx'] : upper-right x coordinate</li>
2104 * <li>['BleedBox']['ury'] : upper-right y coordinate</li>
2105 * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
2106 * <li>['TrimBox']['llx'] : lower-left x coordinate</li>
2107 * <li>['TrimBox']['lly'] : lower-left y coordinate</li>
2108 * <li>['TrimBox']['urx'] : upper-right x coordinate</li>
2109 * <li>['TrimBox']['ury'] : upper-right y coordinate</li>
2110 * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
2111 * <li>['ArtBox']['llx'] : lower-left x coordinate</li>
2112 * <li>['ArtBox']['lly'] : lower-left y coordinate</li>
2113 * <li>['ArtBox']['urx'] : upper-right x coordinate</li>
2114 * <li>['ArtBox']['ury'] : upper-right y coordinate</li>
2115 * <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>
2116 * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
2117 * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
2118 * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
2119 * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
2120 * <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>
2121 * <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>
2122 * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
2123 * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
2124 * <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>
2125 * <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>
2126 * <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>
2127 * <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>
2128 * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
2130 * @param string $orientation page orientation. Possible values are (case insensitive):<ul>
2131 * <li>P or Portrait (default)</li>
2132 * <li>L or Landscape</li>
2133 * <li>'' (empty string) for automatic orientation</li>
2136 * @since 3.0.015 (2008-06-06)
2137 * @see getPageSizeFromFormat()
2139 protected function setPageFormat($format, $orientation='P') {
2140 if (!empty($format) AND isset($this->pagedim
[$this->page
])) {
2141 // remove inherited values
2142 unset($this->pagedim
[$this->page
]);
2144 if (is_string($format)) {
2145 // get page measures from format name
2146 $pf = TCPDF_STATIC
::getPageSizeFromFormat($format);
2147 $this->fwPt
= $pf[0];
2148 $this->fhPt
= $pf[1];
2150 // the boundaries of the physical medium on which the page shall be displayed or printed
2151 if (isset($format['MediaBox'])) {
2152 $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
);
2153 $this->fwPt
= (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k
);
2154 $this->fhPt
= (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k
);
2156 if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
2157 $pf = array(($format[0] * $this->k
), ($format[1] * $this->k
));
2159 if (!isset($format['format'])) {
2161 $format['format'] = 'A4';
2163 $pf = TCPDF_STATIC
::getPageSizeFromFormat($format['format']);
2165 $this->fwPt
= $pf[0];
2166 $this->fhPt
= $pf[1];
2167 $this->pagedim
= TCPDF_STATIC
::setPageBoxes($this->page
, 'MediaBox', 0, 0, $this->fwPt
, $this->fhPt
, true, $this->k
, $this->pagedim
);
2169 // the visible region of default user space
2170 if (isset($format['CropBox'])) {
2171 $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
);
2173 // the region to which the contents of the page shall be clipped when output in a production environment
2174 if (isset($format['BleedBox'])) {
2175 $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
);
2177 // the intended dimensions of the finished page after trimming
2178 if (isset($format['TrimBox'])) {
2179 $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
);
2181 // the page's meaningful content (including potential white space)
2182 if (isset($format['ArtBox'])) {
2183 $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
);
2185 // specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
2186 if (isset($format['BoxColorInfo'])) {
2187 $this->pagedim
[$this->page
]['BoxColorInfo'] = $format['BoxColorInfo'];
2189 if (isset($format['Rotate']) AND (($format['Rotate'] %
90) == 0)) {
2190 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2191 $this->pagedim
[$this->page
]['Rotate'] = intval($format['Rotate']);
2193 if (isset($format['PZ'])) {
2194 // The page's preferred zoom (magnification) factor
2195 $this->pagedim
[$this->page
]['PZ'] = floatval($format['PZ']);
2197 if (isset($format['trans'])) {
2198 // The style and duration of the visual transition to use when moving from another page to the given page during a presentation
2199 if (isset($format['trans']['Dur'])) {
2200 // The page's display duration
2201 $this->pagedim
[$this->page
]['trans']['Dur'] = floatval($format['trans']['Dur']);
2203 $stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
2204 if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
2205 // The transition style that shall be used when moving to this page from another during a presentation
2206 $this->pagedim
[$this->page
]['trans']['S'] = $format['trans']['S'];
2207 $valid_effect = array('Split', 'Blinds');
2208 $valid_vals = array('H', 'V');
2209 if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
2210 $this->pagedim
[$this->page
]['trans']['Dm'] = $format['trans']['Dm'];
2212 $valid_effect = array('Split', 'Box', 'Fly');
2213 $valid_vals = array('I', 'O');
2214 if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
2215 $this->pagedim
[$this->page
]['trans']['M'] = $format['trans']['M'];
2217 $valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
2218 if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
2219 if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
2220 OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
2221 OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
2222 $this->pagedim
[$this->page
]['trans']['Di'] = intval($format['trans']['Di']);
2225 if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
2226 $this->pagedim
[$this->page
]['trans']['SS'] = floatval($format['trans']['SS']);
2228 if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
2229 $this->pagedim
[$this->page
]['trans']['B'] = 'true';
2232 $this->pagedim
[$this->page
]['trans']['S'] = 'R';
2234 if (isset($format['trans']['D'])) {
2235 // The duration of the transition effect, in seconds
2236 $this->pagedim
[$this->page
]['trans']['D'] = floatval($format['trans']['D']);
2238 $this->pagedim
[$this->page
]['trans']['D'] = 1;
2242 $this->setPageOrientation($orientation);
2246 * Set page orientation.
2247 * @param string $orientation 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>
2248 * @param boolean|null $autopagebreak Boolean indicating if auto-page-break mode should be on or off.
2249 * @param float|null $bottommargin bottom margin of the page.
2251 * @since 3.0.015 (2008-06-06)
2253 public function setPageOrientation($orientation, $autopagebreak=null, $bottommargin=null) {
2254 if (!isset($this->pagedim
[$this->page
]['MediaBox'])) {
2255 // the boundaries of the physical medium on which the page shall be displayed or printed
2256 $this->pagedim
= TCPDF_STATIC
::setPageBoxes($this->page
, 'MediaBox', 0, 0, $this->fwPt
, $this->fhPt
, true, $this->k
, $this->pagedim
);
2258 if (!isset($this->pagedim
[$this->page
]['CropBox'])) {
2259 // the visible region of default user space
2260 $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
);
2262 if (!isset($this->pagedim
[$this->page
]['BleedBox'])) {
2263 // the region to which the contents of the page shall be clipped when output in a production environment
2264 $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
);
2266 if (!isset($this->pagedim
[$this->page
]['TrimBox'])) {
2267 // the intended dimensions of the finished page after trimming
2268 $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
);
2270 if (!isset($this->pagedim
[$this->page
]['ArtBox'])) {
2271 // the page's meaningful content (including potential white space)
2272 $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
);
2274 if (!isset($this->pagedim
[$this->page
]['Rotate'])) {
2275 // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
2276 $this->pagedim
[$this->page
]['Rotate'] = 0;
2278 if (!isset($this->pagedim
[$this->page
]['PZ'])) {
2279 // The page's preferred zoom (magnification) factor
2280 $this->pagedim
[$this->page
]['PZ'] = 1;
2282 if ($this->fwPt
> $this->fhPt
) {
2284 $default_orientation = 'L';
2287 $default_orientation = 'P';
2289 $valid_orientations = array('P', 'L');
2290 if (empty($orientation)) {
2291 $orientation = $default_orientation;
2293 $orientation = strtoupper($orientation[0]);
2295 if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
2296 $this->CurOrientation
= $orientation;
2297 $this->wPt
= $this->fhPt
;
2298 $this->hPt
= $this->fwPt
;
2300 $this->CurOrientation
= $default_orientation;
2301 $this->wPt
= $this->fwPt
;
2302 $this->hPt
= $this->fhPt
;
2304 if ((abs($this->pagedim
[$this->page
]['MediaBox']['urx'] - $this->hPt
) < $this->feps
) AND (abs($this->pagedim
[$this->page
]['MediaBox']['ury'] - $this->wPt
) < $this->feps
)){
2305 // swap X and Y coordinates (change page orientation)
2306 $this->pagedim
= TCPDF_STATIC
::swapPageBoxCoordinates($this->page
, $this->pagedim
);
2308 $this->w
= ($this->wPt
/ $this->k
);
2309 $this->h
= ($this->hPt
/ $this->k
);
2310 if (TCPDF_STATIC
::empty_string($autopagebreak)) {
2311 if (isset($this->AutoPageBreak
)) {
2312 $autopagebreak = $this->AutoPageBreak
;
2314 $autopagebreak = true;
2317 if (TCPDF_STATIC
::empty_string($bottommargin)) {
2318 if (isset($this->bMargin
)) {
2319 $bottommargin = $this->bMargin
;
2321 // default value = 2 cm
2322 $bottommargin = 2 * 28.35 / $this->k
;
2325 $this->setAutoPageBreak($autopagebreak, $bottommargin);
2326 // store page dimensions
2327 $this->pagedim
[$this->page
]['w'] = $this->wPt
;
2328 $this->pagedim
[$this->page
]['h'] = $this->hPt
;
2329 $this->pagedim
[$this->page
]['wk'] = $this->w
;
2330 $this->pagedim
[$this->page
]['hk'] = $this->h
;
2331 $this->pagedim
[$this->page
]['tm'] = $this->tMargin
;
2332 $this->pagedim
[$this->page
]['bm'] = $bottommargin;
2333 $this->pagedim
[$this->page
]['lm'] = $this->lMargin
;
2334 $this->pagedim
[$this->page
]['rm'] = $this->rMargin
;
2335 $this->pagedim
[$this->page
]['pb'] = $autopagebreak;
2336 $this->pagedim
[$this->page
]['or'] = $this->CurOrientation
;
2337 $this->pagedim
[$this->page
]['olm'] = $this->original_lMargin
;
2338 $this->pagedim
[$this->page
]['orm'] = $this->original_rMargin
;
2342 * Set regular expression to detect withespaces or word separators.
2343 * The pattern delimiter must be the forward-slash character "/".
2344 * Some example patterns are:
2346 * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
2347 * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
2348 * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
2349 * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
2350 * \s : any whitespace character
2351 * \p{Z} : any separator
2352 * \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants. Is used to chunk chinese words.
2353 * \xa0 : Unicode Character 'NO-BREAK SPACE' (U+00A0)
2355 * @param string $re regular expression (leave empty for default).
2357 * @since 4.6.016 (2009-06-15)
2359 public function setSpacesRE($re='/[^\S\xa0]/') {
2360 $this->re_spaces
= $re;
2361 $re_parts = explode('/', $re);
2362 // get pattern parts
2363 $this->re_space
= array();
2364 if (isset($re_parts[1]) AND !empty($re_parts[1])) {
2365 $this->re_space
['p'] = $re_parts[1];
2367 $this->re_space
['p'] = '[\s]';
2369 // set pattern modifiers
2370 if (isset($re_parts[2]) AND !empty($re_parts[2])) {
2371 $this->re_space
['m'] = $re_parts[2];
2373 $this->re_space
['m'] = '';
2378 * Enable or disable Right-To-Left language mode
2379 * @param boolean $enable if true enable Right-To-Left language mode.
2380 * @param boolean $resetx if true reset the X position on direction change.
2382 * @since 2.0.000 (2008-01-03)
2384 public function setRTL($enable, $resetx=true) {
2385 $enable = $enable ?
true : false;
2386 $resetx = ($resetx AND ($enable != $this->rtl
));
2387 $this->rtl
= $enable;
2388 $this->tmprtl
= false;
2395 * Return the RTL status
2398 * @since 4.0.012 (2008-07-24)
2400 public function getRTL() {
2405 * Force temporary RTL language direction
2406 * @param false|string $mode can be false, 'L' for LTR or 'R' for RTL
2408 * @since 2.1.000 (2008-01-09)
2410 public function setTempRTL($mode) {
2412 switch (strtoupper($mode)) {
2433 $this->tmprtl
= $newmode;
2437 * Return the current temporary RTL status
2440 * @since 4.8.014 (2009-11-04)
2442 public function isRTLTextDir() {
2443 return ($this->rtl
OR ($this->tmprtl
== 'R'));
2447 * Set the last cell height.
2448 * @param float $h cell height.
2449 * @author Nicola Asuni
2451 * @since 1.53.0.TC034
2453 public function setLastH($h) {
2458 * Return the cell height
2459 * @param int $fontsize Font size in internal units
2460 * @param boolean $padding If true add cell padding
2464 public function getCellHeight($fontsize, $padding=TRUE) {
2465 $height = ($fontsize * $this->cell_height_ratio
);
2466 if ($padding && !empty($this->cell_padding
)) {
2467 $height +
= ($this->cell_padding
['T'] +
$this->cell_padding
['B']);
2469 return round($height, 6);
2473 * Reset the last cell height.
2475 * @since 5.9.000 (2010-10-03)
2477 public function resetLastH() {
2478 $this->lasth
= $this->getCellHeight($this->FontSize
);
2482 * Get the last cell height.
2483 * @return float last cell height
2485 * @since 4.0.017 (2008-08-05)
2487 public function getLastH() {
2488 return $this->lasth
;
2492 * Set the adjusting factor to convert pixels to user units.
2493 * @param float $scale adjusting factor to convert pixels to user units.
2494 * @author Nicola Asuni
2498 public function setImageScale($scale) {
2499 $this->imgscale
= $scale;
2503 * Returns the adjusting factor to convert pixels to user units.
2504 * @return float adjusting factor to convert pixels to user units.
2505 * @author Nicola Asuni
2509 public function getImageScale() {
2510 return $this->imgscale
;
2514 * Returns an array of page dimensions:
2515 * <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>
2516 * @param int|null $pagenum page number (empty = current page)
2517 * @return array of page dimensions.
2518 * @author Nicola Asuni
2520 * @since 4.5.027 (2009-03-16)
2522 public function getPageDimensions($pagenum=null) {
2523 if (empty($pagenum)) {
2524 $pagenum = $this->page
;
2526 return $this->pagedim
[$pagenum];
2530 * Returns the page width in units.
2531 * @param int|null $pagenum page number (empty = current page)
2532 * @return int|float page width.
2533 * @author Nicola Asuni
2536 * @see getPageDimensions()
2538 public function getPageWidth($pagenum=null) {
2539 if (empty($pagenum)) {
2542 return $this->pagedim
[$pagenum]['w'];
2546 * Returns the page height in units.
2547 * @param int|null $pagenum page number (empty = current page)
2548 * @return int|float page height.
2549 * @author Nicola Asuni
2552 * @see getPageDimensions()
2554 public function getPageHeight($pagenum=null) {
2555 if (empty($pagenum)) {
2558 return $this->pagedim
[$pagenum]['h'];
2562 * Returns the page break margin.
2563 * @param int|null $pagenum page number (empty = current page)
2564 * @return int|float page break margin.
2565 * @author Nicola Asuni
2568 * @see getPageDimensions()
2570 public function getBreakMargin($pagenum=null) {
2571 if (empty($pagenum)) {
2572 return $this->bMargin
;
2574 return $this->pagedim
[$pagenum]['bm'];
2578 * Returns the scale factor (number of points in user unit).
2579 * @return int scale factor.
2580 * @author Nicola Asuni
2584 public function getScaleFactor() {
2589 * Defines the left, top and right margins.
2590 * @param int|float $left Left margin.
2591 * @param int|float $top Top margin.
2592 * @param int|float|null $right Right margin. Default value is the left one.
2593 * @param boolean $keepmargins if true overwrites the default page margins
2596 * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
2598 public function setMargins($left, $top, $right=null, $keepmargins=false) {
2599 //Set left, top and right margins
2600 $this->lMargin
= $left;
2601 $this->tMargin
= $top;
2602 if ($right == -1 OR $right === null) {
2605 $this->rMargin
= $right;
2607 // overwrite original values
2608 $this->original_lMargin
= $this->lMargin
;
2609 $this->original_rMargin
= $this->rMargin
;
2614 * 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.
2615 * @param int|float $margin The margin.
2618 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2620 public function setLeftMargin($margin) {
2622 $this->lMargin
= $margin;
2623 if (($this->page
> 0) AND ($this->x
< $margin)) {
2629 * Defines the top margin. The method can be called before creating the first page.
2630 * @param int|float $margin The margin.
2633 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2635 public function setTopMargin($margin) {
2637 $this->tMargin
= $margin;
2638 if (($this->page
> 0) AND ($this->y
< $margin)) {
2644 * Defines the right margin. The method can be called before creating the first page.
2645 * @param int|float $margin The margin.
2648 * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
2650 public function setRightMargin($margin) {
2651 $this->rMargin
= $margin;
2652 if (($this->page
> 0) AND ($this->x
> ($this->w
- $margin))) {
2653 $this->x
= $this->w
- $margin;
2658 * Set the same internal Cell padding for top, right, bottom, left-
2659 * @param int|float $pad internal padding.
2661 * @since 2.1.000 (2008-01-09)
2662 * @see getCellPaddings(), setCellPaddings()
2664 public function setCellPadding($pad) {
2666 $this->cell_padding
['L'] = $pad;
2667 $this->cell_padding
['T'] = $pad;
2668 $this->cell_padding
['R'] = $pad;
2669 $this->cell_padding
['B'] = $pad;
2674 * Set the internal Cell paddings.
2675 * @param int|float|null $left left padding
2676 * @param int|float|null $top top padding
2677 * @param int|float|null $right right padding
2678 * @param int|float|null $bottom bottom padding
2680 * @since 5.9.000 (2010-10-03)
2681 * @see getCellPaddings(), SetCellPadding()
2683 public function setCellPaddings($left=null, $top=null, $right=null, $bottom=null) {
2684 if (!TCPDF_STATIC
::empty_string($left) AND ($left >= 0)) {
2685 $this->cell_padding
['L'] = $left;
2687 if (!TCPDF_STATIC
::empty_string($top) AND ($top >= 0)) {
2688 $this->cell_padding
['T'] = $top;
2690 if (!TCPDF_STATIC
::empty_string($right) AND ($right >= 0)) {
2691 $this->cell_padding
['R'] = $right;
2693 if (!TCPDF_STATIC
::empty_string($bottom) AND ($bottom >= 0)) {
2694 $this->cell_padding
['B'] = $bottom;
2699 * Get the internal Cell padding array.
2700 * @return array of padding values
2702 * @since 5.9.000 (2010-10-03)
2703 * @see setCellPaddings(), SetCellPadding()
2705 public function getCellPaddings() {
2706 return $this->cell_padding
;
2710 * Set the internal Cell margins.
2711 * @param int|float|null $left left margin
2712 * @param int|float|null $top top margin
2713 * @param int|float|null $right right margin
2714 * @param int|float|null $bottom bottom margin
2716 * @since 5.9.000 (2010-10-03)
2717 * @see getCellMargins()
2719 public function setCellMargins($left=null, $top=null, $right=null, $bottom=null) {
2720 if (!TCPDF_STATIC
::empty_string($left) AND ($left >= 0)) {
2721 $this->cell_margin
['L'] = $left;
2723 if (!TCPDF_STATIC
::empty_string($top) AND ($top >= 0)) {
2724 $this->cell_margin
['T'] = $top;
2726 if (!TCPDF_STATIC
::empty_string($right) AND ($right >= 0)) {
2727 $this->cell_margin
['R'] = $right;
2729 if (!TCPDF_STATIC
::empty_string($bottom) AND ($bottom >= 0)) {
2730 $this->cell_margin
['B'] = $bottom;
2735 * Get the internal Cell margin array.
2736 * @return array of margin values
2738 * @since 5.9.000 (2010-10-03)
2739 * @see setCellMargins()
2741 public function getCellMargins() {
2742 return $this->cell_margin
;
2746 * Adjust the internal Cell padding array to take account of the line width.
2747 * @param string|array|int|bool $brd 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)))
2748 * @return void|array array of adjustments
2750 * @since 5.9.000 (2010-10-03)
2752 protected function adjustCellPadding($brd=0) {
2756 if (is_string($brd)) {
2757 // convert string to array
2758 $slen = strlen($brd);
2760 for ($i = 0; $i < $slen; ++
$i) {
2761 $newbrd[$brd[$i]] = true;
2767 ||
(is_numeric($brd) && ((int)$brd > 0))
2769 $brd = array('LRTB' => true);
2771 if (!is_array($brd)) {
2774 // store current cell padding
2775 $cp = $this->cell_padding
;
2776 // select border mode
2777 if (isset($brd['mode'])) {
2778 $mode = $brd['mode'];
2779 unset($brd['mode']);
2784 foreach ($brd as $border => $style) {
2785 $line_width = $this->LineWidth
;
2786 if (is_array($style) && isset($style['width'])) {
2788 $line_width = $style['width'];
2790 $adj = 0; // line width inside the cell
2802 $adj = ($line_width / 2);
2806 // correct internal cell padding if required to avoid overlap between text and lines
2808 is_numeric($this->cell_padding
['T'])
2809 && ($this->cell_padding
['T'] < $adj)
2810 && (strpos($border, 'T') !== false)
2812 $this->cell_padding
['T'] = $adj;
2815 is_numeric($this->cell_padding
['R'])
2816 && ($this->cell_padding
['R'] < $adj)
2817 && (strpos($border, 'R') !== false)
2819 $this->cell_padding
['R'] = $adj;
2822 is_numeric($this->cell_padding
['B'])
2823 && ($this->cell_padding
['B'] < $adj)
2824 && (strpos($border, 'B') !== false)
2826 $this->cell_padding
['B'] = $adj;
2829 is_numeric($this->cell_padding
['L'])
2830 && ($this->cell_padding
['L'] < $adj)
2831 && (strpos($border, 'L') !== false)
2833 $this->cell_padding
['L'] = $adj;
2839 'T' => ($this->cell_padding
['T'] - $cp['T']),
2840 'R' => ($this->cell_padding
['R'] - $cp['R']),
2841 'B' => ($this->cell_padding
['B'] - $cp['B']),
2842 'L' => ($this->cell_padding
['L'] - $cp['L']),
2847 * 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.
2848 * @param boolean $auto Boolean indicating if mode should be on or off.
2849 * @param float $margin Distance from the bottom of the page.
2852 * @see Cell(), MultiCell(), AcceptPageBreak()
2854 public function setAutoPageBreak($auto, $margin=0) {
2855 $this->AutoPageBreak
= $auto ?
true : false;
2856 $this->bMargin
= $margin;
2857 $this->PageBreakTrigger
= $this->h
- $margin;
2861 * Return the auto-page-break mode (true or false).
2862 * @return bool auto-page-break mode
2866 public function getAutoPageBreak() {
2867 return $this->AutoPageBreak
;
2871 * Defines the way the document is to be displayed by the viewer.
2872 * @param mixed $zoom 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>
2873 * @param string $layout 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>
2874 * @param string $mode 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>
2878 public function setDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
2879 if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
2880 $this->ZoomMode
= $zoom;
2882 $this->Error('Incorrect zoom display mode: '.$zoom);
2884 $this->LayoutMode
= TCPDF_STATIC
::getPageLayoutMode($layout);
2885 $this->PageMode
= TCPDF_STATIC
::getPageMode($mode);
2889 * 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.
2890 * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
2891 * @param boolean $compress Boolean indicating if compression must be enabled.
2895 public function setCompression($compress=true) {
2896 $this->compress
= false;
2897 if (function_exists('gzcompress')) {
2899 if ( !$this->pdfa_mode
) {
2900 $this->compress
= true;
2907 * Set flag to force sRGB_IEC61966-2.1 black scaled ICC color profile for the whole document.
2908 * @param boolean $mode If true force sRGB output intent.
2910 * @since 5.9.121 (2011-09-28)
2912 public function setSRGBmode($mode=false) {
2913 $this->force_srgb
= $mode ?
true : false;
2917 * Turn on/off Unicode mode for document information dictionary (meta tags).
2918 * This has effect only when unicode mode is set to false.
2919 * @param boolean $unicode if true set the meta information in Unicode
2920 * @since 5.9.027 (2010-12-01)
2923 public function setDocInfoUnicode($unicode=true) {
2924 $this->docinfounicode
= $unicode ?
true : false;
2928 * Defines the title of the document.
2929 * @param string $title The title.
2932 * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
2934 public function setTitle($title) {
2935 $this->title
= $title;
2939 * Defines the subject of the document.
2940 * @param string $subject The subject.
2943 * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
2945 public function setSubject($subject) {
2946 $this->subject
= $subject;
2950 * Defines the author of the document.
2951 * @param string $author The name of the author.
2954 * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
2956 public function setAuthor($author) {
2957 $this->author
= $author;
2961 * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
2962 * @param string $keywords The list of keywords.
2965 * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
2967 public function setKeywords($keywords) {
2968 $this->keywords
= $keywords;
2972 * Defines the creator of the document. This is typically the name of the application that generates the PDF.
2973 * @param string $creator The name of the creator.
2976 * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
2978 public function setCreator($creator) {
2979 $this->creator
= $creator;
2983 * Whether to allow local file path in image html tags, when prefixed with file://
2985 * @param bool $allowLocalFiles true, when local files should be allowed. Otherwise false.
2989 public function setAllowLocalFiles($allowLocalFiles) {
2990 $this->allowLocalFiles
= (bool) $allowLocalFiles;
2995 * Throw an exception or print an error message and die if the K_TCPDF_PARSER_THROW_EXCEPTION_ERROR constant is set to true.
2996 * @param string $msg The error message
3000 public function Error($msg) {
3001 // unset all class variables
3002 $this->_destroy(true);
3003 if (defined('K_TCPDF_THROW_EXCEPTION_ERROR') AND !K_TCPDF_THROW_EXCEPTION_ERROR
) {
3004 die('<strong>TCPDF ERROR: </strong>'.$msg);
3006 throw new Exception('TCPDF ERROR: '.$msg);
3011 * This method begins the generation of the PDF document.
3012 * It is not necessary to call it explicitly because AddPage() does it automatically.
3013 * Note: no page is created by this method
3016 * @see AddPage(), Close()
3018 public function Open() {
3023 * Terminates the PDF document.
3024 * It is not necessary to call this method explicitly because Output() does it automatically.
3025 * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
3028 * @see Open(), Output()
3030 public function Close() {
3031 if ($this->state
== 3) {
3034 if ($this->page
== 0) {
3038 if ($this->tcpdflink
) {
3039 // save current graphic settings
3040 $gvars = $this->getGraphicVars();
3041 $this->setEqualColumns();
3042 $this->lastpage(true);
3043 $this->setAutoPageBreak(false);
3045 $this->y
= $this->h
- (1 / $this->k
);
3047 $this->_outSaveGraphicsState();
3048 $font = defined('PDF_FONT_NAME_MAIN')?PDF_FONT_NAME_MAIN
:'helvetica';
3049 $this->setFont($font, '', 1);
3050 $this->setTextRenderingMode(0, false, false);
3051 $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";
3052 $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
3053 $this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
3054 $this->_outRestoreGraphicsState();
3055 // restore graphic settings
3056 $this->setGraphicVars($gvars);
3062 // unset all class variables (except critical ones)
3063 $this->_destroy(false);
3067 * Move pointer at the specified document page and update page dimensions.
3068 * @param int $pnum page number (1 ... numpages)
3069 * @param boolean $resetmargins if true reset left, right, top margins and Y position.
3071 * @since 2.1.000 (2008-01-07)
3072 * @see getPage(), lastpage(), getNumPages()
3074 public function setPage($pnum, $resetmargins=false) {
3075 if (($pnum == $this->page
) AND ($this->state
== 2)) {
3078 if (($pnum > 0) AND ($pnum <= $this->numpages
)) {
3080 // save current graphic settings
3081 //$gvars = $this->getGraphicVars();
3082 $oldpage = $this->page
;
3083 $this->page
= $pnum;
3084 $this->wPt
= $this->pagedim
[$this->page
]['w'];
3085 $this->hPt
= $this->pagedim
[$this->page
]['h'];
3086 $this->w
= $this->pagedim
[$this->page
]['wk'];
3087 $this->h
= $this->pagedim
[$this->page
]['hk'];
3088 $this->tMargin
= $this->pagedim
[$this->page
]['tm'];
3089 $this->bMargin
= $this->pagedim
[$this->page
]['bm'];
3090 $this->original_lMargin
= $this->pagedim
[$this->page
]['olm'];
3091 $this->original_rMargin
= $this->pagedim
[$this->page
]['orm'];
3092 $this->AutoPageBreak
= $this->pagedim
[$this->page
]['pb'];
3093 $this->CurOrientation
= $this->pagedim
[$this->page
]['or'];
3094 $this->setAutoPageBreak($this->AutoPageBreak
, $this->bMargin
);
3095 // restore graphic settings
3096 //$this->setGraphicVars($gvars);
3097 if ($resetmargins) {
3098 $this->lMargin
= $this->pagedim
[$this->page
]['olm'];
3099 $this->rMargin
= $this->pagedim
[$this->page
]['orm'];
3100 $this->setY($this->tMargin
);
3102 // account for booklet mode
3103 if ($this->pagedim
[$this->page
]['olm'] != $this->pagedim
[$oldpage]['olm']) {
3104 $deltam = $this->pagedim
[$this->page
]['olm'] - $this->pagedim
[$this->page
]['orm'];
3105 $this->lMargin +
= $deltam;
3106 $this->rMargin
-= $deltam;
3110 $this->Error('Wrong page number on setPage() function: '.$pnum);
3115 * Reset pointer to the last document page.
3116 * @param boolean $resetmargins if true reset left, right, top margins and Y position.
3118 * @since 2.0.000 (2008-01-04)
3119 * @see setPage(), getPage(), getNumPages()
3121 public function lastPage($resetmargins=false) {
3122 $this->setPage($this->getNumPages(), $resetmargins);
3126 * Get current document page number.
3127 * @return int page number
3129 * @since 2.1.000 (2008-01-07)
3130 * @see setPage(), lastpage(), getNumPages()
3132 public function getPage() {
3137 * Get the total number of insered pages.
3138 * @return int number of pages
3140 * @since 2.1.000 (2008-01-07)
3141 * @see setPage(), getPage(), lastpage()
3143 public function getNumPages() {
3144 return $this->numpages
;
3148 * Adds a new TOC (Table Of Content) page to the document.
3149 * @param string $orientation page orientation.
3150 * @param mixed $format 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().
3151 * @param boolean $keepmargins if true overwrites the default page margins with the current margins
3153 * @since 5.0.001 (2010-05-06)
3154 * @see AddPage(), startPage(), endPage(), endTOCPage()
3156 public function addTOCPage($orientation='', $format='', $keepmargins=false) {
3157 $this->AddPage($orientation, $format, $keepmargins, true);
3161 * Terminate the current TOC (Table Of Content) page
3163 * @since 5.0.001 (2010-05-06)
3164 * @see AddPage(), startPage(), endPage(), addTOCPage()
3166 public function endTOCPage() {
3167 $this->endPage(true);
3171 * 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).
3172 * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
3173 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3174 * @param mixed $format 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().
3175 * @param boolean $keepmargins if true overwrites the default page margins with the current margins
3176 * @param boolean $tocpage if true set the tocpage state to true (the added page will be used to display Table Of Content).
3179 * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3181 public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
3182 if ($this->inxobj
) {
3183 // we are inside an XObject template
3186 if (!isset($this->original_lMargin
) OR $keepmargins) {
3187 $this->original_lMargin
= $this->lMargin
;
3189 if (!isset($this->original_rMargin
) OR $keepmargins) {
3190 $this->original_rMargin
= $this->rMargin
;
3192 // terminate previous page
3195 $this->startPage($orientation, $format, $tocpage);
3199 * Terminate the current page
3200 * @param boolean $tocpage if true set the tocpage state to false (end the page used to display Table Of Content).
3202 * @since 4.2.010 (2008-11-14)
3203 * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
3205 public function endPage($tocpage=false) {
3206 // check if page is already closed
3207 if (($this->page
== 0) OR ($this->numpages
> $this->page
) OR (!$this->pageopen
[$this->page
])) {
3210 // print page footer
3214 // mark page as closed
3215 $this->pageopen
[$this->page
] = false;
3217 $this->tocpage
= false;
3222 * Starts a new page to the document. The page must be closed using the endPage() function.
3223 * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
3224 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
3225 * @param mixed $format 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().
3226 * @param boolean $tocpage if true the page is designated to contain the Table-Of-Content.
3227 * @since 4.2.010 (2008-11-14)
3228 * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
3231 public function startPage($orientation='', $format='', $tocpage=false) {
3233 $this->tocpage
= true;
3235 // move page numbers of documents to be attached
3236 if ($this->tocpage
) {
3237 // move reference to unexistent pages (used for page attachments)
3239 $tmpoutlines = $this->outlines
;
3240 foreach ($tmpoutlines as $key => $outline) {
3241 if (!$outline['f'] AND ($outline['p'] > $this->numpages
)) {
3242 $this->outlines
[$key]['p'] = ($outline['p'] +
1);
3246 $tmpdests = $this->dests
;
3247 foreach ($tmpdests as $key => $dest) {
3248 if (!$dest['f'] AND ($dest['p'] > $this->numpages
)) {
3249 $this->dests
[$key]['p'] = ($dest['p'] +
1);
3253 $tmplinks = $this->links
;
3254 foreach ($tmplinks as $key => $link) {
3255 if (!$link['f'] AND ($link['p'] > $this->numpages
)) {
3256 $this->links
[$key]['p'] = ($link['p'] +
1);
3260 if ($this->numpages
> $this->page
) {
3261 // this page has been already added
3262 $this->setPage($this->page +
1);
3263 $this->setY($this->tMargin
);
3267 if ($this->state
== 0) {
3271 $this->swapMargins($this->booklet
);
3272 // save current graphic settings
3273 $gvars = $this->getGraphicVars();
3275 $this->_beginpage($orientation, $format);
3276 // mark page as open
3277 $this->pageopen
[$this->page
] = true;
3278 // restore graphic settings
3279 $this->setGraphicVars($gvars);
3281 $this->setPageMark();
3282 // print page header
3284 // restore graphic settings
3285 $this->setGraphicVars($gvars);
3287 $this->setPageMark();
3288 // print table header (if any)
3289 $this->setTableHeader();
3290 // set mark for empty page check
3291 $this->emptypagemrk
[$this->page
]= $this->pagelen
[$this->page
];
3295 * Set start-writing mark on current page stream used to put borders and fills.
3296 * Borders and fills are always created after content and inserted on the position marked by this method.
3297 * This function must be called after calling Image() function for a background image.
3298 * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
3300 * @since 4.0.016 (2008-07-30)
3302 public function setPageMark() {
3303 $this->intmrk
[$this->page
] = $this->pagelen
[$this->page
];
3304 $this->bordermrk
[$this->page
] = $this->intmrk
[$this->page
];
3305 $this->setContentMark();
3309 * Set start-writing mark on selected page.
3310 * Borders and fills are always created after content and inserted on the position marked by this method.
3311 * @param int $page page number (default is the current page)
3313 * @since 4.6.021 (2009-07-20)
3315 protected function setContentMark($page=0) {
3317 $page = $this->page
;
3319 if (isset($this->footerlen
[$page])) {
3320 $this->cntmrk
[$page] = $this->pagelen
[$page] - $this->footerlen
[$page];
3322 $this->cntmrk
[$page] = $this->pagelen
[$page];
3328 * @param string $ln header image logo
3329 * @param int $lw header image logo width in mm
3330 * @param string $ht string to print as title on document header
3331 * @param string $hs string to print on document header
3332 * @param int[] $tc RGB array color for text.
3333 * @param int[] $lc RGB array color for line.
3336 public function setHeaderData($ln='', $lw=0, $ht='', $hs='', $tc=array(0,0,0), $lc=array(0,0,0)) {
3337 $this->header_logo
= $ln;
3338 $this->header_logo_width
= $lw;
3339 $this->header_title
= $ht;
3340 $this->header_string
= $hs;
3341 $this->header_text_color
= $tc;
3342 $this->header_line_color
= $lc;
3347 * @param int[] $tc RGB array color for text.
3348 * @param int[] $lc RGB array color for line.
3351 public function setFooterData($tc=array(0,0,0), $lc=array(0,0,0)) {
3352 $this->footer_text_color
= $tc;
3353 $this->footer_line_color
= $lc;
3357 * Returns header data:
3358 * <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>
3359 * @return array<string,mixed>
3361 * @since 4.0.012 (2008-07-24)
3363 public function getHeaderData() {
3365 $ret['logo'] = $this->header_logo
;
3366 $ret['logo_width'] = $this->header_logo_width
;
3367 $ret['title'] = $this->header_title
;
3368 $ret['string'] = $this->header_string
;
3369 $ret['text_color'] = $this->header_text_color
;
3370 $ret['line_color'] = $this->header_line_color
;
3375 * Set header margin.
3376 * (minimum distance between header and top page margin)
3377 * @param float $hm distance in user units
3380 public function setHeaderMargin($hm=10) {
3381 $this->header_margin
= $hm;
3385 * Returns header margin in user units.
3387 * @since 4.0.012 (2008-07-24)
3390 public function getHeaderMargin() {
3391 return $this->header_margin
;
3395 * Set footer margin.
3396 * (minimum distance between footer and bottom page margin)
3397 * @param float $fm distance in user units
3400 public function setFooterMargin($fm=10) {
3401 $this->footer_margin
= $fm;
3405 * Returns footer margin in user units.
3407 * @since 4.0.012 (2008-07-24)
3410 public function getFooterMargin() {
3411 return $this->footer_margin
;
3414 * Set a flag to print page header.
3415 * @param boolean $val set to true to print the page header (default), false otherwise.
3418 public function setPrintHeader($val=true) {
3419 $this->print_header
= $val ?
true : false;
3423 * Set a flag to print page footer.
3424 * @param boolean $val set to true to print the page footer (default), false otherwise.
3427 public function setPrintFooter($val=true) {
3428 $this->print_footer
= $val ?
true : false;
3432 * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
3436 public function getImageRBX() {
3437 return $this->img_rb_x
;
3441 * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
3445 public function getImageRBY() {
3446 return $this->img_rb_y
;
3450 * Reset the xobject template used by Header() method.
3453 public function resetHeaderTemplate() {
3454 $this->header_xobjid
= false;
3458 * Set a flag to automatically reset the xobject template used by Header() method at each page.
3459 * @param boolean $val set to true to reset Header xobject template at each page, false otherwise.
3462 public function setHeaderTemplateAutoreset($val=true) {
3463 $this->header_xobj_autoreset
= $val ?
true : false;
3467 * This method is used to render the page header.
3468 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3471 public function Header() {
3472 if ($this->header_xobjid
=== false) {
3473 // start a new XObject Template
3474 $this->header_xobjid
= $this->startTemplate($this->w
, $this->tMargin
);
3475 $headerfont = $this->getHeaderFont();
3476 $headerdata = $this->getHeaderData();
3477 $this->y
= $this->header_margin
;
3479 $this->x
= $this->w
- $this->original_rMargin
;
3481 $this->x
= $this->original_lMargin
;
3483 if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE
)) {
3484 $imgtype = TCPDF_IMAGES
::getImageFileType(K_PATH_IMAGES
.$headerdata['logo']);
3485 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
3486 $this->ImageEps(K_PATH_IMAGES
.$headerdata['logo'], '', '', $headerdata['logo_width']);
3487 } elseif ($imgtype == 'svg') {
3488 $this->ImageSVG(K_PATH_IMAGES
.$headerdata['logo'], '', '', $headerdata['logo_width']);
3490 $this->Image(K_PATH_IMAGES
.$headerdata['logo'], '', '', $headerdata['logo_width']);
3492 $imgy = $this->getImageRBY();
3496 $cell_height = $this->getCellHeight($headerfont[2] / $this->k
);
3497 // set starting margin for text data cell
3498 if ($this->getRTL()) {
3499 $header_x = $this->original_rMargin +
($headerdata['logo_width'] * 1.1);
3501 $header_x = $this->original_lMargin +
($headerdata['logo_width'] * 1.1);
3503 $cw = $this->w
- $this->original_lMargin
- $this->original_rMargin
- ($headerdata['logo_width'] * 1.1);
3504 $this->setTextColorArray($this->header_text_color
);
3506 $this->setFont($headerfont[0], 'B', $headerfont[2] +
1);
3507 $this->setX($header_x);
3508 $this->Cell($cw, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
3510 $this->setFont($headerfont[0], $headerfont[1], $headerfont[2]);
3511 $this->setX($header_x);
3512 $this->MultiCell($cw, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false, true, 0, 'T', false);
3513 // print an ending header line
3514 $this->setLineStyle(array('width' => 0.85 / $this->k
, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $headerdata['line_color']));
3515 $this->setY((2.835 / $this->k
) +
max($imgy, $this->y
));
3517 $this->setX($this->original_rMargin
);
3519 $this->setX($this->original_lMargin
);
3521 $this->Cell(($this->w
- $this->original_lMargin
- $this->original_rMargin
), 0, '', 'T', 0, 'C');
3522 $this->endTemplate();
3524 // print header template
3527 if (!$this->header_xobj_autoreset
AND $this->booklet
AND (($this->page %
2) == 0)) {
3528 // adjust margins for booklet mode
3529 $dx = ($this->original_lMargin
- $this->original_rMargin
);
3532 $x = $this->w +
$dx;
3536 $this->printTemplate($this->header_xobjid
, $x, 0, 0, 0, '', '', false);
3537 if ($this->header_xobj_autoreset
) {
3538 // reset header xobject template at each page
3539 $this->header_xobjid
= false;
3544 * This method is used to render the page footer.
3545 * It is automatically called by AddPage() and could be overwritten in your own inherited class.
3548 public function Footer() {
3550 $this->setTextColorArray($this->footer_text_color
);
3551 //set style for cell border
3552 $line_width = (0.85 / $this->k
);
3553 $this->setLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $this->footer_line_color
));
3554 //print document barcode
3555 $barcode = $this->getBarcode();
3556 if (!empty($barcode)) {
3557 $this->Ln($line_width);
3558 $barcode_width = round(($this->w
- $this->original_lMargin
- $this->original_rMargin
) / 3);
3560 'position' => $this->rtl?
'R':'L',
3561 'align' => $this->rtl?
'R':'L',
3564 'cellfitalign' => '',
3567 'fgcolor' => array(0,0,0),
3571 $this->write1DBarcode($barcode, 'C128', '', $cur_y +
$line_width, '', (($this->footer_margin
/ 3) - $line_width), 0.3, $style, '');
3573 $w_page = isset($this->l
['w_page']) ?
$this->l
['w_page'].' ' : '';
3574 if (empty($this->pagegroups
)) {
3575 $pagenumtxt = $w_page.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
3577 $pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3579 $this->setY($cur_y);
3581 if ($this->getRTL()) {
3582 $this->setX($this->original_rMargin
);
3583 $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3585 $this->setX($this->original_lMargin
);
3586 $this->Cell(0, 0, $this->getAliasRightShift().$pagenumtxt, 'T', 0, 'R');
3591 * This method is used to render the page header.
3593 * @since 4.0.012 (2008-07-24)
3595 protected function setHeader() {
3596 if (!$this->print_header
OR ($this->state
!= 2)) {
3599 $this->InHeader
= true;
3600 $this->setGraphicVars($this->default_graphic_vars
);
3601 $temp_thead = $this->thead
;
3602 $temp_theadMargins = $this->theadMargins
;
3603 $lasth = $this->lasth
;
3604 $newline = $this->newline
;
3605 $this->_outSaveGraphicsState();
3606 $this->rMargin
= $this->original_rMargin
;
3607 $this->lMargin
= $this->original_lMargin
;
3608 $this->setCellPadding(0);
3609 //set current position
3611 $this->setXY($this->original_rMargin
, $this->header_margin
);
3613 $this->setXY($this->original_lMargin
, $this->header_margin
);
3615 $this->setFont($this->header_font
[0], $this->header_font
[1], $this->header_font
[2]);
3619 $this->setXY($this->original_rMargin
, $this->tMargin
);
3621 $this->setXY($this->original_lMargin
, $this->tMargin
);
3623 $this->_outRestoreGraphicsState();
3624 $this->lasth
= $lasth;
3625 $this->thead
= $temp_thead;
3626 $this->theadMargins
= $temp_theadMargins;
3627 $this->newline
= $newline;
3628 $this->InHeader
= false;
3632 * This method is used to render the page footer.
3634 * @since 4.0.012 (2008-07-24)
3636 protected function setFooter() {
3637 if ($this->state
!= 2) {
3640 $this->InFooter
= true;
3641 // save current graphic settings
3642 $gvars = $this->getGraphicVars();
3644 $this->footerpos
[$this->page
] = $this->pagelen
[$this->page
];
3646 if ($this->print_footer
) {
3647 $this->setGraphicVars($this->default_graphic_vars
);
3648 $this->current_column
= 0;
3649 $this->num_columns
= 1;
3650 $temp_thead = $this->thead
;
3651 $temp_theadMargins = $this->theadMargins
;
3652 $lasth = $this->lasth
;
3653 $this->_outSaveGraphicsState();
3654 $this->rMargin
= $this->original_rMargin
;
3655 $this->lMargin
= $this->original_lMargin
;
3656 $this->setCellPadding(0);
3657 //set current position
3658 $footer_y = $this->h
- $this->footer_margin
;
3660 $this->setXY($this->original_rMargin
, $footer_y);
3662 $this->setXY($this->original_lMargin
, $footer_y);
3664 $this->setFont($this->footer_font
[0], $this->footer_font
[1], $this->footer_font
[2]);
3668 $this->setXY($this->original_rMargin
, $this->tMargin
);
3670 $this->setXY($this->original_lMargin
, $this->tMargin
);
3672 $this->_outRestoreGraphicsState();
3673 $this->lasth
= $lasth;
3674 $this->thead
= $temp_thead;
3675 $this->theadMargins
= $temp_theadMargins;
3677 // restore graphic settings
3678 $this->setGraphicVars($gvars);
3679 $this->current_column
= $gvars['current_column'];
3680 $this->num_columns
= $gvars['num_columns'];
3681 // calculate footer length
3682 $this->footerlen
[$this->page
] = $this->pagelen
[$this->page
] - $this->footerpos
[$this->page
] +
1;
3683 $this->InFooter
= false;
3687 * Check if we are on the page body (excluding page header and footer).
3688 * @return bool true if we are not in page header nor in page footer, false otherwise.
3690 * @since 5.9.091 (2011-06-15)
3692 protected function inPageBody() {
3693 return (($this->InHeader
=== false) AND ($this->InFooter
=== false));
3697 * This method is used to render the table header on new page (if any).
3699 * @since 4.5.030 (2009-03-25)
3701 protected function setTableHeader() {
3702 if ($this->num_columns
> 1) {
3703 // multi column mode
3706 if (isset($this->theadMargins
['top'])) {
3707 // restore the original top-margin
3708 $this->tMargin
= $this->theadMargins
['top'];
3709 $this->pagedim
[$this->page
]['tm'] = $this->tMargin
;
3710 $this->y
= $this->tMargin
;
3712 if (!TCPDF_STATIC
::empty_string($this->thead
) AND (!$this->inthead
)) {
3714 $prev_lMargin = $this->lMargin
;
3715 $prev_rMargin = $this->rMargin
;
3716 $prev_cell_padding = $this->cell_padding
;
3717 $this->lMargin
= $this->theadMargins
['lmargin'] +
($this->pagedim
[$this->page
]['olm'] - $this->pagedim
[$this->theadMargins
['page']]['olm']);
3718 $this->rMargin
= $this->theadMargins
['rmargin'] +
($this->pagedim
[$this->page
]['orm'] - $this->pagedim
[$this->theadMargins
['page']]['orm']);
3719 $this->cell_padding
= $this->theadMargins
['cell_padding'];
3721 $this->x
= $this->w
- $this->rMargin
;
3723 $this->x
= $this->lMargin
;
3725 // account for special "cell" mode
3726 if ($this->theadMargins
['cell']) {
3728 $this->x
-= $this->cell_padding
['R'];
3730 $this->x +
= $this->cell_padding
['L'];
3733 $gvars = $this->getGraphicVars();
3734 if (!empty($this->theadMargins
['gvars'])) {
3735 // set the correct graphic style
3736 $this->setGraphicVars($this->theadMargins
['gvars']);
3737 $this->rMargin
= $gvars['rMargin'];
3738 $this->lMargin
= $gvars['lMargin'];
3740 // print table header
3741 $this->writeHTML($this->thead
, false, false, false, false, '');
3742 $this->setGraphicVars($gvars);
3743 // set new top margin to skip the table headers
3744 if (!isset($this->theadMargins
['top'])) {
3745 $this->theadMargins
['top'] = $this->tMargin
;
3747 // store end of header position
3748 if (!isset($this->columns
[0]['th'])) {
3749 $this->columns
[0]['th'] = array();
3751 $this->columns
[0]['th']['\''.$this->page
.'\''] = $this->y
;
3752 $this->tMargin
= $this->y
;
3753 $this->pagedim
[$this->page
]['tm'] = $this->tMargin
;
3755 $this->lMargin
= $prev_lMargin;
3756 $this->rMargin
= $prev_rMargin;
3757 $this->cell_padding
= $prev_cell_padding;
3762 * Returns the current page number.
3763 * @return int page number
3766 * @see getAliasNbPages()
3768 public function PageNo() {
3773 * Returns the array of spot colors.
3774 * @return array Spot colors array.
3776 * @since 6.0.038 (2013-09-30)
3778 public function getAllSpotColors() {
3779 return $this->spot_colors
;
3783 * Defines a new spot color.
3784 * It can be expressed in RGB components or gray scale.
3785 * The method can be called before the first page is created and the value is retained from page to page.
3786 * @param string $name Full name of the spot color.
3787 * @param float $c Cyan color for CMYK. Value between 0 and 100.
3788 * @param float $m Magenta color for CMYK. Value between 0 and 100.
3789 * @param float $y Yellow color for CMYK. Value between 0 and 100.
3790 * @param float $k Key (Black) color for CMYK. Value between 0 and 100.
3792 * @since 4.0.024 (2008-09-12)
3793 * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3795 public function AddSpotColor($name, $c, $m, $y, $k) {
3796 if (!isset($this->spot_colors
[$name])) {
3797 $i = (1 +
count($this->spot_colors
));
3798 $this->spot_colors
[$name] = array('C' => $c, 'M' => $m, 'Y' => $y, 'K' => $k, 'name' => $name, 'i' => $i);
3803 * Set the spot color for the specified type ('draw', 'fill', 'text').
3804 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3805 * @param string $name Name of the spot color.
3806 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3807 * @return string PDF color command.
3809 * @since 5.9.125 (2011-10-03)
3811 public function setSpotColor($type, $name, $tint=100) {
3812 $spotcolor = TCPDF_COLORS
::getSpotColor($name, $this->spot_colors
);
3813 if ($spotcolor === false) {
3814 $this->Error('Undefined spot color: '.$name.', you must add it using the AddSpotColor() method.');
3816 $tint = (max(0, min(100, $tint)) / 100);
3817 $pdfcolor = sprintf('/CS%d ', $this->spot_colors
[$name]['i']);
3820 $pdfcolor .= sprintf('CS %F SCN', $tint);
3821 $this->DrawColor
= $pdfcolor;
3822 $this->strokecolor
= $spotcolor;
3826 $pdfcolor .= sprintf('cs %F scn', $tint);
3827 $this->FillColor
= $pdfcolor;
3828 $this->bgcolor
= $spotcolor;
3832 $pdfcolor .= sprintf('cs %F scn', $tint);
3833 $this->TextColor
= $pdfcolor;
3834 $this->fgcolor
= $spotcolor;
3838 $this->ColorFlag
= ($this->FillColor
!= $this->TextColor
);
3839 if ($this->state
== 2) {
3840 $this->_out($pdfcolor);
3842 if ($this->inxobj
) {
3843 // we are inside an XObject template
3844 $this->xobjects
[$this->xobjid
]['spot_colors'][$name] = $this->spot_colors
[$name];
3850 * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
3851 * @param string $name Name of the spot color.
3852 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3854 * @since 4.0.024 (2008-09-12)
3855 * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
3857 public function setDrawSpotColor($name, $tint=100) {
3858 $this->setSpotColor('draw', $name, $tint);
3862 * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
3863 * @param string $name Name of the spot color.
3864 * @param float $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3866 * @since 4.0.024 (2008-09-12)
3867 * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
3869 public function setFillSpotColor($name, $tint=100) {
3870 $this->setSpotColor('fill', $name, $tint);
3874 * Defines the spot color used for text.
3875 * @param string $name Name of the spot color.
3876 * @param int $tint Intensity of the color (from 0 to 100 ; 100 = full intensity by default).
3878 * @since 4.0.024 (2008-09-12)
3879 * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
3881 public function setTextSpotColor($name, $tint=100) {
3882 $this->setSpotColor('text', $name, $tint);
3886 * Set the color array for the specified type ('draw', 'fill', 'text').
3887 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3888 * The method can be called before the first page is created and the value is retained from page to page.
3889 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3890 * @param array $color Array of colors (1=gray, 3=RGB, 4=CMYK or 5=spotcolor=CMYK+name values).
3891 * @param boolean $ret If true do not send the PDF command.
3892 * @return string The PDF command or empty string.
3894 * @since 3.1.000 (2008-06-11)
3896 public function setColorArray($type, $color, $ret=false) {
3897 if (is_array($color)) {
3898 $color = array_values($color);
3899 // component: grey, RGB red or CMYK cyan
3900 $c = isset($color[0]) ?
$color[0] : -1;
3901 // component: RGB green or CMYK magenta
3902 $m = isset($color[1]) ?
$color[1] : -1;
3903 // component: RGB blue or CMYK yellow
3904 $y = isset($color[2]) ?
$color[2] : -1;
3905 // component: CMYK black
3906 $k = isset($color[3]) ?
$color[3] : -1;
3908 $name = isset($color[4]) ?
$color[4] : '';
3910 return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3917 * Defines the color used for all drawing operations (lines, rectangles and cell borders).
3918 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3919 * The method can be called before the first page is created and the value is retained from page to page.
3920 * @param array $color Array of colors (1, 3 or 4 values).
3921 * @param boolean $ret If true do not send the PDF command.
3922 * @return string the PDF command
3924 * @since 3.1.000 (2008-06-11)
3925 * @see SetDrawColor()
3927 public function setDrawColorArray($color, $ret=false) {
3928 return $this->setColorArray('draw', $color, $ret);
3932 * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
3933 * It can be expressed in RGB, CMYK or GRAY SCALE components.
3934 * The method can be called before the first page is created and the value is retained from page to page.
3935 * @param array $color Array of colors (1, 3 or 4 values).
3936 * @param boolean $ret If true do not send the PDF command.
3938 * @since 3.1.000 (2008-6-11)
3939 * @see SetFillColor()
3941 public function setFillColorArray($color, $ret=false) {
3942 return $this->setColorArray('fill', $color, $ret);
3946 * Defines the color used for text. It can be expressed in RGB components or gray scale.
3947 * The method can be called before the first page is created and the value is retained from page to page.
3948 * @param array $color Array of colors (1, 3 or 4 values).
3949 * @param boolean $ret If true do not send the PDF command.
3951 * @since 3.1.000 (2008-6-11)
3952 * @see SetFillColor()
3954 public function setTextColorArray($color, $ret=false) {
3955 return $this->setColorArray('text', $color, $ret);
3959 * Defines the color used by the specified type ('draw', 'fill', 'text').
3960 * @param string $type Type of object affected by this color: ('draw', 'fill', 'text').
3961 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
3962 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
3963 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
3964 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
3965 * @param boolean $ret If true do not send the command.
3966 * @param string $name spot color name (if any)
3967 * @return string The PDF command or empty string.
3969 * @since 5.9.125 (2011-10-03)
3971 public function setColor($type, $col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
3972 // set default values
3973 if (!is_numeric($col1)) {
3976 if (!is_numeric($col2)) {
3979 if (!is_numeric($col3)) {
3982 if (!is_numeric($col4)) {
3985 // set color by case
3987 if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3989 $col1 = max(0, min(255, $col1));
3990 $intcolor = array('G' => $col1);
3991 $pdfcolor = sprintf('%F ', ($col1 / 255));
3993 } elseif ($col4 == -1) {
3995 $col1 = max(0, min(255, $col1));
3996 $col2 = max(0, min(255, $col2));
3997 $col3 = max(0, min(255, $col3));
3998 $intcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
3999 $pdfcolor = sprintf('%F %F %F ', ($col1 / 255), ($col2 / 255), ($col3 / 255));
4002 $col1 = max(0, min(100, $col1));
4003 $col2 = max(0, min(100, $col2));
4004 $col3 = max(0, min(100, $col3));
4005 $col4 = max(0, min(100, $col4));
4008 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
4009 $pdfcolor = sprintf('%F %F %F %F ', ($col1 / 100), ($col2 / 100), ($col3 / 100), ($col4 / 100));
4013 $intcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4, 'name' => $name);
4014 $this->AddSpotColor($name, $col1, $col2, $col3, $col4);
4015 $pdfcolor = $this->setSpotColor($type, $name, 100);
4020 $pdfcolor .= strtoupper($suffix);
4021 $this->DrawColor
= $pdfcolor;
4022 $this->strokecolor
= $intcolor;
4026 $pdfcolor .= $suffix;
4027 $this->FillColor
= $pdfcolor;
4028 $this->bgcolor
= $intcolor;
4032 $pdfcolor .= $suffix;
4033 $this->TextColor
= $pdfcolor;
4034 $this->fgcolor
= $intcolor;
4038 $this->ColorFlag
= ($this->FillColor
!= $this->TextColor
);
4039 if (($type != 'text') AND ($this->state
== 2) AND $type !== 0) {
4041 $this->_out($pdfcolor);
4049 * 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.
4050 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4051 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4052 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4053 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4054 * @param boolean $ret If true do not send the command.
4055 * @param string $name spot color name (if any)
4056 * @return string the PDF command
4059 * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
4061 public function setDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4062 return $this->setColor('draw', $col1, $col2, $col3, $col4, $ret, $name);
4066 * 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.
4067 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4068 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4069 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4070 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4071 * @param boolean $ret If true do not send the command.
4072 * @param string $name Spot color name (if any).
4073 * @return string The PDF command.
4076 * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
4078 public function setFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4079 return $this->setColor('fill', $col1, $col2, $col3, $col4, $ret, $name);
4083 * 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.
4084 * @param float $col1 GRAY level for single color, or Red color for RGB (0-255), or CYAN color for CMYK (0-100).
4085 * @param float $col2 GREEN color for RGB (0-255), or MAGENTA color for CMYK (0-100).
4086 * @param float $col3 BLUE color for RGB (0-255), or YELLOW color for CMYK (0-100).
4087 * @param float $col4 KEY (BLACK) color for CMYK (0-100).
4088 * @param boolean $ret If true do not send the command.
4089 * @param string $name Spot color name (if any).
4090 * @return string Empty string.
4093 * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
4095 public function setTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false, $name='') {
4096 return $this->setColor('text', $col1, $col2, $col3, $col4, $ret, $name);
4100 * Returns the length of a string in user unit. A font must be selected.<br>
4101 * @param string $s The string whose length is to be computed
4102 * @param string $fontname 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.
4103 * @param string $fontstyle 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.
4104 * @param float $fontsize Font size in points. The default value is the current size.
4105 * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
4106 * @return float[]|float total string length or array of characted widths
4107 * @phpstan-return ($getarray is true ? float[] : float) total string length or array of characted widths
4108 * @author Nicola Asuni
4112 public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4113 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);
4117 * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
4118 * @param array $sa The array of chars whose total length is to be computed
4119 * @param string $fontname 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.
4120 * @param string $fontstyle 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.
4121 * @param float $fontsize Font size in points. The default value is the current size.
4122 * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
4123 * @return float[]|float total string length or array of characted widths
4124 * @phpstan-return ($getarray is true ? float[] : float) total string length or array of characted widths
4125 * @author Nicola Asuni
4127 * @since 2.4.000 (2008-03-06)
4129 public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
4130 // store current values
4131 if (!TCPDF_STATIC
::empty_string($fontname)) {
4132 $prev_FontFamily = $this->FontFamily
;
4133 $prev_FontStyle = $this->FontStyle
;
4134 $prev_FontSizePt = $this->FontSizePt
;
4135 $this->setFont($fontname, $fontstyle, $fontsize, '', 'default', false);
4137 // convert UTF-8 array to Latin1 if required
4138 if ($this->isunicode
AND (!$this->isUnicodeFont())) {
4139 $sa = TCPDF_FONTS
::UTF8ArrToLatin1Arr($sa);
4141 $w = 0; // total width
4142 $wa = array(); // array of characters widths
4143 foreach ($sa as $ck => $char) {
4145 $cw = $this->GetCharWidth($char, isset($sa[($ck +
1)]));
4149 // restore previous values
4150 if (!TCPDF_STATIC
::empty_string($fontname)) {
4151 $this->setFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4160 * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking).
4161 * @param int $char The char code whose length is to be returned
4162 * @param boolean $notlast If false ignore the font-spacing.
4163 * @return float char width
4164 * @author Nicola Asuni
4166 * @since 2.4.000 (2008-03-06)
4168 public function GetCharWidth($char, $notlast=true) {
4170 $chw = $this->getRawCharWidth($char);
4171 if (($this->font_spacing
< 0) OR (($this->font_spacing
> 0) AND $notlast)) {
4172 // increase/decrease font spacing
4173 $chw +
= $this->font_spacing
;
4175 if ($this->font_stretching
!= 100) {
4176 // fixed stretching mode
4177 $chw *= ($this->font_stretching
/ 100);
4183 * Returns the length of the char in user unit for the current font.
4184 * @param int $char The char code whose length is to be returned
4185 * @return float char width
4186 * @author Nicola Asuni
4188 * @since 5.9.000 (2010-09-28)
4190 public function getRawCharWidth($char) {
4192 // SHY character will not be printed
4195 if (isset($this->CurrentFont
['cw'][intval($char)])) {
4196 $w = $this->CurrentFont
['cw'][intval($char)];
4197 } elseif (isset($this->CurrentFont
['dw'])) {
4199 $w = $this->CurrentFont
['dw'];
4200 } elseif (isset($this->CurrentFont
['cw'][32])) {
4202 $w = $this->CurrentFont
['cw'][32];
4206 return $this->getAbsFontMeasure($w);
4210 * Returns the numbero of characters in a string.
4211 * @param string $s The input string.
4212 * @return int number of characters
4214 * @since 2.0.0001 (2008-01-07)
4216 public function GetNumChars($s) {
4217 if ($this->isUnicodeFont()) {
4218 return count(TCPDF_FONTS
::UTF8StringToArray($s, $this->isunicode
, $this->CurrentFont
));
4224 * Fill the list of available fonts ($this->fontlist).
4226 * @since 4.0.013 (2008-07-28)
4228 protected function getFontsList() {
4229 if (($fontsdir = opendir(TCPDF_FONTS
::_getfontpath())) !== false) {
4230 while (($file = readdir($fontsdir)) !== false) {
4231 if (substr($file, -4) == '.php') {
4232 array_push($this->fontlist
, strtolower(basename($file, '.php')));
4235 closedir($fontsdir);
4240 * Imports a TrueType, Type1, core, or CID0 font and makes it available.
4241 * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
4242 * 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.
4243 * @param string $family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
4244 * @param string $style 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>
4245 * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4246 * @return array|false array containing the font data, or false in case of error.
4247 * @param mixed $subset 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.
4250 * @see SetFont(), setFontSubsetting()
4252 public function AddFont($family, $style='', $fontfile='', $subset='default') {
4253 if ($subset === 'default') {
4254 $subset = $this->font_subsetting
;
4256 if ($this->pdfa_mode
) {
4259 if (TCPDF_STATIC
::empty_string($family)) {
4260 if (!TCPDF_STATIC
::empty_string($this->FontFamily
)) {
4261 $family = $this->FontFamily
;
4263 $this->Error('Empty font family');
4266 // move embedded styles on $style
4267 if (substr($family, -1) == 'I') {
4269 $family = substr($family, 0, -1);
4271 if (substr($family, -1) == 'B') {
4273 $family = substr($family, 0, -1);
4275 // normalize family name
4276 $family = strtolower($family);
4277 if ((!$this->isunicode
) AND ($family == 'arial')) {
4278 $family = 'helvetica';
4280 if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
4283 if ($this->pdfa_mode
AND (isset($this->CoreFonts
[$family]))) {
4284 // all fonts must be embedded
4285 $family = 'pdfa'.$family;
4287 $tempstyle = strtoupper($style === null ?
'' : $style);
4290 if (strpos($tempstyle, 'U') !== false) {
4291 $this->underline
= true;
4293 $this->underline
= false;
4295 // line-through (deleted)
4296 if (strpos($tempstyle, 'D') !== false) {
4297 $this->linethrough
= true;
4299 $this->linethrough
= false;
4302 if (strpos($tempstyle, 'O') !== false) {
4303 $this->overline
= true;
4305 $this->overline
= false;
4308 if (strpos($tempstyle, 'B') !== false) {
4312 if (strpos($tempstyle, 'I') !== false) {
4316 $fontkey = $family.$style;
4317 $font_style = $style.($this->underline ?
'U' : '').($this->linethrough ?
'D' : '').($this->overline ?
'O' : '');
4318 $fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
4319 // check if the font has been already added
4320 $fb = $this->getFontBuffer($fontkey);
4321 if ($fb !== false) {
4322 if ($this->inxobj
) {
4323 // we are inside an XObject template
4324 $this->xobjects
[$this->xobjid
]['fonts'][$fontkey] = $fb['i'];
4328 // get specified font directory (if any)
4330 if (!TCPDF_STATIC
::empty_string($fontfile)) {
4331 $fontdir = dirname($fontfile);
4332 if (TCPDF_STATIC
::empty_string($fontdir) OR ($fontdir == '.')) {
4338 // true when the font style variation is missing
4339 $missing_style = false;
4340 // search and include font file
4341 if (TCPDF_STATIC
::empty_string($fontfile) OR (!@TCPDF_STATIC
::file_exists($fontfile))) {
4342 // build a standard filenames for specified font
4343 $tmp_fontfile = str_replace(' ', '', $family).strtolower($style).'.php';
4344 $fontfile = TCPDF_FONTS
::getFontFullPath($tmp_fontfile, $fontdir);
4345 if (TCPDF_STATIC
::empty_string($fontfile)) {
4346 $missing_style = true;
4347 // try to remove the style part
4348 $tmp_fontfile = str_replace(' ', '', $family).'.php';
4349 $fontfile = TCPDF_FONTS
::getFontFullPath($tmp_fontfile, $fontdir);
4352 // include font file
4353 if (!TCPDF_STATIC
::empty_string($fontfile) AND (@TCPDF_STATIC
::file_exists($fontfile))) {
4372 $this->Error('Could not include font definition file: '.$family.'');
4374 // check font parameters
4375 if ((!isset($type)) OR (!isset($cw))) {
4376 $this->Error('The font definition file has a bad format: '.$fontfile.'');
4378 // SET default parameters
4379 if (!isset($file) OR TCPDF_STATIC
::empty_string($file)) {
4382 if (!isset($enc) OR TCPDF_STATIC
::empty_string($enc)) {
4385 if (!isset($cidinfo) OR TCPDF_STATIC
::empty_string($cidinfo)) {
4386 $cidinfo = array('Registry'=>'Adobe', 'Ordering'=>'Identity', 'Supplement'=>0);
4387 $cidinfo['uni2cid'] = array();
4389 if (!isset($ctg) OR TCPDF_STATIC
::empty_string($ctg)) {
4392 if (!isset($desc) OR TCPDF_STATIC
::empty_string($desc)) {
4395 if (!isset($up) OR TCPDF_STATIC
::empty_string($up)) {
4398 if (!isset($ut) OR TCPDF_STATIC
::empty_string($ut)) {
4401 if (!isset($cw) OR TCPDF_STATIC
::empty_string($cw)) {
4404 if (!isset($dw) OR TCPDF_STATIC
::empty_string($dw)) {
4405 // set default width
4406 if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
4407 $dw = $desc['MissingWidth'];
4408 } elseif (isset($cw[32])) {
4415 if ($type == 'core') {
4416 $name = $this->CoreFonts
[$fontkey];
4418 } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4420 } elseif ($type == 'TrueTypeUnicode') {
4421 $enc = 'Identity-H';
4422 } elseif ($type == 'cidfont0') {
4423 if ($this->pdfa_mode
) {
4424 $this->Error('All fonts must be embedded in PDF/A mode!');
4427 $this->Error('Unknow font type: '.$type.'');
4429 // set name if unset
4430 if (!isset($name) OR empty($name)) {
4433 // create artificial font style variations if missing (only works with non-embedded fonts)
4434 if (($type != 'core') AND $missing_style) {
4436 $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4437 $name .= $styles[$bistyle];
4439 if (strpos($bistyle, 'B') !== false) {
4440 if (isset($desc['StemV'])) {
4441 // from normal to bold
4442 $desc['StemV'] = round($desc['StemV'] * 1.75);
4445 $desc['StemV'] = 123;
4448 // artificial italic
4449 if (strpos($bistyle, 'I') !== false) {
4450 if (isset($desc['ItalicAngle'])) {
4451 $desc['ItalicAngle'] -= 11;
4453 $desc['ItalicAngle'] = -11;
4455 if (isset($desc['Flags'])) {
4456 $desc['Flags'] |
= 64; //bit 7
4458 $desc['Flags'] = 64;
4462 // check if the array of characters bounding boxes is defined
4463 if (!isset($cbbox)) {
4466 // initialize subsetchars
4467 $subsetchars = array_fill(0, 255, true);
4468 $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));
4469 if ($this->inxobj
) {
4470 // we are inside an XObject template
4471 $this->xobjects
[$this->xobjid
]['fonts'][$fontkey] = $this->numfonts
;
4473 if (isset($diff) AND (!empty($diff))) {
4474 //Search existing encodings
4476 $nb = count($this->diffs
);
4477 for ($i=1; $i <= $nb; ++
$i) {
4478 if ($this->diffs
[$i] == $diff) {
4485 $this->diffs
[$d] = $diff;
4487 $this->setFontSubBuffer($fontkey, 'diff', $d);
4489 if (!TCPDF_STATIC
::empty_string($file)) {
4490 if (!isset($this->FontFiles
[$file])) {
4491 if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
4492 $this->FontFiles
[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4493 } elseif ($type != 'core') {
4494 $this->FontFiles
[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
4497 // update fontkeys that are sharing this font file
4498 $this->FontFiles
[$file]['subset'] = ($this->FontFiles
[$file]['subset'] AND $subset);
4499 if (!in_array($fontkey, $this->FontFiles
[$file]['fontkeys'])) {
4500 $this->FontFiles
[$file]['fontkeys'][] = $fontkey;
4508 * Sets the font used to print character strings.
4509 * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
4510 * The method can be called before the first page is created and the font is retained from page to page.
4511 * If you just wish to change the current font size, it is simpler to call SetFontSize().
4512 * 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 />
4513 * @param string $family 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.
4514 * @param string $style 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.
4515 * @param float|null $size 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
4516 * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
4517 * @param mixed $subset 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.
4518 * @param boolean $out if true output the font size command, otherwise only set the font properties.
4519 * @author Nicola Asuni
4522 * @see AddFont(), SetFontSize()
4524 public function setFont($family, $style='', $size=null, $fontfile='', $subset='default', $out=true) {
4525 //Select a font; size given in points
4526 if ($size === null) {
4527 $size = $this->FontSizePt
;
4532 // try to add font (if not already added)
4533 $fontdata = $this->AddFont($family, $style, $fontfile, $subset);
4534 $this->FontFamily
= $fontdata['family'];
4535 $this->FontStyle
= $fontdata['style'];
4536 if (isset($this->CurrentFont
['fontkey']) AND isset($this->CurrentFont
['subsetchars'])) {
4537 // save subset chars of the previous font
4538 $this->setFontSubBuffer($this->CurrentFont
['fontkey'], 'subsetchars', $this->CurrentFont
['subsetchars']);
4540 $this->CurrentFont
= $this->getFontBuffer($fontdata['fontkey']);
4541 $this->setFontSize($size, $out);
4545 * Defines the size of the current font.
4546 * @param float $size The font size in points.
4547 * @param boolean $out if true output the font size command, otherwise only set the font properties.
4552 public function setFontSize($size, $out=true) {
4553 $size = (float)$size;
4554 // font size in points
4555 $this->FontSizePt
= $size;
4556 // font size in user units
4557 $this->FontSize
= $size / $this->k
;
4558 // calculate some font metrics
4559 if (isset($this->CurrentFont
['desc']['FontBBox'])) {
4560 $bbox = explode(' ', substr($this->CurrentFont
['desc']['FontBBox'], 1, -1));
4561 $font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
4563 $font_height = $size * 1.219;
4565 if (isset($this->CurrentFont
['desc']['Ascent']) AND ($this->CurrentFont
['desc']['Ascent'] > 0)) {
4566 $font_ascent = ($this->CurrentFont
['desc']['Ascent'] * $size / 1000);
4568 if (isset($this->CurrentFont
['desc']['Descent']) AND ($this->CurrentFont
['desc']['Descent'] <= 0)) {
4569 $font_descent = (- $this->CurrentFont
['desc']['Descent'] * $size / 1000);
4571 if (!isset($font_ascent) AND !isset($font_descent)) {
4573 $font_ascent = 0.76 * $font_height;
4574 $font_descent = $font_height - $font_ascent;
4575 } elseif (!isset($font_descent)) {
4576 $font_descent = $font_height - $font_ascent;
4577 } elseif (!isset($font_ascent)) {
4578 $font_ascent = $font_height - $font_descent;
4580 $this->FontAscent
= ($font_ascent / $this->k
);
4581 $this->FontDescent
= ($font_descent / $this->k
);
4582 if ($out AND ($this->page
> 0) AND (isset($this->CurrentFont
['i'])) AND ($this->state
== 2)) {
4583 $this->_out(sprintf('BT /F%d %F Tf ET', $this->CurrentFont
['i'], $this->FontSizePt
));
4588 * Returns the bounding box of the current font in user units.
4591 * @since 5.9.152 (2012-03-23)
4593 public function getFontBBox() {
4595 if (isset($this->CurrentFont
['desc']['FontBBox'])) {
4596 $tmpbbox = explode(' ', substr($this->CurrentFont
['desc']['FontBBox'], 1, -1));
4597 $fbbox = array_map(array($this,'getAbsFontMeasure'), $tmpbbox);
4600 if (isset($this->CurrentFont
['desc']['MaxWidth'])) {
4601 $maxw = $this->getAbsFontMeasure(intval($this->CurrentFont
['desc']['MaxWidth']));
4604 if (isset($this->CurrentFont
['desc']['MissingWidth'])) {
4605 $maxw = max($maxw, $this->CurrentFont
['desc']['MissingWidth']);
4607 if (isset($this->CurrentFont
['desc']['AvgWidth'])) {
4608 $maxw = max($maxw, $this->CurrentFont
['desc']['AvgWidth']);
4610 if (isset($this->CurrentFont
['dw'])) {
4611 $maxw = max($maxw, $this->CurrentFont
['dw']);
4613 foreach ($this->CurrentFont
['cw'] as $char => $w) {
4614 $maxw = max($maxw, $w);
4619 $maxw = $this->getAbsFontMeasure($maxw);
4621 $fbbox = array(0, (0 - $this->FontDescent
), $maxw, $this->FontAscent
);
4627 * Convert a relative font measure into absolute value.
4628 * @param int $s Font measure.
4629 * @return float Absolute measure.
4630 * @since 5.9.186 (2012-09-13)
4632 public function getAbsFontMeasure($s) {
4633 return ($s * $this->FontSize
/ 1000);
4637 * Returns the glyph bounding box of the specified character in the current font in user units.
4638 * @param int $char Input character code.
4639 * @return false|array array(xMin, yMin, xMax, yMax) or FALSE if not defined.
4640 * @since 5.9.186 (2012-09-13)
4642 public function getCharBBox($char) {
4644 if (isset($this->CurrentFont
['cw'][$c])) {
4645 // glyph is defined ... use zero width & height for glyphs without outlines
4646 $result = array(0,0,0,0);
4647 if (isset($this->CurrentFont
['cbbox'][$c])) {
4648 $result = $this->CurrentFont
['cbbox'][$c];
4650 return array_map(array($this,'getAbsFontMeasure'), $result);
4656 * Return the font descent value
4657 * @param string $font font name
4658 * @param string $style font style
4659 * @param float $size The size (in points)
4660 * @return int font descent
4662 * @author Nicola Asuni
4663 * @since 4.9.003 (2010-03-30)
4665 public function getFontDescent($font, $style='', $size=0) {
4666 $fontdata = $this->AddFont($font, $style);
4667 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4668 if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
4669 $descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
4671 $descent = (1.219 * 0.24 * $size);
4673 return ($descent / $this->k
);
4677 * Return the font ascent value.
4678 * @param string $font font name
4679 * @param string $style font style
4680 * @param float $size The size (in points)
4681 * @return int font ascent
4683 * @author Nicola Asuni
4684 * @since 4.9.003 (2010-03-30)
4686 public function getFontAscent($font, $style='', $size=0) {
4687 $fontdata = $this->AddFont($font, $style);
4688 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4689 if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
4690 $ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
4692 $ascent = 1.219 * 0.76 * $size;
4694 return ($ascent / $this->k
);
4698 * Return true in the character is present in the specified font.
4699 * @param mixed $char Character to check (integer value or string)
4700 * @param string $font Font name (family name).
4701 * @param string $style Font style.
4702 * @return bool true if the char is defined, false otherwise.
4704 * @since 5.9.153 (2012-03-28)
4706 public function isCharDefined($char, $font='', $style='') {
4707 if (is_string($char)) {
4708 // get character code
4709 $char = TCPDF_FONTS
::UTF8StringToArray($char, $this->isunicode
, $this->CurrentFont
);
4712 if (TCPDF_STATIC
::empty_string($font)) {
4713 if (TCPDF_STATIC
::empty_string($style)) {
4714 return (isset($this->CurrentFont
['cw'][intval($char)]));
4716 $font = $this->FontFamily
;
4718 $fontdata = $this->AddFont($font, $style);
4719 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4720 return (isset($fontinfo['cw'][intval($char)]));
4724 * Replace missing font characters on selected font with specified substitutions.
4725 * @param string $text Text to process.
4726 * @param string $font Font name (family name).
4727 * @param string $style Font style.
4728 * @param array $subs 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.
4729 * @return string Processed text.
4731 * @since 5.9.153 (2012-03-28)
4733 public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4737 if (TCPDF_STATIC
::empty_string($font)) {
4738 $font = $this->FontFamily
;
4740 $fontdata = $this->AddFont($font, $style);
4741 $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
4742 $uniarr = TCPDF_FONTS
::UTF8StringToArray($text, $this->isunicode
, $this->CurrentFont
);
4743 foreach ($uniarr as $k => $chr) {
4744 if (!isset($fontinfo['cw'][$chr])) {
4745 // this character is missing on the selected font
4746 if (isset($subs[$chr])) {
4747 // we have available substitutions
4748 if (is_array($subs[$chr])) {
4749 foreach($subs[$chr] as $s) {
4750 if (isset($fontinfo['cw'][$s])) {
4755 } elseif (isset($fontinfo['cw'][$subs[$chr]])) {
4756 $uniarr[$k] = $subs[$chr];
4761 return TCPDF_FONTS
::UniArrSubString(TCPDF_FONTS
::UTF8ArrayToUniArray($uniarr, $this->isunicode
));
4765 * Defines the default monospaced font.
4766 * @param string $font Font name.
4770 public function setDefaultMonospacedFont($font) {
4771 $this->default_monospaced_font
= $font;
4775 * 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 />
4776 * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
4779 * @see Cell(), Write(), Image(), Link(), SetLink()
4781 public function AddLink() {
4782 // create a new internal link
4783 $n = count($this->links
) +
1;
4784 $this->links
[$n] = array('p' => 0, 'y' => 0, 'f' => false);
4789 * Defines the page and position a link points to.
4790 * @param int $link The link identifier returned by AddLink()
4791 * @param float $y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
4792 * @param int|string $page 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.
4797 public function setLink($link, $y=0, $page=-1) {
4799 if (!empty($page) AND (substr($page, 0, 1) == '*')) {
4800 $page = intval(substr($page, 1));
4801 // this page number will not be changed when moving/add/deleting pages
4805 $page = $this->page
;
4810 $this->links
[$link] = array('p' => $page, 'y' => $y, 'f' => $fixed);
4814 * Puts a link on a rectangular area of the page.
4815 * 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.
4816 * @param float $x Abscissa of the upper-left corner of the rectangle
4817 * @param float $y Ordinate of the upper-left corner of the rectangle
4818 * @param float $w Width of the rectangle
4819 * @param float $h Height of the rectangle
4820 * @param mixed $link URL or identifier returned by AddLink()
4821 * @param int $spaces number of spaces on the text to link
4824 * @see AddLink(), Annotation(), Cell(), Write(), Image()
4826 public function Link($x, $y, $w, $h, $link, $spaces=0) {
4827 $this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
4831 * Puts a markup annotation on a rectangular area of the page.
4832 * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
4833 * @param float $x Abscissa of the upper-left corner of the rectangle
4834 * @param float $y Ordinate of the upper-left corner of the rectangle
4835 * @param float $w Width of the rectangle
4836 * @param float $h Height of the rectangle
4837 * @param string $text annotation text or alternate content
4838 * @param array $opt array of options (see section 8.4 of PDF reference 1.7).
4839 * @param int $spaces number of spaces on the text to link
4841 * @since 4.0.018 (2008-08-06)
4843 public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
4844 if ($this->inxobj
) {
4845 // store parameters for later use on template
4846 $this->xobjects
[$this->xobjid
]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
4855 // check page for no-write regions and adapt page margins if necessary
4856 list($x, $y) = $this->checkPageRegions($h, $x, $y);
4857 // recalculate coordinates to account for graphic transformations
4858 if (isset($this->transfmatrix
) AND !empty($this->transfmatrix
)) {
4859 for ($i=$this->transfmatrix_key
; $i > 0; --$i) {
4860 $maxid = count($this->transfmatrix
[$i]) - 1;
4861 for ($j=$maxid; $j >= 0; --$j) {
4862 $ctm = $this->transfmatrix
[$i][$j];
4863 if (isset($ctm['a'])) {
4865 $y = ($this->h
- $y) * $this->k
;
4871 $x1 = ($ctm['a'] * $xt) +
($ctm['c'] * $yt) +
$ctm['e'];
4872 $y1 = ($ctm['b'] * $xt) +
($ctm['d'] * $yt) +
$ctm['f'];
4876 $x2 = ($ctm['a'] * $xt) +
($ctm['c'] * $yt) +
$ctm['e'];
4877 $y2 = ($ctm['b'] * $xt) +
($ctm['d'] * $yt) +
$ctm['f'];
4881 $x3 = ($ctm['a'] * $xt) +
($ctm['c'] * $yt) +
$ctm['e'];
4882 $y3 = ($ctm['b'] * $xt) +
($ctm['d'] * $yt) +
$ctm['f'];
4886 $x4 = ($ctm['a'] * $xt) +
($ctm['c'] * $yt) +
$ctm['e'];
4887 $y4 = ($ctm['b'] * $xt) +
($ctm['d'] * $yt) +
$ctm['f'];
4888 // new coordinates (rectangle area)
4889 $x = min($x1, $x2, $x3, $x4);
4890 $y = max($y1, $y2, $y3, $y4);
4891 $w = (max($x1, $x2, $x3, $x4) - $x) / $this->k
;
4892 $h = ($y - min($y1, $y2, $y3, $y4)) / $this->k
;
4894 $y = $this->h
- ($y / $this->k
);
4899 if ($this->page
<= 0) {
4902 $page = $this->page
;
4904 if (!isset($this->PageAnnots
[$page])) {
4905 $this->PageAnnots
[$page] = array();
4907 $this->PageAnnots
[$page][] = array('n' => ++
$this->n
, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
4908 if (!$this->pdfa_mode ||
($this->pdfa_mode
&& $this->pdfa_version
== 3)) {
4909 if ((($opt['Subtype'] == 'FileAttachment') OR ($opt['Subtype'] == 'Sound')) AND (!TCPDF_STATIC
::empty_string($opt['FS']))
4910 AND (@TCPDF_STATIC
::file_exists($opt['FS']) OR TCPDF_STATIC
::isValidURL($opt['FS']))
4911 AND (!isset($this->embeddedfiles
[basename($opt['FS'])]))) {
4912 $this->embeddedfiles
[basename($opt['FS'])] = array('f' => ++
$this->n
, 'n' => ++
$this->n
, 'file' => $opt['FS']);
4915 // Add widgets annotation's icons
4916 if (isset($opt['mk']['i']) AND @TCPDF_STATIC
::file_exists($opt['mk']['i'])) {
4917 $this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
4919 if (isset($opt['mk']['ri']) AND @TCPDF_STATIC
::file_exists($opt['mk']['ri'])) {
4920 $this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4922 if (isset($opt['mk']['ix']) AND @TCPDF_STATIC
::file_exists($opt['mk']['ix'])) {
4923 $this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
4928 * Embedd the attached files.
4929 * @since 4.4.000 (2008-12-07)
4933 protected function _putEmbeddedFiles() {
4934 if ($this->pdfa_mode
&& $this->pdfa_version
!= 3) {
4935 // embedded files are not allowed in PDF/A mode version 1 and 2
4938 reset($this->embeddedfiles
);
4939 foreach ($this->embeddedfiles
as $filename => $filedata) {
4940 $data = $this->getCachedFileContents($filedata['file']);
4941 if ($data !== FALSE) {
4942 $rawsize = strlen($data);
4945 $this->efnames
[$filename] = $filedata['f'].' 0 R';
4946 // embedded file specification object
4947 $out = $this->_getobj($filedata['f'])."\n";
4948 $out .= '<</Type /Filespec /F '.$this->_datastring($filename, $filedata['f']);
4949 $out .= ' /UF '.$this->_datastring($filename, $filedata['f']);
4950 $out .= ' /AFRelationship /Source';
4951 $out .= ' /EF <</F '.$filedata['n'].' 0 R>> >>';
4952 $out .= "\n".'endobj';
4954 // embedded file object
4956 if ($this->compress
) {
4957 $data = gzcompress($data);
4958 $filter = ' /Filter /FlateDecode';
4961 if ($this->pdfa_version
== 3) {
4962 $filter = ' /Subtype /text#2Fxml';
4965 $stream = $this->_getrawstream($data, $filedata['n']);
4966 $out = $this->_getobj($filedata['n'])."\n";
4967 $out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' /Params <</Size '.$rawsize.'>> >>';
4968 $out .= ' stream'."\n".$stream."\n".'endstream';
4969 $out .= "\n".'endobj';
4977 * Prints a text cell at the specified position.
4978 * This method allows to place a string precisely on the page.
4979 * @param float $x Abscissa of the cell origin
4980 * @param float $y Ordinate of the cell origin
4981 * @param string $txt String to print
4982 * @param int $fstroke outline size in user units (0 = disable)
4983 * @param boolean $fclip if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
4984 * @param boolean $ffill if true fills the text
4985 * @param mixed $border 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)))
4986 * @param int $ln 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.
4987 * @param string $align 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>
4988 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
4989 * @param mixed $link URL or identifier returned by AddLink().
4990 * @param int $stretch 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.
4991 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
4992 * @param string $calign 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>
4993 * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
4994 * @param boolean $rtloff if true uses the page top-left corner as origin of axis for $x and $y initial position.
4997 * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
4999 public function Text($x, $y, $txt, $fstroke=0, $fclip=false, $ffill=true, $border=0, $ln=0, $align='', $fill=false, $link='', $stretch=0, $ignore_min_height=false, $calign='T', $valign='M', $rtloff=false) {
5000 $textrendermode = $this->textrendermode
;
5001 $textstrokewidth = $this->textstrokewidth
;
5002 $this->setTextRenderingMode($fstroke, $ffill, $fclip);
5003 $this->setXY($x, $y, $rtloff);
5004 $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
5005 // restore previous rendering mode
5006 $this->textrendermode
= $textrendermode;
5007 $this->textstrokewidth
= $textstrokewidth;
5011 * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
5012 * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
5013 * This method is called automatically and should not be called directly by the application.
5017 * @see SetAutoPageBreak()
5019 public function AcceptPageBreak() {
5020 if ($this->num_columns
> 1) {
5021 // multi column mode
5022 if ($this->current_column
< ($this->num_columns
- 1)) {
5023 // go to next column
5024 $this->selectColumn($this->current_column +
1);
5025 } elseif ($this->AutoPageBreak
) {
5029 $this->selectColumn(0);
5031 // avoid page breaking from checkPageBreak()
5034 return $this->AutoPageBreak
;
5038 * Add page if needed.
5039 * @param float $h Cell height. Default value: 0.
5040 * @param float|null $y starting y position, leave empty for current position.
5041 * @param bool $addpage if true add a page, otherwise only return the true/false state
5042 * @return bool true in case of page break, false otherwise.
5043 * @since 3.2.000 (2008-07-01)
5046 protected function checkPageBreak($h=0, $y=null, $addpage=true) {
5047 if (TCPDF_STATIC
::empty_string($y)) {
5050 $current_page = $this->page
;
5051 if ((($y +
$h) > $this->PageBreakTrigger
) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
5053 //Automatic page break
5055 $this->AddPage($this->CurOrientation
);
5056 $this->y
= $this->tMargin
;
5057 $oldpage = $this->page
- 1;
5059 if ($this->pagedim
[$this->page
]['orm'] != $this->pagedim
[$oldpage]['orm']) {
5060 $this->x
= $x - ($this->pagedim
[$this->page
]['orm'] - $this->pagedim
[$oldpage]['orm']);
5065 if ($this->pagedim
[$this->page
]['olm'] != $this->pagedim
[$oldpage]['olm']) {
5066 $this->x
= $x +
($this->pagedim
[$this->page
]['olm'] - $this->pagedim
[$oldpage]['olm']);
5074 if ($current_page != $this->page
) {
5075 // account for columns mode
5082 * 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 />
5083 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5084 * @param float $w Cell width. If 0, the cell extends up to the right margin.
5085 * @param float $h Cell height. Default value: 0.
5086 * @param string $txt String to print. Default value: empty string.
5087 * @param mixed $border 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)))
5088 * @param int $ln 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.
5089 * @param string $align 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>
5090 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5091 * @param mixed $link URL or identifier returned by AddLink().
5092 * @param int $stretch 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.
5093 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5094 * @param string $calign 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>
5095 * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
5098 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
5100 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') {
5101 $prev_cell_margin = $this->cell_margin
;
5102 $prev_cell_padding = $this->cell_padding
;
5103 $this->adjustCellPadding($border);
5104 if (!$ignore_min_height) {
5105 $min_cell_height = $this->getCellHeight($this->FontSize
);
5106 if ($h < $min_cell_height) {
5107 $h = $min_cell_height;
5110 $this->checkPageBreak($h +
$this->cell_margin
['T'] +
$this->cell_margin
['B']);
5111 // apply text shadow if enabled
5112 if ($this->txtshadow
['enabled']) {
5116 $bc = $this->bgcolor
;
5117 $fc = $this->fgcolor
;
5118 $sc = $this->strokecolor
;
5119 $alpha = $this->alpha
;
5121 $this->x +
= $this->txtshadow
['depth_w'];
5122 $this->y +
= $this->txtshadow
['depth_h'];
5123 $this->setFillColorArray($this->txtshadow
['color']);
5124 $this->setTextColorArray($this->txtshadow
['color']);
5125 $this->setDrawColorArray($this->txtshadow
['color']);
5126 if ($this->txtshadow
['opacity'] != $alpha['CA']) {
5127 $this->setAlpha($this->txtshadow
['opacity'], $this->txtshadow
['blend_mode']);
5129 if ($this->state
== 2) {
5130 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5135 $this->setFillColorArray($bc);
5136 $this->setTextColorArray($fc);
5137 $this->setDrawColorArray($sc);
5138 if ($this->txtshadow
['opacity'] != $alpha['CA']) {
5139 $this->setAlpha($alpha['CA'], $alpha['BM'], $alpha['ca'], $alpha['AIS']);
5142 if ($this->state
== 2) {
5143 $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
5145 $this->cell_padding
= $prev_cell_padding;
5146 $this->cell_margin
= $prev_cell_margin;
5150 * 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 />
5151 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
5152 * @param float $w Cell width. If 0, the cell extends up to the right margin.
5153 * @param float $h Cell height. Default value: 0.
5154 * @param string $txt String to print. Default value: empty string.
5155 * @param mixed $border 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)))
5156 * @param int $ln 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.
5157 * @param string $align 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>
5158 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5159 * @param mixed $link URL or identifier returned by AddLink().
5160 * @param int $stretch 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.
5161 * @param boolean $ignore_min_height if true ignore automatic minimum height value.
5162 * @param string $calign 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>
5163 * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
5164 * @return string containing cell code
5169 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') {
5170 // replace 'NO-BREAK SPACE' (U+00A0) character with a simple space
5171 $txt = is_null($txt) ?
'' : $txt;
5172 $txt = str_replace(TCPDF_FONTS
::unichr(160, $this->isunicode
), ' ', $txt);
5173 $prev_cell_margin = $this->cell_margin
;
5174 $prev_cell_padding = $this->cell_padding
;
5175 $txt = TCPDF_STATIC
::removeSHY($txt, $this->isunicode
);
5176 $rs = ''; //string to be returned
5177 $this->adjustCellPadding($border);
5178 if (!$ignore_min_height) {
5179 $min_cell_height = $this->getCellHeight($this->FontSize
);
5180 if ($h < $min_cell_height) {
5181 $h = $min_cell_height;
5185 // check page for no-write regions and adapt page margins if necessary
5186 list($this->x
, $this->y
) = $this->checkPageRegions($h, $this->x
, $this->y
);
5188 $x = $this->x
- $this->cell_margin
['R'];
5190 $x = $this->x +
$this->cell_margin
['L'];
5192 $y = $this->y +
$this->cell_margin
['T'];
5193 $prev_font_stretching = $this->font_stretching
;
5194 $prev_font_spacing = $this->font_spacing
;
5195 // cell vertical alignment
5202 $y -= $this->cell_padding
['T'];
5207 $y -= ($h - $this->cell_padding
['B'] - $this->FontAscent
- $this->FontDescent
);
5214 $y -= (($h - $this->FontAscent
- $this->FontDescent
) / 2);
5225 $y -= ($this->cell_padding
['T'] +
$this->FontAscent
);
5230 $y -= ($h - $this->cell_padding
['B'] - $this->FontDescent
);
5237 $y -= (($h +
$this->FontAscent
- $this->FontDescent
) / 2);
5248 $y -= ($this->cell_padding
['T'] +
$this->FontAscent +
$this->FontDescent
);
5253 $y -= ($h - $this->cell_padding
['B']);
5260 $y -= (($h +
$this->FontAscent +
$this->FontDescent
) / 2);
5283 // text vertical alignment
5287 $yt = $y +
$this->cell_padding
['T'];
5292 $yt = $y +
$h - $this->cell_padding
['B'] - $this->FontAscent
- $this->FontDescent
;
5299 $yt = $y +
(($h - $this->FontAscent
- $this->FontDescent
) / 2);
5303 $basefonty = $yt +
$this->FontAscent
;
5304 if (TCPDF_STATIC
::empty_string($w) OR ($w <= 0)) {
5306 $w = $x - $this->lMargin
;
5308 $w = $this->w
- $this->rMargin
- $x;
5313 if (is_string($border) AND (strlen($border) == 4)) {
5317 if ($fill OR ($border == 1)) {
5319 $op = ($border == 1) ?
'B' : 'f';
5324 $xk = (($x - $w) * $k);
5328 $s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h
- $y) * $k), ($w * $k), (-$h * $k), $op);
5331 $s .= $this->getCellBorder($x, $y, $w, $h, $border);
5334 if ($this->isunicode
) {
5335 if (($this->CurrentFont
['type'] == 'core') OR ($this->CurrentFont
['type'] == 'TrueType') OR ($this->CurrentFont
['type'] == 'Type1')) {
5336 $txt2 = TCPDF_FONTS
::UTF8ToLatin1($txt2, $this->isunicode
, $this->CurrentFont
);
5338 $unicode = TCPDF_FONTS
::UTF8StringToArray($txt, $this->isunicode
, $this->CurrentFont
); // array of UTF-8 unicode values
5339 $unicode = TCPDF_FONTS
::utf8Bidi($unicode, '', $this->tmprtl
, $this->isunicode
, $this->CurrentFont
);
5340 // replace thai chars (if any)
5341 if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS
== true)) {
5343 $numchars = count($unicode);
5344 // po pla, for far, for fan
5345 $longtail = array(0x0e1b, 0x0e1d, 0x0e1f);
5346 // do chada, to patak
5347 $lowtail = array(0x0e0e, 0x0e0f);
5348 // mai hun arkad, sara i, sara ii, sara ue, sara uee
5349 $upvowel = array(0x0e31, 0x0e34, 0x0e35, 0x0e36, 0x0e37);
5350 // mai ek, mai tho, mai tri, mai chattawa, karan
5351 $tonemark = array(0x0e48, 0x0e49, 0x0e4a, 0x0e4b, 0x0e4c);
5352 // sara u, sara uu, pinthu
5353 $lowvowel = array(0x0e38, 0x0e39, 0x0e3a);
5355 for ($i = 0; $i < $numchars; $i++
) {
5356 if (($unicode[$i] >= 0x0e00) && ($unicode[$i] <= 0x0e5b)) {
5357 $ch0 = $unicode[$i];
5358 $ch1 = ($i > 0) ?
$unicode[($i - 1)] : 0;
5359 $ch2 = ($i > 1) ?
$unicode[($i - 2)] : 0;
5360 $chn = ($i < ($numchars - 1)) ?
$unicode[($i +
1)] : 0;
5361 if (in_array($ch0, $tonemark)) {
5362 if ($chn == 0x0e33) {
5364 if (in_array($ch1, $longtail)) {
5365 // tonemark at upper left
5366 $output[] = $this->replaceChar($ch0, (0xf713 +
$ch0 - 0x0e48));
5368 // tonemark at upper right (normal position)
5371 } elseif (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $lowvowel))) {
5372 // tonemark at lower left
5373 $output[] = $this->replaceChar($ch0, (0xf705 +
$ch0 - 0x0e48));
5374 } elseif (in_array($ch1, $upvowel)) {
5375 if (in_array($ch2, $longtail)) {
5376 // tonemark at upper left
5377 $output[] = $this->replaceChar($ch0, (0xf713 +
$ch0 - 0x0e48));
5379 // tonemark at upper right (normal position)
5383 // tonemark at lower right
5384 $output[] = $this->replaceChar($ch0, (0xf70a +
$ch0 - 0x0e48));
5386 } elseif (($ch0 == 0x0e33) AND (in_array($ch1, $longtail) OR (in_array($ch2, $longtail) AND in_array($ch1, $tonemark)))) {
5387 // add lower left nikhahit and sara aa
5388 if ($this->isCharDefined(0xf711) AND $this->isCharDefined(0x0e32)) {
5390 $this->CurrentFont
['subsetchars'][0xf711] = true;
5392 $this->CurrentFont
['subsetchars'][0x0e32] = true;
5396 } elseif (in_array($ch1, $longtail)) {
5397 if ($ch0 == 0x0e31) {
5398 // lower left mai hun arkad
5399 $output[] = $this->replaceChar($ch0, 0xf710);
5400 } elseif (in_array($ch0, $upvowel)) {
5402 $output[] = $this->replaceChar($ch0, (0xf701 +
$ch0 - 0x0e34));
5403 } elseif ($ch0 == 0x0e47) {
5404 // lower left mai tai koo
5405 $output[] = $this->replaceChar($ch0, 0xf712);
5410 } elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5412 $output[] = $this->replaceChar($ch0, (0xf718 +
$ch0 - 0x0e38));
5413 } elseif (($ch0 == 0x0e0d) AND in_array($chn, $lowvowel)) {
5414 // yo ying without lower part
5415 $output[] = $this->replaceChar($ch0, 0xf70f);
5416 } elseif (($ch0 == 0x0e10) AND in_array($chn, $lowvowel)) {
5417 // tho santan without lower part
5418 $output[] = $this->replaceChar($ch0, 0xf700);
5423 // non-thai character
5424 $output[] = $unicode[$i];
5428 // update font subsetchars
5429 $this->setFontSubBuffer($this->CurrentFont
['fontkey'], 'subsetchars', $this->CurrentFont
['subsetchars']);
5430 } // end of K_THAI_TOPCHARS
5431 $txt2 = TCPDF_FONTS
::arrUTF8ToUTF16BE($unicode, false);
5434 $txt2 = TCPDF_STATIC
::_escape($txt2);
5435 // get current text width (considering general font stretching and spacing)
5436 $txwidth = $this->GetStringWidth($txt);
5438 // check for stretch mode
5440 // calculate ratio between cell width and text width
5444 $ratio = (($w - $this->cell_padding
['L'] - $this->cell_padding
['R']) / $width);
5446 // check if stretching is required
5447 if (($ratio < 1) OR (($ratio > 1) AND (($stretch %
2) == 0))) {
5448 // the text will be stretched to fit cell width
5450 // set new character spacing
5451 $this->font_spacing +
= ($w - $this->cell_padding
['L'] - $this->cell_padding
['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching
/ 100));
5453 // set new horizontal stretching
5454 $this->font_stretching
*= $ratio;
5456 // recalculate text width (the text fills the entire cell)
5457 $width = $w - $this->cell_padding
['L'] - $this->cell_padding
['R'];
5462 if ($this->font_stretching
!= 100) {
5463 // apply font stretching
5464 $rs .= sprintf('BT %F Tz ET ', $this->font_stretching
);
5466 if ($this->font_spacing
!= 0) {
5467 // increase/decrease font spacing
5468 $rs .= sprintf('BT %F Tc ET ', ($this->font_spacing
* $this->k
));
5470 if ($this->ColorFlag
AND ($this->textrendermode
< 4)) {
5471 $s .= 'q '.$this->TextColor
.' ';
5474 $s .= sprintf('BT %d Tr %F w ET ', $this->textrendermode
, ($this->textstrokewidth
* $this->k
));
5475 // count number of spaces
5476 $ns = substr_count($txt, chr(32));
5479 if (($align == 'J') AND ($ns > 0)) {
5480 if ($this->isUnicodeFont()) {
5481 // get string width without spaces
5482 $width = $this->GetStringWidth(str_replace(' ', '', $txt));
5483 // calculate average space width
5484 $spacewidth = -1000 * ($w - $width - $this->cell_padding
['L'] - $this->cell_padding
['R']) / ($ns?
$ns:1) / ($this->FontSize?
$this->FontSize
:1);
5485 if ($this->font_stretching
!= 100) {
5486 // word spacing is affected by stretching
5487 $spacewidth /= ($this->font_stretching
/ 100);
5489 // set word position to be used with TJ operator
5490 $txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacewidth).' (', $txt2);
5491 $unicode_justification = true;
5496 $spacewidth = (($w - $width - $this->cell_padding
['L'] - $this->cell_padding
['R']) / ($ns?
$ns:1)) * $this->k
;
5497 if ($this->font_stretching
!= 100) {
5498 // word spacing (Tw) is affected by stretching
5499 $spacewidth /= ($this->font_stretching
/ 100);
5502 $rs .= sprintf('BT %F Tw ET ', $spacewidth);
5504 $width = $w - $this->cell_padding
['L'] - $this->cell_padding
['R'];
5506 // replace carriage return characters
5507 $txt2 = str_replace("\r", ' ', $txt2);
5510 $dx = ($w - $width) / 2;
5515 $dx = $this->cell_padding
['R'];
5517 $dx = $w - $width - $this->cell_padding
['R'];
5523 $dx = $w - $width - $this->cell_padding
['L'];
5525 $dx = $this->cell_padding
['L'];
5532 $dx = $this->cell_padding
['R'];
5534 $dx = $this->cell_padding
['L'];
5540 $xdx = $x - $dx - $width;
5546 $s .= sprintf('BT %F %F Td [(%s)] TJ ET', $xdk, (($this->h
- $basefonty) * $k), $txt2);
5547 if (isset($uniblock)) { // @phpstan-ignore-line
5548 // print overlapping characters as separate string
5549 $xshift = 0; // horizontal shift
5550 $ty = (($this->h
- $basefonty +
(0.2 * $this->FontSize
)) * $k);
5551 $spw = (($w - $txwidth - $this->cell_padding
['L'] - $this->cell_padding
['R']) / ($ns?
$ns:1));
5552 foreach ($uniblock as $uk => $uniarr) { // @phpstan-ignore-line
5553 if (($uk %
2) == 0) {
5555 if ($spacewidth != 0) {
5556 // justification shift
5557 $xshift +
= (count(array_keys($uniarr, 32)) * $spw);
5559 $xshift +
= $this->GetArrStringWidth($uniarr); // + shift justification
5561 // character to print
5562 $topchr = TCPDF_FONTS
::arrUTF8ToUTF16BE($uniarr, false);
5563 $topchr = TCPDF_STATIC
::_escape($topchr);
5564 $s .= sprintf(' BT %F %F Td [(%s)] TJ ET', ($xdk +
($xshift * $k)), $ty, $topchr);
5568 if ($this->underline
) {
5569 $s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
5571 if ($this->linethrough
) {
5572 $s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
5574 if ($this->overline
) {
5575 $s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
5577 if ($this->ColorFlag
AND ($this->textrendermode
< 4)) {
5581 $this->Link($xdx, $yt, $width, ($this->FontAscent +
$this->FontDescent
), $link, $ns);
5588 if ($this->font_spacing
!= 0) {
5589 // reset font spacing mode
5590 $rs .= ' BT 0 Tc ET';
5592 if ($this->font_stretching
!= 100) {
5593 // reset font stretching mode
5594 $rs .= ' BT 100 Tz ET';
5597 // reset word spacing
5598 if (!$this->isUnicodeFont() AND ($align == 'J')) {
5599 $rs .= ' BT 0 Tw ET';
5601 // reset stretching and spacing
5602 $this->font_stretching
= $prev_font_stretching;
5603 $this->font_spacing
= $prev_font_spacing;
5606 //Go to the beginning of the next line
5607 $this->y
= $y +
$h +
$this->cell_margin
['B'];
5610 $this->x
= $this->w
- $this->rMargin
;
5612 $this->x
= $this->lMargin
;
5616 // go left or right by case
5618 $this->x
= $x - $w - $this->cell_margin
['L'];
5620 $this->x
= $x +
$w +
$this->cell_margin
['R'];
5623 $gstyles = ''.$this->linestyleWidth
.' '.$this->linestyleCap
.' '.$this->linestyleJoin
.' '.$this->linestyleDash
.' '.$this->DrawColor
.' '.$this->FillColor
."\n";
5625 $this->cell_padding
= $prev_cell_padding;
5626 $this->cell_margin
= $prev_cell_margin;
5631 * Replace a char if is defined on the current font.
5632 * @param int $oldchar Integer code (unicode) of the character to replace.
5633 * @param int $newchar Integer code (unicode) of the new character.
5634 * @return int the replaced char or the old char in case the new char i not defined
5636 * @since 5.9.167 (2012-06-22)
5638 protected function replaceChar($oldchar, $newchar) {
5639 if ($this->isCharDefined($newchar)) {
5640 // add the new char on the subset list
5641 $this->CurrentFont
['subsetchars'][$newchar] = true;
5642 // return the new character
5645 // return the old char
5650 * Returns the code to draw the cell border
5651 * @param float $x X coordinate.
5652 * @param float $y Y coordinate.
5653 * @param float $w Cell width.
5654 * @param float $h Cell height.
5655 * @param string|array|int $brd 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)))
5656 * @return string containing cell border code
5658 * @see SetLineStyle()
5659 * @since 5.7.000 (2010-08-02)
5661 protected function getCellBorder($x, $y, $w, $h, $brd) {
5662 $s = ''; // string to be returned
5667 $brd = array('LRTB' => true);
5669 // calculate coordinates for border
5672 $xeL = ($x - $w) * $k;
5676 $xeR = ($x +
$w) * $k;
5678 $yeL = (($this->h
- ($y +
$h)) * $k);
5679 $yeT = (($this->h
- $y) * $k);
5684 if (is_string($brd)) {
5685 // convert string to array
5686 $slen = strlen($brd);
5688 for ($i = 0; $i < $slen; ++
$i) {
5689 $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5693 if (isset($brd['mode'])) {
5694 $mode = $brd['mode'];
5695 unset($brd['mode']);
5699 foreach ($brd as $border => $style) {
5700 if (is_array($style) AND !empty($style)) {
5701 // apply border style
5702 $prev_style = $this->linestyleWidth
.' '.$this->linestyleCap
.' '.$this->linestyleJoin
.' '.$this->linestyleDash
.' '.$this->DrawColor
.' ';
5703 $s .= $this->setLineStyle($style, true)."\n";
5707 $off = (($this->LineWidth
/ 2) * $k);
5716 $w +
= $this->LineWidth
;
5717 $h +
= $this->LineWidth
;
5721 $off = ($this->LineWidth
/ 2) * $k;
5730 $w -= $this->LineWidth
;
5731 $h -= $this->LineWidth
;
5747 // draw borders by case
5748 if (strlen($border) == 4) {
5749 $s .= sprintf('%F %F %F %F re S ', $xT, $yT, ($w * $k), (-$h * $k));
5750 } elseif (strlen($border) == 3) {
5751 if (strpos($border,'B') === false) { // LTR
5752 $s .= sprintf('%F %F m ', $xL, $yL);
5753 $s .= sprintf('%F %F l ', $xT, $yT);
5754 $s .= sprintf('%F %F l ', $xR, $yR);
5755 $s .= sprintf('%F %F l ', $xB, $yB);
5757 } elseif (strpos($border,'L') === false) { // TRB
5758 $s .= sprintf('%F %F m ', $xT, $yT);
5759 $s .= sprintf('%F %F l ', $xR, $yR);
5760 $s .= sprintf('%F %F l ', $xB, $yB);
5761 $s .= sprintf('%F %F l ', $xL, $yL);
5763 } elseif (strpos($border,'T') === false) { // RBL
5764 $s .= sprintf('%F %F m ', $xR, $yR);
5765 $s .= sprintf('%F %F l ', $xB, $yB);
5766 $s .= sprintf('%F %F l ', $xL, $yL);
5767 $s .= sprintf('%F %F l ', $xT, $yT);
5769 } elseif (strpos($border,'R') === false) { // BLT
5770 $s .= sprintf('%F %F m ', $xB, $yB);
5771 $s .= sprintf('%F %F l ', $xL, $yL);
5772 $s .= sprintf('%F %F l ', $xT, $yT);
5773 $s .= sprintf('%F %F l ', $xR, $yR);
5776 } elseif (strlen($border) == 2) {
5777 if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
5778 $s .= sprintf('%F %F m ', $xL, $yL);
5779 $s .= sprintf('%F %F l ', $xT, $yT);
5780 $s .= sprintf('%F %F l ', $xR, $yR);
5782 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
5783 $s .= sprintf('%F %F m ', $xT, $yT);
5784 $s .= sprintf('%F %F l ', $xR, $yR);
5785 $s .= sprintf('%F %F l ', $xB, $yB);
5787 } elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
5788 $s .= sprintf('%F %F m ', $xR, $yR);
5789 $s .= sprintf('%F %F l ', $xB, $yB);
5790 $s .= sprintf('%F %F l ', $xL, $yL);
5792 } elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
5793 $s .= sprintf('%F %F m ', $xB, $yB);
5794 $s .= sprintf('%F %F l ', $xL, $yL);
5795 $s .= sprintf('%F %F l ', $xT, $yT);
5797 } elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
5798 $s .= sprintf('%F %F m ', $xL, $yL);
5799 $s .= sprintf('%F %F l ', $xT, $yT);
5801 $s .= sprintf('%F %F m ', $xR, $yR);
5802 $s .= sprintf('%F %F l ', $xB, $yB);
5804 } elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
5805 $s .= sprintf('%F %F m ', $xT, $yT);
5806 $s .= sprintf('%F %F l ', $xR, $yR);
5808 $s .= sprintf('%F %F m ', $xB, $yB);
5809 $s .= sprintf('%F %F l ', $xL, $yL);
5812 } else { // strlen($border) == 1
5813 if (strpos($border,'L') !== false) { // L
5814 $s .= sprintf('%F %F m ', $xL, $yL);
5815 $s .= sprintf('%F %F l ', $xT, $yT);
5817 } elseif (strpos($border,'T') !== false) { // T
5818 $s .= sprintf('%F %F m ', $xT, $yT);
5819 $s .= sprintf('%F %F l ', $xR, $yR);
5821 } elseif (strpos($border,'R') !== false) { // R
5822 $s .= sprintf('%F %F m ', $xR, $yR);
5823 $s .= sprintf('%F %F l ', $xB, $yB);
5825 } elseif (strpos($border,'B') !== false) { // B
5826 $s .= sprintf('%F %F m ', $xB, $yB);
5827 $s .= sprintf('%F %F l ', $xL, $yL);
5831 if (is_array($style) AND !empty($style)) {
5832 // reset border style to previous value
5833 $s .= "\n".$this->linestyleWidth
.' '.$this->linestyleCap
.' '.$this->linestyleJoin
.' '.$this->linestyleDash
.' '.$this->DrawColor
."\n";
5840 * This method allows printing text with line breaks.
5841 * 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 />
5842 * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
5843 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
5844 * @param float $h Cell minimum height. The cell extends automatically if needed.
5845 * @param string $txt String to print
5846 * @param mixed $border 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)))
5847 * @param string $align 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>
5848 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
5849 * @param int $ln 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>
5850 * @param float|null $x x position in user units
5851 * @param float|null $y y position in user units
5852 * @param boolean $reseth if true reset the last cell height (default true).
5853 * @param int $stretch 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.
5854 * @param boolean $ishtml 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.
5855 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
5856 * @param float $maxh 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.
5857 * @param string $valign 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.
5858 * @param boolean $fitcell 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 equal to $h.
5859 * @return int Return the number of cells or 1 for html mode.
5862 * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
5864 public function MultiCell($w, $h, $txt, $border=0, $align='J', $fill=false, $ln=1, $x=null, $y=null, $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0, $valign='T', $fitcell=false) {
5865 $prev_cell_margin = $this->cell_margin
;
5866 $prev_cell_padding = $this->cell_padding
;
5867 // adjust internal padding
5868 $this->adjustCellPadding($border);
5869 $mc_padding = $this->cell_padding
;
5870 $mc_margin = $this->cell_margin
;
5871 $this->cell_padding
['T'] = 0;
5872 $this->cell_padding
['B'] = 0;
5873 $this->setCellMargins(0, 0, 0, 0);
5874 if (TCPDF_STATIC
::empty_string($this->lasth
) OR $reseth) {
5876 $this->resetLastH();
5878 if (!TCPDF_STATIC
::empty_string($y)) {
5879 $this->setY($y); // set y in order to convert negative y values to positive ones
5883 if (($h > 0) AND $this->inPageBody() AND (($y +
$h +
$mc_margin['T'] +
$mc_margin['B']) > $this->PageBreakTrigger
)) {
5884 // spit cell in more pages/columns
5885 $newh = ($this->PageBreakTrigger
- $y);
5886 $resth = ($h - $newh); // cell to be printed on the next page/column
5889 // get current page number
5890 $startpage = $this->page
;
5891 // get current column
5892 $startcolumn = $this->current_column
;
5893 if (!TCPDF_STATIC
::empty_string($x)) {
5898 // check page for no-write regions and adapt page margins if necessary
5899 list($x, $y) = $this->checkPageRegions(0, $x, $y);
5901 $oy = $y +
$mc_margin['T'];
5903 $ox = ($this->w
- $x - $mc_margin['R']);
5905 $ox = ($x +
$mc_margin['L']);
5910 if (TCPDF_STATIC
::empty_string($w) OR ($w <= 0)) {
5912 $w = ($this->x
- $this->lMargin
- $mc_margin['L']);
5914 $w = ($this->w
- $this->x
- $this->rMargin
- $mc_margin['R']);
5917 // store original margin values
5918 $lMargin = $this->lMargin
;
5919 $rMargin = $this->rMargin
;
5921 $this->rMargin
= ($this->w
- $this->x
);
5922 $this->lMargin
= ($this->x
- $w);
5924 $this->lMargin
= ($this->x
);
5925 $this->rMargin
= ($this->w
- $this->x
- $w);
5927 $this->clMargin
= $this->lMargin
;
5928 $this->crMargin
= $this->rMargin
;
5931 $this->y +
= $mc_padding['T'];
5933 if ($ishtml) { // ******* Write HTML text
5934 $this->writeHTML($txt, true, false, $reseth, true, $align);
5936 } else { // ******* Write simple text
5937 $prev_FontSizePt = $this->FontSizePt
;
5939 // ajust height values
5940 $tobottom = ($this->h
- $this->y
- $this->bMargin
- $this->cell_padding
['T'] - $this->cell_padding
['B']);
5941 $h = $maxh = max(min($h, $tobottom), min($maxh, $tobottom));
5943 // vertical alignment
5946 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5947 if ($fitcell AND ($text_height > $maxh) AND ($this->FontSizePt
> 1)) {
5948 // try to reduce font size to fit text on cell (use a quick search algorithm)
5950 $fmax = $this->FontSizePt
;
5951 $diff_epsilon = (1 / $this->k
); // one point (min resolution)
5952 $maxit = (2 * min(100, max(10, intval($fmax)))); // max number of iterations
5953 while ($maxit >= 0) {
5954 $fmid = (($fmax +
$fmin) / 2);
5955 $this->setFontSize($fmid, false);
5956 $this->resetLastH();
5957 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5958 $diff = ($maxh - $text_height);
5960 if ($diff <= $diff_epsilon) {
5970 // premature exit, we get the minimum font value to fit the cell
5971 $this->setFontSize($fmin);
5972 $this->resetLastH();
5973 $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
5975 $this->setFontSize($fmid);
5976 $this->resetLastH();
5979 if ($text_height < $maxh) {
5980 if ($valign == 'M') {
5981 // text vertically centered
5982 $this->y +
= (($maxh - $text_height) / 2);
5983 } elseif ($valign == 'B') {
5984 // text vertically aligned on bottom
5985 $this->y +
= ($maxh - $text_height);
5989 $nl = $this->Write($this->lasth
, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
5991 // restore font size
5992 $this->setFontSize($prev_FontSizePt);
5996 // add bottom padding
5997 $this->y +
= $mc_padding['B'];
5999 // Get end-of-text Y position
6000 $currentY = $this->y
;
6001 // get latest page number
6002 $endpage = $this->page
;
6004 $skip = ($endpage - $startpage);
6006 while ($tmpresth > 0) {
6008 // add a page (or trig AcceptPageBreak() for multicolumn mode)
6009 $this->checkPageBreak($this->PageBreakTrigger +
1);
6011 if ($this->num_columns
> 1) {
6012 $tmpresth -= ($this->h
- $this->y
- $this->bMargin
);
6014 $tmpresth -= ($this->h
- $this->tMargin
- $this->bMargin
);
6018 $currentY = $this->y
;
6019 $endpage = $this->page
;
6021 // get latest column
6022 $endcolumn = $this->current_column
;
6023 if ($this->num_columns
== 0) {
6024 $this->num_columns
= 1;
6026 // disable page regions check
6027 $check_page_regions = $this->check_page_regions
;
6028 $this->check_page_regions
= false;
6030 $border_start = TCPDF_STATIC
::getBorderMode($border, $position='start', $this->opencell
);
6031 $border_end = TCPDF_STATIC
::getBorderMode($border, $position='end', $this->opencell
);
6032 $border_middle = TCPDF_STATIC
::getBorderMode($border, $position='middle', $this->opencell
);
6033 // design borders around HTML cells.
6034 for ($page = $startpage; $page <= $endpage; ++
$page) { // for each page
6036 $this->setPage($page);
6037 if ($this->num_columns
< 2) {
6038 // single-column mode
6040 $this->y
= $this->tMargin
;
6042 // account for margin changes
6043 if ($page > $startpage) {
6044 if (($this->rtl
) AND ($this->pagedim
[$page]['orm'] != $this->pagedim
[$startpage]['orm'])) {
6045 $this->x
-= ($this->pagedim
[$page]['orm'] - $this->pagedim
[$startpage]['orm']);
6046 } elseif ((!$this->rtl
) AND ($this->pagedim
[$page]['olm'] != $this->pagedim
[$startpage]['olm'])) {
6047 $this->x +
= ($this->pagedim
[$page]['olm'] - $this->pagedim
[$startpage]['olm']);
6050 if ($startpage == $endpage) {
6052 for ($column = $startcolumn; $column <= $endcolumn; ++
$column) { // for each column
6053 if ($column != $this->current_column
) {
6054 $this->selectColumn($column);
6057 $this->x
-= $mc_margin['R'];
6059 $this->x +
= $mc_margin['L'];
6061 if ($startcolumn == $endcolumn) { // single column
6063 $h = max($h, ($currentY - $oy));
6065 } elseif ($column == $startcolumn) { // first column
6066 $cborder = $border_start;
6068 $h = $this->h
- $this->y
- $this->bMargin
;
6069 } elseif ($column == $endcolumn) { // end column
6070 $cborder = $border_end;
6071 $h = $currentY - $this->y
;
6075 } else { // middle column
6076 $cborder = $border_middle;
6077 $h = $this->h
- $this->y
- $this->bMargin
;
6080 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6081 } // end for each column
6082 } elseif ($page == $startpage) { // first page
6083 for ($column = $startcolumn; $column < $this->num_columns
; ++
$column) { // for each column
6084 if ($column != $this->current_column
) {
6085 $this->selectColumn($column);
6088 $this->x
-= $mc_margin['R'];
6090 $this->x +
= $mc_margin['L'];
6092 if ($column == $startcolumn) { // first column
6093 $cborder = $border_start;
6095 $h = $this->h
- $this->y
- $this->bMargin
;
6096 } else { // middle column
6097 $cborder = $border_middle;
6098 $h = $this->h
- $this->y
- $this->bMargin
;
6101 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6102 } // end for each column
6103 } elseif ($page == $endpage) { // last page
6104 for ($column = 0; $column <= $endcolumn; ++
$column) { // for each column
6105 if ($column != $this->current_column
) {
6106 $this->selectColumn($column);
6109 $this->x
-= $mc_margin['R'];
6111 $this->x +
= $mc_margin['L'];
6113 if ($column == $endcolumn) {
6115 $cborder = $border_end;
6116 $h = $currentY - $this->y
;
6122 $cborder = $border_middle;
6123 $h = $this->h
- $this->y
- $this->bMargin
;
6126 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6127 } // end for each column
6128 } else { // middle page
6129 for ($column = 0; $column < $this->num_columns
; ++
$column) { // for each column
6130 $this->selectColumn($column);
6132 $this->x
-= $mc_margin['R'];
6134 $this->x +
= $mc_margin['L'];
6136 $cborder = $border_middle;
6137 $h = $this->h
- $this->y
- $this->bMargin
;
6139 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
6140 } // end for each column
6142 if ($cborder OR $fill) {
6143 $offsetlen = strlen($ccode);
6144 // draw border and fill
6145 if ($this->inxobj
) {
6146 // we are inside an XObject template
6147 if (end($this->xobjects
[$this->xobjid
]['transfmrk']) !== false) {
6148 $pagemarkkey = key($this->xobjects
[$this->xobjid
]['transfmrk']);
6149 $pagemark = $this->xobjects
[$this->xobjid
]['transfmrk'][$pagemarkkey];
6150 $this->xobjects
[$this->xobjid
]['transfmrk'][$pagemarkkey] +
= $offsetlen;
6152 $pagemark = $this->xobjects
[$this->xobjid
]['intmrk'];
6153 $this->xobjects
[$this->xobjid
]['intmrk'] +
= $offsetlen;
6155 $pagebuff = $this->xobjects
[$this->xobjid
]['outdata'];
6156 $pstart = substr($pagebuff, 0, $pagemark);
6157 $pend = substr($pagebuff, $pagemark);
6158 $this->xobjects
[$this->xobjid
]['outdata'] = $pstart.$ccode.$pend;
6160 if (end($this->transfmrk
[$this->page
]) !== false) {
6161 $pagemarkkey = key($this->transfmrk
[$this->page
]);
6162 $pagemark = $this->transfmrk
[$this->page
][$pagemarkkey];
6163 $this->transfmrk
[$this->page
][$pagemarkkey] +
= $offsetlen;
6164 } elseif ($this->InFooter
) {
6165 $pagemark = $this->footerpos
[$this->page
];
6166 $this->footerpos
[$this->page
] +
= $offsetlen;
6168 $pagemark = $this->intmrk
[$this->page
];
6169 $this->intmrk
[$this->page
] +
= $offsetlen;
6171 $pagebuff = $this->getPageBuffer($this->page
);
6172 $pstart = substr($pagebuff, 0, $pagemark);
6173 $pend = substr($pagebuff, $pagemark);
6174 $this->setPageBuffer($this->page
, $pstart.$ccode.$pend);
6177 } // end for each page
6178 // restore page regions check
6179 $this->check_page_regions
= $check_page_regions;
6180 // Get end-of-cell Y position
6181 $currentY = $this->GetY();
6182 // restore previous values
6183 if ($this->num_columns
> 1) {
6184 $this->selectColumn();
6186 // restore original margins
6187 $this->lMargin
= $lMargin;
6188 $this->rMargin
= $rMargin;
6189 if ($this->page
> $startpage) {
6190 // check for margin variations between pages (i.e. booklet mode)
6191 $dl = ($this->pagedim
[$this->page
]['olm'] - $this->pagedim
[$startpage]['olm']);
6192 $dr = ($this->pagedim
[$this->page
]['orm'] - $this->pagedim
[$startpage]['orm']);
6193 if (($dl != 0) OR ($dr != 0)) {
6194 $this->lMargin +
= $dl;
6195 $this->rMargin +
= $dr;
6200 //Go to the beginning of the next line
6201 $this->setY($currentY +
$mc_margin['B']);
6203 $this->setX($x +
$w +
$mc_margin['L'] +
$mc_margin['R']);
6206 // go left or right by case
6207 $this->setPage($startpage);
6209 $this->setX($x +
$w +
$mc_margin['L'] +
$mc_margin['R']);
6211 $this->setContentMark();
6212 $this->cell_padding
= $prev_cell_padding;
6213 $this->cell_margin
= $prev_cell_margin;
6214 $this->clMargin
= $this->lMargin
;
6215 $this->crMargin
= $this->rMargin
;
6220 * This method return the estimated number of lines for print a simple text string using Multicell() method.
6221 * @param string $txt String for calculating his height
6222 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
6223 * @param boolean $reseth if true reset the last cell height (default false).
6224 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
6225 * @param array|null $cellpadding Internal cell padding, if empty uses default cell padding.
6226 * @param mixed $border 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)))
6227 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6228 * @author Alexander Escalona Fern\E1ndez, Nicola Asuni
6232 public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding=null, $border=0) {
6233 if ($txt === NULL) {
6240 // adjust internal padding
6241 $prev_cell_padding = $this->cell_padding
;
6242 $prev_lasth = $this->lasth
;
6243 if (is_array($cellpadding)) {
6244 $this->cell_padding
= $cellpadding;
6246 $this->adjustCellPadding($border);
6247 if (TCPDF_STATIC
::empty_string($w) OR ($w <= 0)) {
6249 $w = $this->x
- $this->lMargin
;
6251 $w = $this->w
- $this->rMargin
- $this->x
;
6254 $wmax = $w - $this->cell_padding
['L'] - $this->cell_padding
['R'];
6257 $this->resetLastH();
6261 $chars = TCPDF_FONTS
::utf8Bidi(TCPDF_FONTS
::UTF8StringToArray($txt, $this->isunicode
, $this->CurrentFont
), $txt, $this->tmprtl
, $this->isunicode
, $this->CurrentFont
);
6262 $charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
6263 $length = count($chars);
6264 $lastSeparator = -1;
6265 for ($i = 0; $i < $length; ++
$i) {
6267 $charWidth = $charsWidth[$i];
6270 OR preg_match($this->re_spaces
, TCPDF_FONTS
::unichr($c, $this->isunicode
))
6272 AND ($i > 0) AND ($i < ($length - 1))
6273 AND @preg_match
('/[\p{L}]/'.$this->re_space
['m'], TCPDF_FONTS
::unichr($chars[($i - 1)], $this->isunicode
))
6274 AND @preg_match
('/[\p{L}]/'.$this->re_space
['m'], TCPDF_FONTS
::unichr($chars[($i +
1)], $this->isunicode
))
6278 $lastSeparator = $i;
6280 if ((($sum +
$charWidth) > $wmax) OR ($c == 10)) {
6283 $lastSeparator = -1;
6285 } elseif ($lastSeparator != -1) {
6286 $i = $lastSeparator;
6287 $lastSeparator = -1;
6296 if ($chars[($length - 1)] == 10) {
6299 $this->cell_padding
= $prev_cell_padding;
6300 $this->lasth
= $prev_lasth;
6305 * This method return the estimated height needed for printing a simple text string using the Multicell() method.
6306 * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
6308 * // store current object
6309 * $pdf->startTransaction();
6310 * // store starting values
6311 * $start_y = $pdf->GetY();
6312 * $start_page = $pdf->getPage();
6313 * // call your printing functions with your parameters
6314 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6315 * $pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x=null, $y=null, $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0);
6316 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6318 * $end_y = $pdf->GetY();
6319 * $end_page = $pdf->getPage();
6320 * // calculate height
6322 * if ($end_page == $start_page) {
6323 * $height = $end_y - $start_y;
6325 * for ($page=$start_page; $page <= $end_page; ++$page) {
6326 * $this->setPage($page);
6327 * if ($page == $start_page) {
6329 * $height += $this->h - $start_y - $this->bMargin;
6330 * } elseif ($page == $end_page) {
6332 * $height += $end_y - $this->tMargin;
6334 * $height += $this->h - $this->tMargin - $this->bMargin;
6338 * // restore previous object
6339 * $pdf = $pdf->rollbackTransaction();
6341 * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
6342 * @param string $txt String for calculating his height
6343 * @param boolean $reseth if true reset the last cell height (default false).
6344 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
6345 * @param array|null $cellpadding Internal cell padding, if empty uses default cell padding.
6346 * @param mixed $border 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)))
6347 * @return float Return the minimal height needed for multicell method for printing the $txt param.
6348 * @author Nicola Asuni, Alexander Escalona Fern\E1ndez
6351 public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding=null, $border=0) {
6352 // adjust internal padding
6353 $prev_cell_padding = $this->cell_padding
;
6354 $prev_lasth = $this->lasth
;
6355 if (is_array($cellpadding)) {
6356 $this->cell_padding
= $cellpadding;
6358 $this->adjustCellPadding($border);
6359 $lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
6360 $height = $this->getCellHeight(($lines * $this->FontSize
), $autopadding);
6361 $this->cell_padding
= $prev_cell_padding;
6362 $this->lasth
= $prev_lasth;
6367 * This method prints text from the current position.<br />
6368 * @param float $h Line height
6369 * @param string $txt String to print
6370 * @param mixed $link URL or identifier returned by AddLink()
6371 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
6372 * @param string $align 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>
6373 * @param boolean $ln if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
6374 * @param int $stretch 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.
6375 * @param boolean $firstline if true prints only the first line and return the remaining string.
6376 * @param boolean $firstblock if true the string is the starting of a line.
6377 * @param float $maxh maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature.
6378 * @param float $wadj first line width will be reduced by this amount (used in HTML mode).
6379 * @param array|null $margin margin array of the parent container
6380 * @return mixed Return the number of cells or the remaining string if $firstline = true.
6384 public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin=null) {
6385 // check page for no-write regions and adapt page margins if necessary
6386 list($this->x
, $this->y
) = $this->checkPageRegions($h, $this->x
, $this->y
);
6387 if (strlen($txt) == 0) {
6391 if (!is_array($margin)) {
6392 // set default margins
6393 $margin = $this->cell_margin
;
6395 // remove carriage returns
6396 $s = str_replace("\r", '', $txt);
6397 // check if string contains arabic text
6398 if (preg_match(TCPDF_FONT_DATA
::$uni_RE_PATTERN_ARABIC, $s)) {
6403 // check if string contains RTL text
6404 if ($arabic OR ($this->tmprtl
== 'R') OR preg_match(TCPDF_FONT_DATA
::$uni_RE_PATTERN_RTL, $s)) {
6410 $chrwidth = $this->GetCharWidth(46); // dot character
6411 // get array of unicode values
6412 $chars = TCPDF_FONTS
::UTF8StringToArray($s, $this->isunicode
, $this->CurrentFont
);
6413 // calculate maximum width for a single character on string
6414 $chrw = $this->GetArrStringWidth($chars, '', '', 0, true);
6415 array_walk($chrw, array($this, 'getRawCharWidth'));
6416 $maxchwidth = ((is_array($chrw) ||
$chrw instanceof Countable
) && count($chrw) > 0) ?
max($chrw) : 0;
6417 // get array of chars
6418 $uchars = TCPDF_FONTS
::UTF8ArrayToUniArray($chars, $this->isunicode
);
6419 // get the number of characters
6420 $nb = count($chars);
6421 // replacement for SHY character (minus symbol)
6422 $shy_replacement = 45;
6423 $shy_replacement_char = TCPDF_FONTS
::unichr($shy_replacement, $this->isunicode
);
6424 // widht for SHY replacement
6425 $shy_replacement_width = $this->GetCharWidth($shy_replacement);
6427 $pw = $w = $this->w
- $this->lMargin
- $this->rMargin
;
6428 // calculate remaining line width ($w)
6430 $w = $this->x
- $this->lMargin
;
6432 $w = $this->w
- $this->rMargin
- $this->x
;
6435 $wmax = ($w - $wadj);
6437 $wmax -= ($this->cell_padding
['L'] +
$this->cell_padding
['R']);
6439 if ((!$firstline) AND (($chrwidth > $wmax) OR ($maxchwidth > $wmax))) {
6440 // the maximum width character do not fit on column
6443 // minimum row height
6444 $row_height = max($h, $this->getCellHeight($this->FontSize
));
6446 $maxy = $this->y +
$maxh - max($row_height, $h);
6447 $start_page = $this->page
;
6448 $i = 0; // character position
6449 $j = 0; // current starting position
6450 $sep = -1; // position of the last blank space
6451 $prevsep = $sep; // previous separator
6452 $shy = false; // true if the last blank is a soft hypen (SHY)
6453 $prevshy = $shy; // previous shy mode
6454 $l = 0; // current string length
6455 $nl = 0; //number of lines
6457 $pc = 0; // previous character
6458 // for each character
6460 if (($maxh > 0) AND ($this->y
> $maxy) ) {
6463 //Get the current character
6465 if ($c == 10) { // 10 = "\n" = new line
6466 //Explicit line break
6467 if ($align == 'J') {
6476 $tmpstr = TCPDF_FONTS
::UniArrSubString($uchars, $j, $i);
6479 $tmparr = array_slice($chars, $j, ($i - $j));
6481 $tmparr = TCPDF_FONTS
::utf8Bidi($tmparr, $tmpstr, $this->tmprtl
, $this->isunicode
, $this->CurrentFont
);
6483 $linew = $this->GetArrStringWidth($tmparr);
6486 $this->endlinex
= $startx - $linew;
6488 $this->endlinex
= $startx +
$linew;
6491 $tmpcellpadding = $this->cell_padding
;
6493 $this->setCellPadding(0);
6496 if ($firstblock AND $this->isRTLTextDir()) {
6497 $tmpstr = $this->stringRightTrim($tmpstr);
6499 // Skip newlines at the beginning of a page or column
6500 if (!empty($tmpstr) OR ($this->y
< ($this->PageBreakTrigger
- $row_height))) {
6501 $this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
6505 $this->cell_padding
= $tmpcellpadding;
6506 return (TCPDF_FONTS
::UniArrSubString($uchars, $i));
6514 // account for margin changes
6515 if ((($this->y +
$this->lasth
) > $this->PageBreakTrigger
) AND ($this->inPageBody())) {
6516 if ($this->AcceptPageBreak())
6519 $this->x
-= $margin['R'];
6521 $this->x +
= $margin['L'];
6523 $this->lMargin +
= $margin['L'];
6524 $this->rMargin +
= $margin['R'];
6527 $w = $this->getRemainingWidth();
6528 $wmax = ($w - $this->cell_padding
['L'] - $this->cell_padding
['R']);
6530 // 160 is the non-breaking space.
6531 // 173 is SHY (Soft Hypen).
6532 // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
6533 // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
6534 // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
6537 OR preg_match($this->re_spaces
, TCPDF_FONTS
::unichr($c, $this->isunicode
))
6539 AND ($i < ($nb - 1))
6540 AND @preg_match
('/[\p{L}]/'.$this->re_space
['m'], TCPDF_FONTS
::unichr($pc, $this->isunicode
))
6541 AND @preg_match
('/[\p{L}]/'.$this->re_space
['m'], TCPDF_FONTS
::unichr($chars[($i +
1)], $this->isunicode
))
6545 // update last blank space position
6548 // check if is a SHY
6549 if (($c == 173) OR ($c == 45)) {
6553 $tmp_shy_replacement_width = 0;
6554 $tmp_shy_replacement_char = '';
6556 $tmp_shy_replacement_width = $shy_replacement_width;
6557 $tmp_shy_replacement_char = $shy_replacement_char;
6563 // update string length
6564 if ($this->isUnicodeFont() AND ($arabic)) {
6565 // with bidirectional algorithm some chars may be changed affecting the line length
6566 // *** very slow ***
6567 $l = $this->GetArrStringWidth(TCPDF_FONTS
::utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl
, $this->isunicode
, $this->CurrentFont
));
6569 $l +
= $this->GetCharWidth($c, ($i+
1 < $nb));
6571 if (($l > $wmax) OR (($c == 173) AND (($l +
$tmp_shy_replacement_width) >= $wmax))) {
6572 if (($c == 173) AND (($l +
$tmp_shy_replacement_width) > $wmax)) {
6576 // we have reached the end of column
6578 // check if the line was already started
6579 if (($this->rtl
AND ($this->x
<= ($this->w
- $this->rMargin
- $this->cell_padding
['R'] - $margin['R'] - $chrwidth)))
6580 OR ((!$this->rtl
) AND ($this->x
>= ($this->lMargin +
$this->cell_padding
['L'] +
$margin['L'] +
$chrwidth)))) {
6581 // print a void cell and go to next line
6582 $this->Cell($w, $h, '', 0, 1);
6585 return (TCPDF_FONTS
::UniArrSubString($uchars, $j));
6588 // truncate the word because do not fit on column
6589 $tmpstr = TCPDF_FONTS
::UniArrSubString($uchars, $j, $i);
6592 $tmparr = array_slice($chars, $j, ($i - $j));
6594 $tmparr = TCPDF_FONTS
::utf8Bidi($tmparr, $tmpstr, $this->tmprtl
, $this->isunicode
, $this->CurrentFont
);
6596 $linew = $this->GetArrStringWidth($tmparr);
6599 $this->endlinex
= $startx - $linew;
6601 $this->endlinex
= $startx +
$linew;
6604 $tmpcellpadding = $this->cell_padding
;
6606 $this->setCellPadding(0);
6609 if ($firstblock AND $this->isRTLTextDir()) {
6610 $tmpstr = $this->stringRightTrim($tmpstr);
6612 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6615 $this->cell_padding
= $tmpcellpadding;
6616 return (TCPDF_FONTS
::UniArrSubString($uchars, $i));
6623 if ($this->rtl
AND (!$firstblock) AND ($sep < $i)) {
6628 // check the length of the next string
6629 $strrest = TCPDF_FONTS
::UniArrSubString($uchars, ($sep +
$endspace));
6630 $nextstr = TCPDF_STATIC
::pregSplit('/'.$this->re_space
['p'].'/', $this->re_space
['m'], $this->stringTrim($strrest));
6631 if (isset($nextstr[0]) AND ($this->GetStringWidth($nextstr[0]) > $pw)) {
6632 // truncate the word because do not fit on a full page width
6633 $tmpstr = TCPDF_FONTS
::UniArrSubString($uchars, $j, $i);
6636 $tmparr = array_slice($chars, $j, ($i - $j));
6638 $tmparr = TCPDF_FONTS
::utf8Bidi($tmparr, $tmpstr, $this->tmprtl
, $this->isunicode
, $this->CurrentFont
);
6640 $linew = $this->GetArrStringWidth($tmparr);
6643 $this->endlinex
= ($startx - $linew);
6645 $this->endlinex
= ($startx +
$linew);
6648 $tmpcellpadding = $this->cell_padding
;
6650 $this->setCellPadding(0);
6653 if ($firstblock AND $this->isRTLTextDir()) {
6654 $tmpstr = $this->stringRightTrim($tmpstr);
6656 $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
6659 $this->cell_padding
= $tmpcellpadding;
6660 return (TCPDF_FONTS
::UniArrSubString($uchars, $i));
6667 // add hypen (minus symbol) at the end of the line
6668 $shy_width = $tmp_shy_replacement_width;
6670 $shy_char_left = $tmp_shy_replacement_char;
6671 $shy_char_right = '';
6673 $shy_char_left = '';
6674 $shy_char_right = $tmp_shy_replacement_char;
6678 $shy_char_left = '';
6679 $shy_char_right = '';
6681 $tmpstr = TCPDF_FONTS
::UniArrSubString($uchars, $j, ($sep +
$endspace));
6684 $tmparr = array_slice($chars, $j, (($sep +
$endspace) - $j));
6686 $tmparr = TCPDF_FONTS
::utf8Bidi($tmparr, $tmpstr, $this->tmprtl
, $this->isunicode
, $this->CurrentFont
);
6688 $linew = $this->GetArrStringWidth($tmparr);
6691 $this->endlinex
= $startx - $linew - $shy_width;
6693 $this->endlinex
= $startx +
$linew +
$shy_width;
6696 $tmpcellpadding = $this->cell_padding
;
6698 $this->setCellPadding(0);
6702 if ($firstblock AND $this->isRTLTextDir()) {
6703 $tmpstr = $this->stringRightTrim($tmpstr);
6705 $this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
6708 if ($chars[$sep] == 45) {
6711 // return the remaining text
6712 $this->cell_padding
= $tmpcellpadding;
6713 return (TCPDF_FONTS
::UniArrSubString($uchars, ($sep +
$endspace)));
6721 // account for margin changes
6722 if ((($this->y +
$this->lasth
) > $this->PageBreakTrigger
) AND ($this->inPageBody())) {
6723 if ($this->AcceptPageBreak())
6726 $this->x
-= $margin['R'];
6728 $this->x +
= $margin['L'];
6730 $this->lMargin +
= $margin['L'];
6731 $this->rMargin +
= $margin['R'];
6734 $w = $this->getRemainingWidth();
6735 $wmax = $w - $this->cell_padding
['L'] - $this->cell_padding
['R'];
6744 // save last character
6747 } // end while i < nb
6748 // print last substring (if any)
6772 $tmpstr = TCPDF_FONTS
::UniArrSubString($uchars, $j, $nb);
6775 $tmparr = array_slice($chars, $j, ($nb - $j));
6777 $tmparr = TCPDF_FONTS
::utf8Bidi($tmparr, $tmpstr, $this->tmprtl
, $this->isunicode
, $this->CurrentFont
);
6779 $linew = $this->GetArrStringWidth($tmparr);
6782 $this->endlinex
= $startx - $linew;
6784 $this->endlinex
= $startx +
$linew;
6787 $tmpcellpadding = $this->cell_padding
;
6789 $this->setCellPadding(0);
6792 if ($firstblock AND $this->isRTLTextDir()) {
6793 $tmpstr = $this->stringRightTrim($tmpstr);
6795 $this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
6798 $this->cell_padding
= $tmpcellpadding;
6799 return (TCPDF_FONTS
::UniArrSubString($uchars, $nb));
6810 * Returns the remaining width between the current position and margins.
6811 * @return float Return the remaining width
6814 protected function getRemainingWidth() {
6815 list($this->x
, $this->y
) = $this->checkPageRegions(0, $this->x
, $this->y
);
6817 return ($this->x
- $this->lMargin
);
6819 return ($this->w
- $this->rMargin
- $this->x
);
6824 * Set the block dimensions accounting for page breaks and page/column fitting
6825 * @param float $w width
6826 * @param float $h height
6827 * @param float $x X coordinate
6828 * @param float $y Y coodiante
6829 * @param boolean $fitonpage if true the block is resized to not exceed page dimensions.
6830 * @return array array($w, $h, $x, $y)
6832 * @since 5.5.009 (2010-07-05)
6834 protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6836 // set maximum width
6837 $w = ($this->w
- $this->lMargin
- $this->rMargin
);
6843 // set maximum height
6844 $h = ($this->PageBreakTrigger
- $this->tMargin
);
6849 // resize the block to be vertically contained on a single page or single column
6850 if ($fitonpage OR $this->AutoPageBreak
) {
6851 $ratio_wh = ($w / $h);
6852 if ($h > ($this->PageBreakTrigger
- $this->tMargin
)) {
6853 $h = $this->PageBreakTrigger
- $this->tMargin
;
6854 $w = ($h * $ratio_wh);
6856 // resize the block to be horizontally contained on a single page or single column
6858 $maxw = ($this->w
- $this->lMargin
- $this->rMargin
);
6861 $h = ($w / $ratio_wh);
6865 // Check whether we need a new page or new column first as this does not fit
6868 if ($this->checkPageBreak($h, $y) OR ($this->y
< $prev_y)) {
6871 $x +
= ($prev_x - $this->x
);
6873 $x +
= ($this->x
- $prev_x);
6875 $this->newline
= true;
6877 // resize the block to be contained on the remaining available page or column space
6879 // fallback to avoid division by zero
6880 $h = $h == 0 ?
1 : $h;
6881 $ratio_wh = ($w / $h);
6882 if (($y +
$h) > $this->PageBreakTrigger
) {
6883 $h = $this->PageBreakTrigger
- $y;
6884 $w = ($h * $ratio_wh);
6886 if ((!$this->rtl
) AND (($x +
$w) > ($this->w
- $this->rMargin
))) {
6887 $w = $this->w
- $this->rMargin
- $x;
6888 $h = ($w / $ratio_wh);
6889 } elseif (($this->rtl
) AND (($x - $w) < ($this->lMargin
))) {
6890 $w = $x - $this->lMargin
;
6891 $h = ($w / $ratio_wh);
6894 return array($w, $h, $x, $y);
6898 * Puts an image in the page.
6899 * The upper-left corner must be given.
6900 * The dimensions can be specified in different ways:<ul>
6901 * <li>explicit width and height (expressed in user unit)</li>
6902 * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
6903 * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
6904 * 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;
6905 * The format can be specified explicitly or inferred from the file extension.<br />
6906 * It is possible to put a link on the image.<br />
6907 * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
6908 * @param string $file 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').
6909 * @param float|null $x Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
6910 * @param float|null $y Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
6911 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
6912 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
6913 * @param string $type 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.
6914 * @param mixed $link URL or identifier returned by AddLink().
6915 * @param string $align 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>
6916 * @param mixed $resize 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).
6917 * @param int $dpi dot-per-inch resolution used on resize
6918 * @param string $palign 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>
6919 * @param boolean $ismask true if this image is a mask, false otherwise
6920 * @param mixed $imgmask image object returned by this function or false
6921 * @param mixed $border 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)))
6922 * @param mixed $fitbox 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).
6923 * @param boolean $hidden If true do not display the image.
6924 * @param boolean $fitonpage If true the image is resized to not exceed page dimensions.
6925 * @param boolean $alt If true the image will be added as alternative and not directly printed (the ID of the image will be returned).
6926 * @param array $altimgs 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.
6927 * @return mixed|false image information
6931 public function Image($file, $x=null, $y=null, $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()) {
6932 if ($this->state
!= 2) {
6935 if (TCPDF_STATIC
::empty_string($x)) {
6938 if (TCPDF_STATIC
::empty_string($y)) {
6941 // check page for no-write regions and adapt page margins if necessary
6942 list($x, $y) = $this->checkPageRegions($h, $x, $y);
6943 $exurl = ''; // external streams
6946 // Make sure the file variable is not empty or null because accessing $file[0] later
6947 // results in error when running PHP 7.4
6951 // check if we are passing an image as file or string
6952 if ($file[0] === '@') {
6953 // image from string
6954 $imgdata = substr($file, 1);
6955 } else { // image file
6956 if ($file[0] === '*') {
6957 // image as external stream
6958 $file = substr($file, 1);
6961 // check if file exist and it is valid
6962 if (!@$this->fileExists($file)) {
6965 if (false !== $info = $this->getImageBuffer($file)) {
6966 $imsize = array($info['w'], $info['h']);
6967 } elseif (($imsize = @getimagesize
($file)) === FALSE && strpos($file, '__tcpdf_'.$this->file_id
.'_img') === FALSE){
6968 $imgdata = $this->getCachedFileContents($file);
6971 if (!empty($imgdata)) {
6972 // copy image to cache
6973 $original_file = $file;
6974 $file = TCPDF_STATIC
::getObjFilename('img', $this->file_id
);
6975 $fp = TCPDF_STATIC
::fopenLocal($file, 'w');
6977 $this->Error('Unable to write file: '.$file);
6979 fwrite($fp, $imgdata);
6982 $imsize = @getimagesize
($file);
6983 if ($imsize === FALSE) {
6985 $file = $original_file;
6988 if ($imsize === FALSE) {
6989 if (($w > 0) AND ($h > 0)) {
6990 // get measures from specified data
6991 $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit
, true) * $this->imgscale
* $this->k
;
6992 $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit
, true) * $this->imgscale
* $this->k
;
6993 $imsize = array($pw, $ph);
6995 $this->Error('[Image] Unable to get the size of the image: '.$file);
6999 $filehash = md5($file);
7000 // get original image width and height in pixels
7001 list($pixw, $pixh) = $imsize;
7002 // calculate image width and height on document
7003 if (($w <= 0) AND ($h <= 0)) {
7004 // convert image size to document unit
7005 $w = $this->pixelsToUnits($pixw);
7006 $h = $this->pixelsToUnits($pixh);
7007 } elseif ($w <= 0) {
7008 $w = $h * $pixw / $pixh;
7009 } elseif ($h <= 0) {
7010 $h = $w * $pixh / $pixw;
7011 } elseif (($fitbox !== false) AND ($w > 0) AND ($h > 0)) {
7012 if (strlen($fitbox) !== 2) {
7013 // set default alignment
7016 // scale image dimensions proportionally to fit within the ($w, $h) box
7017 if ((($w * $pixh) / ($h * $pixw)) < 1) {
7018 // store current height
7020 // calculate new height
7021 $h = $w * $pixh / $pixw;
7022 // height difference
7023 $hdiff = ($oldh - $h);
7024 // vertical alignment
7025 switch (strtoupper($fitbox[1])) {
7039 // store current width
7041 // calculate new width
7042 $w = $h * $pixw / $pixh;
7044 $wdiff = ($oldw - $w);
7045 // horizontal alignment
7046 switch (strtoupper($fitbox[0])) {
7070 // fit the image on available space
7071 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
7072 // calculate new minimum dimensions in pixels
7073 $neww = round($w * $this->k
* $dpi / $this->dpi
);
7074 $newh = round($h * $this->k
* $dpi / $this->dpi
);
7075 // check if resize is necessary (resize is used only to reduce the image)
7076 $newsize = ($neww * $newh);
7077 $pixsize = ($pixw * $pixh);
7078 if (intval($resize) == 2) {
7080 } elseif ($newsize >= $pixsize) {
7083 // check if image has been already added on document
7085 if (in_array($file, $this->imagekeys
)) {
7087 // get existing image data
7088 $info = $this->getImageBuffer($file);
7089 if (strpos($file, '__tcpdf_'.$this->file_id
.'_imgmask_') === FALSE) {
7090 // check if the newer image is larger
7091 $oldsize = ($info['w'] * $info['h']);
7092 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7096 } elseif (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id
.'_imgmask_') === FALSE)) {
7097 // create temp image file (without alpha channel)
7098 $tempfile_plain = K_PATH_CACHE
.'__tcpdf_'.$this->file_id
.'_imgmask_plain_'.$filehash;
7099 // create temp alpha file
7100 $tempfile_alpha = K_PATH_CACHE
.'__tcpdf_'.$this->file_id
.'_imgmask_alpha_'.$filehash;
7101 // check for cached images
7102 if (in_array($tempfile_plain, $this->imagekeys
)) {
7103 // get existing image data
7104 $info = $this->getImageBuffer($tempfile_plain);
7105 // check if the newer image is larger
7106 $oldsize = ($info['w'] * $info['h']);
7107 if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
7112 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7113 // embed image, masked with previously embedded mask
7114 return $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7119 //First use of image, get info
7120 $type = strtolower($type);
7122 $type = TCPDF_IMAGES
::getImageFileType($file, $imsize);
7123 } elseif ($type == 'jpg') {
7126 // Specific image handlers (defined on TCPDF_IMAGES CLASS)
7127 $mtd = '_parse'.$type;
7128 // GD image handler function
7129 $gdfunction = 'imagecreatefrom'.$type;
7131 if ((method_exists('TCPDF_IMAGES', $mtd)) AND (!($resize AND (function_exists($gdfunction) OR extension_loaded('imagick'))))) {
7132 // TCPDF image functions
7133 $info = TCPDF_IMAGES
::$mtd($file);
7134 if (($ismask === false) AND ($imgmask === false) AND (strpos($file, '__tcpdf_'.$this->file_id
.'_imgmask_') === FALSE)
7135 AND (($info === 'pngalpha') OR (isset($info['trns']) AND !empty($info['trns'])))) {
7136 return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign, $filehash);
7139 if (($info === false) AND function_exists($gdfunction)) {
7142 $img = $gdfunction($file);
7143 if ($img !== false) {
7145 $imgr = imagecreatetruecolor($neww, $newh);
7146 if (($type == 'gif') OR ($type == 'png')) {
7147 $imgr = TCPDF_IMAGES
::setGDImageTransparency($imgr, $img);
7149 imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
7152 if (($type == 'gif') OR ($type == 'png')) {
7153 $info = TCPDF_IMAGES
::_toPNG($img, TCPDF_STATIC
::getObjFilename('img', $this->file_id
));
7155 $info = TCPDF_IMAGES
::_toJPEG($img, $this->jpeg_quality
, TCPDF_STATIC
::getObjFilename('img', $this->file_id
));
7158 } catch(Exception
$e) {
7162 if (($info === false) AND extension_loaded('imagick')) {
7164 // ImageMagick library
7165 $img = new Imagick();
7166 if ($type == 'svg') {
7167 if ($file[0] === '@') {
7168 // image from string
7169 $svgimg = substr($file, 1);
7171 // get SVG file content
7172 $svgimg = $this->getCachedFileContents($file);
7174 if ($svgimg !== FALSE) {
7175 // get width and height
7177 if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7180 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7181 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit
, false);
7182 $owu = sprintf('%F', ($ow * $dpi / 72)).$this->pdfunit
;
7183 $svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
7188 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
7189 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit
, false);
7190 $ohu = sprintf('%F', ($oh * $dpi / 72)).$this->pdfunit
;
7191 $svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
7196 if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
7197 $vbw = ($ow * $this->imgscale
* $this->k
);
7198 $vbh = ($oh * $this->imgscale
* $this->k
);
7199 $vbox = sprintf(' viewBox="0 0 %F %F" ', $vbw, $vbh);
7200 $svgtag = $vbox.$svgtag;
7202 $svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
7204 $img->readImageBlob($svgimg);
7207 $img->readImage($file);
7210 $img->resizeImage($neww, $newh, 10, 1, false);
7212 $img->setCompressionQuality($this->jpeg_quality
);
7213 $img->setImageFormat('jpeg');
7214 $tempname = TCPDF_STATIC
::getObjFilename('img', $this->file_id
);
7215 $img->writeImage($tempname);
7216 $info = TCPDF_IMAGES
::_parsejpeg($tempname);
7219 } catch(Exception
$e) {
7223 if ($info === false) {
7224 // unable to process image
7229 $info['cs'] = 'DeviceGray';
7231 if ($imgmask !== false) {
7232 $info['masked'] = $imgmask;
7234 if (!empty($exurl)) {
7235 $info['exurl'] = $exurl;
7237 // array of alternative images
7238 $info['altimgs'] = $altimgs;
7239 // add image to document
7240 $info['i'] = $this->setImageBuffer($file, $info);
7243 $this->img_rb_x
= $x +
$w;
7244 $this->img_rb_y
= $y +
$h;
7247 if ($palign == 'L') {
7248 $ximg = $this->lMargin
;
7249 } elseif ($palign == 'C') {
7250 $ximg = ($this->w +
$this->lMargin
- $this->rMargin
- $w) / 2;
7251 } elseif ($palign == 'R') {
7252 $ximg = $this->w
- $this->rMargin
- $w;
7254 $ximg = $this->rtl ?
$x - $w : $x;
7257 if ($ismask OR $hidden) {
7258 // image is not displayed
7261 $xkimg = $ximg * $this->k
;
7263 // only non-alternative immages will be set
7264 $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']));
7266 if (!empty($border)) {
7274 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7279 $this->Link($ximg, $y, $w, $h, $link, 0);
7281 // set pointer to align the next text/objects
7285 $this->x
= $this->img_rb_x
;
7289 $this->y
= $y +
round($h/2);
7290 $this->x
= $this->img_rb_x
;
7294 $this->y
= $this->img_rb_y
;
7295 $this->x
= $this->img_rb_x
;
7299 $this->setY($this->img_rb_y
);
7306 $this->endlinex
= $this->img_rb_x
;
7307 if ($this->inxobj
) {
7308 // we are inside an XObject template
7309 $this->xobjects
[$this->xobjid
]['images'][] = $info['i'];
7315 * Extract info from a PNG image with alpha channel using the Imagick or GD library.
7316 * @param string $file Name of the file containing the image.
7317 * @param float $x Abscissa of the upper-left corner.
7318 * @param float $y Ordinate of the upper-left corner.
7319 * @param float $wpx Original width of the image in pixels.
7320 * @param float $hpx original height of the image in pixels.
7321 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
7322 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
7323 * @param string $type 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.
7324 * @param mixed $link URL or identifier returned by AddLink().
7325 * @param string $align 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>
7326 * @param boolean $resize If true resize (reduce) the image to fit $w and $h (requires GD library).
7327 * @param int $dpi dot-per-inch resolution used on resize
7328 * @param string $palign 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>
7329 * @param string $filehash File hash used to build unique file names.
7330 * @author Nicola Asuni
7332 * @since 4.3.007 (2008-12-04)
7335 protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign, $filehash='') {
7336 // create temp images
7337 if (empty($filehash)) {
7338 $filehash = md5($file);
7340 // create temp image file (without alpha channel)
7341 $tempfile_plain = K_PATH_CACHE
.'__tcpdf_'.$this->file_id
.'_imgmask_plain_'.$filehash;
7342 // create temp alpha file
7343 $tempfile_alpha = K_PATH_CACHE
.'__tcpdf_'.$this->file_id
.'_imgmask_alpha_'.$filehash;
7346 // ImageMagick extension
7347 if (($parsed === false) AND extension_loaded('imagick')) {
7349 // ImageMagick library
7350 $img = new Imagick();
7351 $img->readImage($file);
7352 // clone image object
7353 $imga = TCPDF_STATIC
::objclone($img);
7354 // extract alpha channel
7355 if (method_exists($img, 'setImageAlphaChannel') AND defined('Imagick::ALPHACHANNEL_EXTRACT')) {
7356 $img->setImageAlphaChannel(Imagick
::ALPHACHANNEL_EXTRACT
);
7358 $img->separateImageChannel(8); // 8 = (imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
7359 $img->negateImage(true);
7361 $img->setImageFormat('png');
7362 $img->writeImage($tempfile_alpha);
7363 // remove alpha channel
7364 if (method_exists($imga, 'setImageMatte')) {
7365 $imga->setImageMatte(false);
7367 $imga->separateImageChannel(39); // 39 = (imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
7369 $imga->setImageFormat('png');
7370 $imga->writeImage($tempfile_plain);
7372 } catch (Exception
$e) {
7373 // Imagemagick fails, try with GD
7374 $parse_error = 'Imagick library error: '.$e->getMessage();
7378 if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7381 $img = imagecreatefrompng($file);
7382 $imgalpha = imagecreate($wpx, $hpx);
7383 // generate gray scale palette (0 -> 255)
7384 for ($c = 0; $c < 256; ++
$c) {
7385 ImageColorAllocate($imgalpha, $c, $c, $c);
7387 // extract alpha channel
7388 for ($xpx = 0; $xpx < $wpx; ++
$xpx) {
7389 for ($ypx = 0; $ypx < $hpx; ++
$ypx) {
7390 $color = imagecolorat($img, $xpx, $ypx);
7391 // get and correct gamma color
7392 $alpha = $this->getGDgamma($img, $color);
7393 imagesetpixel($imgalpha, (int) $xpx, (int) $ypx, (int) $alpha);
7396 imagepng($imgalpha, $tempfile_alpha);
7397 imagedestroy($imgalpha);
7398 // extract image without alpha channel
7399 $imgplain = imagecreatetruecolor($wpx, $hpx);
7400 imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
7401 imagepng($imgplain, $tempfile_plain);
7402 imagedestroy($imgplain);
7404 } catch (Exception
$e) {
7406 $parse_error = 'GD library error: '.$e->getMessage();
7409 if ($parsed === false) {
7410 if (empty($parse_error)) {
7411 $this->Error('TCPDF requires the Imagick or GD extension to handle PNG images with alpha channel.');
7413 $this->Error($parse_error);
7417 $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
7418 // embed image, masked with previously embedded mask
7419 $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
7423 * Get the GD-corrected PNG gamma value from alpha color
7424 * @param resource $img GD image Resource ID.
7425 * @param int $c alpha color
7427 * @since 4.3.007 (2008-12-04)
7429 protected function getGDgamma($img, $c) {
7430 if (!isset($this->gdgammacache
['#'.$c])) {
7431 $colors = imagecolorsforindex($img, $c);
7432 // GD alpha is only 7 bit (0 -> 127)
7433 $this->gdgammacache
['#'.$c] = (int) (((127 - $colors['alpha']) / 127) * 255);
7435 $this->gdgammacache
['#'.$c] = (int) (pow(($this->gdgammacache
['#'.$c] / 255), 2.2) * 255);
7436 // store the latest values on cache to improve performances
7437 if (count($this->gdgammacache
) > 8) {
7438 // remove one element from the cache array
7439 array_shift($this->gdgammacache
);
7442 return $this->gdgammacache
['#'.$c];
7446 * Performs a line break.
7447 * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
7448 * @param float|null $h The height of the break. By default, the value equals the height of the last printed cell.
7449 * @param boolean $cell if true add the current left (or right o for RTL) padding to the X coordinate
7454 public function Ln($h=null, $cell=false) {
7455 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'])) {
7456 // revove vertical space from the top of the column
7461 $cellpadding = $this->cell_padding
['R'];
7463 $cellpadding = $this->cell_padding
['L'];
7469 $this->x
= $this->w
- $this->rMargin
- $cellpadding;
7471 $this->x
= $this->lMargin +
$cellpadding;
7473 if (TCPDF_STATIC
::empty_string($h)) {
7477 $this->newline
= true;
7481 * Returns the relative X value of current position.
7482 * The value is relative to the left border for LTR languages and to the right border for RTL languages.
7486 * @see SetX(), GetY(), SetY()
7488 public function GetX() {
7491 return ($this->w
- $this->x
);
7498 * Returns the absolute X value of current position.
7502 * @see SetX(), GetY(), SetY()
7504 public function GetAbsX() {
7509 * Returns the ordinate of the current position.
7513 * @see SetY(), GetX(), SetX()
7515 public function GetY() {
7520 * Defines the abscissa of the current position.
7521 * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
7522 * @param float $x The value of the abscissa in user units.
7523 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7526 * @see GetX(), GetY(), SetY(), SetXY()
7528 public function setX($x, $rtloff=false) {
7530 if (!$rtloff AND $this->rtl
) {
7532 $this->x
= $this->w
- $x;
7540 $this->x
= $this->w +
$x;
7546 if ($this->x
> $this->w
) {
7547 $this->x
= $this->w
;
7552 * Moves the current abscissa back to the left margin and sets the ordinate.
7553 * If the passed value is negative, it is relative to the bottom of the page.
7554 * @param float $y The value of the ordinate in user units.
7555 * @param bool $resetx if true (default) reset the X position.
7556 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7559 * @see GetX(), GetY(), SetY(), SetXY()
7561 public function setY($y, $resetx=true, $rtloff=false) {
7565 if (!$rtloff AND $this->rtl
) {
7566 $this->x
= $this->w
- $this->rMargin
;
7568 $this->x
= $this->lMargin
;
7574 $this->y
= $this->h +
$y;
7579 if ($this->y
> $this->h
) {
7580 $this->y
= $this->h
;
7585 * Defines the abscissa and ordinate of the current position.
7586 * If the passed values are negative, they are relative respectively to the right and bottom of the page.
7587 * @param float $x The value of the abscissa.
7588 * @param float $y The value of the ordinate.
7589 * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
7592 * @see SetX(), SetY()
7594 public function setXY($x, $y, $rtloff=false) {
7595 $this->setY($y, false, $rtloff);
7596 $this->setX($x, $rtloff);
7600 * Set the absolute X coordinate of the current pointer.
7601 * @param float $x The value of the abscissa in user units.
7603 * @since 5.9.186 (2012-09-13)
7604 * @see setAbsX(), setAbsY(), SetAbsXY()
7606 public function setAbsX($x) {
7607 $this->x
= floatval($x);
7611 * Set the absolute Y coordinate of the current pointer.
7612 * @param float $y (float) The value of the ordinate in user units.
7614 * @since 5.9.186 (2012-09-13)
7615 * @see setAbsX(), setAbsY(), SetAbsXY()
7617 public function setAbsY($y) {
7618 $this->y
= floatval($y);
7622 * Set the absolute X and Y coordinates of the current pointer.
7623 * @param float $x The value of the abscissa in user units.
7624 * @param float $y (float) The value of the ordinate in user units.
7626 * @since 5.9.186 (2012-09-13)
7627 * @see setAbsX(), setAbsY(), SetAbsXY()
7629 public function setAbsXY($x, $y) {
7635 * Send the document to a given destination: string, local file or browser.
7636 * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
7637 * The method first calls Close() if necessary to terminate the document.
7638 * @param string $name The name of the file when saved
7639 * @param string $dest 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>
7645 public function Output($name='doc.pdf', $dest='I') {
7646 //Output PDF to some destination
7647 //Finish document if necessary
7648 if ($this->state
< 3) {
7651 //Normalize parameters
7652 if (is_bool($dest)) {
7653 $dest = $dest ?
'D' : 'F';
7655 $dest = strtoupper($dest);
7658 // *** apply digital signature to the document ***
7659 // get the document content
7660 $pdfdoc = $this->getBuffer();
7661 // remove last newline
7662 $pdfdoc = substr($pdfdoc, 0, -1);
7663 // remove filler space
7664 $byterange_string_len = strlen(TCPDF_STATIC
::$byterange_string);
7665 // define the ByteRange
7666 $byte_range = array();
7668 $byte_range[1] = strpos($pdfdoc, TCPDF_STATIC
::$byterange_string) +
$byterange_string_len +
10;
7669 $byte_range[2] = $byte_range[1] +
$this->signature_max_length +
2;
7670 $byte_range[3] = strlen($pdfdoc) - $byte_range[2];
7671 $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
7672 // replace the ByteRange
7673 $byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
7674 $byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
7675 $pdfdoc = str_replace(TCPDF_STATIC
::$byterange_string, $byterange, $pdfdoc);
7676 // write the document to a temporary folder
7677 $tempdoc = TCPDF_STATIC
::getObjFilename('doc', $this->file_id
);
7678 $f = TCPDF_STATIC
::fopenLocal($tempdoc, 'wb');
7680 $this->Error('Unable to create temporary file: '.$tempdoc);
7682 $pdfdoc_length = strlen($pdfdoc);
7683 fwrite($f, $pdfdoc, $pdfdoc_length);
7685 // get digital signature via openssl library
7686 $tempsign = TCPDF_STATIC
::getObjFilename('sig', $this->file_id
);
7687 if (empty($this->signature_data
['extracerts'])) {
7688 openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data
['signcert'], array($this->signature_data
['privkey'], $this->signature_data
['password']), array(), PKCS7_BINARY | PKCS7_DETACHED
);
7690 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']);
7693 $signature = file_get_contents($tempsign);
7694 // extract signature
7695 $signature = substr($signature, $pdfdoc_length);
7696 $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") +
13));
7697 $tmparr = explode("\n\n", $signature);
7698 $signature = $tmparr[1];
7700 $signature = base64_decode(trim($signature));
7701 // add TSA timestamp to signature
7702 $signature = $this->applyTSA($signature);
7703 // convert signature to hex
7704 $signature = current(unpack('H*', $signature));
7705 $signature = str_pad($signature, $this->signature_max_length
, '0');
7706 // Add signature to the document
7707 $this->buffer
= substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
7708 $this->bufferlen
= strlen($this->buffer
);
7712 // Send PDF to the standard output
7713 if (ob_get_contents()) {
7714 $this->Error('Some data has already been output, can\'t send PDF file');
7716 if (php_sapi_name() != 'cli') {
7717 // send output to a browser
7718 header('Content-Type: application/pdf');
7719 if (headers_sent()) {
7720 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7722 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7723 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7724 header('Pragma: public');
7725 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7726 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7727 header('Content-Disposition: inline; filename="' . rawurlencode(basename($name)) . '"; ' .
7728 'filename*=UTF-8\'\'' . rawurlencode(basename($name)));
7729 TCPDF_STATIC
::sendOutputData($this->getBuffer(), $this->bufferlen
);
7731 echo $this->getBuffer();
7736 // download PDF as file
7737 if (ob_get_contents()) {
7738 $this->Error('Some data has already been output, can\'t send PDF file');
7740 header('Content-Description: File Transfer');
7741 if (headers_sent()) {
7742 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7744 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7745 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7746 header('Pragma: public');
7747 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7748 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7749 // force download dialog
7750 if (strpos(php_sapi_name(), 'cgi') === false) {
7751 header('Content-Type: application/force-download');
7752 header('Content-Type: application/octet-stream', false);
7753 header('Content-Type: application/download', false);
7754 header('Content-Type: application/pdf', false);
7756 header('Content-Type: application/pdf');
7758 // use the Content-Disposition header to supply a recommended filename
7759 header('Content-Disposition: attachment; filename="' . rawurlencode(basename($name)) . '"; ' .
7760 'filename*=UTF-8\'\'' . rawurlencode(basename($name)));
7761 header('Content-Transfer-Encoding: binary');
7762 TCPDF_STATIC
::sendOutputData($this->getBuffer(), $this->bufferlen
);
7768 // save PDF to a local file
7769 $f = TCPDF_STATIC
::fopenLocal($name, 'wb');
7771 $this->Error('Unable to create output file: '.$name);
7773 fwrite($f, $this->getBuffer(), $this->bufferlen
);
7775 if ($dest == 'FI') {
7776 // send headers to browser
7777 header('Content-Type: application/pdf');
7778 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7779 //header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
7780 header('Pragma: public');
7781 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7782 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7783 header('Content-Disposition: inline; filename="'.basename($name).'"');
7784 TCPDF_STATIC
::sendOutputData(file_get_contents($name), filesize($name));
7785 } elseif ($dest == 'FD') {
7786 // send headers to browser
7787 if (ob_get_contents()) {
7788 $this->Error('Some data has already been output, can\'t send PDF file');
7790 header('Content-Description: File Transfer');
7791 if (headers_sent()) {
7792 $this->Error('Some data has already been output to browser, can\'t send PDF file');
7794 header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
7795 header('Pragma: public');
7796 header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
7797 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
7798 // force download dialog
7799 if (strpos(php_sapi_name(), 'cgi') === false) {
7800 header('Content-Type: application/force-download');
7801 header('Content-Type: application/octet-stream', false);
7802 header('Content-Type: application/download', false);
7803 header('Content-Type: application/pdf', false);
7805 header('Content-Type: application/pdf');
7807 // use the Content-Disposition header to supply a recommended filename
7808 header('Content-Disposition: attachment; filename="'.basename($name).'"');
7809 header('Content-Transfer-Encoding: binary');
7810 TCPDF_STATIC
::sendOutputData(file_get_contents($name), filesize($name));
7815 // return PDF as base64 mime multi-part email attachment (RFC 2045)
7816 $retval = 'Content-Type: application/pdf;'."\r\n";
7817 $retval .= ' name="'.$name.'"'."\r\n";
7818 $retval .= 'Content-Transfer-Encoding: base64'."\r\n";
7819 $retval .= 'Content-Disposition: attachment;'."\r\n";
7820 $retval .= ' filename="'.$name.'"'."\r\n\r\n";
7821 $retval .= chunk_split(base64_encode($this->getBuffer()), 76, "\r\n");
7825 // returns PDF as a string
7826 return $this->getBuffer();
7829 $this->Error('Incorrect output destination: '.$dest);
7835 protected static $cleaned_ids = array();
7837 * Unset all class variables except the following critical variables.
7838 * @param boolean $destroyall if true destroys all class variables, otherwise preserves critical variables.
7839 * @param boolean $preserve_objcopy if true preserves the objcopy variable
7841 * @since 4.5.016 (2009-02-24)
7843 public function _destroy($destroyall=false, $preserve_objcopy=false) {
7844 if (isset(self
::$cleaned_ids[$this->file_id
])) {
7845 $destroyall = false;
7847 if ($destroyall AND !$preserve_objcopy && isset($this->file_id
)) {
7848 self
::$cleaned_ids[$this->file_id
] = true;
7849 // remove all temporary files
7850 if ($handle = @opendir
(K_PATH_CACHE
)) {
7851 while ( false !== ( $file_name = readdir( $handle ) ) ) {
7852 if (strpos($file_name, '__tcpdf_'.$this->file_id
.'_') === 0) {
7853 unlink(K_PATH_CACHE
.$file_name);
7858 if (isset($this->imagekeys
)) {
7859 foreach($this->imagekeys
as $file) {
7860 if (strpos($file, K_PATH_CACHE
) === 0 && TCPDF_STATIC
::file_exists($file)) {
7875 'signature_max_length',
7880 foreach (array_keys(get_object_vars($this)) as $val) {
7881 if ($destroyall OR !in_array($val, $preserve)) {
7882 if ((!$preserve_objcopy OR ($val != 'objcopy')) AND ($val != 'file_id') AND isset($this->$val)) {
7890 * Check for locale-related bug
7893 protected function _dochecks() {
7894 //Check for locale-related bug
7896 $this->Error('Don\'t alter the locale before including class file');
7898 //Check for decimal separator
7899 if (sprintf('%.1F', 1.0) != '1.0') {
7900 setlocale(LC_NUMERIC
, 'C');
7905 * Return an array containing variations for the basic page number alias.
7906 * @param string $a Base alias.
7907 * @return array of page number aliases
7910 protected function getInternalPageNumberAliases($a= '') {
7912 // build array of Unicode + ASCII variants (the order is important)
7913 $alias = array('u' => array(), 'a' => array());
7915 $alias['u'][] = TCPDF_STATIC
::_escape($u);
7916 if ($this->isunicode
) {
7917 $alias['u'][] = TCPDF_STATIC
::_escape(TCPDF_FONTS
::UTF8ToLatin1($u, $this->isunicode
, $this->CurrentFont
));
7918 $alias['u'][] = TCPDF_STATIC
::_escape(TCPDF_FONTS
::utf8StrRev($u, false, $this->tmprtl
, $this->isunicode
, $this->CurrentFont
));
7919 $alias['a'][] = TCPDF_STATIC
::_escape(TCPDF_FONTS
::UTF8ToLatin1($a, $this->isunicode
, $this->CurrentFont
));
7920 $alias['a'][] = TCPDF_STATIC
::_escape(TCPDF_FONTS
::utf8StrRev($a, false, $this->tmprtl
, $this->isunicode
, $this->CurrentFont
));
7922 $alias['a'][] = TCPDF_STATIC
::_escape($a);
7927 * Return an array containing all internal page aliases.
7928 * @return array of page number aliases
7931 protected function getAllInternalPageNumberAliases() {
7932 $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);
7934 foreach($basic_alias as $k => $a) {
7935 $pnalias[$k] = $this->getInternalPageNumberAliases($a);
7941 * Replace right shift page number aliases with spaces to correct right alignment.
7942 * This works perfectly only when using monospaced fonts.
7943 * @param string $page Page content.
7944 * @param array $aliases Array of page aliases.
7945 * @param int $diff initial difference to add.
7946 * @return string replaced page content.
7949 protected function replaceRightShiftPageNumAliases($page, $aliases, $diff) {
7950 foreach ($aliases as $type => $alias) {
7951 foreach ($alias as $a) {
7952 // find position of compensation factor
7953 $startnum = (strpos($a, ':') +
1);
7954 $a = substr($a, 0, $startnum);
7955 if (($pos = strpos($page, $a)) !== false) {
7957 $endnum = strpos($page, '}', $pos);
7958 // string to be replaced
7959 $aa = substr($page, $pos, ($endnum - $pos +
1));
7960 // get compensation factor
7961 $ratio = substr($page, ($pos +
$startnum), ($endnum - $pos - $startnum));
7962 $ratio = preg_replace('/[^0-9\.]/', '', $ratio);
7963 $ratio = floatval($ratio);
7965 $chrdiff = floor(($diff +
12) * $ratio);
7966 $shift = str_repeat(' ', $chrdiff);
7967 $shift = TCPDF_FONTS
::UTF8ToUTF16BE($shift, false, $this->isunicode
, $this->CurrentFont
);
7969 $chrdiff = floor(($diff +
11) * $ratio);
7970 $shift = str_repeat(' ', $chrdiff);
7972 $page = str_replace($aa, $shift, $page);
7980 * Set page boxes to be included on page descriptions.
7981 * @param array $boxes Array of page boxes to set on document: ('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox').
7984 protected function setPageBoxTypes($boxes) {
7985 $this->page_boxes
= array();
7986 foreach ($boxes as $box) {
7987 if (in_array($box, TCPDF_STATIC
::$pageboxes)) {
7988 $this->page_boxes
[] = $box;
7994 * Output pages (and replace page number aliases).
7997 protected function _putpages() {
7998 $filter = ($this->compress
) ?
'/Filter /FlateDecode ' : '';
7999 // get internal aliases for page numbers
8000 $pnalias = $this->getAllInternalPageNumberAliases();
8001 $num_pages = $this->numpages
;
8002 $ptpa = TCPDF_STATIC
::formatPageNumber(($this->starting_page_number +
$num_pages - 1));
8003 $ptpu = TCPDF_FONTS
::UTF8ToUTF16BE($ptpa, false, $this->isunicode
, $this->CurrentFont
);
8004 $ptp_num_chars = $this->GetNumChars($ptpa);
8010 for ($n = 1; $n <= $num_pages; ++
$n) {
8012 $temppage = $this->getPageBuffer($n);
8013 $pagelen = strlen($temppage);
8014 // set replacements for total pages number
8015 $pnpa = TCPDF_STATIC
::formatPageNumber(($this->starting_page_number +
$n - 1));
8016 $pnpu = TCPDF_FONTS
::UTF8ToUTF16BE($pnpa, false, $this->isunicode
, $this->CurrentFont
);
8017 $pnp_num_chars = $this->GetNumChars($pnpa);
8018 $pdiff = 0; // difference used for right shift alignment of page numbers
8019 $gdiff = 0; // difference used for right shift alignment of page group numbers
8020 if (!empty($this->pagegroups
)) {
8021 if (isset($this->newpagegroup
[$n])) {
8024 $ptga = TCPDF_STATIC
::formatPageNumber($this->pagegroups
[$groupnum]);
8025 $ptgu = TCPDF_FONTS
::UTF8ToUTF16BE($ptga, false, $this->isunicode
, $this->CurrentFont
);
8026 $ptg_num_chars = $this->GetNumChars($ptga);
8029 $pnga = TCPDF_STATIC
::formatPageNumber($pagegroupnum);
8030 $pngu = TCPDF_FONTS
::UTF8ToUTF16BE($pnga, false, $this->isunicode
, $this->CurrentFont
);
8031 $png_num_chars = $this->GetNumChars($pnga);
8032 // replace page numbers
8034 $replace[] = array($ptgu, $ptg_num_chars, 9, $pnalias[2]['u']);
8035 $replace[] = array($ptga, $ptg_num_chars, 7, $pnalias[2]['a']);
8036 $replace[] = array($pngu, $png_num_chars, 9, $pnalias[3]['u']);
8037 $replace[] = array($pnga, $png_num_chars, 7, $pnalias[3]['a']);
8038 list($temppage, $gdiff) = TCPDF_STATIC
::replacePageNumAliases($temppage, $replace, $gdiff);
8040 // replace page numbers
8042 $replace[] = array($ptpu, $ptp_num_chars, 9, $pnalias[0]['u']);
8043 $replace[] = array($ptpa, $ptp_num_chars, 7, $pnalias[0]['a']);
8044 $replace[] = array($pnpu, $pnp_num_chars, 9, $pnalias[1]['u']);
8045 $replace[] = array($pnpa, $pnp_num_chars, 7, $pnalias[1]['a']);
8046 list($temppage, $pdiff) = TCPDF_STATIC
::replacePageNumAliases($temppage, $replace, $pdiff);
8047 // replace right shift alias
8048 $temppage = $this->replaceRightShiftPageNumAliases($temppage, $pnalias[4], max($pdiff, $gdiff));
8049 // replace EPS marker
8050 $temppage = str_replace($this->epsmarker
, '', $temppage);
8052 $this->page_obj_id
[$n] = $this->_newobj();
8054 $out .= ' /Type /Page';
8055 $out .= ' /Parent 1 0 R';
8056 if (empty($this->signature_data
['approval']) OR ($this->signature_data
['approval'] != 'A')) {
8057 $out .= ' /LastModified '.$this->_datestring(0, $this->doc_modification_timestamp
);
8059 $out .= ' /Resources 2 0 R';
8060 foreach ($this->page_boxes
as $box) {
8062 $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']);
8064 if (isset($this->pagedim
[$n]['BoxColorInfo']) AND !empty($this->pagedim
[$n]['BoxColorInfo'])) {
8065 $out .= ' /BoxColorInfo <<';
8066 foreach ($this->page_boxes
as $box) {
8067 if (isset($this->pagedim
[$n]['BoxColorInfo'][$box])) {
8068 $out .= ' /'.$box.' <<';
8069 if (isset($this->pagedim
[$n]['BoxColorInfo'][$box]['C'])) {
8070 $color = $this->pagedim
[$n]['BoxColorInfo'][$box]['C'];
8072 $out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
8075 if (isset($this->pagedim
[$n]['BoxColorInfo'][$box]['W'])) {
8076 $out .= ' /W '.($this->pagedim
[$n]['BoxColorInfo'][$box]['W'] * $this->k
);
8078 if (isset($this->pagedim
[$n]['BoxColorInfo'][$box]['S'])) {
8079 $out .= ' /S /'.$this->pagedim
[$n]['BoxColorInfo'][$box]['S'];
8081 if (isset($this->pagedim
[$n]['BoxColorInfo'][$box]['D'])) {
8082 $dashes = $this->pagedim
[$n]['BoxColorInfo'][$box]['D'];
8084 foreach ($dashes as $dash) {
8085 $out .= sprintf(' %F', ($dash * $this->k
));
8094 $out .= ' /Contents '.($this->n +
1).' 0 R';
8095 $out .= ' /Rotate '.$this->pagedim
[$n]['Rotate'];
8096 if (!$this->pdfa_mode ||
$this->pdfa_version
>= 2) {
8097 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
8099 if (isset($this->pagedim
[$n]['trans']) AND !empty($this->pagedim
[$n]['trans'])) {
8101 if (isset($this->pagedim
[$n]['trans']['Dur'])) {
8102 $out .= ' /Dur '.$this->pagedim
[$n]['trans']['Dur'];
8104 $out .= ' /Trans <<';
8105 $out .= ' /Type /Trans';
8106 if (isset($this->pagedim
[$n]['trans']['S'])) {
8107 $out .= ' /S /'.$this->pagedim
[$n]['trans']['S'];
8109 if (isset($this->pagedim
[$n]['trans']['D'])) {
8110 $out .= ' /D '.$this->pagedim
[$n]['trans']['D'];
8112 if (isset($this->pagedim
[$n]['trans']['Dm'])) {
8113 $out .= ' /Dm /'.$this->pagedim
[$n]['trans']['Dm'];
8115 if (isset($this->pagedim
[$n]['trans']['M'])) {
8116 $out .= ' /M /'.$this->pagedim
[$n]['trans']['M'];
8118 if (isset($this->pagedim
[$n]['trans']['Di'])) {
8119 $out .= ' /Di '.$this->pagedim
[$n]['trans']['Di'];
8121 if (isset($this->pagedim
[$n]['trans']['SS'])) {
8122 $out .= ' /SS '.$this->pagedim
[$n]['trans']['SS'];
8124 if (isset($this->pagedim
[$n]['trans']['B'])) {
8125 $out .= ' /B '.$this->pagedim
[$n]['trans']['B'];
8129 $out .= $this->_getannotsrefs($n);
8130 $out .= ' /PZ '.$this->pagedim
[$n]['PZ'];
8132 $out .= "\n".'endobj';
8135 $p = ($this->compress
) ?
gzcompress($temppage) : $temppage;
8137 $p = $this->_getrawstream($p);
8138 $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8141 $out = $this->_getobj(1)."\n";
8142 $out .= '<< /Type /Pages /Kids [';
8143 foreach($this->page_obj_id
as $page_obj) {
8144 $out .= ' '.$page_obj.' 0 R';
8146 $out .= ' ] /Count '.$num_pages.' >>';
8147 $out .= "\n".'endobj';
8152 * Get references to page annotations.
8153 * @param int $n page number
8156 * @author Nicola Asuni
8157 * @since 5.0.010 (2010-05-17)
8159 protected function _getannotsrefs($n) {
8160 if (!(isset($this->PageAnnots
[$n]) OR ($this->sign
AND isset($this->signature_data
['cert_type'])))) {
8163 $out = ' /Annots [';
8164 if (isset($this->PageAnnots
[$n])) {
8165 foreach ($this->PageAnnots
[$n] as $key => $val) {
8166 if (!in_array($val['n'], $this->radio_groups
)) {
8167 $out .= ' '.$val['n'].' 0 R';
8170 // add radiobutton groups
8171 if (isset($this->radiobutton_groups
[$n])) {
8172 foreach ($this->radiobutton_groups
[$n] as $key => $data) {
8173 if (isset($data['n'])) {
8174 $out .= ' '.$data['n'].' 0 R';
8179 if ($this->sign
AND ($n == $this->signature_appearance
['page']) AND isset($this->signature_data
['cert_type'])) {
8180 // set reference for signature object
8181 $out .= ' '.$this->sig_obj_id
.' 0 R';
8183 if (!empty($this->empty_signature_appearance
)) {
8184 foreach ($this->empty_signature_appearance
as $esa) {
8185 if ($esa['page'] == $n) {
8186 // set reference for empty signature objects
8187 $out .= ' '.$esa['objid'].' 0 R';
8196 * Output annotations objects for all pages.
8197 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8198 * See section 12.5 of PDF 32000_2008 reference.
8200 * @author Nicola Asuni
8201 * @since 4.0.018 (2008-08-06)
8203 protected function _putannotsobjs() {
8204 // reset object counter
8205 for ($n=1; $n <= $this->numpages
; ++
$n) {
8206 if (isset($this->PageAnnots
[$n])) {
8207 // set page annotations
8208 foreach ($this->PageAnnots
[$n] as $key => $pl) {
8209 $annot_obj_id = $this->PageAnnots
[$n][$key]['n'];
8210 // create annotation object for grouping radiobuttons
8211 if (isset($this->radiobutton_groups
[$n][$pl['txt']]) AND is_array($this->radiobutton_groups
[$n][$pl['txt']])) {
8212 $radio_button_obj_id = $this->radiobutton_groups
[$n][$pl['txt']]['n'];
8214 $annots .= ' /Type /Annot';
8215 $annots .= ' /Subtype /Widget';
8216 $annots .= ' /Rect [0 0 0 0]';
8217 if ($this->radiobutton_groups
[$n][$pl['txt']]['#readonly#']) {
8219 $annots .= ' /F 68';
8220 $annots .= ' /Ff 49153';
8222 $annots .= ' /F 4'; // default print for PDF/A
8223 $annots .= ' /Ff 49152';
8225 $annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
8226 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8227 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $radio_button_obj_id);
8229 $annots .= ' /FT /Btn';
8230 $annots .= ' /Kids [';
8232 foreach ($this->radiobutton_groups
[$n][$pl['txt']] as $key => $data) {
8233 if (isset($data['kid'])) {
8234 $annots .= ' '.$data['kid'].' 0 R';
8235 if ($data['def'] !== 'Off') {
8236 $defval = $data['def'];
8241 if (!empty($defval)) {
8242 $annots .= ' /V /'.$defval;
8245 $this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
8246 $this->form_obj_id
[] = $radio_button_obj_id;
8247 // store object id to be used on Parent entry of Kids
8248 $this->radiobutton_groups
[$n][$pl['txt']] = $radio_button_obj_id;
8251 $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER
);
8252 $a = $pl['x'] * $this->k
;
8253 $b = $this->pagedim
[$n]['h'] - (($pl['y'] +
$pl['h']) * $this->k
);
8254 $c = $pl['w'] * $this->k
;
8255 $d = $pl['h'] * $this->k
;
8256 $rect = sprintf('%F %F %F %F', $a, $b, $a+
$c, $b+
$d);
8257 // create new annotation object
8258 $annots = '<</Type /Annot';
8259 $annots .= ' /Subtype /'.$pl['opt']['subtype'];
8260 $annots .= ' /Rect ['.$rect.']';
8261 $ft = array('Btn', 'Tx', 'Ch', 'Sig');
8262 if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
8263 $annots .= ' /FT /'.$pl['opt']['ft'];
8266 if ($pl['opt']['subtype'] !== 'Link') {
8267 $annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
8269 $annots .= ' /P '.$this->page_obj_id
[$n].' 0 R';
8270 $annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
8271 $annots .= ' /M '.$this->_datestring($annot_obj_id, $this->doc_modification_timestamp
);
8272 if (isset($pl['opt']['f'])) {
8274 if (is_array($pl['opt']['f'])) {
8275 foreach ($pl['opt']['f'] as $f) {
8276 switch (strtolower($f)) {
8309 case 'togglenoview': {
8313 case 'lockedcontents': {
8323 $fval = intval($pl['opt']['f']);
8328 if ($this->pdfa_mode
) {
8329 // force print flag for PDF/A mode
8332 $annots .= ' /F '.intval($fval);
8333 if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
8334 $annots .= ' /AS /'.$pl['opt']['as'];
8336 if (isset($pl['opt']['ap'])) {
8337 // appearance stream
8338 $annots .= ' /AP <<';
8339 if (is_array($pl['opt']['ap'])) {
8340 foreach ($pl['opt']['ap'] as $apmode => $apdef) {
8341 // $apmode can be: n = normal; r = rollover; d = down;
8342 $annots .= ' /'.strtoupper($apmode);
8343 if (is_array($apdef)) {
8345 foreach ($apdef as $apstate => $stream) {
8346 // reference to XObject that define the appearance for this mode-state
8347 $apsobjid = $this->_putAPXObject($c, $d, $stream);
8348 $annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
8352 // reference to XObject that define the appearance for this mode
8353 $apsobjid = $this->_putAPXObject($c, $d, $apdef);
8354 $annots .= ' '.$apsobjid.' 0 R';
8358 $annots .= $pl['opt']['ap'];
8362 if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
8363 $annots .= ' /BS <<';
8364 $annots .= ' /Type /Border';
8365 if (isset($pl['opt']['bs']['w'])) {
8366 $annots .= ' /W '.intval($pl['opt']['bs']['w']);
8368 $bstyles = array('S', 'D', 'B', 'I', 'U');
8369 if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
8370 $annots .= ' /S /'.$pl['opt']['bs']['s'];
8372 if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
8374 foreach ($pl['opt']['bs']['d'] as $cord) {
8375 $annots .= ' '.intval($cord);
8381 $annots .= ' /Border [';
8382 if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
8383 $annots .= intval($pl['opt']['border'][0]).' ';
8384 $annots .= intval($pl['opt']['border'][1]).' ';
8385 $annots .= intval($pl['opt']['border'][2]);
8386 if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
8388 foreach ($pl['opt']['border'][3] as $dash) {
8389 $annots .= intval($dash).' ';
8398 if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
8399 $annots .= ' /BE <<';
8400 $bstyles = array('S', 'C');
8401 if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $bstyles)) {
8402 $annots .= ' /S /'.$pl['opt']['bs']['s'];
8404 $annots .= ' /S /S';
8406 if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
8407 $annots .= ' /I '.sprintf(' %F', $pl['opt']['be']['i']);
8411 if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
8412 $annots .= ' /C '.TCPDF_COLORS
::getColorStringFromArray($pl['opt']['c']);
8414 //$annots .= ' /StructParent ';
8415 //$annots .= ' /OC ';
8416 $markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
8417 if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
8418 // this is a markup type
8419 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8420 $annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
8422 //$annots .= ' /Popup ';
8423 if (isset($pl['opt']['ca'])) {
8424 $annots .= ' /CA '.sprintf('%F', floatval($pl['opt']['ca']));
8426 if (isset($pl['opt']['rc'])) {
8427 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8429 $annots .= ' /CreationDate '.$this->_datestring($annot_obj_id, $this->doc_creation_timestamp
);
8430 //$annots .= ' /IRT ';
8431 if (isset($pl['opt']['subj'])) {
8432 $annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
8434 //$annots .= ' /RT ';
8435 //$annots .= ' /IT ';
8436 //$annots .= ' /ExData ';
8438 $lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
8440 switch (strtolower($pl['opt']['subtype'])) {
8442 if (isset($pl['opt']['open'])) {
8443 $annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ?
'true' : 'false');
8445 $iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
8446 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8447 $annots .= ' /Name /'.$pl['opt']['name'];
8449 $annots .= ' /Name /Note';
8451 $hasStateModel = isset($pl['opt']['statemodel']);
8452 $hasState = isset($pl['opt']['state']);
8453 $statemodels = array('Marked', 'Review');
8454 if (!$hasStateModel && !$hasState) {
8457 if ($hasStateModel AND in_array($pl['opt']['statemodel'], $statemodels)) {
8458 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8460 $pl['opt']['statemodel'] = 'Marked';
8461 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8463 if ($pl['opt']['statemodel'] == 'Marked') {
8464 $states = array('Accepted', 'Unmarked');
8466 $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8468 if ($hasState AND in_array($pl['opt']['state'], $states)) {
8469 $annots .= ' /State /'.$pl['opt']['state'];
8471 if ($pl['opt']['statemodel'] == 'Marked') {
8472 $annots .= ' /State /Unmarked';
8474 $annots .= ' /State /None';
8480 if (is_string($pl['txt']) && !empty($pl['txt'])) {
8481 if ($pl['txt'][0] == '#') {
8482 // internal destination
8483 $annots .= ' /A <</S /GoTo /D /'.TCPDF_STATIC
::encodeNameObject(substr($pl['txt'], 1)).'>>';
8484 } elseif ($pl['txt'][0] == '%') {
8485 // embedded PDF file
8486 $filename = basename(substr($pl['txt'], 1));
8487 $annots .= ' /A << /S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($n - 1).' /A '.$this->embeddedfiles
[$filename]['a'].' >> >>';
8488 } elseif ($pl['txt'][0] == '*') {
8489 // embedded generic file
8490 $filename = basename(substr($pl['txt'], 1));
8491 $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});';
8492 $annots .= ' /A << /S /JavaScript /JS '.$this->_textstring($jsa, $annot_obj_id).'>>';
8494 $parsedUrl = parse_url($pl['txt']);
8495 if (empty($parsedUrl['scheme']) AND (!empty($parsedUrl['path']) && strtolower(substr($parsedUrl['path'], -4)) == '.pdf')) {
8496 // relative link to a PDF file
8497 $dest = '[0 /Fit]'; // default page 0
8498 if (!empty($parsedUrl['fragment'])) {
8499 // check for named destination
8500 $tmp = explode('=', $parsedUrl['fragment']);
8501 $dest = '('.((count($tmp) == 2) ?
$tmp[1] : $tmp[0]).')';
8503 $annots .= ' /A <</S /GoToR /D '.$dest.' /F '.$this->_datastring($this->unhtmlentities($parsedUrl['path']), $annot_obj_id).' /NewWindow true>>';
8505 // external URI link
8506 $annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
8509 } elseif (isset($this->links
[$pl['txt']])) {
8511 $l = $this->links
[$pl['txt']];
8512 if (isset($this->page_obj_id
[($l['p'])])) {
8513 $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
)));
8516 $hmodes = array('N', 'I', 'O', 'P');
8517 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
8518 $annots .= ' /H /'.$pl['opt']['h'];
8520 $annots .= ' /H /I';
8522 //$annots .= ' /PA ';
8523 //$annots .= ' /Quadpoints ';
8527 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8528 $annots .= ' /DA ('.$pl['opt']['da'].')';
8530 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8531 $annots .= ' /Q '.intval($pl['opt']['q']);
8533 if (isset($pl['opt']['rc'])) {
8534 $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
8536 if (isset($pl['opt']['ds'])) {
8537 $annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
8539 if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
8540 $annots .= ' /CL [';
8541 foreach ($pl['opt']['cl'] as $cl) {
8542 $annots .= sprintf('%F ', $cl * $this->k
);
8546 $tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
8547 if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
8548 $annots .= ' /IT /'.$pl['opt']['it'];
8550 if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
8551 $l = $pl['opt']['rd'][0] * $this->k
;
8552 $r = $pl['opt']['rd'][1] * $this->k
;
8553 $t = $pl['opt']['rd'][2] * $this->k
;
8554 $b = $pl['opt']['rd'][3] * $this->k
;
8555 $annots .= ' /RD ['.sprintf('%F %F %F %F', $l, $r, $t, $b).']';
8557 if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
8558 $annots .= ' /LE /'.$pl['opt']['le'];
8601 case 'fileattachment': {
8602 if ($this->pdfa_mode
&& $this->pdfa_version
!= 3) {
8603 // embedded files are not allowed in PDF/A mode version 1 and 2
8606 if (!isset($pl['opt']['fs'])) {
8609 $filename = basename($pl['opt']['fs']);
8610 if (isset($this->embeddedfiles
[$filename]['f'])) {
8611 $annots .= ' /FS '.$this->embeddedfiles
[$filename]['f'].' 0 R';
8612 $iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
8613 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8614 $annots .= ' /Name /'.$pl['opt']['name'];
8616 $annots .= ' /Name /PushPin';
8618 // index (zero-based) of the annotation in the Annots array of this page
8619 $this->embeddedfiles
[$filename]['a'] = $key;
8624 if (!isset($pl['opt']['fs'])) {
8627 $filename = basename($pl['opt']['fs']);
8628 if (isset($this->embeddedfiles
[$filename]['f'])) {
8629 // ... TO BE COMPLETED ...
8630 // /R /C /B /E /CO /CP
8631 $annots .= ' /Sound '.$this->embeddedfiles
[$filename]['f'].' 0 R';
8632 $iconsapp = array('Speaker', 'Mic');
8633 if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
8634 $annots .= ' /Name /'.$pl['opt']['name'];
8636 $annots .= ' /Name /Speaker';
8645 $hmode = array('N', 'I', 'O', 'P', 'T');
8646 if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
8647 $annots .= ' /H /'.$pl['opt']['h'];
8649 if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
8650 $annots .= ' /MK <<';
8651 if (isset($pl['opt']['mk']['r'])) {
8652 $annots .= ' /R '.$pl['opt']['mk']['r'];
8654 if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
8655 $annots .= ' /BC '.TCPDF_COLORS
::getColorStringFromArray($pl['opt']['mk']['bc']);
8657 if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
8658 $annots .= ' /BG '.TCPDF_COLORS
::getColorStringFromArray($pl['opt']['mk']['bg']);
8660 if (isset($pl['opt']['mk']['ca'])) {
8661 $annots .= ' /CA '.$pl['opt']['mk']['ca'];
8663 if (isset($pl['opt']['mk']['rc'])) {
8664 $annots .= ' /RC '.$pl['opt']['mk']['rc'];
8666 if (isset($pl['opt']['mk']['ac'])) {
8667 $annots .= ' /AC '.$pl['opt']['mk']['ac'];
8669 if (isset($pl['opt']['mk']['i'])) {
8670 $info = $this->getImageBuffer($pl['opt']['mk']['i']);
8671 if ($info !== false) {
8672 $annots .= ' /I '.$info['n'].' 0 R';
8675 if (isset($pl['opt']['mk']['ri'])) {
8676 $info = $this->getImageBuffer($pl['opt']['mk']['ri']);
8677 if ($info !== false) {
8678 $annots .= ' /RI '.$info['n'].' 0 R';
8681 if (isset($pl['opt']['mk']['ix'])) {
8682 $info = $this->getImageBuffer($pl['opt']['mk']['ix']);
8683 if ($info !== false) {
8684 $annots .= ' /IX '.$info['n'].' 0 R';
8687 if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
8688 $annots .= ' /IF <<';
8689 $if_sw = array('A', 'B', 'S', 'N');
8690 if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
8691 $annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
8693 $if_s = array('A', 'P');
8694 if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
8695 $annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
8697 if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
8698 $annots .= sprintf(' /A [%F %F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
8700 if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
8701 $annots .= ' /FB true';
8705 if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
8706 $annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
8710 // --- Entries for field dictionaries ---
8711 if (isset($this->radiobutton_groups
[$n][$pl['txt']])) {
8713 $annots .= ' /Parent '.$this->radiobutton_groups
[$n][$pl['txt']].' 0 R';
8715 if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
8716 $annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
8718 if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
8719 $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
8721 if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
8722 $annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
8724 if (isset($pl['opt']['ff'])) {
8725 if (is_array($pl['opt']['ff'])) {
8726 // array of bit settings
8728 foreach($pl['opt']['ff'] as $val) {
8729 $flag +
= 1 << ($val - 1);
8732 $flag = intval($pl['opt']['ff']);
8734 $annots .= ' /Ff '.$flag;
8736 if (isset($pl['opt']['maxlen'])) {
8737 $annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
8739 if (isset($pl['opt']['v'])) {
8741 if (is_array($pl['opt']['v'])) {
8742 foreach ($pl['opt']['v'] AS $optval) {
8743 if (is_float($optval)) {
8744 $optval = sprintf('%F', $optval);
8746 $annots .= ' '.$optval;
8749 $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8752 if (isset($pl['opt']['dv'])) {
8754 if (is_array($pl['opt']['dv'])) {
8755 foreach ($pl['opt']['dv'] AS $optval) {
8756 if (is_float($optval)) {
8757 $optval = sprintf('%F', $optval);
8759 $annots .= ' '.$optval;
8762 $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8765 if (isset($pl['opt']['rv'])) {
8767 if (is_array($pl['opt']['rv'])) {
8768 foreach ($pl['opt']['rv'] AS $optval) {
8769 if (is_float($optval)) {
8770 $optval = sprintf('%F', $optval);
8772 $annots .= ' '.$optval;
8775 $annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
8778 if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
8779 $annots .= ' /A << '.$pl['opt']['a'].' >>';
8781 if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
8782 $annots .= ' /AA << '.$pl['opt']['aa'].' >>';
8784 if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
8785 $annots .= ' /DA ('.$pl['opt']['da'].')';
8787 if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
8788 $annots .= ' /Q '.intval($pl['opt']['q']);
8790 if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
8791 $annots .= ' /Opt [';
8792 foreach($pl['opt']['opt'] AS $copt) {
8793 if (is_array($copt)) {
8794 $annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
8796 $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8801 if (isset($pl['opt']['ti'])) {
8802 $annots .= ' /TI '.intval($pl['opt']['ti']);
8804 if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
8806 foreach($pl['opt']['i'] AS $copt) {
8807 $annots .= intval($copt).' ';
8816 case 'printermark': {
8833 // create new annotation object
8834 $this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
8835 if ($formfield AND !isset($this->radiobutton_groups
[$n][$pl['txt']])) {
8836 // store reference of form object
8837 $this->form_obj_id
[] = $annot_obj_id;
8841 } // end for each page
8845 * Put appearance streams XObject used to define annotation's appearance states.
8846 * @param int $w annotation width
8847 * @param int $h annotation height
8848 * @param string $stream appearance stream
8849 * @return int object ID
8851 * @since 4.8.001 (2009-09-09)
8853 protected function _putAPXObject($w=0, $h=0, $stream='') {
8854 $stream = trim($stream);
8855 $out = $this->_getobj()."\n";
8856 $this->xobjects
['AX'.$this->n
] = array('n' => $this->n
);
8858 $out .= ' /Type /XObject';
8859 $out .= ' /Subtype /Form';
8860 $out .= ' /FormType 1';
8861 if ($this->compress
) {
8862 $stream = gzcompress($stream);
8863 $out .= ' /Filter /FlateDecode';
8865 $rect = sprintf('%F %F', $w, $h);
8866 $out .= ' /BBox [0 0 '.$rect.']';
8867 $out .= ' /Matrix [1 0 0 1 0 0]';
8868 $out .= ' /Resources 2 0 R';
8869 $stream = $this->_getrawstream($stream);
8870 $out .= ' /Length '.strlen($stream);
8872 $out .= ' stream'."\n".$stream."\n".'endstream';
8873 $out .= "\n".'endobj';
8880 * @author Nicola Asuni
8883 protected function _putfonts() {
8885 foreach ($this->diffs
as $diff) {
8888 $this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
8890 foreach ($this->FontFiles
as $file => $info) {
8891 // search and get font file to embedd
8892 $fontfile = TCPDF_FONTS
::getFontFullPath($file, $info['fontdir']);
8893 if (!TCPDF_STATIC
::empty_string($fontfile)) {
8894 $font = file_get_contents($fontfile);
8895 $compressed = (substr($file, -2) == '.z');
8896 if ((!$compressed) AND (isset($info['length2']))) {
8897 $header = (ord($font[0]) == 128);
8899 // strip first binary header
8900 $font = substr($font, 6);
8902 if ($header AND (ord($font[$info['length1']]) == 128)) {
8903 // strip second binary header
8904 $font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] +
6));
8906 } elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
8909 $font = gzuncompress($font);
8911 // merge subset characters
8912 $subsetchars = array(); // used chars
8913 foreach ($info['fontkeys'] as $fontkey) {
8914 $fontinfo = $this->getFontBuffer($fontkey);
8915 $subsetchars +
= $fontinfo['subsetchars'];
8917 // rebuild a font subset
8918 $font = TCPDF_FONTS
::_getTrueTypeFontSubset($font, $subsetchars);
8919 // calculate new font length
8920 $info['length1'] = strlen($font);
8923 $font = gzcompress($font);
8927 $this->FontFiles
[$file]['n'] = $this->n
;
8928 $stream = $this->_getrawstream($font);
8929 $out = '<< /Length '.strlen($stream);
8931 $out .= ' /Filter /FlateDecode';
8933 $out .= ' /Length1 '.$info['length1'];
8934 if (isset($info['length2'])) {
8935 $out .= ' /Length2 '.$info['length2'].' /Length3 0';
8938 $out .= ' stream'."\n".$stream."\n".'endstream';
8939 $out .= "\n".'endobj';
8943 foreach ($this->fontkeys
as $k) {
8945 $font = $this->getFontBuffer($k);
8946 $type = $font['type'];
8947 $name = $font['name'];
8948 if ($type == 'core') {
8949 // standard core font
8950 $out = $this->_getobj($this->font_obj_ids
[$k])."\n";
8951 $out .= '<</Type /Font';
8952 $out .= ' /Subtype /Type1';
8953 $out .= ' /BaseFont /'.$name;
8954 $out .= ' /Name /F'.$font['i'];
8955 if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
8956 $out .= ' /Encoding /WinAnsiEncoding';
8958 if ($k == 'helvetica') {
8959 // add default font for annotations
8960 $this->annotation_fonts
[$k] = $font['i'];
8963 $out .= "\n".'endobj';
8965 } elseif (($type == 'Type1') OR ($type == 'TrueType')) {
8966 // additional Type1 or TrueType font
8967 $out = $this->_getobj($this->font_obj_ids
[$k])."\n";
8968 $out .= '<</Type /Font';
8969 $out .= ' /Subtype /'.$type;
8970 $out .= ' /BaseFont /'.$name;
8971 $out .= ' /Name /F'.$font['i'];
8972 $out .= ' /FirstChar 32 /LastChar 255';
8973 $out .= ' /Widths '.($this->n +
1).' 0 R';
8974 $out .= ' /FontDescriptor '.($this->n +
2).' 0 R';
8976 if (isset($font['diff'])) {
8977 $out .= ' /Encoding '.($nf +
$font['diff']).' 0 R';
8979 $out .= ' /Encoding /WinAnsiEncoding';
8983 $out .= "\n".'endobj';
8988 for ($i = 32; $i < 256; ++
$i) {
8989 if (isset($font['cw'][$i])) {
8990 $s .= $font['cw'][$i].' ';
8992 $s .= $font['dw'].' ';
8996 $s .= "\n".'endobj';
9000 $s = '<</Type /FontDescriptor /FontName /'.$name;
9001 foreach ($font['desc'] as $fdk => $fdv) {
9002 if (is_float($fdv)) {
9003 $fdv = sprintf('%F', $fdv);
9005 $s .= ' /'.$fdk.' '.$fdv.'';
9007 if (!TCPDF_STATIC
::empty_string($font['file'])) {
9008 $s .= ' /FontFile'.($type == 'Type1' ?
'' : '2').' '.$this->FontFiles
[$font['file']]['n'].' 0 R';
9011 $s .= "\n".'endobj';
9015 $mtd = '_put'.strtolower($type);
9016 if (!method_exists($this, $mtd)) {
9017 $this->Error('Unsupported font type: '.$type);
9025 * Adds unicode fonts.<br>
9026 * Based on PDF Reference 1.3 (section 5)
9027 * @param array $font font data
9029 * @author Nicola Asuni
9030 * @since 1.52.0.TC005 (2005-01-05)
9032 protected function _puttruetypeunicode($font) {
9034 if ($font['subset']) {
9035 // change name for font subsetting
9036 $subtag = sprintf('%06u', $font['i']);
9037 $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
9038 $fontname .= $subtag.'+';
9040 $fontname .= $font['name'];
9042 // A composite font composed of other fonts, organized hierarchically
9043 $out = $this->_getobj($this->font_obj_ids
[$font['fontkey']])."\n";
9044 $out .= '<< /Type /Font';
9045 $out .= ' /Subtype /Type0';
9046 $out .= ' /BaseFont /'.$fontname;
9047 $out .= ' /Name /F'.$font['i'];
9048 $out .= ' /Encoding /'.$font['enc'];
9049 $out .= ' /ToUnicode '.($this->n +
1).' 0 R';
9050 $out .= ' /DescendantFonts ['.($this->n +
2).' 0 R]';
9052 $out .= "\n".'endobj';
9054 // ToUnicode map for Identity-H
9055 $stream = TCPDF_FONT_DATA
::$uni_identity_h;
9058 $stream = ($this->compress
) ?
gzcompress($stream) : $stream;
9059 $filter = ($this->compress
) ?
'/Filter /FlateDecode ' : '';
9060 $stream = $this->_getrawstream($stream);
9061 $this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
9063 // A CIDFont whose glyph descriptions are based on TrueType font technology
9064 $oid = $this->_newobj();
9065 $out = '<< /Type /Font';
9066 $out .= ' /Subtype /CIDFontType2';
9067 $out .= ' /BaseFont /'.$fontname;
9068 // A dictionary containing entries that define the character collection of the CIDFont.
9069 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9070 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9071 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9072 $out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
9073 $out .= ' /FontDescriptor '.($this->n +
1).' 0 R';
9074 $out .= ' /DW '.$font['dw']; // default width
9075 $out .= "\n".TCPDF_FONTS
::_putfontwidths($font, 0);
9076 if (isset($font['ctg']) AND (!TCPDF_STATIC
::empty_string($font['ctg']))) {
9077 $out .= "\n".'/CIDToGIDMap '.($this->n +
2).' 0 R';
9080 $out .= "\n".'endobj';
9083 // A font descriptor describing the CIDFont default metrics other than its glyph widths
9085 $out = '<< /Type /FontDescriptor';
9086 $out .= ' /FontName /'.$fontname;
9087 foreach ($font['desc'] as $key => $value) {
9088 if (is_float($value)) {
9089 $value = sprintf('%F', $value);
9091 $out .= ' /'.$key.' '.$value;
9094 if (!TCPDF_STATIC
::empty_string($font['file'])) {
9095 // A stream containing a TrueType font
9096 $out .= ' /FontFile2 '.$this->FontFiles
[$font['file']]['n'].' 0 R';
9097 $fontdir = $this->FontFiles
[$font['file']]['fontdir'];
9100 $out .= "\n".'endobj';
9102 if (isset($font['ctg']) AND (!TCPDF_STATIC
::empty_string($font['ctg']))) {
9104 // Embed CIDToGIDMap
9105 // A specification of the mapping from CIDs to glyph indices
9106 // search and get CTG font file to embedd
9107 $ctgfile = strtolower($font['ctg']);
9108 // search and get ctg font file to embedd
9109 $fontfile = TCPDF_FONTS
::getFontFullPath($ctgfile, $fontdir);
9110 if (TCPDF_STATIC
::empty_string($fontfile)) {
9111 $this->Error('Font file not found: '.$ctgfile);
9113 $stream = $this->_getrawstream(file_get_contents($fontfile));
9114 $out = '<< /Length '.strlen($stream).'';
9115 if (substr($fontfile, -2) == '.z') { // check file extension
9116 // Decompresses data encoded using the public-domain
9117 // zlib/deflate compression method, reproducing the
9118 // original text or binary data
9119 $out .= ' /Filter /FlateDecode';
9122 $out .= ' stream'."\n".$stream."\n".'endstream';
9123 $out .= "\n".'endobj';
9129 * Output CID-0 fonts.
9130 * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
9131 * @param array $font font data
9133 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9134 * @since 3.2.000 (2008-06-23)
9136 protected function _putcidfont0($font) {
9138 if (!isset($font['cw'][1])) {
9141 if (isset($font['cidinfo']['uni2cid'])) {
9142 // convert unicode to cid.
9143 $uni2cid = $font['cidinfo']['uni2cid'];
9145 foreach ($font['cw'] as $uni => $width) {
9146 if (isset($uni2cid[$uni])) {
9147 $cw[($uni2cid[$uni] +
$cidoffset)] = $width;
9148 } elseif ($uni < 256) {
9150 } // else unknown character
9152 $font = array_merge($font, array('cw' => $cw));
9154 $name = $font['name'];
9155 $enc = $font['enc'];
9157 $longname = $name.'-'.$enc;
9161 $out = $this->_getobj($this->font_obj_ids
[$font['fontkey']])."\n";
9162 $out .= '<</Type /Font';
9163 $out .= ' /Subtype /Type0';
9164 $out .= ' /BaseFont /'.$longname;
9165 $out .= ' /Name /F'.$font['i'];
9167 $out .= ' /Encoding /'.$enc;
9169 $out .= ' /DescendantFonts ['.($this->n +
1).' 0 R]';
9171 $out .= "\n".'endobj';
9173 $oid = $this->_newobj();
9174 $out = '<</Type /Font';
9175 $out .= ' /Subtype /CIDFontType0';
9176 $out .= ' /BaseFont /'.$name;
9177 $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
9178 $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
9179 $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
9180 $out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
9181 $out .= ' /FontDescriptor '.($this->n +
1).' 0 R';
9182 $out .= ' /DW '.$font['dw'];
9183 $out .= "\n".TCPDF_FONTS
::_putfontwidths($font, $cidoffset);
9185 $out .= "\n".'endobj';
9188 $s = '<</Type /FontDescriptor /FontName /'.$name;
9189 foreach ($font['desc'] as $k => $v) {
9190 if ($k != 'Style') {
9192 $v = sprintf('%F', $v);
9194 $s .= ' /'.$k.' '.$v.'';
9198 $s .= "\n".'endobj';
9206 protected function _putimages() {
9207 $filter = ($this->compress
) ?
'/Filter /FlateDecode ' : '';
9208 foreach ($this->imagekeys
as $file) {
9209 $info = $this->getImageBuffer($file);
9210 // set object for alternate images array
9212 if ((!$this->pdfa_mode
) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9213 $altoid = $this->_newobj();
9215 foreach ($info['altimgs'] as $altimage) {
9216 if (isset($this->xobjects
['I'.$altimage[0]]['n'])) {
9217 $out .= ' << /Image '.$this->xobjects
['I'.$altimage[0]]['n'].' 0 R';
9218 $out .= ' /DefaultForPrinting';
9219 if ($altimage[1] === true) {
9228 $out .= "\n".'endobj';
9232 $oid = $this->_newobj();
9233 $this->xobjects
['I'.$info['i']] = array('n' => $oid);
9234 $this->setImageSubBuffer($file, 'n', $this->n
);
9235 $out = '<</Type /XObject';
9236 $out .= ' /Subtype /Image';
9237 $out .= ' /Width '.$info['w'];
9238 $out .= ' /Height '.$info['h'];
9239 if (array_key_exists('masked', $info)) {
9240 $out .= ' /SMask '.($this->n
- 1).' 0 R';
9244 if (isset($info['icc']) AND ($info['icc'] !== false)) {
9247 $out .= ' /ColorSpace [/ICCBased '.($this->n +
1).' 0 R]';
9248 } elseif ($info['cs'] == 'Indexed') {
9249 // Indexed Colour Space
9250 $out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n +
1).' 0 R]';
9252 // Device Colour Space
9253 $out .= ' /ColorSpace /'.$info['cs'];
9255 if ($info['cs'] == 'DeviceCMYK') {
9256 $out .= ' /Decode [1 0 1 0 1 0 1 0]';
9258 $out .= ' /BitsPerComponent '.$info['bpc'];
9260 // reference to alternate images dictionary
9261 $out .= ' /Alternates '.$altoid.' 0 R';
9263 if (isset($info['exurl']) AND !empty($info['exurl'])) {
9265 $out .= ' /Length 0';
9266 $out .= ' /F << /FS /URL /F '.$this->_datastring($info['exurl'], $oid).' >>';
9267 if (isset($info['f'])) {
9268 $out .= ' /FFilter /'.$info['f'];
9271 $out .= ' stream'."\n".'endstream';
9273 if (isset($info['f'])) {
9274 $out .= ' /Filter /'.$info['f'];
9276 if (isset($info['parms'])) {
9277 $out .= ' '.$info['parms'];
9279 if (isset($info['trns']) AND is_array($info['trns'])) {
9281 $count_info = count($info['trns']);
9282 if ($info['cs'] == 'Indexed') {
9283 $maxval =(pow(2, $info['bpc']) - 1);
9284 for ($i = 0; $i < $count_info; ++
$i) {
9285 if (($info['trns'][$i] != 0) AND ($info['trns'][$i] != $maxval)) {
9286 // this is not a binary type mask @TODO: create a SMask
9289 } elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9290 // store the first fully transparent value
9291 $trns .= $i.' '.$i.' ';
9296 for ($i = 0; $i < $count_info; ++
$i) {
9297 if ($info['trns'][$i] == 0) {
9298 $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
9302 // Colour Key Masking
9303 if (!empty($trns)) {
9304 $out .= ' /Mask ['.$trns.']';
9307 $stream = $this->_getrawstream($info['data']);
9308 $out .= ' /Length '.strlen($stream).' >>';
9309 $out .= ' stream'."\n".$stream."\n".'endstream';
9311 $out .= "\n".'endobj';
9314 // ICC colour profile
9316 $icc = ($this->compress
) ?
gzcompress($info['icc']) : $info['icc'];
9317 $icc = $this->_getrawstream($icc);
9318 $this->_out('<</N '.$info['ch'].' /Alternate /'.$info['cs'].' '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9319 } elseif ($info['cs'] == 'Indexed') {
9322 $pal = ($this->compress
) ?
gzcompress($info['pal']) : $info['pal'];
9323 $pal = $this->_getrawstream($pal);
9324 $this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
9330 * Output Form XObjects Templates.
9331 * @author Nicola Asuni
9332 * @since 5.8.017 (2010-08-24)
9334 * @see startTemplate(), endTemplate(), printTemplate()
9336 protected function _putxobjects() {
9337 foreach ($this->xobjects
as $key => $data) {
9338 if (isset($data['outdata'])) {
9339 $stream = str_replace($this->epsmarker
, '', trim($data['outdata']));
9340 $out = $this->_getobj($data['n'])."\n";
9342 $out .= ' /Type /XObject';
9343 $out .= ' /Subtype /Form';
9344 $out .= ' /FormType 1';
9345 if ($this->compress
) {
9346 $stream = gzcompress($stream);
9347 $out .= ' /Filter /FlateDecode';
9349 $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
));
9350 $out .= ' /Matrix [1 0 0 1 0 0]';
9351 $out .= ' /Resources <<';
9352 $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9353 if (!$this->pdfa_mode ||
$this->pdfa_version
>= 2) {
9355 if (isset($data['extgstates']) AND !empty($data['extgstates'])) {
9356 $out .= ' /ExtGState <<';
9357 foreach ($data['extgstates'] as $k => $extgstate) {
9358 if (isset($this->extgstates
[$k]['name'])) {
9359 $out .= ' /'.$this->extgstates
[$k]['name'];
9363 $out .= ' '.$this->extgstates
[$k]['n'].' 0 R';
9367 if (isset($data['gradients']) AND !empty($data['gradients'])) {
9370 foreach ($data['gradients'] as $id => $grad) {
9371 // gradient patterns
9372 $gp .= ' /p'.$id.' '.$this->gradients
[$id]['pattern'].' 0 R';
9373 // gradient shadings
9374 $gs .= ' /Sh'.$id.' '.$this->gradients
[$id]['id'].' 0 R';
9376 $out .= ' /Pattern <<'.$gp.' >>';
9377 $out .= ' /Shading <<'.$gs.' >>';
9381 if (isset($data['spot_colors']) AND !empty($data['spot_colors'])) {
9382 $out .= ' /ColorSpace <<';
9383 foreach ($data['spot_colors'] as $name => $color) {
9384 $out .= ' /CS'.$color['i'].' '.$this->spot_colors
[$name]['n'].' 0 R';
9389 if (!empty($data['fonts'])) {
9390 $out .= ' /Font <<';
9391 foreach ($data['fonts'] as $fontkey => $fontid) {
9392 $out .= ' /F'.$fontid.' '.$this->font_obj_ids
[$fontkey].' 0 R';
9396 // images or nested xobjects
9397 if (!empty($data['images']) OR !empty($data['xobjects'])) {
9398 $out .= ' /XObject <<';
9399 foreach ($data['images'] as $imgid) {
9400 $out .= ' /I'.$imgid.' '.$this->xobjects
['I'.$imgid]['n'].' 0 R';
9402 foreach ($data['xobjects'] as $sub_id => $sub_objid) {
9403 $out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
9407 $out .= ' >>'; //end resources
9408 if (isset($data['group']) AND ($data['group'] !== false)) {
9409 // set transparency group
9410 $out .= ' /Group << /Type /Group /S /Transparency';
9411 if (is_array($data['group'])) {
9412 if (isset($data['group']['CS']) AND !empty($data['group']['CS'])) {
9413 $out .= ' /CS /'.$data['group']['CS'];
9415 if (isset($data['group']['I'])) {
9416 $out .= ' /I /'.($data['group']['I']===true?
'true':'false');
9418 if (isset($data['group']['K'])) {
9419 $out .= ' /K /'.($data['group']['K']===true?
'true':'false');
9424 $stream = $this->_getrawstream($stream, $data['n']);
9425 $out .= ' /Length '.strlen($stream);
9427 $out .= ' stream'."\n".$stream."\n".'endstream';
9428 $out .= "\n".'endobj';
9435 * Output Spot Colors Resources.
9437 * @since 4.0.024 (2008-09-12)
9439 protected function _putspotcolors() {
9440 foreach ($this->spot_colors
as $name => $color) {
9442 $this->spot_colors
[$name]['n'] = $this->n
;
9443 $out = '[/Separation /'.str_replace(' ', '#20', $name);
9444 $out .= ' /DeviceCMYK <<';
9445 $out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
9446 $out .= ' '.sprintf('/C1 [%F %F %F %F] ', ($color['C'] / 100), ($color['M'] / 100), ($color['Y'] / 100), ($color['K'] / 100));
9447 $out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
9448 $out .= "\n".'endobj';
9454 * Return XObjects Dictionary.
9455 * @return string XObjects dictionary
9457 * @since 5.8.014 (2010-08-23)
9459 protected function _getxobjectdict() {
9461 foreach ($this->xobjects
as $id => $objid) {
9462 $out .= ' /'.$id.' '.$objid['n'].' 0 R';
9468 * Output Resources Dictionary.
9471 protected function _putresourcedict() {
9472 $out = $this->_getobj(2)."\n";
9473 $out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
9474 $out .= ' /Font <<';
9475 foreach ($this->fontkeys
as $fontkey) {
9476 $font = $this->getFontBuffer($fontkey);
9477 $out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
9480 $out .= ' /XObject <<';
9481 $out .= $this->_getxobjectdict();
9484 if (!empty($this->pdflayers
)) {
9485 $out .= ' /Properties <<';
9486 foreach ($this->pdflayers
as $layer) {
9487 $out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9491 if (!$this->pdfa_mode ||
$this->pdfa_version
>= 2) {
9493 if (isset($this->extgstates
) AND !empty($this->extgstates
)) {
9494 $out .= ' /ExtGState <<';
9495 foreach ($this->extgstates
as $k => $extgstate) {
9496 if (isset($extgstate['name'])) {
9497 $out .= ' /'.$extgstate['name'];
9501 $out .= ' '.$extgstate['n'].' 0 R';
9505 if (isset($this->gradients
) AND !empty($this->gradients
)) {
9508 foreach ($this->gradients
as $id => $grad) {
9509 // gradient patterns
9510 $gp .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
9511 // gradient shadings
9512 $gs .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
9514 $out .= ' /Pattern <<'.$gp.' >>';
9515 $out .= ' /Shading <<'.$gs.' >>';
9519 if (isset($this->spot_colors
) AND !empty($this->spot_colors
)) {
9520 $out .= ' /ColorSpace <<';
9521 foreach ($this->spot_colors
as $color) {
9522 $out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
9527 $out .= "\n".'endobj';
9535 protected function _putresources() {
9536 $this->_putextgstates();
9539 $this->_putimages();
9540 $this->_putspotcolors();
9541 $this->_putshaders();
9542 $this->_putxobjects();
9543 $this->_putresourcedict();
9545 $this->_putEmbeddedFiles();
9546 $this->_putannotsobjs();
9547 $this->_putjavascript();
9548 $this->_putbookmarks();
9549 $this->_putencryption();
9553 * Adds some Metadata information (Document Information Dictionary)
9554 * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
9555 * @return int object id
9558 protected function _putinfo() {
9559 $oid = $this->_newobj();
9561 // store current isunicode value
9562 $prev_isunicode = $this->isunicode
;
9563 if ($this->docinfounicode
) {
9564 $this->isunicode
= true;
9566 if (!TCPDF_STATIC
::empty_string($this->title
)) {
9567 // The document's title.
9568 $out .= ' /Title '.$this->_textstring($this->title
, $oid);
9570 if (!TCPDF_STATIC
::empty_string($this->author
)) {
9571 // The name of the person who created the document.
9572 $out .= ' /Author '.$this->_textstring($this->author
, $oid);
9574 if (!TCPDF_STATIC
::empty_string($this->subject
)) {
9575 // The subject of the document.
9576 $out .= ' /Subject '.$this->_textstring($this->subject
, $oid);
9578 if (!TCPDF_STATIC
::empty_string($this->keywords
)) {
9579 // Keywords associated with the document.
9580 $out .= ' /Keywords '.$this->_textstring($this->keywords
, $oid);
9582 if (!TCPDF_STATIC
::empty_string($this->creator
)) {
9583 // 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.
9584 $out .= ' /Creator '.$this->_textstring($this->creator
, $oid);
9586 // restore previous isunicode value
9587 $this->isunicode
= $prev_isunicode;
9589 $out .= ' /Producer '.$this->_textstring(TCPDF_STATIC
::getTCPDFProducer(), $oid);
9590 // The date and time the document was created, in human-readable form
9591 $out .= ' /CreationDate '.$this->_datestring(0, $this->doc_creation_timestamp
);
9592 // The date and time the document was most recently modified, in human-readable form
9593 $out .= ' /ModDate '.$this->_datestring(0, $this->doc_modification_timestamp
);
9594 // A name object indicating whether the document has been modified to include trapping information
9595 $out .= ' /Trapped /False';
9597 $out .= "\n".'endobj';
9603 * Set additional XMP data to be added on the default XMP data just before the end of "x:xmpmeta" tag.
9604 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9605 * @param string $xmp Custom XMP data.
9606 * @since 5.9.128 (2011-10-06)
9609 public function setExtraXMP($xmp) {
9610 $this->custom_xmp
= $xmp;
9614 * Set additional XMP data to be added on the default XMP data just before the end of "rdf:RDF" tag.
9615 * IMPORTANT: This data is added as-is without controls, so you have to validate your data before using this method!
9616 * @param string $xmp Custom XMP RDF data.
9617 * @since 6.3.0 (2019-09-19)
9620 public function setExtraXMPRDF($xmp) {
9621 $this->custom_xmp_rdf
= $xmp;
9625 * Put XMP data object and return ID.
9626 * @return int The object ID.
9627 * @since 5.9.121 (2011-09-28)
9630 protected function _putXMP() {
9631 $oid = $this->_newobj();
9632 // store current isunicode value
9633 $prev_isunicode = $this->isunicode
;
9634 $this->isunicode
= true;
9635 $prev_encrypted = $this->encrypted
;
9636 $this->encrypted
= false;
9638 $xmp = '<?xpacket begin="'.TCPDF_FONTS
::unichr(0xfeff, $this->isunicode
).'" id="W5M0MpCehiHzreSzNTczkc9d"?>'."\n";
9639 $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";
9640 $xmp .= "\t".'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'."\n";
9641 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'."\n";
9642 $xmp .= "\t\t\t".'<dc:format>application/pdf</dc:format>'."\n";
9643 $xmp .= "\t\t\t".'<dc:title>'."\n";
9644 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9645 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC
::_escapeXML($this->title
).'</rdf:li>'."\n";
9646 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9647 $xmp .= "\t\t\t".'</dc:title>'."\n";
9648 $xmp .= "\t\t\t".'<dc:creator>'."\n";
9649 $xmp .= "\t\t\t\t".'<rdf:Seq>'."\n";
9650 $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC
::_escapeXML($this->author
).'</rdf:li>'."\n";
9651 $xmp .= "\t\t\t\t".'</rdf:Seq>'."\n";
9652 $xmp .= "\t\t\t".'</dc:creator>'."\n";
9653 $xmp .= "\t\t\t".'<dc:description>'."\n";
9654 $xmp .= "\t\t\t\t".'<rdf:Alt>'."\n";
9655 $xmp .= "\t\t\t\t\t".'<rdf:li xml:lang="x-default">'.TCPDF_STATIC
::_escapeXML($this->subject
).'</rdf:li>'."\n";
9656 $xmp .= "\t\t\t\t".'</rdf:Alt>'."\n";
9657 $xmp .= "\t\t\t".'</dc:description>'."\n";
9658 $xmp .= "\t\t\t".'<dc:subject>'."\n";
9659 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9660 $xmp .= "\t\t\t\t\t".'<rdf:li>'.TCPDF_STATIC
::_escapeXML($this->keywords
).'</rdf:li>'."\n";
9661 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9662 $xmp .= "\t\t\t".'</dc:subject>'."\n";
9663 $xmp .= "\t\t".'</rdf:Description>'."\n";
9664 // convert doc creation date format
9665 $dcdate = TCPDF_STATIC
::getFormattedDate($this->doc_creation_timestamp
);
9666 $doccreationdate = substr($dcdate, 0, 4).'-'.substr($dcdate, 4, 2).'-'.substr($dcdate, 6, 2);
9667 $doccreationdate .= 'T'.substr($dcdate, 8, 2).':'.substr($dcdate, 10, 2).':'.substr($dcdate, 12, 2);
9668 $doccreationdate .= substr($dcdate, 14, 3).':'.substr($dcdate, 18, 2);
9669 $doccreationdate = TCPDF_STATIC
::_escapeXML($doccreationdate);
9670 // convert doc modification date format
9671 $dmdate = TCPDF_STATIC
::getFormattedDate($this->doc_modification_timestamp
);
9672 $docmoddate = substr($dmdate, 0, 4).'-'.substr($dmdate, 4, 2).'-'.substr($dmdate, 6, 2);
9673 $docmoddate .= 'T'.substr($dmdate, 8, 2).':'.substr($dmdate, 10, 2).':'.substr($dmdate, 12, 2);
9674 $docmoddate .= substr($dmdate, 14, 3).':'.substr($dmdate, 18, 2);
9675 $docmoddate = TCPDF_STATIC
::_escapeXML($docmoddate);
9676 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'."\n";
9677 $xmp .= "\t\t\t".'<xmp:CreateDate>'.$doccreationdate.'</xmp:CreateDate>'."\n";
9678 $xmp .= "\t\t\t".'<xmp:CreatorTool>'.$this->creator
.'</xmp:CreatorTool>'."\n";
9679 $xmp .= "\t\t\t".'<xmp:ModifyDate>'.$docmoddate.'</xmp:ModifyDate>'."\n";
9680 $xmp .= "\t\t\t".'<xmp:MetadataDate>'.$doccreationdate.'</xmp:MetadataDate>'."\n";
9681 $xmp .= "\t\t".'</rdf:Description>'."\n";
9682 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'."\n";
9683 $xmp .= "\t\t\t".'<pdf:Keywords>'.TCPDF_STATIC
::_escapeXML($this->keywords
).'</pdf:Keywords>'."\n";
9684 $xmp .= "\t\t\t".'<pdf:Producer>'.TCPDF_STATIC
::_escapeXML(TCPDF_STATIC
::getTCPDFProducer()).'</pdf:Producer>'."\n";
9685 $xmp .= "\t\t".'</rdf:Description>'."\n";
9686 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">'."\n";
9687 $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);
9688 $xmp .= "\t\t\t".'<xmpMM:DocumentID>'.$uuid.'</xmpMM:DocumentID>'."\n";
9689 $xmp .= "\t\t\t".'<xmpMM:InstanceID>'.$uuid.'</xmpMM:InstanceID>'."\n";
9690 $xmp .= "\t\t".'</rdf:Description>'."\n";
9691 if ($this->pdfa_mode
) {
9692 $xmp .= "\t\t".'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'."\n";
9693 $xmp .= "\t\t\t".'<pdfaid:part>'.$this->pdfa_version
.'</pdfaid:part>'."\n";
9694 $xmp .= "\t\t\t".'<pdfaid:conformance>B</pdfaid:conformance>'."\n";
9695 $xmp .= "\t\t".'</rdf:Description>'."\n";
9697 // XMP extension schemas
9698 $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";
9699 $xmp .= "\t\t\t".'<pdfaExtension:schemas>'."\n";
9700 $xmp .= "\t\t\t\t".'<rdf:Bag>'."\n";
9701 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9702 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>'."\n";
9703 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdf</pdfaSchema:prefix>'."\n";
9704 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>'."\n";
9705 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9706 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9707 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9708 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9709 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Adobe PDF Schema</pdfaProperty:description>'."\n";
9710 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9711 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9712 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9713 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9714 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9715 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9716 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9717 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>'."\n";
9718 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>'."\n";
9719 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>'."\n";
9720 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9721 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9722 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9723 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9724 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>'."\n";
9725 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>InstanceID</pdfaProperty:name>'."\n";
9726 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>URI</pdfaProperty:valueType>'."\n";
9727 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9728 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9729 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9730 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9731 $xmp .= "\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9732 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>'."\n";
9733 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>'."\n";
9734 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>'."\n";
9735 $xmp .= "\t\t\t\t\t\t".'<pdfaSchema:property>'."\n";
9736 $xmp .= "\t\t\t\t\t\t\t".'<rdf:Seq>'."\n";
9737 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9738 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9739 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>'."\n";
9740 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>part</pdfaProperty:name>'."\n";
9741 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Integer</pdfaProperty:valueType>'."\n";
9742 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9743 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9744 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9745 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>'."\n";
9746 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>amd</pdfaProperty:name>'."\n";
9747 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9748 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9749 $xmp .= "\t\t\t\t\t\t\t\t".'<rdf:li rdf:parseType="Resource">'."\n";
9750 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:category>internal</pdfaProperty:category>'."\n";
9751 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>'."\n";
9752 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:name>conformance</pdfaProperty:name>'."\n";
9753 $xmp .= "\t\t\t\t\t\t\t\t\t".'<pdfaProperty:valueType>Text</pdfaProperty:valueType>'."\n";
9754 $xmp .= "\t\t\t\t\t\t\t\t".'</rdf:li>'."\n";
9755 $xmp .= "\t\t\t\t\t\t\t".'</rdf:Seq>'."\n";
9756 $xmp .= "\t\t\t\t\t\t".'</pdfaSchema:property>'."\n";
9757 $xmp .= "\t\t\t\t\t".'</rdf:li>'."\n";
9758 $xmp .= "\t\t\t\t".'</rdf:Bag>'."\n";
9759 $xmp .= "\t\t\t".'</pdfaExtension:schemas>'."\n";
9760 $xmp .= "\t\t".'</rdf:Description>'."\n";
9761 $xmp .= $this->custom_xmp_rdf
;
9762 $xmp .= "\t".'</rdf:RDF>'."\n";
9763 $xmp .= $this->custom_xmp
;
9764 $xmp .= '</x:xmpmeta>'."\n";
9765 $xmp .= '<?xpacket end="w"?>';
9766 $out = '<< /Type /Metadata /Subtype /XML /Length '.strlen($xmp).' >> stream'."\n".$xmp."\n".'endstream'."\n".'endobj';
9767 // restore previous isunicode value
9768 $this->isunicode
= $prev_isunicode;
9769 $this->encrypted
= $prev_encrypted;
9776 * @return int object id
9779 protected function _putcatalog() {
9781 $xmpobj = $this->_putXMP();
9782 // if required, add standard sRGB ICC colour profile
9783 if ($this->pdfa_mode
OR $this->force_srgb
) {
9784 $iccobj = $this->_newobj();
9785 $icc = file_get_contents(dirname(__FILE__
).'/include/sRGB.icc');
9787 if ($this->compress
) {
9788 $filter = ' /Filter /FlateDecode';
9789 $icc = gzcompress($icc);
9791 $icc = $this->_getrawstream($icc);
9792 $this->_out('<</N 3 '.$filter.'/Length '.strlen($icc).'>> stream'."\n".$icc."\n".'endstream'."\n".'endobj');
9795 $oid = $this->_newobj();
9796 $out = '<< /Type /Catalog';
9797 $out .= ' /Version /'.$this->PDFVersion
;
9798 //$out .= ' /Extensions <<>>';
9799 $out .= ' /Pages 1 0 R';
9800 //$out .= ' /PageLabels ' //...;
9801 $out .= ' /Names <<';
9802 if ((!$this->pdfa_mode
) AND !empty($this->n_js
)) {
9803 $out .= ' /JavaScript '.$this->n_js
;
9805 if (!empty($this->efnames
)) {
9806 $out .= ' /EmbeddedFiles <</Names [';
9807 foreach ($this->efnames
AS $fn => $fref) {
9808 $out .= ' '.$this->_datastring($fn).' '.$fref;
9813 if (!empty($this->dests
)) {
9814 $out .= ' /Dests '.($this->n_dests
).' 0 R';
9816 $out .= $this->_putviewerpreferences();
9817 if (isset($this->LayoutMode
) AND (!TCPDF_STATIC
::empty_string($this->LayoutMode
))) {
9818 $out .= ' /PageLayout /'.$this->LayoutMode
;
9820 if (isset($this->PageMode
) AND (!TCPDF_STATIC
::empty_string($this->PageMode
))) {
9821 $out .= ' /PageMode /'.$this->PageMode
;
9823 if (count($this->outlines
) > 0) {
9824 $out .= ' /Outlines '.$this->OutlineRoot
.' 0 R';
9825 $out .= ' /PageMode /UseOutlines';
9827 //$out .= ' /Threads []';
9828 if ($this->ZoomMode
== 'fullpage') {
9829 $out .= ' /OpenAction ['.$this->page_obj_id
[1].' 0 R /Fit]';
9830 } elseif ($this->ZoomMode
== 'fullwidth') {
9831 $out .= ' /OpenAction ['.$this->page_obj_id
[1].' 0 R /FitH null]';
9832 } elseif ($this->ZoomMode
== 'real') {
9833 $out .= ' /OpenAction ['.$this->page_obj_id
[1].' 0 R /XYZ null null 1]';
9834 } elseif (!is_string($this->ZoomMode
)) {
9835 $out .= sprintf(' /OpenAction ['.$this->page_obj_id
[1].' 0 R /XYZ null null %F]', ($this->ZoomMode
/ 100));
9837 //$out .= ' /AA <<>>';
9838 //$out .= ' /URI <<>>';
9839 $out .= ' /Metadata '.$xmpobj.' 0 R';
9840 //$out .= ' /StructTreeRoot <<>>';
9841 //$out .= ' /MarkInfo <<>>';
9842 if (isset($this->l
['a_meta_language'])) {
9843 $out .= ' /Lang '.$this->_textstring($this->l
['a_meta_language'], $oid);
9845 //$out .= ' /SpiderInfo <<>>';
9846 // set OutputIntent to sRGB IEC61966-2.1 if required
9847 if ($this->pdfa_mode
OR $this->force_srgb
) {
9848 $out .= ' /OutputIntents [<<';
9849 $out .= ' /Type /OutputIntent';
9850 $out .= ' /S /GTS_PDFA1';
9851 $out .= ' /OutputCondition '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9852 $out .= ' /OutputConditionIdentifier '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9853 $out .= ' /RegistryName '.$this->_textstring('http://www.color.org', $oid);
9854 $out .= ' /Info '.$this->_textstring('sRGB IEC61966-2.1', $oid);
9855 $out .= ' /DestOutputProfile '.$iccobj.' 0 R';
9858 //$out .= ' /PieceInfo <<>>';
9859 if (!empty($this->pdflayers
)) {
9863 foreach ($this->pdflayers
as $layer) {
9864 $layer_obj_ref = ' '.$layer['objid'].' 0 R';
9865 $lyrobjs .= $layer_obj_ref;
9866 if ($layer['view'] === false) {
9867 $lyrobjs_off .= $layer_obj_ref;
9869 if ($layer['lock']) {
9870 $lyrobjs_lock .= $layer_obj_ref;
9873 $out .= ' /OCProperties << /OCGs ['.$lyrobjs.']';
9875 $out .= ' /Name '.$this->_textstring('Layers', $oid);
9876 $out .= ' /Creator '.$this->_textstring('TCPDF', $oid);
9877 $out .= ' /BaseState /ON';
9878 $out .= ' /OFF ['.$lyrobjs_off.']';
9879 $out .= ' /Locked ['.$lyrobjs_lock.']';
9880 $out .= ' /Intent /View';
9882 $out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9883 $out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9885 $out .= ' /Order ['.$lyrobjs.']';
9886 $out .= ' /ListMode /AllPages';
9887 //$out .= ' /RBGroups ['..']';
9888 //$out .= ' /Locked ['..']';
9893 if (!empty($this->form_obj_id
)
9894 OR ($this->sign
AND isset($this->signature_data
['cert_type']))
9895 OR !empty($this->empty_signature_appearance
)) {
9896 $out .= ' /AcroForm <<';
9898 if ($this->sign
AND isset($this->signature_data
['cert_type'])) {
9899 // set reference for signature object
9900 $objrefs .= $this->sig_obj_id
.' 0 R';
9902 if (!empty($this->empty_signature_appearance
)) {
9903 foreach ($this->empty_signature_appearance
as $esa) {
9904 // set reference for empty signature objects
9905 $objrefs .= ' '.$esa['objid'].' 0 R';
9908 if (!empty($this->form_obj_id
)) {
9909 foreach($this->form_obj_id
as $objid) {
9910 $objrefs .= ' '.$objid.' 0 R';
9913 $out .= ' /Fields ['.$objrefs.']';
9914 // It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
9915 if (empty($this->signature_data
['approval']) OR ($this->signature_data
['approval'] != 'A')) {
9916 $out .= ' /NeedAppearances false';
9918 if ($this->sign
AND isset($this->signature_data
['cert_type'])) {
9919 if ($this->signature_data
['cert_type'] > 0) {
9920 $out .= ' /SigFlags 3';
9922 $out .= ' /SigFlags 1';
9926 if (isset($this->annotation_fonts
) AND !empty($this->annotation_fonts
)) {
9928 $out .= ' /Font <<';
9929 foreach ($this->annotation_fonts
as $fontkey => $fontid) {
9930 $out .= ' /F'.$fontid.' '.$this->font_obj_ids
[$fontkey].' 0 R';
9934 $font = $this->getFontBuffer((($this->pdfa_mode
) ?
'pdfa' : '') .'helvetica');
9935 $out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
9936 $out .= ' /Q '.(($this->rtl
)?
'2':'0');
9940 if ($this->sign
AND isset($this->signature_data
['cert_type'])
9941 AND (empty($this->signature_data
['approval']) OR ($this->signature_data
['approval'] != 'A'))) {
9942 if ($this->signature_data
['cert_type'] > 0) {
9943 $out .= ' /Perms << /DocMDP '.($this->sig_obj_id +
1).' 0 R >>';
9945 $out .= ' /Perms << /UR3 '.($this->sig_obj_id +
1).' 0 R >>';
9949 //$out .= ' /Legal <<>>';
9950 //$out .= ' /Requirements []';
9951 //$out .= ' /Collection <<>>';
9952 //$out .= ' /NeedsRendering true';
9954 $out .= "\n".'endobj';
9960 * Output viewer preferences.
9961 * @return string for viewer preferences
9962 * @author Nicola asuni
9963 * @since 3.1.000 (2008-06-09)
9966 protected function _putviewerpreferences() {
9967 $vp = $this->viewer_preferences
;
9968 $out = ' /ViewerPreferences <<';
9970 $out .= ' /Direction /R2L';
9972 $out .= ' /Direction /L2R';
9974 if (isset($vp['HideToolbar']) AND ($vp['HideToolbar'])) {
9975 $out .= ' /HideToolbar true';
9977 if (isset($vp['HideMenubar']) AND ($vp['HideMenubar'])) {
9978 $out .= ' /HideMenubar true';
9980 if (isset($vp['HideWindowUI']) AND ($vp['HideWindowUI'])) {
9981 $out .= ' /HideWindowUI true';
9983 if (isset($vp['FitWindow']) AND ($vp['FitWindow'])) {
9984 $out .= ' /FitWindow true';
9986 if (isset($vp['CenterWindow']) AND ($vp['CenterWindow'])) {
9987 $out .= ' /CenterWindow true';
9989 if (isset($vp['DisplayDocTitle']) AND ($vp['DisplayDocTitle'])) {
9990 $out .= ' /DisplayDocTitle true';
9992 if (isset($vp['NonFullScreenPageMode'])) {
9993 $out .= ' /NonFullScreenPageMode /'.$vp['NonFullScreenPageMode'];
9995 if (isset($vp['ViewArea'])) {
9996 $out .= ' /ViewArea /'.$vp['ViewArea'];
9998 if (isset($vp['ViewClip'])) {
9999 $out .= ' /ViewClip /'.$vp['ViewClip'];
10001 if (isset($vp['PrintArea'])) {
10002 $out .= ' /PrintArea /'.$vp['PrintArea'];
10004 if (isset($vp['PrintClip'])) {
10005 $out .= ' /PrintClip /'.$vp['PrintClip'];
10007 if (isset($vp['PrintScaling'])) {
10008 $out .= ' /PrintScaling /'.$vp['PrintScaling'];
10010 if (isset($vp['Duplex']) AND (!TCPDF_STATIC
::empty_string($vp['Duplex']))) {
10011 $out .= ' /Duplex /'.$vp['Duplex'];
10013 if (isset($vp['PickTrayByPDFSize'])) {
10014 if ($vp['PickTrayByPDFSize']) {
10015 $out .= ' /PickTrayByPDFSize true';
10017 $out .= ' /PickTrayByPDFSize false';
10020 if (isset($vp['PrintPageRange'])) {
10021 $PrintPageRangeNum = '';
10022 foreach ($vp['PrintPageRange'] as $k => $v) {
10023 $PrintPageRangeNum .= ' '.($v - 1).'';
10025 $out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
10027 if (isset($vp['NumCopies'])) {
10028 $out .= ' /NumCopies '.intval($vp['NumCopies']);
10035 * Output PDF File Header (7.5.2).
10038 protected function _putheader() {
10039 $this->_out('%PDF-'.$this->PDFVersion
);
10040 $this->_out('%'.chr(0xe2).chr(0xe3).chr(0xcf).chr(0xd3));
10044 * Output end of document (EOF).
10047 protected function _enddoc() {
10048 if (isset($this->CurrentFont
['fontkey']) AND isset($this->CurrentFont
['subsetchars'])) {
10049 // save subset chars of the previous font
10050 $this->setFontSubBuffer($this->CurrentFont
['fontkey'], 'subsetchars', $this->CurrentFont
['subsetchars']);
10053 $this->_putheader();
10054 $this->_putpages();
10055 $this->_putresources();
10056 // empty signature fields
10057 if (!empty($this->empty_signature_appearance
)) {
10058 foreach ($this->empty_signature_appearance
as $key => $esa) {
10059 // widget annotation for empty signature
10060 $out = $this->_getobj($esa['objid'])."\n";
10061 $out .= '<< /Type /Annot';
10062 $out .= ' /Subtype /Widget';
10063 $out .= ' /Rect ['.$esa['rect'].']';
10064 $out .= ' /P '.$this->page_obj_id
[($esa['page'])].' 0 R'; // link to signature appearance page
10066 $out .= ' /FT /Sig';
10067 $signame = $esa['name'].sprintf(' [%03d]', ($key +
1));
10068 $out .= ' /T '.$this->_textstring($signame, $esa['objid']);
10071 $out .= "\n".'endobj';
10076 if ($this->sign
AND isset($this->signature_data
['cert_type'])) {
10077 // widget annotation for signature
10078 $out = $this->_getobj($this->sig_obj_id
)."\n";
10079 $out .= '<< /Type /Annot';
10080 $out .= ' /Subtype /Widget';
10081 $out .= ' /Rect ['.$this->signature_appearance
['rect'].']';
10082 $out .= ' /P '.$this->page_obj_id
[($this->signature_appearance
['page'])].' 0 R'; // link to signature appearance page
10084 $out .= ' /FT /Sig';
10085 $out .= ' /T '.$this->_textstring($this->signature_appearance
['name'], $this->sig_obj_id
);
10087 $out .= ' /V '.($this->sig_obj_id +
1).' 0 R';
10089 $out .= "\n".'endobj';
10092 $this->_putsignature();
10095 $objid_info = $this->_putinfo();
10097 $objid_catalog = $this->_putcatalog();
10099 $o = $this->bufferlen
;
10101 $this->_out('xref');
10102 $this->_out('0 '.($this->n +
1));
10103 $this->_out('0000000000 65535 f ');
10104 $freegen = ($this->n +
2);
10105 for ($i=1; $i <= $this->n
; ++
$i) {
10106 if (!isset($this->offsets
[$i]) AND ($i > 1)) {
10107 $this->_out(sprintf('0000000000 %05d f ', $freegen));
10110 $this->_out(sprintf('%010d 00000 n ', $this->offsets
[$i]));
10114 $out = 'trailer'."\n";
10116 $out .= ' /Size '.($this->n +
1);
10117 $out .= ' /Root '.$objid_catalog.' 0 R';
10118 $out .= ' /Info '.$objid_info.' 0 R';
10119 if ($this->encrypted
) {
10120 $out .= ' /Encrypt '.$this->encryptdata
['objid'].' 0 R';
10122 $out .= ' /ID [ <'.$this->file_id
.'> <'.$this->file_id
.'> ]';
10125 $this->_out('startxref');
10127 $this->_out('%%EOF');
10128 $this->state
= 3; // end-of-doc
10132 * Initialize a new page.
10133 * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
10134 * @param mixed $format 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().
10136 * @see getPageSizeFromFormat(), setPageFormat()
10138 protected function _beginpage($orientation='', $format='') {
10140 $this->pageobjects
[$this->page
] = array();
10141 $this->setPageBuffer($this->page
, '');
10142 // initialize array for graphics tranformation positions inside a page buffer
10143 $this->transfmrk
[$this->page
] = array();
10145 if (TCPDF_STATIC
::empty_string($orientation)) {
10146 if (isset($this->CurOrientation
)) {
10147 $orientation = $this->CurOrientation
;
10148 } elseif ($this->fwPt
> $this->fhPt
) {
10150 $orientation = 'L';
10153 $orientation = 'P';
10156 if (TCPDF_STATIC
::empty_string($format)) {
10157 $this->pagedim
[$this->page
] = $this->pagedim
[($this->page
- 1)];
10158 $this->setPageOrientation($orientation);
10160 $this->setPageFormat($format, $orientation);
10163 $this->x
= $this->w
- $this->rMargin
;
10165 $this->x
= $this->lMargin
;
10167 $this->y
= $this->tMargin
;
10168 if (isset($this->newpagegroup
[$this->page
])) {
10169 // start a new group
10170 $this->currpagegroup
= $this->newpagegroup
[$this->page
];
10171 $this->pagegroups
[$this->currpagegroup
] = 1;
10172 } elseif (isset($this->currpagegroup
) AND ($this->currpagegroup
> 0)) {
10173 ++
$this->pagegroups
[$this->currpagegroup
];
10178 * Mark end of page.
10181 protected function _endpage() {
10182 $this->setVisibility('all');
10187 * Begin a new object and return the object number.
10188 * @return int object number
10191 protected function _newobj() {
10192 $this->_out($this->_getobj());
10197 * Return the starting object string for the selected object ID.
10198 * @param int|null $objid Object ID (leave empty to get a new ID).
10199 * @return string the starting object string
10201 * @since 5.8.009 (2010-08-20)
10203 protected function _getobj($objid=null) {
10204 if (TCPDF_STATIC
::empty_string($objid)) {
10208 $this->offsets
[$objid] = $this->bufferlen
;
10209 $this->pageobjects
[$this->page
][] = $objid;
10210 return $objid.' 0 obj';
10215 * @param int $x X coordinate
10216 * @param int $y Y coordinate
10217 * @param string $txt text to underline
10220 protected function _dounderline($x, $y, $txt) {
10221 $w = $this->GetStringWidth($txt);
10222 return $this->_dounderlinew($x, $y, $w);
10226 * Underline for rectangular text area.
10227 * @param int $x X coordinate
10228 * @param int $y Y coordinate
10229 * @param int $w width to underline
10231 * @since 4.8.008 (2009-09-29)
10233 protected function _dounderlinew($x, $y, $w) {
10234 $linew = - $this->CurrentFont
['ut'] / 1000 * $this->FontSizePt
;
10235 return sprintf('%F %F %F %F re f', $x * $this->k
, ((($this->h
- $y) * $this->k
) +
$linew), $w * $this->k
, $linew);
10239 * Line through text.
10240 * @param int $x X coordinate
10241 * @param int $y Y coordinate
10242 * @param string $txt text to linethrough
10245 protected function _dolinethrough($x, $y, $txt) {
10246 $w = $this->GetStringWidth($txt);
10247 return $this->_dolinethroughw($x, $y, $w);
10251 * Line through for rectangular text area.
10252 * @param int $x X coordinate
10253 * @param int $y Y coordinate
10254 * @param int $w line length (width)
10256 * @since 4.9.008 (2009-09-29)
10258 protected function _dolinethroughw($x, $y, $w) {
10259 $linew = - $this->CurrentFont
['ut'] / 1000 * $this->FontSizePt
;
10260 return sprintf('%F %F %F %F re f', $x * $this->k
, ((($this->h
- $y) * $this->k
) +
$linew +
($this->FontSizePt
/ 3)), $w * $this->k
, $linew);
10265 * @param int $x X coordinate
10266 * @param int $y Y coordinate
10267 * @param string $txt text to overline
10269 * @since 4.9.015 (2010-04-19)
10271 protected function _dooverline($x, $y, $txt) {
10272 $w = $this->GetStringWidth($txt);
10273 return $this->_dooverlinew($x, $y, $w);
10277 * Overline for rectangular text area.
10278 * @param int $x X coordinate
10279 * @param int $y Y coordinate
10280 * @param int $w width to overline
10282 * @since 4.9.015 (2010-04-19)
10284 protected function _dooverlinew($x, $y, $w) {
10285 $linew = - $this->CurrentFont
['ut'] / 1000 * $this->FontSizePt
;
10286 return sprintf('%F %F %F %F re f', $x * $this->k
, (($this->h
- $y +
$this->FontAscent
) * $this->k
) - $linew, $w * $this->k
, $linew);
10291 * Format a data string for meta information
10292 * @param string $s data string to escape.
10293 * @param int $n object ID
10294 * @return string escaped string.
10297 protected function _datastring($s, $n=0) {
10301 $s = $this->_encrypt_data($n, $s);
10302 return '('. TCPDF_STATIC
::_escape($s).')';
10306 * Set the document creation timestamp
10307 * @param mixed $time Document creation timestamp in seconds or date-time string.
10309 * @since 5.9.152 (2012-03-23)
10311 public function setDocCreationTimestamp($time) {
10312 if (is_string($time)) {
10313 $time = TCPDF_STATIC
::getTimestamp($time);
10315 $this->doc_creation_timestamp
= intval($time);
10319 * Set the document modification timestamp
10320 * @param mixed $time Document modification timestamp in seconds or date-time string.
10322 * @since 5.9.152 (2012-03-23)
10324 public function setDocModificationTimestamp($time) {
10325 if (is_string($time)) {
10326 $time = TCPDF_STATIC
::getTimestamp($time);
10328 $this->doc_modification_timestamp
= intval($time);
10332 * Returns document creation timestamp in seconds.
10333 * @return int Creation timestamp in seconds.
10335 * @since 5.9.152 (2012-03-23)
10337 public function getDocCreationTimestamp() {
10338 return $this->doc_creation_timestamp
;
10342 * Returns document modification timestamp in seconds.
10343 * @return int Modfication timestamp in seconds.
10345 * @since 5.9.152 (2012-03-23)
10347 public function getDocModificationTimestamp() {
10348 return $this->doc_modification_timestamp
;
10352 * Returns a formatted date for meta information
10353 * @param int $n Object ID.
10354 * @param int $timestamp Timestamp to convert.
10355 * @return string escaped date string.
10357 * @since 4.6.028 (2009-08-25)
10359 protected function _datestring($n=0, $timestamp=0) {
10360 if ((empty($timestamp)) OR ($timestamp < 0)) {
10361 $timestamp = $this->doc_creation_timestamp
;
10363 return $this->_datastring('D:'.TCPDF_STATIC
::getFormattedDate($timestamp), $n);
10367 * Format a text string for meta information
10368 * @param string $s string to escape.
10369 * @param int $n object ID
10370 * @return string escaped string.
10373 protected function _textstring($s, $n=0) {
10374 if ($this->isunicode
) {
10375 //Convert string to UTF-16BE
10376 $s = TCPDF_FONTS
::UTF8ToUTF16BE($s, true, $this->isunicode
, $this->CurrentFont
);
10378 return $this->_datastring($s, $n);
10382 * get raw output stream.
10383 * @param string $s string to output.
10384 * @param int $n object reference for encryption mode
10386 * @author Nicola Asuni
10387 * @since 5.5.000 (2010-06-22)
10389 protected function _getrawstream($s, $n=0) {
10391 // default to current object
10394 return $this->_encrypt_data($n, $s);
10398 * Output a string to the document.
10399 * @param string $s string to output.
10402 protected function _out($s) {
10403 if ($this->state
== 2) {
10404 if ($this->inxobj
) {
10405 // we are inside an XObject template
10406 $this->xobjects
[$this->xobjid
]['outdata'] .= $s."\n";
10407 } elseif ((!$this->InFooter
) AND isset($this->footerlen
[$this->page
]) AND ($this->footerlen
[$this->page
] > 0)) {
10408 // puts data before page footer
10409 $pagebuff = $this->getPageBuffer($this->page
);
10410 $page = substr($pagebuff, 0, -$this->footerlen
[$this->page
]);
10411 $footer = substr($pagebuff, -$this->footerlen
[$this->page
]);
10412 $this->setPageBuffer($this->page
, $page.$s."\n".$footer);
10413 // update footer position
10414 $this->footerpos
[$this->page
] +
= strlen($s."\n");
10417 $this->setPageBuffer($this->page
, $s."\n", true);
10419 } elseif ($this->state
> 0) {
10420 // set general data
10421 $this->setBuffer($s."\n");
10427 * @param array<int,string|float|null> $font Array describing the basic font parameters: (family, style, size).
10428 * @phpstan-param array{0: string, 1: string, 2: float|null} $font
10432 public function setHeaderFont($font) {
10433 $this->header_font
= $font;
10438 * @return array<int,string|float|null> Array describing the basic font parameters: (family, style, size).
10439 * @phpstan-return array{0: string, 1: string, 2: float|null}
10441 * @since 4.0.012 (2008-07-24)
10443 public function getHeaderFont() {
10444 return $this->header_font
;
10449 * @param array<int,string|float|null> $font Array describing the basic font parameters: (family, style, size).
10450 * @phpstan-param array{0: string, 1: string, 2: float|null} $font
10454 public function setFooterFont($font) {
10455 $this->footer_font
= $font;
10460 * @return array<int,string|float|null> Array describing the basic font parameters: (family, style, size).
10461 * @phpstan-return array{0: string, 1: string, 2: float|null} $font
10463 * @since 4.0.012 (2008-07-24)
10465 public function getFooterFont() {
10466 return $this->footer_font
;
10470 * Set language array.
10471 * @param array $language
10475 public function setLanguageArray($language) {
10476 $this->l
= $language;
10477 if (isset($this->l
['a_meta_dir'])) {
10478 $this->rtl
= $this->l
['a_meta_dir']=='rtl' ?
true : false;
10480 $this->rtl
= false;
10485 * Returns the PDF data.
10488 public function getPDFData() {
10489 if ($this->state
< 3) {
10492 return $this->buffer
;
10496 * Output anchor link.
10497 * @param string $url link URL or internal link (i.e.: <a href="#23,4.5">link to page 23 at 4.5 Y position</a>)
10498 * @param string $name link name
10499 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
10500 * @param boolean $firstline if true prints only the first line and return the remaining string.
10501 * @param array|null $color array of RGB text color
10502 * @param string $style font style (U, D, B, I)
10503 * @param boolean $firstblock if true the string is the starting of a line.
10504 * @return int the number of cells used or the remaining text if $firstline = true;
10507 public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color=null, $style=-1, $firstblock=false) {
10508 if (isset($url[1]) AND ($url[0] == '#') AND is_numeric($url[1])) {
10509 // convert url to internal link
10510 $lnkdata = explode(',', $url);
10511 if (isset($lnkdata[0]) ) {
10512 $page = substr($lnkdata[0], 1);
10513 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
10514 $lnky = floatval($lnkdata[1]);
10518 $url = $this->AddLink();
10519 $this->setLink($url, $lnky, $page);
10522 // store current settings
10523 $prevcolor = $this->fgcolor
;
10524 $prevstyle = $this->FontStyle
;
10525 if (empty($color)) {
10526 $this->setTextColorArray($this->htmlLinkColorArray
);
10528 $this->setTextColorArray($color);
10530 if ($style == -1) {
10531 $this->setFont('', $this->FontStyle
.$this->htmlLinkFontStyle
);
10533 $this->setFont('', $this->FontStyle
.$style);
10535 $ret = $this->Write($this->lasth
, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
10536 // restore settings
10537 $this->setFont('', $prevstyle);
10538 $this->setTextColorArray($prevcolor);
10543 * Converts pixels to User's Units.
10544 * @param int $px pixels
10545 * @return float value in user's unit
10547 * @see setImageScale(), getImageScale()
10549 public function pixelsToUnits($px) {
10550 return ($px / ($this->imgscale
* $this->k
));
10554 * Reverse function for htmlentities.
10555 * Convert entities in UTF-8.
10556 * @param string $text_to_convert Text to convert.
10557 * @return string converted text string
10560 public function unhtmlentities($text_to_convert) {
10561 return @html_entity_decode
($text_to_convert, ENT_QUOTES
, $this->encoding
);
10564 // ENCRYPTION METHODS ----------------------------------
10567 * Compute encryption key depending on object number where the encrypted data is stored.
10568 * This is used for all strings and streams without crypt filter specifier.
10569 * @param int $n object number
10570 * @return int object key
10572 * @author Nicola Asuni
10573 * @since 2.0.000 (2008-01-02)
10575 protected function _objectkey($n) {
10576 $objkey = $this->encryptdata
['key'].pack('VXxx', $n);
10577 if ($this->encryptdata
['mode'] == 2) { // AES-128
10579 $objkey .= "\x73\x41\x6C\x54"; // sAlT
10581 $objkey = substr(TCPDF_STATIC
::_md5_16($objkey), 0, (($this->encryptdata
['Length'] / 8) +
5));
10582 $objkey = substr($objkey, 0, 16);
10587 * Encrypt the input string.
10588 * @param int $n object number
10589 * @param string $s data string to encrypt
10590 * @return string encrypted string
10592 * @author Nicola Asuni
10593 * @since 5.0.005 (2010-05-11)
10595 protected function _encrypt_data($n, $s) {
10596 if (!$this->encrypted
) {
10599 switch ($this->encryptdata
['mode']) {
10601 case 1: { // RC4-128
10602 $s = TCPDF_STATIC
::_RC4($this->_objectkey($n), $s, $this->last_enc_key
, $this->last_enc_key_c
);
10605 case 2: { // AES-128
10606 $s = TCPDF_STATIC
::_AES($this->_objectkey($n), $s);
10609 case 3: { // AES-256
10610 $s = TCPDF_STATIC
::_AES($this->encryptdata
['key'], $s);
10618 * Put encryption on PDF document.
10620 * @author Nicola Asuni
10621 * @since 2.0.000 (2008-01-02)
10623 protected function _putencryption() {
10624 if (!$this->encrypted
) {
10627 $this->encryptdata
['objid'] = $this->_newobj();
10629 if (!isset($this->encryptdata
['Filter']) OR empty($this->encryptdata
['Filter'])) {
10630 $this->encryptdata
['Filter'] = 'Standard';
10632 $out .= ' /Filter /'.$this->encryptdata
['Filter'];
10633 if (isset($this->encryptdata
['SubFilter']) AND !empty($this->encryptdata
['SubFilter'])) {
10634 $out .= ' /SubFilter /'.$this->encryptdata
['SubFilter'];
10636 if (!isset($this->encryptdata
['V']) OR empty($this->encryptdata
['V'])) {
10637 $this->encryptdata
['V'] = 1;
10639 // V is a code specifying the algorithm to be used in encrypting and decrypting the document
10640 $out .= ' /V '.$this->encryptdata
['V'];
10641 if (isset($this->encryptdata
['Length']) AND !empty($this->encryptdata
['Length'])) {
10642 // The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
10643 $out .= ' /Length '.$this->encryptdata
['Length'];
10645 $out .= ' /Length 40';
10647 if ($this->encryptdata
['V'] >= 4) {
10648 if (!isset($this->encryptdata
['StmF']) OR empty($this->encryptdata
['StmF'])) {
10649 $this->encryptdata
['StmF'] = 'Identity';
10651 if (!isset($this->encryptdata
['StrF']) OR empty($this->encryptdata
['StrF'])) {
10652 // The name of the crypt filter that shall be used when decrypting all strings in the document.
10653 $this->encryptdata
['StrF'] = 'Identity';
10655 // A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
10656 if (isset($this->encryptdata
['CF']) AND !empty($this->encryptdata
['CF'])) {
10658 $out .= ' /'.$this->encryptdata
['StmF'].' <<';
10659 $out .= ' /Type /CryptFilter';
10660 if (isset($this->encryptdata
['CF']['CFM']) AND !empty($this->encryptdata
['CF']['CFM'])) {
10662 $out .= ' /CFM /'.$this->encryptdata
['CF']['CFM'];
10663 if ($this->encryptdata
['pubkey']) {
10664 $out .= ' /Recipients [';
10665 foreach ($this->encryptdata
['Recipients'] as $rec) {
10666 $out .= ' <'.$rec.'>';
10669 if (isset($this->encryptdata
['CF']['EncryptMetadata']) AND (!$this->encryptdata
['CF']['EncryptMetadata'])) {
10670 $out .= ' /EncryptMetadata false';
10672 $out .= ' /EncryptMetadata true';
10676 $out .= ' /CFM /None';
10678 if (isset($this->encryptdata
['CF']['AuthEvent']) AND !empty($this->encryptdata
['CF']['AuthEvent'])) {
10679 // The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
10680 $out .= ' /AuthEvent /'.$this->encryptdata
['CF']['AuthEvent'];
10682 $out .= ' /AuthEvent /DocOpen';
10684 if (isset($this->encryptdata
['CF']['Length']) AND !empty($this->encryptdata
['CF']['Length'])) {
10685 // The bit length of the encryption key.
10686 $out .= ' /Length '.$this->encryptdata
['CF']['Length'];
10690 // The name of the crypt filter that shall be used by default when decrypting streams.
10691 $out .= ' /StmF /'.$this->encryptdata
['StmF'];
10692 // The name of the crypt filter that shall be used when decrypting all strings in the document.
10693 $out .= ' /StrF /'.$this->encryptdata
['StrF'];
10694 if (isset($this->encryptdata
['EFF']) AND !empty($this->encryptdata
['EFF'])) {
10695 // The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
10696 $out .= ' /EFF /'.$this->encryptdata
[''];
10699 // Additional encryption dictionary entries for the standard security handler
10700 if ($this->encryptdata
['pubkey']) {
10701 if (($this->encryptdata
['V'] < 4) AND isset($this->encryptdata
['Recipients']) AND !empty($this->encryptdata
['Recipients'])) {
10702 $out .= ' /Recipients [';
10703 foreach ($this->encryptdata
['Recipients'] as $rec) {
10704 $out .= ' <'.$rec.'>';
10710 if ($this->encryptdata
['V'] == 5) { // AES-256
10712 $out .= ' /OE ('.TCPDF_STATIC
::_escape($this->encryptdata
['OE']).')';
10713 $out .= ' /UE ('.TCPDF_STATIC
::_escape($this->encryptdata
['UE']).')';
10714 $out .= ' /Perms ('.TCPDF_STATIC
::_escape($this->encryptdata
['perms']).')';
10715 } elseif ($this->encryptdata
['V'] == 4) { // AES-128
10717 } elseif ($this->encryptdata
['V'] < 2) { // RC-40
10722 $out .= ' /O ('.TCPDF_STATIC
::_escape($this->encryptdata
['O']).')';
10723 $out .= ' /U ('.TCPDF_STATIC
::_escape($this->encryptdata
['U']).')';
10724 $out .= ' /P '.$this->encryptdata
['P'];
10725 if (isset($this->encryptdata
['EncryptMetadata']) AND (!$this->encryptdata
['EncryptMetadata'])) {
10726 $out .= ' /EncryptMetadata false';
10728 $out .= ' /EncryptMetadata true';
10732 $out .= "\n".'endobj';
10737 * Compute U value (used for encryption)
10738 * @return string U value
10740 * @since 2.0.000 (2008-01-02)
10741 * @author Nicola Asuni
10743 protected function _Uvalue() {
10744 if ($this->encryptdata
['mode'] == 0) { // RC4-40
10745 return TCPDF_STATIC
::_RC4($this->encryptdata
['key'], TCPDF_STATIC
::$enc_padding, $this->last_enc_key
, $this->last_enc_key_c
);
10746 } elseif ($this->encryptdata
['mode'] < 3) { // RC4-128, AES-128
10747 $tmp = TCPDF_STATIC
::_md5_16(TCPDF_STATIC
::$enc_padding.$this->encryptdata
['fileid']);
10748 $enc = TCPDF_STATIC
::_RC4($this->encryptdata
['key'], $tmp, $this->last_enc_key
, $this->last_enc_key_c
);
10749 $len = strlen($tmp);
10750 for ($i = 1; $i <= 19; ++
$i) {
10752 for ($j = 0; $j < $len; ++
$j) {
10753 $ek .= chr(ord($this->encryptdata
['key'][$j]) ^
$i);
10755 $enc = TCPDF_STATIC
::_RC4($ek, $enc, $this->last_enc_key
, $this->last_enc_key_c
);
10757 $enc .= str_repeat("\x00", 16);
10758 return substr($enc, 0, 32);
10759 } elseif ($this->encryptdata
['mode'] == 3) { // AES-256
10760 $seed = TCPDF_STATIC
::_md5_16(TCPDF_STATIC
::getRandomSeed());
10761 // User Validation Salt
10762 $this->encryptdata
['UVS'] = substr($seed, 0, 8);
10764 $this->encryptdata
['UKS'] = substr($seed, 8, 16);
10765 return hash('sha256', $this->encryptdata
['user_password'].$this->encryptdata
['UVS'], true).$this->encryptdata
['UVS'].$this->encryptdata
['UKS'];
10770 * Compute UE value (used for encryption)
10771 * @return string UE value
10773 * @since 5.9.006 (2010-10-19)
10774 * @author Nicola Asuni
10776 protected function _UEvalue() {
10777 $hashkey = hash('sha256', $this->encryptdata
['user_password'].$this->encryptdata
['UKS'], true);
10778 return TCPDF_STATIC
::_AESnopad($hashkey, $this->encryptdata
['key']);
10782 * Compute O value (used for encryption)
10783 * @return string O value
10785 * @since 2.0.000 (2008-01-02)
10786 * @author Nicola Asuni
10788 protected function _Ovalue() {
10789 if ($this->encryptdata
['mode'] < 3) { // RC4-40, RC4-128, AES-128
10790 $tmp = TCPDF_STATIC
::_md5_16($this->encryptdata
['owner_password']);
10791 if ($this->encryptdata
['mode'] > 0) {
10792 for ($i = 0; $i < 50; ++
$i) {
10793 $tmp = TCPDF_STATIC
::_md5_16($tmp);
10796 $owner_key = substr($tmp, 0, ($this->encryptdata
['Length'] / 8));
10797 $enc = TCPDF_STATIC
::_RC4($owner_key, $this->encryptdata
['user_password'], $this->last_enc_key
, $this->last_enc_key_c
);
10798 if ($this->encryptdata
['mode'] > 0) {
10799 $len = strlen($owner_key);
10800 for ($i = 1; $i <= 19; ++
$i) {
10802 for ($j = 0; $j < $len; ++
$j) {
10803 $ek .= chr(ord($owner_key[$j]) ^
$i);
10805 $enc = TCPDF_STATIC
::_RC4($ek, $enc, $this->last_enc_key
, $this->last_enc_key_c
);
10809 } elseif ($this->encryptdata
['mode'] == 3) { // AES-256
10810 $seed = TCPDF_STATIC
::_md5_16(TCPDF_STATIC
::getRandomSeed());
10811 // Owner Validation Salt
10812 $this->encryptdata
['OVS'] = substr($seed, 0, 8);
10814 $this->encryptdata
['OKS'] = substr($seed, 8, 16);
10815 return hash('sha256', $this->encryptdata
['owner_password'].$this->encryptdata
['OVS'].$this->encryptdata
['U'], true).$this->encryptdata
['OVS'].$this->encryptdata
['OKS'];
10820 * Compute OE value (used for encryption)
10821 * @return string OE value
10823 * @since 5.9.006 (2010-10-19)
10824 * @author Nicola Asuni
10826 protected function _OEvalue() {
10827 $hashkey = hash('sha256', $this->encryptdata
['owner_password'].$this->encryptdata
['OKS'].$this->encryptdata
['U'], true);
10828 return TCPDF_STATIC
::_AESnopad($hashkey, $this->encryptdata
['key']);
10832 * Convert password for AES-256 encryption mode
10833 * @param string $password password
10834 * @return string password
10836 * @since 5.9.006 (2010-10-19)
10837 * @author Nicola Asuni
10839 protected function _fixAES256Password($password) {
10840 $psw = ''; // password to be returned
10841 $psw_array = TCPDF_FONTS
::utf8Bidi(TCPDF_FONTS
::UTF8StringToArray($password, $this->isunicode
, $this->CurrentFont
), $password, $this->rtl
, $this->isunicode
, $this->CurrentFont
);
10842 foreach ($psw_array as $c) {
10843 $psw .= TCPDF_FONTS
::unichr($c, $this->isunicode
);
10845 return substr($psw, 0, 127);
10849 * Compute encryption key
10851 * @since 2.0.000 (2008-01-02)
10852 * @author Nicola Asuni
10854 protected function _generateencryptionkey() {
10855 $keybytelen = ($this->encryptdata
['Length'] / 8);
10856 if (!$this->encryptdata
['pubkey']) { // standard mode
10857 if ($this->encryptdata
['mode'] == 3) { // AES-256
10858 // generate 256 bit random key
10859 $this->encryptdata
['key'] = substr(hash('sha256', TCPDF_STATIC
::getRandomSeed(), true), 0, $keybytelen);
10860 // truncate passwords
10861 $this->encryptdata
['user_password'] = $this->_fixAES256Password($this->encryptdata
['user_password']);
10862 $this->encryptdata
['owner_password'] = $this->_fixAES256Password($this->encryptdata
['owner_password']);
10864 $this->encryptdata
['U'] = $this->_Uvalue();
10865 // Compute UE value
10866 $this->encryptdata
['UE'] = $this->_UEvalue();
10868 $this->encryptdata
['O'] = $this->_Ovalue();
10869 // Compute OE value
10870 $this->encryptdata
['OE'] = $this->_OEvalue();
10872 $this->encryptdata
['P'] = $this->encryptdata
['protection'];
10873 // Computing the encryption dictionary's Perms (permissions) value
10874 $perms = TCPDF_STATIC
::getEncPermissionsString($this->encryptdata
['protection']); // bytes 0-3
10875 $perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
10876 if (isset($this->encryptdata
['CF']['EncryptMetadata']) AND (!$this->encryptdata
['CF']['EncryptMetadata'])) { // byte 8
10881 $perms .= 'adb'; // bytes 9-11
10882 $perms .= 'nick'; // bytes 12-15
10883 $this->encryptdata
['perms'] = TCPDF_STATIC
::_AESnopad($this->encryptdata
['key'], $perms);
10884 } else { // RC4-40, RC4-128, AES-128
10886 $this->encryptdata
['user_password'] = substr($this->encryptdata
['user_password'].TCPDF_STATIC
::$enc_padding, 0, 32);
10887 $this->encryptdata
['owner_password'] = substr($this->encryptdata
['owner_password'].TCPDF_STATIC
::$enc_padding, 0, 32);
10889 $this->encryptdata
['O'] = $this->_Ovalue();
10890 // get default permissions (reverse byte order)
10891 $permissions = TCPDF_STATIC
::getEncPermissionsString($this->encryptdata
['protection']);
10892 // Compute encryption key
10893 $tmp = TCPDF_STATIC
::_md5_16($this->encryptdata
['user_password'].$this->encryptdata
['O'].$permissions.$this->encryptdata
['fileid']);
10894 if ($this->encryptdata
['mode'] > 0) {
10895 for ($i = 0; $i < 50; ++
$i) {
10896 $tmp = TCPDF_STATIC
::_md5_16(substr($tmp, 0, $keybytelen));
10899 $this->encryptdata
['key'] = substr($tmp, 0, $keybytelen);
10901 $this->encryptdata
['U'] = $this->_Uvalue();
10903 $this->encryptdata
['P'] = $this->encryptdata
['protection'];
10905 } else { // Public-Key mode
10906 // random 20-byte seed
10907 $seed = sha1(TCPDF_STATIC
::getRandomSeed(), true);
10908 $recipient_bytes = '';
10909 foreach ($this->encryptdata
['pubkeys'] as $pubkey) {
10910 // for each public certificate
10911 if (isset($pubkey['p'])) {
10912 $pkprotection = TCPDF_STATIC
::getUserPermissionCode($pubkey['p'], $this->encryptdata
['mode']);
10914 $pkprotection = $this->encryptdata
['protection'];
10916 // get default permissions (reverse byte order)
10917 $pkpermissions = TCPDF_STATIC
::getEncPermissionsString($pkprotection);
10919 $envelope = $seed.$pkpermissions;
10920 // write the envelope data to a temporary file
10921 $tempkeyfile = TCPDF_STATIC
::getObjFilename('key', $this->file_id
);
10922 $f = TCPDF_STATIC
::fopenLocal($tempkeyfile, 'wb');
10924 $this->Error('Unable to create temporary key file: '.$tempkeyfile);
10926 $envelope_length = strlen($envelope);
10927 fwrite($f, $envelope, $envelope_length);
10929 $tempencfile = TCPDF_STATIC
::getObjFilename('enc', $this->file_id
);
10930 if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_BINARY | PKCS7_DETACHED
)) {
10931 $this->Error('Unable to encrypt the file: '.$tempkeyfile);
10933 // read encryption signature
10934 $signature = file_get_contents($tempencfile, false, null, $envelope_length);
10935 // extract signature
10936 $signature = substr($signature, strpos($signature, 'Content-Disposition'));
10937 $tmparr = explode("\n\n", $signature);
10938 $signature = trim($tmparr[1]);
10940 // decode signature
10941 $signature = base64_decode($signature);
10942 // convert signature to hex
10943 $hexsignature = current(unpack('H*', $signature));
10944 // store signature on recipients array
10945 $this->encryptdata
['Recipients'][] = $hexsignature;
10946 // The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
10947 $recipient_bytes .= $signature;
10949 // calculate encryption key
10950 if ($this->encryptdata
['mode'] == 3) { // AES-256
10951 $this->encryptdata
['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
10952 } else { // RC4-40, RC4-128, AES-128
10953 $this->encryptdata
['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
10959 * Set document protection
10960 * Remark: the protection against modification is for people who have the full Acrobat product.
10961 * 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.
10962 * 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.
10963 * @param array $permissions 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>
10964 * @param string $user_pass user password. Empty by default.
10965 * @param string|null $owner_pass owner password. If not specified, a random value is used.
10966 * @param int $mode encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
10967 * @param array|null $pubkeys 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')))
10969 * @since 2.0.000 (2008-01-02)
10970 * @author Nicola Asuni
10972 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) {
10973 if ($this->pdfa_mode
) {
10974 // encryption is not allowed in PDF/A mode
10977 $this->encryptdata
['protection'] = TCPDF_STATIC
::getUserPermissionCode($permissions, $mode);
10978 if (($pubkeys !== null) AND (is_array($pubkeys))) {
10980 $this->encryptdata
['pubkeys'] = $pubkeys;
10982 // public-Key Security requires at least 128 bit
10985 if (!function_exists('openssl_pkcs7_encrypt')) {
10986 $this->Error('Public-Key Security requires openssl library.');
10988 // Set Public-Key filter (available are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
10989 $this->encryptdata
['pubkey'] = true;
10990 $this->encryptdata
['Filter'] = 'Adobe.PubSec';
10991 $this->encryptdata
['StmF'] = 'DefaultCryptFilter';
10992 $this->encryptdata
['StrF'] = 'DefaultCryptFilter';
10994 // standard mode (password mode)
10995 $this->encryptdata
['pubkey'] = false;
10996 $this->encryptdata
['Filter'] = 'Standard';
10997 $this->encryptdata
['StmF'] = 'StdCF';
10998 $this->encryptdata
['StrF'] = 'StdCF';
11000 if ($mode > 1) { // AES
11001 if (!extension_loaded('openssl') && !extension_loaded('mcrypt')) {
11002 $this->Error('AES encryption requires openssl or mcrypt extension (http://www.php.net/manual/en/mcrypt.requirements.php).');
11004 if (extension_loaded('openssl') && !in_array('aes-256-cbc', openssl_get_cipher_methods())) {
11005 $this->Error('AES encryption requires openssl/aes-256-cbc cypher.');
11007 if (extension_loaded('mcrypt') && mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128
) === false) {
11008 $this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
11010 if (($mode == 3) AND !function_exists('hash')) {
11011 // the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
11012 $this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
11015 if ($owner_pass === null) {
11016 $owner_pass = md5(TCPDF_STATIC
::getRandomSeed());
11018 $this->encryptdata
['user_password'] = $user_pass;
11019 $this->encryptdata
['owner_password'] = $owner_pass;
11020 $this->encryptdata
['mode'] = $mode;
11022 case 0: { // RC4 40 bit
11023 $this->encryptdata
['V'] = 1;
11024 $this->encryptdata
['Length'] = 40;
11025 $this->encryptdata
['CF']['CFM'] = 'V2';
11028 case 1: { // RC4 128 bit
11029 $this->encryptdata
['V'] = 2;
11030 $this->encryptdata
['Length'] = 128;
11031 $this->encryptdata
['CF']['CFM'] = 'V2';
11032 if ($this->encryptdata
['pubkey']) {
11033 $this->encryptdata
['SubFilter'] = 'adbe.pkcs7.s4';
11034 $this->encryptdata
['Recipients'] = array();
11038 case 2: { // AES 128 bit
11039 $this->encryptdata
['V'] = 4;
11040 $this->encryptdata
['Length'] = 128;
11041 $this->encryptdata
['CF']['CFM'] = 'AESV2';
11042 $this->encryptdata
['CF']['Length'] = 128;
11043 if ($this->encryptdata
['pubkey']) {
11044 $this->encryptdata
['SubFilter'] = 'adbe.pkcs7.s5';
11045 $this->encryptdata
['Recipients'] = array();
11049 case 3: { // AES 256 bit
11050 $this->encryptdata
['V'] = 5;
11051 $this->encryptdata
['Length'] = 256;
11052 $this->encryptdata
['CF']['CFM'] = 'AESV3';
11053 $this->encryptdata
['CF']['Length'] = 256;
11054 if ($this->encryptdata
['pubkey']) {
11055 $this->encryptdata
['SubFilter'] = 'adbe.pkcs7.s5';
11056 $this->encryptdata
['Recipients'] = array();
11061 $this->encrypted
= true;
11062 $this->encryptdata
['fileid'] = TCPDF_STATIC
::convertHexStringToString($this->file_id
);
11063 $this->_generateencryptionkey();
11066 // END OF ENCRYPTION FUNCTIONS -------------------------
11068 // START TRANSFORMATIONS SECTION -----------------------
11071 * Starts a 2D tranformation saving current graphic state.
11072 * This function must be called before scaling, mirroring, translation, rotation and skewing.
11073 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11075 * @since 2.1.000 (2008-01-07)
11076 * @see StartTransform(), StopTransform()
11078 public function StartTransform() {
11079 if ($this->state
!= 2) {
11082 $this->_outSaveGraphicsState();
11083 if ($this->inxobj
) {
11084 // we are inside an XObject template
11085 $this->xobjects
[$this->xobjid
]['transfmrk'][] = strlen($this->xobjects
[$this->xobjid
]['outdata']);
11087 $this->transfmrk
[$this->page
][] = $this->pagelen
[$this->page
];
11089 ++
$this->transfmatrix_key
;
11090 $this->transfmatrix
[$this->transfmatrix_key
] = array();
11094 * Stops a 2D tranformation restoring previous graphic state.
11095 * This function must be called after scaling, mirroring, translation, rotation and skewing.
11096 * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
11098 * @since 2.1.000 (2008-01-07)
11099 * @see StartTransform(), StopTransform()
11101 public function StopTransform() {
11102 if ($this->state
!= 2) {
11105 $this->_outRestoreGraphicsState();
11106 if (isset($this->transfmatrix
[$this->transfmatrix_key
])) {
11107 array_pop($this->transfmatrix
[$this->transfmatrix_key
]);
11108 --$this->transfmatrix_key
;
11110 if ($this->inxobj
) {
11111 // we are inside an XObject template
11112 array_pop($this->xobjects
[$this->xobjid
]['transfmrk']);
11114 array_pop($this->transfmrk
[$this->page
]);
11118 * Horizontal Scaling.
11119 * @param float $s_x scaling factor for width as percent. 0 is not allowed.
11120 * @param int $x abscissa of the scaling center. Default is current x position
11121 * @param int $y ordinate of the scaling center. Default is current y position
11123 * @since 2.1.000 (2008-01-07)
11124 * @see StartTransform(), StopTransform()
11126 public function ScaleX($s_x, $x='', $y='') {
11127 $this->Scale($s_x, 100, $x, $y);
11131 * Vertical Scaling.
11132 * @param float $s_y scaling factor for height as percent. 0 is not allowed.
11133 * @param int $x abscissa of the scaling center. Default is current x position
11134 * @param int $y ordinate of the scaling center. Default is current y position
11136 * @since 2.1.000 (2008-01-07)
11137 * @see StartTransform(), StopTransform()
11139 public function ScaleY($s_y, $x='', $y='') {
11140 $this->Scale(100, $s_y, $x, $y);
11144 * Vertical and horizontal proportional Scaling.
11145 * @param float $s scaling factor for width and height as percent. 0 is not allowed.
11146 * @param int $x abscissa of the scaling center. Default is current x position
11147 * @param int $y ordinate of the scaling center. Default is current y position
11149 * @since 2.1.000 (2008-01-07)
11150 * @see StartTransform(), StopTransform()
11152 public function ScaleXY($s, $x='', $y='') {
11153 $this->Scale($s, $s, $x, $y);
11157 * Vertical and horizontal non-proportional Scaling.
11158 * @param float $s_x scaling factor for width as percent. 0 is not allowed.
11159 * @param float $s_y scaling factor for height as percent. 0 is not allowed.
11160 * @param float|null $x abscissa of the scaling center. Default is current x position
11161 * @param float|null $y ordinate of the scaling center. Default is current y position
11163 * @since 2.1.000 (2008-01-07)
11164 * @see StartTransform(), StopTransform()
11166 public function Scale($s_x, $s_y, $x=null, $y=null) {
11167 if (TCPDF_STATIC
::empty_string($x)) {
11170 if (TCPDF_STATIC
::empty_string($y)) {
11173 if (($s_x == 0) OR ($s_y == 0)) {
11174 $this->Error('Please do not use values equal to zero for scaling');
11176 $y = ($this->h
- $y) * $this->k
;
11178 //calculate elements of transformation matrix
11186 $tm[4] = $x * (1 - $s_x);
11187 $tm[5] = $y * (1 - $s_y);
11188 //scale the coordinate system
11189 $this->Transform($tm);
11193 * Horizontal Mirroring.
11194 * @param float|null $x abscissa of the point. Default is current x position
11196 * @since 2.1.000 (2008-01-07)
11197 * @see StartTransform(), StopTransform()
11199 public function MirrorH($x=null) {
11200 $this->Scale(-100, 100, $x);
11204 * Verical Mirroring.
11205 * @param float|null $y ordinate of the point. Default is current y position
11207 * @since 2.1.000 (2008-01-07)
11208 * @see StartTransform(), StopTransform()
11210 public function MirrorV($y=null) {
11211 $this->Scale(100, -100, null, $y);
11215 * Point reflection mirroring.
11216 * @param float|null $x abscissa of the point. Default is current x position
11217 * @param float|null $y ordinate of the point. Default is current y position
11219 * @since 2.1.000 (2008-01-07)
11220 * @see StartTransform(), StopTransform()
11222 public function MirrorP($x=null,$y=null) {
11223 $this->Scale(-100, -100, $x, $y);
11227 * Reflection against a straight line through point (x, y) with the gradient angle (angle).
11228 * @param float $angle gradient angle of the straight line. Default is 0 (horizontal line).
11229 * @param float|null $x abscissa of the point. Default is current x position
11230 * @param float|null $y ordinate of the point. Default is current y position
11232 * @since 2.1.000 (2008-01-07)
11233 * @see StartTransform(), StopTransform()
11235 public function MirrorL($angle=0, $x=null,$y=null) {
11236 $this->Scale(-100, 100, $x, $y);
11237 $this->Rotate(-2*($angle-90), $x, $y);
11241 * Translate graphic object horizontally.
11242 * @param int $t_x movement to the right (or left for RTL)
11244 * @since 2.1.000 (2008-01-07)
11245 * @see StartTransform(), StopTransform()
11247 public function TranslateX($t_x) {
11248 $this->Translate($t_x, 0);
11252 * Translate graphic object vertically.
11253 * @param int $t_y movement to the bottom
11255 * @since 2.1.000 (2008-01-07)
11256 * @see StartTransform(), StopTransform()
11258 public function TranslateY($t_y) {
11259 $this->Translate(0, $t_y);
11263 * Translate graphic object horizontally and vertically.
11264 * @param int $t_x movement to the right
11265 * @param int $t_y movement to the bottom
11267 * @since 2.1.000 (2008-01-07)
11268 * @see StartTransform(), StopTransform()
11270 public function Translate($t_x, $t_y) {
11271 //calculate elements of transformation matrix
11277 $tm[4] = $t_x * $this->k
;
11278 $tm[5] = -$t_y * $this->k
;
11279 //translate the coordinate system
11280 $this->Transform($tm);
11285 * @param float $angle angle in degrees for counter-clockwise rotation
11286 * @param float|null $x abscissa of the rotation center. Default is current x position
11287 * @param float|null $y ordinate of the rotation center. Default is current y position
11289 * @since 2.1.000 (2008-01-07)
11290 * @see StartTransform(), StopTransform()
11292 public function Rotate($angle, $x=null, $y=null) {
11293 if (TCPDF_STATIC
::empty_string($x)) {
11296 if (TCPDF_STATIC
::empty_string($y)) {
11299 $y = ($this->h
- $y) * $this->k
;
11301 //calculate elements of transformation matrix
11303 $tm[0] = cos(deg2rad($angle));
11304 $tm[1] = sin(deg2rad($angle));
11307 $tm[4] = $x +
($tm[1] * $y) - ($tm[0] * $x);
11308 $tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
11309 //rotate the coordinate system around ($x,$y)
11310 $this->Transform($tm);
11314 * Skew horizontally.
11315 * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11316 * @param float|null $x abscissa of the skewing center. default is current x position
11317 * @param float|null $y ordinate of the skewing center. default is current y position
11319 * @since 2.1.000 (2008-01-07)
11320 * @see StartTransform(), StopTransform()
11322 public function SkewX($angle_x, $x=null, $y=null) {
11323 $this->Skew($angle_x, 0, $x, $y);
11328 * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11329 * @param float|null $x abscissa of the skewing center. default is current x position
11330 * @param float|null $y ordinate of the skewing center. default is current y position
11332 * @since 2.1.000 (2008-01-07)
11333 * @see StartTransform(), StopTransform()
11335 public function SkewY($angle_y, $x=null, $y=null) {
11336 $this->Skew(0, $angle_y, $x, $y);
11341 * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
11342 * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
11343 * @param float|null $x abscissa of the skewing center. default is current x position
11344 * @param float|null $y ordinate of the skewing center. default is current y position
11346 * @since 2.1.000 (2008-01-07)
11347 * @see StartTransform(), StopTransform()
11349 public function Skew($angle_x, $angle_y, $x=null, $y=null) {
11350 if (TCPDF_STATIC
::empty_string($x)) {
11353 if (TCPDF_STATIC
::empty_string($y)) {
11356 if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
11357 $this->Error('Please use values between -90 and +90 degrees for Skewing.');
11360 $y = ($this->h
- $y) * $this->k
;
11361 //calculate elements of transformation matrix
11364 $tm[1] = tan(deg2rad($angle_y));
11365 $tm[2] = tan(deg2rad($angle_x));
11367 $tm[4] = -$tm[2] * $y;
11368 $tm[5] = -$tm[1] * $x;
11369 //skew the coordinate system
11370 $this->Transform($tm);
11374 * Apply graphic transformations.
11375 * @param array $tm transformation matrix
11377 * @since 2.1.000 (2008-01-07)
11378 * @see StartTransform(), StopTransform()
11380 protected function Transform($tm) {
11381 if ($this->state
!= 2) {
11384 $this->_out(sprintf('%F %F %F %F %F %F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
11385 // add tranformation matrix
11386 $this->transfmatrix
[$this->transfmatrix_key
][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
11387 // update transformation mark
11388 if ($this->inxobj
) {
11389 // we are inside an XObject template
11390 if (end($this->xobjects
[$this->xobjid
]['transfmrk']) !== false) {
11391 $key = key($this->xobjects
[$this->xobjid
]['transfmrk']);
11392 $this->xobjects
[$this->xobjid
]['transfmrk'][$key] = strlen($this->xobjects
[$this->xobjid
]['outdata']);
11394 } elseif (end($this->transfmrk
[$this->page
]) !== false) {
11395 $key = key($this->transfmrk
[$this->page
]);
11396 $this->transfmrk
[$this->page
][$key] = $this->pagelen
[$this->page
];
11400 // END TRANSFORMATIONS SECTION -------------------------
11402 // START GRAPHIC FUNCTIONS SECTION ---------------------
11403 // The following section is based on the code provided by David Hernandez Sanz
11406 * 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.
11407 * @param float $width The width.
11410 * @see Line(), Rect(), Cell(), MultiCell()
11412 public function setLineWidth($width) {
11414 $this->LineWidth
= $width;
11415 $this->linestyleWidth
= sprintf('%F w', ($width * $this->k
));
11416 if ($this->state
== 2) {
11417 $this->_out($this->linestyleWidth
);
11422 * Returns the current the line width.
11423 * @return int Line width
11425 * @since 2.1.000 (2008-01-07)
11426 * @see Line(), SetLineWidth()
11428 public function GetLineWidth() {
11429 return $this->LineWidth
;
11434 * @param array $style Line style. Array with keys among the following:
11436 * <li>width (float): Width of the line in user units.</li>
11437 * <li>cap (string): Type of cap to put on the line. Possible values are:
11438 * butt, round, square. The difference between "square" and "butt" is that
11439 * "square" projects a flat end past the end of the line.</li>
11440 * <li>join (string): Type of join. Possible values are: miter, round,
11442 * <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
11443 * series of length values, which are the lengths of the on and off dashes.
11444 * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
11445 * 1 off, 2 on, 1 off, ...</li>
11446 * <li>phase (integer): Modifier on the dash pattern which is used to shift
11447 * the point at which the pattern starts.</li>
11448 * <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>
11450 * @param boolean $ret if true do not send the command.
11451 * @return string the PDF command
11453 * @since 2.1.000 (2008-01-08)
11455 public function setLineStyle($style, $ret=false) {
11456 $s = ''; // string to be returned
11457 if (!is_array($style)) {
11460 if (isset($style['width'])) {
11461 $this->LineWidth
= $style['width'];
11462 $this->linestyleWidth
= sprintf('%F w', ($style['width'] * $this->k
));
11463 $s .= $this->linestyleWidth
.' ';
11465 if (isset($style['cap'])) {
11466 $ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
11467 if (isset($ca[$style['cap']])) {
11468 $this->linestyleCap
= $ca[$style['cap']].' J';
11469 $s .= $this->linestyleCap
.' ';
11472 if (isset($style['join'])) {
11473 $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
11474 if (isset($ja[$style['join']])) {
11475 $this->linestyleJoin
= $ja[$style['join']].' j';
11476 $s .= $this->linestyleJoin
.' ';
11479 if (isset($style['dash'])) {
11481 if ($style['dash']) {
11482 if (preg_match('/^.+,/', $style['dash']) > 0) {
11483 $tab = explode(',', $style['dash']);
11485 $tab = array($style['dash']);
11488 foreach ($tab as $i => $v) {
11490 $dash_string .= ' ';
11492 $dash_string .= sprintf('%F', $v);
11495 if (!isset($style['phase']) OR !$style['dash']) {
11496 $style['phase'] = 0;
11498 $this->linestyleDash
= sprintf('[%s] %F d', $dash_string, $style['phase']);
11499 $s .= $this->linestyleDash
.' ';
11501 if (isset($style['color'])) {
11502 $s .= $this->setDrawColorArray($style['color'], true).' ';
11504 if (!$ret AND ($this->state
== 2)) {
11511 * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
11512 * @param float $x Abscissa of point.
11513 * @param float $y Ordinate of point.
11515 * @since 2.1.000 (2008-01-08)
11517 protected function _outPoint($x, $y) {
11518 if ($this->state
== 2) {
11519 $this->_out(sprintf('%F %F m', ($x * $this->k
), (($this->h
- $y) * $this->k
)));
11524 * Append a straight line segment from the current point to the point (x, y).
11525 * The new current point shall be (x, y).
11526 * @param float $x Abscissa of end point.
11527 * @param float $y Ordinate of end point.
11529 * @since 2.1.000 (2008-01-08)
11531 protected function _outLine($x, $y) {
11532 if ($this->state
== 2) {
11533 $this->_out(sprintf('%F %F l', ($x * $this->k
), (($this->h
- $y) * $this->k
)));
11538 * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
11539 * @param float $x Abscissa of upper-left corner.
11540 * @param float $y Ordinate of upper-left corner.
11541 * @param float $w Width.
11542 * @param float $h Height.
11543 * @param string $op options
11545 * @since 2.1.000 (2008-01-08)
11547 protected function _outRect($x, $y, $w, $h, $op) {
11548 if ($this->state
== 2) {
11549 $this->_out(sprintf('%F %F %F %F re %s', ($x * $this->k
), (($this->h
- $y) * $this->k
), ($w * $this->k
), (-$h * $this->k
), $op));
11554 * 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.
11555 * The new current point shall be (x3, y3).
11556 * @param float $x1 Abscissa of control point 1.
11557 * @param float $y1 Ordinate of control point 1.
11558 * @param float $x2 Abscissa of control point 2.
11559 * @param float $y2 Ordinate of control point 2.
11560 * @param float $x3 Abscissa of end point.
11561 * @param float $y3 Ordinate of end point.
11563 * @since 2.1.000 (2008-01-08)
11565 protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
11566 if ($this->state
== 2) {
11567 $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
)));
11572 * 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.
11573 * The new current point shall be (x3, y3).
11574 * @param float $x2 Abscissa of control point 2.
11575 * @param float $y2 Ordinate of control point 2.
11576 * @param float $x3 Abscissa of end point.
11577 * @param float $y3 Ordinate of end point.
11579 * @since 4.9.019 (2010-04-26)
11581 protected function _outCurveV($x2, $y2, $x3, $y3) {
11582 if ($this->state
== 2) {
11583 $this->_out(sprintf('%F %F %F %F v', ($x2 * $this->k
), (($this->h
- $y2) * $this->k
), ($x3 * $this->k
), (($this->h
- $y3) * $this->k
)));
11588 * 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.
11589 * The new current point shall be (x3, y3).
11590 * @param float $x1 Abscissa of control point 1.
11591 * @param float $y1 Ordinate of control point 1.
11592 * @param float $x3 Abscissa of end point.
11593 * @param float $y3 Ordinate of end point.
11595 * @since 2.1.000 (2008-01-08)
11597 protected function _outCurveY($x1, $y1, $x3, $y3) {
11598 if ($this->state
== 2) {
11599 $this->_out(sprintf('%F %F %F %F y', ($x1 * $this->k
), (($this->h
- $y1) * $this->k
), ($x3 * $this->k
), (($this->h
- $y3) * $this->k
)));
11604 * Draws a line between two points.
11605 * @param float $x1 Abscissa of first point.
11606 * @param float $y1 Ordinate of first point.
11607 * @param float $x2 Abscissa of second point.
11608 * @param float $y2 Ordinate of second point.
11609 * @param array $style Line style. Array like for SetLineStyle(). Default value: default line style (empty array).
11612 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11614 public function Line($x1, $y1, $x2, $y2, $style=array()) {
11615 if ($this->state
!= 2) {
11618 if (is_array($style)) {
11619 $this->setLineStyle($style);
11621 $this->_outPoint($x1, $y1);
11622 $this->_outLine($x2, $y2);
11627 * Draws a rectangle.
11628 * @param float $x Abscissa of upper-left corner.
11629 * @param float $y Ordinate of upper-left corner.
11630 * @param float $w Width.
11631 * @param float $h Height.
11632 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11633 * @param array $border_style Border style of rectangle. Array with keys among the following:
11635 * <li>all: Line style of all borders. Array like for SetLineStyle().</li>
11636 * <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for SetLineStyle().</li>
11638 * If a key is not present or is null, the correspondent border is not drawn. Default value: default line style (empty array).
11639 * @param array $fill_color 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).
11642 * @see SetLineStyle()
11644 public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11645 if ($this->state
!= 2) {
11648 if (empty($style)) {
11651 if (!(strpos($style, 'F') === false) AND !empty($fill_color)) {
11652 // set background color
11653 $this->setFillColorArray($fill_color);
11655 if (!empty($border_style)) {
11656 if (isset($border_style['all']) AND !empty($border_style['all'])) {
11657 //set global style for border
11658 $this->setLineStyle($border_style['all']);
11659 $border_style = array();
11661 // remove stroke operator from style
11662 $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*' );
11663 if (isset($opnostroke[$style])) {
11664 $style = $opnostroke[$style];
11668 if (!empty($style)) {
11669 $op = TCPDF_STATIC
::getPathPaintOperator($style);
11670 $this->_outRect($x, $y, $w, $h, $op);
11672 if (!empty($border_style)) {
11673 $border_style2 = array();
11674 foreach ($border_style as $line => $value) {
11675 $length = strlen($line);
11676 for ($i = 0; $i < $length; ++
$i) {
11677 $border_style2[$line[$i]] = $value;
11680 $border_style = $border_style2;
11681 if (isset($border_style['L']) AND $border_style['L']) {
11682 $this->Line($x, $y, $x, $y +
$h, $border_style['L']);
11684 if (isset($border_style['T']) AND $border_style['T']) {
11685 $this->Line($x, $y, $x +
$w, $y, $border_style['T']);
11687 if (isset($border_style['R']) AND $border_style['R']) {
11688 $this->Line($x +
$w, $y, $x +
$w, $y +
$h, $border_style['R']);
11690 if (isset($border_style['B']) AND $border_style['B']) {
11691 $this->Line($x, $y +
$h, $x +
$w, $y +
$h, $border_style['B']);
11697 * Draws a Bezier curve.
11698 * The Bezier curve is a tangent to the line between the control points at
11699 * either end of the curve.
11700 * @param float $x0 Abscissa of start point.
11701 * @param float $y0 Ordinate of start point.
11702 * @param float $x1 Abscissa of control point 1.
11703 * @param float $y1 Ordinate of control point 1.
11704 * @param float $x2 Abscissa of control point 2.
11705 * @param float $y2 Ordinate of control point 2.
11706 * @param float $x3 Abscissa of end point.
11707 * @param float $y3 Ordinate of end point.
11708 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11709 * @param array $line_style Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11710 * @param array $fill_color 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).
11712 * @see SetLineStyle()
11713 * @since 2.1.000 (2008-01-08)
11715 public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
11716 if ($this->state
!= 2) {
11719 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11720 $this->setFillColorArray($fill_color);
11722 $op = TCPDF_STATIC
::getPathPaintOperator($style);
11724 $this->setLineStyle($line_style);
11726 $this->_outPoint($x0, $y0);
11727 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11732 * Draws a poly-Bezier curve.
11733 * Each Bezier curve segment is a tangent to the line between the control points at
11734 * either end of the curve.
11735 * @param float $x0 Abscissa of start point.
11736 * @param float $y0 Ordinate of start point.
11737 * @param float[] $segments An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
11738 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11739 * @param array $line_style Line style of curve. Array like for SetLineStyle(). Default value: default line style (empty array).
11740 * @param array $fill_color 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).
11742 * @see SetLineStyle()
11743 * @since 3.0008 (2008-05-12)
11745 public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
11746 if ($this->state
!= 2) {
11749 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11750 $this->setFillColorArray($fill_color);
11752 $op = TCPDF_STATIC
::getPathPaintOperator($style);
11754 $line_style = array();
11757 $this->setLineStyle($line_style);
11759 $this->_outPoint($x0, $y0);
11760 foreach ($segments as $segment) {
11761 list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
11762 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11768 * Draws an ellipse.
11769 * An ellipse is formed from n Bezier curves.
11770 * @param float $x0 Abscissa of center point.
11771 * @param float $y0 Ordinate of center point.
11772 * @param float $rx Horizontal radius.
11773 * @param float $ry Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11774 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
11775 * @param float $astart Angle start of draw line. Default value: 0.
11776 * @param float $afinish Angle finish of draw line. Default value: 360.
11777 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11778 * @param array $line_style Line style of ellipse. Array like for SetLineStyle(). Default value: default line style (empty array).
11779 * @param array $fill_color 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).
11780 * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
11781 * @author Nicola Asuni
11783 * @since 2.1.000 (2008-01-08)
11785 public function Ellipse($x0, $y0, $rx, $ry=0, $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11786 if ($this->state
!= 2) {
11789 if (TCPDF_STATIC
::empty_string($ry) OR ($ry == 0)) {
11792 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11793 $this->setFillColorArray($fill_color);
11795 $op = TCPDF_STATIC
::getPathPaintOperator($style);
11797 $line_style = array();
11800 $this->setLineStyle($line_style);
11802 $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11807 * Append an elliptical arc to the current path.
11808 * An ellipse is formed from n Bezier curves.
11809 * @param float $xc Abscissa of center point.
11810 * @param float $yc Ordinate of center point.
11811 * @param float $rx Horizontal radius.
11812 * @param float $ry Vertical radius (if ry = 0 then is a circle, see Circle()). Default value: 0.
11813 * @param float $xang Angle between the X-axis and the major axis of the ellipse. Default value: 0.
11814 * @param float $angs Angle start of draw line. Default value: 0.
11815 * @param float $angf Angle finish of draw line. Default value: 360.
11816 * @param boolean $pie if true do not mark the border point (used to draw pie sectors).
11817 * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
11818 * @param boolean $startpoint if true output a starting point.
11819 * @param boolean $ccw if true draws in counter-clockwise.
11820 * @param boolean $svg if true the angles are in svg mode (already calculated).
11821 * @return array bounding box coordinates (x min, y min, x max, y max)
11822 * @author Nicola Asuni
11824 * @since 4.9.019 (2010-04-26)
11826 protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2, $startpoint=true, $ccw=true, $svg=false) {
11827 if (($rx <= 0) OR ($ry < 0)) {
11834 $xmin = 2147483647;
11835 $ymin = 2147483647;
11839 // center of the arc
11840 $this->_outPoint($xc, $yc);
11842 $xang = deg2rad((float) $xang);
11843 $angs = deg2rad((float) $angs);
11844 $angf = deg2rad((float) $angf);
11849 $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11850 $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11858 if ($ccw AND ($as > $af)) {
11859 // reverse rotation
11861 } elseif (!$ccw AND ($as < $af)) {
11862 // reverse rotation
11865 $total_angle = ($af - $as);
11869 // total arcs to draw
11870 $nc *= (2 * abs($total_angle) / M_PI
);
11871 $nc = round($nc) +
1;
11872 // angle of each arc
11873 $arcang = ($total_angle / $nc);
11874 // center point in PDF coordinates
11876 $y0 = ($this->h
- $yc);
11879 $alpha = sin($arcang) * ((sqrt(4 +
(3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
11880 $cos_xang = cos($xang);
11881 $sin_xang = sin($xang);
11882 $cos_ang = cos($ang);
11883 $sin_ang = sin($ang);
11885 $px1 = $x0 +
($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11886 $py1 = $y0 +
($rx * $sin_xang * $cos_ang) +
($ry * $cos_xang * $sin_ang);
11887 // first Bezier control point
11888 $qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11889 $qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) +
($ry * $cos_xang * $cos_ang)));
11891 // line from center to arc starting point
11892 $this->_outLine($px1, $this->h
- $py1);
11893 } elseif ($startpoint) {
11894 // arc starting point
11895 $this->_outPoint($px1, $this->h
- $py1);
11898 for ($i = 1; $i <= $nc; ++
$i) {
11900 $ang = $as +
($i * $arcang);
11904 $cos_ang = cos($ang);
11905 $sin_ang = sin($ang);
11906 // second arc point
11907 $px2 = $x0 +
($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
11908 $py2 = $y0 +
($rx * $sin_xang * $cos_ang) +
($ry * $cos_xang * $sin_ang);
11909 // second Bezier control point
11910 $qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
11911 $qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) +
($ry * $cos_xang * $cos_ang)));
11913 $cx1 = ($px1 +
$qx1);
11914 $cy1 = ($this->h
- ($py1 +
$qy1));
11915 $cx2 = ($px2 - $qx2);
11916 $cy2 = ($this->h
- ($py2 - $qy2));
11918 $cy3 = ($this->h
- $py2);
11919 $this->_outCurve($cx1, $cy1, $cx2, $cy2, $cx3, $cy3);
11920 // get bounding box coordinates
11921 $xmin = min($xmin, $cx1, $cx2, $cx3);
11922 $ymin = min($ymin, $cy1, $cy2, $cy3);
11923 $xmax = max($xmax, $cx1, $cx2, $cx3);
11924 $ymax = max($ymax, $cy1, $cy2, $cy3);
11925 // move to next point
11932 $this->_outLine($xc, $yc);
11933 // get bounding box coordinates
11934 $xmin = min($xmin, $xc);
11935 $ymin = min($ymin, $yc);
11936 $xmax = max($xmax, $xc);
11937 $ymax = max($ymax, $yc);
11939 return array($xmin, $ymin, $xmax, $ymax);
11944 * A circle is formed from n Bezier curves.
11945 * @param float $x0 Abscissa of center point.
11946 * @param float $y0 Ordinate of center point.
11947 * @param float $r Radius.
11948 * @param float $angstr Angle start of draw line. Default value: 0.
11949 * @param float $angend Angle finish of draw line. Default value: 360.
11950 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11951 * @param array $line_style Line style of circle. Array like for SetLineStyle(). Default value: default line style (empty array).
11952 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
11953 * @param integer $nc Number of curves used to draw a 90 degrees portion of circle.
11955 * @since 2.1.000 (2008-01-08)
11957 public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
11958 $this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
11962 * Draws a polygonal line
11963 * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11964 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11965 * @param array $line_style Line style of polygon. Array with keys among the following:
11967 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11968 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11970 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11971 * @param array $fill_color 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).
11972 * @since 4.8.003 (2009-09-15)
11975 public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
11976 $this->Polygon($p, $style, $line_style, $fill_color, false);
11981 * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
11982 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
11983 * @param array $line_style Line style of polygon. Array with keys among the following:
11985 * <li>all: Line style of all lines. Array like for SetLineStyle().</li>
11986 * <li>0 to ($np - 1): Line style of each line. Array like for SetLineStyle().</li>
11988 * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
11989 * @param array $fill_color 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).
11990 * @param boolean $closed if true the polygon is closes, otherwise will remain open
11992 * @since 2.1.000 (2008-01-08)
11994 public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
11995 if ($this->state
!= 2) {
11998 $nc = count($p); // number of coordinates
11999 $np = $nc / 2; // number of points
12001 // close polygon by adding the first 2 points at the end (one line)
12002 for ($i = 0; $i < 4; ++
$i) {
12003 $p[$nc +
$i] = $p[$i];
12005 // copy style for the last added line
12006 if (isset($line_style[0])) {
12007 $line_style[$np] = $line_style[0];
12011 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
12012 $this->setFillColorArray($fill_color);
12014 $op = TCPDF_STATIC
::getPathPaintOperator($style);
12016 $line_style = array();
12020 if (isset($line_style['all'])) {
12021 $this->setLineStyle($line_style['all']);
12027 $this->_outPoint($p[0], $p[1]);
12028 for ($i = 2; $i < $nc; $i = $i +
2) {
12029 $this->_outLine($p[$i], $p[$i +
1]);
12034 $this->_outPoint($p[0], $p[1]);
12035 for ($i = 2; $i < $nc; $i = $i +
2) {
12036 $line_num = ($i / 2) - 1;
12037 if (isset($line_style[$line_num])) {
12038 if ($line_style[$line_num] != 0) {
12039 if (is_array($line_style[$line_num])) {
12041 $this->setLineStyle($line_style[$line_num]);
12042 $this->_outPoint($p[$i - 2], $p[$i - 1]);
12043 $this->_outLine($p[$i], $p[$i +
1]);
12045 $this->_outPoint($p[$i], $p[$i +
1]);
12047 $this->_outLine($p[$i], $p[$i +
1]);
12051 $this->_outLine($p[$i], $p[$i +
1]);
12058 $this->_outPoint($p[0], $p[1]);
12059 for ($i = 2; $i < $nc; $i = $i +
2) {
12060 $this->_outLine($p[$i], $p[$i +
1]);
12067 * Draws a regular polygon.
12068 * @param float $x0 Abscissa of center point.
12069 * @param float $y0 Ordinate of center point.
12070 * @param float $r Radius of inscribed circle.
12071 * @param integer $ns Number of sides.
12072 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
12073 * @param boolean $draw_circle Draw inscribed circle or not. Default value: false.
12074 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12075 * @param array $line_style Line style of polygon sides. Array with keys among the following:
12077 * <li>all: Line style of all sides. Array like for SetLineStyle().</li>
12078 * <li>0 to ($ns - 1): Line style of each side. Array like for SetLineStyle().</li>
12080 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12081 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12082 * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
12084 * <li>D or empty string: Draw (default).</li>
12085 * <li>F: Fill.</li>
12086 * <li>DF or FD: Draw and fill.</li>
12087 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12088 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12090 * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12091 * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12093 * @since 2.1.000 (2008-01-08)
12095 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()) {
12099 if ($draw_circle) {
12100 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12103 for ($i = 0; $i < $ns; ++
$i) {
12104 $a = $angle +
($i * 360 / $ns);
12105 $a_rad = deg2rad((float) $a);
12106 $p[] = $x0 +
($r * sin($a_rad));
12107 $p[] = $y0 +
($r * cos($a_rad));
12109 $this->Polygon($p, $style, $line_style, $fill_color);
12113 * Draws a star polygon
12114 * @param float $x0 Abscissa of center point.
12115 * @param float $y0 Ordinate of center point.
12116 * @param float $r Radius of inscribed circle.
12117 * @param integer $nv Number of vertices.
12118 * @param integer $ng Number of gap (if ($ng % $nv = 1) then is a regular polygon).
12119 * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
12120 * @param boolean $draw_circle Draw inscribed circle or not. Default value is false.
12121 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12122 * @param array $line_style Line style of polygon sides. Array with keys among the following:
12124 * <li>all: Line style of all sides. Array like for
12125 * SetLineStyle().</li>
12126 * <li>0 to (n - 1): Line style of each side. Array like for SetLineStyle().</li>
12128 * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
12129 * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
12130 * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
12132 * <li>D or empty string: Draw (default).</li>
12133 * <li>F: Fill.</li>
12134 * <li>DF or FD: Draw and fill.</li>
12135 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
12136 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
12138 * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for SetLineStyle(). Default value: default line style (empty array).
12139 * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
12141 * @since 2.1.000 (2008-01-08)
12143 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()) {
12147 if ($draw_circle) {
12148 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12151 $visited = array();
12152 for ($i = 0; $i < $nv; ++
$i) {
12153 $a = $angle +
($i * 360 / $nv);
12154 $a_rad = deg2rad((float) $a);
12155 $p2[] = $x0 +
($r * sin($a_rad));
12156 $p2[] = $y0 +
($r * cos($a_rad));
12157 $visited[] = false;
12162 $p[] = $p2[$i * 2];
12163 $p[] = $p2[($i * 2) +
1];
12164 $visited[$i] = true;
12167 } while (!$visited[$i]);
12168 $this->Polygon($p, $style, $line_style, $fill_color);
12172 * Draws a rounded rectangle.
12173 * @param float $x Abscissa of upper-left corner.
12174 * @param float $y Ordinate of upper-left corner.
12175 * @param float $w Width.
12176 * @param float $h Height.
12177 * @param float $r the radius of the circle used to round off the corners of the rectangle.
12178 * @param string $round_corner 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").
12179 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12180 * @param array $border_style Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12181 * @param array $fill_color 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).
12183 * @since 2.1.000 (2008-01-08)
12185 public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12186 $this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
12190 * Draws a rounded rectangle.
12191 * @param float $x Abscissa of upper-left corner.
12192 * @param float $y Ordinate of upper-left corner.
12193 * @param float $w Width.
12194 * @param float $h Height.
12195 * @param float $rx the x-axis radius of the ellipse used to round off the corners of the rectangle.
12196 * @param float $ry the y-axis radius of the ellipse used to round off the corners of the rectangle.
12197 * @param string $round_corner 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").
12198 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
12199 * @param array $border_style Border style of rectangle. Array like for SetLineStyle(). Default value: default line style (empty array).
12200 * @param array $fill_color 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).
12202 * @since 4.9.019 (2010-04-22)
12204 public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
12205 if ($this->state
!= 2) {
12208 if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12210 $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12214 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
12215 $this->setFillColorArray($fill_color);
12217 $op = TCPDF_STATIC
::getPathPaintOperator($style);
12219 $border_style = array();
12221 if ($border_style) {
12222 $this->setLineStyle($border_style);
12224 $MyArc = 4 / 3 * (sqrt(2) - 1);
12225 $this->_outPoint($x +
$rx, $y);
12226 $xc = $x +
$w - $rx;
12228 $this->_outLine($xc, $y);
12229 if ($round_corner[0]) {
12230 $this->_outCurve($xc +
($rx * $MyArc), $yc - $ry, $xc +
$rx, $yc - ($ry * $MyArc), $xc +
$rx, $yc);
12232 $this->_outLine($x +
$w, $y);
12234 $xc = $x +
$w - $rx;
12235 $yc = $y +
$h - $ry;
12236 $this->_outLine($x +
$w, $yc);
12237 if ($round_corner[1]) {
12238 $this->_outCurve($xc +
$rx, $yc +
($ry * $MyArc), $xc +
($rx * $MyArc), $yc +
$ry, $xc, $yc +
$ry);
12240 $this->_outLine($x +
$w, $y +
$h);
12243 $yc = $y +
$h - $ry;
12244 $this->_outLine($xc, $y +
$h);
12245 if ($round_corner[2]) {
12246 $this->_outCurve($xc - ($rx * $MyArc), $yc +
$ry, $xc - $rx, $yc +
($ry * $MyArc), $xc - $rx, $yc);
12248 $this->_outLine($x, $y +
$h);
12252 $this->_outLine($x, $yc);
12253 if ($round_corner[3]) {
12254 $this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
12256 $this->_outLine($x, $y);
12257 $this->_outLine($x +
$rx, $y);
12263 * Draws a grahic arrow.
12264 * @param float $x0 Abscissa of first point.
12265 * @param float $y0 Ordinate of first point.
12266 * @param float $x1 Abscissa of second point.
12267 * @param float $y1 Ordinate of second point.
12268 * @param int $head_style (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
12269 * @param float $arm_size length of arrowhead arms
12270 * @param int $arm_angle angle between an arm and the shaft
12271 * @author Piotr Galecki, Nicola Asuni, Andy Meier
12272 * @since 4.6.018 (2009-07-10)
12274 public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
12275 // getting arrow direction angle
12276 // 0 deg angle is when both arms go along X axis. angle grows clockwise.
12277 $dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
12278 if ($dir_angle < 0) {
12279 $dir_angle +
= (2 * M_PI
);
12281 $arm_angle = deg2rad($arm_angle);
12284 if ($head_style > 0) {
12285 // calculate the stopping point for the arrow shaft
12286 $sx1 = $x1 +
(($arm_size - $this->LineWidth
) * cos($dir_angle));
12287 $sy1 = $y1 +
(($arm_size - $this->LineWidth
) * sin($dir_angle));
12289 // main arrow line / shaft
12290 $this->Line($x0, $y0, $sx1, $sy1);
12291 // left arrowhead arm tip
12292 $x2L = $x1 +
($arm_size * cos($dir_angle +
$arm_angle));
12293 $y2L = $y1 +
($arm_size * sin($dir_angle +
$arm_angle));
12294 // right arrowhead arm tip
12295 $x2R = $x1 +
($arm_size * cos($dir_angle - $arm_angle));
12296 $y2R = $y1 +
($arm_size * sin($dir_angle - $arm_angle));
12299 switch ($head_style) {
12301 // draw only arrowhead arms
12303 $style = array(1, 1, 0);
12307 // draw closed arrowhead, but no fill
12312 // closed and filled arrowhead
12317 // filled arrowhead
12322 $this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
12325 // END GRAPHIC FUNCTIONS SECTION -----------------------
12328 * Add a Named Destination.
12329 * NOTE: destination names are unique, so only last entry will be saved.
12330 * @param string $name Destination name.
12331 * @param float $y Y position in user units of the destiantion on the selected page (default = -1 = current position; 0 = page start;).
12332 * @param int|string $page 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.
12333 * @param float $x X position in user units of the destiantion on the selected page (default = -1 = current position;).
12334 * @return string|false Stripped named destination identifier or false in case of error.
12336 * @author Christian Deligant, Nicola Asuni
12337 * @since 5.9.097 (2011-06-23)
12339 public function setDestination($name, $y=-1, $page='', $x=-1) {
12340 // remove unsupported characters
12341 $name = TCPDF_STATIC
::encodeNameObject($name);
12342 if (TCPDF_STATIC
::empty_string($name)) {
12346 $y = $this->GetY();
12347 } elseif ($y < 0) {
12349 } elseif ($y > $this->h
) {
12353 $x = $this->GetX();
12354 } elseif ($x < 0) {
12356 } elseif ($x > $this->w
) {
12360 if (!empty($page) AND (substr($page, 0, 1) == '*')) {
12361 $page = intval(substr($page, 1));
12362 // this page number will not be changed when moving/add/deleting pages
12365 if (empty($page)) {
12366 $page = $this->PageNo();
12367 if (empty($page)) {
12371 $this->dests
[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12376 * Return the Named Destination array.
12377 * @return array Named Destination array.
12379 * @author Nicola Asuni
12380 * @since 5.9.097 (2011-06-23)
12382 public function getDestination() {
12383 return $this->dests
;
12387 * Insert Named Destinations.
12389 * @author Johannes G\FCntert, Nicola Asuni
12390 * @since 5.9.098 (2011-06-23)
12392 protected function _putdests() {
12393 if (empty($this->dests
)) {
12396 $this->n_dests
= $this->_newobj();
12398 foreach($this->dests
as $name => $o) {
12399 $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
)));
12402 $out .= "\n".'endobj';
12407 * Adds a bookmark - alias for Bookmark().
12408 * @param string $txt Bookmark description.
12409 * @param int $level Bookmark level (minimum value is 0).
12410 * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12411 * @param int|string $page 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.
12412 * @param string $style Font style: B = Bold, I = Italic, BI = Bold + Italic.
12413 * @param array $color RGB color array (values from 0 to 255).
12414 * @param float $x X position in user units of the bookmark on the selected page (default = -1 = current position;).
12415 * @param mixed $link URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12418 public function setBookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12419 $this->Bookmark($txt, $level, $y, $page, $style, $color, $x, $link);
12424 * @param string $txt Bookmark description.
12425 * @param int $level Bookmark level (minimum value is 0).
12426 * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
12427 * @param int|string $page 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.
12428 * @param string $style Font style: B = Bold, I = Italic, BI = Bold + Italic.
12429 * @param array $color RGB color array (values from 0 to 255).
12430 * @param float $x X position in user units of the bookmark on the selected page (default = -1 = current position;).
12431 * @param mixed $link URL, or numerical link ID, or named destination (# character followed by the destination name), or embedded file (* character followed by the file name).
12433 * @since 2.1.002 (2008-02-12)
12435 public function Bookmark($txt, $level=0, $y=-1, $page='', $style='', $color=array(0,0,0), $x=-1, $link='') {
12439 if (isset($this->outlines
[0])) {
12440 $lastoutline = end($this->outlines
);
12441 $maxlevel = $lastoutline['l'] +
1;
12445 if ($level > $maxlevel) {
12446 $level = $maxlevel;
12449 $y = $this->GetY();
12450 } elseif ($y < 0) {
12452 } elseif ($y > $this->h
) {
12456 $x = $this->GetX();
12457 } elseif ($x < 0) {
12459 } elseif ($x > $this->w
) {
12463 $pageAsString = (string) $page;
12464 if ($pageAsString && $pageAsString[0] == '*') {
12465 $page = intval(substr($page, 1));
12466 // this page number will not be changed when moving/add/deleting pages
12469 if (empty($page)) {
12470 $page = $this->PageNo();
12471 if (empty($page)) {
12475 $this->outlines
[] = array('t' => $txt, 'l' => $level, 'x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed, 's' => strtoupper($style), 'c' => $color, 'u' => $link);
12479 * Sort bookmarks for page and key.
12481 * @since 5.9.119 (2011-09-19)
12483 protected function sortBookmarks() {
12484 // get sorting columns
12485 $outline_p = array();
12486 $outline_y = array();
12487 foreach ($this->outlines
as $key => $row) {
12488 $outline_p[$key] = $row['p'];
12489 $outline_k[$key] = $key;
12491 // sort outlines by page and original position
12492 array_multisort($outline_p, SORT_NUMERIC
, SORT_ASC
, $outline_k, SORT_NUMERIC
, SORT_ASC
, $this->outlines
);
12496 * Create a bookmark PDF string.
12498 * @author Olivier Plathey, Nicola Asuni
12499 * @since 2.1.002 (2008-02-12)
12501 protected function _putbookmarks() {
12502 $nb = count($this->outlines
);
12507 $this->sortBookmarks();
12510 foreach ($this->outlines
as $i => $o) {
12512 $parent = $lru[($o['l'] - 1)];
12513 //Set parent and last pointers
12514 $this->outlines
[$i]['parent'] = $parent;
12515 $this->outlines
[$parent]['last'] = $i;
12516 if ($o['l'] > $level) {
12517 //Level increasing: set first pointer
12518 $this->outlines
[$parent]['first'] = $i;
12521 $this->outlines
[$i]['parent'] = $nb;
12523 if (($o['l'] <= $level) AND ($i > 0)) {
12524 //Set prev and next pointers
12525 $prev = $lru[$o['l']];
12526 $this->outlines
[$prev]['next'] = $i;
12527 $this->outlines
[$i]['prev'] = $prev;
12529 $lru[$o['l']] = $i;
12534 $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';
12535 foreach ($this->outlines
as $i => $o) {
12536 $oid = $this->_newobj();
12537 // covert HTML title to string
12538 $title = preg_replace($nltags, "\n", $o['t']);
12539 $title = preg_replace("/[\r]+/si", '', $title);
12540 $title = preg_replace("/[\n]+/si", "\n", $title);
12541 $title = strip_tags($title);
12542 $title = $this->stringTrim($title);
12543 $out = '<</Title '.$this->_textstring($title, $oid);
12544 $out .= ' /Parent '.($n +
$o['parent']).' 0 R';
12545 if (isset($o['prev'])) {
12546 $out .= ' /Prev '.($n +
$o['prev']).' 0 R';
12548 if (isset($o['next'])) {
12549 $out .= ' /Next '.($n +
$o['next']).' 0 R';
12551 if (isset($o['first'])) {
12552 $out .= ' /First '.($n +
$o['first']).' 0 R';
12554 if (isset($o['last'])) {
12555 $out .= ' /Last '.($n +
$o['last']).' 0 R';
12557 if (isset($o['u']) AND !empty($o['u'])) {
12559 if (is_string($o['u'])) {
12560 if ($o['u'][0] == '#') {
12561 // internal destination
12562 $out .= ' /Dest /'.TCPDF_STATIC
::encodeNameObject(substr($o['u'], 1));
12563 } elseif ($o['u'][0] == '%') {
12564 // embedded PDF file
12565 $filename = basename(substr($o['u'], 1));
12566 $out .= ' /A <</S /GoToE /D [0 /Fit] /NewWindow true /T << /R /C /P '.($o['p'] - 1).' /A '.$this->embeddedfiles
[$filename]['a'].' >> >>';
12567 } elseif ($o['u'][0] == '*') {
12568 // embedded generic file
12569 $filename = basename(substr($o['u'], 1));
12570 $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});';
12571 $out .= ' /A <</S /JavaScript /JS '.$this->_textstring($jsa, $oid).'>>';
12573 // external URI link
12574 $out .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($o['u']), $oid).'>>';
12576 } elseif (isset($this->links
[$o['u']])) {
12577 // internal link ID
12578 $l = $this->links
[$o['u']];
12579 if (isset($this->page_obj_id
[($l['p'])])) {
12580 $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
)));
12583 } elseif (isset($this->page_obj_id
[($o['p'])])) {
12585 $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
)));
12589 if (!empty($o['s'])) {
12591 if (strpos($o['s'], 'B') !== false) {
12595 if (strpos($o['s'], 'I') !== false) {
12599 $out .= sprintf(' /F %d', $style);
12600 // set bookmark color
12601 if (isset($o['c']) AND is_array($o['c']) AND (count($o['c']) == 3)) {
12602 $color = array_values($o['c']);
12603 $out .= sprintf(' /C [%F %F %F]', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
12606 $out .= ' /C [0.0 0.0 0.0]';
12608 $out .= ' /Count 0'; // normally closed item
12610 $out .= "\n".'endobj';
12614 $this->OutlineRoot
= $this->_newobj();
12615 $this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n +
$lru[0]).' 0 R >>'."\n".'endobj');
12618 // --- JAVASCRIPT ------------------------------------------------------
12621 * Adds a javascript
12622 * @param string $script Javascript code
12624 * @author Johannes G\FCntert, Nicola Asuni
12625 * @since 2.1.002 (2008-02-12)
12627 public function IncludeJS($script) {
12628 $this->javascript
.= $script;
12632 * Adds a javascript object and return object ID
12633 * @param string $script Javascript code
12634 * @param boolean $onload if true executes this object when opening the document
12635 * @return int internal object ID
12637 * @author Nicola Asuni
12638 * @since 4.8.000 (2009-09-07)
12640 public function addJavascriptObject($script, $onload=false) {
12641 if ($this->pdfa_mode
) {
12642 // javascript is not allowed in PDF/A mode
12646 $this->js_objects
[$this->n
] = array('n' => $this->n
, 'js' => $script, 'onload' => $onload);
12651 * Create a javascript PDF string.
12653 * @author Johannes G\FCntert, Nicola Asuni
12654 * @since 2.1.002 (2008-02-12)
12656 protected function _putjavascript() {
12657 if ($this->pdfa_mode
OR (empty($this->javascript
) AND empty($this->js_objects
))) {
12660 if (strpos($this->javascript
, 'this.addField') > 0) {
12661 if (!$this->ur
['enabled']) {
12662 //$this->setUserRights();
12664 // the following two lines are used to avoid form fields duplication after saving
12665 // The addField method only works when releasing user rights (UR3)
12666 $jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%F,%F,%F,%F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
12667 $jsb = "getField('tcpdfdocsaved').value='saved';";
12668 $this->javascript
= $jsa."\n".$this->javascript
."\n".$jsb;
12670 // name tree for javascript
12671 $this->n_js
= '<< /Names [';
12672 if (!empty($this->javascript
)) {
12673 $this->n_js
.= ' (EmbeddedJS) '.($this->n +
1).' 0 R';
12675 if (!empty($this->js_objects
)) {
12676 foreach ($this->js_objects
as $key => $val) {
12677 if ($val['onload']) {
12678 $this->n_js
.= ' (JS'.$key.') '.$key.' 0 R';
12682 $this->n_js
.= ' ] >>';
12683 // default Javascript object
12684 if (!empty($this->javascript
)) {
12685 $obj_id = $this->_newobj();
12686 $out = '<< /S /JavaScript';
12687 $out .= ' /JS '.$this->_textstring($this->javascript
, $obj_id);
12689 $out .= "\n".'endobj';
12692 // additional Javascript objects
12693 if (!empty($this->js_objects
)) {
12694 foreach ($this->js_objects
as $key => $val) {
12695 $out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
12702 * Adds a javascript form field.
12703 * @param string $type field type
12704 * @param string $name field name
12705 * @param int $x horizontal position
12706 * @param int $y vertical position
12707 * @param int $w width
12708 * @param int $h height
12709 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12711 * @author Denis Van Nuffelen, Nicola Asuni
12712 * @since 2.1.002 (2008-02-12)
12714 protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
12718 // the followind avoid fields duplication after saving the document
12719 $this->javascript
.= "if (getField('tcpdfdocsaved').value != 'saved') {";
12721 $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";
12722 $this->javascript
.= 'f'.$name.'.textSize='.$this->FontSizePt
.";\n";
12723 foreach($prop as $key => $val) {
12724 if (strcmp(substr($key, -5), 'Color') == 0) {
12725 $val = TCPDF_COLORS
::_JScolor($val);
12727 $val = "'".$val."'";
12729 $this->javascript
.= 'f'.$name.'.'.$key.'='.$val.";\n";
12736 $this->javascript
.= '}';
12739 // --- FORM FIELDS -----------------------------------------------------
12744 * Set default properties for form fields.
12745 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12747 * @author Nicola Asuni
12748 * @since 4.8.000 (2009-09-06)
12750 public function setFormDefaultProp($prop=array()) {
12751 $this->default_form_prop
= $prop;
12755 * Return the default properties for form fields.
12756 * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12758 * @author Nicola Asuni
12759 * @since 4.8.000 (2009-09-06)
12761 public function getFormDefaultProp() {
12762 return $this->default_form_prop
;
12766 * Creates a text field
12767 * @param string $name field name
12768 * @param float $w Width of the rectangle
12769 * @param float $h Height of the rectangle
12770 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12771 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
12772 * @param float|null $x Abscissa of the upper-left corner of the rectangle
12773 * @param float|null $y Ordinate of the upper-left corner of the rectangle
12774 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
12776 * @author Nicola Asuni
12777 * @since 4.8.000 (2009-09-07)
12779 public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
12780 if (TCPDF_STATIC
::empty_string($x)) {
12783 if (TCPDF_STATIC
::empty_string($y)) {
12786 // check page for no-write regions and adapt page margins if necessary
12787 list($x, $y) = $this->checkPageRegions($h, $x, $y);
12789 $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12792 // get default style
12793 $prop = array_merge($this->getFormDefaultProp(), $prop);
12794 // get annotation data
12795 $popt = TCPDF_STATIC
::getAnnotOptFromJSProp($prop, $this->spot_colors
, $this->rtl
);
12796 // set default appearance stream
12797 $this->annotation_fonts
[$this->CurrentFont
['fontkey']] = $this->CurrentFont
['i'];
12798 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont
['i'], $this->FontSizePt
, $this->TextColor
);
12799 $popt['da'] = $fontstyle;
12800 // build appearance stream
12801 $popt['ap'] = array();
12802 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
12804 if (isset($prop['value']) AND !empty($prop['value'])) {
12805 $text = $prop['value'];
12806 } elseif (isset($opt['v']) AND !empty($opt['v'])) {
12809 $tmpid = $this->startTemplate($w, $h, false);
12811 if (isset($popt['q'])) {
12812 switch ($popt['q']) {
12831 $this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12832 $this->endTemplate();
12834 $popt['ap']['n'] .= $this->xobjects
[$tmpid]['outdata'];
12835 unset($this->xobjects
[$tmpid]);
12836 $popt['ap']['n'] .= 'Q EMC';
12838 $opt = array_merge($popt, $opt);
12839 // remove some conflicting options
12841 // set remaining annotation data
12842 $opt['Subtype'] = 'Widget';
12845 // Additional annotation's parameters (check _putannotsobj() method):
12855 //$opt['mk']['bc'];
12856 //$opt['mk']['bg'];
12857 unset($opt['mk']['ca']);
12858 unset($opt['mk']['rc']);
12859 unset($opt['mk']['ac']);
12860 unset($opt['mk']['i']);
12861 unset($opt['mk']['ri']);
12862 unset($opt['mk']['ix']);
12863 unset($opt['mk']['if']);
12864 //$opt['mk']['if']['sw'];
12865 //$opt['mk']['if']['s'];
12866 //$opt['mk']['if']['a'];
12867 //$opt['mk']['if']['fb'];
12868 unset($opt['mk']['tp']);
12877 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12886 * Creates a RadioButton field.
12887 * @param string $name Field name.
12888 * @param int $w Width of the radio button.
12889 * @param array $prop Javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12890 * @param array $opt Annotation parameters. Possible values are described on official PDF32000_2008 reference.
12891 * @param string $onvalue Value to be returned if selected.
12892 * @param boolean $checked Define the initial state.
12893 * @param float|null $x Abscissa of the upper-left corner of the rectangle
12894 * @param float|null $y Ordinate of the upper-left corner of the rectangle
12895 * @param boolean $js If true put the field using JavaScript (requires Acrobat Writer to be rendered).
12897 * @author Nicola Asuni
12898 * @since 4.8.000 (2009-09-07)
12900 public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x=null, $y=null, $js=false) {
12901 if (TCPDF_STATIC
::empty_string($x)) {
12904 if (TCPDF_STATIC
::empty_string($y)) {
12907 // check page for no-write regions and adapt page margins if necessary
12908 list($x, $y) = $this->checkPageRegions($w, $x, $y);
12910 $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12913 if (TCPDF_STATIC
::empty_string($onvalue)) {
12917 $defval = $onvalue;
12922 $font = 'zapfdingbats';
12923 if ($this->pdfa_mode
) {
12924 // all fonts must be embedded
12925 $font = 'pdfa'.$font;
12927 $this->AddFont($font);
12928 $tmpfont = $this->getFontBuffer($font);
12929 // set data for parent group
12930 if (!isset($this->radiobutton_groups
[$this->page
])) {
12931 $this->radiobutton_groups
[$this->page
] = array();
12933 if (!isset($this->radiobutton_groups
[$this->page
][$name])) {
12934 $this->radiobutton_groups
[$this->page
][$name] = array();
12936 $this->radiobutton_groups
[$this->page
][$name]['n'] = $this->n
;
12937 $this->radio_groups
[] = $this->n
;
12939 $kid = ($this->n +
1);
12940 // save object ID to be added on Kids entry on parent object
12941 $this->radiobutton_groups
[$this->page
][$name][] = array('kid' => $kid, 'def' => $defval);
12942 // get default style
12943 $prop = array_merge($this->getFormDefaultProp(), $prop);
12944 $prop['NoToggleToOff'] = 'true';
12945 $prop['Radio'] = 'true';
12946 $prop['borderStyle'] = 'inset';
12947 // get annotation data
12948 $popt = TCPDF_STATIC
::getAnnotOptFromJSProp($prop, $this->spot_colors
, $this->rtl
);
12949 // set additional default options
12950 $this->annotation_fonts
[$tmpfont['fontkey']] = $tmpfont['i'];
12951 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt
, $this->TextColor
);
12952 $popt['da'] = $fontstyle;
12953 // build appearance stream
12954 $popt['ap'] = array();
12955 $popt['ap']['n'] = array();
12956 $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][108])) / 2) * $this->k
);
12957 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt
/ 1000) / $this->k
)) * $this->k
);
12958 $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);
12959 $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);
12960 if (!isset($popt['mk'])) {
12961 $popt['mk'] = array();
12963 $popt['mk']['ca'] = '(l)';
12965 $opt = array_merge($popt, $opt);
12966 // set remaining annotation data
12967 $opt['Subtype'] = 'Widget';
12968 $opt['ft'] = 'Btn';
12970 $opt['v'] = array('/'.$onvalue);
12971 $opt['as'] = $onvalue;
12973 $opt['as'] = 'Off';
12975 // store readonly flag
12976 if (!isset($this->radiobutton_groups
[$this->page
][$name]['#readonly#'])) {
12977 $this->radiobutton_groups
[$this->page
][$name]['#readonly#'] = false;
12979 $this->radiobutton_groups
[$this->page
][$name]['#readonly#'] |
= ($opt['f'] & 64);
12980 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
12989 * Creates a List-box field
12990 * @param string $name field name
12991 * @param int $w width
12992 * @param int $h height
12993 * @param array $values array containing the list of values.
12994 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
12995 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
12996 * @param float|null $x Abscissa of the upper-left corner of the rectangle
12997 * @param float|null $y Ordinate of the upper-left corner of the rectangle
12998 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13000 * @author Nicola Asuni
13001 * @since 4.8.000 (2009-09-07)
13003 public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13004 if (TCPDF_STATIC
::empty_string($x)) {
13007 if (TCPDF_STATIC
::empty_string($y)) {
13010 // check page for no-write regions and adapt page margins if necessary
13011 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13013 $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
13015 foreach ($values as $value) {
13016 if (is_array($value)) {
13017 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13019 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13022 $this->javascript
.= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13025 // get default style
13026 $prop = array_merge($this->getFormDefaultProp(), $prop);
13027 // get annotation data
13028 $popt = TCPDF_STATIC
::getAnnotOptFromJSProp($prop, $this->spot_colors
, $this->rtl
);
13029 // set additional default values
13030 $this->annotation_fonts
[$this->CurrentFont
['fontkey']] = $this->CurrentFont
['i'];
13031 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont
['i'], $this->FontSizePt
, $this->TextColor
);
13032 $popt['da'] = $fontstyle;
13033 // build appearance stream
13034 $popt['ap'] = array();
13035 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13037 foreach($values as $item) {
13038 if (is_array($item)) {
13039 $text .= $item[1]."\n";
13041 $text .= $item."\n";
13044 $tmpid = $this->startTemplate($w, $h, false);
13045 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13046 $this->endTemplate();
13048 $popt['ap']['n'] .= $this->xobjects
[$tmpid]['outdata'];
13049 unset($this->xobjects
[$tmpid]);
13050 $popt['ap']['n'] .= 'Q EMC';
13052 $opt = array_merge($popt, $opt);
13053 // set remaining annotation data
13054 $opt['Subtype'] = 'Widget';
13057 $opt['opt'] = $values;
13058 unset($opt['mk']['ca']);
13059 unset($opt['mk']['rc']);
13060 unset($opt['mk']['ac']);
13061 unset($opt['mk']['i']);
13062 unset($opt['mk']['ri']);
13063 unset($opt['mk']['ix']);
13064 unset($opt['mk']['if']);
13065 unset($opt['mk']['tp']);
13066 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13075 * Creates a Combo-box field
13076 * @param string $name field name
13077 * @param int $w width
13078 * @param int $h height
13079 * @param array $values array containing the list of values.
13080 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13081 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13082 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13083 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13084 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13086 * @author Nicola Asuni
13087 * @since 4.8.000 (2009-09-07)
13089 public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13090 if (TCPDF_STATIC
::empty_string($x)) {
13093 if (TCPDF_STATIC
::empty_string($y)) {
13096 // check page for no-write regions and adapt page margins if necessary
13097 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13099 $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
13101 foreach ($values as $value) {
13102 if (is_array($value)) {
13103 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13105 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13108 $this->javascript
.= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13111 // get default style
13112 $prop = array_merge($this->getFormDefaultProp(), $prop);
13113 $prop['Combo'] = true;
13114 // get annotation data
13115 $popt = TCPDF_STATIC
::getAnnotOptFromJSProp($prop, $this->spot_colors
, $this->rtl
);
13116 // set additional default options
13117 $this->annotation_fonts
[$this->CurrentFont
['fontkey']] = $this->CurrentFont
['i'];
13118 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont
['i'], $this->FontSizePt
, $this->TextColor
);
13119 $popt['da'] = $fontstyle;
13120 // build appearance stream
13121 $popt['ap'] = array();
13122 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13124 foreach($values as $item) {
13125 if (is_array($item)) {
13126 $text .= $item[1]."\n";
13128 $text .= $item."\n";
13131 $tmpid = $this->startTemplate($w, $h, false);
13132 $this->MultiCell($w, $h, $text, 0, '', false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
13133 $this->endTemplate();
13135 $popt['ap']['n'] .= $this->xobjects
[$tmpid]['outdata'];
13136 unset($this->xobjects
[$tmpid]);
13137 $popt['ap']['n'] .= 'Q EMC';
13139 $opt = array_merge($popt, $opt);
13140 // set remaining annotation data
13141 $opt['Subtype'] = 'Widget';
13144 $opt['opt'] = $values;
13145 unset($opt['mk']['ca']);
13146 unset($opt['mk']['rc']);
13147 unset($opt['mk']['ac']);
13148 unset($opt['mk']['i']);
13149 unset($opt['mk']['ri']);
13150 unset($opt['mk']['ix']);
13151 unset($opt['mk']['if']);
13152 unset($opt['mk']['tp']);
13153 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13162 * Creates a CheckBox field
13163 * @param string $name field name
13164 * @param int $w width
13165 * @param boolean $checked define the initial state.
13166 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13167 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13168 * @param string $onvalue value to be returned if selected.
13169 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13170 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13171 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13173 * @author Nicola Asuni
13174 * @since 4.8.000 (2009-09-07)
13176 public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x=null, $y=null, $js=false) {
13177 if (TCPDF_STATIC
::empty_string($x)) {
13180 if (TCPDF_STATIC
::empty_string($y)) {
13183 // check page for no-write regions and adapt page margins if necessary
13184 list($x, $y) = $this->checkPageRegions($w, $x, $y);
13186 $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13189 if (!isset($prop['value'])) {
13190 $prop['value'] = array('Yes');
13192 // get default style
13193 $prop = array_merge($this->getFormDefaultProp(), $prop);
13194 $prop['borderStyle'] = 'inset';
13195 // get annotation data
13196 $popt = TCPDF_STATIC
::getAnnotOptFromJSProp($prop, $this->spot_colors
, $this->rtl
);
13197 // set additional default options
13198 $font = 'zapfdingbats';
13199 if ($this->pdfa_mode
) {
13200 // all fonts must be embedded
13201 $font = 'pdfa'.$font;
13203 $this->AddFont($font);
13204 $tmpfont = $this->getFontBuffer($font);
13205 $this->annotation_fonts
[$tmpfont['fontkey']] = $tmpfont['i'];
13206 $fontstyle = sprintf('/F%d %F Tf %s', $tmpfont['i'], $this->FontSizePt
, $this->TextColor
);
13207 $popt['da'] = $fontstyle;
13208 // build appearance stream
13209 $popt['ap'] = array();
13210 $popt['ap']['n'] = array();
13211 $fx = ((($w - $this->getAbsFontMeasure($tmpfont['cw'][110])) / 2) * $this->k
);
13212 $fy = (($w - ((($tmpfont['desc']['Ascent'] - $tmpfont['desc']['Descent']) * $this->FontSizePt
/ 1000) / $this->k
)) * $this->k
);
13213 $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);
13214 $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);
13216 $opt = array_merge($popt, $opt);
13217 // set remaining annotation data
13218 $opt['Subtype'] = 'Widget';
13219 $opt['ft'] = 'Btn';
13221 if (TCPDF_STATIC
::empty_string($onvalue)) {
13224 $opt['opt'] = array($onvalue);
13226 $opt['v'] = array('/Yes');
13227 $opt['as'] = 'Yes';
13229 $opt['v'] = array('/Off');
13230 $opt['as'] = 'Off';
13232 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13241 * Creates a button field
13242 * @param string $name field name
13243 * @param int $w width
13244 * @param int $h height
13245 * @param string $caption caption.
13246 * @param mixed $action 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.
13247 * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
13248 * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
13249 * @param float|null $x Abscissa of the upper-left corner of the rectangle
13250 * @param float|null $y Ordinate of the upper-left corner of the rectangle
13251 * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
13253 * @author Nicola Asuni
13254 * @since 4.8.000 (2009-09-07)
13256 public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x=null, $y=null, $js=false) {
13257 if (TCPDF_STATIC
::empty_string($x)) {
13260 if (TCPDF_STATIC
::empty_string($y)) {
13263 // check page for no-write regions and adapt page margins if necessary
13264 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13266 $this->_addfield('button', $name, $this->x
, $this->y
, $w, $h, $prop);
13267 $this->javascript
.= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
13268 $this->javascript
.= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
13269 $this->javascript
.= 'f'.$name.".highlight='push';\n";
13270 $this->javascript
.= 'f'.$name.".print=false;\n";
13273 // get default style
13274 $prop = array_merge($this->getFormDefaultProp(), $prop);
13275 $prop['Pushbutton'] = 'true';
13276 $prop['highlight'] = 'push';
13277 $prop['display'] = 'display.noPrint';
13278 // get annotation data
13279 $popt = TCPDF_STATIC
::getAnnotOptFromJSProp($prop, $this->spot_colors
, $this->rtl
);
13280 $this->annotation_fonts
[$this->CurrentFont
['fontkey']] = $this->CurrentFont
['i'];
13281 $fontstyle = sprintf('/F%d %F Tf %s', $this->CurrentFont
['i'], $this->FontSizePt
, $this->TextColor
);
13282 $popt['da'] = $fontstyle;
13283 // build appearance stream
13284 $popt['ap'] = array();
13285 $popt['ap']['n'] = '/Tx BMC q '.$fontstyle.' ';
13286 $tmpid = $this->startTemplate($w, $h, false);
13287 $bw = (2 / $this->k
); // border width
13289 'L' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13290 'R' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)),
13291 'T' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(231)),
13292 'B' => array('width' => $bw, 'cap' => 'square', 'join' => 'miter', 'dash' => 0, 'color' => array(51)));
13293 $this->setFillColor(204);
13294 $this->Cell($w, $h, $caption, $border, 0, 'C', true, '', 1, false, 'T', 'M');
13295 $this->endTemplate();
13297 $popt['ap']['n'] .= $this->xobjects
[$tmpid]['outdata'];
13298 unset($this->xobjects
[$tmpid]);
13299 $popt['ap']['n'] .= 'Q EMC';
13300 // set additional default options
13301 if (!isset($popt['mk'])) {
13302 $popt['mk'] = array();
13304 $ann_obj_id = ($this->n +
1);
13305 if (!empty($action) AND !is_array($action)) {
13306 $ann_obj_id = ($this->n +
2);
13308 $popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
13309 $popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
13310 $popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
13312 $opt = array_merge($popt, $opt);
13313 // set remaining annotation data
13314 $opt['Subtype'] = 'Widget';
13315 $opt['ft'] = 'Btn';
13316 $opt['t'] = $caption;
13318 if (!empty($action)) {
13319 if (is_array($action)) {
13320 // form action options as on section 12.7.5 of PDF32000_2008.
13321 $opt['aa'] = '/D <<';
13322 $bmode = array('SubmitForm', 'ResetForm', 'ImportData');
13323 foreach ($action AS $key => $val) {
13324 if (($key == 'S') AND in_array($val, $bmode)) {
13325 $opt['aa'] .= ' /S /'.$val;
13326 } elseif (($key == 'F') AND (!empty($val))) {
13327 $opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
13328 } elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
13329 $opt['aa'] .= ' /Fields [';
13330 foreach ($val AS $field) {
13331 $opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
13334 } elseif (($key == 'Flags')) {
13336 if (is_array($val)) {
13337 foreach ($val AS $flag) {
13339 case 'Include/Exclude': {
13343 case 'IncludeNoValueFields': {
13347 case 'ExportFormat': {
13351 case 'GetMethod': {
13355 case 'SubmitCoordinates': {
13363 case 'IncludeAppendSaves': {
13367 case 'IncludeAnnotations': {
13371 case 'SubmitPDF': {
13375 case 'CanonicalFormat': {
13379 case 'ExclNonUserAnnots': {
13387 case 'EmbedForm': {
13394 $ff = intval($val);
13396 $opt['aa'] .= ' /Flags '.$ff;
13399 $opt['aa'] .= ' >>';
13401 // Javascript action or raw action command
13402 $js_obj_id = $this->addJavascriptObject($action);
13403 $opt['aa'] = '/D '.$js_obj_id.' 0 R';
13406 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
13414 // --- END FORMS FIELDS ------------------------------------------------
13417 * Add certification signature (DocMDP or UR3)
13418 * You can set only one signature type
13420 * @author Nicola Asuni
13421 * @since 4.6.008 (2009-05-07)
13423 protected function _putsignature() {
13424 if ((!$this->sign
) OR (!isset($this->signature_data
['cert_type']))) {
13427 $sigobjid = ($this->sig_obj_id +
1);
13428 $out = $this->_getobj($sigobjid)."\n";
13429 $out .= '<< /Type /Sig';
13430 $out .= ' /Filter /Adobe.PPKLite';
13431 $out .= ' /SubFilter /adbe.pkcs7.detached';
13432 $out .= ' '.TCPDF_STATIC
::$byterange_string;
13433 $out .= ' /Contents<'.str_repeat('0', $this->signature_max_length
).'>';
13434 if (empty($this->signature_data
['approval']) OR ($this->signature_data
['approval'] != 'A')) {
13435 $out .= ' /Reference ['; // array of signature reference dictionaries
13436 $out .= ' << /Type /SigRef';
13437 if ($this->signature_data
['cert_type'] > 0) {
13438 $out .= ' /TransformMethod /DocMDP';
13439 $out .= ' /TransformParams <<';
13440 $out .= ' /Type /TransformParams';
13441 $out .= ' /P '.$this->signature_data
['cert_type'];
13442 $out .= ' /V /1.2';
13444 $out .= ' /TransformMethod /UR3';
13445 $out .= ' /TransformParams <<';
13446 $out .= ' /Type /TransformParams';
13447 $out .= ' /V /2.2';
13448 if (!TCPDF_STATIC
::empty_string($this->ur
['document'])) {
13449 $out .= ' /Document['.$this->ur
['document'].']';
13451 if (!TCPDF_STATIC
::empty_string($this->ur
['form'])) {
13452 $out .= ' /Form['.$this->ur
['form'].']';
13454 if (!TCPDF_STATIC
::empty_string($this->ur
['signature'])) {
13455 $out .= ' /Signature['.$this->ur
['signature'].']';
13457 if (!TCPDF_STATIC
::empty_string($this->ur
['annots'])) {
13458 $out .= ' /Annots['.$this->ur
['annots'].']';
13460 if (!TCPDF_STATIC
::empty_string($this->ur
['ef'])) {
13461 $out .= ' /EF['.$this->ur
['ef'].']';
13463 if (!TCPDF_STATIC
::empty_string($this->ur
['formex'])) {
13464 $out .= ' /FormEX['.$this->ur
['formex'].']';
13467 $out .= ' >>'; // close TransformParams
13468 // optional digest data (values must be calculated and replaced later)
13469 //$out .= ' /Data ********** 0 R';
13470 //$out .= ' /DigestMethod/MD5';
13471 //$out .= ' /DigestLocation[********** 34]';
13472 //$out .= ' /DigestValue<********************************>';
13474 $out .= ' ]'; // end of reference
13476 if (isset($this->signature_data
['info']['Name']) AND !TCPDF_STATIC
::empty_string($this->signature_data
['info']['Name'])) {
13477 $out .= ' /Name '.$this->_textstring($this->signature_data
['info']['Name'], $sigobjid);
13479 if (isset($this->signature_data
['info']['Location']) AND !TCPDF_STATIC
::empty_string($this->signature_data
['info']['Location'])) {
13480 $out .= ' /Location '.$this->_textstring($this->signature_data
['info']['Location'], $sigobjid);
13482 if (isset($this->signature_data
['info']['Reason']) AND !TCPDF_STATIC
::empty_string($this->signature_data
['info']['Reason'])) {
13483 $out .= ' /Reason '.$this->_textstring($this->signature_data
['info']['Reason'], $sigobjid);
13485 if (isset($this->signature_data
['info']['ContactInfo']) AND !TCPDF_STATIC
::empty_string($this->signature_data
['info']['ContactInfo'])) {
13486 $out .= ' /ContactInfo '.$this->_textstring($this->signature_data
['info']['ContactInfo'], $sigobjid);
13488 $out .= ' /M '.$this->_datestring($sigobjid, $this->doc_modification_timestamp
);
13490 $out .= "\n".'endobj';
13495 * Set User's Rights for PDF Reader
13496 * WARNING: This is experimental and currently do not work.
13497 * Check the PDF Reference 8.7.1 Transform Methods,
13498 * Table 8.105 Entries in the UR transform parameters dictionary
13499 * @param boolean $enable if true enable user's rights on PDF reader
13500 * @param string $document 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.
13501 * @param string $annots 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.
13502 * @param string $form Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
13503 * @param string $signature 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.
13504 * @param string $ef 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
13505 Names specifying additional embedded-files-related usage rights for the document.
13506 * @param string $formex 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.
13508 * @author Nicola Asuni
13509 * @since 2.9.000 (2008-03-26)
13511 public function setUserRights(
13513 $document='/FullSave',
13514 $annots='/Create/Delete/Modify/Copy/Import/Export',
13515 $form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
13516 $signature='/Modify',
13517 $ef='/Create/Delete/Modify/Import',
13519 $this->ur
['enabled'] = $enable;
13520 $this->ur
['document'] = $document;
13521 $this->ur
['annots'] = $annots;
13522 $this->ur
['form'] = $form;
13523 $this->ur
['signature'] = $signature;
13524 $this->ur
['ef'] = $ef;
13525 $this->ur
['formex'] = $formex;
13526 if (!$this->sign
) {
13527 $this->setSignature('', '', '', '', 0, array());
13532 * Enable document signature (requires the OpenSSL Library).
13533 * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
13534 * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13535 * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13536 * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
13537 * @param mixed $signing_cert signing certificate (string or filename prefixed with 'file://')
13538 * @param mixed $private_key private key (string or filename prefixed with 'file://')
13539 * @param string $private_key_password password
13540 * @param string $extracerts 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.
13541 * @param int $cert_type 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.
13542 * @param array $info array of option information: Name, Location, Reason, ContactInfo.
13543 * @param string $approval Enable approval signature eg. for PDF incremental update
13545 * @author Nicola Asuni
13546 * @since 4.6.005 (2009-04-24)
13548 public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array(), $approval='') {
13549 // to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
13550 // to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
13551 // to convert pfx certificate to pem: openssl
13552 // OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
13553 $this->sign
= true;
13555 $this->sig_obj_id
= $this->n
; // signature widget
13556 ++
$this->n
; // signature object ($this->sig_obj_id + 1)
13557 $this->signature_data
= array();
13558 if (strlen($signing_cert) == 0) {
13559 $this->Error('Please provide a certificate file and password!');
13561 if (strlen($private_key) == 0) {
13562 $private_key = $signing_cert;
13564 $this->signature_data
['signcert'] = $signing_cert;
13565 $this->signature_data
['privkey'] = $private_key;
13566 $this->signature_data
['password'] = $private_key_password;
13567 $this->signature_data
['extracerts'] = $extracerts;
13568 $this->signature_data
['cert_type'] = $cert_type;
13569 $this->signature_data
['info'] = $info;
13570 $this->signature_data
['approval'] = $approval;
13574 * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
13575 * @param float $x Abscissa of the upper-left corner.
13576 * @param float $y Ordinate of the upper-left corner.
13577 * @param float $w Width of the signature area.
13578 * @param float $h Height of the signature area.
13579 * @param int $page option page number (if < 0 the current page is used).
13580 * @param string $name Name of the signature.
13582 * @author Nicola Asuni
13583 * @since 5.3.011 (2010-06-17)
13585 public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13586 $this->signature_appearance
= $this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13590 * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties)
13591 * @param float $x Abscissa of the upper-left corner.
13592 * @param float $y Ordinate of the upper-left corner.
13593 * @param float $w Width of the signature area.
13594 * @param float $h Height of the signature area.
13595 * @param int $page option page number (if < 0 the current page is used).
13596 * @param string $name Name of the signature.
13598 * @author Nicola Asuni
13599 * @since 5.9.101 (2011-07-06)
13601 public function addEmptySignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13603 $this->empty_signature_appearance
[] = array('objid' => $this->n
) +
$this->getSignatureAppearanceArray($x, $y, $w, $h, $page, $name);
13607 * Get the array that defines the signature appearance (page and rectangle coordinates).
13608 * @param float $x Abscissa of the upper-left corner.
13609 * @param float $y Ordinate of the upper-left corner.
13610 * @param float $w Width of the signature area.
13611 * @param float $h Height of the signature area.
13612 * @param int $page option page number (if < 0 the current page is used).
13613 * @param string $name Name of the signature.
13614 * @return array Array defining page and rectangle coordinates of signature appearance.
13616 * @author Nicola Asuni
13617 * @since 5.9.101 (2011-07-06)
13619 protected function getSignatureAppearanceArray($x=0, $y=0, $w=0, $h=0, $page=-1, $name='') {
13621 if (($page < 1) OR ($page > $this->numpages
)) {
13622 $sigapp['page'] = $this->page
;
13624 $sigapp['page'] = intval($page);
13626 if (empty($name)) {
13627 $sigapp['name'] = 'Signature';
13629 $sigapp['name'] = $name;
13631 $a = $x * $this->k
;
13632 $b = $this->pagedim
[($sigapp['page'])]['h'] - (($y +
$h) * $this->k
);
13633 $c = $w * $this->k
;
13634 $d = $h * $this->k
;
13635 $sigapp['rect'] = sprintf('%F %F %F %F', $a, $b, ($a +
$c), ($b +
$d));
13640 * Enable document timestamping (requires the OpenSSL Library).
13641 * The trusted timestamping improve document security that means that no one should be able to change the document once it has been recorded.
13642 * Use with digital signature only!
13643 * @param string $tsa_host Time Stamping Authority (TSA) server (prefixed with 'https://')
13644 * @param string $tsa_username Specifies the username for TSA authorization (optional) OR specifies the TSA authorization PEM file (see: example_66.php, optional)
13645 * @param string $tsa_password Specifies the password for TSA authorization (optional)
13646 * @param string $tsa_cert Specifies the location of TSA certificate for authorization (optional for cURL)
13648 * @author Richard Stockinger
13649 * @since 6.0.090 (2014-06-16)
13651 public function setTimeStamp($tsa_host='', $tsa_username='', $tsa_password='', $tsa_cert='') {
13652 $this->tsa_data
= array();
13653 if (!function_exists('curl_init')) {
13654 $this->Error('Please enable cURL PHP extension!');
13656 if (strlen($tsa_host) == 0) {
13657 $this->Error('Please specify the host of Time Stamping Authority (TSA)!');
13659 $this->tsa_data
['tsa_host'] = $tsa_host;
13660 if (is_file($tsa_username)) {
13661 $this->tsa_data
['tsa_auth'] = $tsa_username;
13663 $this->tsa_data
['tsa_username'] = $tsa_username;
13665 $this->tsa_data
['tsa_password'] = $tsa_password;
13666 $this->tsa_data
['tsa_cert'] = $tsa_cert;
13667 $this->tsa_timestamp
= true;
13671 * NOT YET IMPLEMENTED
13672 * Request TSA for a timestamp
13673 * @param string $signature Digital signature as binary string
13674 * @return string Timestamped digital signature
13676 * @author Richard Stockinger
13677 * @since 6.0.090 (2014-06-16)
13679 protected function applyTSA($signature) {
13680 if (!$this->tsa_timestamp
) {
13683 //@TODO: implement this feature
13688 * Create a new page group.
13689 * NOTE: call this function before calling AddPage()
13690 * @param int|null $page starting group page (leave empty for next page).
13692 * @since 3.0.000 (2008-03-27)
13694 public function startPageGroup($page=null) {
13695 if (empty($page)) {
13696 $page = $this->page +
1;
13698 $this->newpagegroup
[$page] = sizeof($this->newpagegroup
) +
1;
13702 * Set the starting page number.
13703 * @param int $num Starting page number.
13704 * @since 5.9.093 (2011-06-16)
13707 public function setStartingPageNumber($num=1) {
13708 $this->starting_page_number
= max(0, intval($num));
13712 * Returns the string alias used right align page numbers.
13713 * If the current font is unicode type, the returned string wil contain an additional open curly brace.
13715 * @since 5.9.099 (2011-06-27)
13718 public function getAliasRightShift() {
13719 // calculate aproximatively the ratio between widths of aliases and replacements.
13720 $ref = '{'.TCPDF_STATIC
::$alias_right_shift.'}{'.TCPDF_STATIC
::$alias_tot_pages.'}{'.TCPDF_STATIC
::$alias_num_page.'}';
13721 $rep = str_repeat(' ', $this->GetNumChars($ref));
13722 $wrep = $this->GetStringWidth($rep);
13724 $wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13728 $sdiff = sprintf('%F', $wdiff);
13729 $alias = TCPDF_STATIC
::$alias_right_shift.$sdiff.'}';
13730 if ($this->isUnicodeFont()) {
13731 $alias = '{'.$alias;
13737 * Returns the string alias used for the total number of pages.
13738 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13739 * This alias will be replaced by the total number of pages in the document.
13741 * @since 4.0.018 (2008-08-08)
13744 public function getAliasNbPages() {
13745 if ($this->isUnicodeFont()) {
13746 return '{'.TCPDF_STATIC
::$alias_tot_pages.'}';
13748 return TCPDF_STATIC
::$alias_tot_pages;
13752 * Returns the string alias used for the page number.
13753 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13754 * This alias will be replaced by the page number.
13756 * @since 4.5.000 (2009-01-02)
13759 public function getAliasNumPage() {
13760 if ($this->isUnicodeFont()) {
13761 return '{'.TCPDF_STATIC
::$alias_num_page.'}';
13763 return TCPDF_STATIC
::$alias_num_page;
13767 * Return the alias for the total number of pages in the current page group.
13768 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13769 * This alias will be replaced by the total number of pages in this group.
13770 * @return string alias of the current page group
13772 * @since 3.0.000 (2008-03-27)
13774 public function getPageGroupAlias() {
13775 if ($this->isUnicodeFont()) {
13776 return '{'.TCPDF_STATIC
::$alias_group_tot_pages.'}';
13778 return TCPDF_STATIC
::$alias_group_tot_pages;
13782 * Return the alias for the page number on the current page group.
13783 * If the current font is unicode type, the returned string is surrounded by additional curly braces.
13784 * This alias will be replaced by the page number (relative to the belonging group).
13785 * @return string alias of the current page group
13787 * @since 4.5.000 (2009-01-02)
13789 public function getPageNumGroupAlias() {
13790 if ($this->isUnicodeFont()) {
13791 return '{'.TCPDF_STATIC
::$alias_group_num_page.'}';
13793 return TCPDF_STATIC
::$alias_group_num_page;
13797 * Return the current page in the group.
13798 * @return int current page in the group
13800 * @since 3.0.000 (2008-03-27)
13802 public function getGroupPageNo() {
13803 return $this->pagegroups
[$this->currpagegroup
];
13807 * Returns the current group page number formatted as a string.
13809 * @since 4.3.003 (2008-11-18)
13810 * @see PaneNo(), formatPageNumber()
13812 public function getGroupPageNoFormatted() {
13813 return TCPDF_STATIC
::formatPageNumber($this->getGroupPageNo());
13817 * Returns the current page number formatted as a string.
13819 * @since 4.2.005 (2008-11-06)
13820 * @see PaneNo(), formatPageNumber()
13822 public function PageNoFormatted() {
13823 return TCPDF_STATIC
::formatPageNumber($this->PageNo());
13829 * @since 3.0.000 (2008-03-27)
13831 protected function _putocg() {
13832 if (empty($this->pdflayers
)) {
13835 foreach ($this->pdflayers
as $key => $layer) {
13836 $this->pdflayers
[$key]['objid'] = $this->_newobj();
13837 $out = '<< /Type /OCG';
13838 $out .= ' /Name '.$this->_textstring($layer['name'], $this->pdflayers
[$key]['objid']);
13839 $out .= ' /Usage <<';
13840 if (isset($layer['print']) AND ($layer['print'] !== NULL)) {
13841 $out .= ' /Print <</PrintState /'.($layer['print']?
'ON':'OFF').'>>';
13843 $out .= ' /View <</ViewState /'.($layer['view']?
'ON':'OFF').'>>';
13845 $out .= "\n".'endobj';
13851 * Start a new pdf layer.
13852 * @param string $name Layer name (only a-z letters and numbers). Leave empty for automatic name.
13853 * @param boolean|null $print Set to TRUE to print this layer, FALSE to not print and NULL to not set this option
13854 * @param boolean $view Set to true to view this layer.
13855 * @param boolean $lock If true lock the layer
13857 * @since 5.9.102 (2011-07-13)
13859 public function startLayer($name='', $print=true, $view=true, $lock=true) {
13860 if ($this->state
!= 2) {
13863 $layer = sprintf('LYR%03d', (count($this->pdflayers
) +
1));
13864 if (empty($name)) {
13867 $name = preg_replace('/[^a-zA-Z0-9_\-]/', '', $name);
13869 $this->pdflayers
[] = array('layer' => $layer, 'name' => $name, 'print' => $print, 'view' => $view, 'lock' => $lock);
13870 $this->openMarkedContent
= true;
13871 $this->_out('/OC /'.$layer.' BDC');
13875 * End the current PDF layer.
13877 * @since 5.9.102 (2011-07-13)
13879 public function endLayer() {
13880 if ($this->state
!= 2) {
13883 if ($this->openMarkedContent
) {
13884 // close existing open marked-content layer
13885 $this->_out('EMC');
13886 $this->openMarkedContent
= false;
13891 * Set the visibility of the successive elements.
13892 * This can be useful, for instance, to put a background
13893 * image or color that will show on screen but won't print.
13894 * @param string $v visibility mode. Legal values are: all, print, screen or view.
13896 * @since 3.0.000 (2008-03-27)
13898 public function setVisibility($v) {
13899 if ($this->state
!= 2) {
13905 $this->startLayer('Print', true, false);
13910 $this->startLayer('View', false, true);
13918 $this->Error('Incorrect visibility: '.$v);
13925 * Add transparency parameters to the current extgstate
13926 * @param array $parms parameters
13927 * @return int|void the number of extgstates
13929 * @since 3.0.000 (2008-03-27)
13931 protected function addExtGState($parms) {
13932 if ($this->pdfa_mode ||
$this->pdfa_version
>= 2) {
13933 // transparencies are not allowed in PDF/A mode
13936 // check if this ExtGState already exist
13937 foreach ($this->extgstates
as $i => $ext) {
13938 if ($ext['parms'] == $parms) {
13939 if ($this->inxobj
) {
13940 // we are inside an XObject template
13941 $this->xobjects
[$this->xobjid
]['extgstates'][$i] = $ext;
13943 // return reference to existing ExtGState
13947 $n = (count($this->extgstates
) +
1);
13948 $this->extgstates
[$n] = array('parms' => $parms);
13949 if ($this->inxobj
) {
13950 // we are inside an XObject template
13951 $this->xobjects
[$this->xobjid
]['extgstates'][$n] = $this->extgstates
[$n];
13958 * @param int $gs extgstate
13960 * @since 3.0.000 (2008-03-27)
13962 protected function setExtGState($gs) {
13963 if (($this->pdfa_mode
&& $this->pdfa_version
< 2) OR ($this->state
!= 2)) {
13964 // transparency is not allowed in PDF/A-1 mode
13967 $this->_out(sprintf('/GS%d gs', $gs));
13971 * Put extgstates for object transparency
13973 * @since 3.0.000 (2008-03-27)
13975 protected function _putextgstates() {
13976 foreach ($this->extgstates
as $i => $ext) {
13977 $this->extgstates
[$i]['n'] = $this->_newobj();
13978 $out = '<< /Type /ExtGState';
13979 foreach ($ext['parms'] as $k => $v) {
13980 if (is_float($v)) {
13981 $v = sprintf('%F', $v);
13982 } elseif ($v === true) {
13984 } elseif ($v === false) {
13987 $out .= ' /'.$k.' '.$v;
13990 $out .= "\n".'endobj';
13996 * Set overprint mode for stroking (OP) and non-stroking (op) painting operations.
13997 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
13998 * @param boolean $stroking If true apply overprint for stroking operations.
13999 * @param boolean|null $nonstroking If true apply overprint for painting operations other than stroking.
14000 * @param integer $mode 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).
14002 * @since 5.9.152 (2012-03-23)
14004 public function setOverprint($stroking=true, $nonstroking=null, $mode=0) {
14005 if ($this->state
!= 2) {
14008 $stroking = $stroking ?
true : false;
14009 if (TCPDF_STATIC
::empty_string($nonstroking)) {
14010 // default value if not set
14011 $nonstroking = $stroking;
14013 $nonstroking = $nonstroking ?
true : false;
14015 if (($mode != 0) AND ($mode != 1)) {
14018 $this->overprint
= array('OP' => $stroking, 'op' => $nonstroking, 'OPM' => $mode);
14019 $gs = $this->addExtGState($this->overprint
);
14020 $this->setExtGState($gs);
14024 * Get the overprint mode array (OP, op, OPM).
14025 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14026 * @return array<string,bool|int>
14028 * @since 5.9.152 (2012-03-23)
14030 public function getOverprint() {
14031 return $this->overprint
;
14035 * Set alpha for stroking (CA) and non-stroking (ca) operations.
14036 * @param float $stroking Alpha value for stroking operations: real value from 0 (transparent) to 1 (opaque).
14037 * @param string $bm blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity
14038 * @param float|null $nonstroking Alpha value for non-stroking operations: real value from 0 (transparent) to 1 (opaque).
14039 * @param boolean $ais
14041 * @since 3.0.000 (2008-03-27)
14043 public function setAlpha($stroking=1, $bm='Normal', $nonstroking=null, $ais=false) {
14044 if ($this->pdfa_mode
&& $this->pdfa_version
< 2) {
14045 // transparency is not allowed in PDF/A-1 mode
14048 $stroking = floatval($stroking);
14049 if (TCPDF_STATIC
::empty_string($nonstroking)) {
14050 // default value if not set
14051 $nonstroking = $stroking;
14053 $nonstroking = floatval($nonstroking);
14055 if ($bm[0] == '/') {
14056 // remove trailing slash
14057 $bm = substr($bm, 1);
14059 if (!in_array($bm, array('Normal', 'Multiply', 'Screen', 'Overlay', 'Darken', 'Lighten', 'ColorDodge', 'ColorBurn', 'HardLight', 'SoftLight', 'Difference', 'Exclusion', 'Hue', 'Saturation', 'Color', 'Luminosity'))) {
14062 $ais = $ais ?
true : false;
14063 $this->alpha
= array('CA' => $stroking, 'ca' => $nonstroking, 'BM' => '/'.$bm, 'AIS' => $ais);
14064 $gs = $this->addExtGState($this->alpha
);
14065 $this->setExtGState($gs);
14069 * Get the alpha mode array (CA, ca, BM, AIS).
14070 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
14071 * @return array<string,bool|string>
14073 * @since 5.9.152 (2012-03-23)
14075 public function getAlpha() {
14076 return $this->alpha
;
14080 * Set the default JPEG compression quality (1-100)
14081 * @param int $quality JPEG quality, integer between 1 and 100
14083 * @since 3.0.000 (2008-03-27)
14085 public function setJPEGQuality($quality) {
14086 if (($quality < 1) OR ($quality > 100)) {
14089 $this->jpeg_quality
= intval($quality);
14093 * Set the default number of columns in a row for HTML tables.
14094 * @param int $cols number of columns
14096 * @since 3.0.014 (2008-06-04)
14098 public function setDefaultTableColumns($cols=4) {
14099 $this->default_table_columns
= intval($cols);
14103 * Set the height of the cell (line height) respect the font height.
14104 * @param float $h cell proportion respect font height (typical value = 1.25).
14106 * @since 3.0.014 (2008-06-04)
14108 public function setCellHeightRatio($h) {
14109 $this->cell_height_ratio
= $h;
14113 * return the height of cell repect font height.
14116 * @since 4.0.012 (2008-07-24)
14118 public function getCellHeightRatio() {
14119 return $this->cell_height_ratio
;
14123 * Set the PDF version (check PDF reference for valid values).
14124 * @param string $version PDF document version.
14126 * @since 3.1.000 (2008-06-09)
14128 public function setPDFVersion($version='1.7') {
14129 if ($this->pdfa_mode
&& $this->pdfa_version
== 1 ) {
14131 $this->PDFVersion
= '1.4';
14132 } elseif ($this->pdfa_mode
&& $this->pdfa_version
>= 2 ) {
14134 $this->PDFVersion
= '1.7';
14136 $this->PDFVersion
= $version;
14141 * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
14142 * (see Section 8.1 of PDF reference, "Viewer Preferences").
14143 * <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>
14144 * @param array $preferences array of options.
14145 * @author Nicola Asuni
14147 * @since 3.1.000 (2008-06-09)
14149 public function setViewerPreferences($preferences) {
14150 $this->viewer_preferences
= $preferences;
14154 * Paints color transition registration bars
14155 * @param float $x abscissa of the top left corner of the rectangle.
14156 * @param float $y ordinate of the top left corner of the rectangle.
14157 * @param float $w width of the rectangle.
14158 * @param float $h height of the rectangle.
14159 * @param boolean $transition if true prints tcolor transitions to white.
14160 * @param boolean $vertical if true prints bar vertically.
14161 * @param string $colors 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.
14162 * @author Nicola Asuni
14163 * @since 4.9.000 (2010-03-26)
14166 public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
14167 if (strpos($colors, 'ALLSPOT') !== false) {
14168 // expand spot colors
14170 foreach ($this->spot_colors
as $spot_color_name => $v) {
14171 $spot_colors .= ','.$spot_color_name;
14173 if (!empty($spot_colors)) {
14174 $spot_colors = substr($spot_colors, 1);
14175 $colors = str_replace('ALLSPOT', $spot_colors, $colors);
14177 $colors = str_replace('ALLSPOT', 'NONE', $colors);
14180 $bars = explode(',', $colors);
14181 $numbars = count($bars); // number of bars to print
14182 if ($numbars <= 0) {
14185 // set bar measures
14187 $coords = array(0, 0, 0, 1);
14188 $wb = $w / $numbars; // bar width
14189 $hb = $h; // bar height
14190 $xd = $wb; // delta x
14191 $yd = 0; // delta y
14193 $coords = array(1, 0, 0, 0);
14194 $wb = $w; // bar width
14195 $hb = $h / $numbars; // bar height
14196 $xd = 0; // delta x
14197 $yd = $hb; // delta y
14201 foreach ($bars as $col) {
14203 // set transition colors
14204 case 'A': { // BLACK (GRAYSCALE)
14205 $col_a = array(255);
14209 case 'W': { // WHITE (GRAYSCALE)
14211 $col_b = array(255);
14214 case 'R': { // RED (RGB)
14215 $col_a = array(255,255,255);
14216 $col_b = array(255,0,0);
14219 case 'G': { // GREEN (RGB)
14220 $col_a = array(255,255,255);
14221 $col_b = array(0,255,0);
14224 case 'B': { // BLUE (RGB)
14225 $col_a = array(255,255,255);
14226 $col_b = array(0,0,255);
14229 case 'C': { // CYAN (CMYK)
14230 $col_a = array(0,0,0,0);
14231 $col_b = array(100,0,0,0);
14234 case 'M': { // MAGENTA (CMYK)
14235 $col_a = array(0,0,0,0);
14236 $col_b = array(0,100,0,0);
14239 case 'Y': { // YELLOW (CMYK)
14240 $col_a = array(0,0,0,0);
14241 $col_b = array(0,0,100,0);
14244 case 'K': { // KEY - BLACK (CMYK)
14245 $col_a = array(0,0,0,0);
14246 $col_b = array(0,0,0,100);
14249 case 'RGB': { // BLACK REGISTRATION (RGB)
14250 $col_a = array(255,255,255);
14251 $col_b = array(0,0,0);
14254 case 'CMYK': { // BLACK REGISTRATION (CMYK)
14255 $col_a = array(0,0,0,0);
14256 $col_b = array(100,100,100,100);
14259 case 'ALL': { // SPOT COLOR REGISTRATION
14260 $col_a = array(0,0,0,0,'None');
14261 $col_b = array(100,100,100,100,'All');
14264 case 'NONE': { // SKIP THIS COLOR
14265 $col_a = array(0,0,0,0,'None');
14266 $col_b = array(0,0,0,0,'None');
14269 default: { // SPECIFIC SPOT COLOR NAME
14270 $col_a = array(0,0,0,0,'None');
14271 $col_b = TCPDF_COLORS
::getSpotColor($col, $this->spot_colors
);
14272 if ($col_b === false) {
14273 // in case of error defaults to the registration color
14274 $col_b = array(100,100,100,100,'All');
14279 if ($col != 'NONE') {
14282 $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14284 $this->setFillColorArray($col_b);
14285 // colored rectangle
14286 $this->Rect($xb, $yb, $wb, $hb, 'F', array());
14295 * Paints crop marks.
14296 * @param float $x abscissa of the crop mark center.
14297 * @param float $y ordinate of the crop mark center.
14298 * @param float $w width of the crop mark.
14299 * @param float $h height of the crop mark.
14300 * @param string $type 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.
14301 * @param array $color crop mark color (default spot registration color).
14302 * @author Nicola Asuni
14303 * @since 4.9.000 (2010-03-26)
14306 public function cropMark($x, $y, $w, $h, $type='T,R,B,L', $color=array(100,100,100,100,'All')) {
14307 $this->setLineStyle(array('width' => (0.5 / $this->k
), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
14308 $type = strtoupper($type);
14309 $type = preg_replace('/[^A-Z\-\,]*/', '', $type);
14310 // split type in single components
14311 $type = str_replace('-', ',', $type);
14312 $type = str_replace('TL', 'T,L', $type);
14313 $type = str_replace('TR', 'T,R', $type);
14314 $type = str_replace('BL', 'F,L', $type);
14315 $type = str_replace('BR', 'F,R', $type);
14316 $type = str_replace('A', 'T,L', $type);
14317 $type = str_replace('B', 'T,R', $type);
14318 $type = str_replace('T,RO', 'BO', $type);
14319 $type = str_replace('C', 'F,L', $type);
14320 $type = str_replace('D', 'F,R', $type);
14321 $crops = explode(',', strtoupper($type));
14322 // remove duplicates
14323 $crops = array_unique($crops);
14324 $dw = ($w / 4); // horizontal space to leave before the intersection point
14325 $dh = ($h / 4); // vertical space to leave before the intersection point
14326 foreach ($crops as $crop) {
14361 $this->Line($x1, $y1, $x2, $y2);
14366 * Paints a registration mark
14367 * @param float $x abscissa of the registration mark center.
14368 * @param float $y ordinate of the registration mark center.
14369 * @param float $r radius of the crop mark.
14370 * @param boolean $double if true print two concentric crop marks.
14371 * @param array $cola crop mark color (default spot registration color 'All').
14372 * @param array $colb second crop mark color (default spot registration color 'None').
14373 * @author Nicola Asuni
14374 * @since 4.9.000 (2010-03-26)
14377 public function registrationMark($x, $y, $r, $double=false, $cola=array(100,100,100,100,'All'), $colb=array(0,0,0,0,'None')) {
14378 $line_style = array('width' => max((0.5 / $this->k
),($r / 30)), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
14379 $this->setFillColorArray($cola);
14380 $this->PieSector($x, $y, $r, 90, 180, 'F');
14381 $this->PieSector($x, $y, $r, 270, 360, 'F');
14382 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14385 $this->setFillColorArray($colb);
14386 $this->PieSector($x, $y, $ri, 90, 180, 'F');
14387 $this->PieSector($x, $y, $ri, 270, 360, 'F');
14388 $this->setFillColorArray($cola);
14389 $this->PieSector($x, $y, $ri, 0, 90, 'F');
14390 $this->PieSector($x, $y, $ri, 180, 270, 'F');
14391 $this->Circle($x, $y, $ri, 0, 360, 'C', $line_style, array(), 8);
14396 * Paints a CMYK registration mark
14397 * @param float $x abscissa of the registration mark center.
14398 * @param float $y ordinate of the registration mark center.
14399 * @param float $r radius of the crop mark.
14400 * @author Nicola Asuni
14401 * @since 6.0.038 (2013-09-30)
14404 public function registrationMarkCMYK($x, $y, $r) {
14406 $lw = max((0.5 / $this->k
),($r / 8));
14412 $this->setFillColorArray(array(100,0,0,0));
14413 $this->PieSector($x, $y, $ri, 270, 360, 'F');
14415 $this->setFillColorArray(array(0,100,0,0));
14416 $this->PieSector($x, $y, $ri, 0, 90, 'F');
14418 $this->setFillColorArray(array(0,0,100,0));
14419 $this->PieSector($x, $y, $ri, 90, 180, 'F');
14421 $this->setFillColorArray(array(0,0,0,100));
14422 $this->PieSector($x, $y, $ri, 180, 270, 'F');
14423 // registration color
14424 $line_style = array('width' => $lw, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(100,100,100,100,'All'));
14425 $this->setFillColorArray(array(100,100,100,100,'All'));
14427 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14429 $this->Line($x, ($y - $re), $x, ($y - $ri));
14430 $this->Line($x, ($y +
$ri), $x, ($y +
$re));
14431 $this->Line(($x - $re), $y, ($x - $ri), $y);
14432 $this->Line(($x +
$ri), $y, ($x +
$re), $y);
14436 * Paints a linear colour gradient.
14437 * @param float $x abscissa of the top left corner of the rectangle.
14438 * @param float $y ordinate of the top left corner of the rectangle.
14439 * @param float $w width of the rectangle.
14440 * @param float $h height of the rectangle.
14441 * @param array $col1 first color (Grayscale, RGB or CMYK components).
14442 * @param array $col2 second color (Grayscale, RGB or CMYK components).
14443 * @param array $coords 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).
14444 * @author Andreas W\FCrmser, Nicola Asuni
14445 * @since 3.1.000 (2008-06-09)
14448 public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
14449 $this->Clip($x, $y, $w, $h);
14450 $this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14454 * Paints a radial colour gradient.
14455 * @param float $x abscissa of the top left corner of the rectangle.
14456 * @param float $y ordinate of the top left corner of the rectangle.
14457 * @param float $w width of the rectangle.
14458 * @param float $h height of the rectangle.
14459 * @param array $col1 first color (Grayscale, RGB or CMYK components).
14460 * @param array $col2 second color (Grayscale, RGB or CMYK components).
14461 * @param array $coords 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.
14462 * @author Andreas W\FCrmser, Nicola Asuni
14463 * @since 3.1.000 (2008-06-09)
14466 public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
14467 $this->Clip($x, $y, $w, $h);
14468 $this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
14472 * Paints a coons patch mesh.
14473 * @param float $x abscissa of the top left corner of the rectangle.
14474 * @param float $y ordinate of the top left corner of the rectangle.
14475 * @param float $w width of the rectangle.
14476 * @param float $h height of the rectangle.
14477 * @param array $col1 first color (lower left corner) (RGB components).
14478 * @param array $col2 second color (lower right corner) (RGB components).
14479 * @param array $col3 third color (upper right corner) (RGB components).
14480 * @param array $col4 fourth color (upper left corner) (RGB components).
14481 * @param array $coords <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>
14482 * @param array $coords_min minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0
14483 * @param array $coords_max maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1
14484 * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14485 * @author Andreas W\FCrmser, Nicola Asuni
14486 * @since 3.1.000 (2008-06-09)
14489 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) {
14490 if (($this->pdfa_mode
&& $this->pdfa_version
< 2) OR ($this->state
!= 2)) {
14493 $this->Clip($x, $y, $w, $h);
14494 $n = count($this->gradients
) +
1;
14495 $this->gradients
[$n] = array();
14496 $this->gradients
[$n]['type'] = 6; //coons patch mesh
14497 $this->gradients
[$n]['coords'] = array();
14498 $this->gradients
[$n]['antialias'] = $antialias;
14499 $this->gradients
[$n]['colors'] = array();
14500 $this->gradients
[$n]['transparency'] = false;
14501 //check the coords array if it is the simple array or the multi patch array
14502 if (!isset($coords[0]['f'])) {
14503 //simple array -> convert to multi patch array
14504 if (!isset($col1[1])) {
14505 $col1[1] = $col1[2] = $col1[0];
14507 if (!isset($col2[1])) {
14508 $col2[1] = $col2[2] = $col2[0];
14510 if (!isset($col3[1])) {
14511 $col3[1] = $col3[2] = $col3[0];
14513 if (!isset($col4[1])) {
14514 $col4[1] = $col4[2] = $col4[0];
14516 $patch_array[0]['f'] = 0;
14517 $patch_array[0]['points'] = $coords;
14518 $patch_array[0]['colors'][0]['r'] = $col1[0];
14519 $patch_array[0]['colors'][0]['g'] = $col1[1];
14520 $patch_array[0]['colors'][0]['b'] = $col1[2];
14521 $patch_array[0]['colors'][1]['r'] = $col2[0];
14522 $patch_array[0]['colors'][1]['g'] = $col2[1];
14523 $patch_array[0]['colors'][1]['b'] = $col2[2];
14524 $patch_array[0]['colors'][2]['r'] = $col3[0];
14525 $patch_array[0]['colors'][2]['g'] = $col3[1];
14526 $patch_array[0]['colors'][2]['b'] = $col3[2];
14527 $patch_array[0]['colors'][3]['r'] = $col4[0];
14528 $patch_array[0]['colors'][3]['g'] = $col4[1];
14529 $patch_array[0]['colors'][3]['b'] = $col4[2];
14531 //multi patch array
14532 $patch_array = $coords;
14534 $bpcd = 65535; //16 bits per coordinate
14535 //build the data stream
14536 $this->gradients
[$n]['stream'] = '';
14537 $count_patch = count($patch_array);
14538 for ($i=0; $i < $count_patch; ++
$i) {
14539 $this->gradients
[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
14540 $count_points = count($patch_array[$i]['points']);
14541 for ($j=0; $j < $count_points; ++
$j) {
14542 //each point as 16 bit
14543 $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
14544 if ($patch_array[$i]['points'][$j] < 0) {
14545 $patch_array[$i]['points'][$j] = 0;
14547 if ($patch_array[$i]['points'][$j] > $bpcd) {
14548 $patch_array[$i]['points'][$j] = $bpcd;
14550 $this->gradients
[$n]['stream'] .= chr((int) floor($patch_array[$i]['points'][$j] / 256));
14551 $this->gradients
[$n]['stream'] .= chr((int) floor(intval($patch_array[$i]['points'][$j]) %
256));
14553 $count_cols = count($patch_array[$i]['colors']);
14554 for ($j=0; $j < $count_cols; ++
$j) {
14555 //each color component as 8 bit
14556 $this->gradients
[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
14557 $this->gradients
[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
14558 $this->gradients
[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
14561 //paint the gradient
14562 $this->_out('/Sh'.$n.' sh');
14563 //restore previous Graphic State
14564 $this->_outRestoreGraphicsState();
14565 if ($this->inxobj
) {
14566 // we are inside an XObject template
14567 $this->xobjects
[$this->xobjid
]['gradients'][$n] = $this->gradients
[$n];
14572 * Set a rectangular clipping area.
14573 * @param float $x abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
14574 * @param float $y ordinate of the top left corner of the rectangle.
14575 * @param float $w width of the rectangle.
14576 * @param float $h height of the rectangle.
14577 * @author Andreas W\FCrmser, Nicola Asuni
14578 * @since 3.1.000 (2008-06-09)
14581 protected function Clip($x, $y, $w, $h) {
14582 if ($this->state
!= 2) {
14586 $x = $this->w
- $x - $w;
14588 //save current Graphic State
14590 //set clipping area
14591 $s .= sprintf(' %F %F %F %F re W n', $x*$this->k
, ($this->h
-$y)*$this->k
, $w*$this->k
, -$h*$this->k
);
14592 //set up transformation matrix for gradient
14593 $s .= sprintf(' %F 0 0 %F %F %F cm', $w*$this->k
, $h*$this->k
, $x*$this->k
, ($this->h
-($y+
$h))*$this->k
);
14599 * @param int $type 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)
14600 * @param array $coords array of coordinates.
14601 * @param array $stops 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).
14602 * @param array $background An array of colour components appropriate to the colour space, specifying a single background colour value.
14603 * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
14604 * @author Nicola Asuni
14605 * @since 3.1.000 (2008-06-09)
14608 public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14609 if (($this->pdfa_mode
&& $this->pdfa_version
< 2) OR ($this->state
!= 2)) {
14612 $n = count($this->gradients
) +
1;
14613 $this->gradients
[$n] = array();
14614 $this->gradients
[$n]['type'] = $type;
14615 $this->gradients
[$n]['coords'] = $coords;
14616 $this->gradients
[$n]['antialias'] = $antialias;
14617 $this->gradients
[$n]['colors'] = array();
14618 $this->gradients
[$n]['transparency'] = false;
14620 $numcolspace = count($stops[0]['color']);
14621 $bcolor = array_values($background);
14622 switch($numcolspace) {
14625 $this->gradients
[$n]['colspace'] = 'DeviceCMYK';
14626 if (!empty($background)) {
14627 $this->gradients
[$n]['background'] = sprintf('%F %F %F %F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
14632 $this->gradients
[$n]['colspace'] = 'DeviceRGB';
14633 if (!empty($background)) {
14634 $this->gradients
[$n]['background'] = sprintf('%F %F %F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
14638 case 1: { // GRAY SCALE
14639 $this->gradients
[$n]['colspace'] = 'DeviceGray';
14640 if (!empty($background)) {
14641 $this->gradients
[$n]['background'] = sprintf('%F', $bcolor[0]/255);
14646 $num_stops = count($stops);
14647 $last_stop_id = $num_stops - 1;
14648 foreach ($stops as $key => $stop) {
14649 $this->gradients
[$n]['colors'][$key] = array();
14650 // offset represents a location along the gradient vector
14651 if (isset($stop['offset'])) {
14652 $this->gradients
[$n]['colors'][$key]['offset'] = $stop['offset'];
14655 $this->gradients
[$n]['colors'][$key]['offset'] = 0;
14656 } elseif ($key == $last_stop_id) {
14657 $this->gradients
[$n]['colors'][$key]['offset'] = 1;
14659 $offsetstep = (1 - $this->gradients
[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
14660 $this->gradients
[$n]['colors'][$key]['offset'] = $this->gradients
[$n]['colors'][($key - 1)]['offset'] +
$offsetstep;
14663 if (isset($stop['opacity'])) {
14664 $this->gradients
[$n]['colors'][$key]['opacity'] = $stop['opacity'];
14665 if ((!($this->pdfa_mode
&& $this->pdfa_version
< 2)) AND ($stop['opacity'] < 1)) {
14666 $this->gradients
[$n]['transparency'] = true;
14669 $this->gradients
[$n]['colors'][$key]['opacity'] = 1;
14671 // exponent for the exponential interpolation function
14672 if (isset($stop['exponent'])) {
14673 $this->gradients
[$n]['colors'][$key]['exponent'] = $stop['exponent'];
14675 $this->gradients
[$n]['colors'][$key]['exponent'] = 1;
14678 $color = array_values($stop['color']);
14679 switch($numcolspace) {
14682 $this->gradients
[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14686 $this->gradients
[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14689 case 1: { // GRAY SCALE
14690 $this->gradients
[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14695 if ($this->gradients
[$n]['transparency']) {
14696 // paint luminosity gradient
14697 $this->_out('/TGS'.$n.' gs');
14699 //paint the gradient
14700 $this->_out('/Sh'.$n.' sh');
14701 //restore previous Graphic State
14702 $this->_outRestoreGraphicsState();
14703 if ($this->inxobj
) {
14704 // we are inside an XObject template
14705 $this->xobjects
[$this->xobjid
]['gradients'][$n] = $this->gradients
[$n];
14710 * Output gradient shaders.
14711 * @author Nicola Asuni
14712 * @since 3.1.000 (2008-06-09)
14715 function _putshaders() {
14716 if ($this->pdfa_mode
&& $this->pdfa_version
< 2) {
14719 $idt = count($this->gradients
); //index for transparency gradients
14720 foreach ($this->gradients
as $id => $grad) {
14721 if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
14722 $fc = $this->_newobj();
14724 $out .= ' /FunctionType 3';
14725 $out .= ' /Domain [0 1]';
14730 $num_cols = count($grad['colors']);
14731 $lastcols = $num_cols - 1;
14732 for ($i = 1; $i < $num_cols; ++
$i) {
14733 $functions .= ($fc +
$i).' 0 R ';
14734 if ($i < $lastcols) {
14735 $bounds .= sprintf('%F ', $grad['colors'][$i]['offset']);
14739 $out .= ' /Functions ['.trim($functions).']';
14740 $out .= ' /Bounds ['.trim($bounds).']';
14741 $out .= ' /Encode ['.trim($encode).']';
14743 $out .= "\n".'endobj';
14745 for ($i = 1; $i < $num_cols; ++
$i) {
14748 $out .= ' /FunctionType 2';
14749 $out .= ' /Domain [0 1]';
14750 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
14751 $out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
14752 $out .= ' /N '.$grad['colors'][$i]['exponent'];
14754 $out .= "\n".'endobj';
14757 // set transparency functions
14758 if ($grad['transparency']) {
14759 $ft = $this->_newobj();
14761 $out .= ' /FunctionType 3';
14762 $out .= ' /Domain [0 1]';
14765 $num_cols = count($grad['colors']);
14766 for ($i = 1; $i < $num_cols; ++
$i) {
14767 $functions .= ($ft +
$i).' 0 R ';
14769 $out .= ' /Functions ['.trim($functions).']';
14770 $out .= ' /Bounds ['.trim($bounds).']';
14771 $out .= ' /Encode ['.trim($encode).']';
14773 $out .= "\n".'endobj';
14775 for ($i = 1; $i < $num_cols; ++
$i) {
14778 $out .= ' /FunctionType 2';
14779 $out .= ' /Domain [0 1]';
14780 $out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
14781 $out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
14782 $out .= ' /N '.$grad['colors'][$i]['exponent'];
14784 $out .= "\n".'endobj';
14789 // set shading object
14791 $out = '<< /ShadingType '.$grad['type'];
14792 if (isset($grad['colspace'])) {
14793 $out .= ' /ColorSpace /'.$grad['colspace'];
14795 $out .= ' /ColorSpace /DeviceRGB';
14797 if (isset($grad['background']) AND !empty($grad['background'])) {
14798 $out .= ' /Background ['.$grad['background'].']';
14800 if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
14801 $out .= ' /AntiAlias true';
14803 if ($grad['type'] == 2) {
14804 $out .= ' '.sprintf('/Coords [%F %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
14805 $out .= ' /Domain [0 1]';
14806 $out .= ' /Function '.$fc.' 0 R';
14807 $out .= ' /Extend [true true]';
14809 } elseif ($grad['type'] == 3) {
14810 //x0, y0, r0, x1, y1, r1
14811 //at this this time radius of inner circle is 0
14812 $out .= ' '.sprintf('/Coords [%F %F 0 %F %F %F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
14813 $out .= ' /Domain [0 1]';
14814 $out .= ' /Function '.$fc.' 0 R';
14815 $out .= ' /Extend [true true]';
14817 } elseif ($grad['type'] == 6) {
14818 $out .= ' /BitsPerCoordinate 16';
14819 $out .= ' /BitsPerComponent 8';
14820 $out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
14821 $out .= ' /BitsPerFlag 8';
14822 $stream = $this->_getrawstream($grad['stream']);
14823 $out .= ' /Length '.strlen($stream);
14825 $out .= ' stream'."\n".$stream."\n".'endstream';
14827 $out .= "\n".'endobj';
14829 if ($grad['transparency']) {
14830 $shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
14831 $shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
14833 $this->gradients
[$id]['id'] = $this->n
;
14834 // set pattern object
14836 $out = '<< /Type /Pattern /PatternType 2';
14837 $out .= ' /Shading '.$this->gradients
[$id]['id'].' 0 R';
14839 $out .= "\n".'endobj';
14841 $this->gradients
[$id]['pattern'] = $this->n
;
14842 // set shading and pattern for transparency mask
14843 if ($grad['transparency']) {
14844 // luminosity pattern
14845 $idgs = $id +
$idt;
14847 $this->_out($shading_transparency);
14848 $this->gradients
[$idgs]['id'] = $this->n
;
14850 $out = '<< /Type /Pattern /PatternType 2';
14851 $out .= ' /Shading '.$this->gradients
[$idgs]['id'].' 0 R';
14853 $out .= "\n".'endobj';
14855 $this->gradients
[$idgs]['pattern'] = $this->n
;
14856 // luminosity XObject
14857 $oid = $this->_newobj();
14858 $this->xobjects
['LX'.$oid] = array('n' => $oid);
14860 $stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt
.' '.$this->hPt
.' re f Q';
14861 if ($this->compress
) {
14862 $filter = ' /Filter /FlateDecode';
14863 $stream = gzcompress($stream);
14865 $stream = $this->_getrawstream($stream);
14866 $out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
14867 $out .= ' /Length '.strlen($stream);
14868 $rect = sprintf('%F %F', $this->wPt
, $this->hPt
);
14869 $out .= ' /BBox [0 0 '.$rect.']';
14870 $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
14871 $out .= ' /Resources <<';
14872 $out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
14873 $out .= ' /Pattern << /p'.$idgs.' '.$this->gradients
[$idgs]['pattern'].' 0 R >>';
14876 $out .= ' stream'."\n".$stream."\n".'endstream';
14877 $out .= "\n".'endobj';
14881 $out = '<< /Type /Mask /S /Luminosity /G '.($this->n
- 1).' 0 R >>'."\n".'endobj';
14885 $out = '<< /Type /ExtGState /SMask '.($this->n
- 1).' 0 R /AIS false >>'."\n".'endobj';
14887 $this->extgstates
[] = array('n' => $this->n
, 'name' => 'TGS'.$id);
14893 * Draw the sector of a circle.
14894 * It can be used for instance to render pie charts.
14895 * @param float $xc abscissa of the center.
14896 * @param float $yc ordinate of the center.
14897 * @param float $r radius.
14898 * @param float $a start angle (in degrees).
14899 * @param float $b end angle (in degrees).
14900 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
14901 * @param float $cw indicates whether to go clockwise (default: true).
14902 * @param float $o origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90.
14903 * @author Maxime Delorme, Nicola Asuni
14904 * @since 3.1.000 (2008-06-09)
14907 public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
14908 $this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
14912 * Draw the sector of an ellipse.
14913 * It can be used for instance to render pie charts.
14914 * @param float $xc abscissa of the center.
14915 * @param float $yc ordinate of the center.
14916 * @param float $rx the x-axis radius.
14917 * @param float $ry the y-axis radius.
14918 * @param float $a start angle (in degrees).
14919 * @param float $b end angle (in degrees).
14920 * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
14921 * @param float $cw indicates whether to go clockwise.
14922 * @param float $o origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
14923 * @param integer $nc Number of curves used to draw a 90 degrees portion of arc.
14924 * @author Maxime Delorme, Nicola Asuni
14925 * @since 3.1.000 (2008-06-09)
14928 public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14929 if ($this->state
!= 2) {
14933 $xc = ($this->w
- $xc);
14935 $op = TCPDF_STATIC
::getPathPaintOperator($style);
14937 $line_style = array();
14941 $b = (360 - $a +
$o);
14942 $a = (360 - $d +
$o);
14947 $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
14952 * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
14953 * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
14954 * Only vector drawing is supported, not text or bitmap.
14955 * 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).
14956 * @param string $file Name of the file containing the image or a '@' character followed by the EPS/AI data string.
14957 * @param float|null $x Abscissa of the upper-left corner.
14958 * @param float|null $y Ordinate of the upper-left corner.
14959 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
14960 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
14961 * @param mixed $link URL or identifier returned by AddLink().
14962 * @param boolean $useBoundingBox specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
14963 * @param string $align 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>
14964 * @param string $palign 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>
14965 * @param mixed $border 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)))
14966 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
14967 * @param boolean $fixoutvals if true remove values outside the bounding box.
14968 * @author Valentin Schmidt, Nicola Asuni
14969 * @since 3.1.000 (2008-06-09)
14972 public function ImageEps($file, $x=null, $y=null, $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false, $fixoutvals=false) {
14973 if ($this->state
!= 2) {
14976 if ($this->rasterize_vector_images
AND ($w > 0) AND ($h > 0)) {
14977 // convert EPS to raster image using GD or ImageMagick libraries
14978 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
14980 if (TCPDF_STATIC
::empty_string($x)) {
14983 if (TCPDF_STATIC
::empty_string($y)) {
14986 // check page for no-write regions and adapt page margins if necessary
14987 list($x, $y) = $this->checkPageRegions($h, $x, $y);
14989 if ($file[0] === '@') { // image from string
14990 $data = substr($file, 1);
14991 } else { // EPS/AI file
14992 $data = $this->getCachedFileContents($file);
14994 if ($data === FALSE) {
14995 $this->Error('EPS file not found: '.$file);
14998 // EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
14999 preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
15000 if (count($regs) > 1) {
15001 $version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
15002 if (strpos($version_str, 'Adobe Illustrator') !== false) {
15003 $versexp = explode(' ', $version_str);
15004 $version = (float)array_pop($versexp);
15005 if ($version >= 9) {
15006 $this->Error('This version of Adobe Illustrator file is not supported: '.$file);
15010 // strip binary bytes in front of PS-header
15011 $start = strpos($data, '%!PS-Adobe');
15013 $data = substr($data, $start);
15015 // find BoundingBox params
15016 preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
15017 if (count($regs) > 1) {
15018 list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
15020 $this->Error('No BoundingBox found in EPS/AI file: '.$file);
15022 $start = strpos($data, '%%EndSetup');
15023 if ($start === false) {
15024 $start = strpos($data, '%%EndProlog');
15026 if ($start === false) {
15027 $start = strpos($data, '%%BoundingBox');
15029 $data = substr($data, $start);
15030 $end = strpos($data, '%%PageTrailer');
15031 if ($end===false) {
15032 $end = strpos($data, 'showpage');
15035 $data = substr($data, 0, $end);
15037 // calculate image width and height on document
15038 if (($w <= 0) AND ($h <= 0)) {
15039 $w = ($x2 - $x1) / $k;
15040 $h = ($y2 - $y1) / $k;
15041 } elseif ($w <= 0) {
15042 $w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
15043 } elseif ($h <= 0) {
15044 $h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
15046 // fit the image on available space
15047 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
15048 if ($this->rasterize_vector_images
) {
15049 // convert EPS to raster image using GD or ImageMagick libraries
15050 return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
15052 // set scaling factors
15053 $scale_x = $w / (($x2 - $x1) / $k);
15054 $scale_y = $h / (($y2 - $y1) / $k);
15056 $this->img_rb_y
= $y +
$h;
15059 if ($palign == 'L') {
15060 $ximg = $this->lMargin
;
15061 } elseif ($palign == 'C') {
15062 $ximg = ($this->w +
$this->lMargin
- $this->rMargin
- $w) / 2;
15063 } elseif ($palign == 'R') {
15064 $ximg = $this->w
- $this->rMargin
- $w;
15068 $this->img_rb_x
= $ximg;
15070 if ($palign == 'L') {
15071 $ximg = $this->lMargin
;
15072 } elseif ($palign == 'C') {
15073 $ximg = ($this->w +
$this->lMargin
- $this->rMargin
- $w) / 2;
15074 } elseif ($palign == 'R') {
15075 $ximg = $this->w
- $this->rMargin
- $w;
15079 $this->img_rb_x
= $ximg +
$w;
15081 if ($useBoundingBox) {
15082 $dx = $ximg * $k - $x1;
15083 $dy = $y * $k - $y1;
15088 // save the current graphic state
15089 $this->_out('q'.$this->epsmarker
);
15091 $this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy +
($this->hPt
- (2 * $y * $k) - ($y2 - $y1))));
15093 $this->_out(sprintf('%F %F %F %F %F %F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
15094 // handle pc/unix/mac line endings
15095 $lines = preg_split('/[\r\n]+/si', $data, -1, PREG_SPLIT_NO_EMPTY
);
15097 $cnt = count($lines);
15098 for ($i=0; $i < $cnt; ++
$i) {
15099 $line = $lines[$i];
15100 if (($line == '') OR ($line[0] == '%')) {
15103 $len = strlen($line);
15104 // check for spot color names
15106 if (strcasecmp('x', substr(trim($line), -1)) == 0) {
15107 if (preg_match('/\([^\)]*\)/', $line, $matches) > 0) {
15108 // extract spot color name
15109 $color_name = $matches[0];
15110 // remove color name from string
15111 $line = str_replace(' '.$color_name, '', $line);
15112 // remove pharentesis from color name
15113 $color_name = substr($color_name, 1, -1);
15116 $chunks = explode(' ', $line);
15117 $cmd = trim(array_pop($chunks));
15119 if (($cmd == 'Xa') OR ($cmd == 'XA')) {
15120 $b = array_pop($chunks);
15121 $g = array_pop($chunks);
15122 $r = array_pop($chunks);
15123 $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!
15128 // check for values outside the bounding box
15133 // skip values outside bounding box
15134 foreach ($chunks as $key => $val) {
15135 if ((($key %
2) == 0) AND (($val < $x1) OR ($val > $x2))) {
15137 } elseif ((($key %
2) != 0) AND (($val < $y1) OR ($val > $y2))) {
15165 $this->_out($line);
15168 case 'x': {// custom fill color
15169 if (empty($color_name)) {
15171 list($col_c, $col_m, $col_y, $col_k) = $chunks;
15172 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15174 // Spot Color (CMYK + tint)
15175 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15176 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15177 $color_cmd = sprintf('/CS%d cs %F scn', $this->spot_colors
[$color_name]['i'], (1 - $col_t));
15178 $this->_out($color_cmd);
15182 case 'X': { // custom stroke color
15183 if (empty($color_name)) {
15185 list($col_c, $col_m, $col_y, $col_k) = $chunks;
15186 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15188 // Spot Color (CMYK + tint)
15189 list($col_c, $col_m, $col_y, $col_k, $col_t) = $chunks;
15190 $this->AddSpotColor($color_name, ($col_c * 100), ($col_m * 100), ($col_y * 100), ($col_k * 100));
15191 $color_cmd = sprintf('/CS%d CS %F SCN', $this->spot_colors
[$color_name]['i'], (1 - $col_t));
15192 $this->_out($color_cmd);
15204 $line[($len - 1)] = strtolower($cmd);
15205 $this->_out($line);
15210 $this->_out($cmd . '*');
15217 $max = min(($i +
5), $cnt);
15218 for ($j = ($i +
1); $j < $max; ++
$j) {
15219 $isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
15239 // restore previous graphic state
15240 $this->_out($this->epsmarker
.'Q');
15241 if (!empty($border)) {
15249 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15254 $this->Link($ximg, $y, $w, $h, $link, 0);
15256 // set pointer to align the next text/objects
15260 $this->x
= $this->img_rb_x
;
15264 $this->y
= $y +
round($h/2);
15265 $this->x
= $this->img_rb_x
;
15269 $this->y
= $this->img_rb_y
;
15270 $this->x
= $this->img_rb_x
;
15274 $this->setY($this->img_rb_y
);
15281 $this->endlinex
= $this->img_rb_x
;
15285 * Set document barcode.
15286 * @param string $bc barcode
15289 public function setBarcode($bc='') {
15290 $this->barcode
= $bc;
15294 * Get current barcode.
15297 * @since 4.0.012 (2008-07-24)
15299 public function getBarcode() {
15300 return $this->barcode
;
15304 * Print a Linear Barcode.
15305 * @param string $code code to print
15306 * @param string $type type of barcode (see tcpdf_barcodes_1d.php for supported formats).
15307 * @param float|null $x x position in user units (null = current x position)
15308 * @param float|null $y y position in user units (null = current y position)
15309 * @param float|null $w width in user units (null = remaining page width)
15310 * @param float|null $h height in user units (null = remaining page height)
15311 * @param float|null $xres width of the smallest bar in user units (null = default value = 0.4mm)
15312 * @param array $style array of options:<ul>
15313 * <li>boolean $style['border'] if true prints a border</li>
15314 * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
15315 * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
15316 * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
15317 * <li>array $style['fgcolor'] color array for bars and text</li>
15318 * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
15319 * <li>boolean $style['text'] if true prints text below the barcode</li>
15320 * <li>string $style['label'] override default label</li>
15321 * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
15322 * <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>
15323 * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
15324 * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
15325 * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
15326 * <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>
15327 * <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>
15328 * @param string $align 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>
15329 * @author Nicola Asuni
15330 * @since 3.1.000 (2008-06-09)
15333 public function write1DBarcode($code, $type, $x=null, $y=null, $w=null, $h=null, $xres=null, $style=array(), $align='') {
15334 if (TCPDF_STATIC
::empty_string(trim($code))) {
15337 require_once(dirname(__FILE__
).'/tcpdf_barcodes_1d.php');
15338 // save current graphic settings
15339 $gvars = $this->getGraphicVars();
15340 // create new barcode object
15341 $barcodeobj = new TCPDFBarcode($code, $type);
15342 $arrcode = $barcodeobj->getBarcodeArray();
15343 if (empty($arrcode) OR ($arrcode['maxw'] <= 0)) {
15344 $this->Error('Error in 1D barcode string');
15346 if ($arrcode['maxh'] <= 0) {
15347 $arrcode['maxh'] = 1;
15349 // set default values
15350 if (!isset($style['position'])) {
15351 $style['position'] = '';
15352 } elseif ($style['position'] == 'S') {
15353 // keep this for backward compatibility
15354 $style['position'] = '';
15355 $style['stretch'] = true;
15357 if (!isset($style['fitwidth'])) {
15358 if (!isset($style['stretch'])) {
15359 $style['fitwidth'] = true;
15361 $style['fitwidth'] = false;
15364 if ($style['fitwidth']) {
15366 $style['stretch'] = false;
15368 if (!isset($style['stretch'])) {
15369 if (($w === '') OR ($w <= 0)) {
15370 $style['stretch'] = false;
15372 $style['stretch'] = true;
15375 if (!isset($style['fgcolor'])) {
15376 $style['fgcolor'] = array(0,0,0); // default black
15378 if (!isset($style['bgcolor'])) {
15379 $style['bgcolor'] = false; // default transparent
15381 if (!isset($style['border'])) {
15382 $style['border'] = false;
15385 if (!isset($style['text'])) {
15386 $style['text'] = false;
15388 if ($style['text'] AND isset($style['font'])) {
15389 if (isset($style['fontsize'])) {
15390 $fontsize = $style['fontsize'];
15392 $this->setFont($style['font'], '', $fontsize);
15394 if (!isset($style['stretchtext'])) {
15395 $style['stretchtext'] = 4;
15397 if (TCPDF_STATIC
::empty_string($x)) {
15400 if (TCPDF_STATIC
::empty_string($y)) {
15403 // check page for no-write regions and adapt page margins if necessary
15404 list($x, $y) = $this->checkPageRegions($h, $x, $y);
15405 if (TCPDF_STATIC
::empty_string($w) OR ($w <= 0)) {
15407 $w = $x - $this->lMargin
;
15409 $w = $this->w
- $this->rMargin
- $x;
15413 if (!isset($style['padding'])) {
15415 } elseif ($style['padding'] === 'auto') {
15416 $padding = 10 * ($w / ($arrcode['maxw'] +
20));
15418 $padding = floatval($style['padding']);
15420 // horizontal padding
15421 if (!isset($style['hpadding'])) {
15422 $hpadding = $padding;
15423 } elseif ($style['hpadding'] === 'auto') {
15424 $hpadding = 10 * ($w / ($arrcode['maxw'] +
20));
15426 $hpadding = floatval($style['hpadding']);
15428 // vertical padding
15429 if (!isset($style['vpadding'])) {
15430 $vpadding = $padding;
15431 } elseif ($style['vpadding'] === 'auto') {
15432 $vpadding = ($hpadding / 2);
15434 $vpadding = floatval($style['vpadding']);
15436 // calculate xres (single bar width)
15437 $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15438 if ($style['stretch']) {
15441 if (TCPDF_STATIC
::empty_string($xres)) {
15442 $xres = (0.141 * $this->k
); // default bar width = 0.4 mm
15444 if ($xres > $max_xres) {
15445 // correct xres to fit on $w
15448 if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
15449 OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
15450 $hpadding = 10 * $xres;
15451 if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
15452 $vpadding = ($hpadding / 2);
15456 if ($style['fitwidth']) {
15458 $w = (($arrcode['maxw'] * $xres) +
(2 * $hpadding));
15459 if (isset($style['cellfitalign'])) {
15460 switch ($style['cellfitalign']) {
15463 $x -= ($wold - $w);
15469 $x +
= ($wold - $w);
15475 $x -= (($wold - $w) / 2);
15477 $x +
= (($wold - $w) / 2);
15487 $text_height = $this->getCellHeight($fontsize / $this->k
);
15489 if (TCPDF_STATIC
::empty_string($h) OR ($h <= 0)) {
15490 // set default height
15491 $h = (($arrcode['maxw'] * $xres) / 3) +
(2 * $vpadding) +
$text_height;
15493 $barh = $h - $text_height - (2 * $vpadding);
15495 // try to reduce font or padding to fit barcode on available height
15496 if ($text_height > $h) {
15497 $fontsize = (($h * $this->k
) / (4 * $this->cell_height_ratio
));
15498 $text_height = $this->getCellHeight($fontsize / $this->k
);
15499 $this->setFont($style['font'], '', $fontsize);
15501 if ($vpadding > 0) {
15502 $vpadding = (($h - $text_height) / 4);
15504 $barh = $h - $text_height - (2 * $vpadding);
15506 // fit the barcode on available space
15507 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15509 $this->img_rb_y
= $y +
$h;
15512 if ($style['position'] == 'L') {
15513 $xpos = $this->lMargin
;
15514 } elseif ($style['position'] == 'C') {
15515 $xpos = ($this->w +
$this->lMargin
- $this->rMargin
- $w) / 2;
15516 } elseif ($style['position'] == 'R') {
15517 $xpos = $this->w
- $this->rMargin
- $w;
15521 $this->img_rb_x
= $xpos;
15523 if ($style['position'] == 'L') {
15524 $xpos = $this->lMargin
;
15525 } elseif ($style['position'] == 'C') {
15526 $xpos = ($this->w +
$this->lMargin
- $this->rMargin
- $w) / 2;
15527 } elseif ($style['position'] == 'R') {
15528 $xpos = $this->w
- $this->rMargin
- $w;
15532 $this->img_rb_x
= $xpos +
$w;
15534 $xpos_rect = $xpos;
15535 if (!isset($style['align'])) {
15536 $style['align'] = 'C';
15538 switch ($style['align']) {
15540 $xpos = $xpos_rect +
$hpadding;
15544 $xpos = $xpos_rect +
($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15549 $xpos = $xpos_rect +
(($w - ($arrcode['maxw'] * $xres)) / 2);
15553 $xpos_text = $xpos;
15554 // barcode is always printed in LTR direction
15555 $tempRTL = $this->rtl
;
15556 $this->rtl
= false;
15557 // print background color
15558 if ($style['bgcolor']) {
15559 $this->Rect($xpos_rect, $y, $w, $h, $style['border'] ?
'DF' : 'F', '', $style['bgcolor']);
15560 } elseif ($style['border']) {
15561 $this->Rect($xpos_rect, $y, $w, $h, 'D');
15563 // set foreground color
15564 $this->setDrawColorArray($style['fgcolor']);
15565 $this->setTextColorArray($style['fgcolor']);
15567 foreach ($arrcode['bcode'] as $k => $v) {
15568 $bw = ($v['w'] * $xres);
15570 // draw a vertical bar
15571 $ypos = $y +
$vpadding +
($v['p'] * $barh / $arrcode['maxh']);
15572 $this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
15577 if ($style['text']) {
15578 if (isset($style['label']) AND !TCPDF_STATIC
::empty_string($style['label'])) {
15579 $label = $style['label'];
15583 $txtwidth = ($arrcode['maxw'] * $xres);
15584 if ($this->GetStringWidth($label) > $txtwidth) {
15585 $style['stretchtext'] = 2;
15588 $this->x
= $xpos_text;
15589 $this->y
= $y +
$vpadding +
$barh;
15590 $cellpadding = $this->cell_padding
;
15591 $this->setCellPadding(0);
15592 $this->Cell($txtwidth, 0, $label, 0, 0, 'C', false, '', $style['stretchtext'], false, 'T', 'T');
15593 $this->cell_padding
= $cellpadding;
15595 // restore original direction
15596 $this->rtl
= $tempRTL;
15597 // restore previous settings
15598 $this->setGraphicVars($gvars);
15599 // set pointer to align the next text/objects
15603 $this->x
= $this->img_rb_x
;
15607 $this->y
= $y +
round($h / 2);
15608 $this->x
= $this->img_rb_x
;
15612 $this->y
= $this->img_rb_y
;
15613 $this->x
= $this->img_rb_x
;
15617 $this->setY($this->img_rb_y
);
15624 $this->endlinex
= $this->img_rb_x
;
15628 * Print 2D Barcode.
15629 * @param string $code code to print
15630 * @param string $type type of barcode (see tcpdf_barcodes_2d.php for supported formats).
15631 * @param float|null $x x position in user units
15632 * @param float|null $y y position in user units
15633 * @param float|null $w width in user units
15634 * @param float|null $h height in user units
15635 * @param array $style array of options:<ul>
15636 * <li>boolean $style['border'] if true prints a border around the barcode</li>
15637 * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
15638 * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
15639 * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
15640 * <li>int $style['module_width'] width of a single module in points</li>
15641 * <li>int $style['module_height'] height of a single module in points</li>
15642 * <li>array $style['fgcolor'] color array for bars and text</li>
15643 * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
15644 * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li>
15645 * @param string $align 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>
15646 * @param boolean $distort if true distort the barcode to fit width and height, otherwise preserve aspect ratio
15647 * @author Nicola Asuni
15648 * @since 4.5.037 (2009-04-07)
15651 public function write2DBarcode($code, $type, $x=null, $y=null, $w=null, $h=null, $style=array(), $align='', $distort=false) {
15652 if (TCPDF_STATIC
::empty_string(trim($code))) {
15655 require_once(dirname(__FILE__
).'/tcpdf_barcodes_2d.php');
15656 // save current graphic settings
15657 $gvars = $this->getGraphicVars();
15658 // create new barcode object
15659 $barcodeobj = new TCPDF2DBarcode($code, $type);
15660 $arrcode = $barcodeobj->getBarcodeArray();
15661 if (empty($arrcode) OR !isset($arrcode['num_rows']) OR ($arrcode['num_rows'] == 0) OR !isset($arrcode['num_cols']) OR ($arrcode['num_cols'] == 0)) {
15662 $this->Error('Error in 2D barcode string');
15664 // set default values
15665 if (!isset($style['position'])) {
15666 $style['position'] = '';
15668 if (!isset($style['fgcolor'])) {
15669 $style['fgcolor'] = array(0,0,0); // default black
15671 if (!isset($style['bgcolor'])) {
15672 $style['bgcolor'] = false; // default transparent
15674 if (!isset($style['border'])) {
15675 $style['border'] = false;
15678 if (!isset($style['padding'])) {
15679 $style['padding'] = 0;
15680 } elseif ($style['padding'] === 'auto') {
15681 $style['padding'] = 4;
15683 if (!isset($style['hpadding'])) {
15684 $style['hpadding'] = $style['padding'];
15685 } elseif ($style['hpadding'] === 'auto') {
15686 $style['hpadding'] = 4;
15688 if (!isset($style['vpadding'])) {
15689 $style['vpadding'] = $style['padding'];
15690 } elseif ($style['vpadding'] === 'auto') {
15691 $style['vpadding'] = 4;
15693 $hpad = (2 * $style['hpadding']);
15694 $vpad = (2 * $style['vpadding']);
15695 // cell (module) dimension
15696 if (!isset($style['module_width'])) {
15697 $style['module_width'] = 1; // width of a single module in points
15699 if (!isset($style['module_height'])) {
15700 $style['module_height'] = 1; // height of a single module in points
15702 if (TCPDF_STATIC
::empty_string($x)) {
15705 if (TCPDF_STATIC
::empty_string($y)) {
15708 // check page for no-write regions and adapt page margins if necessary
15709 list($x, $y) = $this->checkPageRegions($h, $x, $y);
15710 // number of barcode columns and rows
15711 $rows = $arrcode['num_rows'];
15712 $cols = $arrcode['num_cols'];
15713 if (($rows <= 0) ||
($cols <= 0)){
15714 $this->Error('Error in 2D barcode string');
15716 // module width and height
15717 $mw = $style['module_width'];
15718 $mh = $style['module_height'];
15719 if (($mw <= 0) OR ($mh <= 0)) {
15720 $this->Error('Error in 2D barcode string');
15722 // get max dimensions
15724 $maxw = $x - $this->lMargin
;
15726 $maxw = $this->w
- $this->rMargin
- $x;
15728 $maxh = ($this->h
- $this->tMargin
- $this->bMargin
);
15729 $ratioHW = ((($rows * $mh) +
$hpad) / (($cols * $mw) +
$vpad));
15730 $ratioWH = ((($cols * $mw) +
$vpad) / (($rows * $mh) +
$hpad));
15732 if (($maxw * $ratioHW) > $maxh) {
15733 $maxw = $maxh * $ratioWH;
15735 if (($maxh * $ratioWH) > $maxw) {
15736 $maxh = $maxw * $ratioHW;
15739 // set maximum dimensions
15747 if ((TCPDF_STATIC
::empty_string($w) OR ($w <= 0)) AND (TCPDF_STATIC
::empty_string($h) OR ($h <= 0))) {
15748 $w = ($cols +
$hpad) * ($mw / $this->k
);
15749 $h = ($rows +
$vpad) * ($mh / $this->k
);
15750 } elseif (($w === '') OR ($w <= 0)) {
15751 $w = $h * $ratioWH;
15752 } elseif (($h === '') OR ($h <= 0)) {
15753 $h = $w * $ratioHW;
15755 // barcode size (excluding padding)
15756 $bw = ($w * $cols) / ($cols +
$hpad);
15757 $bh = ($h * $rows) / ($rows +
$vpad);
15758 // dimension of single barcode cell unit
15762 if (($cw / $ch) > ($mw / $mh)) {
15763 // correct horizontal distortion
15764 $cw = $ch * $mw / $mh;
15766 $style['hpadding'] = ($w - $bw) / (2 * $cw);
15768 // correct vertical distortion
15769 $ch = $cw * $mh / $mw;
15771 $style['vpadding'] = ($h - $bh) / (2 * $ch);
15774 // fit the barcode on available space
15775 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, false);
15777 $this->img_rb_y
= $y +
$h;
15780 if ($style['position'] == 'L') {
15781 $xpos = $this->lMargin
;
15782 } elseif ($style['position'] == 'C') {
15783 $xpos = ($this->w +
$this->lMargin
- $this->rMargin
- $w) / 2;
15784 } elseif ($style['position'] == 'R') {
15785 $xpos = $this->w
- $this->rMargin
- $w;
15789 $this->img_rb_x
= $xpos;
15791 if ($style['position'] == 'L') {
15792 $xpos = $this->lMargin
;
15793 } elseif ($style['position'] == 'C') {
15794 $xpos = ($this->w +
$this->lMargin
- $this->rMargin
- $w) / 2;
15795 } elseif ($style['position'] == 'R') {
15796 $xpos = $this->w
- $this->rMargin
- $w;
15800 $this->img_rb_x
= $xpos +
$w;
15802 $xstart = $xpos +
($style['hpadding'] * $cw);
15803 $ystart = $y +
($style['vpadding'] * $ch);
15804 // barcode is always printed in LTR direction
15805 $tempRTL = $this->rtl
;
15806 $this->rtl
= false;
15807 // print background color
15808 if ($style['bgcolor']) {
15809 $this->Rect($xpos, $y, $w, $h, $style['border'] ?
'DF' : 'F', '', $style['bgcolor']);
15810 } elseif ($style['border']) {
15811 $this->Rect($xpos, $y, $w, $h, 'D');
15813 // set foreground color
15814 $this->setDrawColorArray($style['fgcolor']);
15815 // print barcode cells
15817 for ($r = 0; $r < $rows; ++
$r) {
15820 for ($c = 0; $c < $cols; ++
$c) {
15821 if ($arrcode['bcode'][$r][$c] == 1) {
15822 // draw a single barcode cell
15823 $this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
15829 // restore original direction
15830 $this->rtl
= $tempRTL;
15831 // restore previous settings
15832 $this->setGraphicVars($gvars);
15833 // set pointer to align the next text/objects
15837 $this->x
= $this->img_rb_x
;
15841 $this->y
= $y +
round($h/2);
15842 $this->x
= $this->img_rb_x
;
15846 $this->y
= $this->img_rb_y
;
15847 $this->x
= $this->img_rb_x
;
15851 $this->setY($this->img_rb_y
);
15858 $this->endlinex
= $this->img_rb_x
;
15862 * Returns an array containing current margins:
15864 <li>$ret['left'] = left margin</li>
15865 <li>$ret['right'] = right margin</li>
15866 <li>$ret['top'] = top margin</li>
15867 <li>$ret['bottom'] = bottom margin</li>
15868 <li>$ret['header'] = header margin</li>
15869 <li>$ret['footer'] = footer margin</li>
15870 <li>$ret['cell'] = cell padding array</li>
15871 <li>$ret['padding_left'] = cell left padding</li>
15872 <li>$ret['padding_top'] = cell top padding</li>
15873 <li>$ret['padding_right'] = cell right padding</li>
15874 <li>$ret['padding_bottom'] = cell bottom padding</li>
15876 * @return array containing all margins measures
15878 * @since 3.2.000 (2008-06-23)
15880 public function getMargins() {
15882 'left' => $this->lMargin
,
15883 'right' => $this->rMargin
,
15884 'top' => $this->tMargin
,
15885 'bottom' => $this->bMargin
,
15886 'header' => $this->header_margin
,
15887 'footer' => $this->footer_margin
,
15888 'cell' => $this->cell_padding
,
15889 'padding_left' => $this->cell_padding
['L'],
15890 'padding_top' => $this->cell_padding
['T'],
15891 'padding_right' => $this->cell_padding
['R'],
15892 'padding_bottom' => $this->cell_padding
['B']
15898 * Returns an array containing original margins:
15900 <li>$ret['left'] = left margin</li>
15901 <li>$ret['right'] = right margin</li>
15903 * @return array containing all margins measures
15905 * @since 4.0.012 (2008-07-24)
15907 public function getOriginalMargins() {
15909 'left' => $this->original_lMargin
,
15910 'right' => $this->original_rMargin
15916 * Returns the current font size.
15917 * @return float current font size
15919 * @since 3.2.000 (2008-06-23)
15921 public function getFontSize() {
15922 return $this->FontSize
;
15926 * Returns the current font size in points unit.
15927 * @return int current font size in points unit
15929 * @since 3.2.000 (2008-06-23)
15931 public function getFontSizePt() {
15932 return $this->FontSizePt
;
15936 * Returns the current font family name.
15937 * @return string current font family name
15939 * @since 4.3.008 (2008-12-05)
15941 public function getFontFamily() {
15942 return $this->FontFamily
;
15946 * Returns the current font style.
15947 * @return string current font style
15949 * @since 4.3.008 (2008-12-05)
15951 public function getFontStyle() {
15952 return $this->FontStyle
;
15956 * Cleanup HTML code (requires HTML Tidy library).
15957 * @param string $html htmlcode to fix
15958 * @param string $default_css CSS commands to add
15959 * @param array|null $tagvs parameters for setHtmlVSpace method
15960 * @param array|null $tidy_options options for tidy_parse_string function
15961 * @return string XHTML code cleaned up
15962 * @author Nicola Asuni
15964 * @since 5.9.017 (2010-11-16)
15965 * @see setHtmlVSpace()
15967 public function fixHTMLCode($html, $default_css='', $tagvs=null, $tidy_options=null) {
15968 return TCPDF_STATIC
::fixHTMLCode($html, $default_css, $tagvs, $tidy_options, $this->tagvspaces
);
15972 * Returns the border width from CSS property
15973 * @param string $width border width
15974 * @return int with in user units
15976 * @since 5.7.000 (2010-08-02)
15978 protected function getCSSBorderWidth($width) {
15979 if ($width == 'thin') {
15980 $width = (2 / $this->k
);
15981 } elseif ($width == 'medium') {
15982 $width = (4 / $this->k
);
15983 } elseif ($width == 'thick') {
15984 $width = (6 / $this->k
);
15986 $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
15992 * Returns the border dash style from CSS property
15993 * @param string $style border style to convert
15994 * @return int sash style (return -1 in case of none or hidden border)
15996 * @since 5.7.000 (2010-08-02)
15998 protected function getCSSBorderDashStyle($style) {
15999 switch (strtolower($style)) {
16028 * Returns the border style array from CSS border properties
16029 * @param string $cssborder border properties
16030 * @return array containing border properties
16032 * @since 5.7.000 (2010-08-02)
16034 protected function getCSSBorderStyle($cssborder) {
16035 $bprop = preg_split('/[\s]+/', trim($cssborder));
16036 $count = count($bprop);
16037 if ($count > 0 && $bprop[$count - 1] === '!important') {
16038 unset($bprop[$count - 1]);
16042 $border = array(); // value to be returned
16046 $style = $bprop[0];
16047 $color = $bprop[1];
16052 $style = $bprop[0];
16063 $width = $bprop[0];
16064 $style = $bprop[1];
16065 $color = $bprop[2];
16069 if ($style == 'none') {
16072 $border['cap'] = 'square';
16073 $border['join'] = 'miter';
16074 $border['dash'] = $this->getCSSBorderDashStyle($style);
16075 if ($border['dash'] < 0) {
16078 $border['width'] = $this->getCSSBorderWidth($width);
16079 $border['color'] = TCPDF_COLORS
::convertHTMLColorToDec($color, $this->spot_colors
);
16084 * Get the internal Cell padding from CSS attribute.
16085 * @param string $csspadding padding properties
16086 * @param float $width width of the containing element
16087 * @return array of cell paddings
16089 * @since 5.9.000 (2010-10-04)
16091 public function getCSSPadding($csspadding, $width=0) {
16092 $padding = preg_split('/[\s]+/', trim($csspadding));
16093 $cell_padding = array(); // value to be returned
16094 switch (count($padding)) {
16096 $cell_padding['T'] = $padding[0];
16097 $cell_padding['R'] = $padding[1];
16098 $cell_padding['B'] = $padding[2];
16099 $cell_padding['L'] = $padding[3];
16103 $cell_padding['T'] = $padding[0];
16104 $cell_padding['R'] = $padding[1];
16105 $cell_padding['B'] = $padding[2];
16106 $cell_padding['L'] = $padding[1];
16110 $cell_padding['T'] = $padding[0];
16111 $cell_padding['R'] = $padding[1];
16112 $cell_padding['B'] = $padding[0];
16113 $cell_padding['L'] = $padding[1];
16117 $cell_padding['T'] = $padding[0];
16118 $cell_padding['R'] = $padding[0];
16119 $cell_padding['B'] = $padding[0];
16120 $cell_padding['L'] = $padding[0];
16124 return $this->cell_padding
;
16128 $width = $this->w
- $this->lMargin
- $this->rMargin
;
16130 $cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
16131 $cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
16132 $cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
16133 $cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
16134 return $cell_padding;
16138 * Get the internal Cell margin from CSS attribute.
16139 * @param string $cssmargin margin properties
16140 * @param float $width width of the containing element
16141 * @return array of cell margins
16143 * @since 5.9.000 (2010-10-04)
16145 public function getCSSMargin($cssmargin, $width=0) {
16146 $margin = preg_split('/[\s]+/', trim($cssmargin));
16147 $cell_margin = array(); // value to be returned
16148 switch (count($margin)) {
16150 $cell_margin['T'] = $margin[0];
16151 $cell_margin['R'] = $margin[1];
16152 $cell_margin['B'] = $margin[2];
16153 $cell_margin['L'] = $margin[3];
16157 $cell_margin['T'] = $margin[0];
16158 $cell_margin['R'] = $margin[1];
16159 $cell_margin['B'] = $margin[2];
16160 $cell_margin['L'] = $margin[1];
16164 $cell_margin['T'] = $margin[0];
16165 $cell_margin['R'] = $margin[1];
16166 $cell_margin['B'] = $margin[0];
16167 $cell_margin['L'] = $margin[1];
16171 $cell_margin['T'] = $margin[0];
16172 $cell_margin['R'] = $margin[0];
16173 $cell_margin['B'] = $margin[0];
16174 $cell_margin['L'] = $margin[0];
16178 return $this->cell_margin
;
16182 $width = $this->w
- $this->lMargin
- $this->rMargin
;
16184 $cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
16185 $cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
16186 $cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
16187 $cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
16188 return $cell_margin;
16192 * Get the border-spacing from CSS attribute.
16193 * @param string $cssbspace border-spacing CSS properties
16194 * @param float $width width of the containing element
16195 * @return array of border spacings
16197 * @since 5.9.010 (2010-10-27)
16199 public function getCSSBorderMargin($cssbspace, $width=0) {
16200 $space = preg_split('/[\s]+/', trim($cssbspace));
16201 $border_spacing = array(); // value to be returned
16202 switch (count($space)) {
16204 $border_spacing['H'] = $space[0];
16205 $border_spacing['V'] = $space[1];
16209 $border_spacing['H'] = $space[0];
16210 $border_spacing['V'] = $space[0];
16214 return array('H' => 0, 'V' => 0);
16218 $width = $this->w
- $this->lMargin
- $this->rMargin
;
16220 $border_spacing['H'] = $this->getHTMLUnitToUnits($border_spacing['H'], $width, 'px', false);
16221 $border_spacing['V'] = $this->getHTMLUnitToUnits($border_spacing['V'], $width, 'px', false);
16222 return $border_spacing;
16226 * Returns the letter-spacing value from CSS value
16227 * @param string $spacing letter-spacing value
16228 * @param float $parent font spacing (tracking) value of the parent element
16229 * @return float quantity to increases or decreases the space between characters in a text.
16231 * @since 5.9.000 (2010-10-02)
16233 protected function getCSSFontSpacing($spacing, $parent=0) {
16234 $val = 0; // value to be returned
16235 $spacing = trim($spacing);
16236 switch ($spacing) {
16242 if ($parent == 'normal') {
16250 $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16257 * Returns the percentage of font stretching from CSS value
16258 * @param string $stretch stretch mode
16259 * @param float $parent stretch value of the parent element
16260 * @return float font stretching percentage
16262 * @since 5.9.000 (2010-10-02)
16264 protected function getCSSFontStretching($stretch, $parent=100) {
16265 $val = 100; // value to be returned
16266 $stretch = trim($stretch);
16267 switch ($stretch) {
16268 case 'ultra-condensed': {
16272 case 'extra-condensed': {
16276 case 'condensed': {
16280 case 'semi-condensed': {
16288 case 'semi-expanded': {
16296 case 'extra-expanded': {
16300 case 'ultra-expanded': {
16305 $val = ($parent +
10);
16309 $val = ($parent - 10);
16313 if ($parent == 'normal') {
16321 $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16328 * Convert HTML string containing font size value to points
16329 * @param string $val String containing font size value and unit.
16330 * @param float $refsize Reference font size in points.
16331 * @param float $parent_size Parent font size in points.
16332 * @param string $defaultunit Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
16333 * @return float value in points
16336 public function getHTMLFontUnits($val, $refsize=12, $parent_size=12, $defaultunit='pt') {
16337 $refsize = TCPDF_FONTS
::getFontRefSize($refsize);
16338 $parent_size = TCPDF_FONTS
::getFontRefSize($parent_size, $refsize);
16341 $size = ($refsize - 4);
16345 $size = ($refsize - 3);
16349 $size = ($refsize - 2);
16357 $size = ($refsize +
2);
16361 $size = ($refsize +
4);
16365 $size = ($refsize +
6);
16369 $size = ($parent_size - 3);
16373 $size = ($parent_size +
3);
16377 $parentSize = $this->getHTMLUnitToUnits($parent_size, $refsize, $defaultunit, true);
16378 $size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16385 * Returns the HTML DOM array.
16386 * @param string $html html code
16389 * @since 3.2.000 (2008-06-20)
16391 protected function getHtmlDomArray($html) {
16392 // array of CSS styles ( selector => properties).
16394 // get CSS array defined at previous call
16395 $matches = array();
16396 if (preg_match_all('/<cssarray>([^\<]*?)<\/cssarray>/is', $html, $matches) > 0) {
16397 if (isset($matches[1][0])) {
16398 $css = array_merge($css, json_decode($this->unhtmlentities($matches[1][0]), true));
16400 $html = preg_replace('/<cssarray>(.*?)<\/cssarray>/is', '', $html);
16402 // extract external CSS files
16403 $matches = array();
16404 if (preg_match_all('/<link([^\>]*?)>/is', $html, $matches) > 0) {
16405 foreach ($matches[1] as $key => $link) {
16407 if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16409 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
16410 // get 'all' and 'print' media, other media types are discarded
16411 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16412 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16414 if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
16415 // read CSS data file
16416 $cssdata = $this->getCachedFileContents(trim($type[1]));
16417 if (($cssdata !== FALSE) AND (strlen($cssdata) > 0)) {
16418 $css = array_merge($css, TCPDF_STATIC
::extractCSSproperties($cssdata));
16425 // extract style tags
16426 $matches = array();
16427 if (preg_match_all('/<style([^\>]*?)>([^\<]*?)<\/style>/is', $html, $matches) > 0) {
16428 foreach ($matches[1] as $key => $media) {
16430 preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
16431 // get 'all' and 'print' media, other media types are discarded
16432 // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
16433 if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
16434 $cssdata = $matches[2][$key];
16435 $css = array_merge($css, TCPDF_STATIC
::extractCSSproperties($cssdata));
16439 // create a special tag to contain the CSS array (used for table content)
16440 $csstagarray = '<cssarray>'.htmlentities(json_encode($css)).'</cssarray>';
16441 // remove head and style blocks
16442 $html = preg_replace('/<head([^\>]*?)>(.*?)<\/head>/is', '', $html);
16443 $html = preg_replace('/<style([^\>]*?)>([^\<]*?)<\/style>/is', '', $html);
16444 // define block tags
16445 $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
16446 // define self-closing tags
16447 $selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
16448 // remove all unsupported tags (the line below lists all supported tags)
16449 $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>');
16450 //replace some blank characters
16451 $html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
16452 $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);
16453 $html = preg_replace('@(\r\n|\r)@', "\n", $html);
16454 $repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
16455 $html = strtr($html, $repTable);
16457 while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
16458 $html_a = substr($html, 0, $offset);
16459 $html_b = substr($html, $offset, ($pos - $offset +
6));
16460 while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
16461 // preserve newlines on <pre> tag
16462 $html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
16464 while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space
['p']."(.*?)</pre>'".$this->re_space
['m'], $html_b)) {
16465 // preserve spaces on <pre> tag
16466 $html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space
['p']."(.*?)</pre>'".$this->re_space
['m'], "<xre\\1>\\2 \\3</pre>", $html_b);
16468 $html = $html_a.$html_b.substr($html, $pos +
6);
16469 $offset = strlen($html_a.$html_b);
16472 while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
16473 $html_a = substr($html, 0, $offset);
16474 $html_b = substr($html, $offset, ($pos - $offset +
11));
16475 while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
16476 // preserve newlines on <textarea> tag
16477 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
16478 $html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
16480 $html = $html_a.$html_b.substr($html, $pos +
11);
16481 $offset = strlen($html_a.$html_b);
16483 $html = preg_replace('/([\s]*)<option/si', '<option', $html);
16484 $html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
16486 while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
16487 $html_a = substr($html, 0, $offset);
16488 $html_b = substr($html, $offset, ($pos - $offset +
9));
16489 while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
16490 $html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
16491 $html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
16493 $html = $html_a.$html_b.substr($html, $pos +
9);
16494 $offset = strlen($html_a.$html_b);
16496 if (preg_match("'</select'si", $html)) {
16497 $html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
16498 $html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
16500 $html = str_replace("\n", ' ', $html);
16501 // restore textarea newlines
16502 $html = str_replace('<TBR>', "\n", $html);
16503 // remove extra spaces from code
16504 $html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
16505 $html = preg_replace('/'.$this->re_space
['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space
['m'], '</\\1>', $html);
16506 $html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
16507 $html = preg_replace('/'.$this->re_space
['p'].'+<(ul|ol|dl|br)/'.$this->re_space
['m'], '<\\1', $html);
16508 $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);
16509 $html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
16510 $html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
16511 $html = preg_replace('/'.$this->re_space
['p'].'+<img/'.$this->re_space
['m'], chr(32).'<img', $html);
16512 $html = preg_replace('/<img([^\>]*)>[\s]+([^\<])/xi', '<img\\1> \\2', $html);
16513 $html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
16514 $html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
16515 $html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
16516 $html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1> </li>', $html);
16517 $html = preg_replace('/<li([^\>]*)>'.$this->re_space
['p'].'*<img/'.$this->re_space
['m'], '<li\\1><font size="1"> </font><img', $html);
16518 $html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1> ', $html); // preserve some spaces
16519 $html = preg_replace('/[\s]<\/([^\>]*)>/', ' </\\1>', $html); // preserve some spaces
16520 $html = preg_replace('/<su([bp])/', '<zws/><su\\1', $html); // fix sub/sup alignment
16521 $html = preg_replace('/<\/su([bp])>/', '</su\\1><zws/>', $html); // fix sub/sup alignment
16522 $html = preg_replace('/'.$this->re_space
['p'].'+/'.$this->re_space
['m'], chr(32), $html); // replace multiple spaces with a single space
16524 $html = $this->stringTrim($html);
16525 // fix br tag after li
16526 $html = preg_replace('/<li><br([^\>]*)>/', '<li> <br\\1>', $html);
16527 // fix first image tag alignment
16528 $html = preg_replace('/^<img/', '<span style="font-size:0"><br /></span> <img', $html, 1);
16529 // pattern for generic tag
16530 $tagpattern = '/(<[^>]+>)/';
16531 // explodes the string
16532 $a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
);
16534 $maxel = count($a);
16537 // create an array of elements
16539 $dom[$key] = array();
16540 // set inheritable properties fot the first void element
16541 // 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
16542 $dom[$key]['tag'] = false;
16543 $dom[$key]['block'] = false;
16544 $dom[$key]['value'] = '';
16545 $dom[$key]['parent'] = 0;
16546 $dom[$key]['hide'] = false;
16547 $dom[$key]['fontname'] = $this->FontFamily
;
16548 $dom[$key]['fontstyle'] = $this->FontStyle
;
16549 $dom[$key]['fontsize'] = $this->FontSizePt
;
16550 $dom[$key]['font-stretch'] = $this->font_stretching
;
16551 $dom[$key]['letter-spacing'] = $this->font_spacing
;
16552 $dom[$key]['stroke'] = $this->textstrokewidth
;
16553 $dom[$key]['fill'] = (($this->textrendermode %
2) == 0);
16554 $dom[$key]['clip'] = ($this->textrendermode
> 3);
16555 $dom[$key]['line-height'] = $this->cell_height_ratio
;
16556 $dom[$key]['bgcolor'] = false;
16557 $dom[$key]['fgcolor'] = $this->fgcolor
; // color
16558 $dom[$key]['strokecolor'] = $this->strokecolor
;
16559 $dom[$key]['align'] = '';
16560 $dom[$key]['listtype'] = '';
16561 $dom[$key]['text-indent'] = 0;
16562 $dom[$key]['text-transform'] = '';
16563 $dom[$key]['border'] = array();
16564 $dom[$key]['dir'] = $this->rtl?
'rtl':'ltr';
16565 $thead = false; // true when we are inside the THEAD tag
16568 array_push($level, 0); // root
16569 while ($elkey < $maxel) {
16570 $dom[$key] = array();
16571 $element = $a[$elkey];
16572 $dom[$key]['elkey'] = $elkey;
16573 if (preg_match($tagpattern, $element)) {
16575 $element = substr($element, 1, -1);
16577 preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
16578 $tagname = strtolower($tag[1]);
16579 // check if we are inside a table header
16580 if ($tagname == 'thead') {
16581 if ($element[0] == '/') {
16589 $dom[$key]['tag'] = true;
16590 $dom[$key]['value'] = $tagname;
16591 if (in_array($dom[$key]['value'], $blocktags)) {
16592 $dom[$key]['block'] = true;
16594 $dom[$key]['block'] = false;
16596 if ($element[0] == '/') {
16597 // *** closing html tag
16598 $dom[$key]['opening'] = false;
16599 $dom[$key]['parent'] = end($level);
16601 $dom[$key]['hide'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['hide'];
16602 $dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
16603 $dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
16604 $dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
16605 $dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
16606 $dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
16607 $dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
16608 $dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
16609 $dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
16610 $dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
16611 $dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
16612 $dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
16613 $dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
16614 $dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
16615 $dom[$key]['text-transform'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['text-transform'];
16616 $dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
16617 if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
16618 $dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
16620 // set the number of columns in table tag
16621 if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
16622 $dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
16624 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
16625 $dom[($dom[$key]['parent'])]['content'] = $csstagarray;
16626 for ($i = ($dom[$key]['parent'] +
1); $i < $key; ++
$i) {
16627 $dom[($dom[$key]['parent'])]['content'] .= stripslashes($a[$dom[$i]['elkey']]);
16630 // mark nested tables
16631 $dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true"', $dom[($dom[$key]['parent'])]['content']);
16632 // remove thead sections from nested tables
16633 $dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
16634 $dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
16636 // store header rows on a new table
16638 ($dom[$key]['value'] === 'tr')
16639 && !empty($dom[($dom[$key]['parent'])]['thead'])
16640 && ($dom[($dom[$key]['parent'])]['thead'] === true)
16642 if (TCPDF_STATIC
::empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
16643 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $csstagarray.$a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
16645 for ($i = $dom[$key]['parent']; $i <= $key; ++
$i) {
16646 $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
16648 if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
16649 $dom[($dom[$key]['parent'])]['attribute'] = array();
16651 // header elements must be always contained in a single page
16652 $dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
16654 if (($dom[$key]['value'] == 'table') AND (!TCPDF_STATIC
::empty_string($dom[($dom[$key]['parent'])]['thead']))) {
16655 // remove the nobr attributes from the table header
16656 $dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
16657 $dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
16660 // *** opening or self-closing html tag
16661 $dom[$key]['opening'] = true;
16662 $dom[$key]['parent'] = end($level);
16663 if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
16664 // self-closing tag
16665 $dom[$key]['self'] = true;
16668 array_push($level, $key);
16669 $dom[$key]['self'] = false;
16671 // copy some values from parent
16674 $parentkey = $dom[$key]['parent'];
16675 $dom[$key]['hide'] = $dom[$parentkey]['hide'];
16676 $dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
16677 $dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
16678 $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
16679 $dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
16680 $dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
16681 $dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
16682 $dom[$key]['fill'] = $dom[$parentkey]['fill'];
16683 $dom[$key]['clip'] = $dom[$parentkey]['clip'];
16684 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16685 $dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
16686 $dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
16687 $dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
16688 $dom[$key]['align'] = $dom[$parentkey]['align'];
16689 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16690 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16691 $dom[$key]['text-transform'] = $dom[$parentkey]['text-transform'];
16692 $dom[$key]['border'] = array();
16693 $dom[$key]['dir'] = $dom[$parentkey]['dir'];
16696 preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER
);
16697 $dom[$key]['attribute'] = array(); // reset attribute array
16698 foreach($attr_array[1] as $id => $name) {
16699 $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
16701 if (!empty($css)) {
16702 // merge CSS style to current style
16703 list($dom[$key]['csssel'], $dom[$key]['cssdata']) = TCPDF_STATIC
::getCSSdataArray($dom, $key, $css);
16704 $dom[$key]['attribute']['style'] = TCPDF_STATIC
::getTagStyleFromCSSarray($dom[$key]['cssdata']);
16706 // split style attributes
16707 if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
16708 // get style attributes
16709 preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER
);
16710 $dom[$key]['style'] = array(); // reset style attribute array
16711 foreach($style_array[1] as $id => $name) {
16712 // in case of duplicate attribute the last replace the previous
16713 $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
16715 // --- get some style attributes ---
16717 if (isset($dom[$key]['style']['direction'])) {
16718 $dom[$key]['dir'] = $dom[$key]['style']['direction'];
16721 if (isset($dom[$key]['style']['display'])) {
16722 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16725 if (isset($dom[$key]['style']['font-family'])) {
16726 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16729 if (isset($dom[$key]['style']['list-style-type'])) {
16730 $dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
16731 if ($dom[$key]['listtype'] == 'inherit') {
16732 $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
16736 if (isset($dom[$key]['style']['text-indent'])) {
16737 $dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
16738 if ($dom[$key]['text-indent'] == 'inherit') {
16739 $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
16743 if (isset($dom[$key]['style']['text-transform'])) {
16744 $dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16747 if (isset($dom[$key]['style']['font-size'])) {
16748 $fsize = trim($dom[$key]['style']['font-size']);
16749 $dom[$key]['fontsize'] = $this->getHTMLFontUnits($fsize, $dom[0]['fontsize'], $dom[$parentkey]['fontsize'], 'pt');
16752 if (isset($dom[$key]['style']['font-stretch'])) {
16753 $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16756 if (isset($dom[$key]['style']['letter-spacing'])) {
16757 $dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
16759 // line-height (internally is the cell height ratio)
16760 if (isset($dom[$key]['style']['line-height'])) {
16761 $lineheight = trim($dom[$key]['style']['line-height']);
16762 switch ($lineheight) {
16763 // A normal line height. This is default
16765 $dom[$key]['line-height'] = $dom[0]['line-height'];
16769 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16772 if (is_numeric($lineheight)) {
16773 // convert to percentage of font height
16774 $lineheight = ($lineheight * 100).'%';
16776 $dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
16777 if (substr($lineheight, -1) !== '%') {
16778 if ($dom[$key]['fontsize'] <= 0) {
16779 $dom[$key]['line-height'] = 1;
16781 $dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding
['T'] - $this->cell_padding
['B']) / $dom[$key]['fontsize']);
16788 if (isset($dom[$key]['style']['font-weight'])) {
16789 if (strtolower($dom[$key]['style']['font-weight'][0]) == 'n') {
16790 if (strpos($dom[$key]['fontstyle'], 'B') !== false) {
16791 $dom[$key]['fontstyle'] = str_replace('B', '', $dom[$key]['fontstyle']);
16793 } elseif (strtolower($dom[$key]['style']['font-weight'][0]) == 'b') {
16794 $dom[$key]['fontstyle'] .= 'B';
16797 if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style'][0]) == 'i')) {
16798 $dom[$key]['fontstyle'] .= 'I';
16801 if (isset($dom[$key]['style']['color']) AND (!TCPDF_STATIC
::empty_string($dom[$key]['style']['color']))) {
16802 $dom[$key]['fgcolor'] = TCPDF_COLORS
::convertHTMLColorToDec($dom[$key]['style']['color'], $this->spot_colors
);
16803 } elseif ($dom[$key]['value'] == 'a') {
16804 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray
;
16806 // background color
16807 if (isset($dom[$key]['style']['background-color']) AND (!TCPDF_STATIC
::empty_string($dom[$key]['style']['background-color']))) {
16808 $dom[$key]['bgcolor'] = TCPDF_COLORS
::convertHTMLColorToDec($dom[$key]['style']['background-color'], $this->spot_colors
);
16811 if (isset($dom[$key]['style']['text-decoration'])) {
16812 $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16813 foreach ($decors as $dec) {
16815 if (!TCPDF_STATIC
::empty_string($dec)) {
16816 if ($dec[0] == 'u') {
16818 $dom[$key]['fontstyle'] .= 'U';
16819 } elseif ($dec[0] == 'l') {
16821 $dom[$key]['fontstyle'] .= 'D';
16822 } elseif ($dec[0] == 'o') {
16824 $dom[$key]['fontstyle'] .= 'O';
16828 } elseif ($dom[$key]['value'] == 'a') {
16829 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle
;
16831 // check for width attribute
16832 if (isset($dom[$key]['style']['width'])) {
16833 $dom[$key]['width'] = $dom[$key]['style']['width'];
16835 // check for height attribute
16836 if (isset($dom[$key]['style']['height'])) {
16837 $dom[$key]['height'] = $dom[$key]['style']['height'];
16839 // check for text alignment
16840 if (isset($dom[$key]['style']['text-align'])) {
16841 $dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align'][0]);
16843 // check for CSS border properties
16844 if (isset($dom[$key]['style']['border'])) {
16845 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
16846 if (!empty($borderstyle)) {
16847 $dom[$key]['border']['LTRB'] = $borderstyle;
16850 if (isset($dom[$key]['style']['border-color'])) {
16851 $brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
16852 if (isset($brd_colors[3])) {
16853 $dom[$key]['border']['L']['color'] = TCPDF_COLORS
::convertHTMLColorToDec($brd_colors[3], $this->spot_colors
);
16855 if (isset($brd_colors[1])) {
16856 $dom[$key]['border']['R']['color'] = TCPDF_COLORS
::convertHTMLColorToDec($brd_colors[1], $this->spot_colors
);
16858 if (isset($brd_colors[0])) {
16859 $dom[$key]['border']['T']['color'] = TCPDF_COLORS
::convertHTMLColorToDec($brd_colors[0], $this->spot_colors
);
16861 if (isset($brd_colors[2])) {
16862 $dom[$key]['border']['B']['color'] = TCPDF_COLORS
::convertHTMLColorToDec($brd_colors[2], $this->spot_colors
);
16865 if (isset($dom[$key]['style']['border-width'])) {
16866 $brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
16867 if (isset($brd_widths[3])) {
16868 $dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
16870 if (isset($brd_widths[1])) {
16871 $dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
16873 if (isset($brd_widths[0])) {
16874 $dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
16876 if (isset($brd_widths[2])) {
16877 $dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
16880 if (isset($dom[$key]['style']['border-style'])) {
16881 $brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
16882 if (isset($brd_styles[3]) AND ($brd_styles[3]!='none')) {
16883 $dom[$key]['border']['L']['cap'] = 'square';
16884 $dom[$key]['border']['L']['join'] = 'miter';
16885 $dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
16886 if ($dom[$key]['border']['L']['dash'] < 0) {
16887 $dom[$key]['border']['L'] = array();
16890 if (isset($brd_styles[1])) {
16891 $dom[$key]['border']['R']['cap'] = 'square';
16892 $dom[$key]['border']['R']['join'] = 'miter';
16893 $dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
16894 if ($dom[$key]['border']['R']['dash'] < 0) {
16895 $dom[$key]['border']['R'] = array();
16898 if (isset($brd_styles[0])) {
16899 $dom[$key]['border']['T']['cap'] = 'square';
16900 $dom[$key]['border']['T']['join'] = 'miter';
16901 $dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
16902 if ($dom[$key]['border']['T']['dash'] < 0) {
16903 $dom[$key]['border']['T'] = array();
16906 if (isset($brd_styles[2])) {
16907 $dom[$key]['border']['B']['cap'] = 'square';
16908 $dom[$key]['border']['B']['join'] = 'miter';
16909 $dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
16910 if ($dom[$key]['border']['B']['dash'] < 0) {
16911 $dom[$key]['border']['B'] = array();
16915 $cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
16916 foreach ($cellside as $bsk => $bsv) {
16917 if (isset($dom[$key]['style']['border-'.$bsv])) {
16918 $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
16919 if (!empty($borderstyle)) {
16920 $dom[$key]['border'][$bsk] = $borderstyle;
16923 if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
16924 $dom[$key]['border'][$bsk]['color'] = TCPDF_COLORS
::convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color'], $this->spot_colors
);
16926 if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
16927 $dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
16929 if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
16930 $dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
16931 if ($dom[$key]['border'][$bsk]['dash'] < 0) {
16932 $dom[$key]['border'][$bsk] = array();
16936 // check for CSS padding properties
16937 if (isset($dom[$key]['style']['padding'])) {
16938 $dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
16940 $dom[$key]['padding'] = $this->cell_padding
;
16942 foreach ($cellside as $psk => $psv) {
16943 if (isset($dom[$key]['style']['padding-'.$psv])) {
16944 $dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
16947 // check for CSS margin properties
16948 if (isset($dom[$key]['style']['margin'])) {
16949 $dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
16951 $dom[$key]['margin'] = $this->cell_margin
;
16953 foreach ($cellside as $psk => $psv) {
16954 if (isset($dom[$key]['style']['margin-'.$psv])) {
16955 $dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
16958 // check for CSS border-spacing properties
16959 if (isset($dom[$key]['style']['border-spacing'])) {
16960 $dom[$key]['border-spacing'] = $this->getCSSBorderMargin($dom[$key]['style']['border-spacing']);
16962 // page-break-inside
16963 if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
16964 $dom[$key]['attribute']['nobr'] = 'true';
16966 // page-break-before
16967 if (isset($dom[$key]['style']['page-break-before'])) {
16968 if ($dom[$key]['style']['page-break-before'] == 'always') {
16969 $dom[$key]['attribute']['pagebreak'] = 'true';
16970 } elseif ($dom[$key]['style']['page-break-before'] == 'left') {
16971 $dom[$key]['attribute']['pagebreak'] = 'left';
16972 } elseif ($dom[$key]['style']['page-break-before'] == 'right') {
16973 $dom[$key]['attribute']['pagebreak'] = 'right';
16976 // page-break-after
16977 if (isset($dom[$key]['style']['page-break-after'])) {
16978 if ($dom[$key]['style']['page-break-after'] == 'always') {
16979 $dom[$key]['attribute']['pagebreakafter'] = 'true';
16980 } elseif ($dom[$key]['style']['page-break-after'] == 'left') {
16981 $dom[$key]['attribute']['pagebreakafter'] = 'left';
16982 } elseif ($dom[$key]['style']['page-break-after'] == 'right') {
16983 $dom[$key]['attribute']['pagebreakafter'] = 'right';
16987 if (isset($dom[$key]['attribute']['display'])) {
16988 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['attribute']['display'])) == 'none');
16990 if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
16991 $borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
16992 if (!empty($borderstyle)) {
16993 $dom[$key]['border']['LTRB'] = $borderstyle;
16996 // check for font tag
16997 if ($dom[$key]['value'] == 'font') {
16999 if (isset($dom[$key]['attribute']['face'])) {
17000 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
17003 if (isset($dom[$key]['attribute']['size'])) {
17005 if ($dom[$key]['attribute']['size'][0] == '+') {
17006 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] +
intval(substr($dom[$key]['attribute']['size'], 1));
17007 } elseif ($dom[$key]['attribute']['size'][0] == '-') {
17008 $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
17010 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
17013 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
17017 // force natural alignment for lists
17018 if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
17019 AND (!isset($dom[$key]['align']) OR TCPDF_STATIC
::empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
17021 $dom[$key]['align'] = 'R';
17023 $dom[$key]['align'] = 'L';
17026 if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
17027 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17028 $dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO
;
17031 if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
17032 $dom[$key]['fontstyle'] .= 'B';
17034 if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
17035 $dom[$key]['fontstyle'] .= 'I';
17037 if ($dom[$key]['value'] == 'u') {
17038 $dom[$key]['fontstyle'] .= 'U';
17040 if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
17041 $dom[$key]['fontstyle'] .= 'D';
17043 if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
17044 $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle
;
17046 if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
17047 $dom[$key]['fontname'] = $this->default_monospaced_font
;
17049 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)) {
17050 // headings h1, h2, h3, h4, h5, h6
17051 if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
17052 $headsize = (4 - intval($dom[$key]['value'][1])) * 2;
17053 $dom[$key]['fontsize'] = $dom[0]['fontsize'] +
$headsize;
17055 if (!isset($dom[$key]['style']['font-weight'])) {
17056 $dom[$key]['fontstyle'] .= 'B';
17059 if (($dom[$key]['value'] == 'table')) {
17060 $dom[$key]['rows'] = 0; // number of rows
17061 $dom[$key]['trids'] = array(); // IDs of TR elements
17062 $dom[$key]['thead'] = ''; // table header rows
17064 if (($dom[$key]['value'] == 'tr')) {
17065 $dom[$key]['cols'] = 0;
17067 $dom[$key]['thead'] = true;
17068 // rows on thead block are printed as a separate table
17070 $dom[$key]['thead'] = false;
17071 $parent = $dom[$key]['parent'];
17073 if (!isset($dom[$parent]['rows'])) {
17074 $dom[$parent]['rows'] = 0;
17076 // store the number of rows on table element
17077 ++
$dom[$parent]['rows'];
17079 if (!isset($dom[$parent]['trids'])) {
17080 $dom[$parent]['trids'] = array();
17083 // store the TR elements IDs on table element
17084 array_push($dom[$parent]['trids'], $key);
17087 if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
17088 if (isset($dom[$key]['attribute']['colspan'])) {
17089 $colspan = intval($dom[$key]['attribute']['colspan']);
17093 $dom[$key]['attribute']['colspan'] = $colspan;
17094 $dom[($dom[$key]['parent'])]['cols'] +
= $colspan;
17097 if (isset($dom[$key]['attribute']['dir'])) {
17098 $dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
17100 // set foreground color attribute
17101 if (isset($dom[$key]['attribute']['color']) AND (!TCPDF_STATIC
::empty_string($dom[$key]['attribute']['color']))) {
17102 $dom[$key]['fgcolor'] = TCPDF_COLORS
::convertHTMLColorToDec($dom[$key]['attribute']['color'], $this->spot_colors
);
17103 } elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
17104 $dom[$key]['fgcolor'] = $this->htmlLinkColorArray
;
17106 // set background color attribute
17107 if (isset($dom[$key]['attribute']['bgcolor']) AND (!TCPDF_STATIC
::empty_string($dom[$key]['attribute']['bgcolor']))) {
17108 $dom[$key]['bgcolor'] = TCPDF_COLORS
::convertHTMLColorToDec($dom[$key]['attribute']['bgcolor'], $this->spot_colors
);
17110 // set stroke color attribute
17111 if (isset($dom[$key]['attribute']['strokecolor']) AND (!TCPDF_STATIC
::empty_string($dom[$key]['attribute']['strokecolor']))) {
17112 $dom[$key]['strokecolor'] = TCPDF_COLORS
::convertHTMLColorToDec($dom[$key]['attribute']['strokecolor'], $this->spot_colors
);
17114 // check for width attribute
17115 if (isset($dom[$key]['attribute']['width'])) {
17116 $dom[$key]['width'] = $dom[$key]['attribute']['width'];
17118 // check for height attribute
17119 if (isset($dom[$key]['attribute']['height'])) {
17120 $dom[$key]['height'] = $dom[$key]['attribute']['height'];
17122 // check for text alignment
17123 if (isset($dom[$key]['attribute']['align']) AND (!TCPDF_STATIC
::empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
17124 $dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align'][0]);
17126 // check for text rendering mode (the following attributes do not exist in HTML)
17127 if (isset($dom[$key]['attribute']['stroke'])) {
17128 // font stroke width
17129 $dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
17131 if (isset($dom[$key]['attribute']['fill'])) {
17133 if ($dom[$key]['attribute']['fill'] == 'true') {
17134 $dom[$key]['fill'] = true;
17136 $dom[$key]['fill'] = false;
17139 if (isset($dom[$key]['attribute']['clip'])) {
17141 if ($dom[$key]['attribute']['clip'] == 'true') {
17142 $dom[$key]['clip'] = true;
17144 $dom[$key]['clip'] = false;
17147 } // end opening tag
17150 $dom[$key]['tag'] = false;
17151 $dom[$key]['block'] = false;
17152 $dom[$key]['parent'] = end($level);
17153 $dom[$key]['dir'] = $dom[$dom[$key]['parent']]['dir'];
17154 if (!empty($dom[$dom[$key]['parent']]['text-transform'])) {
17155 // text-transform for unicode requires mb_convert_case (Multibyte String Functions)
17156 if (function_exists('mb_convert_case')) {
17157 $ttm = array('capitalize' => MB_CASE_TITLE
, 'uppercase' => MB_CASE_UPPER
, 'lowercase' => MB_CASE_LOWER
);
17158 if (isset($ttm[$dom[$dom[$key]['parent']]['text-transform']])) {
17159 $element = mb_convert_case($element, $ttm[$dom[$dom[$key]['parent']]['text-transform']], $this->encoding
);
17161 } elseif (!$this->isunicode
) {
17162 switch ($dom[$dom[$key]['parent']]['text-transform']) {
17163 case 'capitalize': {
17164 $element = ucwords(strtolower($element));
17167 case 'uppercase': {
17168 $element = strtoupper($element);
17171 case 'lowercase': {
17172 $element = strtolower($element);
17177 $element = preg_replace("/&NBSP;/i", " ", $element);
17179 $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17188 * Returns the string used to find spaces
17191 * @author Nicola Asuni
17192 * @since 4.8.024 (2010-01-15)
17194 protected function getSpaceString() {
17195 $spacestr = chr(32);
17196 if ($this->isUnicodeFont()) {
17197 $spacestr = chr(0).chr(32);
17203 * Return an hash code used to ensure that the serialized data has been generated by this TCPDF instance.
17204 * @param string $data serialized data
17208 protected function getHashForTCPDFtagParams($data) {
17209 return md5(strlen($data).$this->file_id
.$data);
17213 * Serialize an array of parameters to be used with TCPDF tag in HTML code.
17214 * @param array $data parameters array
17215 * @return string containing serialized data
17218 public function serializeTCPDFtagParameters($data) {
17219 $encoded = urlencode(json_encode($data));
17220 return $this->getHashForTCPDFtagParams($encoded).$encoded;
17224 * Unserialize parameters to be used with TCPDF tag in HTML code.
17225 * @param string $data serialized data
17226 * @return array containing unserialized data
17227 * @protected static
17229 protected function unserializeTCPDFtagParameters($data) {
17230 $hash = substr($data, 0, 32);
17231 $encoded = substr($data, 32);
17232 if ($hash != $this->getHashForTCPDFtagParams($encoded)) {
17233 $this->Error('Invalid parameters');
17235 return json_decode(urldecode($encoded), true);
17239 * Prints a cell (rectangular area) with optional borders, background color and html text string.
17240 * 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 />
17241 * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
17242 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17243 * 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
17244 * NOTE: all the HTML attributes must be enclosed in double-quote.
17245 * @param float $w Cell width. If 0, the cell extends up to the right margin.
17246 * @param float $h Cell minimum height. The cell extends automatically if needed.
17247 * @param float|null $x upper-left corner X coordinate
17248 * @param float|null $y upper-left corner Y coordinate
17249 * @param string $html html text to print. Default value: empty string.
17250 * @param mixed $border 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)))
17251 * @param int $ln 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>
17252 Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
17253 * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
17254 * @param boolean $reseth if true reset the last cell height (default true).
17255 * @param string $align 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>
17256 * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
17257 * @see Multicell(), writeHTML()
17260 public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
17261 return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0, 'T', false);
17265 * Allows to preserve some HTML formatting (limited support).<br />
17266 * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
17267 * 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
17268 * NOTE: all the HTML attributes must be enclosed in double-quote.
17269 * @param string $html text to display
17270 * @param boolean $ln if true add a new line after text (default = true)
17271 * @param boolean $fill Indicates if the background must be painted (true) or transparent (false).
17272 * @param boolean $reseth if true reset the last cell height (default false).
17273 * @param boolean $cell if true add the current left (or right for RTL) padding to each Write (default false).
17274 * @param string $align 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>
17277 public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
17278 $gvars = $this->getGraphicVars();
17279 // store current values
17280 $prev_cell_margin = $this->cell_margin
;
17281 $prev_cell_padding = $this->cell_padding
;
17282 $prevPage = $this->page
;
17283 $prevlMargin = $this->lMargin
;
17284 $prevrMargin = $this->rMargin
;
17285 $curfontname = $this->FontFamily
;
17286 $curfontstyle = $this->FontStyle
;
17287 $curfontsize = $this->FontSizePt
;
17288 $curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
17289 $curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
17290 $curfontstretcing = $this->font_stretching
;
17291 $curfonttracking = $this->font_spacing
;
17292 $this->newline
= true;
17294 $startlinepage = $this->page
;
17295 $minstartliney = $this->y
;
17296 $maxbottomliney = 0;
17297 $startlinex = $this->x
;
17298 $startliney = $this->y
;
17302 $this_method_vars = array();
17304 $fontaligned = false;
17305 $reverse_dir = false; // true when the text direction is reversed
17306 $this->premode
= false;
17307 if ($this->inxobj
) {
17308 // we are inside an XObject template
17309 $pask = count($this->xobjects
[$this->xobjid
]['annotations']);
17310 } elseif (isset($this->PageAnnots
[$this->page
])) {
17311 $pask = count($this->PageAnnots
[$this->page
]);
17315 if ($this->inxobj
) {
17316 // we are inside an XObject template
17317 $startlinepos = strlen($this->xobjects
[$this->xobjid
]['outdata']);
17318 } elseif (!$this->InFooter
) {
17319 if (isset($this->footerlen
[$this->page
])) {
17320 $this->footerpos
[$this->page
] = $this->pagelen
[$this->page
] - $this->footerlen
[$this->page
];
17322 $this->footerpos
[$this->page
] = $this->pagelen
[$this->page
];
17324 $startlinepos = $this->footerpos
[$this->page
];
17326 // we are inside the footer
17327 $startlinepos = $this->pagelen
[$this->page
];
17332 $w = $this->x
- $this->lMargin
;
17334 $w = $this->w
- $this->rMargin
- $this->x
;
17336 $w -= ($this->cell_padding
['L'] +
$this->cell_padding
['R']);
17339 $this->x
-= $this->cell_padding
['R'];
17340 $this->lMargin +
= $this->cell_padding
['L'];
17342 $this->x +
= $this->cell_padding
['L'];
17343 $this->rMargin +
= $this->cell_padding
['R'];
17346 if ($this->customlistindent
>= 0) {
17347 $this->listindent
= $this->customlistindent
;
17349 $this->listindent
= $this->GetStringWidth('000000');
17351 $this->listindentlevel
= 0;
17352 // save previous states
17353 $prev_cell_height_ratio = $this->cell_height_ratio
;
17354 $prev_listnum = $this->listnum
;
17355 $prev_listordered = $this->listordered
;
17356 $prev_listcount = $this->listcount
;
17357 $prev_lispacer = $this->lispacer
;
17358 $this->listnum
= 0;
17359 $this->listordered
= array();
17360 $this->listcount
= array();
17361 $this->lispacer
= '';
17362 if ((TCPDF_STATIC
::empty_string($this->lasth
)) OR ($reseth)) {
17363 // reset row height
17364 $this->resetLastH();
17366 $dom = $this->getHtmlDomArray($html);
17367 $maxel = count($dom);
17369 while ($key < $maxel) {
17370 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND $dom[$key]['hide']) {
17371 // store the node key
17372 $hidden_node_key = $key;
17373 if ($dom[$key]['self']) {
17374 // skip just this self-closing tag
17377 // skip this and all children tags
17378 while (($key < $maxel) AND (!$dom[$key]['tag'] OR $dom[$key]['opening'] OR ($dom[$key]['parent'] != $hidden_node_key))) {
17379 // skip hidden objects
17385 if ($key == $maxel) break;
17386 if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
17387 // check for pagebreak
17388 if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
17389 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17390 $this->checkPageBreak($this->PageBreakTrigger +
1);
17391 $this->htmlvspace
= ($this->PageBreakTrigger +
1);
17393 if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl
) AND (($this->page %
2) == 0)) OR (($this->rtl
) AND (($this->page %
2) != 0))))
17394 OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl
) AND (($this->page %
2) != 0)) OR (($this->rtl
) AND (($this->page %
2) == 0))))) {
17395 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17396 $this->checkPageBreak($this->PageBreakTrigger +
1);
17397 $this->htmlvspace
= ($this->PageBreakTrigger +
1);
17400 if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
17401 if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
17402 $dom[$key]['attribute']['nobr'] = false;
17404 // store current object
17405 $this->startTransaction();
17406 // save this method vars
17407 $this_method_vars['html'] = $html;
17408 $this_method_vars['ln'] = $ln;
17409 $this_method_vars['fill'] = $fill;
17410 $this_method_vars['reseth'] = $reseth;
17411 $this_method_vars['cell'] = $cell;
17412 $this_method_vars['align'] = $align;
17413 $this_method_vars['gvars'] = $gvars;
17414 $this_method_vars['prevPage'] = $prevPage;
17415 $this_method_vars['prev_cell_margin'] = $prev_cell_margin;
17416 $this_method_vars['prev_cell_padding'] = $prev_cell_padding;
17417 $this_method_vars['prevlMargin'] = $prevlMargin;
17418 $this_method_vars['prevrMargin'] = $prevrMargin;
17419 $this_method_vars['curfontname'] = $curfontname;
17420 $this_method_vars['curfontstyle'] = $curfontstyle;
17421 $this_method_vars['curfontsize'] = $curfontsize;
17422 $this_method_vars['curfontascent'] = $curfontascent;
17423 $this_method_vars['curfontdescent'] = $curfontdescent;
17424 $this_method_vars['curfontstretcing'] = $curfontstretcing;
17425 $this_method_vars['curfonttracking'] = $curfonttracking;
17426 $this_method_vars['minstartliney'] = $minstartliney;
17427 $this_method_vars['maxbottomliney'] = $maxbottomliney;
17428 $this_method_vars['yshift'] = $yshift;
17429 $this_method_vars['startlinepage'] = $startlinepage;
17430 $this_method_vars['startlinepos'] = $startlinepos;
17431 $this_method_vars['startlinex'] = $startlinex;
17432 $this_method_vars['startliney'] = $startliney;
17433 $this_method_vars['newline'] = $newline;
17434 $this_method_vars['loop'] = $loop;
17435 $this_method_vars['curpos'] = $curpos;
17436 $this_method_vars['pask'] = $pask;
17437 $this_method_vars['lalign'] = $lalign;
17438 $this_method_vars['plalign'] = $plalign;
17439 $this_method_vars['w'] = $w;
17440 $this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
17441 $this_method_vars['prev_listnum'] = $prev_listnum;
17442 $this_method_vars['prev_listordered'] = $prev_listordered;
17443 $this_method_vars['prev_listcount'] = $prev_listcount;
17444 $this_method_vars['prev_lispacer'] = $prev_lispacer;
17445 $this_method_vars['fontaligned'] = $fontaligned;
17446 $this_method_vars['key'] = $key;
17447 $this_method_vars['dom'] = $dom;
17450 // print THEAD block
17451 if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
17452 if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !TCPDF_STATIC
::empty_string($dom[$dom[$key]['parent']]['thead'])) {
17453 $this->inthead
= true;
17454 // print table header (thead)
17455 $this->writeHTML($this->thead
, false, false, false, false, '');
17456 // check if we are on a new page or on a new column
17457 if (($this->y
< $this->start_transaction_y
) OR ($this->checkPageBreak($this->lasth
, '', false))) {
17458 // we are on a new page or on a new column and the total object height is less than the available vertical space.
17459 // restore previous object
17460 $this->rollbackTransaction(true);
17461 // restore previous values
17462 foreach ($this_method_vars as $vkey => $vval) {
17465 // disable table header
17466 $tmp_thead = $this->thead
;
17468 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17470 if ((!$this->checkPageBreak($this->PageBreakTrigger +
1)) AND ($this->y
< $pre_y)) {
17471 // fix for multicolumn mode
17472 $startliney = $this->y
;
17474 $this->start_transaction_page
= $this->page
;
17475 $this->start_transaction_y
= $this->y
;
17476 // restore table header
17477 $this->thead
= $tmp_thead;
17478 // fix table border properties
17479 if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
17480 $tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
17481 } elseif (isset($dom[$dom[$key]['parent']]['border-spacing'])) {
17482 $tmp_cellspacing = $dom[$dom[$key]['parent']]['border-spacing']['V'];
17484 $tmp_cellspacing = 0;
17486 $dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page
;
17487 $dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column
;
17488 $dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y +
$tmp_cellspacing;
17489 $xoffset = ($this->x
- $dom[$dom[$key]['parent']]['borderposition']['x']);
17490 $dom[$dom[$key]['parent']]['borderposition']['x'] +
= $xoffset;
17491 $dom[$dom[$key]['parent']]['borderposition']['xmax'] +
= $xoffset;
17492 // print table header (thead)
17493 $this->writeHTML($this->thead
, false, false, false, false, '');
17496 // move $key index forward to skip THEAD block
17497 while ( ($key < $maxel) AND (!(
17498 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
17499 OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
17503 if ($dom[$key]['tag'] OR ($key == 0)) {
17504 if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
17505 $dom[$key]['align'] = ($this->rtl
) ?
'R' : 'L';
17507 // vertically align image in line
17508 if ((!$this->newline
) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
17509 // get image height
17510 $imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], ($dom[$key]['fontsize'] / $this->k
), 'px');
17511 $autolinebreak = false;
17512 if (!empty($dom[$key]['width'])) {
17513 $imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], ($dom[$key]['fontsize'] / $this->k
), 'px', false);
17514 if (($imgw <= ($this->w
- $this->lMargin
- $this->rMargin
- $this->cell_padding
['L'] - $this->cell_padding
['R']))
17515 AND ((($this->rtl
) AND (($this->x
- $imgw) < ($this->lMargin +
$this->cell_padding
['L'])))
17516 OR ((!$this->rtl
) AND (($this->x +
$imgw) > ($this->w
- $this->rMargin
- $this->cell_padding
['R']))))) {
17517 // add automatic line break
17518 $autolinebreak = true;
17519 $this->Ln('', $cell);
17520 if ((!$dom[($key-1)]['tag']) AND ($dom[($key-1)]['value'] == ' ')) {
17521 // go back to evaluate this line break
17526 if (!$autolinebreak) {
17527 if ($this->inPageBody()) {
17529 // check for page break
17530 if ((!$this->checkPageBreak($imgh)) AND ($this->y
< $pre_y)) {
17531 // fix for multicolumn mode
17532 $startliney = $this->y
;
17535 if ($this->page
> $startlinepage) {
17536 // fix line splitted over two pages
17537 if (isset($this->footerlen
[$startlinepage])) {
17538 $curpos = $this->pagelen
[$startlinepage] - $this->footerlen
[$startlinepage];
17540 // line to be moved one page forward
17541 $pagebuff = $this->getPageBuffer($startlinepage);
17542 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17543 $tstart = substr($pagebuff, 0, $startlinepos);
17544 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17545 // remove line from previous page
17546 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17547 $pagebuff = $this->getPageBuffer($this->page
);
17548 $tstart = substr($pagebuff, 0, $this->cntmrk
[$this->page
]);
17549 $tend = substr($pagebuff, $this->cntmrk
[$this->page
]);
17550 // add line start to current page
17551 $yshift = ($minstartliney - $this->y
);
17552 if ($fontaligned) {
17553 $yshift +
= ($curfontsize / $this->k
);
17555 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k
));
17556 $this->setPageBuffer($this->page
, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17557 // shift the annotations and links
17558 if (isset($this->PageAnnots
[$this->page
])) {
17559 $next_pask = count($this->PageAnnots
[$this->page
]);
17563 if (isset($this->PageAnnots
[$startlinepage])) {
17564 foreach ($this->PageAnnots
[$startlinepage] as $pak => $pac) {
17565 if ($pak >= $pask) {
17566 $this->PageAnnots
[$this->page
][] = $pac;
17567 unset($this->PageAnnots
[$startlinepage][$pak]);
17568 $npak = count($this->PageAnnots
[$this->page
]) - 1;
17569 $this->PageAnnots
[$this->page
][$npak]['y'] -= $yshift;
17573 $pask = $next_pask;
17574 $startlinepos = $this->cntmrk
[$this->page
];
17575 $startlinepage = $this->page
;
17576 $startliney = $this->y
;
17577 $this->newline
= false;
17579 $this->y +
= ($this->getCellHeight($curfontsize / $this->k
) - ($curfontdescent * $this->cell_height_ratio
) - $imgh);
17580 $minstartliney = min($this->y
, $minstartliney);
17581 $maxbottomliney = ($startliney +
$this->getCellHeight($curfontsize / $this->k
));
17583 } elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
17584 // account for different font size
17585 $pfontname = $curfontname;
17586 $pfontstyle = $curfontstyle;
17587 $pfontsize = $curfontsize;
17588 $fontname = (isset($dom[$key]['fontname']) ?
$dom[$key]['fontname'] : $curfontname);
17589 $fontstyle = (isset($dom[$key]['fontstyle']) ?
$dom[$key]['fontstyle'] : $curfontstyle);
17590 $fontsize = (isset($dom[$key]['fontsize']) ?
$dom[$key]['fontsize'] : $curfontsize);
17591 $fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
17592 $fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
17593 if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize)
17594 OR ($this->cell_height_ratio
!= $dom[$key]['line-height'])
17595 OR ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li')) ) {
17596 if (($key < ($maxel - 1)) AND (
17597 ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'li'))
17598 OR ($this->cell_height_ratio
!= $dom[$key]['line-height'])
17599 OR (!$this->newline
AND is_numeric($fontsize) AND is_numeric($curfontsize)
17600 AND ($fontsize >= 0) AND ($curfontsize >= 0)
17601 AND (($fontsize != $curfontsize) OR ($fontstyle != $curfontstyle) OR ($fontname != $curfontname)))
17603 if ($this->page
> $startlinepage) {
17604 // fix lines splitted over two pages
17605 if (isset($this->footerlen
[$startlinepage])) {
17606 $curpos = $this->pagelen
[$startlinepage] - $this->footerlen
[$startlinepage];
17608 // line to be moved one page forward
17609 $pagebuff = $this->getPageBuffer($startlinepage);
17610 $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
17611 $tstart = substr($pagebuff, 0, $startlinepos);
17612 $tend = substr($this->getPageBuffer($startlinepage), $curpos);
17613 // remove line start from previous page
17614 $this->setPageBuffer($startlinepage, $tstart.''.$tend);
17615 $pagebuff = $this->getPageBuffer($this->page
);
17616 $tstart = substr($pagebuff, 0, $this->cntmrk
[$this->page
]);
17617 $tend = substr($pagebuff, $this->cntmrk
[$this->page
]);
17618 // add line start to current page
17619 $yshift = ($minstartliney - $this->y
);
17620 $try = sprintf('1 0 0 1 0 %F cm', ($yshift * $this->k
));
17621 $this->setPageBuffer($this->page
, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
17622 // shift the annotations and links
17623 if (isset($this->PageAnnots
[$this->page
])) {
17624 $next_pask = count($this->PageAnnots
[$this->page
]);
17628 if (isset($this->PageAnnots
[$startlinepage])) {
17629 foreach ($this->PageAnnots
[$startlinepage] as $pak => $pac) {
17630 if ($pak >= $pask) {
17631 $this->PageAnnots
[$this->page
][] = $pac;
17632 unset($this->PageAnnots
[$startlinepage][$pak]);
17633 $npak = count($this->PageAnnots
[$this->page
]) - 1;
17634 $this->PageAnnots
[$this->page
][$npak]['y'] -= $yshift;
17638 $pask = $next_pask;
17639 $startlinepos = $this->cntmrk
[$this->page
];
17640 $startlinepage = $this->page
;
17641 $startliney = $this->y
;
17643 if (!isset($dom[$key]['line-height'])) {
17644 $dom[$key]['line-height'] = $this->cell_height_ratio
;
17646 if (!$dom[$key]['block']) {
17647 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']))) {
17648 $this->y +
= (((($curfontsize * $this->cell_height_ratio
) - ($fontsize * $dom[$key]['line-height'])) / $this->k
) +
$curfontascent - $fontascent - $curfontdescent +
$fontdescent) / 2;
17650 if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
17651 $current_line_align_data = array($key, $minstartliney, $maxbottomliney);
17652 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)))) {
17653 $minstartliney = min($this->y
, $line_align_data[1]);
17654 $maxbottomliney = max(($this->y +
$this->getCellHeight($fontsize / $this->k
)), $line_align_data[2]);
17656 $minstartliney = min($this->y
, $minstartliney);
17657 $maxbottomliney = max(($this->y +
$this->getCellHeight($fontsize / $this->k
)), $maxbottomliney);
17659 $line_align_data = $current_line_align_data;
17662 $this->cell_height_ratio
= $dom[$key]['line-height'];
17663 $fontaligned = true;
17665 $this->setFont($fontname, $fontstyle, $fontsize);
17666 // reset row height
17667 $this->resetLastH();
17668 $curfontname = $fontname;
17669 $curfontstyle = $fontstyle;
17670 $curfontsize = $fontsize;
17671 $curfontascent = $fontascent;
17672 $curfontdescent = $fontdescent;
17675 // set text rendering mode
17676 $textstroke = isset($dom[$key]['stroke']) ?
$dom[$key]['stroke'] : $this->textstrokewidth
;
17677 $textfill = isset($dom[$key]['fill']) ?
$dom[$key]['fill'] : (($this->textrendermode %
2) == 0);
17678 $textclip = isset($dom[$key]['clip']) ?
$dom[$key]['clip'] : ($this->textrendermode
> 3);
17679 $this->setTextRenderingMode($textstroke, $textfill, $textclip);
17680 if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
17681 $this->setFontStretching($dom[$key]['font-stretch']);
17683 if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
17684 $this->setFontSpacing($dom[$key]['letter-spacing']);
17686 if (($plalign == 'J') AND $dom[$key]['block']) {
17689 // get current position on page buffer
17690 $curpos = $this->pagelen
[$startlinepage];
17691 if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
17692 $this->setFillColorArray($dom[$key]['bgcolor']);
17695 $wfill = $fill |
false;
17697 if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
17698 $this->setTextColorArray($dom[$key]['fgcolor']);
17700 if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
17701 $this->setDrawColorArray($dom[$key]['strokecolor']);
17703 if (isset($dom[$key]['align'])) {
17704 $lalign = $dom[$key]['align'];
17706 if (TCPDF_STATIC
::empty_string($lalign)) {
17711 if ($this->newline
AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17713 $fontaligned = false;
17714 // we are at the beginning of a new line
17715 if (isset($startlinex)) {
17716 $yshift = ($minstartliney - $startliney);
17717 if (($yshift > 0) OR ($this->page
> $startlinepage)) {
17721 // the last line must be shifted to be aligned as requested
17722 $linew = abs($this->endlinex
- $startlinex);
17723 if ($this->inxobj
) {
17724 // we are inside an XObject template
17725 $pstart = substr($this->xobjects
[$this->xobjid
]['outdata'], 0, $startlinepos);
17726 if (isset($opentagpos)) {
17727 $midpos = $opentagpos;
17732 $pmid = substr($this->xobjects
[$this->xobjid
]['outdata'], $startlinepos, ($midpos - $startlinepos));
17733 $pend = substr($this->xobjects
[$this->xobjid
]['outdata'], $midpos);
17735 $pmid = substr($this->xobjects
[$this->xobjid
]['outdata'], $startlinepos);
17739 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
17740 if (isset($opentagpos) AND isset($this->footerlen
[$startlinepage]) AND (!$this->InFooter
)) {
17741 $this->footerpos
[$startlinepage] = $this->pagelen
[$startlinepage] - $this->footerlen
[$startlinepage];
17742 $midpos = min($opentagpos, $this->footerpos
[$startlinepage]);
17743 } elseif (isset($opentagpos)) {
17744 $midpos = $opentagpos;
17745 } elseif (isset($this->footerlen
[$startlinepage]) AND (!$this->InFooter
)) {
17746 $this->footerpos
[$startlinepage] = $this->pagelen
[$startlinepage] - $this->footerlen
[$startlinepage];
17747 $midpos = $this->footerpos
[$startlinepage];
17752 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17753 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
17755 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17759 if ((((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl
)) OR (($plalign == 'L') AND ($this->rtl
)))))) {
17760 // calculate shifting amount
17762 if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns
> 1)) {
17763 $tw +
= $this->cell_padding
['R'];
17765 if ($this->lMargin
!= $prevlMargin) {
17766 $tw +
= ($prevlMargin - $this->lMargin
);
17768 if ($this->rMargin
!= $prevrMargin) {
17769 $tw +
= ($prevrMargin - $this->rMargin
);
17771 $one_space_width = $this->GetStringWidth(chr(32));
17772 $no = 0; // number of spaces on a line contained on a single block
17773 if ($this->isRTLTextDir()) { // RTL
17774 // remove left space if exist
17775 $pos1 = TCPDF_STATIC
::revstrpos($pmid, '[(');
17777 $pos1 = intval($pos1);
17778 if ($this->isUnicodeFont()) {
17779 $pos2 = intval(TCPDF_STATIC
::revstrpos($pmid, '[('.chr(0).chr(32)));
17782 $pos2 = intval(TCPDF_STATIC
::revstrpos($pmid, '[('.chr(32)));
17785 if ($pos1 == $pos2) {
17786 $pmid = substr($pmid, 0, ($pos1 +
2)).substr($pmid, ($pos1 +
2 +
$spacelen));
17787 if (substr($pmid, $pos1, 4) == '[()]') {
17788 $linew -= $one_space_width;
17789 } elseif ($pos1 == strpos($pmid, '[(')) {
17795 // remove right space if exist
17796 $pos1 = TCPDF_STATIC
::revstrpos($pmid, ')]');
17798 $pos1 = intval($pos1);
17799 if ($this->isUnicodeFont()) {
17800 $pos2 = intval(TCPDF_STATIC
::revstrpos($pmid, chr(0).chr(32).')]')) +
2;
17803 $pos2 = intval(TCPDF_STATIC
::revstrpos($pmid, chr(32).')]')) +
1;
17806 if ($pos1 == $pos2) {
17807 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
17808 $linew -= $one_space_width;
17812 $mdiff = ($tw - $linew);
17813 if ($plalign == 'C') {
17815 $t_x = -($mdiff / 2);
17817 $t_x = ($mdiff / 2);
17819 } elseif ($plalign == 'R') {
17820 // right alignment on LTR document
17822 } elseif ($plalign == 'L') {
17823 // left alignment on RTL document
17825 } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17827 if ($this->isRTLTextDir()) {
17828 // align text on the left
17831 $ns = 0; // number of spaces
17833 // escape special characters
17834 $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17835 $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17837 if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER
)) {
17838 $spacestr = $this->getSpaceString();
17839 $maxkk = count($lnstring[1]) - 1;
17840 for ($kk=0; $kk <= $maxkk; ++
$kk) {
17841 // restore special characters
17842 $lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
17843 $lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
17844 // store number of spaces on the strings
17845 $lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
17846 // count total spaces on line
17847 $ns +
= $lnstring[2][$kk];
17848 $lnstring[3][$kk] = $ns;
17853 // calculate additional space to add to each existing space
17854 $spacewidth = ($mdiff / ($ns - $no)) * $this->k
;
17855 if ($this->FontSize
<= 0) {
17856 $this->FontSize
= 1;
17858 $spacewidthu = -1000 * ($mdiff +
(($ns +
$no) * $one_space_width)) / $ns / $this->FontSize
;
17859 if ($this->font_spacing
!= 0) {
17860 // fixed spacing mode
17861 $osw = -1000 * $this->font_spacing
/ $this->FontSize
;
17862 $spacewidthu +
= $osw;
17869 $prev_epsposbeg = 0;
17871 if ($this->isRTLTextDir()) {
17872 $textpos = $this->wPt
;
17874 while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE
, $offset) == 1) {
17875 // check if we are inside a string section '[( ... )]'
17876 $stroffset = strpos($pmid, '[(', $offset);
17877 if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
17878 // set offset to the end of string section
17879 $offset = strpos($pmid, ')]', $stroffset);
17880 while (($offset !== false) AND ($pmid[($offset - 1)] == '\\')) {
17881 $offset = strpos($pmid, ')]', ($offset +
1));
17883 if ($offset === false) {
17884 $this->Error('HTML Justification: malformed PDF code.');
17888 if ($this->isRTLTextDir()) {
17889 $spacew = ($spacewidth * ($nsmax - $ns));
17891 $spacew = ($spacewidth * $ns);
17893 $offset = $strpiece[2][1] +
strlen($strpiece[2][0]);
17894 $epsposend = strpos($pmid, $this->epsmarker
.'Q', $offset);
17895 if ($epsposend !== null) {
17896 $epsposend +
= strlen($this->epsmarker
.'Q');
17897 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker
, $offset);
17898 if ($epsposbeg === null) {
17899 $epsposbeg = strpos($pmid, 'q'.$this->epsmarker
, ($prev_epsposbeg - 6));
17900 $prev_epsposbeg = $epsposbeg;
17902 if (($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend)) {
17903 // shift EPS images
17904 $trx = sprintf('1 0 0 1 %F 0 cm', $spacew);
17905 $pmid_b = substr($pmid, 0, $epsposbeg);
17906 $pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
17907 $pmid_e = substr($pmid, $epsposend);
17908 $pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
17909 $offset = $epsposend;
17914 // shift blocks of code
17915 switch ($strpiece[2][0]) {
17920 // get current X position
17921 preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
17922 if (!isset($xmatches[1])) {
17925 $currentxpos = $xmatches[1];
17926 $textpos = $currentxpos;
17927 if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
17928 $ns = $lnstring[3][$strcount];
17929 if ($this->isRTLTextDir()) {
17930 $spacew = ($spacewidth * ($nsmax - $ns));
17935 if (preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $pmatch) == 1) {
17936 $newpmid = sprintf('%F',(floatval($pmatch[1]) +
$spacew)).' '.$pmatch[2].' x*#!#*x'.$pmatch[3].$pmatch[4];
17937 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17938 unset($pmatch, $newpmid);
17944 if (!TCPDF_STATIC
::empty_string($this->lispacer
)) {
17945 $this->lispacer
= '';
17948 preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
17949 if (!isset($xmatches[1])) {
17952 $currentxpos = $xmatches[1];
17955 if ($this->isRTLTextDir()) { // RTL
17956 if ($currentxpos < $textpos) {
17957 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
17958 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17960 if ($strcount > 0) {
17961 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
17962 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17966 if ($currentxpos > $textpos) {
17967 if ($strcount > 0) {
17968 $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
17970 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17972 if ($strcount > 1) {
17973 $x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
17975 if ($strcount > 0) {
17976 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17980 if (preg_match('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $pmatch) == 1) {
17981 $newx = sprintf('%F',(floatval($pmatch[1]) +
$x_diff));
17982 $neww = sprintf('%F',(floatval($pmatch[3]) +
$w_diff));
17983 $newpmid = $newx.' '.$pmatch[2].' '.$neww.' '.$pmatch[4].' x*#!#*x'.$pmatch[5].$pmatch[6];
17984 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
17985 unset($pmatch, $newpmid, $newx, $neww);
17990 // get current X position
17991 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);
17992 if (!isset($xmatches[1])) {
17995 $currentxpos = $xmatches[1];
17997 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) {
17998 $newx1 = sprintf('%F',(floatval($pmatch[1]) +
$spacew));
17999 $newx2 = sprintf('%F',(floatval($pmatch[3]) +
$spacew));
18000 $newx3 = sprintf('%F',(floatval($pmatch[5]) +
$spacew));
18001 $newpmid = $newx1.' '.$pmatch[2].' '.$newx2.' '.$pmatch[4].' '.$newx3.' '.$pmatch[6].' x*#!#*x'.$pmatch[7].$pmatch[8];
18002 $pmid = str_replace($pmatch[0], $newpmid, $pmid);
18003 unset($pmatch, $newpmid, $newx1, $newx2, $newx3);
18008 // shift the annotations and links
18009 $cxpos = ($currentxpos / $this->k
);
18010 $lmpos = ($this->lMargin +
$this->cell_padding
['L'] +
$this->feps
);
18011 if ($this->inxobj
) {
18012 // we are inside an XObject template
18013 foreach ($this->xobjects
[$this->xobjid
]['annotations'] as $pak => $pac) {
18014 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k
) >= ($currentxpos - $this->feps
)) AND (($pac['x'] * $this->k
) <= ($currentxpos +
$this->feps
))) {
18015 if ($cxpos > $lmpos) {
18016 $this->xobjects
[$this->xobjid
]['annotations'][$pak]['x'] +
= ($spacew / $this->k
);
18017 $this->xobjects
[$this->xobjid
]['annotations'][$pak]['w'] +
= (($spacewidth * $pac['numspaces']) / $this->k
);
18019 $this->xobjects
[$this->xobjid
]['annotations'][$pak]['w'] +
= (($spacewidth * $pac['numspaces']) / $this->k
);
18024 } elseif (isset($this->PageAnnots
[$this->page
])) {
18025 foreach ($this->PageAnnots
[$this->page
] as $pak => $pac) {
18026 if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k
) >= ($currentxpos - $this->feps
)) AND (($pac['x'] * $this->k
) <= ($currentxpos +
$this->feps
))) {
18027 if ($cxpos > $lmpos) {
18028 $this->PageAnnots
[$this->page
][$pak]['x'] +
= ($spacew / $this->k
);
18029 $this->PageAnnots
[$this->page
][$pak]['w'] +
= (($spacewidth * $pac['numspaces']) / $this->k
);
18031 $this->PageAnnots
[$this->page
][$pak]['w'] +
= (($spacewidth * $pac['numspaces']) / $this->k
);
18039 $pmid = str_replace('x*#!#*x', '', $pmid);
18040 if ($this->isUnicodeFont()) {
18041 // multibyte characters
18042 $spacew = $spacewidthu;
18043 if ($this->font_stretching
!= 100) {
18044 // word spacing is affected by stretching
18045 $spacew /= ($this->font_stretching
/ 100);
18047 // escape special characters
18049 $pmid = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmid);
18050 $pmid = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmid);
18051 if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmid, $pamatch) > 0) {
18052 foreach($pamatch[0] as $pk => $pmatch) {
18053 $replace = $pamatch[1][$pk];
18054 $replace = str_replace('#!#OP#!#', '(', $replace);
18055 $replace = str_replace('#!#CP#!#', ')', $replace);
18056 $newpmid = '[('.str_replace(chr(0).chr(32), ') '.sprintf('%F', $spacew).' (', $replace).')]';
18057 $pos = strpos($pmid, $pmatch, $pos);
18058 if ($pos !== FALSE) {
18059 $pmid = substr_replace($pmid, $newpmid, $pos, strlen($pmatch));
18065 if ($this->inxobj
) {
18066 // we are inside an XObject template
18067 $this->xobjects
[$this->xobjid
]['outdata'] = $pstart."\n".$pmid."\n".$pend;
18069 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
18071 $endlinepos = strlen($pstart."\n".$pmid."\n");
18073 // non-unicode (single-byte characters)
18074 if ($this->font_stretching
!= 100) {
18075 // word spacing (Tw) is affected by stretching
18076 $spacewidth /= ($this->font_stretching
/ 100);
18078 $rs = sprintf('%F Tw', $spacewidth);
18079 $pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
18080 if ($this->inxobj
) {
18081 // we are inside an XObject template
18082 $this->xobjects
[$this->xobjid
]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
18084 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
18086 $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
18090 } // end if $startlinex
18091 if (($t_x != 0) OR ($yshift < 0)) {
18093 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k
), ($yshift * $this->k
));
18094 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18095 $endlinepos = strlen($pstart);
18096 if ($this->inxobj
) {
18097 // we are inside an XObject template
18098 $this->xobjects
[$this->xobjid
]['outdata'] = $pstart.$pend;
18099 foreach ($this->xobjects
[$this->xobjid
]['annotations'] as $pak => $pac) {
18100 if ($pak >= $pask) {
18101 $this->xobjects
[$this->xobjid
]['annotations'][$pak]['x'] +
= $t_x;
18102 $this->xobjects
[$this->xobjid
]['annotations'][$pak]['y'] -= $yshift;
18106 $this->setPageBuffer($startlinepage, $pstart.$pend);
18107 // shift the annotations and links
18108 if (isset($this->PageAnnots
[$this->page
])) {
18109 foreach ($this->PageAnnots
[$this->page
] as $pak => $pac) {
18110 if ($pak >= $pask) {
18111 $this->PageAnnots
[$this->page
][$pak]['x'] +
= $t_x;
18112 $this->PageAnnots
[$this->page
][$pak]['y'] -= $yshift;
18117 $this->y
-= $yshift;
18120 $pbrk = $this->checkPageBreak($this->lasth
);
18121 $this->newline
= false;
18122 $startlinex = $this->x
;
18123 $startliney = $this->y
;
18124 if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
18125 $startliney -= ((0.3 * $this->FontSizePt
) / $this->k
);
18126 } elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
18127 $startliney -= (($this->FontSizePt
/ 0.7) / $this->k
);
18129 $minstartliney = $startliney;
18130 $maxbottomliney = ($this->y +
$this->getCellHeight($fontsize / $this->k
));
18132 $startlinepage = $this->page
;
18133 if (isset($endlinepos) AND (!$pbrk)) {
18134 $startlinepos = $endlinepos;
18136 if ($this->inxobj
) {
18137 // we are inside an XObject template
18138 $startlinepos = strlen($this->xobjects
[$this->xobjid
]['outdata']);
18139 } elseif (!$this->InFooter
) {
18140 if (isset($this->footerlen
[$this->page
])) {
18141 $this->footerpos
[$this->page
] = $this->pagelen
[$this->page
] - $this->footerlen
[$this->page
];
18143 $this->footerpos
[$this->page
] = $this->pagelen
[$this->page
];
18145 $startlinepos = $this->footerpos
[$this->page
];
18147 $startlinepos = $this->pagelen
[$this->page
];
18150 unset($endlinepos);
18151 $plalign = $lalign;
18152 if (isset($this->PageAnnots
[$this->page
])) {
18153 $pask = count($this->PageAnnots
[$this->page
]);
18157 if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table')
18158 AND (isset($this->emptypagemrk
[$this->page
]))
18159 AND ($this->emptypagemrk
[$this->page
] == $this->pagelen
[$this->page
]))) {
18160 $this->setFont($fontname, $fontstyle, $fontsize);
18162 $this->setFillColorArray($this->bgcolor
);
18166 if (isset($opentagpos)) {
18167 unset($opentagpos);
18169 if ($dom[$key]['tag']) {
18170 if ($dom[$key]['opening']) {
18171 // get text indentation (if any)
18172 if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
18173 $this->textindent
= $dom[$key]['text-indent'];
18174 $this->newline
= true;
18177 if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18178 // available page width
18180 $wtmp = $this->x
- $this->lMargin
;
18182 $wtmp = $this->w
- $this->rMargin
- $this->x
;
18184 // get cell spacing
18185 if (isset($dom[$key]['attribute']['cellspacing'])) {
18186 $clsp = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
18187 $cellspacing = array('H' => $clsp, 'V' => $clsp);
18188 } elseif (isset($dom[$key]['border-spacing'])) {
18189 $cellspacing = $dom[$key]['border-spacing'];
18191 $cellspacing = array('H' => 0, 'V' => 0);
18194 if (isset($dom[$key]['width'])) {
18195 $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18197 $table_width = $wtmp;
18199 $table_width -= (2 * $cellspacing['H']);
18200 if (!$this->inthead
) {
18201 $this->y +
= $cellspacing['V'];
18204 $cellspacingx = -$cellspacing['H'];
18206 $cellspacingx = $cellspacing['H'];
18208 // total table width without cellspaces
18209 $table_columns_width = ($table_width - ($cellspacing['H'] * ($dom[$key]['cols'] - 1)));
18210 // minimum column width
18211 $table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
18212 // array of custom column widths
18213 $table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
18216 if ($dom[$key]['value'] == 'tr') {
18217 // reset column counter
18221 if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
18222 $trid = $dom[$key]['parent'];
18223 $table_el = $dom[$trid]['parent'];
18224 if (!isset($dom[$table_el]['cols'])) {
18225 $dom[$table_el]['cols'] = $dom[$trid]['cols'];
18227 // store border info
18229 if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
18230 $tdborder = $dom[$key]['border'];
18232 $colspan = intval($dom[$key]['attribute']['colspan']);
18233 if ($colspan <= 0) {
18236 $old_cell_padding = $this->cell_padding
;
18237 if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
18238 $crclpd = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
18239 $current_cell_padding = array('L' => $crclpd, 'T' => $crclpd, 'R' => $crclpd, 'B' => $crclpd);
18240 } elseif (isset($dom[($dom[$trid]['parent'])]['padding'])) {
18241 $current_cell_padding = $dom[($dom[$trid]['parent'])]['padding'];
18243 $current_cell_padding = array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0);
18245 $this->cell_padding
= $current_cell_padding;
18246 if (isset($dom[$key]['height'])) {
18247 // minimum cell height
18248 $cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
18252 if (isset($dom[$key]['content'])) {
18253 $cell_content = $dom[$key]['content'];
18255 $cell_content = ' ';
18257 $tagtype = $dom[$key]['value'];
18259 while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
18260 // move $key index forward
18263 if (!isset($dom[$trid]['startpage'])) {
18264 $dom[$trid]['startpage'] = $this->page
;
18266 $this->setPage($dom[$trid]['startpage']);
18268 if (!isset($dom[$trid]['startcolumn'])) {
18269 $dom[$trid]['startcolumn'] = $this->current_column
;
18270 } elseif ($this->current_column
!= $dom[$trid]['startcolumn']) {
18272 $this->selectColumn($dom[$trid]['startcolumn']);
18275 if (!isset($dom[$trid]['starty'])) {
18276 $dom[$trid]['starty'] = $this->y
;
18278 $this->y
= $dom[$trid]['starty'];
18280 if (!isset($dom[$trid]['startx'])) {
18281 $dom[$trid]['startx'] = $this->x
;
18282 $this->x +
= $cellspacingx;
18284 $this->x +
= ($cellspacingx / 2);
18286 if (isset($dom[$parentid]['attribute']['rowspan'])) {
18287 $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18291 // skip row-spanned cells started on the previous rows
18292 if (isset($dom[$table_el]['rowspans'])) {
18294 $rskmax = count($dom[$table_el]['rowspans']);
18295 while ($rsk < $rskmax) {
18296 $trwsp = $dom[$table_el]['rowspans'][$rsk];
18297 $rsstartx = $trwsp['startx'];
18298 $rsendx = $trwsp['endx'];
18299 // account for margin changes
18300 if ($trwsp['startpage'] < $this->page
) {
18301 if (($this->rtl
) AND ($this->pagedim
[$this->page
]['orm'] != $this->pagedim
[$trwsp['startpage']]['orm'])) {
18302 $dl = ($this->pagedim
[$this->page
]['orm'] - $this->pagedim
[$trwsp['startpage']]['orm']);
18305 } elseif ((!$this->rtl
) AND ($this->pagedim
[$this->page
]['olm'] != $this->pagedim
[$trwsp['startpage']]['olm'])) {
18306 $dl = ($this->pagedim
[$this->page
]['olm'] - $this->pagedim
[$trwsp['startpage']]['olm']);
18311 if (($trwsp['rowspan'] > 0)
18312 AND ($rsstartx > ($this->x
- $cellspacing['H'] - $current_cell_padding['L'] - $this->feps
))
18313 AND ($rsstartx < ($this->x +
$cellspacing['H'] +
$current_cell_padding['R'] +
$this->feps
))
18314 AND (($trwsp['starty'] < ($this->y
- $this->feps
)) OR ($trwsp['startpage'] < $this->page
) OR ($trwsp['startcolumn'] < $this->current_column
))) {
18315 // set the starting X position of the current cell
18316 $this->x
= $rsendx +
$cellspacingx;
18317 // increment column indicator
18318 $colid +
= $trwsp['colspan'];
18319 if (($trwsp['rowspan'] == 1)
18320 AND (isset($dom[$trid]['endy']))
18321 AND (isset($dom[$trid]['endpage']))
18322 AND (isset($dom[$trid]['endcolumn']))
18323 AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
18324 AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18325 // set ending Y position for row
18326 $dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18327 $dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
18335 if (isset($dom[$parentid]['width'])) {
18336 // user specified width
18337 $cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
18338 $tmpcw = ($cellw / $colspan);
18339 for ($i = 0; $i < $colspan; ++
$i) {
18340 $table_colwidths[($colid +
$i)] = $tmpcw;
18343 // inherit column width
18345 for ($i = 0; $i < $colspan; ++
$i) {
18346 $cellw +
= (isset($table_colwidths[($colid +
$i)]) ?
$table_colwidths[($colid +
$i)] : 0);
18349 $cellw +
= (($colspan - 1) * $cellspacing['H']);
18350 // increment column indicator
18351 $colid +
= $colspan;
18352 // add rowspan information to table element
18353 if ($rowspan > 1) {
18354 $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
));
18356 $cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x
));
18357 if ($rowspan > 1) {
18358 $dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
18360 // push background colors
18361 if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
18362 $dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
18364 // store border info
18365 if (!empty($tdborder)) {
18366 $dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
18368 $prevLastH = $this->lasth
;
18369 // store some info for multicolumn mode
18371 $this->colxshift
['x'] = $this->w
- $this->x
- $this->rMargin
;
18373 $this->colxshift
['x'] = $this->x
- $this->lMargin
;
18375 $this->colxshift
['s'] = $cellspacing;
18376 $this->colxshift
['p'] = $current_cell_padding;
18377 // ****** write the cell content ******
18378 $this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true, true, 0, 'T', false);
18379 // restore some values
18380 $this->colxshift
= array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
18381 $this->lasth
= $prevLastH;
18382 $this->cell_padding
= $old_cell_padding;
18383 $dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x
;
18384 // update the end of row position
18385 if ($rowspan <= 1) {
18386 if (isset($dom[$trid]['endy'])) {
18387 if (($this->page
== $dom[$trid]['endpage']) AND ($this->current_column
== $dom[$trid]['endcolumn'])) {
18388 $dom[$trid]['endy'] = max($this->y
, $dom[$trid]['endy']);
18389 } elseif (($this->page
> $dom[$trid]['endpage']) OR ($this->current_column
> $dom[$trid]['endcolumn'])) {
18390 $dom[$trid]['endy'] = $this->y
;
18393 $dom[$trid]['endy'] = $this->y
;
18395 if (isset($dom[$trid]['endpage'])) {
18396 $dom[$trid]['endpage'] = max($this->page
, $dom[$trid]['endpage']);
18398 $dom[$trid]['endpage'] = $this->page
;
18400 if (isset($dom[$trid]['endcolumn'])) {
18401 $dom[$trid]['endcolumn'] = max($this->current_column
, $dom[$trid]['endcolumn']);
18403 $dom[$trid]['endcolumn'] = $this->current_column
;
18406 // account for row-spanned cells
18407 $dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x
;
18408 $dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y
;
18409 $dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page
;
18410 $dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column
;
18412 if (isset($dom[$table_el]['rowspans'])) {
18413 // update endy and endpage on rowspanned cells
18414 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
18415 if ($trwsp['rowspan'] > 0) {
18416 if (isset($dom[$trid]['endpage'])) {
18417 if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
18418 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
18419 } elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
18420 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
18421 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
18422 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
18424 $dom[$trid]['endy'] = $this->pagedim
[$dom[$trid]['endpage']]['hk'] - $this->pagedim
[$dom[$trid]['endpage']]['bm'];
18430 $this->x +
= ($cellspacingx / 2);
18432 // opening tag (or self-closing tag)
18433 if (!isset($opentagpos)) {
18434 if ($this->inxobj
) {
18435 // we are inside an XObject template
18436 $opentagpos = strlen($this->xobjects
[$this->xobjid
]['outdata']);
18437 } elseif (!$this->InFooter
) {
18438 if (isset($this->footerlen
[$this->page
])) {
18439 $this->footerpos
[$this->page
] = $this->pagelen
[$this->page
] - $this->footerlen
[$this->page
];
18441 $this->footerpos
[$this->page
] = $this->pagelen
[$this->page
];
18443 $opentagpos = $this->footerpos
[$this->page
];
18446 $dom = $this->openHTMLTagHandler($dom, $key, $cell);
18448 } else { // closing tag
18449 $prev_numpages = $this->numpages
;
18450 $old_bordermrk = $this->bordermrk
[$this->page
];
18451 $dom = $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
18452 if ($this->bordermrk
[$this->page
] > $old_bordermrk) {
18453 $startlinepos +
= ($this->bordermrk
[$this->page
] - $old_bordermrk);
18455 if ($prev_numpages > $this->numpages
) {
18456 $startlinepage = $this->page
;
18459 } elseif (strlen($dom[$key]['value']) > 0) {
18461 if (!TCPDF_STATIC
::empty_string($this->lispacer
) AND ($this->lispacer
!= '^')) {
18462 $this->setFont($pfontname, $pfontstyle, $pfontsize);
18463 $this->resetLastH();
18464 $minstartliney = $this->y
;
18465 $maxbottomliney = ($startliney +
$this->getCellHeight($this->FontSize
));
18466 if (is_numeric($pfontsize) AND ($pfontsize > 0)) {
18467 $this->putHtmlListBullet($this->listnum
, $this->lispacer
, $pfontsize);
18469 $this->setFont($curfontname, $curfontstyle, $curfontsize);
18470 $this->resetLastH();
18471 if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
18472 $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
18473 $pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
18474 $this->y +
= ($this->getCellHeight(($pfontsize - $curfontsize) / $this->k
) +
$pfontascent - $curfontascent - $pfontdescent +
$curfontdescent) / 2;
18475 $minstartliney = min($this->y
, $minstartliney);
18476 $maxbottomliney = max(($this->y +
$this->getCellHeight($pfontsize / $this->k
)), $maxbottomliney);
18480 $this->htmlvspace
= 0;
18481 $isRTLString = preg_match(TCPDF_FONT_DATA
::$uni_RE_PATTERN_RTL, $dom[$key]['value']) ||
preg_match(TCPDF_FONT_DATA
::$uni_RE_PATTERN_ARABIC, $dom[$key]['value']);
18482 if ((!$this->premode
) AND $this->isRTLTextDir() AND !$isRTLString) {
18483 // reverse spaces order
18484 $lsp = ''; // left spaces
18485 $rsp = ''; // right spaces
18486 if (preg_match('/^('.$this->re_space
['p'].'+)/'.$this->re_space
['m'], $dom[$key]['value'], $matches)) {
18487 $lsp = $matches[1];
18489 if (preg_match('/('.$this->re_space
['p'].'+)$/'.$this->re_space
['m'], $dom[$key]['value'], $matches)) {
18490 $rsp = $matches[1];
18492 $dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
18495 if (!$this->premode
) {
18496 $prelen = strlen($dom[$key]['value']);
18497 if ($this->isRTLTextDir() AND !$isRTLString) {
18498 // right trim except non-breaking space
18499 $dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
18501 // left trim except non-breaking space
18502 $dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
18504 $postlen = strlen($dom[$key]['value']);
18505 if (($postlen == 0) AND ($prelen > 0)) {
18506 $dom[$key]['trimmed_space'] = true;
18510 $firstblock = true;
18512 $firstblock = false;
18513 // replace empty multiple spaces string with a single space
18514 $dom[$key]['value'] = preg_replace('/^'.$this->re_space
['p'].'+$/'.$this->re_space
['m'], chr(32), $dom[$key]['value']);
18518 $this->x
-= $this->textindent
;
18520 $this->x +
= $this->textindent
;
18522 if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
18523 $strlinelen = $this->GetStringWidth($dom[$key]['value']);
18524 if (!empty($this->HREF
) AND (isset($this->HREF
['url']))) {
18527 if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18528 $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18531 if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
18532 $hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
18534 $strrest = $this->addHtmlLink($this->HREF
['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
18536 $wadj = 0; // space to leave for block continuity
18538 $cwa = ($this->x
- $this->lMargin
);
18540 $cwa = ($this->w
- $this->rMargin
- $this->x
);
18542 if (($strlinelen < $cwa) AND (isset($dom[($key +
1)])) AND ($dom[($key +
1)]['tag']) AND (!$dom[($key +
1)]['block'])) {
18543 // check the next text blocks for continuity
18544 $nkey = ($key +
1);
18545 $write_block = true;
18546 $same_textdir = true;
18547 $tmp_fontname = $this->FontFamily
;
18548 $tmp_fontstyle = $this->FontStyle
;
18549 $tmp_fontsize = $this->FontSizePt
;
18550 while ($write_block AND isset($dom[$nkey])) {
18551 if ($dom[$nkey]['tag']) {
18552 if ($dom[$nkey]['block']) {
18554 $write_block = false;
18556 $tmp_fontname = isset($dom[$nkey]['fontname']) ?
$dom[$nkey]['fontname'] : $this->FontFamily
;
18557 $tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ?
$dom[$nkey]['fontstyle'] : $this->FontStyle
;
18558 $tmp_fontsize = isset($dom[$nkey]['fontsize']) ?
$dom[$nkey]['fontsize'] : $this->FontSizePt
;
18559 $same_textdir = ($dom[$nkey]['dir'] == $dom[$key]['dir']);
18561 $nextstr = TCPDF_STATIC
::pregSplit('/'.$this->re_space
['p'].'+/', $this->re_space
['m'], $dom[$nkey]['value']);
18562 if (isset($nextstr[0]) AND $same_textdir) {
18563 $wadj +
= $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
18564 if (isset($nextstr[1])) {
18565 $write_block = false;
18572 if (($wadj > 0) AND (($strlinelen +
$wadj) >= $cwa)) {
18574 $nextstr = TCPDF_STATIC
::pregSplit('/'.$this->re_space
['p'].'/', $this->re_space
['m'], $dom[$key]['value']);
18575 $numblks = count($nextstr);
18576 if ($numblks > 1) {
18577 // try to split on blank spaces
18578 $wadj = ($cwa - $strlinelen +
$this->GetStringWidth($nextstr[($numblks - 1)]));
18580 // set the entire block on new line
18581 $wadj = $this->GetStringWidth($nextstr[0]);
18584 // check for reversed text direction
18585 if (($wadj > 0) AND (($this->rtl
AND ($this->tmprtl
=== 'L')) OR (!$this->rtl
AND ($this->tmprtl
=== 'R')))) {
18586 // LTR text on RTL direction or RTL text on LTR direction
18587 $reverse_dir = true;
18588 $this->rtl
= !$this->rtl
;
18589 $revshift = ($strlinelen +
$wadj +
0.000001); // add little quantity for rounding problems
18591 $this->x +
= $revshift;
18593 $this->x
-= $revshift;
18597 // ****** write only until the end of the line and get the rest ******
18598 $strrest = $this->Write($this->lasth
, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
18599 // restore default direction
18600 if ($reverse_dir AND ($wadj == 0)) {
18601 $this->x
= $xws; // @phpstan-ignore-line
18602 $this->rtl
= !$this->rtl
;
18603 $reverse_dir = false;
18607 $this->textindent
= 0;
18608 if (strlen($strrest) > 0) {
18609 // store the remaining string on the previous $key position
18610 $this->newline
= true;
18611 if ($strrest == $dom[$key]['value']) {
18612 // used to avoid infinite loop
18617 $dom[$key]['value'] = $strrest;
18620 $this->x
-= $this->cell_padding
['R'];
18622 $this->x +
= $this->cell_padding
['L'];
18630 // add the positive font spacing of the last character (if any)
18631 if ($this->font_spacing
> 0) {
18633 $this->x
-= $this->font_spacing
;
18635 $this->x +
= $this->font_spacing
;
18641 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')) {
18642 // check if we are on a new page or on a new column
18643 if ((!$undo) AND (($this->y
< $this->start_transaction_y
) OR (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['endy'] < $this->start_transaction_y
)))) {
18644 // we are on a new page or on a new column and the total object height is less than the available vertical space.
18645 // restore previous object
18646 $this->rollbackTransaction(true);
18647 // restore previous values
18648 foreach ($this_method_vars as $vkey => $vval) {
18651 if (!empty($dom[$key]['thead'])) {
18652 $this->inthead
= true;
18654 // add a page (or trig AcceptPageBreak() for multicolumn mode)
18656 if ((!$this->checkPageBreak($this->PageBreakTrigger +
1)) AND ($this->y
< $pre_y)) {
18657 $startliney = $this->y
;
18659 $undo = true; // avoid infinite loop
18664 } // end for each $key
18665 // align the last line
18666 if (isset($startlinex)) {
18667 $yshift = ($minstartliney - $startliney);
18668 if (($yshift > 0) OR ($this->page
> $startlinepage)) {
18672 // the last line must be shifted to be aligned as requested
18673 $linew = abs($this->endlinex
- $startlinex);
18674 if ($this->inxobj
) {
18675 // we are inside an XObject template
18676 $pstart = substr($this->xobjects
[$this->xobjid
]['outdata'], 0, $startlinepos);
18677 if (isset($opentagpos)) {
18678 $midpos = $opentagpos;
18683 $pmid = substr($this->xobjects
[$this->xobjid
]['outdata'], $startlinepos, ($midpos - $startlinepos));
18684 $pend = substr($this->xobjects
[$this->xobjid
]['outdata'], $midpos);
18686 $pmid = substr($this->xobjects
[$this->xobjid
]['outdata'], $startlinepos);
18690 $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
18691 if (isset($opentagpos) AND isset($this->footerlen
[$startlinepage]) AND (!$this->InFooter
)) {
18692 $this->footerpos
[$startlinepage] = $this->pagelen
[$startlinepage] - $this->footerlen
[$startlinepage];
18693 $midpos = min($opentagpos, $this->footerpos
[$startlinepage]);
18694 } elseif (isset($opentagpos)) {
18695 $midpos = $opentagpos;
18696 } elseif (isset($this->footerlen
[$startlinepage]) AND (!$this->InFooter
)) {
18697 $this->footerpos
[$startlinepage] = $this->pagelen
[$startlinepage] - $this->footerlen
[$startlinepage];
18698 $midpos = $this->footerpos
[$startlinepage];
18703 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18704 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
18706 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18710 if ((((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl
)) OR (($plalign == 'L') AND ($this->rtl
)))))) {
18711 // calculate shifting amount
18713 if ($this->lMargin
!= $prevlMargin) {
18714 $tw +
= ($prevlMargin - $this->lMargin
);
18716 if ($this->rMargin
!= $prevrMargin) {
18717 $tw +
= ($prevrMargin - $this->rMargin
);
18719 $one_space_width = $this->GetStringWidth(chr(32));
18720 $no = 0; // number of spaces on a line contained on a single block
18721 if ($this->isRTLTextDir()) { // RTL
18722 // remove left space if exist
18723 $pos1 = TCPDF_STATIC
::revstrpos($pmid, '[(');
18725 $pos1 = intval($pos1);
18726 if ($this->isUnicodeFont()) {
18727 $pos2 = intval(TCPDF_STATIC
::revstrpos($pmid, '[('.chr(0).chr(32)));
18730 $pos2 = intval(TCPDF_STATIC
::revstrpos($pmid, '[('.chr(32)));
18733 if ($pos1 == $pos2) {
18734 $pmid = substr($pmid, 0, ($pos1 +
2)).substr($pmid, ($pos1 +
2 +
$spacelen));
18735 if (substr($pmid, $pos1, 4) == '[()]') {
18736 $linew -= $one_space_width;
18737 } elseif ($pos1 == strpos($pmid, '[(')) {
18743 // remove right space if exist
18744 $pos1 = TCPDF_STATIC
::revstrpos($pmid, ')]');
18746 $pos1 = intval($pos1);
18747 if ($this->isUnicodeFont()) {
18748 $pos2 = intval(TCPDF_STATIC
::revstrpos($pmid, chr(0).chr(32).')]')) +
2;
18751 $pos2 = intval(TCPDF_STATIC
::revstrpos($pmid, chr(32).')]')) +
1;
18754 if ($pos1 == $pos2) {
18755 $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
18756 $linew -= $one_space_width;
18760 $mdiff = ($tw - $linew);
18761 if ($plalign == 'C') {
18763 $t_x = -($mdiff / 2);
18765 $t_x = ($mdiff / 2);
18767 } elseif ($plalign == 'R') {
18768 // right alignment on LTR document
18770 } elseif ($plalign == 'L') {
18771 // left alignment on RTL document
18774 } // end if startlinex
18775 if (($t_x != 0) OR ($yshift < 0)) {
18777 $trx = sprintf('1 0 0 1 %F %F cm', ($t_x * $this->k
), ($yshift * $this->k
));
18778 $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
18779 $endlinepos = strlen($pstart);
18780 if ($this->inxobj
) {
18781 // we are inside an XObject template
18782 $this->xobjects
[$this->xobjid
]['outdata'] = $pstart.$pend;
18783 foreach ($this->xobjects
[$this->xobjid
]['annotations'] as $pak => $pac) {
18784 if ($pak >= $pask) {
18785 $this->xobjects
[$this->xobjid
]['annotations'][$pak]['x'] +
= $t_x;
18786 $this->xobjects
[$this->xobjid
]['annotations'][$pak]['y'] -= $yshift;
18790 $this->setPageBuffer($startlinepage, $pstart.$pend);
18791 // shift the annotations and links
18792 if (isset($this->PageAnnots
[$this->page
])) {
18793 foreach ($this->PageAnnots
[$this->page
] as $pak => $pac) {
18794 if ($pak >= $pask) {
18795 $this->PageAnnots
[$this->page
][$pak]['x'] +
= $t_x;
18796 $this->PageAnnots
[$this->page
][$pak]['y'] -= $yshift;
18801 $this->y
-= $yshift;
18805 // restore previous values
18806 $this->setGraphicVars($gvars);
18807 if ($this->num_columns
> 1) {
18808 $this->selectColumn();
18809 } elseif ($this->page
> $prevPage) {
18810 $this->lMargin
= $this->pagedim
[$this->page
]['olm'];
18811 $this->rMargin
= $this->pagedim
[$this->page
]['orm'];
18813 // restore previous list state
18814 $this->cell_height_ratio
= $prev_cell_height_ratio;
18815 $this->listnum
= $prev_listnum;
18816 $this->listordered
= $prev_listordered;
18817 $this->listcount
= $prev_listcount;
18818 $this->lispacer
= $prev_lispacer;
18819 if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
18820 $this->Ln($this->lasth
);
18821 if (($this->y
< $maxbottomliney) AND ($startlinepage == $this->page
)) {
18822 $this->y
= $maxbottomliney;
18829 * Process opening tags.
18830 * @param array $dom html dom array
18831 * @param int $key current element id
18832 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
18833 * @return array $dom
18836 protected function openHTMLTagHandler($dom, $key, $cell) {
18838 $parent = $dom[($dom[$key]['parent'])];
18839 $firsttag = ($key == 1);
18840 // check for text direction attribute
18841 if (isset($tag['dir'])) {
18842 $this->setTempRTL($tag['dir']);
18844 $this->tmprtl
= false;
18846 if ($tag['block']) {
18847 $hbz = 0; // distance from y to line bottom
18848 $hb = 0; // vertical space between block tags
18849 // calculate vertical space for block tags
18850 if (isset($this->tagvspaces
[$tag['value']][0]['h']) && !empty($this->tagvspaces
[$tag['value']][0]['h']) && ($this->tagvspaces
[$tag['value']][0]['h'] >= 0)) {
18851 $cur_h = $this->tagvspaces
[$tag['value']][0]['h'];
18852 } elseif (isset($tag['fontsize'])) {
18853 $cur_h = $this->getCellHeight($tag['fontsize'] / $this->k
);
18855 $cur_h = $this->getCellHeight($this->FontSize
);
18857 if (isset($this->tagvspaces
[$tag['value']][0]['n'])) {
18858 $on = $this->tagvspaces
[$tag['value']][0]['n'];
18859 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18864 if ((!isset($this->tagvspaces
[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18867 $hb = ($on * $cur_h);
18869 if (($this->htmlvspace
<= 0) AND ($on > 0)) {
18870 if (isset($parent['fontsize'])) {
18871 $hbz = (($parent['fontsize'] / $this->k
) * $this->cell_height_ratio
);
18873 $hbz = $this->getCellHeight($this->FontSize
);
18876 if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18877 // fix vertical space after table
18880 // closing vertical space
18882 if (isset($this->tagvspaces
[$tag['value']][1]['h']) && !empty($this->tagvspaces
[$tag['value']][1]['h']) && ($this->tagvspaces
[$tag['value']][1]['h'] >= 0)) {
18883 $pre_h = $this->tagvspaces
[$tag['value']][1]['h'];
18884 } elseif (isset($parent['fontsize'])) {
18885 $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k
);
18887 $pre_h = $this->getCellHeight($this->FontSize
);
18889 if (isset($this->tagvspaces
[$tag['value']][1]['n'])) {
18890 $cn = $this->tagvspaces
[$tag['value']][1]['n'];
18891 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
18896 if (isset($this->tagvspaces
[$tag['value']][1])) {
18897 $hbc = ($cn * $pre_h);
18901 switch($tag['value']) {
18905 $dom[$key]['rowspans'] = array();
18906 if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
18907 $this->htmlvspace
= 0;
18908 // set table header
18909 if (!TCPDF_STATIC
::empty_string($dom[$key]['thead'])) {
18910 // set table header
18911 $this->thead
= $dom[$key]['thead'];
18912 if (!isset($this->theadMargins
) OR (empty($this->theadMargins
))) {
18913 $this->theadMargins
= array();
18914 $this->theadMargins
['cell_padding'] = $this->cell_padding
;
18915 $this->theadMargins
['lmargin'] = $this->lMargin
;
18916 $this->theadMargins
['rmargin'] = $this->rMargin
;
18917 $this->theadMargins
['page'] = $this->page
;
18918 $this->theadMargins
['cell'] = $cell;
18919 $this->theadMargins
['gvars'] = $this->getGraphicVars();
18923 // store current margins and page
18924 $dom[$key]['old_cell_padding'] = $this->cell_padding
;
18925 if (isset($tag['attribute']['cellpadding'])) {
18926 $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
18927 $this->setCellPadding($pad);
18928 } elseif (isset($tag['padding'])) {
18929 $this->cell_padding
= $tag['padding'];
18931 if (isset($tag['attribute']['cellspacing'])) {
18932 $cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
18933 } elseif (isset($tag['border-spacing'])) {
18934 $cs = $tag['border-spacing']['V'];
18936 $prev_y = $this->y
;
18937 if ($this->checkPageBreak(((2 * $cp) +
(2 * $cs) +
$this->lasth
), '', false) OR ($this->y
< $prev_y)) {
18938 $this->inthead
= true;
18939 // add a page (or trig AcceptPageBreak() for multicolumn mode)
18940 $this->checkPageBreak($this->PageBreakTrigger +
1);
18945 // array of columns positions
18946 $dom[$key]['cellpos'] = array();
18950 if ((isset($tag['height'])) AND ($tag['height'] != '')) {
18951 $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
18953 $hrHeight = $this->GetLineWidth();
18955 $this->addHTMLVertSpace($hbz, max($hb, ($hrHeight / 2)), $cell, $firsttag);
18956 $x = $this->GetX();
18957 $y = $this->GetY();
18958 $wtmp = $this->w
- $this->lMargin
- $this->rMargin
;
18960 $wtmp -= ($this->cell_padding
['L'] +
$this->cell_padding
['R']);
18962 if ((isset($tag['width'])) AND ($tag['width'] != '')) {
18963 $hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
18967 $prevlinewidth = $this->GetLineWidth();
18968 $this->setLineWidth($hrHeight);
18970 $lineStyle = array();
18971 if (isset($tag['fgcolor'])) {
18972 $lineStyle['color'] = $tag['fgcolor'];
18975 if (isset($tag['fgcolor'])) {
18976 $lineStyle['color'] = $tag['fgcolor'];
18979 if (isset($tag['style']['cap'])) {
18980 $lineStyle['cap'] = $tag['style']['cap'];
18983 if (isset($tag['style']['join'])) {
18984 $lineStyle['join'] = $tag['style']['join'];
18987 if (isset($tag['style']['dash'])) {
18988 $lineStyle['dash'] = $tag['style']['dash'];
18991 if (isset($tag['style']['phase'])) {
18992 $lineStyle['phase'] = $tag['style']['phase'];
18995 $lineStyle = array_filter($lineStyle);
18997 $this->Line($x, $y, $x +
$hrWidth, $y, $lineStyle);
18998 $this->setLineWidth($prevlinewidth);
18999 $this->addHTMLVertSpace(max($hbc, ($hrHeight / 2)), 0, $cell, !isset($dom[($key +
1)]));
19003 if (array_key_exists('href', $tag['attribute'])) {
19004 $this->HREF
['url'] = $tag['attribute']['href'];
19009 if (empty($tag['attribute']['src'])) {
19012 $imgsrc = $tag['attribute']['src'];
19013 if ($imgsrc[0] === '@') {
19015 $imgsrc = '@'.base64_decode(substr($imgsrc, 1));
19017 } else if (preg_match('@^data:image/([^;]*);base64,(.*)@', $imgsrc, $reg)) {
19018 $imgsrc = '@'.base64_decode($reg[2]);
19020 } elseif ( $this->allowLocalFiles
&& substr($imgsrc, 0, 7) === 'file://') {
19021 // get image type from a local file path
19022 $imgsrc = substr($imgsrc, 7);
19023 $type = TCPDF_IMAGES
::getImageFileType($imgsrc);
19025 if (($imgsrc[0] === '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
19027 $findroot = strpos($imgsrc, $_SERVER['DOCUMENT_ROOT']);
19028 if (($findroot === false) OR ($findroot > 1)) {
19029 if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
19030 $imgsrc = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$imgsrc;
19032 $imgsrc = $_SERVER['DOCUMENT_ROOT'].$imgsrc;
19035 $imgsrc = urldecode($imgsrc);
19036 $testscrtype = @parse_url
($imgsrc);
19037 if (empty($testscrtype['query'])) {
19038 // convert URL to server path
19039 $imgsrc = str_replace(K_PATH_URL
, K_PATH_MAIN
, $imgsrc);
19040 } elseif (preg_match('|^https?://|', $imgsrc) !== 1) {
19041 // convert URL to server path
19042 $imgsrc = str_replace(K_PATH_MAIN
, K_PATH_URL
, $imgsrc);
19046 $type = TCPDF_IMAGES
::getImageFileType($imgsrc);
19048 if (!isset($tag['width'])) {
19051 if (!isset($tag['height'])) {
19052 $tag['height'] = 0;
19054 //if (!isset($tag['attribute']['align'])) {
19055 // the only alignment supported is "bottom"
19056 // further development is required for other modes.
19057 $tag['attribute']['align'] = 'bottom';
19059 switch($tag['attribute']['align']) {
19080 if (isset($this->HREF
['url']) AND !TCPDF_STATIC
::empty_string($this->HREF
['url'])) {
19081 $imglink = $this->HREF
['url'];
19082 if ($imglink[0] == '#') {
19083 // convert url to internal link
19084 $lnkdata = explode(',', $imglink);
19085 if (isset($lnkdata[0])) {
19086 $page = intval(substr($lnkdata[0], 1));
19087 if (empty($page) OR ($page <= 0)) {
19088 $page = $this->page
;
19090 if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
19091 $lnky = floatval($lnkdata[1]);
19095 $imglink = $this->AddLink();
19096 $this->setLink($imglink, $lnky, $page);
19101 if (isset($tag['border']) AND !empty($tag['border'])) {
19102 // currently only support 1 (frame) or a combination of 'LTRB'
19103 $border = $tag['border'];
19106 if (isset($tag['width'])) {
19107 $iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k
), 'px', false);
19110 if (isset($tag['height'])) {
19111 $ih = $this->getHTMLUnitToUnits($tag['height'], ($tag['fontsize'] / $this->k
), 'px', false);
19113 if (($type == 'eps') OR ($type == 'ai')) {
19114 $this->ImageEps($imgsrc, $xpos, $this->y
, $iw, $ih, $imglink, true, $align, '', $border, true);
19115 } elseif ($type == 'svg') {
19116 $this->ImageSVG($imgsrc, $xpos, $this->y
, $iw, $ih, $imglink, $align, '', $border, true);
19118 $this->Image($imgsrc, $xpos, $this->y
, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
19126 $this->y
= (($this->img_rb_y +
$prevy - ($this->getCellHeight($tag['fontsize'] / $this->k
))) / 2);
19130 $this->y
= $this->img_rb_y
- ($this->getCellHeight($tag['fontsize'] / $this->k
) - ($this->getFontDescent($tag['fontname'], $tag['fontstyle'], $tag['fontsize']) * $this->cell_height_ratio
));
19138 if ($this->listnum
== 1) {
19139 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19141 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19146 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19151 $this->rMargin +
= $this->listindent
;
19153 $this->lMargin +
= $this->listindent
;
19155 ++
$this->listindentlevel
;
19156 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19162 if ($tag['value'] == 'ol') {
19163 $this->listordered
[$this->listnum
] = true;
19165 $this->listordered
[$this->listnum
] = false;
19167 if (isset($tag['attribute']['start'])) {
19168 $this->listcount
[$this->listnum
] = intval($tag['attribute']['start']) - 1;
19170 $this->listcount
[$this->listnum
] = 0;
19173 $this->rMargin +
= $this->listindent
;
19174 $this->x
-= $this->listindent
;
19176 $this->lMargin +
= $this->listindent
;
19177 $this->x +
= $this->listindent
;
19179 ++
$this->listindentlevel
;
19180 if ($this->listnum
== 1) {
19182 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19185 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19191 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19193 if ($this->listordered
[$this->listnum
]) {
19195 if (isset($parent['attribute']['type']) AND !TCPDF_STATIC
::empty_string($parent['attribute']['type'])) {
19196 $this->lispacer
= $parent['attribute']['type'];
19197 } elseif (isset($parent['listtype']) AND !TCPDF_STATIC
::empty_string($parent['listtype'])) {
19198 $this->lispacer
= $parent['listtype'];
19199 } elseif (isset($this->lisymbol
) AND !TCPDF_STATIC
::empty_string($this->lisymbol
)) {
19200 $this->lispacer
= $this->lisymbol
;
19202 $this->lispacer
= '#';
19204 ++
$this->listcount
[$this->listnum
];
19205 if (isset($tag['attribute']['value'])) {
19206 $this->listcount
[$this->listnum
] = intval($tag['attribute']['value']);
19210 if (isset($parent['attribute']['type']) AND !TCPDF_STATIC
::empty_string($parent['attribute']['type'])) {
19211 $this->lispacer
= $parent['attribute']['type'];
19212 } elseif (isset($parent['listtype']) AND !TCPDF_STATIC
::empty_string($parent['listtype'])) {
19213 $this->lispacer
= $parent['listtype'];
19214 } elseif (isset($this->lisymbol
) AND !TCPDF_STATIC
::empty_string($this->lisymbol
)) {
19215 $this->lispacer
= $this->lisymbol
;
19217 $this->lispacer
= '!';
19222 case 'blockquote': {
19224 $this->rMargin +
= $this->listindent
;
19226 $this->lMargin +
= $this->listindent
;
19228 ++
$this->listindentlevel
;
19229 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19233 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19237 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19241 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19245 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19246 $this->premode
= true;
19250 $this->setXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt
) / $this->k
));
19254 $this->setXY($this->GetX(), $this->GetY() +
((0.3 * $this->FontSizePt
) / $this->k
));
19263 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19266 // Form fields (since 4.8.000 - 2009-09-07)
19268 if (isset($tag['attribute']['action'])) {
19269 $this->form_action
= $tag['attribute']['action'];
19271 $this->Error('Please explicitly set action attribute path!');
19273 if (isset($tag['attribute']['enctype'])) {
19274 $this->form_enctype
= $tag['attribute']['enctype'];
19276 $this->form_enctype
= 'application/x-www-form-urlencoded';
19278 if (isset($tag['attribute']['method'])) {
19279 $this->form_mode
= $tag['attribute']['method'];
19281 $this->form_mode
= 'post';
19286 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC
::empty_string($tag['attribute']['name'])) {
19287 $name = $tag['attribute']['name'];
19293 if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC
::empty_string($tag['attribute']['readonly'])) {
19294 $prop['readonly'] = true;
19296 if (isset($tag['attribute']['value']) AND !TCPDF_STATIC
::empty_string($tag['attribute']['value'])) {
19297 $value = $tag['attribute']['value'];
19299 if (isset($tag['attribute']['maxlength']) AND !TCPDF_STATIC
::empty_string($tag['attribute']['maxlength'])) {
19300 $opt['maxlen'] = intval($tag['attribute']['maxlength']);
19302 $h = $this->getCellHeight($this->FontSize
);
19303 if (isset($tag['attribute']['size']) AND !TCPDF_STATIC
::empty_string($tag['attribute']['size'])) {
19304 $w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
19308 if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19313 if (isset($tag['align'])) {
19314 switch ($tag['align']) {
19329 switch ($tag['attribute']['type']) {
19331 if (isset($value)) {
19332 $opt['v'] = $value;
19334 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19338 if (isset($value)) {
19339 $opt['v'] = $value;
19341 $prop['password'] = 'true';
19342 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19346 if (!isset($value)) {
19349 $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19353 if (!isset($value)) {
19356 $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19360 if (!isset($value)) {
19363 $w = $this->GetStringWidth($value) * 1.5;
19365 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19367 $action['S'] = 'SubmitForm';
19368 $action['F'] = $this->form_action
;
19369 if ($this->form_enctype
!= 'FDF') {
19370 $action['Flags'] = array('ExportFormat');
19372 if ($this->form_mode
== 'get') {
19373 $action['Flags'] = array('GetMethod');
19375 $this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
19379 if (!isset($value)) {
19382 $w = $this->GetStringWidth($value) * 1.5;
19384 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19385 $this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
19389 $prop['fileSelect'] = 'true';
19390 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19391 if (!isset($value)) {
19394 $w = $this->GetStringWidth($value) * 2;
19396 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19397 $jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
19398 $this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19402 if (isset($value)) {
19403 $opt['v'] = $value;
19405 $opt['f'] = array('invisible', 'hidden');
19406 $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19410 // THIS TYPE MUST BE FIXED
19411 if (isset($tag['attribute']['src']) AND !TCPDF_STATIC
::empty_string($tag['attribute']['src'])) {
19412 $img = $tag['attribute']['src'];
19417 //$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
19418 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19419 $jsaction = $tag['attribute']['onclick'];
19423 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19427 if (!isset($value)) {
19430 $w = $this->GetStringWidth($value) * 1.5;
19432 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19433 if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
19434 $jsaction = $tag['attribute']['onclick'];
19438 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19447 if (isset($tag['attribute']['readonly']) AND !TCPDF_STATIC
::empty_string($tag['attribute']['readonly'])) {
19448 $prop['readonly'] = true;
19450 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC
::empty_string($tag['attribute']['name'])) {
19451 $name = $tag['attribute']['name'];
19455 if (isset($tag['attribute']['value']) AND !TCPDF_STATIC
::empty_string($tag['attribute']['value'])) {
19456 $opt['v'] = $tag['attribute']['value'];
19458 if (isset($tag['attribute']['cols']) AND !TCPDF_STATIC
::empty_string($tag['attribute']['cols'])) {
19459 $w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
19463 if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC
::empty_string($tag['attribute']['rows'])) {
19464 $h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize
);
19468 $prop['multiline'] = 'true';
19469 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19473 $h = $this->getCellHeight($this->FontSize
);
19474 if (isset($tag['attribute']['size']) AND !TCPDF_STATIC
::empty_string($tag['attribute']['size'])) {
19475 $h *= ($tag['attribute']['size'] +
1);
19479 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC
::empty_string($tag['attribute']['name'])) {
19480 $name = $tag['attribute']['name'];
19485 if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC
::empty_string($tag['attribute']['opt'])) {
19486 $options = explode('#!NwL!#', $tag['attribute']['opt']);
19488 foreach ($options as $val) {
19489 if (strpos($val, '#!TaB!#') !== false) {
19490 $opts = explode('#!TaB!#', $val);
19492 $w = max($w, $this->GetStringWidth($opts[1]));
19495 $w = max($w, $this->GetStringWidth($val));
19502 if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
19503 $prop['multipleSelection'] = 'true';
19504 $this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19506 $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19511 if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML
=== true)) {
19512 // Special tag used to call TCPDF methods
19513 if (isset($tag['attribute']['method'])) {
19514 $tcpdf_method = $tag['attribute']['method'];
19515 if (method_exists($this, $tcpdf_method)) {
19516 if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
19517 $params = $this->unserializeTCPDFtagParameters($tag['attribute']['params']);
19518 call_user_func_array(array($this, $tcpdf_method), $params);
19520 $this->$tcpdf_method();
19522 $this->newline
= true;
19532 // define tags that support borders and background colors
19533 $bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
19534 if (in_array($tag['value'], $bordertags)) {
19536 $dom[$key]['borderposition'] = $this->getBorderStartPosition();
19538 if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
19539 $pba = $dom[$key]['attribute']['pagebreakafter'];
19540 // check for pagebreak
19541 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
19542 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19543 $this->checkPageBreak($this->PageBreakTrigger +
1);
19545 if ((($pba == 'left') AND (((!$this->rtl
) AND (($this->page %
2) == 0)) OR (($this->rtl
) AND (($this->page %
2) != 0))))
19546 OR (($pba == 'right') AND (((!$this->rtl
) AND (($this->page %
2) != 0)) OR (($this->rtl
) AND (($this->page %
2) == 0))))) {
19547 // add a page (or trig AcceptPageBreak() for multicolumn mode)
19548 $this->checkPageBreak($this->PageBreakTrigger +
1);
19555 * Process closing tags.
19556 * @param array $dom html dom array
19557 * @param int $key current element id
19558 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
19559 * @param int $maxbottomliney maximum y value of current line
19560 * @return array $dom
19563 protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19565 $parent = $dom[($dom[$key]['parent'])];
19566 $lasttag = ((!isset($dom[($key +
1)])) OR ((!isset($dom[($key +
2)])) AND ($dom[($key +
1)]['value'] == 'marker')));
19567 $in_table_head = false;
19568 // maximum x position (used to draw borders)
19574 if ($tag['block']) {
19575 $hbz = 0; // distance from y to line bottom
19576 $hb = 0; // vertical space between block tags
19577 // calculate vertical space for block tags
19578 if (isset($this->tagvspaces
[$tag['value']][1]['h']) && !empty($this->tagvspaces
[$tag['value']][1]['h']) && ($this->tagvspaces
[$tag['value']][1]['h'] >= 0)) {
19579 $pre_h = $this->tagvspaces
[$tag['value']][1]['h'];
19580 } elseif (isset($parent['fontsize'])) {
19581 $pre_h = $this->getCellHeight($parent['fontsize'] / $this->k
);
19583 $pre_h = $this->getCellHeight($this->FontSize
);
19585 if (isset($this->tagvspaces
[$tag['value']][1]['n'])) {
19586 $cn = $this->tagvspaces
[$tag['value']][1]['n'];
19587 } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
19592 if ((!isset($this->tagvspaces
[$tag['value']])) AND ($tag['value'] == 'div')) {
19595 $hb = ($cn * $pre_h);
19597 if ($maxbottomliney > $this->PageBreakTrigger
) {
19598 $hbz = $this->getCellHeight($this->FontSize
);
19599 } elseif ($this->y
< $maxbottomliney) {
19600 $hbz = ($maxbottomliney - $this->y
);
19604 switch($tag['value']) {
19606 $table_el = $dom[($dom[$key]['parent'])]['parent'];
19607 if (!isset($parent['endy'])) {
19608 $dom[($dom[$key]['parent'])]['endy'] = $this->y
;
19609 $parent['endy'] = $this->y
;
19611 if (!isset($parent['endpage'])) {
19612 $dom[($dom[$key]['parent'])]['endpage'] = $this->page
;
19613 $parent['endpage'] = $this->page
;
19615 if (!isset($parent['endcolumn'])) {
19616 $dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column
;
19617 $parent['endcolumn'] = $this->current_column
;
19619 // update row-spanned cells
19620 if (isset($dom[$table_el]['rowspans'])) {
19621 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19622 $dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
19623 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19624 if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
19625 $dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
19626 } elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
19627 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19628 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19629 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19633 // report new endy and endpage to the rowspanned cells
19634 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19635 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19636 $dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
19637 $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
19638 $dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
19639 $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
19640 $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
19641 $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
19644 // update remaining rowspanned cells
19645 foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
19646 if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
19647 $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
19648 $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
19649 $dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
19653 $prev_page = $this->page
;
19654 $this->setPage($dom[($dom[$key]['parent'])]['endpage']);
19655 if ($this->num_columns
> 1) {
19656 if (($prev_page < $this->page
)
19657 AND ((($this->current_column
== 0) AND ($dom[($dom[$key]['parent'])]['endcolumn'] == ($this->num_columns
- 1)))
19658 OR ($this->current_column
== $dom[($dom[$key]['parent'])]['endcolumn']))) {
19660 $this->selectColumn(0);
19661 $dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19662 $dom[($dom[$key]['parent'])]['endy'] = $this->y
;
19664 $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19665 $this->y
= $dom[($dom[$key]['parent'])]['endy'];
19668 $this->y
= $dom[($dom[$key]['parent'])]['endy'];
19670 if (isset($dom[$table_el]['attribute']['cellspacing'])) {
19671 $this->y +
= $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
19672 } elseif (isset($dom[$table_el]['border-spacing'])) {
19673 $this->y +
= $dom[$table_el]['border-spacing']['V'];
19675 $this->Ln(0, $cell);
19676 if ($this->current_column
== $parent['startcolumn']) {
19677 $this->x
= $parent['startx'];
19679 // account for booklet mode
19680 if ($this->page
> $parent['startpage']) {
19681 if (($this->rtl
) AND ($this->pagedim
[$this->page
]['orm'] != $this->pagedim
[$parent['startpage']]['orm'])) {
19682 $this->x
-= ($this->pagedim
[$this->page
]['orm'] - $this->pagedim
[$parent['startpage']]['orm']);
19683 } elseif ((!$this->rtl
) AND ($this->pagedim
[$this->page
]['olm'] != $this->pagedim
[$parent['startpage']]['olm'])) {
19684 $this->x +
= ($this->pagedim
[$this->page
]['olm'] - $this->pagedim
[$parent['startpage']]['olm']);
19690 // closing tag used for the thead part
19691 $in_table_head = true;
19692 $this->inthead
= false;
19694 $table_el = $parent;
19695 // set default border
19696 if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
19697 // set default border
19698 $border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
19702 $default_border = $border;
19703 // fix bottom line alignment of last line before page break
19704 foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
19705 // update row-spanned cells
19706 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19707 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19708 if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] > 0)) {
19709 $dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
19711 if ($dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] == $trkey) {
19712 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
19716 if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
19717 $pgendy = $this->pagedim
[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim
[$dom[$prevtrkey]['endpage']]['bm'];
19718 $dom[$prevtrkey]['endy'] = $pgendy;
19719 // update row-spanned cells
19720 if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
19721 foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
19722 if (($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
19723 $dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
19724 $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
19729 $prevtrkey = $trkey;
19730 $table_el = $dom[($dom[$key]['parent'])];
19733 if (!empty($table_el['trids'])) {
19736 foreach ($table_el['trids'] as $j => $trkey) {
19737 $parent = $dom[$trkey];
19738 if (!isset($xmax)) {
19739 $xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
19741 // for each cell on the row
19742 foreach ($parent['cellpos'] as $k => $cellpos) {
19743 if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
19744 $cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
19745 $cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
19746 $endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
19747 $startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
19748 $endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
19749 $startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
19750 $endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
19752 $endy = $parent['endy'];
19753 $startpage = $parent['startpage'];
19754 $endpage = $parent['endpage'];
19755 $startcolumn = $parent['startcolumn'];
19756 $endcolumn = $parent['endcolumn'];
19758 if ($this->num_columns
== 0) {
19759 $this->num_columns
= 1;
19761 if (isset($cellpos['border'])) {
19762 $border = $cellpos['border'];
19764 if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
19765 $this->setFillColorArray($cellpos['bgcolor']);
19770 $x = $cellpos['startx'];
19771 $y = $parent['starty'];
19773 $w = abs($cellpos['endx'] - $cellpos['startx']);
19774 // get border modes
19775 $border_start = TCPDF_STATIC
::getBorderMode($border, $position='start', $this->opencell
);
19776 $border_end = TCPDF_STATIC
::getBorderMode($border, $position='end', $this->opencell
);
19777 $border_middle = TCPDF_STATIC
::getBorderMode($border, $position='middle', $this->opencell
);
19778 // design borders around HTML cells.
19779 for ($page = $startpage; $page <= $endpage; ++
$page) { // for each page
19781 $this->setPage($page);
19782 if ($this->num_columns
< 2) {
19783 // single-column mode
19785 $this->y
= $this->tMargin
;
19787 // account for margin changes
19788 if ($page > $startpage) {
19789 if (($this->rtl
) AND ($this->pagedim
[$page]['orm'] != $this->pagedim
[$startpage]['orm'])) {
19790 $this->x
-= ($this->pagedim
[$page]['orm'] - $this->pagedim
[$startpage]['orm']);
19791 } elseif ((!$this->rtl
) AND ($this->pagedim
[$page]['olm'] != $this->pagedim
[$startpage]['olm'])) {
19792 $this->x +
= ($this->pagedim
[$page]['olm'] - $this->pagedim
[$startpage]['olm']);
19795 if ($startpage == $endpage) { // single page
19798 for ($column = $startcolumn; $column <= $endcolumn; ++
$column) { // for each column
19799 $this->selectColumn($column);
19800 if ($startcolumn == $endcolumn) { // single column
19801 $cborder = $border;
19802 $h = $endy - $parent['starty'];
19805 } elseif ($column == $startcolumn) { // first column
19806 $cborder = $border_start;
19807 $this->y
= $starty;
19809 $h = $this->h
- $this->y
- $this->bMargin
;
19811 $deltacol = $this->x +
$this->rMargin
- $this->w
;
19813 $deltacol = $this->x
- $this->lMargin
;
19815 } elseif ($column == $endcolumn) { // end column
19816 $cborder = $border_end;
19817 if (isset($this->columns
[$column]['th']['\''.$page.'\''])) {
19818 $this->y
= $this->columns
[$column]['th']['\''.$page.'\''];
19820 $this->x +
= $deltacol;
19821 $h = $endy - $this->y
;
19822 } else { // middle column
19823 $cborder = $border_middle;
19824 if (isset($this->columns
[$column]['th']['\''.$page.'\''])) {
19825 $this->y
= $this->columns
[$column]['th']['\''.$page.'\''];
19827 $this->x +
= $deltacol;
19828 $h = $this->h
- $this->y
- $this->bMargin
;
19830 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19831 } // end for each column
19832 } elseif ($page == $startpage) { // first page
19835 for ($column = $startcolumn; $column < $this->num_columns
; ++
$column) { // for each column
19836 $this->selectColumn($column);
19837 if ($column == $startcolumn) { // first column
19838 $cborder = $border_start;
19839 $this->y
= $starty;
19841 $h = $this->h
- $this->y
- $this->bMargin
;
19843 $deltacol = $this->x +
$this->rMargin
- $this->w
;
19845 $deltacol = $this->x
- $this->lMargin
;
19847 } else { // middle column
19848 $cborder = $border_middle;
19849 if (isset($this->columns
[$column]['th']['\''.$page.'\''])) {
19850 $this->y
= $this->columns
[$column]['th']['\''.$page.'\''];
19852 $this->x +
= $deltacol;
19853 $h = $this->h
- $this->y
- $this->bMargin
;
19855 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19856 } // end for each column
19857 } elseif ($page == $endpage) { // last page
19860 for ($column = 0; $column <= $endcolumn; ++
$column) { // for each column
19861 $this->selectColumn($column);
19862 if ($column == $endcolumn) { // end column
19863 $cborder = $border_end;
19864 if (isset($this->columns
[$column]['th']['\''.$page.'\''])) {
19865 $this->y
= $this->columns
[$column]['th']['\''.$page.'\''];
19867 $this->x +
= $deltacol;
19868 $h = $endy - $this->y
;
19869 } else { // middle column
19870 $cborder = $border_middle;
19871 if (isset($this->columns
[$column]['th']['\''.$page.'\''])) {
19872 $this->y
= $this->columns
[$column]['th']['\''.$page.'\''];
19874 $this->x +
= $deltacol;
19875 $h = $this->h
- $this->y
- $this->bMargin
;
19877 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19878 } // end for each column
19879 } else { // middle page
19882 for ($column = 0; $column < $this->num_columns
; ++
$column) { // for each column
19883 $this->selectColumn($column);
19884 $cborder = $border_middle;
19885 if (isset($this->columns
[$column]['th']['\''.$page.'\''])) {
19886 $this->y
= $this->columns
[$column]['th']['\''.$page.'\''];
19888 $this->x +
= $deltacol;
19889 $h = $this->h
- $this->y
- $this->bMargin
;
19890 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
19891 } // end for each column
19893 if (!empty($cborder) OR !empty($fill)) {
19894 $offsetlen = strlen($ccode);
19895 // draw border and fill
19896 if ($this->inxobj
) {
19897 // we are inside an XObject template
19898 if (end($this->xobjects
[$this->xobjid
]['transfmrk']) !== false) {
19899 $pagemarkkey = key($this->xobjects
[$this->xobjid
]['transfmrk']);
19900 $pagemark = $this->xobjects
[$this->xobjid
]['transfmrk'][$pagemarkkey];
19901 $this->xobjects
[$this->xobjid
]['transfmrk'][$pagemarkkey] +
= $offsetlen;
19903 $pagemark = $this->xobjects
[$this->xobjid
]['intmrk'];
19904 $this->xobjects
[$this->xobjid
]['intmrk'] +
= $offsetlen;
19906 $pagebuff = $this->xobjects
[$this->xobjid
]['outdata'];
19907 $pstart = substr($pagebuff, 0, $pagemark);
19908 $pend = substr($pagebuff, $pagemark);
19909 $this->xobjects
[$this->xobjid
]['outdata'] = $pstart.$ccode.$pend;
19911 // draw border and fill
19912 if (end($this->transfmrk
[$this->page
]) !== false) {
19913 $pagemarkkey = key($this->transfmrk
[$this->page
]);
19914 $pagemark = $this->transfmrk
[$this->page
][$pagemarkkey];
19915 } elseif ($this->InFooter
) {
19916 $pagemark = $this->footerpos
[$this->page
];
19918 $pagemark = $this->intmrk
[$this->page
];
19920 $pagebuff = $this->getPageBuffer($this->page
);
19921 $pstart = substr($pagebuff, 0, $pagemark);
19922 $pend = substr($pagebuff, $pagemark);
19923 $this->setPageBuffer($this->page
, $pstart.$ccode.$pend);
19926 } // end for each page
19927 // restore default border
19928 $border = $default_border;
19929 } // end for each cell on the row
19930 if (isset($table_el['attribute']['cellspacing'])) {
19931 $this->y +
= $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
19932 } elseif (isset($table_el['border-spacing'])) {
19933 $this->y +
= $table_el['border-spacing']['V'];
19935 $this->Ln(0, $cell);
19936 $this->x
= $parent['startx'];
19937 if ($endpage > $startpage) {
19938 if (($this->rtl
) AND ($this->pagedim
[$endpage]['orm'] != $this->pagedim
[$startpage]['orm'])) {
19939 $this->x +
= ($this->pagedim
[$endpage]['orm'] - $this->pagedim
[$startpage]['orm']);
19940 } elseif ((!$this->rtl
) AND ($this->pagedim
[$endpage]['olm'] != $this->pagedim
[$startpage]['olm'])) {
19941 $this->x +
= ($this->pagedim
[$endpage]['olm'] - $this->pagedim
[$startpage]['olm']);
19945 if (!$in_table_head) { // we are not inside a thead section
19946 $this->cell_padding
= isset($table_el['old_cell_padding']) ?
$table_el['old_cell_padding'] : null;
19947 // reset row height
19948 $this->resetLastH();
19949 if (($this->page
== ($this->numpages
- 1)) AND ($this->pageopen
[$this->numpages
])) {
19950 $plendiff = ($this->pagelen
[$this->numpages
] - $this->emptypagemrk
[$this->numpages
]);
19951 if (($plendiff > 0) AND ($plendiff < 60)) {
19952 $pagediff = substr($this->getPageBuffer($this->numpages
), $this->emptypagemrk
[$this->numpages
], $plendiff);
19953 if (substr($pagediff, 0, 5) == 'BT /F') {
19954 // the difference is only a font setting
19958 if ($plendiff == 0) {
19959 // remove last blank page
19960 $this->deletePage($this->numpages
);
19963 if (isset($this->theadMargins
['top'])) {
19964 // restore top margin
19965 $this->tMargin
= $this->theadMargins
['top'];
19967 if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
19968 // reset main table header
19970 $this->theadMargins
= array();
19971 $this->pagedim
[$this->page
]['tm'] = $this->tMargin
;
19974 $parent = $table_el;
19978 $this->HREF
= array();
19982 $this->setXY($this->GetX(), $this->GetY() +
((0.7 * $parent['fontsize']) / $this->k
));
19986 $this->setXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k
));
19990 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19993 case 'blockquote': {
19995 $this->rMargin
-= $this->listindent
;
19997 $this->lMargin
-= $this->listindent
;
19999 --$this->listindentlevel
;
20000 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20004 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20008 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20009 $this->premode
= false;
20014 if ($this->listnum
<= 0) {
20015 $this->listnum
= 0;
20016 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20018 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20020 $this->resetLastH();
20024 $this->lispacer
= '';
20025 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20029 $this->lispacer
= '';
20031 $this->rMargin
-= $this->listindent
;
20033 $this->lMargin
-= $this->listindent
;
20035 --$this->listindentlevel
;
20036 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20042 $this->lispacer
= '';
20044 $this->rMargin
-= $this->listindent
;
20046 $this->lMargin
-= $this->listindent
;
20048 --$this->listindentlevel
;
20049 if ($this->listnum
<= 0) {
20050 $this->listnum
= 0;
20051 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20053 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20055 $this->resetLastH();
20059 $this->lispacer
= '';
20060 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20069 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20072 // Form fields (since 4.8.000 - 2009-09-07)
20074 $this->form_action
= '';
20075 $this->form_enctype
= 'application/x-www-form-urlencoded';
20082 // draw border and background (if any)
20083 $this->drawHTMLTagBorder($parent, $xmax);
20084 if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
20085 $pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
20086 // check for pagebreak
20087 if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
20088 // add a page (or trig AcceptPageBreak() for multicolumn mode)
20089 $this->checkPageBreak($this->PageBreakTrigger +
1);
20091 if ((($pba == 'left') AND (((!$this->rtl
) AND (($this->page %
2) == 0)) OR (($this->rtl
) AND (($this->page %
2) != 0))))
20092 OR (($pba == 'right') AND (((!$this->rtl
) AND (($this->page %
2) != 0)) OR (($this->rtl
) AND (($this->page %
2) == 0))))) {
20093 // add a page (or trig AcceptPageBreak() for multicolumn mode)
20094 $this->checkPageBreak($this->PageBreakTrigger +
1);
20097 $this->tmprtl
= false;
20102 * Add vertical spaces if needed.
20103 * @param string $hbz Distance between current y and line bottom.
20104 * @param string $hb The height of the break.
20105 * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
20106 * @param boolean $firsttag set to true when the tag is the first.
20107 * @param boolean $lasttag set to true when the tag is the last.
20110 protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
20112 $this->Ln(0, $cell);
20113 $this->htmlvspace
= 0;
20117 $this->Ln($hbz, $cell);
20118 $this->htmlvspace
= 0;
20121 if ($hb < $this->htmlvspace
) {
20124 $hd = $hb - $this->htmlvspace
;
20125 $this->htmlvspace
= $hb;
20127 $this->Ln(($hbz +
$hd), $cell);
20131 * Return the starting coordinates to draw an html border
20132 * @return array containing top-left border coordinates
20134 * @since 5.7.000 (2010-08-03)
20136 protected function getBorderStartPosition() {
20138 $xmax = $this->lMargin
;
20140 $xmax = $this->w
- $this->rMargin
;
20142 return array('page' => $this->page
, 'column' => $this->current_column
, 'x' => $this->x
, 'y' => $this->y
, 'xmax' => $xmax);
20146 * Draw an HTML block border and fill
20147 * @param array $tag array of tag properties.
20148 * @param int $xmax end X coordinate for border.
20150 * @since 5.7.000 (2010-08-03)
20152 protected function drawHTMLTagBorder($tag, $xmax) {
20153 if (!isset($tag['borderposition'])) {
20157 $prev_x = $this->x
;
20158 $prev_y = $this->y
;
20159 $prev_lasth = $this->lasth
;
20163 if (isset($tag['border']) AND !empty($tag['border'])) {
20164 // get border style
20165 $border = $tag['border'];
20166 if (!TCPDF_STATIC
::empty_string($this->thead
) AND (!$this->inthead
)) {
20167 // border for table header
20168 $border = TCPDF_STATIC
::getBorderMode($border, $position='middle', $this->opencell
);
20171 if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
20172 // get background color
20173 $old_bgcolor = $this->bgcolor
;
20174 $this->setFillColorArray($tag['bgcolor']);
20177 if (!$border AND !$fill) {
20181 if (isset($tag['attribute']['cellspacing'])) {
20182 $clsp = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
20183 $cellspacing = array('H' => $clsp, 'V' => $clsp);
20184 } elseif (isset($tag['border-spacing'])) {
20185 $cellspacing = $tag['border-spacing'];
20187 $cellspacing = array('H' => 0, 'V' => 0);
20189 if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
20190 // draw the border externally respect the sqare edge.
20191 $border['mode'] = 'ext';
20194 if ($xmax >= $tag['borderposition']['x']) {
20195 $xmax = $tag['borderposition']['xmax'];
20197 $w = ($tag['borderposition']['x'] - $xmax);
20199 if ($xmax <= $tag['borderposition']['x']) {
20200 $xmax = $tag['borderposition']['xmax'];
20202 $w = ($xmax - $tag['borderposition']['x']);
20207 $w +
= $cellspacing['H'];
20208 $startpage = $tag['borderposition']['page'];
20209 $startcolumn = $tag['borderposition']['column'];
20210 $x = $tag['borderposition']['x'];
20211 $y = $tag['borderposition']['y'];
20212 $endpage = $this->page
;
20213 $starty = $tag['borderposition']['y'] - $cellspacing['V'];
20214 $currentY = $this->y
;
20216 // get latest column
20217 $endcolumn = $this->current_column
;
20218 if ($this->num_columns
== 0) {
20219 $this->num_columns
= 1;
20221 // get border modes
20222 $border_start = TCPDF_STATIC
::getBorderMode($border, $position='start', $this->opencell
);
20223 $border_end = TCPDF_STATIC
::getBorderMode($border, $position='end', $this->opencell
);
20224 $border_middle = TCPDF_STATIC
::getBorderMode($border, $position='middle', $this->opencell
);
20225 // temporary disable page regions
20226 $temp_page_regions = $this->page_regions
;
20227 $this->page_regions
= array();
20228 // design borders around HTML cells.
20229 for ($page = $startpage; $page <= $endpage; ++
$page) { // for each page
20231 $this->setPage($page);
20232 if ($this->num_columns
< 2) {
20233 // single-column mode
20235 $this->y
= $this->tMargin
;
20237 // account for margin changes
20238 if ($page > $startpage) {
20239 if (($this->rtl
) AND ($this->pagedim
[$page]['orm'] != $this->pagedim
[$startpage]['orm'])) {
20240 $this->x
-= ($this->pagedim
[$page]['orm'] - $this->pagedim
[$startpage]['orm']);
20241 } elseif ((!$this->rtl
) AND ($this->pagedim
[$page]['olm'] != $this->pagedim
[$startpage]['olm'])) {
20242 $this->x +
= ($this->pagedim
[$page]['olm'] - $this->pagedim
[$startpage]['olm']);
20245 if ($startpage == $endpage) {
20247 for ($column = $startcolumn; $column <= $endcolumn; ++
$column) { // for each column
20248 $this->selectColumn($column);
20249 if ($startcolumn == $endcolumn) { // single column
20250 $cborder = $border;
20251 $h = ($currentY - $y) +
$cellspacing['V'];
20252 $this->y
= $starty;
20253 } elseif ($column == $startcolumn) { // first column
20254 $cborder = $border_start;
20255 $this->y
= $starty;
20256 $h = $this->h
- $this->y
- $this->bMargin
;
20257 } elseif ($column == $endcolumn) { // end column
20258 $cborder = $border_end;
20259 $h = $currentY - $this->y
;
20260 } else { // middle column
20261 $cborder = $border_middle;
20262 $h = $this->h
- $this->y
- $this->bMargin
;
20264 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20265 } // end for each column
20266 } elseif ($page == $startpage) { // first page
20267 for ($column = $startcolumn; $column < $this->num_columns
; ++
$column) { // for each column
20268 $this->selectColumn($column);
20269 if ($column == $startcolumn) { // first column
20270 $cborder = $border_start;
20271 $this->y
= $starty;
20272 $h = $this->h
- $this->y
- $this->bMargin
;
20273 } else { // middle column
20274 $cborder = $border_middle;
20275 $h = $this->h
- $this->y
- $this->bMargin
;
20277 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20278 } // end for each column
20279 } elseif ($page == $endpage) { // last page
20280 for ($column = 0; $column <= $endcolumn; ++
$column) { // for each column
20281 $this->selectColumn($column);
20282 if ($column == $endcolumn) {
20284 $cborder = $border_end;
20285 $h = $currentY - $this->y
;
20288 $cborder = $border_middle;
20289 $h = $this->h
- $this->y
- $this->bMargin
;
20291 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20292 } // end for each column
20293 } else { // middle page
20294 for ($column = 0; $column < $this->num_columns
; ++
$column) { // for each column
20295 $this->selectColumn($column);
20296 $cborder = $border_middle;
20297 $h = $this->h
- $this->y
- $this->bMargin
;
20298 $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
20299 } // end for each column
20301 if ($cborder OR $fill) {
20302 $offsetlen = strlen($ccode);
20303 // draw border and fill
20304 if ($this->inxobj
) {
20305 // we are inside an XObject template
20306 if (end($this->xobjects
[$this->xobjid
]['transfmrk']) !== false) {
20307 $pagemarkkey = key($this->xobjects
[$this->xobjid
]['transfmrk']);
20308 $pagemark = $this->xobjects
[$this->xobjid
]['transfmrk'][$pagemarkkey];
20309 $this->xobjects
[$this->xobjid
]['transfmrk'][$pagemarkkey] +
= $offsetlen;
20311 $pagemark = $this->xobjects
[$this->xobjid
]['intmrk'];
20312 $this->xobjects
[$this->xobjid
]['intmrk'] +
= $offsetlen;
20314 $pagebuff = $this->xobjects
[$this->xobjid
]['outdata'];
20315 $pstart = substr($pagebuff, 0, $pagemark);
20316 $pend = substr($pagebuff, $pagemark);
20317 $this->xobjects
[$this->xobjid
]['outdata'] = $pstart.$ccode.$pend;
20319 if (end($this->transfmrk
[$this->page
]) !== false) {
20320 $pagemarkkey = key($this->transfmrk
[$this->page
]);
20321 $pagemark = $this->transfmrk
[$this->page
][$pagemarkkey];
20322 } elseif ($this->InFooter
) {
20323 $pagemark = $this->footerpos
[$this->page
];
20325 $pagemark = $this->intmrk
[$this->page
];
20327 $pagebuff = $this->getPageBuffer($this->page
);
20328 $pstart = substr($pagebuff, 0, $pagemark);
20329 $pend = substr($pagebuff, $pagemark);
20330 $this->setPageBuffer($this->page
, $pstart.$ccode.$pend);
20331 $this->bordermrk
[$this->page
] +
= $offsetlen;
20332 $this->cntmrk
[$this->page
] +
= $offsetlen;
20335 } // end for each page
20336 // restore page regions
20337 $this->page_regions
= $temp_page_regions;
20338 if (isset($old_bgcolor)) {
20339 // restore background color
20340 $this->setFillColorArray($old_bgcolor);
20342 // restore pointer position
20343 $this->x
= $prev_x;
20344 $this->y
= $prev_y;
20345 $this->lasth
= $prev_lasth;
20349 * Set the default bullet to be used as LI bullet symbol
20350 * @param string $symbol 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')
20352 * @since 4.0.028 (2008-09-26)
20354 public function setLIsymbol($symbol='!') {
20355 // check for custom image symbol
20356 if (substr($symbol, 0, 4) == 'img|') {
20357 $this->lisymbol
= $symbol;
20360 $symbol = strtolower($symbol);
20361 $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');
20362 if (in_array($symbol, $valid_symbols)) {
20363 $this->lisymbol
= $symbol;
20365 $this->lisymbol
= '';
20370 * Set the booklet mode for double-sided pages.
20371 * @param boolean $booklet true set the booklet mode on, false otherwise.
20372 * @param float $inner Inner page margin.
20373 * @param float $outer Outer page margin.
20375 * @since 4.2.000 (2008-10-29)
20377 public function setBooklet($booklet=true, $inner=-1, $outer=-1) {
20378 $this->booklet
= $booklet;
20380 $this->lMargin
= $inner;
20383 $this->rMargin
= $outer;
20388 * Swap the left and right margins.
20389 * @param boolean $reverse if true swap left and right margins.
20391 * @since 4.2.000 (2008-10-29)
20393 protected function swapMargins($reverse=true) {
20395 // swap left and right margins
20396 $mtemp = $this->original_lMargin
;
20397 $this->original_lMargin
= $this->original_rMargin
;
20398 $this->original_rMargin
= $mtemp;
20399 $deltam = $this->original_lMargin
- $this->original_rMargin
;
20400 $this->lMargin +
= $deltam;
20401 $this->rMargin
-= $deltam;
20406 * Set the vertical spaces for HTML tags.
20407 * The array must have the following structure (example):
20408 * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
20409 * The first array level contains the tag names,
20410 * the second level contains 0 for opening tags or 1 for closing tags,
20411 * the third level contains the vertical space unit (h) and the number spaces to add (n).
20412 * If the h parameter is not specified, default values are used.
20413 * @param array $tagvs array of tags and relative vertical spaces.
20415 * @since 4.2.001 (2008-10-30)
20417 public function setHtmlVSpace($tagvs) {
20418 $this->tagvspaces
= $tagvs;
20422 * Set custom width for list indentation.
20423 * @param float $width width of the indentation. Use negative value to disable it.
20425 * @since 4.2.007 (2008-11-12)
20427 public function setListIndentWidth($width) {
20428 return $this->customlistindent
= floatval($width);
20432 * Set the top/bottom cell sides to be open or closed when the cell cross the page.
20433 * @param boolean $isopen if true keeps the top/bottom border open for the cell sides that cross the page.
20435 * @since 4.2.010 (2008-11-14)
20437 public function setOpenCell($isopen) {
20438 $this->opencell
= $isopen;
20442 * Set the color and font style for HTML links.
20443 * @param array $color RGB array of colors
20444 * @param string $fontstyle additional font styles to add
20446 * @since 4.4.003 (2008-12-09)
20448 public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
20449 $this->htmlLinkColorArray
= $color;
20450 $this->htmlLinkFontStyle
= $fontstyle;
20454 * Convert HTML string containing value and unit of measure to user's units or points.
20455 * @param string $htmlval String containing values and unit.
20456 * @param string $refsize Reference value in points.
20457 * @param string $defaultunit Default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
20458 * @param boolean $points If true returns points, otherwise returns value in user's units.
20459 * @return float value in user's unit or point if $points=true
20461 * @since 4.4.004 (2008-12-10)
20463 public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
20464 $supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
20473 if (in_array($defaultunit, $supportedunits)) {
20474 $unit = $defaultunit;
20476 if (is_numeric($htmlval)) {
20477 $value = floatval($htmlval);
20478 } elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
20479 $value = floatval($mnum[1]);
20480 if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
20481 if (in_array($munit[1], $supportedunits)) {
20489 $retval = (($value * $refsize) / 100);
20494 $retval = ($value * $refsize);
20497 // height of lower case 'x' (about half the font-size)
20499 $retval = ($value * ($refsize / 2));
20504 $retval = (($value * $this->dpi
) / $k);
20509 $retval = (($value / 2.54 * $this->dpi
) / $k);
20514 $retval = (($value / 25.4 * $this->dpi
) / $k);
20517 // one pica is 12 points
20519 $retval = (($value * 12) / $k);
20524 $retval = ($value / $k);
20529 $retval = $this->pixelsToUnits($value);
20531 $retval *= $this->k
;
20540 * Output an HTML list bullet or ordered item symbol
20541 * @param int $listdepth list nesting level
20542 * @param string $listtype type of list
20543 * @param float $size current font size
20545 * @since 4.4.004 (2008-12-10)
20547 protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20548 if ($this->state
!= 2) {
20553 $bgcolor = $this->bgcolor
;
20554 $color = $this->fgcolor
;
20555 $strokecolor = $this->strokecolor
;
20559 $lspace = $this->GetStringWidth(' ');
20560 if ($listtype == '^') {
20561 // special symbol used for avoid justification of rect bullet
20562 $this->lispacer
= '';
20564 } elseif ($listtype == '!') {
20565 // set default list type for unordered list
20566 $deftypes = array('disc', 'circle', 'square');
20567 $listtype = $deftypes[($listdepth - 1) %
3];
20568 } elseif ($listtype == '#') {
20569 // set default list type for ordered list
20570 $listtype = 'decimal';
20571 } elseif (substr($listtype, 0, 4) == 'img|') {
20572 // custom image type ('img|type|width|height|image.ext')
20573 $img = explode('|', $listtype);
20576 switch ($listtype) {
20583 $lspace +
= (2 * $r);
20585 $this->x +
= $lspace;
20587 $this->x
-= $lspace;
20589 $this->Circle(($this->x +
$r), ($this->y +
($this->lasth
/ 2)), $r, 0, 360, 'F', array(), $color, 8);
20594 $lspace +
= (2 * $r);
20596 $this->x +
= $lspace;
20598 $this->x
-= $lspace;
20600 $prev_line_style = $this->linestyleWidth
.' '.$this->linestyleCap
.' '.$this->linestyleJoin
.' '.$this->linestyleDash
.' '.$this->DrawColor
;
20601 $new_line_style = array('width' => ($r / 3), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'phase' => 0, 'color'=>$color);
20602 $this->Circle(($this->x +
$r), ($this->y +
($this->lasth
/ 2)), ($r * (1 - (1/6))), 0, 360, 'D', $new_line_style, array(), 8);
20603 $this->_out($prev_line_style); // restore line settings
20610 $this->x +
= $lspace;
20612 $this->x
-= $lspace;
20614 $this->Rect($this->x
, ($this->y +
(($this->lasth
- $l) / 2)), $l, $l, 'F', array(), $color);
20618 // 1=>type, 2=>width, 3=>height, 4=>image.ext
20619 $lspace +
= $img[2];
20621 $this->x +
= $lspace;
20623 $this->x
-= $lspace;
20625 $imgtype = strtolower($img[1]);
20626 $prev_y = $this->y
;
20627 switch ($imgtype) {
20629 $this->ImageSVG($img[4], $this->x
, ($this->y +
(($this->lasth
- $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20634 $this->ImageEps($img[4], $this->x
, ($this->y +
(($this->lasth
- $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20638 $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);
20642 $this->y
= $prev_y;
20646 // $this->listcount[$this->listnum];
20650 $textitem = $this->listcount
[$this->listnum
];
20653 case 'decimal-leading-zero': {
20654 $textitem = sprintf('%02d', $this->listcount
[$this->listnum
]);
20658 case 'lower-roman': {
20659 $textitem = strtolower(TCPDF_STATIC
::intToRoman($this->listcount
[$this->listnum
]));
20663 case 'upper-roman': {
20664 $textitem = TCPDF_STATIC
::intToRoman($this->listcount
[$this->listnum
]);
20668 case 'lower-alpha':
20669 case 'lower-latin': {
20670 $textitem = chr(97 +
$this->listcount
[$this->listnum
] - 1);
20674 case 'upper-alpha':
20675 case 'upper-latin': {
20676 $textitem = chr(65 +
$this->listcount
[$this->listnum
] - 1);
20679 case 'lower-greek': {
20680 $textitem = TCPDF_FONTS
::unichr((945 +
$this->listcount
[$this->listnum
] - 1), $this->isunicode
);
20684 // Types to be implemented (special handling)
20694 case 'cjk-ideographic': {
20703 case 'hiragana-iroha': {
20706 case 'katakana-iroha': {
20711 $textitem = $this->listcount
[$this->listnum
];
20714 if (!TCPDF_STATIC
::empty_string($textitem)) {
20715 // Check whether we need a new page or new column
20716 $prev_y = $this->y
;
20717 $h = $this->getCellHeight($this->FontSize
);
20718 if ($this->checkPageBreak($h) OR ($this->y
< $prev_y)) {
20721 // print ordered item
20723 $textitem = '.'.$textitem;
20725 $textitem = $textitem.'.';
20727 $lspace +
= $this->GetStringWidth($textitem);
20729 $this->x +
= $lspace;
20731 $this->x
-= $lspace;
20733 $this->Write($this->lasth
, $textitem, '', false, '', false, 0, false);
20736 $this->lispacer
= '^';
20738 $this->setFillColorArray($bgcolor);
20739 $this->setDrawColorArray($strokecolor);
20740 $this->settextColorArray($color);
20744 * Returns current graphic variables as array.
20745 * @return array of graphic variables
20747 * @since 4.2.010 (2008-11-14)
20749 protected function getGraphicVars() {
20751 'FontFamily' => $this->FontFamily
,
20752 'FontStyle' => $this->FontStyle
,
20753 'FontSizePt' => $this->FontSizePt
,
20754 'rMargin' => $this->rMargin
,
20755 'lMargin' => $this->lMargin
,
20756 'cell_padding' => $this->cell_padding
,
20757 'cell_margin' => $this->cell_margin
,
20758 'LineWidth' => $this->LineWidth
,
20759 'linestyleWidth' => $this->linestyleWidth
,
20760 'linestyleCap' => $this->linestyleCap
,
20761 'linestyleJoin' => $this->linestyleJoin
,
20762 'linestyleDash' => $this->linestyleDash
,
20763 'textrendermode' => $this->textrendermode
,
20764 'textstrokewidth' => $this->textstrokewidth
,
20765 'DrawColor' => $this->DrawColor
,
20766 'FillColor' => $this->FillColor
,
20767 'TextColor' => $this->TextColor
,
20768 'ColorFlag' => $this->ColorFlag
,
20769 'bgcolor' => $this->bgcolor
,
20770 'fgcolor' => $this->fgcolor
,
20771 'htmlvspace' => $this->htmlvspace
,
20772 'listindent' => $this->listindent
,
20773 'listindentlevel' => $this->listindentlevel
,
20774 'listnum' => $this->listnum
,
20775 'listordered' => $this->listordered
,
20776 'listcount' => $this->listcount
,
20777 'lispacer' => $this->lispacer
,
20778 'cell_height_ratio' => $this->cell_height_ratio
,
20779 'font_stretching' => $this->font_stretching
,
20780 'font_spacing' => $this->font_spacing
,
20781 'alpha' => $this->alpha
,
20783 'lasth' => $this->lasth
,
20784 'tMargin' => $this->tMargin
,
20785 'bMargin' => $this->bMargin
,
20786 'AutoPageBreak' => $this->AutoPageBreak
,
20787 'PageBreakTrigger' => $this->PageBreakTrigger
,
20792 'wPt' => $this->wPt
,
20793 'hPt' => $this->hPt
,
20794 'fwPt' => $this->fwPt
,
20795 'fhPt' => $this->fhPt
,
20796 'page' => $this->page
,
20797 'current_column' => $this->current_column
,
20798 'num_columns' => $this->num_columns
20804 * Set graphic variables.
20805 * @param array $gvars array of graphic variablesto restore
20806 * @param boolean $extended if true restore extended graphic variables
20808 * @since 4.2.010 (2008-11-14)
20810 protected function setGraphicVars($gvars, $extended=false) {
20811 if ($this->state
!= 2) {
20814 $this->FontFamily
= $gvars['FontFamily'];
20815 $this->FontStyle
= $gvars['FontStyle'];
20816 $this->FontSizePt
= $gvars['FontSizePt'];
20817 $this->rMargin
= $gvars['rMargin'];
20818 $this->lMargin
= $gvars['lMargin'];
20819 $this->cell_padding
= $gvars['cell_padding'];
20820 $this->cell_margin
= $gvars['cell_margin'];
20821 $this->LineWidth
= $gvars['LineWidth'];
20822 $this->linestyleWidth
= $gvars['linestyleWidth'];
20823 $this->linestyleCap
= $gvars['linestyleCap'];
20824 $this->linestyleJoin
= $gvars['linestyleJoin'];
20825 $this->linestyleDash
= $gvars['linestyleDash'];
20826 $this->textrendermode
= $gvars['textrendermode'];
20827 $this->textstrokewidth
= $gvars['textstrokewidth'];
20828 $this->DrawColor
= $gvars['DrawColor'];
20829 $this->FillColor
= $gvars['FillColor'];
20830 $this->TextColor
= $gvars['TextColor'];
20831 $this->ColorFlag
= $gvars['ColorFlag'];
20832 $this->bgcolor
= $gvars['bgcolor'];
20833 $this->fgcolor
= $gvars['fgcolor'];
20834 $this->htmlvspace
= $gvars['htmlvspace'];
20835 $this->listindent
= $gvars['listindent'];
20836 $this->listindentlevel
= $gvars['listindentlevel'];
20837 $this->listnum
= $gvars['listnum'];
20838 $this->listordered
= $gvars['listordered'];
20839 $this->listcount
= $gvars['listcount'];
20840 $this->lispacer
= $gvars['lispacer'];
20841 $this->cell_height_ratio
= $gvars['cell_height_ratio'];
20842 $this->font_stretching
= $gvars['font_stretching'];
20843 $this->font_spacing
= $gvars['font_spacing'];
20844 $this->alpha
= $gvars['alpha'];
20846 // restore extended values
20847 $this->lasth
= $gvars['lasth'];
20848 $this->tMargin
= $gvars['tMargin'];
20849 $this->bMargin
= $gvars['bMargin'];
20850 $this->AutoPageBreak
= $gvars['AutoPageBreak'];
20851 $this->PageBreakTrigger
= $gvars['PageBreakTrigger'];
20852 $this->x
= $gvars['x'];
20853 $this->y
= $gvars['y'];
20854 $this->w
= $gvars['w'];
20855 $this->h
= $gvars['h'];
20856 $this->wPt
= $gvars['wPt'];
20857 $this->hPt
= $gvars['hPt'];
20858 $this->fwPt
= $gvars['fwPt'];
20859 $this->fhPt
= $gvars['fhPt'];
20860 $this->page
= $gvars['page'];
20861 $this->current_column
= $gvars['current_column'];
20862 $this->num_columns
= $gvars['num_columns'];
20864 $this->_out(''.$this->linestyleWidth
.' '.$this->linestyleCap
.' '.$this->linestyleJoin
.' '.$this->linestyleDash
.' '.$this->DrawColor
.' '.$this->FillColor
.'');
20865 if (!TCPDF_STATIC
::empty_string($this->FontFamily
)) {
20866 $this->setFont($this->FontFamily
, $this->FontStyle
, $this->FontSizePt
);
20871 * Outputs the "save graphics state" operator 'q'
20874 protected function _outSaveGraphicsState() {
20879 * Outputs the "restore graphics state" operator 'Q'
20882 protected function _outRestoreGraphicsState() {
20887 * Set buffer content (always append data).
20888 * @param string $data data
20890 * @since 4.5.000 (2009-01-02)
20892 protected function setBuffer($data) {
20893 $this->bufferlen +
= strlen($data);
20894 $this->buffer
.= $data;
20898 * Replace the buffer content
20899 * @param string $data data
20901 * @since 5.5.000 (2010-06-22)
20903 protected function replaceBuffer($data) {
20904 $this->bufferlen
= strlen($data);
20905 $this->buffer
= $data;
20909 * Get buffer content.
20910 * @return string buffer content
20912 * @since 4.5.000 (2009-01-02)
20914 protected function getBuffer() {
20915 return $this->buffer
;
20919 * Set page buffer content.
20920 * @param int $page page number
20921 * @param string $data page data
20922 * @param boolean $append if true append data, false replace.
20924 * @since 4.5.000 (2008-12-31)
20926 protected function setPageBuffer($page, $data, $append=false) {
20928 $this->pages
[$page] .= $data;
20930 $this->pages
[$page] = $data;
20932 if ($append AND isset($this->pagelen
[$page])) {
20933 $this->pagelen
[$page] +
= strlen($data);
20935 $this->pagelen
[$page] = strlen($data);
20940 * Get page buffer content.
20941 * @param int $page page number
20942 * @return string page buffer content or false in case of error
20944 * @since 4.5.000 (2008-12-31)
20946 protected function getPageBuffer($page) {
20947 if (isset($this->pages
[$page])) {
20948 return $this->pages
[$page];
20954 * Set image buffer content.
20955 * @param string $image image key
20956 * @param array $data image data
20957 * @return int image index number
20959 * @since 4.5.000 (2008-12-31)
20961 protected function setImageBuffer($image, $data) {
20962 if (($data['i'] = array_search($image, $this->imagekeys
)) === FALSE) {
20963 $this->imagekeys
[$this->numimages
] = $image;
20964 $data['i'] = $this->numimages
;
20965 ++
$this->numimages
;
20967 $this->images
[$image] = $data;
20972 * Set image buffer content for a specified sub-key.
20973 * @param string $image image key
20974 * @param string $key image sub-key
20975 * @param array $data image data
20977 * @since 4.5.000 (2008-12-31)
20979 protected function setImageSubBuffer($image, $key, $data) {
20980 if (!isset($this->images
[$image])) {
20981 $this->setImageBuffer($image, array());
20983 $this->images
[$image][$key] = $data;
20987 * Get image buffer content.
20988 * @param string $image image key
20989 * @return string|false image buffer content or false in case of error
20991 * @since 4.5.000 (2008-12-31)
20993 protected function getImageBuffer($image) {
20994 if (isset($this->images
[$image])) {
20995 return $this->images
[$image];
21001 * Set font buffer content.
21002 * @param string $font font key
21003 * @param array $data font data
21005 * @since 4.5.000 (2009-01-02)
21007 protected function setFontBuffer($font, $data) {
21008 $this->fonts
[$font] = $data;
21009 if (!in_array($font, $this->fontkeys
)) {
21010 $this->fontkeys
[] = $font;
21011 // store object ID for current font
21013 $this->font_obj_ids
[$font] = $this->n
;
21014 $this->setFontSubBuffer($font, 'n', $this->n
);
21019 * Set font buffer content.
21020 * @param string $font font key
21021 * @param string $key font sub-key
21022 * @param mixed $data font data
21024 * @since 4.5.000 (2009-01-02)
21026 protected function setFontSubBuffer($font, $key, $data) {
21027 if (!isset($this->fonts
[$font])) {
21028 $this->setFontBuffer($font, array());
21030 $this->fonts
[$font][$key] = $data;
21034 * Get font buffer content.
21035 * @param string $font font key
21036 * @return string|false font buffer content or false in case of error
21038 * @since 4.5.000 (2009-01-02)
21040 protected function getFontBuffer($font) {
21041 if (isset($this->fonts
[$font])) {
21042 return $this->fonts
[$font];
21048 * Move a page to a previous position.
21049 * @param int $frompage number of the source page
21050 * @param int $topage number of the destination page (must be less than $frompage)
21051 * @return bool true in case of success, false in case of error.
21053 * @since 4.5.000 (2009-01-02)
21055 public function movePage($frompage, $topage) {
21056 if (($frompage > $this->numpages
) OR ($frompage <= $topage)) {
21059 if ($frompage == $this->page
) {
21060 // close the page before moving it
21063 // move all page-related states
21064 $tmppage = $this->getPageBuffer($frompage);
21065 $tmppagedim = $this->pagedim
[$frompage];
21066 $tmppagelen = $this->pagelen
[$frompage];
21067 $tmpintmrk = $this->intmrk
[$frompage];
21068 $tmpbordermrk = $this->bordermrk
[$frompage];
21069 $tmpcntmrk = $this->cntmrk
[$frompage];
21070 $tmppageobjects = $this->pageobjects
[$frompage];
21071 if (isset($this->footerpos
[$frompage])) {
21072 $tmpfooterpos = $this->footerpos
[$frompage];
21074 if (isset($this->footerlen
[$frompage])) {
21075 $tmpfooterlen = $this->footerlen
[$frompage];
21077 if (isset($this->transfmrk
[$frompage])) {
21078 $tmptransfmrk = $this->transfmrk
[$frompage];
21080 if (isset($this->PageAnnots
[$frompage])) {
21081 $tmpannots = $this->PageAnnots
[$frompage];
21083 if (isset($this->newpagegroup
) AND !empty($this->newpagegroup
)) {
21084 for ($i = $frompage; $i > $topage; --$i) {
21085 if (isset($this->newpagegroup
[$i]) AND (($i +
$this->pagegroups
[$this->newpagegroup
[$i]]) > $frompage)) {
21086 --$this->pagegroups
[$this->newpagegroup
[$i]];
21090 for ($i = $topage; $i > 0; --$i) {
21091 if (isset($this->newpagegroup
[$i]) AND (($i +
$this->pagegroups
[$this->newpagegroup
[$i]]) > $topage)) {
21092 ++
$this->pagegroups
[$this->newpagegroup
[$i]];
21097 for ($i = $frompage; $i > $topage; --$i) {
21099 // shift pages down
21100 $this->setPageBuffer($i, $this->getPageBuffer($j));
21101 $this->pagedim
[$i] = $this->pagedim
[$j];
21102 $this->pagelen
[$i] = $this->pagelen
[$j];
21103 $this->intmrk
[$i] = $this->intmrk
[$j];
21104 $this->bordermrk
[$i] = $this->bordermrk
[$j];
21105 $this->cntmrk
[$i] = $this->cntmrk
[$j];
21106 $this->pageobjects
[$i] = $this->pageobjects
[$j];
21107 if (isset($this->footerpos
[$j])) {
21108 $this->footerpos
[$i] = $this->footerpos
[$j];
21109 } elseif (isset($this->footerpos
[$i])) {
21110 unset($this->footerpos
[$i]);
21112 if (isset($this->footerlen
[$j])) {
21113 $this->footerlen
[$i] = $this->footerlen
[$j];
21114 } elseif (isset($this->footerlen
[$i])) {
21115 unset($this->footerlen
[$i]);
21117 if (isset($this->transfmrk
[$j])) {
21118 $this->transfmrk
[$i] = $this->transfmrk
[$j];
21119 } elseif (isset($this->transfmrk
[$i])) {
21120 unset($this->transfmrk
[$i]);
21122 if (isset($this->PageAnnots
[$j])) {
21123 $this->PageAnnots
[$i] = $this->PageAnnots
[$j];
21124 } elseif (isset($this->PageAnnots
[$i])) {
21125 unset($this->PageAnnots
[$i]);
21127 if (isset($this->newpagegroup
[$j])) {
21128 $this->newpagegroup
[$i] = $this->newpagegroup
[$j];
21129 unset($this->newpagegroup
[$j]);
21131 if ($this->currpagegroup
== $j) {
21132 $this->currpagegroup
= $i;
21135 $this->setPageBuffer($topage, $tmppage);
21136 $this->pagedim
[$topage] = $tmppagedim;
21137 $this->pagelen
[$topage] = $tmppagelen;
21138 $this->intmrk
[$topage] = $tmpintmrk;
21139 $this->bordermrk
[$topage] = $tmpbordermrk;
21140 $this->cntmrk
[$topage] = $tmpcntmrk;
21141 $this->pageobjects
[$topage] = $tmppageobjects;
21142 if (isset($tmpfooterpos)) {
21143 $this->footerpos
[$topage] = $tmpfooterpos;
21144 } elseif (isset($this->footerpos
[$topage])) {
21145 unset($this->footerpos
[$topage]);
21147 if (isset($tmpfooterlen)) {
21148 $this->footerlen
[$topage] = $tmpfooterlen;
21149 } elseif (isset($this->footerlen
[$topage])) {
21150 unset($this->footerlen
[$topage]);
21152 if (isset($tmptransfmrk)) {
21153 $this->transfmrk
[$topage] = $tmptransfmrk;
21154 } elseif (isset($this->transfmrk
[$topage])) {
21155 unset($this->transfmrk
[$topage]);
21157 if (isset($tmpannots)) {
21158 $this->PageAnnots
[$topage] = $tmpannots;
21159 } elseif (isset($this->PageAnnots
[$topage])) {
21160 unset($this->PageAnnots
[$topage]);
21163 $tmpoutlines = $this->outlines
;
21164 foreach ($tmpoutlines as $key => $outline) {
21165 if (!$outline['f']) {
21166 if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
21167 $this->outlines
[$key]['p'] = ($outline['p'] +
1);
21168 } elseif ($outline['p'] == $frompage) {
21169 $this->outlines
[$key]['p'] = $topage;
21174 $tmpdests = $this->dests
;
21175 foreach ($tmpdests as $key => $dest) {
21177 if (($dest['p'] >= $topage) AND ($dest['p'] < $frompage)) {
21178 $this->dests
[$key]['p'] = ($dest['p'] +
1);
21179 } elseif ($dest['p'] == $frompage) {
21180 $this->dests
[$key]['p'] = $topage;
21185 $tmplinks = $this->links
;
21186 foreach ($tmplinks as $key => $link) {
21188 if (($link['p'] >= $topage) AND ($link['p'] < $frompage)) {
21189 $this->links
[$key]['p'] = ($link['p'] +
1);
21190 } elseif ($link['p'] == $frompage) {
21191 $this->links
[$key]['p'] = $topage;
21195 // adjust javascript
21196 $jfrompage = $frompage;
21197 $jtopage = $topage;
21198 if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript
, $pamatch) > 0) {
21199 foreach($pamatch[0] as $pk => $pmatch) {
21200 $pagenum = intval($pamatch[3][$pk]) +
1;
21201 if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
21202 $newpage = ($pagenum +
1);
21203 } elseif ($pagenum == $jfrompage) {
21204 $newpage = $jtopage;
21206 $newpage = $pagenum;
21209 $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21210 $this->javascript
= str_replace($pmatch, $newjs, $this->javascript
);
21214 // return to last page
21215 $this->lastPage(true);
21220 * Remove the specified page.
21221 * @param int $page page to remove
21222 * @return bool true in case of success, false in case of error.
21224 * @since 4.6.004 (2009-04-23)
21226 public function deletePage($page) {
21227 if (($page < 1) OR ($page > $this->numpages
)) {
21230 // delete current page
21231 unset($this->pages
[$page]);
21232 unset($this->pagedim
[$page]);
21233 unset($this->pagelen
[$page]);
21234 unset($this->intmrk
[$page]);
21235 unset($this->bordermrk
[$page]);
21236 unset($this->cntmrk
[$page]);
21237 foreach ($this->pageobjects
[$page] as $oid) {
21238 if (isset($this->offsets
[$oid])){
21239 unset($this->offsets
[$oid]);
21242 unset($this->pageobjects
[$page]);
21243 if (isset($this->footerpos
[$page])) {
21244 unset($this->footerpos
[$page]);
21246 if (isset($this->footerlen
[$page])) {
21247 unset($this->footerlen
[$page]);
21249 if (isset($this->transfmrk
[$page])) {
21250 unset($this->transfmrk
[$page]);
21252 if (isset($this->PageAnnots
[$page])) {
21253 unset($this->PageAnnots
[$page]);
21255 if (isset($this->newpagegroup
) AND !empty($this->newpagegroup
)) {
21256 for ($i = $page; $i > 0; --$i) {
21257 if (isset($this->newpagegroup
[$i]) AND (($i +
$this->pagegroups
[$this->newpagegroup
[$i]]) > $page)) {
21258 --$this->pagegroups
[$this->newpagegroup
[$i]];
21263 if (isset($this->pageopen
[$page])) {
21264 unset($this->pageopen
[$page]);
21266 if ($page < $this->numpages
) {
21267 // update remaining pages
21268 for ($i = $page; $i < $this->numpages
; ++
$i) {
21271 $this->setPageBuffer($i, $this->getPageBuffer($j));
21272 $this->pagedim
[$i] = $this->pagedim
[$j];
21273 $this->pagelen
[$i] = $this->pagelen
[$j];
21274 $this->intmrk
[$i] = $this->intmrk
[$j];
21275 $this->bordermrk
[$i] = $this->bordermrk
[$j];
21276 $this->cntmrk
[$i] = $this->cntmrk
[$j];
21277 $this->pageobjects
[$i] = $this->pageobjects
[$j];
21278 if (isset($this->footerpos
[$j])) {
21279 $this->footerpos
[$i] = $this->footerpos
[$j];
21280 } elseif (isset($this->footerpos
[$i])) {
21281 unset($this->footerpos
[$i]);
21283 if (isset($this->footerlen
[$j])) {
21284 $this->footerlen
[$i] = $this->footerlen
[$j];
21285 } elseif (isset($this->footerlen
[$i])) {
21286 unset($this->footerlen
[$i]);
21288 if (isset($this->transfmrk
[$j])) {
21289 $this->transfmrk
[$i] = $this->transfmrk
[$j];
21290 } elseif (isset($this->transfmrk
[$i])) {
21291 unset($this->transfmrk
[$i]);
21293 if (isset($this->PageAnnots
[$j])) {
21294 $this->PageAnnots
[$i] = $this->PageAnnots
[$j];
21295 } elseif (isset($this->PageAnnots
[$i])) {
21296 unset($this->PageAnnots
[$i]);
21298 if (isset($this->newpagegroup
[$j])) {
21299 $this->newpagegroup
[$i] = $this->newpagegroup
[$j];
21300 unset($this->newpagegroup
[$j]);
21302 if ($this->currpagegroup
== $j) {
21303 $this->currpagegroup
= $i;
21305 if (isset($this->pageopen
[$j])) {
21306 $this->pageopen
[$i] = $this->pageopen
[$j];
21307 } elseif (isset($this->pageopen
[$i])) {
21308 unset($this->pageopen
[$i]);
21311 // remove last page
21312 unset($this->pages
[$this->numpages
]);
21313 unset($this->pagedim
[$this->numpages
]);
21314 unset($this->pagelen
[$this->numpages
]);
21315 unset($this->intmrk
[$this->numpages
]);
21316 unset($this->bordermrk
[$this->numpages
]);
21317 unset($this->cntmrk
[$this->numpages
]);
21318 foreach ($this->pageobjects
[$this->numpages
] as $oid) {
21319 if (isset($this->offsets
[$oid])){
21320 unset($this->offsets
[$oid]);
21323 unset($this->pageobjects
[$this->numpages
]);
21324 if (isset($this->footerpos
[$this->numpages
])) {
21325 unset($this->footerpos
[$this->numpages
]);
21327 if (isset($this->footerlen
[$this->numpages
])) {
21328 unset($this->footerlen
[$this->numpages
]);
21330 if (isset($this->transfmrk
[$this->numpages
])) {
21331 unset($this->transfmrk
[$this->numpages
]);
21333 if (isset($this->PageAnnots
[$this->numpages
])) {
21334 unset($this->PageAnnots
[$this->numpages
]);
21336 if (isset($this->newpagegroup
[$this->numpages
])) {
21337 unset($this->newpagegroup
[$this->numpages
]);
21339 if ($this->currpagegroup
== $this->numpages
) {
21340 $this->currpagegroup
= ($this->numpages
- 1);
21342 if (isset($this->pagegroups
[$this->numpages
])) {
21343 unset($this->pagegroups
[$this->numpages
]);
21345 if (isset($this->pageopen
[$this->numpages
])) {
21346 unset($this->pageopen
[$this->numpages
]);
21350 $this->page
= $this->numpages
;
21352 $tmpoutlines = $this->outlines
;
21353 foreach ($tmpoutlines as $key => $outline) {
21354 if (!$outline['f']) {
21355 if ($outline['p'] > $page) {
21356 $this->outlines
[$key]['p'] = $outline['p'] - 1;
21357 } elseif ($outline['p'] == $page) {
21358 unset($this->outlines
[$key]);
21363 $tmpdests = $this->dests
;
21364 foreach ($tmpdests as $key => $dest) {
21366 if ($dest['p'] > $page) {
21367 $this->dests
[$key]['p'] = $dest['p'] - 1;
21368 } elseif ($dest['p'] == $page) {
21369 unset($this->dests
[$key]);
21374 $tmplinks = $this->links
;
21375 foreach ($tmplinks as $key => $link) {
21377 if ($link['p'] > $page) {
21378 $this->links
[$key]['p'] = $link['p'] - 1;
21379 } elseif ($link['p'] == $page) {
21380 unset($this->links
[$key]);
21384 // adjust javascript
21386 if (preg_match_all('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/', $this->javascript
, $pamatch) > 0) {
21387 foreach($pamatch[0] as $pk => $pmatch) {
21388 $pagenum = intval($pamatch[3][$pk]) +
1;
21389 if ($pagenum >= $jpage) {
21390 $newpage = ($pagenum - 1);
21391 } elseif ($pagenum == $jpage) {
21394 $newpage = $pagenum;
21397 $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21398 $this->javascript
= str_replace($pmatch, $newjs, $this->javascript
);
21402 // return to last page
21403 if ($this->numpages
> 0) {
21404 $this->lastPage(true);
21410 * Clone the specified page to a new page.
21411 * @param int $page number of page to copy (0 = current page)
21412 * @return bool true in case of success, false in case of error.
21414 * @since 4.9.015 (2010-04-20)
21416 public function copyPage($page=0) {
21419 $page = $this->page
;
21421 if (($page < 1) OR ($page > $this->numpages
)) {
21424 // close the last page
21426 // copy all page-related states
21428 $this->page
= $this->numpages
;
21429 $this->setPageBuffer($this->page
, $this->getPageBuffer($page));
21430 $this->pagedim
[$this->page
] = $this->pagedim
[$page];
21431 $this->pagelen
[$this->page
] = $this->pagelen
[$page];
21432 $this->intmrk
[$this->page
] = $this->intmrk
[$page];
21433 $this->bordermrk
[$this->page
] = $this->bordermrk
[$page];
21434 $this->cntmrk
[$this->page
] = $this->cntmrk
[$page];
21435 $this->pageobjects
[$this->page
] = $this->pageobjects
[$page];
21436 $this->pageopen
[$this->page
] = false;
21437 if (isset($this->footerpos
[$page])) {
21438 $this->footerpos
[$this->page
] = $this->footerpos
[$page];
21440 if (isset($this->footerlen
[$page])) {
21441 $this->footerlen
[$this->page
] = $this->footerlen
[$page];
21443 if (isset($this->transfmrk
[$page])) {
21444 $this->transfmrk
[$this->page
] = $this->transfmrk
[$page];
21446 if (isset($this->PageAnnots
[$page])) {
21447 $this->PageAnnots
[$this->page
] = $this->PageAnnots
[$page];
21449 if (isset($this->newpagegroup
[$page])) {
21450 // start a new group
21451 $this->newpagegroup
[$this->page
] = sizeof($this->newpagegroup
) +
1;
21452 $this->currpagegroup
= $this->newpagegroup
[$this->page
];
21453 $this->pagegroups
[$this->currpagegroup
] = 1;
21454 } elseif (isset($this->currpagegroup
) AND ($this->currpagegroup
> 0)) {
21455 ++
$this->pagegroups
[$this->currpagegroup
];
21458 $tmpoutlines = $this->outlines
;
21459 foreach ($tmpoutlines as $key => $outline) {
21460 if ($outline['p'] == $page) {
21461 $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']);
21465 $tmplinks = $this->links
;
21466 foreach ($tmplinks as $key => $link) {
21467 if ($link['p'] == $page) {
21468 $this->links
[] = array('p' => $this->page
, 'y' => $link['y'], 'f' => $link['f']);
21471 // return to last page
21472 $this->lastPage(true);
21477 * Output a Table of Content Index (TOC).
21478 * This method must be called after all Bookmarks were set.
21479 * Before calling this method you have to open the page using the addTOCPage() method.
21480 * After calling this method you have to call endTOCPage() to close the TOC page.
21481 * You can override this method to achieve different styles.
21482 * @param int|null $page page number where this TOC should be inserted (leave empty for current page).
21483 * @param string $numbersfont set the font for page numbers (please use monospaced font for better alignment).
21484 * @param string $filler string used to fill the space between text and page number.
21485 * @param string $toc_name name to use for TOC bookmark.
21486 * @param string $style Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21487 * @param array $color RGB color array for bookmark title (values from 0 to 255).
21489 * @author Nicola Asuni
21490 * @since 4.5.000 (2009-01-02)
21491 * @see addTOCPage(), endTOCPage(), addHTMLTOC()
21493 public function addTOC($page=null, $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) {
21494 $fontsize = $this->FontSizePt
;
21495 $fontfamily = $this->FontFamily
;
21496 $fontstyle = $this->FontStyle
;
21497 $w = $this->w
- $this->lMargin
- $this->rMargin
;
21498 $spacer = $this->GetStringWidth(chr(32)) * 4;
21499 $lmargin = $this->lMargin
;
21500 $rmargin = $this->rMargin
;
21501 $x_start = $this->GetX();
21502 $page_first = $this->page
;
21503 $current_page = $this->page
;
21504 $page_fill_start = false;
21505 $page_fill_end = false;
21506 $current_column = $this->current_column
;
21507 if (TCPDF_STATIC
::empty_string($numbersfont)) {
21508 $numbersfont = $this->default_monospaced_font
;
21510 if (TCPDF_STATIC
::empty_string($filler)) {
21513 if (TCPDF_STATIC
::empty_string($page)) {
21521 $this->setFont($numbersfont, $fontstyle, $fontsize);
21522 $numwidth = $this->GetStringWidth('00000');
21523 $maxpage = 0; //used for pages on attached documents
21524 foreach ($this->outlines
as $key => $outline) {
21525 // check for extra pages (used for attachments)
21526 if (($this->page
> $page_first) AND ($outline['p'] >= $this->numpages
)) {
21527 $outline['p'] +
= ($this->page
- $page_first);
21536 if ($outline['l'] == 0) {
21537 $this->setFont($fontfamily, $outline['s'].'B', $fontsize);
21539 $this->setFont($fontfamily, $outline['s'], $fontsize - $outline['l']);
21541 $this->setTextColorArray($outline['c']);
21542 // check for page break
21543 $this->checkPageBreak(2 * $this->getCellHeight($this->FontSize
));
21544 // set margins and X position
21545 if (($this->page
== $current_page) AND ($this->current_column
== $current_column)) {
21546 $this->lMargin
= $lmargin;
21547 $this->rMargin
= $rmargin;
21549 if ($this->current_column
!= $current_column) {
21551 $x_start = $this->w
- $this->columns
[$this->current_column
]['x'];
21553 $x_start = $this->columns
[$this->current_column
]['x'];
21556 $lmargin = $this->lMargin
;
21557 $rmargin = $this->rMargin
;
21558 $current_page = $this->page
;
21559 $current_column = $this->current_column
;
21561 $this->setX($x_start);
21562 $indent = ($spacer * $outline['l']);
21564 $this->x
-= $indent;
21565 $this->rMargin
= $this->w
- $this->x
;
21567 $this->x +
= $indent;
21568 $this->lMargin
= $this->x
;
21570 $link = $this->AddLink();
21571 $this->setLink($link, $outline['y'], $outline['p']);
21574 $txt = ' '.$outline['t'];
21576 $txt = $outline['t'].' ';
21578 $this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21580 $tw = $this->x
- $this->lMargin
;
21582 $tw = $this->w
- $this->rMargin
- $this->x
;
21584 $this->setFont($numbersfont, $fontstyle, $fontsize);
21585 if (TCPDF_STATIC
::empty_string($page)) {
21586 $pagenum = $outline['p'];
21588 // placemark to be replaced with the correct number
21589 $pagenum = '{#'.($outline['p']).'}';
21590 if ($this->isUnicodeFont()) {
21591 $pagenum = '{'.$pagenum.'}';
21593 $maxpage = max($maxpage, $outline['p']);
21595 $fw = ($tw - $this->GetStringWidth($pagenum.$filler));
21596 $wfiller = $this->GetStringWidth($filler);
21597 if ($wfiller > 0) {
21598 $numfills = floor($fw / $wfiller);
21602 if ($numfills > 0) {
21603 $rowfill = str_repeat($filler, $numfills);
21608 $pagenum = $pagenum.$gap.$rowfill;
21610 $pagenum = $rowfill.$gap.$pagenum;
21612 // write the number
21613 $this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
21615 $page_last = $this->getPage();
21616 $numpages = ($page_last - $page_first +
1);
21617 // account for booklet mode
21618 if ($this->booklet
) {
21619 // check if a blank page is required before TOC
21620 $page_fill_start = ((($page_first %
2) == 0) XOR (($page %
2) == 0));
21621 $page_fill_end = (!((($numpages %
2) == 0) XOR ($page_fill_start)));
21622 if ($page_fill_start) {
21623 // add a page at the end (to be moved before TOC)
21628 if ($page_fill_end) {
21629 // add a page at the end
21635 $maxpage = max($maxpage, $page_last);
21636 if (!TCPDF_STATIC
::empty_string($page)) {
21637 for ($p = $page_first; $p <= $page_last; ++
$p) {
21639 $temppage = $this->getPageBuffer($p);
21640 for ($n = 1; $n <= $maxpage; ++
$n) {
21641 // update page numbers
21643 // get page number aliases
21644 $pnalias = $this->getInternalPageNumberAliases($a);
21645 // calculate replacement number
21646 if (($n >= $page) AND ($n <= $this->numpages
)) {
21647 $np = $n +
$numpages;
21651 $na = TCPDF_STATIC
::formatTOCPageNumber(($this->starting_page_number +
$np - 1));
21652 $nu = TCPDF_FONTS
::UTF8ToUTF16BE($na, false, $this->isunicode
, $this->CurrentFont
);
21653 // replace aliases with numbers
21654 foreach ($pnalias['u'] as $u) {
21655 $sfill = str_repeat($filler, max(0, (strlen($u) - strlen($nu.' '))));
21657 $nr = $nu.TCPDF_FONTS
::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode
, $this->CurrentFont
);
21659 $nr = TCPDF_FONTS
::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode
, $this->CurrentFont
).$nu;
21661 $temppage = str_replace($u, $nr, $temppage);
21663 foreach ($pnalias['a'] as $a) {
21664 $sfill = str_repeat($filler, max(0, (strlen($a) - strlen($na.' '))));
21666 $nr = $na.' '.$sfill;
21668 $nr = $sfill.' '.$na;
21670 $temppage = str_replace($a, $nr, $temppage);
21674 $this->setPageBuffer($p, $temppage);
21677 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21678 if ($page_fill_start) {
21679 $this->movePage($page_last, $page_first);
21681 for ($i = 0; $i < $numpages; ++
$i) {
21682 $this->movePage($page_last, $page);
21688 * Output a Table Of Content Index (TOC) using HTML templates.
21689 * This method must be called after all Bookmarks were set.
21690 * Before calling this method you have to open the page using the addTOCPage() method.
21691 * After calling this method you have to call endTOCPage() to close the TOC page.
21692 * @param int|null $page page number where this TOC should be inserted (leave empty for current page).
21693 * @param string $toc_name name to use for TOC bookmark.
21694 * @param array $templates array of html templates. Use: "#TOC_DESCRIPTION#" for bookmark title, "#TOC_PAGE_NUMBER#" for page number.
21695 * @param boolean $correct_align if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL)
21696 * @param string $style Font style for title: B = Bold, I = Italic, BI = Bold + Italic.
21697 * @param array $color RGB color array for title (values from 0 to 255).
21699 * @author Nicola Asuni
21700 * @since 5.0.001 (2010-05-06)
21701 * @see addTOCPage(), endTOCPage(), addTOC()
21703 public function addHTMLTOC($page=null, $toc_name='TOC', $templates=array(), $correct_align=true, $style='', $color=array(0,0,0)) {
21705 $prev_htmlLinkColorArray = $this->htmlLinkColorArray
;
21706 $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle
;
21707 // set new style for link
21708 $this->htmlLinkColorArray
= array();
21709 $this->htmlLinkFontStyle
= '';
21710 $page_first = $this->getPage();
21711 $page_fill_start = false;
21712 $page_fill_end = false;
21713 // get the font type used for numbers in each template
21714 $current_font = $this->FontFamily
;
21715 foreach ($templates as $level => $html) {
21716 $dom = $this->getHtmlDomArray($html);
21717 foreach ($dom as $key => $value) {
21718 if ($value['value'] == '#TOC_PAGE_NUMBER#') {
21719 $this->setFont($dom[($key - 1)]['fontname']);
21720 $templates['F'.$level] = $this->isUnicodeFont();
21724 $this->setFont($current_font);
21725 $maxpage = 0; //used for pages on attached documents
21726 foreach ($this->outlines
as $key => $outline) {
21727 // get HTML template
21728 $row = $templates[$outline['l']];
21729 if (TCPDF_STATIC
::empty_string($page)) {
21730 $pagenum = $outline['p'];
21732 // placemark to be replaced with the correct number
21733 $pagenum = '{#'.($outline['p']).'}';
21734 if (isset($templates['F'.$outline['l']]) && $templates['F'.$outline['l']]) {
21735 $pagenum = '{'.$pagenum.'}';
21737 $maxpage = max($maxpage, $outline['p']);
21739 // replace templates with current values
21740 $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
21741 $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
21742 // add link to page
21743 $row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
21744 // write bookmark entry
21745 $this->writeHTML($row, false, false, true, false, '');
21747 // restore link styles
21748 $this->htmlLinkColorArray
= $prev_htmlLinkColorArray;
21749 $this->htmlLinkFontStyle
= $prev_htmlLinkFontStyle;
21750 // move TOC page and replace numbers
21751 $page_last = $this->getPage();
21752 $numpages = ($page_last - $page_first +
1);
21753 // account for booklet mode
21754 if ($this->booklet
) {
21755 // check if a blank page is required before TOC
21756 $page_fill_start = ((($page_first %
2) == 0) XOR (($page %
2) == 0));
21757 $page_fill_end = (!((($numpages %
2) == 0) XOR ($page_fill_start)));
21758 if ($page_fill_start) {
21759 // add a page at the end (to be moved before TOC)
21764 if ($page_fill_end) {
21765 // add a page at the end
21771 $maxpage = max($maxpage, $page_last);
21772 if (!TCPDF_STATIC
::empty_string($page)) {
21773 for ($p = $page_first; $p <= $page_last; ++
$p) {
21775 $temppage = $this->getPageBuffer($p);
21776 for ($n = 1; $n <= $maxpage; ++
$n) {
21777 // update page numbers
21779 // get page number aliases
21780 $pnalias = $this->getInternalPageNumberAliases($a);
21781 // calculate replacement number
21783 $np = $n +
$numpages;
21787 $na = TCPDF_STATIC
::formatTOCPageNumber(($this->starting_page_number +
$np - 1));
21788 $nu = TCPDF_FONTS
::UTF8ToUTF16BE($na, false, $this->isunicode
, $this->CurrentFont
);
21789 // replace aliases with numbers
21790 foreach ($pnalias['u'] as $u) {
21791 if ($correct_align) {
21792 $sfill = str_repeat($filler, (strlen($u) - strlen($nu.' ')));
21794 $nr = $nu.TCPDF_FONTS
::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode
, $this->CurrentFont
);
21796 $nr = TCPDF_FONTS
::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode
, $this->CurrentFont
).$nu;
21801 $temppage = str_replace($u, $nr, $temppage);
21803 foreach ($pnalias['a'] as $a) {
21804 if ($correct_align) {
21805 $sfill = str_repeat($filler, (strlen($a) - strlen($na.' ')));
21807 $nr = $na.' '.$sfill;
21809 $nr = $sfill.' '.$na;
21814 $temppage = str_replace($a, $nr, $temppage);
21818 $this->setPageBuffer($p, $temppage);
21821 $this->Bookmark($toc_name, 0, 0, $page_first, $style, $color);
21822 if ($page_fill_start) {
21823 $this->movePage($page_last, $page_first);
21825 for ($i = 0; $i < $numpages; ++
$i) {
21826 $this->movePage($page_last, $page);
21832 * Stores a copy of the current TCPDF object used for undo operation.
21834 * @since 4.5.029 (2009-03-19)
21836 public function startTransaction() {
21837 if (isset($this->objcopy
)) {
21838 // remove previous copy
21839 $this->commitTransaction();
21841 // record current page number and Y position
21842 $this->start_transaction_page
= $this->page
;
21843 $this->start_transaction_y
= $this->y
;
21844 // clone current object
21845 $this->objcopy
= TCPDF_STATIC
::objclone($this);
21849 * Delete the copy of the current TCPDF object used for undo operation.
21851 * @since 4.5.029 (2009-03-19)
21853 public function commitTransaction() {
21854 if (isset($this->objcopy
)) {
21855 $this->objcopy
->_destroy(true, true);
21856 /* The unique file_id should not be used during cleanup again */
21857 $this->objcopy
->file_id
= NULL;
21858 unset($this->objcopy
);
21863 * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
21864 * @param boolean $self if true restores current class object to previous state without the need of reassignment via the returned value.
21865 * @return TCPDF object.
21867 * @since 4.5.029 (2009-03-19)
21869 public function rollbackTransaction($self=false) {
21870 if (isset($this->objcopy
)) {
21871 $objcopy = $this->objcopy
;
21872 $this->_destroy(true, true);
21874 $objvars = get_object_vars($objcopy);
21875 foreach ($objvars as $key => $value) {
21876 $this->$key = $value;
21878 $objcopy->_destroy(true, true);
21879 /* The unique file_id should not be used during cleanup again */
21880 $objcopy->file_id
= NULL;
21884 /* The unique file_id should not be used during cleanup again */
21885 $this->file_id
= NULL;
21891 // --- MULTI COLUMNS METHODS -----------------------
21894 * Set multiple columns of the same size
21895 * @param int $numcols number of columns (set to zero to disable columns mode)
21896 * @param int $width column width
21897 * @param int|null $y column starting Y position (leave empty for current Y position)
21899 * @since 4.9.001 (2010-03-28)
21901 public function setEqualColumns($numcols=0, $width=0, $y=null) {
21902 $this->columns
= array();
21903 if ($numcols < 2) {
21905 $this->columns
= array();
21907 // maximum column width
21908 $maxwidth = ($this->w
- $this->original_lMargin
- $this->original_rMargin
) / $numcols;
21909 if (($width == 0) OR ($width > $maxwidth)) {
21910 $width = $maxwidth;
21912 if (TCPDF_STATIC
::empty_string($y)) {
21915 // space between columns
21916 $space = (($this->w
- $this->original_lMargin
- $this->original_rMargin
- ($numcols * $width)) / ($numcols - 1));
21917 // fill the columns array (with, space, starting Y position)
21918 for ($i = 0; $i < $numcols; ++
$i) {
21919 $this->columns
[$i] = array('w' => $width, 's' => $space, 'y' => $y);
21922 $this->num_columns
= $numcols;
21923 $this->current_column
= 0;
21924 $this->column_start_page
= $this->page
;
21925 $this->selectColumn(0);
21929 * Remove columns and reset page margins.
21931 * @since 5.9.072 (2011-04-26)
21933 public function resetColumns() {
21934 $this->lMargin
= $this->original_lMargin
;
21935 $this->rMargin
= $this->original_rMargin
;
21936 $this->setEqualColumns();
21940 * Set columns array.
21941 * Each column is represented by an array of arrays with the following keys: (w = width, s = space between columns, y = column top position).
21942 * @param array $columns
21944 * @since 4.9.001 (2010-03-28)
21946 public function setColumnsArray($columns) {
21947 $this->columns
= $columns;
21948 $this->num_columns
= count($columns);
21949 $this->current_column
= 0;
21950 $this->column_start_page
= $this->page
;
21951 $this->selectColumn(0);
21955 * Set position at a given column
21956 * @param int|null $col column number (from 0 to getNumberOfColumns()-1); empty string = current column.
21958 * @since 4.9.001 (2010-03-28)
21960 public function selectColumn($col=null) {
21961 if (TCPDF_STATIC
::empty_string($col)) {
21962 $col = $this->current_column
;
21963 } elseif ($col >= $this->num_columns
) {
21966 $xshift = array('x' => 0, 's' => array('H' => 0, 'V' => 0), 'p' => array('L' => 0, 'T' => 0, 'R' => 0, 'B' => 0));
21967 $enable_thead = false;
21968 if ($this->num_columns
> 1) {
21969 if ($col != $this->current_column
) {
21970 // move Y pointer at the top of the column
21971 if ($this->column_start_page
== $this->page
) {
21972 $this->y
= $this->columns
[$col]['y'];
21974 $this->y
= $this->tMargin
;
21976 // Avoid to write table headers more than once
21977 if (($this->page
> $this->maxselcol
['page']) OR (($this->page
== $this->maxselcol
['page']) AND ($col > $this->maxselcol
['column']))) {
21978 $enable_thead = true;
21979 $this->maxselcol
['page'] = $this->page
;
21980 $this->maxselcol
['column'] = $col;
21983 $xshift = $this->colxshift
;
21984 // set X position of the current column by case
21985 $listindent = ($this->listindentlevel
* $this->listindent
);
21986 // calculate column X position
21988 for ($i = 0; $i < $col; ++
$i) {
21989 $colpos +
= ($this->columns
[$i]['w'] +
$this->columns
[$i]['s']);
21992 $x = $this->w
- $this->original_rMargin
- $colpos;
21993 $this->rMargin
= ($this->w
- $x +
$listindent);
21994 $this->lMargin
= ($x - $this->columns
[$col]['w']);
21995 $this->x
= $x - $listindent;
21997 $x = $this->original_lMargin +
$colpos;
21998 $this->lMargin
= ($x +
$listindent);
21999 $this->rMargin
= ($this->w
- $x - $this->columns
[$col]['w']);
22000 $this->x
= $x +
$listindent;
22002 $this->columns
[$col]['x'] = $x;
22004 $this->current_column
= $col;
22005 // fix for HTML mode
22006 $this->newline
= true;
22007 // print HTML table header (if any)
22008 if ((!TCPDF_STATIC
::empty_string($this->thead
)) AND (!$this->inthead
)) {
22009 if ($enable_thead) {
22010 // print table header
22011 $this->writeHTML($this->thead
, false, false, false, false, '');
22012 $this->y +
= $xshift['s']['V'];
22013 // store end of header position
22014 if (!isset($this->columns
[$col]['th'])) {
22015 $this->columns
[$col]['th'] = array();
22017 $this->columns
[$col]['th']['\''.$this->page
.'\''] = $this->y
;
22019 } elseif (isset($this->columns
[$col]['th']['\''.$this->page
.'\''])) {
22020 $this->y
= $this->columns
[$col]['th']['\''.$this->page
.'\''];
22023 // account for an html table cell over multiple columns
22025 $this->rMargin +
= $xshift['x'];
22026 $this->x
-= ($xshift['x'] +
$xshift['p']['R']);
22028 $this->lMargin +
= $xshift['x'];
22029 $this->x +
= $xshift['x'] +
$xshift['p']['L'];
22034 * Return the current column number
22035 * @return int current column number
22037 * @since 5.5.011 (2010-07-08)
22039 public function getColumn() {
22040 return $this->current_column
;
22044 * Return the current number of columns.
22045 * @return int number of columns
22047 * @since 5.8.018 (2010-08-25)
22049 public function getNumberOfColumns() {
22050 return $this->num_columns
;
22054 * Set Text rendering mode.
22055 * @param int $stroke outline size in user units (0 = disable).
22056 * @param boolean $fill if true fills the text (default).
22057 * @param boolean $clip if true activate clipping mode
22059 * @since 4.9.008 (2009-04-02)
22061 public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
22062 // Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
22063 // convert text rendering parameters
22064 if ($stroke < 0 ||
!is_numeric($stroke)) {
22067 if ($fill === true) {
22069 if ($clip === true) {
22070 // Fill, then stroke text and add to path for clipping
22071 $textrendermode = 6;
22073 // Fill, then stroke text
22074 $textrendermode = 2;
22076 $textstrokewidth = $stroke;
22078 if ($clip === true) {
22079 // Fill text and add to path for clipping
22080 $textrendermode = 4;
22083 $textrendermode = 0;
22088 if ($clip === true) {
22089 // Stroke text and add to path for clipping
22090 $textrendermode = 5;
22093 $textrendermode = 1;
22095 $textstrokewidth = $stroke;
22097 if ($clip === true) {
22098 // Add text to path for clipping
22099 $textrendermode = 7;
22101 // Neither fill nor stroke text (invisible)
22102 $textrendermode = 3;
22106 $this->textrendermode
= $textrendermode;
22107 $this->textstrokewidth
= $stroke;
22111 * Set parameters for drop shadow effect for text.
22112 * @param array $params 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.
22113 * @since 5.9.174 (2012-07-25)
22116 public function setTextShadow($params=array('enabled'=>false, 'depth_w'=>0, 'depth_h'=>0, 'color'=>false, 'opacity'=>1, 'blend_mode'=>'Normal')) {
22117 if (isset($params['enabled'])) {
22118 $this->txtshadow
['enabled'] = $params['enabled']?
true:false;
22120 $this->txtshadow
['enabled'] = false;
22122 if (isset($params['depth_w'])) {
22123 $this->txtshadow
['depth_w'] = floatval($params['depth_w']);
22125 $this->txtshadow
['depth_w'] = 0;
22127 if (isset($params['depth_h'])) {
22128 $this->txtshadow
['depth_h'] = floatval($params['depth_h']);
22130 $this->txtshadow
['depth_h'] = 0;
22132 if (isset($params['color']) AND ($params['color'] !== false) AND is_array($params['color'])) {
22133 $this->txtshadow
['color'] = $params['color'];
22135 $this->txtshadow
['color'] = $this->strokecolor
;
22137 if (isset($params['opacity'])) {
22138 $this->txtshadow
['opacity'] = min(1, max(0, floatval($params['opacity'])));
22140 $this->txtshadow
['opacity'] = 1;
22142 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'))) {
22143 $this->txtshadow
['blend_mode'] = $params['blend_mode'];
22145 $this->txtshadow
['blend_mode'] = 'Normal';
22147 if ((($this->txtshadow
['depth_w'] == 0) AND ($this->txtshadow
['depth_h'] == 0)) OR ($this->txtshadow
['opacity'] == 0)) {
22148 $this->txtshadow
['enabled'] = false;
22153 * Return the text shadow parameters array.
22154 * @return array array of parameters.
22155 * @since 5.9.174 (2012-07-25)
22158 public function getTextShadow() {
22159 return $this->txtshadow
;
22163 * Returns an array of chars containing soft hyphens.
22164 * @param array $word array of chars
22165 * @param array $patterns Array of hypenation patterns.
22166 * @param array $dictionary Array of words to be returned without applying the hyphenation algorithm.
22167 * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
22168 * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
22169 * @param int $charmin Minimum word length to apply the hyphenation algorithm.
22170 * @param int $charmax Maximum length of broken piece of word.
22171 * @return array text with soft hyphens
22172 * @author Nicola Asuni
22173 * @since 4.9.012 (2010-04-12)
22176 protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22177 $hyphenword = array(); // hyphens positions
22178 $numchars = count($word);
22179 if ($numchars <= $charmin) {
22182 $word_string = TCPDF_FONTS
::UTF8ArrSubString($word, '', '', $this->isunicode
);
22183 // some words will be returned as-is
22184 $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})(\]?)$/';
22185 if (preg_match($pattern, $word_string) > 0) {
22189 $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})(\]?)$/';
22190 if (preg_match($pattern, $word_string) > 0) {
22194 if (isset($dictionary[$word_string])) {
22195 return TCPDF_FONTS
::UTF8StringToArray($dictionary[$word_string], $this->isunicode
, $this->CurrentFont
);
22197 // surround word with '_' characters
22198 $tmpword = array_merge(array(46), $word, array(46));
22199 $tmpnumchars = $numchars +
2;
22200 $maxpos = $tmpnumchars - 1;
22201 for ($pos = 0; $pos < $maxpos; ++
$pos) {
22202 $imax = min(($tmpnumchars - $pos), $charmax);
22203 for ($i = 1; $i <= $imax; ++
$i) {
22204 $subword = strtolower(TCPDF_FONTS
::UTF8ArrSubString($tmpword, $pos, ($pos +
$i), $this->isunicode
));
22205 if (isset($patterns[$subword])) {
22206 $pattern = TCPDF_FONTS
::UTF8StringToArray($patterns[$subword], $this->isunicode
, $this->CurrentFont
);
22207 $pattern_length = count($pattern);
22209 for ($j = 0; $j < $pattern_length; ++
$j) {
22210 // check if $pattern[$j] is a number = hyphenation level (only numbers from 1 to 5 are valid)
22211 if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
22215 $zero = $pos +
$j - $digits;
22217 // get hyphenation level
22218 $level = ($pattern[$j] - 48);
22219 // if two levels from two different patterns match at the same point, the higher one is selected.
22220 if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] < $level)) {
22221 $hyphenword[$zero] = $level;
22230 $maxpos = $numchars - $rightmin;
22231 for ($i = $leftmin; $i <= $maxpos; ++
$i) {
22232 // only odd levels indicate allowed hyphenation points
22233 if (isset($hyphenword[$i]) AND (($hyphenword[$i] %
2) != 0)) {
22234 // 173 = soft hyphen character
22235 array_splice($word, $i +
$inserted, 0, 173);
22243 * Returns text with soft hyphens.
22244 * @param string $text text to process
22245 * @param mixed $patterns 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/
22246 * @param array $dictionary Array of words to be returned without applying the hyphenation algorithm.
22247 * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
22248 * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
22249 * @param int $charmin Minimum word length to apply the hyphenation algorithm.
22250 * @param int $charmax Maximum length of broken piece of word.
22251 * @return string text with soft hyphens
22252 * @author Nicola Asuni
22253 * @since 4.9.012 (2010-04-12)
22256 public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
22257 $text = $this->unhtmlentities($text);
22258 $word = array(); // last word
22259 $txtarr = array(); // text to be returned
22260 $intag = false; // true if we are inside an HTML tag
22261 $skip = false; // true to skip hyphenation
22262 if (!is_array($patterns)) {
22263 $patterns = TCPDF_STATIC
::getHyphenPatternsFromTEX($patterns);
22265 // get array of characters
22266 $unichars = TCPDF_FONTS
::UTF8StringToArray($text, $this->isunicode
, $this->CurrentFont
);
22268 foreach ($unichars as $char) {
22269 if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA
::$uni_type[$char] == 'L') {
22270 // letter character
22273 // other type of character
22274 if (!TCPDF_STATIC
::empty_string($word)) {
22275 // hypenate the word
22276 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22280 if (chr($char) == '<') {
22281 // we are inside an HTML tag
22283 } elseif ($intag AND (chr($char) == '>')) {
22286 // check for style tag
22287 $expected = array(115, 116, 121, 108, 101); // = 'style'
22288 $current = array_slice($txtarr, -6, 5); // last 5 chars
22289 $compare = array_diff($expected, $current);
22290 if (empty($compare)) {
22291 // check if it is a closing tag
22292 $expected = array(47); // = '/'
22293 $current = array_slice($txtarr, -7, 1);
22294 $compare = array_diff($expected, $current);
22295 if (empty($compare)) {
22296 // closing style tag
22299 // opening style tag
22306 if (!TCPDF_STATIC
::empty_string($word)) {
22307 // hypenate the word
22308 $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
22310 // convert char array to string and return
22311 return TCPDF_FONTS
::UTF8ArrSubString($txtarr, '', '', $this->isunicode
);
22315 * Enable/disable rasterization of vector images using ImageMagick library.
22316 * @param boolean $mode if true enable rasterization, false otherwise.
22318 * @since 5.0.000 (2010-04-27)
22320 public function setRasterizeVectorImages($mode) {
22321 $this->rasterize_vector_images
= $mode;
22325 * Enable or disable default option for font subsetting.
22326 * @param boolean $enable if true enable font subsetting by default.
22327 * @author Nicola Asuni
22329 * @since 5.3.002 (2010-06-07)
22331 public function setFontSubsetting($enable=true) {
22332 if ($this->pdfa_mode
) {
22333 $this->font_subsetting
= false;
22335 $this->font_subsetting
= $enable ?
true : false;
22340 * Return the default option for font subsetting.
22341 * @return bool default font subsetting state.
22342 * @author Nicola Asuni
22344 * @since 5.3.002 (2010-06-07)
22346 public function getFontSubsetting() {
22347 return $this->font_subsetting
;
22351 * Left trim the input string
22352 * @param string $str string to trim
22353 * @param string $replace string that replace spaces.
22354 * @return string left trimmed string
22355 * @author Nicola Asuni
22357 * @since 5.8.000 (2010-08-11)
22359 public function stringLeftTrim($str, $replace='') {
22360 return preg_replace('/^'.$this->re_space
['p'].'+/'.$this->re_space
['m'], $replace, $str);
22364 * Right trim the input string
22365 * @param string $str string to trim
22366 * @param string $replace string that replace spaces.
22367 * @return string right trimmed string
22368 * @author Nicola Asuni
22370 * @since 5.8.000 (2010-08-11)
22372 public function stringRightTrim($str, $replace='') {
22373 return preg_replace('/'.$this->re_space
['p'].'+$/'.$this->re_space
['m'], $replace, $str);
22377 * Trim the input string
22378 * @param string $str string to trim
22379 * @param string $replace string that replace spaces.
22380 * @return string trimmed string
22381 * @author Nicola Asuni
22383 * @since 5.8.000 (2010-08-11)
22385 public function stringTrim($str, $replace='') {
22386 $str = $this->stringLeftTrim($str, $replace);
22387 $str = $this->stringRightTrim($str, $replace);
22392 * Return true if the current font is unicode type.
22393 * @return bool true for unicode font, false otherwise.
22394 * @author Nicola Asuni
22396 * @since 5.8.002 (2010-08-14)
22398 public function isUnicodeFont() {
22399 return (($this->CurrentFont
['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont
['type'] == 'cidfont0'));
22403 * Return normalized font name
22404 * @param string $fontfamily property string containing font family names
22405 * @return string normalized font name
22406 * @author Nicola Asuni
22408 * @since 5.8.004 (2010-08-17)
22410 public function getFontFamilyName($fontfamily) {
22411 // remove spaces and symbols
22412 $fontfamily = preg_replace('/[^a-z0-9_\,]/', '', strtolower($fontfamily));
22413 // extract all font names
22414 $fontslist = preg_split('/[,]/', $fontfamily);
22415 // find first valid font name
22416 foreach ($fontslist as $font) {
22417 // replace font variations
22418 $font = preg_replace('/regular$/', '', $font);
22419 $font = preg_replace('/italic$/', 'I', $font);
22420 $font = preg_replace('/oblique$/', 'I', $font);
22421 $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
22422 // replace common family names and core fonts
22423 $pattern = array();
22424 $replacement = array();
22425 $pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
22426 $replacement[] = 'times';
22427 $pattern[] = '/^sansserif/';
22428 $replacement[] = 'helvetica';
22429 $pattern[] = '/^monospace/';
22430 $replacement[] = 'courier';
22431 $font = preg_replace($pattern, $replacement, $font);
22432 if (in_array(strtolower($font), $this->fontlist
) OR in_array($font, $this->fontkeys
)) {
22436 // return current font as default
22437 return $this->CurrentFont
['fontkey'];
22441 * Start a new XObject Template.
22442 * 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).
22443 * 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.
22444 * Note: X,Y coordinates will be reset to 0,0.
22445 * @param int $w Template width in user units (empty string or zero = page width less margins).
22446 * @param int $h Template height in user units (empty string or zero = page height less margins).
22447 * @param mixed $group 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).
22448 * @return string|false the XObject Template ID in case of success or false in case of error.
22449 * @author Nicola Asuni
22451 * @since 5.8.017 (2010-08-24)
22452 * @see endTemplate(), printTemplate()
22454 public function startTemplate($w=0, $h=0, $group=false) {
22455 if ($this->inxobj
) {
22456 // we are already inside an XObject template
22459 $this->inxobj
= true;
22462 $this->xobjid
= 'XT'.$this->n
;
22464 $this->xobjects
[$this->xobjid
] = array('n' => $this->n
);
22465 // store current graphic state
22466 $this->xobjects
[$this->xobjid
]['gvars'] = $this->getGraphicVars();
22468 $this->xobjects
[$this->xobjid
]['intmrk'] = 0;
22469 $this->xobjects
[$this->xobjid
]['transfmrk'] = array();
22470 $this->xobjects
[$this->xobjid
]['outdata'] = '';
22471 $this->xobjects
[$this->xobjid
]['xobjects'] = array();
22472 $this->xobjects
[$this->xobjid
]['images'] = array();
22473 $this->xobjects
[$this->xobjid
]['fonts'] = array();
22474 $this->xobjects
[$this->xobjid
]['annotations'] = array();
22475 $this->xobjects
[$this->xobjid
]['extgstates'] = array();
22476 $this->xobjects
[$this->xobjid
]['gradients'] = array();
22477 $this->xobjects
[$this->xobjid
]['spot_colors'] = array();
22478 // set new environment
22479 $this->num_columns
= 1;
22480 $this->current_column
= 0;
22481 $this->setAutoPageBreak(false);
22482 if (($w === '') OR ($w <= 0)) {
22483 $w = $this->w
- $this->lMargin
- $this->rMargin
;
22485 if (($h === '') OR ($h <= 0)) {
22486 $h = $this->h
- $this->tMargin
- $this->bMargin
;
22488 $this->xobjects
[$this->xobjid
]['x'] = 0;
22489 $this->xobjects
[$this->xobjid
]['y'] = 0;
22490 $this->xobjects
[$this->xobjid
]['w'] = $w;
22491 $this->xobjects
[$this->xobjid
]['h'] = $h;
22494 $this->wPt
= $this->w
* $this->k
;
22495 $this->hPt
= $this->h
* $this->k
;
22496 $this->fwPt
= $this->wPt
;
22497 $this->fhPt
= $this->hPt
;
22500 $this->lMargin
= 0;
22501 $this->rMargin
= 0;
22502 $this->tMargin
= 0;
22503 $this->bMargin
= 0;
22505 $this->xobjects
[$this->xobjid
]['group'] = $group;
22506 return $this->xobjid
;
22510 * End the current XObject Template started with startTemplate() and restore the previous graphic state.
22511 * 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).
22512 * 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.
22513 * @return string|false the XObject Template ID in case of success or false in case of error.
22514 * @author Nicola Asuni
22516 * @since 5.8.017 (2010-08-24)
22517 * @see startTemplate(), printTemplate()
22519 public function endTemplate() {
22520 if (!$this->inxobj
) {
22521 // we are not inside a template
22524 $this->inxobj
= false;
22525 // restore previous graphic state
22526 $this->setGraphicVars($this->xobjects
[$this->xobjid
]['gvars'], true);
22527 return $this->xobjid
;
22531 * Print an XObject Template.
22532 * You can print an XObject Template inside the currently opened Template.
22533 * 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).
22534 * 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.
22535 * @param string $id The ID of XObject Template to print.
22536 * @param float|null $x X position in user units (empty string = current x position)
22537 * @param float|null $y Y position in user units (empty string = current y position)
22538 * @param float $w Width in user units (zero = remaining page width)
22539 * @param float $h Height in user units (zero = remaining page height)
22540 * @param string $align 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>
22541 * @param string $palign 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>
22542 * @param boolean $fitonpage If true the template is resized to not exceed page dimensions.
22543 * @author Nicola Asuni
22545 * @since 5.8.017 (2010-08-24)
22546 * @see startTemplate(), endTemplate()
22548 public function printTemplate($id, $x=null, $y=null, $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
22549 if ($this->state
!= 2) {
22552 if (!isset($this->xobjects
[$id])) {
22553 $this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
22555 if ($this->inxobj
) {
22556 if ($id == $this->xobjid
) {
22557 // close current template
22558 $this->endTemplate();
22560 // use the template as resource for the template currently opened
22561 $this->xobjects
[$this->xobjid
]['xobjects'][$id] = $this->xobjects
[$id];
22564 // set default values
22565 if (TCPDF_STATIC
::empty_string($x)) {
22568 if (TCPDF_STATIC
::empty_string($y)) {
22571 // check page for no-write regions and adapt page margins if necessary
22572 list($x, $y) = $this->checkPageRegions($h, $x, $y);
22573 $ow = $this->xobjects
[$id]['w'];
22577 $oh = $this->xobjects
[$id]['h'];
22581 // calculate template width and height on document
22582 if (($w <= 0) AND ($h <= 0)) {
22585 } elseif ($w <= 0) {
22586 $w = $h * $ow / $oh;
22587 } elseif ($h <= 0) {
22588 $h = $w * $oh / $ow;
22590 // fit the template on available space
22591 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
22592 // set page alignment
22596 if ($palign == 'L') {
22597 $xt = $this->lMargin
;
22598 } elseif ($palign == 'C') {
22599 $xt = ($this->w +
$this->lMargin
- $this->rMargin
- $w) / 2;
22600 } elseif ($palign == 'R') {
22601 $xt = $this->w
- $this->rMargin
- $w;
22607 if ($palign == 'L') {
22608 $xt = $this->lMargin
;
22609 } elseif ($palign == 'C') {
22610 $xt = ($this->w +
$this->lMargin
- $this->rMargin
- $w) / 2;
22611 } elseif ($palign == 'R') {
22612 $xt = $this->w
- $this->rMargin
- $w;
22618 // print XObject Template + Transformation matrix
22619 $this->StartTransform();
22620 // translate and scale
22628 $tm[4] = $xt * $this->k
;
22629 $tm[5] = ($this->h
- $h - $y) * $this->k
;
22630 $this->Transform($tm);
22632 $this->_out('/'.$id.' Do');
22633 $this->StopTransform();
22635 if (!empty($this->xobjects
[$id]['annotations'])) {
22636 foreach ($this->xobjects
[$id]['annotations'] as $annot) {
22637 // transform original coordinates
22638 $coordlt = TCPDF_STATIC
::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k
), (-$annot['y'] * $this->k
)));
22639 $ax = ($coordlt[4] / $this->k
);
22640 $ay = ($this->h
- $h - ($coordlt[5] / $this->k
));
22641 $coordrb = TCPDF_STATIC
::getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] +
$annot['w']) * $this->k
), ((-$annot['y'] - $annot['h']) * $this->k
)));
22642 $aw = ($coordrb[4] / $this->k
) - $ax;
22643 $ah = ($this->h
- $h - ($coordrb[5] / $this->k
)) - $ay;
22644 $this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
22647 // set pointer to align the next text/objects
22655 $this->y
= $y +
round($h/2);
22665 $this->setY($rb_y);
22675 * Set the percentage of character stretching.
22676 * @param int $perc percentage of stretching (100 = no stretching)
22677 * @author Nicola Asuni
22679 * @since 5.9.000 (2010-09-29)
22681 public function setFontStretching($perc=100) {
22682 $this->font_stretching
= $perc;
22686 * Get the percentage of character stretching.
22687 * @return float stretching value
22688 * @author Nicola Asuni
22690 * @since 5.9.000 (2010-09-29)
22692 public function getFontStretching() {
22693 return $this->font_stretching
;
22697 * Set the amount to increase or decrease the space between characters in a text.
22698 * @param float $spacing amount to increase or decrease the space between characters in a text (0 = default spacing)
22699 * @author Nicola Asuni
22701 * @since 5.9.000 (2010-09-29)
22703 public function setFontSpacing($spacing=0) {
22704 $this->font_spacing
= $spacing;
22708 * Get the amount to increase or decrease the space between characters in a text.
22709 * @return int font spacing (tracking) value
22710 * @author Nicola Asuni
22712 * @since 5.9.000 (2010-09-29)
22714 public function getFontSpacing() {
22715 return $this->font_spacing
;
22719 * Return an array of no-write page regions
22720 * @return array of no-write page regions
22721 * @author Nicola Asuni
22723 * @since 5.9.003 (2010-10-13)
22724 * @see setPageRegions(), addPageRegion()
22726 public function getPageRegions() {
22727 return $this->page_regions
;
22731 * Set no-write regions on page.
22732 * 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.
22733 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22734 * You can set multiple regions for the same page.
22735 * @param array $regions 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.
22736 * @author Nicola Asuni
22738 * @since 5.9.003 (2010-10-13)
22739 * @see addPageRegion(), getPageRegions()
22741 public function setPageRegions($regions=array()) {
22742 // empty current regions array
22743 $this->page_regions
= array();
22745 foreach ($regions as $data) {
22746 $this->addPageRegion($data);
22751 * Add a single no-write region on selected page.
22752 * 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.
22753 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22754 * You can set multiple regions for the same page.
22755 * @param array $region 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).
22756 * @author Nicola Asuni
22758 * @since 5.9.003 (2010-10-13)
22759 * @see setPageRegions(), getPageRegions()
22761 public function addPageRegion($region) {
22762 if (!isset($region['page']) OR empty($region['page'])) {
22763 $region['page'] = $this->page
;
22765 if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
22766 AND isset($region['yt']) AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
22767 AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
22768 $this->page_regions
[] = $region;
22773 * Remove a single no-write region.
22774 * @param int $key region key
22775 * @author Nicola Asuni
22777 * @since 5.9.003 (2010-10-13)
22778 * @see setPageRegions(), getPageRegions()
22780 public function removePageRegion($key) {
22781 if (isset($this->page_regions
[$key])) {
22782 unset($this->page_regions
[$key]);
22787 * Check page for no-write regions and adapt current coordinates and page margins if necessary.
22788 * 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.
22789 * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
22790 * @param float $h height of the text/image/object to print in user units
22791 * @param float $x current X coordinate in user units
22792 * @param float $y current Y coordinate in user units
22793 * @return float[] array($x, $y)
22794 * @author Nicola Asuni
22796 * @since 5.9.003 (2010-10-13)
22798 protected function checkPageRegions($h, $x, $y) {
22799 // set default values
22806 if (!$this->check_page_regions
OR empty($this->page_regions
)) {
22807 // no page regions defined
22808 return array($x, $y);
22811 $h = $this->getCellHeight($this->FontSize
);
22813 // check for page break
22814 if ($this->checkPageBreak($h, $y)) {
22815 // the content will be printed on a new page
22819 if ($this->num_columns
> 1) {
22821 $this->lMargin
= ($this->columns
[$this->current_column
]['x'] - $this->columns
[$this->current_column
]['w']);
22823 $this->rMargin
= ($this->w
- $this->columns
[$this->current_column
]['x'] - $this->columns
[$this->current_column
]['w']);
22827 $this->lMargin
= max($this->clMargin
, $this->original_lMargin
);
22829 $this->rMargin
= max($this->crMargin
, $this->original_rMargin
);
22832 // adjust coordinates and page margins
22833 foreach ($this->page_regions
as $regid => $regdata) {
22834 if ($regdata['page'] == $this->page
) {
22835 // check region boundaries
22836 if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
22837 // Y is inside the region
22838 $minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
22839 $yt = max($y, $regdata['yt']);
22840 $yb = min(($yt +
$h), $regdata['yb']);
22841 $xt = (($yt - $regdata['yt']) * $minv) +
$regdata['xt'];
22842 $xb = (($yb - $regdata['yt']) * $minv) +
$regdata['xt'];
22843 if ($regdata['side'] == 'L') { // left side
22844 $new_margin = max($xt, $xb);
22845 if ($this->lMargin
< $new_margin) {
22847 // adjust left page margin
22848 $this->lMargin
= max(0, $new_margin);
22850 if ($x < $new_margin) {
22851 // adjust x position
22853 if ($new_margin > ($this->w
- $this->rMargin
)) {
22854 // adjust y position
22855 $y = $regdata['yb'] - $h;
22859 } elseif ($regdata['side'] == 'R') { // right side
22860 $new_margin = min($xt, $xb);
22861 if (($this->w
- $this->rMargin
) > $new_margin) {
22863 // adjust right page margin
22864 $this->rMargin
= max(0, ($this->w
- $new_margin));
22866 if ($x > $new_margin) {
22867 // adjust x position
22869 if ($new_margin > $this->lMargin
) {
22870 // adjust y position
22871 $y = $regdata['yb'] - $h;
22879 return array($x, $y);
22882 // --- SVG METHODS ---------------------------------------------------------
22885 * Embedd a Scalable Vector Graphics (SVG) image.
22886 * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
22887 * @param string $file Name of the SVG file or a '@' character followed by the SVG data string.
22888 * @param float|null $x Abscissa of the upper-left corner.
22889 * @param float|null $y Ordinate of the upper-left corner.
22890 * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
22891 * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
22892 * @param mixed $link URL or identifier returned by AddLink().
22893 * @param string $align 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.
22894 * @param string $palign 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>
22895 * @param mixed $border 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)))
22896 * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
22897 * @author Nicola Asuni
22898 * @since 5.0.000 (2010-05-02)
22901 public function ImageSVG($file, $x=null, $y=null, $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
22902 if ($this->state
!= 2) {
22906 $this->svggradients
= array();
22907 $this->svggradientid
= 0;
22908 $this->svgdefsmode
= false;
22909 $this->svgdefs
= array();
22910 $this->svgclipmode
= false;
22911 $this->svgclippaths
= array();
22912 $this->svgcliptm
= array();
22913 $this->svgclipid
= 0;
22914 $this->svgtext
= '';
22915 $this->svgtextmode
= array();
22916 if ($this->rasterize_vector_images
AND ($w > 0) AND ($h > 0)) {
22917 // convert SVG to raster image using GD or ImageMagick libraries
22918 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
22920 if ($file[0] === '@') { // image from string
22921 $this->svgdir
= '';
22922 $svgdata = substr($file, 1);
22923 } else { // SVG file
22924 $this->svgdir
= dirname($file);
22925 $svgdata = $this->getCachedFileContents($file);
22927 if ($svgdata === FALSE) {
22928 $this->Error('SVG file not found: '.$file);
22930 if (TCPDF_STATIC
::empty_string($x)) {
22933 if (TCPDF_STATIC
::empty_string($y)) {
22936 // check page for no-write regions and adapt page margins if necessary
22937 list($x, $y) = $this->checkPageRegions($h, $x, $y);
22943 $aspect_ratio_align = 'xMidYMid';
22944 $aspect_ratio_ms = 'meet';
22946 // get original image width and height
22947 preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
22948 if (isset($regs[1]) AND !empty($regs[1])) {
22950 if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22951 $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit
, false);
22954 if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22955 $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit
, false);
22958 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22959 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit
, false);
22962 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22963 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit
, false);
22966 $view_box = array();
22967 if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
22968 if (count($tmp) == 5) {
22970 foreach ($tmp as $key => $val) {
22971 $view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit
, false);
22973 $ox = $view_box[0];
22974 $oy = $view_box[1];
22976 // get aspect ratio
22978 if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22979 $aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
22980 switch (count($aspect_ratio)) {
22982 $aspect_ratio_align = $aspect_ratio[1];
22983 $aspect_ratio_ms = $aspect_ratio[2];
22987 $aspect_ratio_align = $aspect_ratio[0];
22988 $aspect_ratio_ms = $aspect_ratio[1];
22992 $aspect_ratio_align = $aspect_ratio[0];
22993 $aspect_ratio_ms = 'meet';
23006 // calculate image width and height on document
23007 if (($w <= 0) AND ($h <= 0)) {
23008 // convert image size to document unit
23011 } elseif ($w <= 0) {
23012 $w = $h * $ow / $oh;
23013 } elseif ($h <= 0) {
23014 $h = $w * $oh / $ow;
23016 // fit the image on available space
23017 list($w, $h, $x, $y) = $this->fitBlock($w, $h, $x, $y, $fitonpage);
23018 if ($this->rasterize_vector_images
) {
23019 // convert SVG to raster image using GD or ImageMagick libraries
23020 return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
23023 $this->img_rb_y
= $y +
$h;
23026 if ($palign == 'L') {
23027 $ximg = $this->lMargin
;
23028 } elseif ($palign == 'C') {
23029 $ximg = ($this->w +
$this->lMargin
- $this->rMargin
- $w) / 2;
23030 } elseif ($palign == 'R') {
23031 $ximg = $this->w
- $this->rMargin
- $w;
23035 $this->img_rb_x
= $ximg;
23037 if ($palign == 'L') {
23038 $ximg = $this->lMargin
;
23039 } elseif ($palign == 'C') {
23040 $ximg = ($this->w +
$this->lMargin
- $this->rMargin
- $w) / 2;
23041 } elseif ($palign == 'R') {
23042 $ximg = $this->w
- $this->rMargin
- $w;
23046 $this->img_rb_x
= $ximg +
$w;
23048 // store current graphic vars
23049 $gvars = $this->getGraphicVars();
23050 // store SVG position and scale factors
23051 $svgoffset_x = ($ximg - $ox) * $this->k
;
23052 $svgoffset_y = -($y - $oy) * $this->k
;
23053 if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
23054 $ow = $view_box[2];
23055 $oh = $view_box[3];
23064 $svgscale_x = $w / $ow;
23065 $svgscale_y = $h / $oh;
23066 // scaling and alignment
23067 if ($aspect_ratio_align != 'none') {
23068 // store current scaling values
23069 $svgscale_old_x = $svgscale_x;
23070 $svgscale_old_y = $svgscale_y;
23071 // force uniform scaling
23072 if ($aspect_ratio_ms == 'slice') {
23073 // the entire viewport is covered by the viewBox
23074 if ($svgscale_x > $svgscale_y) {
23075 $svgscale_y = $svgscale_x;
23076 } elseif ($svgscale_x < $svgscale_y) {
23077 $svgscale_x = $svgscale_y;
23080 // the entire viewBox is visible within the viewport
23081 if ($svgscale_x < $svgscale_y) {
23082 $svgscale_y = $svgscale_x;
23083 } elseif ($svgscale_x > $svgscale_y) {
23084 $svgscale_x = $svgscale_y;
23087 // correct X alignment
23088 switch (substr($aspect_ratio_align, 1, 3)) {
23094 $svgoffset_x +
= (($w * $this->k
) - ($ow * $this->k
* $svgscale_x));
23099 $svgoffset_x +
= ((($w * $this->k
) - ($ow * $this->k
* $svgscale_x)) / 2);
23103 // correct Y alignment
23104 switch (substr($aspect_ratio_align, 5)) {
23110 $svgoffset_y -= (($h * $this->k
) - ($oh * $this->k
* $svgscale_y));
23115 $svgoffset_y -= ((($h * $this->k
) - ($oh * $this->k
* $svgscale_y)) / 2);
23120 // store current page break mode
23121 $page_break_mode = $this->AutoPageBreak
;
23122 $page_break_margin = $this->getBreakMargin();
23123 $cell_padding = $this->cell_padding
;
23124 $this->setCellPadding(0);
23125 $this->setAutoPageBreak(false);
23126 // save the current graphic state
23127 $this->_out('q'.$this->epsmarker
);
23128 // set initial clipping mask
23129 $this->Rect($ximg, $y, $w, $h, 'CNZ', array(), array());
23130 // scale and translate
23131 $e = $ox * $this->k
* (1 - $svgscale_x);
23132 $f = ($this->h
- $oy) * $this->k
* (1 - $svgscale_y);
23133 $this->_out(sprintf('%F %F %F %F %F %F cm', $svgscale_x, 0, 0, $svgscale_y, ($e +
$svgoffset_x), ($f +
$svgoffset_y)));
23134 // creates a new XML parser to be used by the other XML functions
23135 $parser = xml_parser_create('UTF-8');
23136 // the following function allows to use parser inside object
23137 xml_set_object($parser, $this);
23138 // disable case-folding for this XML parser
23139 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING
, 0);
23140 // sets the element handler functions for the XML parser
23141 xml_set_element_handler($parser, 'startSVGElementHandler', 'endSVGElementHandler');
23142 // sets the character data handler function for the XML parser
23143 xml_set_character_data_handler($parser, 'segSVGContentHandler');
23144 // start parsing an XML document
23145 if (!xml_parse($parser, $svgdata)) {
23146 $error_message = sprintf('SVG Error: %s at line %d', xml_error_string(xml_get_error_code($parser)), xml_get_current_line_number($parser));
23147 $this->Error($error_message);
23149 // free this XML parser
23150 xml_parser_free($parser);
23152 // >= PHP 7.0.0 "explicitly unset the reference to parser to avoid memory leaks"
23155 // restore previous graphic state
23156 $this->_out($this->epsmarker
.'Q');
23157 // restore graphic vars
23158 $this->setGraphicVars($gvars);
23159 $this->lasth
= $gvars['lasth'];
23160 if (!empty($border)) {
23168 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
23173 $this->Link($ximg, $y, $w, $h, $link, 0);
23175 // set pointer to align the next text/objects
23179 $this->x
= $this->img_rb_x
;
23183 $this->y
= $y +
round($h/2);
23184 $this->x
= $this->img_rb_x
;
23188 $this->y
= $this->img_rb_y
;
23189 $this->x
= $this->img_rb_x
;
23193 $this->setY($this->img_rb_y
);
23197 // restore pointer to starting position
23198 $this->x
= $gvars['x'];
23199 $this->y
= $gvars['y'];
23200 $this->page
= $gvars['page'];
23201 $this->current_column
= $gvars['current_column'];
23202 $this->tMargin
= $gvars['tMargin'];
23203 $this->bMargin
= $gvars['bMargin'];
23204 $this->w
= $gvars['w'];
23205 $this->h
= $gvars['h'];
23206 $this->wPt
= $gvars['wPt'];
23207 $this->hPt
= $gvars['hPt'];
23208 $this->fwPt
= $gvars['fwPt'];
23209 $this->fhPt
= $gvars['fhPt'];
23213 $this->endlinex
= $this->img_rb_x
;
23214 // restore page break
23215 $this->setAutoPageBreak($page_break_mode, $page_break_margin);
23216 $this->cell_padding
= $cell_padding;
23220 * Convert SVG transformation matrix to PDF.
23221 * @param array $tm original SVG transformation matrix
23222 * @return array transformation matrix
23224 * @since 5.0.000 (2010-05-02)
23226 protected function convertSVGtMatrix($tm) {
23231 $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit
, false) * $this->k
;
23232 $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit
, false) * $this->k
;
23234 $y = $this->h
* $this->k
;
23235 $e = ($x * (1 - $a)) - ($y * $c) +
$e;
23236 $f = ($y * (1 - $d)) - ($x * $b) +
$f;
23237 return array($a, $b, $c, $d, $e, $f);
23241 * Apply SVG graphic transformation matrix.
23242 * @param array $tm original SVG transformation matrix
23244 * @since 5.0.000 (2010-05-02)
23246 protected function SVGTransform($tm) {
23247 $this->Transform($this->convertSVGtMatrix($tm));
23251 * Apply the requested SVG styles (*** TO BE COMPLETED ***)
23252 * @param array $svgstyle array of SVG styles to apply
23253 * @param array $prevsvgstyle array of previous SVG style
23254 * @param int $x X origin of the bounding box
23255 * @param int $y Y origin of the bounding box
23256 * @param int $w width of the bounding box
23257 * @param int $h height of the bounding box
23258 * @param string $clip_function clip function
23259 * @param array $clip_params array of parameters for clipping function
23260 * @return string style
23261 * @author Nicola Asuni
23262 * @since 5.0.000 (2010-05-02)
23265 protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23266 if ($this->state
!= 2) {
23270 $minlen = (0.01 / $this->k
); // minimum acceptable length
23271 if (!isset($svgstyle['opacity'])) {
23276 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
23277 $clip_path = $this->svgclippaths
[$regs[1]];
23278 foreach ($clip_path as $cp) {
23279 $this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
23283 if ($svgstyle['opacity'] != 1) {
23284 $this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23287 $fill_color = TCPDF_COLORS
::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors
);
23288 $this->setFillColorArray($fill_color);
23290 $text_color = TCPDF_COLORS
::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors
);
23291 $this->setTextColorArray($text_color);
23293 if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
23294 $top = (isset($regs[1])?
$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit
, false):0);
23295 $right = (isset($regs[2])?
$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit
, false):0);
23296 $bottom = (isset($regs[3])?
$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit
, false):0);
23297 $left = (isset($regs[4])?
$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit
, false):0);
23300 $cw = $w - $left - $right;
23301 $ch = $h - $top - $bottom;
23302 if ($svgstyle['clip-rule'] == 'evenodd') {
23303 $clip_rule = 'CNZ';
23305 $clip_rule = 'CEO';
23307 $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23311 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23313 $gradient = $this->svggradients
[$regs[1]];
23314 if (isset($gradient['xref'])) {
23315 // reference to another gradient definition
23316 $newgradient = $this->svggradients
[$gradient['xref']];
23317 $newgradient['coords'] = $gradient['coords'];
23318 $newgradient['mode'] = $gradient['mode'];
23319 $newgradient['type'] = $gradient['type'];
23320 $newgradient['gradientUnits'] = $gradient['gradientUnits'];
23321 if (isset($gradient['gradientTransform'])) {
23322 $newgradient['gradientTransform'] = $gradient['gradientTransform'];
23324 $gradient = $newgradient;
23326 //save current Graphic State
23327 $this->_outSaveGraphicsState();
23328 //set clipping area
23329 if (!empty($clip_function) AND method_exists($this, $clip_function)) {
23330 $bbox = call_user_func_array(array($this, $clip_function), $clip_params);
23331 if ((!isset($gradient['type']) OR ($gradient['type'] != 3)) AND is_array($bbox) AND (count($bbox) == 4)) {
23332 list($x, $y, $w, $h) = $bbox;
23335 if ($gradient['mode'] == 'measure') {
23336 if (!isset($gradient['coords'][4])) {
23337 $gradient['coords'][4] = 0.5;
23339 if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
23340 $gtm = $gradient['gradientTransform'];
23341 // apply transformation matrix
23342 $xa = ($gtm[0] * $gradient['coords'][0]) +
($gtm[2] * $gradient['coords'][1]) +
$gtm[4];
23343 $ya = ($gtm[1] * $gradient['coords'][0]) +
($gtm[3] * $gradient['coords'][1]) +
$gtm[5];
23344 $xb = ($gtm[0] * $gradient['coords'][2]) +
($gtm[2] * $gradient['coords'][3]) +
$gtm[4];
23345 $yb = ($gtm[1] * $gradient['coords'][2]) +
($gtm[3] * $gradient['coords'][3]) +
$gtm[5];
23346 $r = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) +
pow(($gtm[1] * $gradient['coords'][4]), 2));
23347 $gradient['coords'][0] = $xa;
23348 $gradient['coords'][1] = $ya;
23349 $gradient['coords'][2] = $xb;
23350 $gradient['coords'][3] = $yb;
23351 $gradient['coords'][4] = $r;
23353 // convert SVG coordinates to user units
23354 $gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit
, false);
23355 $gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit
, false);
23356 $gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit
, false);
23357 $gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit
, false);
23358 $gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit
, false);
23359 if ($w <= $minlen) {
23362 if ($h <= $minlen) {
23366 if ($gradient['gradientUnits'] == 'objectBoundingBox') {
23367 // convert to SVG coordinate system
23368 $gradient['coords'][0] +
= $x;
23369 $gradient['coords'][1] +
= $y;
23370 $gradient['coords'][2] +
= $x;
23371 $gradient['coords'][3] +
= $y;
23373 // calculate percentages
23374 $gradient['coords'][0] = (($gradient['coords'][0] - $x) / $w);
23375 $gradient['coords'][1] = (($gradient['coords'][1] - $y) / $h);
23376 $gradient['coords'][2] = (($gradient['coords'][2] - $x) / $w);
23377 $gradient['coords'][3] = (($gradient['coords'][3] - $y) / $h);
23378 $gradient['coords'][4] /= $w;
23379 } elseif ($gradient['mode'] == 'percentage') {
23380 foreach($gradient['coords'] as $key => $val) {
23381 $gradient['coords'][$key] = (intval($val) / 100);
23383 $gradient['coords'][$key] = 0;
23384 } elseif ($val > 1) {
23385 $gradient['coords'][$key] = 1;
23389 if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
23390 // single color (no shading)
23391 $gradient['coords'][0] = 1;
23392 $gradient['coords'][1] = 0;
23393 $gradient['coords'][2] = 0.999;
23394 $gradient['coords'][3] = 0;
23396 // swap Y coordinates
23397 $tmp = $gradient['coords'][1];
23398 $gradient['coords'][1] = $gradient['coords'][3];
23399 $gradient['coords'][3] = $tmp;
23400 // set transformation map for gradient
23401 $cy = ($this->h
- $y);
23402 if ($gradient['type'] == 3) {
23403 // circular gradient
23404 $cy -= ($gradient['coords'][1] * ($w +
$h));
23405 $h = $w = max($w, $h);
23409 $this->_out(sprintf('%F 0 0 %F %F %F cm', ($w * $this->k
), ($h * $this->k
), ($x * $this->k
), ($cy * $this->k
)));
23410 if (count($gradient['stops']) > 1) {
23411 $this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
23413 } elseif ($svgstyle['fill'] != 'none') {
23414 $fill_color = TCPDF_COLORS
::convertHTMLColorToDec($svgstyle['fill'], $this->spot_colors
);
23415 if ($svgstyle['fill-opacity'] != 1) {
23416 $this->setAlpha($this->alpha
['CA'], 'Normal', $svgstyle['fill-opacity'], false);
23418 $this->setFillColorArray($fill_color);
23419 if ($svgstyle['fill-rule'] == 'evenodd') {
23426 if ($svgstyle['stroke'] != 'none') {
23427 if ($svgstyle['stroke-opacity'] != 1) {
23428 $this->setAlpha($svgstyle['stroke-opacity'], 'Normal', $this->alpha
['ca'], false);
23429 } elseif (preg_match('/rgba\(\d+%?,\s*\d+%?,\s*\d+%?,\s*(\d+(?:\.\d+)?)\)/i', $svgstyle['stroke'], $rgba_matches)) {
23430 $this->setAlpha($rgba_matches[1], 'Normal', $this->alpha
['ca'], false);
23432 $stroke_style = array(
23433 'color' => TCPDF_COLORS
::convertHTMLColorToDec($svgstyle['stroke'], $this->spot_colors
),
23434 'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit
, false),
23435 'cap' => $svgstyle['stroke-linecap'],
23436 'join' => $svgstyle['stroke-linejoin']
23438 if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
23439 $stroke_style['dash'] = $svgstyle['stroke-dasharray'];
23441 $this->setLineStyle($stroke_style);
23446 if (!empty($svgstyle['font'])) {
23447 if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23448 $font_family = $this->getFontFamilyName($regs[1]);
23450 $font_family = $svgstyle['font-family'];
23452 if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23453 $font_size = trim($regs[1]);
23455 $font_size = $svgstyle['font-size'];
23457 if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23458 $font_style = trim($regs[1]);
23460 $font_style = $svgstyle['font-style'];
23462 if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23463 $font_weight = trim($regs[1]);
23465 $font_weight = $svgstyle['font-weight'];
23467 if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23468 $font_stretch = trim($regs[1]);
23470 $font_stretch = $svgstyle['font-stretch'];
23472 if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
23473 $font_spacing = trim($regs[1]);
23475 $font_spacing = $svgstyle['letter-spacing'];
23478 $font_family = $this->getFontFamilyName($svgstyle['font-family']);
23479 $font_size = $svgstyle['font-size'];
23480 $font_style = $svgstyle['font-style'];
23481 $font_weight = $svgstyle['font-weight'];
23482 $font_stretch = $svgstyle['font-stretch'];
23483 $font_spacing = $svgstyle['letter-spacing'];
23485 $font_size = $this->getHTMLFontUnits($font_size, $this->svgstyles
[0]['font-size'], $prevsvgstyle['font-size'], $this->svgunit
);
23486 $font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
23487 $font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
23488 switch ($font_style) {
23503 switch ($font_weight) {
23506 $font_style .= 'B';
23510 if ((substr($font_family, -1) == 'I') AND (substr($font_family, -2, 1) == 'B')) {
23511 $font_family = substr($font_family, 0, -2).'I';
23512 } elseif (substr($font_family, -1) == 'B') {
23513 $font_family = substr($font_family, 0, -1);
23518 switch ($svgstyle['text-decoration']) {
23519 case 'underline': {
23520 $font_style .= 'U';
23524 $font_style .= 'O';
23527 case 'line-through': {
23528 $font_style .= 'D';
23536 $this->setFont($font_family, $font_style, $font_size);
23537 $this->setFontStretching($font_stretch);
23538 $this->setFontSpacing($font_spacing);
23543 * Draws an SVG path
23544 * @param string $d attribute d of the path SVG element
23545 * @param string $style Style of rendering. Possible values are:
23547 * <li>D or empty string: Draw (default).</li>
23548 * <li>F: Fill.</li>
23549 * <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23550 * <li>DF or FD: Draw and fill.</li>
23551 * <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
23552 * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
23553 * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
23555 * @return array of container box measures (x, y, w, h)
23556 * @author Nicola Asuni
23557 * @since 5.0.000 (2010-05-02)
23560 protected function SVGPath($d, $style='') {
23561 if ($this->state
!= 2) {
23564 // set fill/stroke style
23565 $op = TCPDF_STATIC
::getPathPaintOperator($style, '');
23570 $d = preg_replace('/([0-9ACHLMQSTVZ])([\-\+])/si', '\\1 \\2', $d);
23571 $d = preg_replace('/(\.[0-9]+)(\.)/s', '\\1 \\2', $d);
23572 preg_match_all('/([ACHLMQSTVZ])[\s]*([^ACHLMQSTVZ\"]*)/si', $d, $paths, PREG_SET_ORDER
);
23579 $xmin = 2147483647;
23581 $ymin = 2147483647;
23586 $minlen = (0.01 / $this->k
); // minimum acceptable length (3 point)
23587 $firstcmd = true; // used to print first point
23588 // draw curve pieces
23589 foreach ($paths as $key => $val) {
23591 $cmd = trim($val[1]);
23592 if (strtolower($cmd) == $cmd) {
23593 // use relative coordinated instead of absolute
23603 if (isset($val[2])) {
23604 // get curve parameters
23605 $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
23607 foreach ($rawparams as $ck => $cp) {
23608 $params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit
, false);
23609 if (abs($params[$ck]) < $minlen) {
23610 // approximate little values to zero
23615 // store current origin point
23618 switch (strtoupper($cmd)) {
23619 case 'M': { // moveto
23620 foreach ($params as $ck => $cp) {
23621 if (($ck %
2) == 0) {
23622 $x = $cp +
$xoffset;
23624 $y = $cp +
$yoffset;
23625 if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23627 $this->_outPoint($x, $y);
23632 $this->_outLine($x, $y);
23637 $xmin = min($xmin, $x);
23638 $ymin = min($ymin, $y);
23639 $xmax = max($xmax, $x);
23640 $ymax = max($ymax, $y);
23649 case 'L': { // lineto
23650 foreach ($params as $ck => $cp) {
23651 if (($ck %
2) == 0) {
23652 $x = $cp +
$xoffset;
23654 $y = $cp +
$yoffset;
23655 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23656 $this->_outLine($x, $y);
23660 $xmin = min($xmin, $x);
23661 $ymin = min($ymin, $y);
23662 $xmax = max($xmax, $x);
23663 $ymax = max($ymax, $y);
23672 case 'H': { // horizontal lineto
23673 foreach ($params as $ck => $cp) {
23674 $x = $cp +
$xoffset;
23675 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23676 $this->_outLine($x, $y);
23680 $xmin = min($xmin, $x);
23681 $xmax = max($xmax, $x);
23688 case 'V': { // vertical lineto
23689 foreach ($params as $ck => $cp) {
23690 $y = $cp +
$yoffset;
23691 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23692 $this->_outLine($x, $y);
23696 $ymin = min($ymin, $y);
23697 $ymax = max($ymax, $y);
23704 case 'C': { // curveto
23705 foreach ($params as $ck => $cp) {
23706 $params[$ck] = $cp;
23707 if ((($ck +
1) %
6) == 0) {
23708 $x1 = $params[($ck - 5)] +
$xoffset;
23709 $y1 = $params[($ck - 4)] +
$yoffset;
23710 $x2 = $params[($ck - 3)] +
$xoffset;
23711 $y2 = $params[($ck - 2)] +
$yoffset;
23712 $x = $params[($ck - 1)] +
$xoffset;
23713 $y = $params[($ck)] +
$yoffset;
23714 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23715 $xmin = min($xmin, $x, $x1, $x2);
23716 $ymin = min($ymin, $y, $y1, $y2);
23717 $xmax = max($xmax, $x, $x1, $x2);
23718 $ymax = max($ymax, $y, $y1, $y2);
23727 case 'S': { // shorthand/smooth curveto
23728 foreach ($params as $ck => $cp) {
23729 $params[$ck] = $cp;
23730 if ((($ck +
1) %
4) == 0) {
23731 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
23732 $x1 = (2 * $x) - $x2;
23733 $y1 = (2 * $y) - $y2;
23738 $x2 = $params[($ck - 3)] +
$xoffset;
23739 $y2 = $params[($ck - 2)] +
$yoffset;
23740 $x = $params[($ck - 1)] +
$xoffset;
23741 $y = $params[($ck)] +
$yoffset;
23742 $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
23743 $xmin = min($xmin, $x, $x1, $x2);
23744 $ymin = min($ymin, $y, $y1, $y2);
23745 $xmax = max($xmax, $x, $x1, $x2);
23746 $ymax = max($ymax, $y, $y1, $y2);
23755 case 'Q': { // quadratic Bezier curveto
23756 foreach ($params as $ck => $cp) {
23757 $params[$ck] = $cp;
23758 if ((($ck +
1) %
4) == 0) {
23759 // convert quadratic points to cubic points
23760 $x1 = $params[($ck - 3)] +
$xoffset;
23761 $y1 = $params[($ck - 2)] +
$yoffset;
23762 $xa = ($x +
(2 * $x1)) / 3;
23763 $ya = ($y +
(2 * $y1)) / 3;
23764 $x = $params[($ck - 1)] +
$xoffset;
23765 $y = $params[($ck)] +
$yoffset;
23766 $xb = ($x +
(2 * $x1)) / 3;
23767 $yb = ($y +
(2 * $y1)) / 3;
23768 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23769 $xmin = min($xmin, $x, $xa, $xb);
23770 $ymin = min($ymin, $y, $ya, $yb);
23771 $xmax = max($xmax, $x, $xa, $xb);
23772 $ymax = max($ymax, $y, $ya, $yb);
23781 case 'T': { // shorthand/smooth quadratic Bezier curveto
23782 foreach ($params as $ck => $cp) {
23783 $params[$ck] = $cp;
23784 if (($ck %
2) != 0) {
23785 if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
23786 $x1 = (2 * $x) - $x1;
23787 $y1 = (2 * $y) - $y1;
23792 // convert quadratic points to cubic points
23793 $xa = ($x +
(2 * $x1)) / 3;
23794 $ya = ($y +
(2 * $y1)) / 3;
23795 $x = $params[($ck - 1)] +
$xoffset;
23796 $y = $params[($ck)] +
$yoffset;
23797 $xb = ($x +
(2 * $x1)) / 3;
23798 $yb = ($y +
(2 * $y1)) / 3;
23799 $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
23800 $xmin = min($xmin, $x, $xa, $xb);
23801 $ymin = min($ymin, $y, $ya, $yb);
23802 $xmax = max($xmax, $x, $xa, $xb);
23803 $ymax = max($ymax, $y, $ya, $yb);
23812 case 'A': { // elliptical arc
23813 foreach ($params as $ck => $cp) {
23814 $params[$ck] = $cp;
23815 if ((($ck +
1) %
7) == 0) {
23818 $rx = max(abs($params[($ck - 6)]), .000000001);
23819 $ry = max(abs($params[($ck - 5)]), .000000001);
23820 $ang = -$rawparams[($ck - 4)];
23821 $angle = deg2rad($ang);
23822 $fa = $rawparams[($ck - 3)]; // large-arc-flag
23823 $fs = $rawparams[($ck - 2)]; // sweep-flag
23824 $x = $params[($ck - 1)] +
$xoffset;
23825 $y = $params[$ck] +
$yoffset;
23826 if ((abs($x0 - $x) < $minlen) AND (abs($y0 - $y) < $minlen)) {
23827 // endpoints are almost identical
23828 $xmin = min($xmin, $x);
23829 $ymin = min($ymin, $y);
23830 $xmax = max($xmax, $x);
23831 $ymax = max($ymax, $y);
23833 $cos_ang = cos($angle);
23834 $sin_ang = sin($angle);
23835 $a = (($x0 - $x) / 2);
23836 $b = (($y0 - $y) / 2);
23837 $xa = ($a * $cos_ang) - ($b * $sin_ang);
23838 $ya = ($a * $sin_ang) +
($b * $cos_ang);
23843 $delta = ($xa2 / $rx2) +
($ya2 / $ry2);
23845 $rx *= sqrt($delta);
23846 $ry *= sqrt($delta);
23850 $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23851 if ($numerator < 0) {
23854 $root = sqrt($numerator / (($rx2 * $ya2) +
($ry2 * $xa2)));
23859 $cax = $root * (($rx * $ya) / $ry);
23860 $cay = -$root * (($ry * $xa) / $rx);
23861 // coordinates of ellipse center
23862 $cx = ($cax * $cos_ang) - ($cay * $sin_ang) +
(($x0 +
$x) / 2);
23863 $cy = ($cax * $sin_ang) +
($cay * $cos_ang) +
(($y0 +
$y) / 2);
23865 $angs = TCPDF_STATIC
::getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
23866 $dang = TCPDF_STATIC
::getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
23867 if (($fs == 0) AND ($dang > 0)) {
23868 $dang -= (2 * M_PI
);
23869 } elseif (($fs == 1) AND ($dang < 0)) {
23870 $dang +
= (2 * M_PI
);
23872 $angf = $angs - $dang;
23873 if ((($fs == 0) AND ($angs > $angf)) OR (($fs == 1) AND ($angs < $angf))) {
23879 $angs = round(rad2deg($angs), 6);
23880 $angf = round(rad2deg($angf), 6);
23881 // covent angles to positive values
23882 if (($angs < 0) AND ($angf < 0)) {
23887 if (($key == 0) AND (isset($paths[($key +
1)][1])) AND (trim($paths[($key +
1)][1]) == 'z')) {
23890 list($axmin, $aymin, $axmax, $aymax) = $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2, false, ($fs == 0), true);
23891 $xmin = min($xmin, $x, $axmin);
23892 $ymin = min($ymin, $y, $aymin);
23893 $xmax = max($xmax, $x, $axmax);
23894 $ymax = max($ymax, $y, $aymax);
23906 $x = $x0 = $xinitial;
23907 $y = $y0 = $yinitial;
23914 return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
23918 * Return the tag name without the namespace
23919 * @param string $name Tag name
23922 protected function removeTagNamespace($name) {
23923 if(strpos($name, ':') !== false) {
23924 $parts = explode(':', $name);
23925 return $parts[(sizeof($parts) - 1)];
23931 * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
23932 * @param resource|string $parser The first parameter, parser, is a reference to the XML parser calling the handler.
23933 * @param string $name 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.
23934 * @param array $attribs 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.
23935 * @param array $ctm tranformation matrix for clipping mode (starting transformation matrix).
23936 * @author Nicola Asuni
23937 * @since 5.0.000 (2010-05-02)
23940 protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
23941 $name = $this->removeTagNamespace($name);
23942 // check if we are in clip mode
23943 if ($this->svgclipmode
) {
23944 $this->svgclippaths
[$this->svgclipid
][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm
[$this->svgclipid
]);
23947 if ($this->svgdefsmode
AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
23948 if (isset($attribs['id'])) {
23949 $attribs['child_elements'] = array();
23950 $this->svgdefs
[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23953 if (end($this->svgdefs
) !== FALSE) {
23954 $last_svgdefs_id = key($this->svgdefs
);
23955 if (isset($this->svgdefs
[$last_svgdefs_id]['attribs']['child_elements'])) {
23956 $attribs['id'] = 'DF_'.(count($this->svgdefs
[$last_svgdefs_id]['attribs']['child_elements']) +
1);
23957 $this->svgdefs
[$last_svgdefs_id]['attribs']['child_elements'][$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
23964 if ($parser == 'clip-path') {
23965 // set clipping mode
23968 // get styling properties
23969 $prev_svgstyle = $this->svgstyles
[max(0,(count($this->svgstyles
) - 1))]; // previous style
23970 $svgstyle = $this->svgstyles
[0]; // set default style
23971 if ($clipping AND !isset($attribs['fill']) AND (!isset($attribs['style']) OR (!preg_match('/[;\"\s]{1}fill[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval)))) {
23972 // default fill attribute for clipping
23973 $attribs['fill'] = 'none';
23975 if (isset($attribs['style']) AND !TCPDF_STATIC
::empty_string($attribs['style']) AND ($attribs['style'][0] != ';')) {
23976 // fix style for regular expression
23977 $attribs['style'] = ';'.$attribs['style'];
23979 foreach ($prev_svgstyle as $key => $val) {
23980 if (in_array($key, TCPDF_IMAGES
::$svginheritprop)) {
23981 // inherit previous value
23982 $svgstyle[$key] = $val;
23984 if (isset($attribs[$key]) AND !TCPDF_STATIC
::empty_string($attribs[$key])) {
23985 // specific attribute settings
23986 if ($attribs[$key] == 'inherit') {
23987 $svgstyle[$key] = $val;
23989 $svgstyle[$key] = $attribs[$key];
23991 } elseif (isset($attribs['style']) AND !TCPDF_STATIC
::empty_string($attribs['style'])) {
23992 // CSS style syntax
23993 $attrval = array();
23994 if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
23995 if ($attrval[1] == 'inherit') {
23996 $svgstyle[$key] = $val;
23998 $svgstyle[$key] = $attrval[1];
24003 // transformation matrix
24004 if (!empty($ctm)) {
24007 $tm = array(1,0,0,1,0,0);
24009 if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
24010 $tm = TCPDF_STATIC
::getTransformationMatrixProduct($tm, TCPDF_STATIC
::getSVGTransformMatrix($attribs['transform']));
24012 $svgstyle['transfmatrix'] = $tm;
24013 $invisible = false;
24014 if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
24015 // the current graphics element is invisible (nothing is painted)
24021 $this->svgdefsmode
= true;
24029 $this->svgclipmode
= true;
24030 if (!isset($attribs['id'])) {
24031 $attribs['id'] = 'CP_'.(count($this->svgcliptm
) +
1);
24033 $this->svgclipid
= $attribs['id'];
24034 $this->svgclippaths
[$this->svgclipid
] = array();
24035 $this->svgcliptm
[$this->svgclipid
] = $tm;
24039 // start of SVG object
24040 if(++
$this->svg_tag_depth
<= 1) {
24044 array_push($this->svgstyles
, $svgstyle);
24045 $this->StartTransform();
24046 $svgX = (isset($attribs['x'])?
$attribs['x']:0);
24047 $svgY = (isset($attribs['y'])?
$attribs['y']:0);
24048 $svgW = (isset($attribs['width'])?
$attribs['width']:0);
24049 $svgH = (isset($attribs['height'])?
$attribs['height']:0);
24050 // set x, y position using transform matrix
24051 $tm = TCPDF_STATIC
::getTransformationMatrixProduct($tm, array( 1, 0, 0, 1, $svgX, $svgY));
24052 $this->SVGTransform($tm);
24053 // set clipping for width and height
24056 $w = (isset($attribs['width'])?
$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit
, false):$this->w
);
24057 $h = (isset($attribs['height'])?
$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit
, false):$this->h
);
24058 // draw clipping rect
24059 $this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
24060 // parse viewbox, calculate extra transformation matrix
24061 if (isset($attribs['viewBox'])) {
24063 preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
24065 if (sizeof($tmp) == 4) {
24070 // get aspect ratio
24075 if (isset($attribs['preserveAspectRatio'])) {
24076 if($attribs['preserveAspectRatio'] == 'none') {
24079 preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
24081 if ((sizeof($tmp) == 2) AND (strlen($tmp[0]) == 8) AND (in_array($tmp[1], array('meet', 'slice', 'none')))) {
24082 $aspectX = substr($tmp[0], 0, 4);
24083 $aspectY = substr($tmp[0], 4, 4);
24088 $wr = ($svgW / $vw);
24089 $hr = ($svgH / $vh);
24091 if ((($fit == 'meet') AND ($hr < $wr)) OR (($fit == 'slice') AND ($hr > $wr))) {
24092 if ($aspectX == 'xMax') {
24093 $ax = (($vw * ($wr / $hr)) - $vw);
24095 if ($aspectX == 'xMid') {
24096 $ax = ((($vw * ($wr / $hr)) - $vw) / 2);
24099 } elseif ((($fit == 'meet') AND ($hr > $wr)) OR (($fit == 'slice') AND ($hr < $wr))) {
24100 if ($aspectY == 'YMax') {
24101 $ay = (($vh * ($hr / $wr)) - $vh);
24103 if ($aspectY == 'YMid') {
24104 $ay = ((($vh * ($hr / $wr)) - $vh) / 2);
24108 $newtm = array($wr, 0, 0, $hr, (($wr * ($ax - $vx)) - $svgX), (($hr * ($ay - $vy)) - $svgY));
24109 $tm = TCPDF_STATIC
::getTransformationMatrixProduct($tm, $newtm);
24110 $this->SVGTransform($tm);
24113 $this->setSVGStyles($svgstyle, $prev_svgstyle);
24117 // group together related graphics elements
24118 array_push($this->svgstyles
, $svgstyle);
24119 $this->StartTransform();
24120 $x = (isset($attribs['x'])?
$attribs['x']:0);
24121 $y = (isset($attribs['y'])?
$attribs['y']:0);
24122 $w = 1;//(isset($attribs['width'])?$attribs['width']:1);
24123 $h = 1;//(isset($attribs['height'])?$attribs['height']:1);
24124 $tm = TCPDF_STATIC
::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24125 $this->SVGTransform($tm);
24126 $this->setSVGStyles($svgstyle, $prev_svgstyle);
24129 case 'linearGradient': {
24130 if ($this->pdfa_mode
&& $this->pdfa_version
< 2) {
24133 if (!isset($attribs['id'])) {
24134 $attribs['id'] = 'GR_'.(count($this->svggradients
) +
1);
24136 $this->svggradientid
= $attribs['id'];
24137 $this->svggradients
[$this->svggradientid
] = array();
24138 $this->svggradients
[$this->svggradientid
]['type'] = 2;
24139 $this->svggradients
[$this->svggradientid
]['stops'] = array();
24140 if (isset($attribs['gradientUnits'])) {
24141 $this->svggradients
[$this->svggradientid
]['gradientUnits'] = $attribs['gradientUnits'];
24143 $this->svggradients
[$this->svggradientid
]['gradientUnits'] = 'objectBoundingBox';
24145 //$attribs['spreadMethod']
24146 if (((!isset($attribs['x1'])) AND (!isset($attribs['y1'])) AND (!isset($attribs['x2'])) AND (!isset($attribs['y2'])))
24147 OR ((isset($attribs['x1']) AND (substr($attribs['x1'], -1) == '%'))
24148 OR (isset($attribs['y1']) AND (substr($attribs['y1'], -1) == '%'))
24149 OR (isset($attribs['x2']) AND (substr($attribs['x2'], -1) == '%'))
24150 OR (isset($attribs['y2']) AND (substr($attribs['y2'], -1) == '%')))) {
24151 $this->svggradients
[$this->svggradientid
]['mode'] = 'percentage';
24153 $this->svggradients
[$this->svggradientid
]['mode'] = 'measure';
24155 $x1 = (isset($attribs['x1'])?
$attribs['x1']:'0');
24156 $y1 = (isset($attribs['y1'])?
$attribs['y1']:'0');
24157 $x2 = (isset($attribs['x2'])?
$attribs['x2']:'100');
24158 $y2 = (isset($attribs['y2'])?
$attribs['y2']:'0');
24159 if (isset($attribs['gradientTransform'])) {
24160 $this->svggradients
[$this->svggradientid
]['gradientTransform'] = TCPDF_STATIC
::getSVGTransformMatrix($attribs['gradientTransform']);
24162 $this->svggradients
[$this->svggradientid
]['coords'] = array($x1, $y1, $x2, $y2);
24163 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24164 // gradient is defined on another place
24165 $this->svggradients
[$this->svggradientid
]['xref'] = substr($attribs['xlink:href'], 1);
24169 case 'radialGradient': {
24170 if ($this->pdfa_mode
&& $this->pdfa_version
< 2) {
24173 if (!isset($attribs['id'])) {
24174 $attribs['id'] = 'GR_'.(count($this->svggradients
) +
1);
24176 $this->svggradientid
= $attribs['id'];
24177 $this->svggradients
[$this->svggradientid
] = array();
24178 $this->svggradients
[$this->svggradientid
]['type'] = 3;
24179 $this->svggradients
[$this->svggradientid
]['stops'] = array();
24180 if (isset($attribs['gradientUnits'])) {
24181 $this->svggradients
[$this->svggradientid
]['gradientUnits'] = $attribs['gradientUnits'];
24183 $this->svggradients
[$this->svggradientid
]['gradientUnits'] = 'objectBoundingBox';
24185 //$attribs['spreadMethod']
24186 if (((!isset($attribs['cx'])) AND (!isset($attribs['cy'])))
24187 OR ((isset($attribs['cx']) AND (substr($attribs['cx'], -1) == '%'))
24188 OR (isset($attribs['cy']) AND (substr($attribs['cy'], -1) == '%')))) {
24189 $this->svggradients
[$this->svggradientid
]['mode'] = 'percentage';
24190 } elseif (isset($attribs['r']) AND is_numeric($attribs['r']) AND ($attribs['r']) <= 1) {
24191 $this->svggradients
[$this->svggradientid
]['mode'] = 'ratio';
24193 $this->svggradients
[$this->svggradientid
]['mode'] = 'measure';
24195 $cx = (isset($attribs['cx']) ?
$attribs['cx'] : 0.5);
24196 $cy = (isset($attribs['cy']) ?
$attribs['cy'] : 0.5);
24197 $fx = (isset($attribs['fx']) ?
$attribs['fx'] : $cx);
24198 $fy = (isset($attribs['fy']) ?
$attribs['fy'] : $cy);
24199 $r = (isset($attribs['r']) ?
$attribs['r'] : 0.5);
24200 if (isset($attribs['gradientTransform'])) {
24201 $this->svggradients
[$this->svggradientid
]['gradientTransform'] = TCPDF_STATIC
::getSVGTransformMatrix($attribs['gradientTransform']);
24203 $this->svggradients
[$this->svggradientid
]['coords'] = array($cx, $cy, $fx, $fy, $r);
24204 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24205 // gradient is defined on another place
24206 $this->svggradients
[$this->svggradientid
]['xref'] = substr($attribs['xlink:href'], 1);
24212 if (substr($attribs['offset'], -1) == '%') {
24213 $offset = floatval(substr($attribs['offset'], 0, -1)) / 100;
24215 $offset = floatval($attribs['offset']);
24220 $stop_color = isset($svgstyle['stop-color'])?TCPDF_COLORS
::convertHTMLColorToDec($svgstyle['stop-color'], $this->spot_colors
):'black';
24221 $opacity = isset($svgstyle['stop-opacity'])?
$svgstyle['stop-opacity']:1;
24222 $this->svggradients
[$this->svggradientid
]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
24230 if (isset($attribs['d'])) {
24231 $d = trim($attribs['d']);
24233 $x = (isset($attribs['x'])?
$attribs['x']:0);
24234 $y = (isset($attribs['y'])?
$attribs['y']:0);
24235 $w = (isset($attribs['width'])?
$attribs['width']:1);
24236 $h = (isset($attribs['height'])?
$attribs['height']:1);
24237 $tm = TCPDF_STATIC
::getTransformationMatrixProduct($tm, array($w, 0, 0, $h, $x, $y));
24239 $this->SVGTransform($tm);
24240 $this->SVGPath($d, 'CNZ');
24242 $this->StartTransform();
24243 $this->SVGTransform($tm);
24244 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'SVGPath', array($d, 'CNZ'));
24245 if (!empty($obstyle)) {
24246 $this->SVGPath($d, $obstyle);
24248 $this->StopTransform();
24259 $x = (isset($attribs['x'])?
$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit
, false):0);
24260 $y = (isset($attribs['y'])?
$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit
, false):0);
24261 $w = (isset($attribs['width'])?
$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit
, false):0);
24262 $h = (isset($attribs['height'])?
$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit
, false):0);
24263 $rx = (isset($attribs['rx'])?
$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit
, false):0);
24264 $ry = (isset($attribs['ry'])?
$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit
, false):$rx);
24266 $this->SVGTransform($tm);
24267 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24269 $this->StartTransform();
24270 $this->SVGTransform($tm);
24271 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
24272 if (!empty($obstyle)) {
24273 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
24275 $this->StopTransform();
24283 $r = (isset($attribs['r']) ?
$this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit
, false) : 0);
24284 $cx = (isset($attribs['cx']) ?
$this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit
, false) : (isset($attribs['x']) ?
$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit
, false) : 0));
24285 $cy = (isset($attribs['cy']) ?
$this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit
, false) : (isset($attribs['y']) ?
$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit
, false) : 0));
24291 $this->SVGTransform($tm);
24292 $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24294 $this->StartTransform();
24295 $this->SVGTransform($tm);
24296 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
24297 if (!empty($obstyle)) {
24298 $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
24300 $this->StopTransform();
24308 $rx = (isset($attribs['rx']) ?
$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit
, false) : 0);
24309 $ry = (isset($attribs['ry']) ?
$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit
, false) : 0);
24310 $cx = (isset($attribs['cx']) ?
$this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit
, false) : (isset($attribs['x']) ?
$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit
, false) : 0));
24311 $cy = (isset($attribs['cy']) ?
$this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit
, false) : (isset($attribs['y']) ?
$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit
, false) : 0));
24317 $this->SVGTransform($tm);
24318 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24320 $this->StartTransform();
24321 $this->SVGTransform($tm);
24322 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
24323 if (!empty($obstyle)) {
24324 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
24326 $this->StopTransform();
24334 $x1 = (isset($attribs['x1'])?
$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit
, false):0);
24335 $y1 = (isset($attribs['y1'])?
$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit
, false):0);
24336 $x2 = (isset($attribs['x2'])?
$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit
, false):0);
24337 $y2 = (isset($attribs['y2'])?
$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit
, false):0);
24340 $w = abs($x2 - $x1);
24341 $h = abs($y2 - $y1);
24343 $this->StartTransform();
24344 $this->SVGTransform($tm);
24345 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
24346 $this->Line($x1, $y1, $x2, $y2);
24347 $this->StopTransform();
24356 $points = (isset($attribs['points'])?
$attribs['points']:'0 0');
24357 $points = trim($points);
24358 // note that point may use a complex syntax not covered here
24359 $points = preg_split('/[\,\s]+/si', $points);
24360 if (count($points) < 4) {
24364 $xmin = 2147483647;
24366 $ymin = 2147483647;
24368 foreach ($points as $key => $val) {
24369 $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit
, false);
24370 if (($key %
2) == 0) {
24372 $xmin = min($xmin, $p[$key]);
24373 $xmax = max($xmax, $p[$key]);
24376 $ymin = min($ymin, $p[$key]);
24377 $ymax = max($ymax, $p[$key]);
24382 $w = ($xmax - $xmin);
24383 $h = ($ymax - $ymin);
24384 if ($name == 'polyline') {
24385 $this->StartTransform();
24386 $this->SVGTransform($tm);
24387 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
24388 if (!empty($obstyle)) {
24389 $this->PolyLine($p, $obstyle, array(), array());
24391 $this->StopTransform();
24392 } else { // polygon
24394 $this->SVGTransform($tm);
24395 $this->Polygon($p, 'CNZ', array(), array(), true);
24397 $this->StartTransform();
24398 $this->SVGTransform($tm);
24399 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
24400 if (!empty($obstyle)) {
24401 $this->Polygon($p, $obstyle, array(), array(), true);
24403 $this->StopTransform();
24413 if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24416 $x = (isset($attribs['x'])?
$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit
, false):0);
24417 $y = (isset($attribs['y'])?
$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit
, false):0);
24418 $w = (isset($attribs['width'])?
$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit
, false):0);
24419 $h = (isset($attribs['height'])?
$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit
, false):0);
24420 $img = $attribs['xlink:href'];
24422 $this->StartTransform();
24423 $this->SVGTransform($tm);
24424 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
24425 if (preg_match('/^data:image\/[^;]+;base64,/', $img, $m) > 0) {
24426 // embedded image encoded as base64
24427 $img = '@'.base64_decode(substr($img, strlen($m[0])));
24430 if (!TCPDF_STATIC
::empty_string($this->svgdir
) AND (($img[0] == '.') OR (basename($img) == $img))) {
24431 // replace relative path with full server path
24432 $img = $this->svgdir
.'/'.$img;
24434 if (($img[0] == '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
24435 $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
24436 if (($findroot === false) OR ($findroot > 1)) {
24437 if (substr($_SERVER['DOCUMENT_ROOT'], -1) == '/') {
24438 $img = substr($_SERVER['DOCUMENT_ROOT'], 0, -1).$img;
24440 $img = $_SERVER['DOCUMENT_ROOT'].$img;
24444 $img = urldecode($img);
24445 $testscrtype = @parse_url
($img);
24446 if (empty($testscrtype['query'])) {
24447 // convert URL to server path
24448 $img = str_replace(K_PATH_URL
, K_PATH_MAIN
, $img);
24449 } elseif (preg_match('|^https?://|', $img) !== 1) {
24450 // convert server path to URL
24451 $img = str_replace(K_PATH_MAIN
, K_PATH_URL
, $img);
24455 $imgtype = TCPDF_IMAGES
::getImageFileType($img);
24456 if (($imgtype == 'eps') OR ($imgtype == 'ai')) {
24457 $this->ImageEps($img, $x, $y, $w, $h);
24458 } elseif ($imgtype == 'svg') {
24460 $svggradients = $this->svggradients
;
24461 $svggradientid = $this->svggradientid
;
24462 $svgdefsmode = $this->svgdefsmode
;
24463 $svgdefs = $this->svgdefs
;
24464 $svgclipmode = $this->svgclipmode
;
24465 $svgclippaths = $this->svgclippaths
;
24466 $svgcliptm = $this->svgcliptm
;
24467 $svgclipid = $this->svgclipid
;
24468 $svgtext = $this->svgtext
;
24469 $svgtextmode = $this->svgtextmode
;
24470 $this->ImageSVG($img, $x, $y, $w, $h);
24471 // restore SVG vars
24472 $this->svggradients
= $svggradients;
24473 $this->svggradientid
= $svggradientid;
24474 $this->svgdefsmode
= $svgdefsmode;
24475 $this->svgdefs
= $svgdefs;
24476 $this->svgclipmode
= $svgclipmode;
24477 $this->svgclippaths
= $svgclippaths;
24478 $this->svgcliptm
= $svgcliptm;
24479 $this->svgclipid
= $svgclipid;
24480 $this->svgtext
= $svgtext;
24481 $this->svgtextmode
= $svgtextmode;
24483 $this->Image($img, $x, $y, $w, $h);
24485 $this->StopTransform();
24492 if (isset($this->svgtextmode
['text-anchor']) AND !empty($this->svgtext
)) {
24493 // @TODO: unsupported feature
24495 // only basic support - advanced features must be implemented
24496 $this->svgtextmode
['invisible'] = $invisible;
24500 array_push($this->svgstyles
, $svgstyle);
24501 if (isset($attribs['x'])) {
24502 $x = $this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit
, false);
24503 } elseif ($name == 'tspan') {
24508 if (isset($attribs['dx'])) {
24509 $x +
= $this->getHTMLUnitToUnits($attribs['dx'], 0, $this->svgunit
, false);
24511 if (isset($attribs['y'])) {
24512 $y = $this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit
, false);
24513 } elseif ($name == 'tspan') {
24518 if (isset($attribs['dy'])) {
24519 $y +
= $this->getHTMLUnitToUnits($attribs['dy'], 0, $this->svgunit
, false);
24521 $svgstyle['text-color'] = $svgstyle['fill'];
24522 $this->svgtext
= '';
24523 if (isset($svgstyle['text-anchor'])) {
24524 $this->svgtextmode
['text-anchor'] = $svgstyle['text-anchor'];
24526 $this->svgtextmode
['text-anchor'] = 'start';
24528 if (isset($svgstyle['direction'])) {
24529 if ($svgstyle['direction'] == 'rtl') {
24530 $this->svgtextmode
['rtl'] = true;
24532 $this->svgtextmode
['rtl'] = false;
24535 $this->svgtextmode
['rtl'] = false;
24537 if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
24538 $this->svgtextmode
['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit
, false);
24540 $this->svgtextmode
['stroke'] = false;
24542 $this->StartTransform();
24543 $this->SVGTransform($tm);
24544 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24551 if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
24552 $svgdefid = substr($attribs['xlink:href'], 1);
24553 if (isset($this->svgdefs
[$svgdefid])) {
24554 $use = $this->svgdefs
[$svgdefid];
24555 if (isset($attribs['xlink:href'])) {
24556 unset($attribs['xlink:href']);
24558 if (isset($attribs['id'])) {
24559 unset($attribs['id']);
24561 if (isset($use['attribs']['x']) AND isset($attribs['x'])) {
24562 $attribs['x'] +
= $use['attribs']['x'];
24564 if (isset($use['attribs']['y']) AND isset($attribs['y'])) {
24565 $attribs['y'] +
= $use['attribs']['y'];
24567 if (empty($attribs['style'])) {
24568 $attribs['style'] = '';
24570 if (!empty($use['attribs']['style'])) {
24572 $attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24574 $attribs = array_merge($use['attribs'], $attribs);
24575 $this->startSVGElementHandler($parser, $use['name'], $attribs);
24585 // process child elements
24586 if (!empty($attribs['child_elements'])) {
24587 $child_elements = $attribs['child_elements'];
24588 unset($attribs['child_elements']);
24589 foreach($child_elements as $child_element) {
24590 if (empty($child_element['attribs']['closing_tag'])) {
24591 $this->startSVGElementHandler('child-tag', $child_element['name'], $child_element['attribs']);
24593 if (isset($child_element['attribs']['content'])) {
24594 $this->svgtext
= $child_element['attribs']['content'];
24596 $this->endSVGElementHandler('child-tag', $child_element['name']);
24603 * Sets the closing SVG element handler function for the XML parser.
24604 * @param resource|string $parser The first parameter, parser, is a reference to the XML parser calling the handler.
24605 * @param string $name 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.
24606 * @author Nicola Asuni
24607 * @since 5.0.000 (2010-05-02)
24610 protected function endSVGElementHandler($parser, $name) {
24611 $name = $this->removeTagNamespace($name);
24612 if ($this->svgdefsmode
AND !in_array($name, array('defs', 'clipPath', 'linearGradient', 'radialGradient', 'stop'))) {;
24613 if (end($this->svgdefs
) !== FALSE) {
24614 $last_svgdefs_id = key($this->svgdefs
);
24615 if (isset($this->svgdefs
[$last_svgdefs_id]['attribs']['child_elements'])) {
24616 foreach($this->svgdefs
[$last_svgdefs_id]['attribs']['child_elements'] as $child_element) {
24617 if (isset($child_element['attribs']['id']) AND ($child_element['name'] == $name)) {
24618 $this->svgdefs
[$last_svgdefs_id]['attribs']['child_elements'][$child_element['attribs']['id'].'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext
));
24622 if ($this->svgdefs
[$last_svgdefs_id]['name'] == $name) {
24623 $this->svgdefs
[$last_svgdefs_id]['attribs']['child_elements'][$last_svgdefs_id.'_CLOSE'] = array('name' => $name, 'attribs' => array('closing_tag' => TRUE, 'content' => $this->svgtext
));
24632 $this->svgdefsmode
= false;
24637 $this->svgclipmode
= false;
24641 if (--$this->svg_tag_depth
<= 0) {
24646 // ungroup: remove last style from array
24647 array_pop($this->svgstyles
);
24648 $this->StopTransform();
24653 if ($this->svgtextmode
['invisible']) {
24654 // This implementation must be fixed to following the rule:
24655 // 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.
24659 $text = $this->svgtext
;
24660 //$text = $this->stringTrim($text);
24661 $textlen = $this->GetStringWidth($text);
24662 if ($this->svgtextmode
['text-anchor'] != 'start') {
24663 // check if string is RTL text
24664 if ($this->svgtextmode
['text-anchor'] == 'end') {
24665 if ($this->svgtextmode
['rtl']) {
24666 $this->x +
= $textlen;
24668 $this->x
-= $textlen;
24670 } elseif ($this->svgtextmode
['text-anchor'] == 'middle') {
24671 if ($this->svgtextmode
['rtl']) {
24672 $this->x +
= ($textlen / 2);
24674 $this->x
-= ($textlen / 2);
24678 $textrendermode = $this->textrendermode
;
24679 $textstrokewidth = $this->textstrokewidth
;
24680 $this->setTextRenderingMode($this->svgtextmode
['stroke'], true, false);
24681 if ($name == 'text') {
24682 // store current coordinates
24687 $this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24688 if ($name == 'text') {
24689 // restore coordinates
24693 // restore previous rendering mode
24694 $this->textrendermode
= $textrendermode;
24695 $this->textstrokewidth
= $textstrokewidth;
24696 $this->svgtext
= '';
24697 $this->StopTransform();
24698 if (!$this->svgdefsmode
) {
24699 array_pop($this->svgstyles
);
24710 * Sets the character data handler function for the XML parser.
24711 * @param resource $parser The first parameter, parser, is a reference to the XML parser calling the handler.
24712 * @param string $data The second parameter, data, contains the character data as a string.
24713 * @author Nicola Asuni
24714 * @since 5.0.000 (2010-05-02)
24717 protected function segSVGContentHandler($parser, $data) {
24718 $this->svgtext
.= $data;
24721 // --- END SVG METHODS -----------------------------------------------------
24724 * Keeps files in memory, so it doesn't need to downloaded everytime in a loop
24725 * @param string $file
24728 protected function getCachedFileContents($file)
24730 if (!isset($this->fileContentCache
[$file])) {
24731 $this->fileContentCache
[$file] = TCPDF_STATIC
::fileGetContents($file);
24733 return $this->fileContentCache
[$file];
24737 * Avoid multiple calls to an external server to see if a file exists
24738 * @param string $file
24741 protected function fileExists($file)
24743 if (isset($this->fileContentCache
[$file]) ||
false !== $this->getImageBuffer($file)) {
24747 return TCPDF_STATIC
::file_exists($file);
24750 } // END OF TCPDF CLASS
24752 //============================================================+
24754 //============================================================+