Merge branch 'MDL-81419-main' of https://github.com/andrewnicols/moodle
[moodle.git] / lib / tcpdf / tcpdf.php
blobcd0205129da47b8ee7cd0d082fcd7a074239bd9a
1 <?php
2 //============================================================+
3 // File name : tcpdf.php
4 // Version : 6.6.5
5 // Begin : 2002-08-03
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 // -------------------------------------------------------------------
31 // Description :
32 // This is a PHP class for generating PDF documents without requiring external extensions.
34 // NOTE:
35 // This class was originally derived in 2002 from the Public
36 // Domain FPDF class by Olivier Plathey (http://www.fpdf.org),
37 // but now is almost entirely rewritten and contains thousands of
38 // new lines of code and hundreds new features.
40 // Main features:
41 // * no external libraries are required for the basic functions;
42 // * all standard page formats, custom page formats, custom margins and units of measure;
43 // * UTF-8 Unicode and Right-To-Left languages;
44 // * TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;
45 // * font subsetting;
46 // * methods to publish some XHTML + CSS code, Javascript and Forms;
47 // * images, graphic (geometric figures) and transformation methods;
48 // * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via 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.
67 // * PDF/A-1b support
68 //============================================================+
70 /**
71 * @file
72 * This is a PHP class for generating PDF documents without requiring external extensions.<br>
73 * TCPDF project (http://www.tcpdf.org) was originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
74 * <h3>TCPDF main features are:</h3>
75 * <ul>
76 * <li>no external libraries are required for the basic functions;</li>
77 * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
78 * <li>UTF-8 Unicode and Right-To-Left languages;</li>
79 * <li>TrueTypeUnicode, TrueType, Type1 and CID-0 fonts;</li>
80 * <li>font subsetting;</li>
81 * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
82 * <li>images, graphic (geometric figures) and transformation methods;
83 * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via 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>
103 * </ul>
104 * Tools to encode your unicode fonts are on fonts/utils directory.</p>
105 * @package com.tecnick.tcpdf
106 * @author Nicola Asuni
107 * @version 6.6.5
110 // TCPDF configuration
111 require_once(dirname(__FILE__).'/tcpdf_autoconfig.php');
112 // TCPDF static font methods and data
113 require_once(dirname(__FILE__).'/include/tcpdf_font_data.php');
114 // TCPDF static font methods and data
115 require_once(dirname(__FILE__).'/include/tcpdf_fonts.php');
116 // TCPDF static color methods and data
117 require_once(dirname(__FILE__).'/include/tcpdf_colors.php');
118 // TCPDF static image methods and data
119 require_once(dirname(__FILE__).'/include/tcpdf_images.php');
120 // TCPDF static methods and data
121 require_once(dirname(__FILE__).'/include/tcpdf_static.php');
123 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
126 * @class TCPDF
127 * PHP class for generating PDF documents without requiring external extensions.
128 * TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
129 * @package com.tecnick.tcpdf
130 * @brief PHP class for generating PDF documents without requiring external extensions.
131 * @version 6.6.5
132 * @author Nicola Asuni - info@tecnick.com
133 * @IgnoreAnnotation("protected")
134 * @IgnoreAnnotation("public")
135 * @IgnoreAnnotation("pre")
137 class TCPDF {
139 // Protected properties
142 * Current page number.
143 * @protected
145 protected $page;
148 * Current object number.
149 * @protected
151 protected $n;
154 * Array of object offsets.
155 * @protected
157 protected $offsets = array();
160 * Array of object IDs for each page.
161 * @protected
163 protected $pageobjects = array();
166 * Buffer holding in-memory PDF.
167 * @protected
169 protected $buffer;
172 * Array containing pages.
173 * @protected
175 protected $pages = array();
178 * Current document state.
179 * @protected
181 protected $state;
184 * Compression flag.
185 * @protected
187 protected $compress;
190 * Current page orientation (P = Portrait, L = Landscape).
191 * @protected
193 protected $CurOrientation;
196 * Page dimensions.
197 * @protected
199 protected $pagedim = array();
202 * Scale factor (number of points in user unit).
203 * @protected
205 protected $k;
208 * Width of page format in points.
209 * @protected
211 protected $fwPt;
214 * Height of page format in points.
215 * @protected
217 protected $fhPt;
220 * Current width of page in points.
221 * @protected
223 protected $wPt;
226 * Current height of page in points.
227 * @protected
229 protected $hPt;
232 * Current width of page in user unit.
233 * @protected
235 protected $w;
238 * Current height of page in user unit.
239 * @protected
241 protected $h;
244 * Left margin.
245 * @protected
247 protected $lMargin;
250 * Right margin.
251 * @protected
253 protected $rMargin;
256 * Cell left margin (used by regions).
257 * @protected
259 protected $clMargin;
262 * Cell right margin (used by regions).
263 * @protected
265 protected $crMargin;
268 * Top margin.
269 * @protected
271 protected $tMargin;
274 * Page break margin.
275 * @protected
277 protected $bMargin;
280 * Array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left).
281 * @since 5.9.000 (2010-10-03)
282 * @protected
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)
289 * @protected
291 protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
294 * Current horizontal position in user unit for cell positioning.
295 * @protected
297 protected $x;
300 * Current vertical position in user unit for cell positioning.
301 * @protected
303 protected $y;
306 * Height of last cell printed.
307 * @protected
309 protected $lasth;
312 * Line width in user unit.
313 * @protected
315 protected $LineWidth;
318 * Array of standard font names.
319 * @protected
321 protected $CoreFonts;
324 * Array of used fonts.
325 * @protected
327 protected $fonts = array();
330 * Array of font files.
331 * @protected
333 protected $FontFiles = array();
336 * Array of encoding differences.
337 * @protected
339 protected $diffs = array();
342 * Array of used images.
343 * @protected
345 protected $images = array();
348 * Depth of the svg tag, to keep track if the svg tag is a subtag or the root tag.
349 * @protected
351 protected $svg_tag_depth = 0;
354 * Array of Annotations in pages.
355 * @protected
357 protected $PageAnnots = array();
360 * Array of internal links.
361 * @protected
363 protected $links = array();
366 * Current font family.
367 * @protected
369 protected $FontFamily;
372 * Current font style.
373 * @protected
375 protected $FontStyle;
378 * Current font ascent (distance between font top and baseline).
379 * @protected
380 * @since 2.8.000 (2007-03-29)
382 protected $FontAscent;
385 * Current font descent (distance between font bottom and baseline).
386 * @protected
387 * @since 2.8.000 (2007-03-29)
389 protected $FontDescent;
392 * Underlining flag.
393 * @protected
395 protected $underline;
398 * Overlining flag.
399 * @protected
401 protected $overline;
404 * Current font info.
405 * @protected
407 protected $CurrentFont;
410 * Current font size in points.
411 * @protected
413 protected $FontSizePt;
416 * Current font size in user unit.
417 * @protected
419 protected $FontSize;
422 * Commands for drawing color.
423 * @protected
425 protected $DrawColor;
428 * Commands for filling color.
429 * @protected
431 protected $FillColor;
434 * Commands for text color.
435 * @protected
437 protected $TextColor;
440 * Indicates whether fill and text colors are different.
441 * @protected
443 protected $ColorFlag;
446 * Automatic page breaking.
447 * @protected
449 protected $AutoPageBreak;
452 * Threshold used to trigger page breaks.
453 * @protected
455 protected $PageBreakTrigger;
458 * Flag set when processing page header.
459 * @protected
461 protected $InHeader = false;
464 * Flag set when processing page footer.
465 * @protected
467 protected $InFooter = false;
470 * Zoom display mode.
471 * @protected
473 protected $ZoomMode;
476 * Layout display mode.
477 * @protected
479 protected $LayoutMode;
482 * If true set the document information dictionary in Unicode.
483 * @protected
485 protected $docinfounicode = true;
488 * Document title.
489 * @protected
491 protected $title = '';
494 * Document subject.
495 * @protected
497 protected $subject = '';
500 * Document author.
501 * @protected
503 protected $author = '';
506 * Document keywords.
507 * @protected
509 protected $keywords = '';
512 * Document creator.
513 * @protected
515 protected $creator = '';
518 * Starting page number.
519 * @protected
521 protected $starting_page_number = 1;
524 * The right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image.
525 * @since 2002-07-31
526 * @author Nicola Asuni
527 * @protected
529 protected $img_rb_x;
532 * The right-bottom corner Y coordinate of last inserted image.
533 * @since 2002-07-31
534 * @author Nicola Asuni
535 * @protected
537 protected $img_rb_y;
540 * Adjusting factor to convert pixels to user units.
541 * @since 2004-06-14
542 * @author Nicola Asuni
543 * @protected
545 protected $imgscale = 1;
548 * Boolean flag set to true when the input text is unicode (require unicode fonts).
549 * @since 2005-01-02
550 * @author Nicola Asuni
551 * @protected
553 protected $isunicode = false;
556 * PDF version.
557 * @since 1.5.3
558 * @protected
560 protected $PDFVersion = '1.7';
563 * ID of the stored default header template (-1 = not set).
564 * @protected
566 protected $header_xobjid = false;
569 * If true reset the Header Xobject template at each page
570 * @protected
572 protected $header_xobj_autoreset = false;
575 * Minimum distance between header and top page margin.
576 * @protected
577 * @var float
579 protected $header_margin;
582 * Minimum distance between footer and bottom page margin.
583 * @protected
584 * @var float
586 protected $footer_margin;
589 * Original left margin value.
590 * @protected
591 * @since 1.53.0.TC013
593 protected $original_lMargin;
596 * Original right margin value.
597 * @protected
598 * @since 1.53.0.TC013
600 protected $original_rMargin;
603 * Default font used on page header.
604 * @protected
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.
612 * @protected
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.
620 * @protected
622 protected $l;
625 * Barcode to print on page footer (only if set).
626 * @protected
628 protected $barcode = false;
631 * Boolean flag to print/hide page header.
632 * @protected
634 protected $print_header = true;
637 * Boolean flag to print/hide page footer.
638 * @protected
640 protected $print_footer = true;
643 * Header image logo.
644 * @protected
646 protected $header_logo = '';
649 * Width of header image logo in user units.
650 * @protected
652 protected $header_logo_width = 30;
655 * Title to be printed on default page header.
656 * @protected
658 protected $header_title = '';
661 * String to print on page header after title.
662 * @protected
664 protected $header_string = '';
667 * Color for header text (RGB array).
668 * @since 5.9.174 (2012-07-25)
669 * @protected
670 * @var int[]
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)
678 * @protected
679 * @var int[]
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)
687 * @protected
688 * @var int[]
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)
696 * @protected
697 * @var int[]
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)
705 * @protected
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.
711 * @protected
713 protected $default_table_columns = 4;
715 // variables for html parser
718 * HTML PARSER: array to store current link and rendering styles.
719 * @protected
721 protected $HREF = array();
724 * List of available fonts on filesystem.
725 * @protected
727 protected $fontlist = array();
730 * Current foreground color.
731 * @protected
733 protected $fgcolor;
736 * HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
737 * @protected
739 protected $listordered = array();
742 * HTML PARSER: array count list items on nested lists.
743 * @protected
745 protected $listcount = array();
748 * HTML PARSER: current list nesting level.
749 * @protected
751 protected $listnum = 0;
754 * HTML PARSER: indent amount for lists.
755 * @protected
757 protected $listindent = 0;
760 * HTML PARSER: current list indententation level.
761 * @protected
763 protected $listindentlevel = 0;
766 * Current background color.
767 * @protected
769 protected $bgcolor;
772 * Temporary font size in points.
773 * @protected
775 protected $tempfontsize = 10;
778 * Spacer string for LI tags.
779 * @protected
781 protected $lispacer = '';
784 * Default encoding.
785 * @protected
786 * @since 1.53.0.TC010
788 protected $encoding = 'UTF-8';
791 * Boolean flag to indicate if the document language is Right-To-Left.
792 * @protected
793 * @since 2.0.000
795 protected $rtl = false;
798 * Boolean flag used to force RTL or LTR string direction.
799 * @protected
800 * @since 2.0.000
802 protected $tmprtl = false;
804 // --- Variables used for document encryption:
807 * IBoolean flag indicating whether document is protected.
808 * @protected
809 * @since 2.0.000 (2008-01-02)
811 protected $encrypted;
814 * Array containing encryption settings.
815 * @protected
816 * @since 5.0.005 (2010-05-11)
818 protected $encryptdata = array();
821 * Last RC4 key encrypted (cached for optimisation).
822 * @protected
823 * @since 2.0.000 (2008-01-02)
825 protected $last_enc_key;
828 * Last RC4 computed key.
829 * @protected
830 * @since 2.0.000 (2008-01-02)
832 protected $last_enc_key_c;
835 * File ID (used on document trailer).
836 * @protected
837 * @since 5.0.005 (2010-05-12)
839 protected $file_id;
841 // --- bookmark ---
844 * Outlines for bookmark.
845 * @protected
846 * @since 2.1.002 (2008-02-12)
848 protected $outlines = array();
851 * Outline root for bookmark.
852 * @protected
853 * @since 2.1.002 (2008-02-12)
855 protected $OutlineRoot;
857 // --- javascript and form ---
860 * Javascript code.
861 * @protected
862 * @since 2.1.002 (2008-02-12)
864 protected $javascript = '';
867 * Javascript counter.
868 * @protected
869 * @since 2.1.002 (2008-02-12)
871 protected $n_js;
874 * line through state
875 * @protected
876 * @since 2.8.000 (2008-03-19)
878 protected $linethrough;
881 * Array with additional document-wide usage rights for the document.
882 * @protected
883 * @since 5.8.014 (2010-08-23)
885 protected $ur = array();
888 * DPI (Dot Per Inch) Document Resolution (do not change).
889 * @protected
890 * @since 3.0.000 (2008-03-27)
892 protected $dpi = 72;
895 * Array of page numbers were a new page group was started (the page numbers are the keys of the array).
896 * @protected
897 * @since 3.0.000 (2008-03-27)
899 protected $newpagegroup = array();
902 * Array that contains the number of pages in each page group.
903 * @protected
904 * @since 3.0.000 (2008-03-27)
906 protected $pagegroups = array();
909 * Current page group number.
910 * @protected
911 * @since 3.0.000 (2008-03-27)
913 protected $currpagegroup = 0;
916 * Array of transparency objects and parameters.
917 * @protected
918 * @since 3.0.000 (2008-03-27)
920 protected $extgstates;
923 * Set the default JPEG compression quality (1-100).
924 * @protected
925 * @since 3.0.000 (2008-03-27)
927 protected $jpeg_quality;
930 * Default cell height ratio.
931 * @protected
932 * @since 3.0.014 (2008-05-23)
933 * @var float
935 protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
938 * PDF viewer preferences.
939 * @protected
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.
946 * @protected
947 * @since 3.1.000 (2008-06-09)
949 protected $PageMode;
952 * Array for storing gradient information.
953 * @protected
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).
960 * @protected
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).
967 * @protected
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).
974 * @protected
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).
981 * @protected
982 * @since 4.6.021 (2009-07-20)
984 protected $cntmrk = array();
987 * Array used to store footer positions of each page.
988 * @protected
989 * @since 3.2.000 (2008-07-01)
991 protected $footerpos = array();
994 * Array used to store footer length of each page.
995 * @protected
996 * @since 4.0.014 (2008-07-29)
998 protected $footerlen = array();
1001 * Boolean flag to indicate if a new line is created.
1002 * @protected
1003 * @since 3.2.000 (2008-07-01)
1005 protected $newline = true;
1008 * End position of the latest inserted line.
1009 * @protected
1010 * @since 3.2.000 (2008-07-01)
1012 protected $endlinex = 0;
1015 * PDF string for width value of the last line.
1016 * @protected
1017 * @since 4.0.006 (2008-07-16)
1019 protected $linestyleWidth = '';
1022 * PDF string for CAP value of the last line.
1023 * @protected
1024 * @since 4.0.006 (2008-07-16)
1026 protected $linestyleCap = '0 J';
1029 * PDF string for join value of the last line.
1030 * @protected
1031 * @since 4.0.006 (2008-07-16)
1033 protected $linestyleJoin = '0 j';
1036 * PDF string for dash value of the last line.
1037 * @protected
1038 * @since 4.0.006 (2008-07-16)
1040 protected $linestyleDash = '[] 0 d';
1043 * Boolean flag to indicate if marked-content sequence is open.
1044 * @protected
1045 * @since 4.0.013 (2008-07-28)
1047 protected $openMarkedContent = false;
1050 * Count the latest inserted vertical spaces on HTML.
1051 * @protected
1052 * @since 4.0.021 (2008-08-24)
1054 protected $htmlvspace = 0;
1057 * Array of Spot colors.
1058 * @protected
1059 * @since 4.0.024 (2008-09-12)
1061 protected $spot_colors = array();
1064 * Symbol used for HTML unordered list items.
1065 * @protected
1066 * @since 4.0.028 (2008-09-26)
1068 protected $lisymbol = '';
1071 * String used to mark the beginning and end of EPS image blocks.
1072 * @protected
1073 * @since 4.1.000 (2008-10-18)
1075 protected $epsmarker = 'x#!#EPS#!#x';
1078 * Array of transformation matrix.
1079 * @protected
1080 * @since 4.2.000 (2008-10-29)
1082 protected $transfmatrix = array();
1085 * Current key for transformation matrix.
1086 * @protected
1087 * @since 4.8.005 (2009-09-17)
1089 protected $transfmatrix_key = 0;
1092 * Booklet mode for double-sided pages.
1093 * @protected
1094 * @since 4.2.000 (2008-10-29)
1096 protected $booklet = false;
1099 * Epsilon value used for float calculations.
1100 * @protected
1101 * @since 4.2.000 (2008-10-29)
1103 protected $feps = 0.005;
1106 * Array used for custom vertical spaces for HTML tags.
1107 * @protected
1108 * @since 4.2.001 (2008-10-30)
1110 protected $tagvspaces = array();
1113 * HTML PARSER: custom indent amount for lists. Negative value means disabled.
1114 * @protected
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.
1121 * @protected
1122 * @since 4.2.010 (2008-11-14)
1124 protected $opencell = true;
1127 * Array of files to embedd.
1128 * @protected
1129 * @since 4.4.000 (2008-12-07)
1131 protected $embeddedfiles = array();
1134 * Boolean flag to indicate if we are inside a PRE tag.
1135 * @protected
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
1143 * @protected
1144 * @since 4.4.002 (2008-12-09)
1146 protected $transfmrk = array();
1149 * Default color for html links.
1150 * @protected
1151 * @since 4.4.003 (2008-12-09)
1153 protected $htmlLinkColorArray = array(0, 0, 255);
1156 * Default font style to add to html links.
1157 * @protected
1158 * @since 4.4.003 (2008-12-09)
1160 protected $htmlLinkFontStyle = 'U';
1163 * Counts the number of pages.
1164 * @protected
1165 * @since 4.5.000 (2008-12-31)
1167 protected $numpages = 0;
1170 * Array containing page lengths in bytes.
1171 * @protected
1172 * @since 4.5.000 (2008-12-31)
1174 protected $pagelen = array();
1177 * Counts the number of pages.
1178 * @protected
1179 * @since 4.5.000 (2008-12-31)
1181 protected $numimages = 0;
1184 * Store the image keys.
1185 * @protected
1186 * @since 4.5.000 (2008-12-31)
1188 protected $imagekeys = array();
1191 * Length of the buffer in bytes.
1192 * @protected
1193 * @since 4.5.000 (2008-12-31)
1195 protected $bufferlen = 0;
1198 * Counts the number of fonts.
1199 * @protected
1200 * @since 4.5.000 (2009-01-02)
1202 protected $numfonts = 0;
1205 * Store the font keys.
1206 * @protected
1207 * @since 4.5.000 (2009-01-02)
1209 protected $fontkeys = array();
1212 * Store the font object IDs.
1213 * @protected
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).
1220 * @protected
1221 * @since 4.5.000 (2009-01-02)
1223 protected $pageopen = array();
1226 * Default monospace font.
1227 * @protected
1228 * @since 4.5.025 (2009-03-10)
1230 protected $default_monospaced_font = 'courier';
1233 * Cloned copy of the current class object.
1234 * @protected
1235 * @since 4.5.029 (2009-03-19)
1237 protected $objcopy;
1240 * Array used to store the lengths of cache files.
1241 * @protected
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.
1248 * @protected
1249 * @since 4.5.030 (2009-03-20)
1251 protected $thead = '';
1254 * Margins used for table header.
1255 * @protected
1256 * @since 4.5.030 (2009-03-20)
1258 protected $theadMargins = array();
1261 * Boolean flag to enable document digital signature.
1262 * @protected
1263 * @since 4.6.005 (2009-04-24)
1265 protected $sign = false;
1268 * Digital signature data.
1269 * @protected
1270 * @since 4.6.005 (2009-04-24)
1272 protected $signature_data = array();
1275 * Digital signature max length.
1276 * @protected
1277 * @since 4.6.005 (2009-04-24)
1279 protected $signature_max_length = 11742;
1282 * Data for digital signature appearance.
1283 * @protected
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.
1290 * @protected
1291 * @since 5.9.101 (2011-07-06)
1293 protected $empty_signature_appearance = array();
1296 * Boolean flag to enable document timestamping with TSA.
1297 * @protected
1298 * @since 6.0.085 (2014-06-19)
1300 protected $tsa_timestamp = false;
1303 * Timestamping data.
1304 * @protected
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).
1311 * @protected
1312 * @since 4.6.006 (2009-04-28)
1314 protected $re_spaces = '/[^\S\xa0]/';
1317 * Array of $re_spaces parts.
1318 * @protected
1319 * @since 5.5.011 (2010-07-09)
1321 protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
1324 * Digital signature object ID.
1325 * @protected
1326 * @since 4.6.022 (2009-06-23)
1328 protected $sig_obj_id = 0;
1331 * ID of page objects.
1332 * @protected
1333 * @since 4.7.000 (2009-08-29)
1335 protected $page_obj_id = array();
1338 * List of form annotations IDs.
1339 * @protected
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.
1346 * @protected
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.
1353 * @protected
1354 * @since 4.8.000 (2009-09-07)
1356 protected $js_objects = array();
1359 * Current form action (used during XHTML rendering).
1360 * @protected
1361 * @since 4.8.000 (2009-09-07)
1363 protected $form_action = '';
1366 * Current form encryption type (used during XHTML rendering).
1367 * @protected
1368 * @since 4.8.000 (2009-09-07)
1370 protected $form_enctype = 'application/x-www-form-urlencoded';
1373 * Current method to submit forms.
1374 * @protected
1375 * @since 4.8.000 (2009-09-07)
1377 protected $form_mode = 'post';
1380 * List of fonts used on form fields (fontname => fontkey).
1381 * @protected
1382 * @since 4.8.001 (2009-09-09)
1384 protected $annotation_fonts = array();
1387 * List of radio buttons parent objects.
1388 * @protected
1389 * @since 4.8.001 (2009-09-09)
1391 protected $radiobutton_groups = array();
1394 * List of radio group objects IDs.
1395 * @protected
1396 * @since 4.8.001 (2009-09-09)
1398 protected $radio_groups = array();
1401 * Text indentation value (used for text-indent CSS attribute).
1402 * @protected
1403 * @since 4.8.006 (2009-09-23)
1405 protected $textindent = 0;
1408 * Store page number when startTransaction() is called.
1409 * @protected
1410 * @since 4.8.006 (2009-09-23)
1412 protected $start_transaction_page = 0;
1415 * Store Y position when startTransaction() is called.
1416 * @protected
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.
1423 * @protected
1424 * @since 4.8.027 (2010-01-25)
1426 protected $inthead = false;
1429 * Array of column measures (width, space, starting Y position).
1430 * @protected
1431 * @since 4.9.001 (2010-03-28)
1433 protected $columns = array();
1436 * Number of colums.
1437 * @protected
1438 * @since 4.9.001 (2010-03-28)
1440 protected $num_columns = 1;
1443 * Current column number.
1444 * @protected
1445 * @since 4.9.001 (2010-03-28)
1447 protected $current_column = 0;
1450 * Starting page for columns.
1451 * @protected
1452 * @since 4.9.001 (2010-03-28)
1454 protected $column_start_page = 0;
1457 * Maximum page and column selected.
1458 * @protected
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.
1465 * @protected
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.
1472 * @protected
1473 * @since 4.9.008 (2010-04-03)
1475 protected $textrendermode = 0;
1478 * Text stroke width in doc units.
1479 * @protected
1480 * @since 4.9.008 (2010-04-03)
1482 protected $textstrokewidth = 0;
1485 * Current stroke color.
1486 * @protected
1487 * @since 4.9.008 (2010-04-03)
1489 protected $strokecolor;
1492 * Default unit of measure for document.
1493 * @protected
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.
1500 * @protected
1502 protected $tocpage = false;
1505 * Boolean flag: if true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
1506 * @protected
1507 * @since 5.0.000 (2010-04-26)
1509 protected $rasterize_vector_images = false;
1512 * Boolean flag: if true enables font subsetting by default.
1513 * @protected
1514 * @since 5.3.002 (2010-06-07)
1516 protected $font_subsetting = true;
1519 * Array of default graphic settings.
1520 * @protected
1521 * @since 5.5.008 (2010-07-02)
1523 protected $default_graphic_vars = array();
1526 * Array of XObjects.
1527 * @protected
1528 * @since 5.8.014 (2010-08-23)
1530 protected $xobjects = array();
1533 * Boolean value true when we are inside an XObject.
1534 * @protected
1535 * @since 5.8.017 (2010-08-24)
1537 protected $inxobj = false;
1540 * Current XObject ID.
1541 * @protected
1542 * @since 5.8.017 (2010-08-24)
1544 protected $xobjid = '';
1547 * Percentage of character stretching.
1548 * @protected
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).
1555 * @protected
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)
1563 * @protected
1564 * @since 5.9.003 (2010-10-14)
1566 protected $page_regions = array();
1569 * Boolean value true when page region check is active.
1570 * @protected
1572 protected $check_page_regions = true;
1575 * Array of PDF layers data.
1576 * @protected
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).
1583 * @protected
1584 * @since 5.9.097 (2011-06-23)
1586 protected $dests = array();
1589 * Object ID for Named Destinations
1590 * @protected
1591 * @since 5.9.097 (2011-06-23)
1593 protected $n_dests;
1596 * Embedded Files Names
1597 * @protected
1598 * @since 5.9.204 (2013-01-23)
1600 protected $efnames = array();
1603 * Directory used for the last SVG image.
1604 * @protected
1605 * @since 5.0.000 (2010-05-05)
1607 protected $svgdir = '';
1610 * Deafult unit of measure for SVG.
1611 * @protected
1612 * @since 5.0.000 (2010-05-02)
1614 protected $svgunit = 'px';
1617 * Array of SVG gradients.
1618 * @protected
1619 * @since 5.0.000 (2010-05-02)
1621 protected $svggradients = array();
1624 * ID of last SVG gradient.
1625 * @protected
1626 * @since 5.0.000 (2010-05-02)
1628 protected $svggradientid = 0;
1631 * Boolean value true when in SVG defs group.
1632 * @protected
1633 * @since 5.0.000 (2010-05-02)
1635 protected $svgdefsmode = false;
1638 * Array of SVG defs.
1639 * @protected
1640 * @since 5.0.000 (2010-05-02)
1642 protected $svgdefs = array();
1645 * Boolean value true when in SVG clipPath tag.
1646 * @protected
1647 * @since 5.0.000 (2010-04-26)
1649 protected $svgclipmode = false;
1652 * Array of SVG clipPath commands.
1653 * @protected
1654 * @since 5.0.000 (2010-05-02)
1656 protected $svgclippaths = array();
1659 * Array of SVG clipPath tranformation matrix.
1660 * @protected
1661 * @since 5.8.022 (2010-08-31)
1663 protected $svgcliptm = array();
1666 * ID of last SVG clipPath.
1667 * @protected
1668 * @since 5.0.000 (2010-05-02)
1670 protected $svgclipid = 0;
1673 * SVG text.
1674 * @protected
1675 * @since 5.0.000 (2010-05-02)
1677 protected $svgtext = '';
1680 * SVG text properties.
1681 * @protected
1682 * @since 5.8.013 (2010-08-23)
1684 protected $svgtextmode = array();
1687 * Array of SVG properties.
1688 * @protected
1689 * @since 5.0.000 (2010-05-02)
1691 protected $svgstyles = array(array(
1692 'alignment-baseline' => 'auto',
1693 'baseline-shift' => 'baseline',
1694 'clip' => 'auto',
1695 'clip-path' => 'none',
1696 'clip-rule' => 'nonzero',
1697 'color' => 'black',
1698 'color-interpolation' => 'sRGB',
1699 'color-interpolation-filters' => 'linearRGB',
1700 'color-profile' => 'auto',
1701 'color-rendering' => 'auto',
1702 'cursor' => 'auto',
1703 'direction' => 'ltr',
1704 'display' => 'inline',
1705 'dominant-baseline' => 'auto',
1706 'enable-background' => 'accumulate',
1707 'fill' => 'black',
1708 'fill-opacity' => 1,
1709 'fill-rule' => 'nonzero',
1710 'filter' => 'none',
1711 'flood-color' => 'black',
1712 'flood-opacity' => 1,
1713 'font' => '',
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',
1727 'marker' => '',
1728 'marker-end' => 'none',
1729 'marker-mid' => 'none',
1730 'marker-start' => 'none',
1731 'mask' => 'none',
1732 'opacity' => 1,
1733 'overflow' => 'auto',
1734 'pointer-events' => 'visiblePainted',
1735 'shape-rendering' => 'auto',
1736 'stop-color' => 'black',
1737 'stop-opacity' => 1,
1738 'stroke' => 'none',
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.
1759 * @protected
1760 * @since 5.9.121 (2011-09-28)
1762 protected $force_srgb = false;
1765 * If true set the document to PDF/A mode.
1766 * @protected
1767 * @since 5.9.121 (2011-09-27)
1769 protected $pdfa_mode = false;
1772 * version of PDF/A mode (1 - 3).
1773 * @protected
1774 * @since 6.2.26 (2019-03-12)
1776 protected $pdfa_version = 1;
1779 * Document creation date-time
1780 * @protected
1781 * @since 5.9.152 (2012-03-22)
1783 protected $doc_creation_timestamp;
1786 * Document modification date-time
1787 * @protected
1788 * @since 5.9.152 (2012-03-22)
1790 protected $doc_modification_timestamp;
1793 * Custom XMP data.
1794 * @protected
1795 * @since 5.9.128 (2011-10-06)
1797 protected $custom_xmp = '';
1800 * Custom XMP RDF data.
1801 * @protected
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).
1809 * @protected
1810 * @since 5.9.152 (2012-03-23)
1811 * @var array<string,bool|int>
1813 protected $overprint = array('OP' => false, 'op' => false, 'OPM' => 0);
1816 * Alpha mode array.
1817 * (Check the "Entries in a Graphics State Parameter Dictionary" on PDF 32000-1:2008).
1818 * @protected
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.
1825 * @protected
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.
1832 * @protected
1833 * @since 5.9.152 (2012-03-23)
1835 protected $tcpdflink = true;
1838 * Cache array for computed GD gamma values.
1839 * @protected
1840 * @since 5.9.1632 (2012-06-05)
1842 protected $gdgammacache = array();
1845 * Cache array for file content
1846 * @protected
1847 * @var array
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://
1855 * @var bool
1856 * @protected
1857 * @since 6.4 (2020-07-23)
1859 protected $allowLocalFiles = false;
1861 //------------------------------------------------------------
1862 // METHODS
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).
1876 * @public
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();
1887 // set pdf/a mode
1888 if ($pdfa != false) {
1889 $this->pdfa_mode = true;
1890 $this->pdfa_version = $pdfa; // 1 or 3
1891 } else
1892 $this->pdfa_mode = false;
1894 $this->force_srgb = false;
1895 // set language direction
1896 $this->rtl = false;
1897 $this->tmprtl = false;
1898 // some checks
1899 $this->_dochecks();
1900 // initialization of properties
1901 $this->isunicode = $unicode;
1902 $this->page = 0;
1903 $this->transfmrk[0] = array();
1904 $this->pagedim = array();
1905 $this->n = 2;
1906 $this->buffer = '';
1907 $this->pages = array();
1908 $this->state = 0;
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;
1916 $this->lasth = 0;
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',
1945 'symbol'=>'Symbol',
1946 'zapfdingbats'=>'ZapfDingbats'
1948 // set scale factor
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);
1960 // cell margins
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');
1972 // compression
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();
1985 // signature
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();
1991 // user's rights
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);
2003 // set default font
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');
2016 } else {
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.
2033 * @public
2034 * @since 1.53.0.TC016
2036 public function __destruct() {
2037 // cleanup
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.
2044 * @public
2045 * @since 3.0.015 (2008-06-06)
2047 public function setPageUnit($unit) {
2048 $unit = strtolower($unit);
2049 //Set scale factor
2050 switch ($unit) {
2051 // points
2052 case 'px':
2053 case 'pt': {
2054 $this->k = 1;
2055 break;
2057 // millimeters
2058 case 'mm': {
2059 $this->k = $this->dpi / 25.4;
2060 break;
2062 // centimeters
2063 case 'cm': {
2064 $this->k = $this->dpi / 2.54;
2065 break;
2067 // inches
2068 case 'in': {
2069 $this->k = $this->dpi;
2070 break;
2072 // unsupported unit
2073 default : {
2074 $this->Error('Incorrect unit: '.$unit);
2075 break;
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>
2129 * </ul>
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>
2134 * </ul>
2135 * @protected
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];
2149 } else {
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);
2155 } else {
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));
2158 } else {
2159 if (!isset($format['format'])) {
2160 // default value
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';
2231 } else {
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']);
2237 } else {
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.
2250 * @public
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) {
2283 // landscape
2284 $default_orientation = 'L';
2285 } else {
2286 // portrait
2287 $default_orientation = 'P';
2289 $valid_orientations = array('P', 'L');
2290 if (empty($orientation)) {
2291 $orientation = $default_orientation;
2292 } else {
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;
2299 } else {
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;
2313 } else {
2314 $autopagebreak = true;
2317 if (TCPDF_STATIC::empty_string($bottommargin)) {
2318 if (isset($this->bMargin)) {
2319 $bottommargin = $this->bMargin;
2320 } else {
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:
2345 * <pre>
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)
2354 * </pre>
2355 * @param string $re regular expression (leave empty for default).
2356 * @public
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];
2366 } else {
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];
2372 } else {
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.
2381 * @public
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;
2389 if ($resetx) {
2390 $this->Ln(0);
2395 * Return the RTL status
2396 * @return bool
2397 * @public
2398 * @since 4.0.012 (2008-07-24)
2400 public function getRTL() {
2401 return $this->rtl;
2405 * Force temporary RTL language direction
2406 * @param false|string $mode can be false, 'L' for LTR or 'R' for RTL
2407 * @public
2408 * @since 2.1.000 (2008-01-09)
2410 public function setTempRTL($mode) {
2411 $newmode = false;
2412 switch (strtoupper($mode)) {
2413 case 'LTR':
2414 case 'L': {
2415 if ($this->rtl) {
2416 $newmode = 'L';
2418 break;
2420 case 'RTL':
2421 case 'R': {
2422 if (!$this->rtl) {
2423 $newmode = 'R';
2425 break;
2427 case false:
2428 default: {
2429 $newmode = false;
2430 break;
2433 $this->tmprtl = $newmode;
2437 * Return the current temporary RTL status
2438 * @return bool
2439 * @public
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
2450 * @public
2451 * @since 1.53.0.TC034
2453 public function setLastH($h) {
2454 $this->lasth = $h;
2458 * Return the cell height
2459 * @param int $fontsize Font size in internal units
2460 * @param boolean $padding If true add cell padding
2461 * @public
2462 * @return float
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.
2474 * @public
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
2484 * @public
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
2495 * @public
2496 * @since 1.5.2
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
2506 * @public
2507 * @since 1.5.2
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
2519 * @public
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
2534 * @public
2535 * @since 1.5.2
2536 * @see getPageDimensions()
2538 public function getPageWidth($pagenum=null) {
2539 if (empty($pagenum)) {
2540 return $this->w;
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
2550 * @public
2551 * @since 1.5.2
2552 * @see getPageDimensions()
2554 public function getPageHeight($pagenum=null) {
2555 if (empty($pagenum)) {
2556 return $this->h;
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
2566 * @public
2567 * @since 1.5.2
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
2581 * @public
2582 * @since 1.5.2
2584 public function getScaleFactor() {
2585 return $this->k;
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
2594 * @public
2595 * @since 1.0
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) {
2603 $right = $left;
2605 $this->rMargin = $right;
2606 if ($keepmargins) {
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.
2616 * @public
2617 * @since 1.4
2618 * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2620 public function setLeftMargin($margin) {
2621 //Set left margin
2622 $this->lMargin = $margin;
2623 if (($this->page > 0) AND ($this->x < $margin)) {
2624 $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.
2631 * @public
2632 * @since 1.5
2633 * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
2635 public function setTopMargin($margin) {
2636 //Set top margin
2637 $this->tMargin = $margin;
2638 if (($this->page > 0) AND ($this->y < $margin)) {
2639 $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.
2646 * @public
2647 * @since 1.5
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.
2660 * @public
2661 * @since 2.1.000 (2008-01-09)
2662 * @see getCellPaddings(), setCellPaddings()
2664 public function setCellPadding($pad) {
2665 if ($pad >= 0) {
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
2679 * @public
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
2701 * @public
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
2715 * @public
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
2737 * @public
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
2749 * @public
2750 * @since 5.9.000 (2010-10-03)
2752 protected function adjustCellPadding($brd=0) {
2753 if (empty($brd)) {
2754 return;
2756 if (is_string($brd)) {
2757 // convert string to array
2758 $slen = strlen($brd);
2759 $newbrd = array();
2760 for ($i = 0; $i < $slen; ++$i) {
2761 $newbrd[$brd[$i]] = true;
2763 $brd = $newbrd;
2764 } elseif (
2765 ($brd === 1)
2766 || ($brd === true)
2767 || (is_numeric($brd) && ((int)$brd > 0))
2769 $brd = array('LRTB' => true);
2771 if (!is_array($brd)) {
2772 return;
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']);
2780 } else {
2781 $mode = 'normal';
2783 // process borders
2784 foreach ($brd as $border => $style) {
2785 $line_width = $this->LineWidth;
2786 if (is_array($style) && isset($style['width'])) {
2787 // get border width
2788 $line_width = $style['width'];
2790 $adj = 0; // line width inside the cell
2791 switch ($mode) {
2792 case 'ext': {
2793 $adj = 0;
2794 break;
2796 case 'int': {
2797 $adj = $line_width;
2798 break;
2800 case 'normal':
2801 default: {
2802 $adj = ($line_width / 2);
2803 break;
2806 // correct internal cell padding if required to avoid overlap between text and lines
2807 if (
2808 is_numeric($this->cell_padding['T'])
2809 && ($this->cell_padding['T'] < $adj)
2810 && (strpos($border, 'T') !== false)
2812 $this->cell_padding['T'] = $adj;
2814 if (
2815 is_numeric($this->cell_padding['R'])
2816 && ($this->cell_padding['R'] < $adj)
2817 && (strpos($border, 'R') !== false)
2819 $this->cell_padding['R'] = $adj;
2821 if (
2822 is_numeric($this->cell_padding['B'])
2823 && ($this->cell_padding['B'] < $adj)
2824 && (strpos($border, 'B') !== false)
2826 $this->cell_padding['B'] = $adj;
2828 if (
2829 is_numeric($this->cell_padding['L'])
2830 && ($this->cell_padding['L'] < $adj)
2831 && (strpos($border, 'L') !== false)
2833 $this->cell_padding['L'] = $adj;
2838 return array(
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.
2850 * @public
2851 * @since 1.0
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
2863 * @public
2864 * @since 5.9.088
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>
2875 * @public
2876 * @since 1.2
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;
2881 } else {
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.
2892 * @public
2893 * @since 1.4
2895 public function setCompression($compress=true) {
2896 $this->compress = false;
2897 if (function_exists('gzcompress')) {
2898 if ($compress) {
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.
2909 * @public
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)
2921 * @public
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.
2930 * @public
2931 * @since 1.2
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.
2941 * @public
2942 * @since 1.2
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.
2952 * @public
2953 * @since 1.2
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.
2963 * @public
2964 * @since 1.2
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.
2974 * @public
2975 * @since 1.2
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.
2986 * @public
2987 * @since 6.4
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
2997 * @public
2998 * @since 1.0
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);
3005 } else {
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
3014 * @public
3015 * @since 1.0
3016 * @see AddPage(), Close()
3018 public function Open() {
3019 $this->state = 1;
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.
3026 * @public
3027 * @since 1.0
3028 * @see Open(), Output()
3030 public function Close() {
3031 if ($this->state == 3) {
3032 return;
3034 if ($this->page == 0) {
3035 $this->AddPage();
3037 $this->endLayer();
3038 if ($this->tcpdflink) {
3039 // save current graphic settings
3040 $gvars = $this->getGraphicVars();
3041 $this->setEqualColumns();
3042 $this->lastpage(true);
3043 $this->setAutoPageBreak(false);
3044 $this->x = 0;
3045 $this->y = $this->h - (1 / $this->k);
3046 $this->lMargin = 0;
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);
3058 // close page
3059 $this->endPage();
3060 // close document
3061 $this->_enddoc();
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.
3070 * @public
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)) {
3076 return;
3078 if (($pnum > 0) AND ($pnum <= $this->numpages)) {
3079 $this->state = 2;
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);
3101 } else {
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;
3109 } else {
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.
3117 * @public
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
3128 * @public
3129 * @since 2.1.000 (2008-01-07)
3130 * @see setPage(), lastpage(), getNumPages()
3132 public function getPage() {
3133 return $this->page;
3137 * Get the total number of insered pages.
3138 * @return int number of pages
3139 * @public
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
3152 * @public
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
3162 * @public
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).
3177 * @public
3178 * @since 1.0
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
3184 return;
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
3193 $this->endPage();
3194 // start new 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).
3201 * @public
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])) {
3208 return;
3210 // print page footer
3211 $this->setFooter();
3212 // close page
3213 $this->_endpage();
3214 // mark page as closed
3215 $this->pageopen[$this->page] = false;
3216 if ($tocpage) {
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()
3229 * @public
3231 public function startPage($orientation='', $format='', $tocpage=false) {
3232 if ($tocpage) {
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)
3238 // adjust outlines
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);
3245 // adjust dests
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);
3252 // adjust links
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);
3264 return;
3266 // start a new page
3267 if ($this->state == 0) {
3268 $this->Open();
3270 ++$this->numpages;
3271 $this->swapMargins($this->booklet);
3272 // save current graphic settings
3273 $gvars = $this->getGraphicVars();
3274 // start new page
3275 $this->_beginpage($orientation, $format);
3276 // mark page as open
3277 $this->pageopen[$this->page] = true;
3278 // restore graphic settings
3279 $this->setGraphicVars($gvars);
3280 // mark this point
3281 $this->setPageMark();
3282 // print page header
3283 $this->setHeader();
3284 // restore graphic settings
3285 $this->setGraphicVars($gvars);
3286 // mark this point
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.
3299 * @public
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)
3312 * @protected
3313 * @since 4.6.021 (2009-07-20)
3315 protected function setContentMark($page=0) {
3316 if ($page <= 0) {
3317 $page = $this->page;
3319 if (isset($this->footerlen[$page])) {
3320 $this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
3321 } else {
3322 $this->cntmrk[$page] = $this->pagelen[$page];
3327 * Set header data.
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.
3334 * @public
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;
3346 * Set footer data.
3347 * @param int[] $tc RGB array color for text.
3348 * @param int[] $lc RGB array color for line.
3349 * @public
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>
3360 * @public
3361 * @since 4.0.012 (2008-07-24)
3363 public function getHeaderData() {
3364 $ret = array();
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;
3371 return $ret;
3375 * Set header margin.
3376 * (minimum distance between header and top page margin)
3377 * @param float $hm distance in user units
3378 * @public
3380 public function setHeaderMargin($hm=10) {
3381 $this->header_margin = $hm;
3385 * Returns header margin in user units.
3386 * @return float
3387 * @since 4.0.012 (2008-07-24)
3388 * @public
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
3398 * @public
3400 public function setFooterMargin($fm=10) {
3401 $this->footer_margin = $fm;
3405 * Returns footer margin in user units.
3406 * @return float
3407 * @since 4.0.012 (2008-07-24)
3408 * @public
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.
3416 * @public
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.
3425 * @public
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
3433 * @return float
3434 * @public
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
3442 * @return float
3443 * @public
3445 public function getImageRBY() {
3446 return $this->img_rb_y;
3450 * Reset the xobject template used by Header() method.
3451 * @public
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.
3460 * @public
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.
3469 * @public
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;
3478 if ($this->rtl) {
3479 $this->x = $this->w - $this->original_rMargin;
3480 } else {
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']);
3489 } else {
3490 $this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
3492 $imgy = $this->getImageRBY();
3493 } else {
3494 $imgy = $this->y;
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);
3500 } else {
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);
3505 // header title
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);
3509 // header string
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));
3516 if ($this->rtl) {
3517 $this->setX($this->original_rMargin);
3518 } else {
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
3525 $x = 0;
3526 $dx = 0;
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);
3531 if ($this->rtl) {
3532 $x = $this->w + $dx;
3533 } else {
3534 $x = 0 + $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.
3546 * @public
3548 public function Footer() {
3549 $cur_y = $this->y;
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);
3559 $style = array(
3560 'position' => $this->rtl?'R':'L',
3561 'align' => $this->rtl?'R':'L',
3562 'stretch' => false,
3563 'fitwidth' => true,
3564 'cellfitalign' => '',
3565 'border' => false,
3566 'padding' => 0,
3567 'fgcolor' => array(0,0,0),
3568 'bgcolor' => false,
3569 'text' => false
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();
3576 } else {
3577 $pagenumtxt = $w_page.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
3579 $this->setY($cur_y);
3580 //Print page number
3581 if ($this->getRTL()) {
3582 $this->setX($this->original_rMargin);
3583 $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
3584 } else {
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.
3592 * @protected
3593 * @since 4.0.012 (2008-07-24)
3595 protected function setHeader() {
3596 if (!$this->print_header OR ($this->state != 2)) {
3597 return;
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
3610 if ($this->rtl) {
3611 $this->setXY($this->original_rMargin, $this->header_margin);
3612 } else {
3613 $this->setXY($this->original_lMargin, $this->header_margin);
3615 $this->setFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
3616 $this->Header();
3617 //restore position
3618 if ($this->rtl) {
3619 $this->setXY($this->original_rMargin, $this->tMargin);
3620 } else {
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.
3633 * @protected
3634 * @since 4.0.012 (2008-07-24)
3636 protected function setFooter() {
3637 if ($this->state != 2) {
3638 return;
3640 $this->InFooter = true;
3641 // save current graphic settings
3642 $gvars = $this->getGraphicVars();
3643 // mark this point
3644 $this->footerpos[$this->page] = $this->pagelen[$this->page];
3645 $this->_out("\n");
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;
3659 if ($this->rtl) {
3660 $this->setXY($this->original_rMargin, $footer_y);
3661 } else {
3662 $this->setXY($this->original_lMargin, $footer_y);
3664 $this->setFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
3665 $this->Footer();
3666 //restore position
3667 if ($this->rtl) {
3668 $this->setXY($this->original_rMargin, $this->tMargin);
3669 } else {
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.
3689 * @protected
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).
3698 * @protected
3699 * @since 4.5.030 (2009-03-25)
3701 protected function setTableHeader() {
3702 if ($this->num_columns > 1) {
3703 // multi column mode
3704 return;
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)) {
3713 // set margins
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'];
3720 if ($this->rtl) {
3721 $this->x = $this->w - $this->rMargin;
3722 } else {
3723 $this->x = $this->lMargin;
3725 // account for special "cell" mode
3726 if ($this->theadMargins['cell']) {
3727 if ($this->rtl) {
3728 $this->x -= $this->cell_padding['R'];
3729 } else {
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;
3754 $this->lasth = 0;
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
3764 * @public
3765 * @since 1.0
3766 * @see getAliasNbPages()
3768 public function PageNo() {
3769 return $this->page;
3773 * Returns the array of spot colors.
3774 * @return array Spot colors array.
3775 * @public
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.
3791 * @public
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.
3808 * @public
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']);
3818 switch ($type) {
3819 case 'draw': {
3820 $pdfcolor .= sprintf('CS %F SCN', $tint);
3821 $this->DrawColor = $pdfcolor;
3822 $this->strokecolor = $spotcolor;
3823 break;
3825 case 'fill': {
3826 $pdfcolor .= sprintf('cs %F scn', $tint);
3827 $this->FillColor = $pdfcolor;
3828 $this->bgcolor = $spotcolor;
3829 break;
3831 case 'text': {
3832 $pdfcolor .= sprintf('cs %F scn', $tint);
3833 $this->TextColor = $pdfcolor;
3834 $this->fgcolor = $spotcolor;
3835 break;
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];
3846 return $pdfcolor;
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).
3853 * @public
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).
3865 * @public
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).
3877 * @public
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.
3893 * @public
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;
3907 // color name
3908 $name = isset($color[4]) ? $color[4] : '';
3909 if ($c >= 0) {
3910 return $this->setColor($type, $c, $m, $y, $k, $ret, $name);
3913 return '';
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
3923 * @public
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.
3937 * @public
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.
3950 * @public
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.
3968 * @public
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)) {
3974 $col1 = 0;
3976 if (!is_numeric($col2)) {
3977 $col2 = -1;
3979 if (!is_numeric($col3)) {
3980 $col3 = -1;
3982 if (!is_numeric($col4)) {
3983 $col4 = -1;
3985 // set color by case
3986 $suffix = '';
3987 if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
3988 // Grey scale
3989 $col1 = max(0, min(255, $col1));
3990 $intcolor = array('G' => $col1);
3991 $pdfcolor = sprintf('%F ', ($col1 / 255));
3992 $suffix = 'g';
3993 } elseif ($col4 == -1) {
3994 // RGB
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));
4000 $suffix = 'rg';
4001 } else {
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));
4006 if (empty($name)) {
4007 // CMYK
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));
4010 $suffix = 'k';
4011 } else {
4012 // SPOT COLOR
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);
4018 switch ($type) {
4019 case 'draw': {
4020 $pdfcolor .= strtoupper($suffix);
4021 $this->DrawColor = $pdfcolor;
4022 $this->strokecolor = $intcolor;
4023 break;
4025 case 'fill': {
4026 $pdfcolor .= $suffix;
4027 $this->FillColor = $pdfcolor;
4028 $this->bgcolor = $intcolor;
4029 break;
4031 case 'text': {
4032 $pdfcolor .= $suffix;
4033 $this->TextColor = $pdfcolor;
4034 $this->fgcolor = $intcolor;
4035 break;
4038 $this->ColorFlag = ($this->FillColor != $this->TextColor);
4039 if (($type != 'text') AND ($this->state == 2) AND $type !== 0) {
4040 if (!$ret) {
4041 $this->_out($pdfcolor);
4043 return $pdfcolor;
4045 return '';
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
4057 * @public
4058 * @since 1.3
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.
4074 * @public
4075 * @since 1.3
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.
4091 * @public
4092 * @since 1.3
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
4109 * @public
4110 * @since 1.2
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
4126 * @public
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) {
4144 // character width
4145 $cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
4146 $wa[] = $cw;
4147 $w += $cw;
4149 // restore previous values
4150 if (!TCPDF_STATIC::empty_string($fontname)) {
4151 $this->setFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt, '', 'default', false);
4153 if ($getarray) {
4154 return $wa;
4156 return $w;
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
4165 * @public
4166 * @since 2.4.000 (2008-03-06)
4168 public function GetCharWidth($char, $notlast=true) {
4169 // get raw width
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);
4179 return $chw;
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
4187 * @public
4188 * @since 5.9.000 (2010-09-28)
4190 public function getRawCharWidth($char) {
4191 if ($char == 173) {
4192 // SHY character will not be printed
4193 return (0);
4195 if (isset($this->CurrentFont['cw'][intval($char)])) {
4196 $w = $this->CurrentFont['cw'][intval($char)];
4197 } elseif (isset($this->CurrentFont['dw'])) {
4198 // default width
4199 $w = $this->CurrentFont['dw'];
4200 } elseif (isset($this->CurrentFont['cw'][32])) {
4201 // default width
4202 $w = $this->CurrentFont['cw'][32];
4203 } else {
4204 $w = 600;
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
4213 * @public
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));
4220 return strlen($s);
4224 * Fill the list of available fonts ($this->fontlist).
4225 * @protected
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.
4248 * @public
4249 * @since 1.5
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) {
4257 $subset = false;
4259 if (TCPDF_STATIC::empty_string($family)) {
4260 if (!TCPDF_STATIC::empty_string($this->FontFamily)) {
4261 $family = $this->FontFamily;
4262 } else {
4263 $this->Error('Empty font family');
4266 // move embedded styles on $style
4267 if (substr($family, -1) == 'I') {
4268 $style .= 'I';
4269 $family = substr($family, 0, -1);
4271 if (substr($family, -1) == 'B') {
4272 $style .= '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')) {
4281 $style = '';
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);
4288 $style = '';
4289 // underline
4290 if (strpos($tempstyle, 'U') !== false) {
4291 $this->underline = true;
4292 } else {
4293 $this->underline = false;
4295 // line-through (deleted)
4296 if (strpos($tempstyle, 'D') !== false) {
4297 $this->linethrough = true;
4298 } else {
4299 $this->linethrough = false;
4301 // overline
4302 if (strpos($tempstyle, 'O') !== false) {
4303 $this->overline = true;
4304 } else {
4305 $this->overline = false;
4307 // bold
4308 if (strpos($tempstyle, 'B') !== false) {
4309 $style .= 'B';
4311 // oblique
4312 if (strpos($tempstyle, 'I') !== false) {
4313 $style .= 'I';
4315 $bistyle = $style;
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'];
4326 return $fontdata;
4328 // get specified font directory (if any)
4329 $fontdir = false;
4330 if (!TCPDF_STATIC::empty_string($fontfile)) {
4331 $fontdir = dirname($fontfile);
4332 if (TCPDF_STATIC::empty_string($fontdir) OR ($fontdir == '.')) {
4333 $fontdir = '';
4334 } else {
4335 $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))) {
4354 $type=null;
4355 $name=null;
4356 $desc=null;
4357 $up=-null;
4358 $ut=null;
4359 $cw=null;
4360 $cbbox=null;
4361 $dw=null;
4362 $enc=null;
4363 $cidinfo=null;
4364 $file=null;
4365 $ctg=null;
4366 $diff=null;
4367 $originalsize=null;
4368 $size1=null;
4369 $size2=null;
4370 include($fontfile);
4371 } else {
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)) {
4380 $file = '';
4382 if (!isset($enc) OR TCPDF_STATIC::empty_string($enc)) {
4383 $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)) {
4390 $ctg = '';
4392 if (!isset($desc) OR TCPDF_STATIC::empty_string($desc)) {
4393 $desc = array();
4395 if (!isset($up) OR TCPDF_STATIC::empty_string($up)) {
4396 $up = -100;
4398 if (!isset($ut) OR TCPDF_STATIC::empty_string($ut)) {
4399 $ut = 50;
4401 if (!isset($cw) OR TCPDF_STATIC::empty_string($cw)) {
4402 $cw = array();
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])) {
4409 $dw = $cw[32];
4410 } else {
4411 $dw = 600;
4414 ++$this->numfonts;
4415 if ($type == 'core') {
4416 $name = $this->CoreFonts[$fontkey];
4417 $subset = false;
4418 } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
4419 $subset = false;
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!');
4426 } else {
4427 $this->Error('Unknow font type: '.$type.'');
4429 // set name if unset
4430 if (!isset($name) OR empty($name)) {
4431 $name = $fontkey;
4433 // create artificial font style variations if missing (only works with non-embedded fonts)
4434 if (($type != 'core') AND $missing_style) {
4435 // style variations
4436 $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
4437 $name .= $styles[$bistyle];
4438 // artificial bold
4439 if (strpos($bistyle, 'B') !== false) {
4440 if (isset($desc['StemV'])) {
4441 // from normal to bold
4442 $desc['StemV'] = round($desc['StemV'] * 1.75);
4443 } else {
4444 // bold
4445 $desc['StemV'] = 123;
4448 // artificial italic
4449 if (strpos($bistyle, 'I') !== false) {
4450 if (isset($desc['ItalicAngle'])) {
4451 $desc['ItalicAngle'] -= 11;
4452 } else {
4453 $desc['ItalicAngle'] = -11;
4455 if (isset($desc['Flags'])) {
4456 $desc['Flags'] |= 64; //bit 7
4457 } else {
4458 $desc['Flags'] = 64;
4462 // check if the array of characters bounding boxes is defined
4463 if (!isset($cbbox)) {
4464 $cbbox = array();
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
4475 $d = 0;
4476 $nb = count($this->diffs);
4477 for ($i=1; $i <= $nb; ++$i) {
4478 if ($this->diffs[$i] == $diff) {
4479 $d = $i;
4480 break;
4483 if ($d == 0) {
4484 $d = $nb + 1;
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));
4496 } else {
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;
4504 return $fontdata;
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
4520 * @public
4521 * @since 1.0
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;
4529 if ($size < 0) {
4530 $size = 0;
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.
4548 * @public
4549 * @since 1.0
4550 * @see SetFont()
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);
4562 } else {
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)) {
4572 // core font
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.
4589 * @return array
4590 * @public
4591 * @since 5.9.152 (2012-03-23)
4593 public function getFontBBox() {
4594 $fbbox = array();
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);
4598 } else {
4599 // Find max width
4600 if (isset($this->CurrentFont['desc']['MaxWidth'])) {
4601 $maxw = $this->getAbsFontMeasure(intval($this->CurrentFont['desc']['MaxWidth']));
4602 } else {
4603 $maxw = 0;
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);
4616 if ($maxw == 0) {
4617 $maxw = 600;
4619 $maxw = $this->getAbsFontMeasure($maxw);
4621 $fbbox = array(0, (0 - $this->FontDescent), $maxw, $this->FontAscent);
4623 return $fbbox;
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) {
4643 $c = intval($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);
4652 return false;
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
4661 * @public
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);
4670 } else {
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
4682 * @public
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);
4691 } else {
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.
4703 * @public
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);
4710 $char = $char[0];
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.
4730 * @public
4731 * @since 5.9.153 (2012-03-28)
4733 public function replaceMissingChars($text, $font='', $style='', $subs=array()) {
4734 if (empty($subs)) {
4735 return $text;
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])) {
4751 $uniarr[$k] = $s;
4752 break;
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.
4767 * @public
4768 * @since 4.5.025
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().
4777 * @public
4778 * @since 1.5
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);
4785 return $n;
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.
4793 * @public
4794 * @since 1.5
4795 * @see AddLink()
4797 public function setLink($link, $y=0, $page=-1) {
4798 $fixed = false;
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
4802 $fixed = true;
4804 if ($page < 0) {
4805 $page = $this->page;
4807 if ($y == -1) {
4808 $y = $this->y;
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
4822 * @public
4823 * @since 1.5
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
4840 * @public
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);
4847 return;
4849 if ($x === '') {
4850 $x = $this->x;
4852 if ($y === '') {
4853 $y = $this->y;
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'])) {
4864 $x = $x * $this->k;
4865 $y = ($this->h - $y) * $this->k;
4866 $w = $w * $this->k;
4867 $h = $h * $this->k;
4868 // top left
4869 $xt = $x;
4870 $yt = $y;
4871 $x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4872 $y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4873 // top right
4874 $xt = $x + $w;
4875 $yt = $y;
4876 $x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4877 $y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4878 // bottom left
4879 $xt = $x;
4880 $yt = $y - $h;
4881 $x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
4882 $y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
4883 // bottom right
4884 $xt = $x + $w;
4885 $yt = $y - $h;
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;
4893 $x = $x / $this->k;
4894 $y = $this->h - ($y / $this->k);
4899 if ($this->page <= 0) {
4900 $page = 1;
4901 } else {
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)
4930 * @protected
4931 * @see Annotation()
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
4936 return;
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);
4943 if ($rawsize > 0) {
4944 // update name tree
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';
4953 $this->_out($out);
4954 // embedded file object
4955 $filter = '';
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';
4970 $this->_out($out);
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.
4995 * @public
4996 * @since 1.0
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.
5014 * @return bool
5015 * @public
5016 * @since 1.4
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) {
5026 // add a new page
5027 $this->AddPage();
5028 // set first column
5029 $this->selectColumn(0);
5031 // avoid page breaking from checkPageBreak()
5032 return false;
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)
5044 * @protected
5046 protected function checkPageBreak($h=0, $y=null, $addpage=true) {
5047 if (TCPDF_STATIC::empty_string($y)) {
5048 $y = $this->y;
5050 $current_page = $this->page;
5051 if ((($y + $h) > $this->PageBreakTrigger) AND ($this->inPageBody()) AND ($this->AcceptPageBreak())) {
5052 if ($addpage) {
5053 //Automatic page break
5054 $x = $this->x;
5055 $this->AddPage($this->CurOrientation);
5056 $this->y = $this->tMargin;
5057 $oldpage = $this->page - 1;
5058 if ($this->rtl) {
5059 if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
5060 $this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
5061 } else {
5062 $this->x = $x;
5064 } else {
5065 if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
5066 $this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
5067 } else {
5068 $this->x = $x;
5072 return true;
5074 if ($current_page != $this->page) {
5075 // account for columns mode
5076 return true;
5078 return false;
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>
5096 * @public
5097 * @since 1.0
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']) {
5113 // save data
5114 $x = $this->x;
5115 $y = $this->y;
5116 $bc = $this->bgcolor;
5117 $fc = $this->fgcolor;
5118 $sc = $this->strokecolor;
5119 $alpha = $this->alpha;
5120 // print shadow
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));
5132 //restore data
5133 $this->x = $x;
5134 $this->y = $y;
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
5165 * @protected
5166 * @since 1.0
5167 * @see Cell()
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;
5184 $k = $this->k;
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);
5187 if ($this->rtl) {
5188 $x = $this->x - $this->cell_margin['R'];
5189 } else {
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
5196 switch ($calign) {
5197 case 'A': {
5198 // font top
5199 switch ($valign) {
5200 case 'T': {
5201 // top
5202 $y -= $this->cell_padding['T'];
5203 break;
5205 case 'B': {
5206 // bottom
5207 $y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
5208 break;
5210 default:
5211 case 'C':
5212 case 'M': {
5213 // center
5214 $y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
5215 break;
5218 break;
5220 case 'L': {
5221 // font baseline
5222 switch ($valign) {
5223 case 'T': {
5224 // top
5225 $y -= ($this->cell_padding['T'] + $this->FontAscent);
5226 break;
5228 case 'B': {
5229 // bottom
5230 $y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
5231 break;
5233 default:
5234 case 'C':
5235 case 'M': {
5236 // center
5237 $y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
5238 break;
5241 break;
5243 case 'D': {
5244 // font bottom
5245 switch ($valign) {
5246 case 'T': {
5247 // top
5248 $y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
5249 break;
5251 case 'B': {
5252 // bottom
5253 $y -= ($h - $this->cell_padding['B']);
5254 break;
5256 default:
5257 case 'C':
5258 case 'M': {
5259 // center
5260 $y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
5261 break;
5264 break;
5266 case 'B': {
5267 // cell bottom
5268 $y -= $h;
5269 break;
5271 case 'C':
5272 case 'M': {
5273 // cell center
5274 $y -= ($h / 2);
5275 break;
5277 default:
5278 case 'T': {
5279 // cell top
5280 break;
5283 // text vertical alignment
5284 switch ($valign) {
5285 case 'T': {
5286 // top
5287 $yt = $y + $this->cell_padding['T'];
5288 break;
5290 case 'B': {
5291 // bottom
5292 $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
5293 break;
5295 default:
5296 case 'C':
5297 case 'M': {
5298 // center
5299 $yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
5300 break;
5303 $basefonty = $yt + $this->FontAscent;
5304 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5305 if ($this->rtl) {
5306 $w = $x - $this->lMargin;
5307 } else {
5308 $w = $this->w - $this->rMargin - $x;
5311 $s = '';
5312 // fill and borders
5313 if (is_string($border) AND (strlen($border) == 4)) {
5314 // full border
5315 $border = 1;
5317 if ($fill OR ($border == 1)) {
5318 if ($fill) {
5319 $op = ($border == 1) ? 'B' : 'f';
5320 } else {
5321 $op = 'S';
5323 if ($this->rtl) {
5324 $xk = (($x - $w) * $k);
5325 } else {
5326 $xk = ($x * $k);
5328 $s .= sprintf('%F %F %F %F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
5330 // draw borders
5331 $s .= $this->getCellBorder($x, $y, $w, $h, $border);
5332 if ($txt != '') {
5333 $txt2 = $txt;
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);
5337 } else {
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)) {
5342 // number of chars
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);
5354 $output = array();
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) {
5363 // sara um
5364 if (in_array($ch1, $longtail)) {
5365 // tonemark at upper left
5366 $output[] = $this->replaceChar($ch0, (0xf713 + $ch0 - 0x0e48));
5367 } else {
5368 // tonemark at upper right (normal position)
5369 $output[] = $ch0;
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));
5378 } else {
5379 // tonemark at upper right (normal position)
5380 $output[] = $ch0;
5382 } else {
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)) {
5389 $output[] = 0xf711;
5390 $this->CurrentFont['subsetchars'][0xf711] = true;
5391 $output[] = 0x0e32;
5392 $this->CurrentFont['subsetchars'][0x0e32] = true;
5393 } else {
5394 $output[] = $ch0;
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)) {
5401 // lower left
5402 $output[] = $this->replaceChar($ch0, (0xf701 + $ch0 - 0x0e34));
5403 } elseif ($ch0 == 0x0e47) {
5404 // lower left mai tai koo
5405 $output[] = $this->replaceChar($ch0, 0xf712);
5406 } else {
5407 // normal character
5408 $output[] = $ch0;
5410 } elseif (in_array($ch1, $lowtail) AND in_array($ch0, $lowvowel)) {
5411 // lower vowel
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);
5419 } else {
5420 $output[] = $ch0;
5422 } else {
5423 // non-thai character
5424 $output[] = $unicode[$i];
5427 $unicode = $output;
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);
5437 $width = $txwidth;
5438 // check for stretch mode
5439 if ($stretch > 0) {
5440 // calculate ratio between cell width and text width
5441 if ($width <= 0) {
5442 $ratio = 1;
5443 } else {
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
5449 if ($stretch > 2) {
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));
5452 } else {
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'];
5458 // reset alignment
5459 $align = '';
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.' ';
5473 // rendering mode
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));
5477 // Justification
5478 $spacewidth = 0;
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;
5492 } else {
5493 // get string width
5494 $width = $txwidth;
5495 // new space width
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);
5501 // set word spacing
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);
5508 switch ($align) {
5509 case 'C': {
5510 $dx = ($w - $width) / 2;
5511 break;
5513 case 'R': {
5514 if ($this->rtl) {
5515 $dx = $this->cell_padding['R'];
5516 } else {
5517 $dx = $w - $width - $this->cell_padding['R'];
5519 break;
5521 case 'L': {
5522 if ($this->rtl) {
5523 $dx = $w - $width - $this->cell_padding['L'];
5524 } else {
5525 $dx = $this->cell_padding['L'];
5527 break;
5529 case 'J':
5530 default: {
5531 if ($this->rtl) {
5532 $dx = $this->cell_padding['R'];
5533 } else {
5534 $dx = $this->cell_padding['L'];
5536 break;
5539 if ($this->rtl) {
5540 $xdx = $x - $dx - $width;
5541 } else {
5542 $xdx = $x + $dx;
5544 $xdk = $xdx * $k;
5545 // print text
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) {
5554 // x space to skip
5555 if ($spacewidth != 0) {
5556 // justification shift
5557 $xshift += (count(array_keys($uniarr, 32)) * $spw);
5559 $xshift += $this->GetArrStringWidth($uniarr); // + shift justification
5560 } else {
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)) {
5578 $s .= ' Q';
5580 if ($link) {
5581 $this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
5584 // output cell
5585 if ($s) {
5586 // output cell
5587 $rs .= $s;
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;
5604 $this->lasth = $h;
5605 if ($ln > 0) {
5606 //Go to the beginning of the next line
5607 $this->y = $y + $h + $this->cell_margin['B'];
5608 if ($ln == 1) {
5609 if ($this->rtl) {
5610 $this->x = $this->w - $this->rMargin;
5611 } else {
5612 $this->x = $this->lMargin;
5615 } else {
5616 // go left or right by case
5617 if ($this->rtl) {
5618 $this->x = $x - $w - $this->cell_margin['L'];
5619 } else {
5620 $this->x = $x + $w + $this->cell_margin['R'];
5623 $gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
5624 $rs = $gstyles.$rs;
5625 $this->cell_padding = $prev_cell_padding;
5626 $this->cell_margin = $prev_cell_margin;
5627 return $rs;
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
5635 * @protected
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
5643 return $newchar;
5645 // return the old char
5646 return $oldchar;
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
5657 * @protected
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
5663 if (empty($brd)) {
5664 return $s;
5666 if ($brd == 1) {
5667 $brd = array('LRTB' => true);
5669 // calculate coordinates for border
5670 $k = $this->k;
5671 if ($this->rtl) {
5672 $xeL = ($x - $w) * $k;
5673 $xeR = $x * $k;
5674 } else {
5675 $xeL = $x * $k;
5676 $xeR = ($x + $w) * $k;
5678 $yeL = (($this->h - ($y + $h)) * $k);
5679 $yeT = (($this->h - $y) * $k);
5680 $xeT = $xeL;
5681 $xeB = $xeR;
5682 $yeR = $yeT;
5683 $yeB = $yeL;
5684 if (is_string($brd)) {
5685 // convert string to array
5686 $slen = strlen($brd);
5687 $newbrd = array();
5688 for ($i = 0; $i < $slen; ++$i) {
5689 $newbrd[$brd[$i]] = array('cap' => 'square', 'join' => 'miter');
5691 $brd = $newbrd;
5693 if (isset($brd['mode'])) {
5694 $mode = $brd['mode'];
5695 unset($brd['mode']);
5696 } else {
5697 $mode = 'normal';
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";
5705 switch ($mode) {
5706 case 'ext': {
5707 $off = (($this->LineWidth / 2) * $k);
5708 $xL = $xeL - $off;
5709 $xR = $xeR + $off;
5710 $yT = $yeT + $off;
5711 $yL = $yeL - $off;
5712 $xT = $xL;
5713 $xB = $xR;
5714 $yR = $yT;
5715 $yB = $yL;
5716 $w += $this->LineWidth;
5717 $h += $this->LineWidth;
5718 break;
5720 case 'int': {
5721 $off = ($this->LineWidth / 2) * $k;
5722 $xL = $xeL + $off;
5723 $xR = $xeR - $off;
5724 $yT = $yeT - $off;
5725 $yL = $yeL + $off;
5726 $xT = $xL;
5727 $xB = $xR;
5728 $yR = $yT;
5729 $yB = $yL;
5730 $w -= $this->LineWidth;
5731 $h -= $this->LineWidth;
5732 break;
5734 case 'normal':
5735 default: {
5736 $xL = $xeL;
5737 $xT = $xeT;
5738 $xB = $xeB;
5739 $xR = $xeR;
5740 $yL = $yeL;
5741 $yT = $yeT;
5742 $yB = $yeB;
5743 $yR = $yeR;
5744 break;
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);
5756 $s .= 'S ';
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);
5762 $s .= 'S ';
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);
5768 $s .= 'S ';
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);
5774 $s .= 'S ';
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);
5781 $s .= 'S ';
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);
5786 $s .= 'S ';
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);
5791 $s .= 'S ';
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);
5796 $s .= 'S ';
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);
5800 $s .= 'S ';
5801 $s .= sprintf('%F %F m ', $xR, $yR);
5802 $s .= sprintf('%F %F l ', $xB, $yB);
5803 $s .= 'S ';
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);
5807 $s .= 'S ';
5808 $s .= sprintf('%F %F m ', $xB, $yB);
5809 $s .= sprintf('%F %F l ', $xL, $yL);
5810 $s .= 'S ';
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);
5816 $s .= 'S ';
5817 } elseif (strpos($border,'T') !== false) { // T
5818 $s .= sprintf('%F %F m ', $xT, $yT);
5819 $s .= sprintf('%F %F l ', $xR, $yR);
5820 $s .= 'S ';
5821 } elseif (strpos($border,'R') !== false) { // R
5822 $s .= sprintf('%F %F m ', $xR, $yR);
5823 $s .= sprintf('%F %F l ', $xB, $yB);
5824 $s .= 'S ';
5825 } elseif (strpos($border,'B') !== false) { // B
5826 $s .= sprintf('%F %F m ', $xB, $yB);
5827 $s .= sprintf('%F %F l ', $xL, $yL);
5828 $s .= 'S ';
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";
5836 return $s;
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.
5860 * @public
5861 * @since 1.3
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) {
5875 // reset row height
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
5881 $y = $this->GetY();
5882 $resth = 0;
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
5887 $h = $newh;
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)) {
5894 $this->setX($x);
5895 } else {
5896 $x = $this->GetX();
5898 // check page for no-write regions and adapt page margins if necessary
5899 list($x, $y) = $this->checkPageRegions(0, $x, $y);
5900 // apply margins
5901 $oy = $y + $mc_margin['T'];
5902 if ($this->rtl) {
5903 $ox = ($this->w - $x - $mc_margin['R']);
5904 } else {
5905 $ox = ($x + $mc_margin['L']);
5907 $this->x = $ox;
5908 $this->y = $oy;
5909 // set width
5910 if (TCPDF_STATIC::empty_string($w) OR ($w <= 0)) {
5911 if ($this->rtl) {
5912 $w = ($this->x - $this->lMargin - $mc_margin['L']);
5913 } else {
5914 $w = ($this->w - $this->x - $this->rMargin - $mc_margin['R']);
5917 // store original margin values
5918 $lMargin = $this->lMargin;
5919 $rMargin = $this->rMargin;
5920 if ($this->rtl) {
5921 $this->rMargin = ($this->w - $this->x);
5922 $this->lMargin = ($this->x - $w);
5923 } else {
5924 $this->lMargin = ($this->x);
5925 $this->rMargin = ($this->w - $this->x - $w);
5927 $this->clMargin = $this->lMargin;
5928 $this->crMargin = $this->rMargin;
5929 if ($autopadding) {
5930 // add top padding
5931 $this->y += $mc_padding['T'];
5933 if ($ishtml) { // ******* Write HTML text
5934 $this->writeHTML($txt, true, false, $reseth, true, $align);
5935 $nl = 1;
5936 } else { // ******* Write simple text
5937 $prev_FontSizePt = $this->FontSizePt;
5938 if ($fitcell) {
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
5944 if ($maxh > 0) {
5945 // get text height
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)
5949 $fmin = 1;
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);
5959 if ($diff >= 0) {
5960 if ($diff <= $diff_epsilon) {
5961 break;
5963 $fmin = $fmid;
5964 } else {
5965 $fmax = $fmid;
5967 --$maxit;
5969 if ($maxit < 0) {
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);
5974 } else {
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);
5990 if ($fitcell) {
5991 // restore font size
5992 $this->setFontSize($prev_FontSizePt);
5995 if ($autopadding) {
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;
6003 if ($resth > 0) {
6004 $skip = ($endpage - $startpage);
6005 $tmpresth = $resth;
6006 while ($tmpresth > 0) {
6007 if ($skip <= 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);
6013 } else {
6014 $tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
6016 --$skip;
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;
6029 // get border modes
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
6035 $ccode = '';
6036 $this->setPage($page);
6037 if ($this->num_columns < 2) {
6038 // single-column mode
6039 $this->setX($x);
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) {
6051 // single page
6052 for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
6053 if ($column != $this->current_column) {
6054 $this->selectColumn($column);
6056 if ($this->rtl) {
6057 $this->x -= $mc_margin['R'];
6058 } else {
6059 $this->x += $mc_margin['L'];
6061 if ($startcolumn == $endcolumn) { // single column
6062 $cborder = $border;
6063 $h = max($h, ($currentY - $oy));
6064 $this->y = $oy;
6065 } elseif ($column == $startcolumn) { // first column
6066 $cborder = $border_start;
6067 $this->y = $oy;
6068 $h = $this->h - $this->y - $this->bMargin;
6069 } elseif ($column == $endcolumn) { // end column
6070 $cborder = $border_end;
6071 $h = $currentY - $this->y;
6072 if ($resth > $h) {
6073 $h = $resth;
6075 } else { // middle column
6076 $cborder = $border_middle;
6077 $h = $this->h - $this->y - $this->bMargin;
6078 $resth -= $h;
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);
6087 if ($this->rtl) {
6088 $this->x -= $mc_margin['R'];
6089 } else {
6090 $this->x += $mc_margin['L'];
6092 if ($column == $startcolumn) { // first column
6093 $cborder = $border_start;
6094 $this->y = $oy;
6095 $h = $this->h - $this->y - $this->bMargin;
6096 } else { // middle column
6097 $cborder = $border_middle;
6098 $h = $this->h - $this->y - $this->bMargin;
6099 $resth -= $h;
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);
6108 if ($this->rtl) {
6109 $this->x -= $mc_margin['R'];
6110 } else {
6111 $this->x += $mc_margin['L'];
6113 if ($column == $endcolumn) {
6114 // end column
6115 $cborder = $border_end;
6116 $h = $currentY - $this->y;
6117 if ($resth > $h) {
6118 $h = $resth;
6120 } else {
6121 // middle column
6122 $cborder = $border_middle;
6123 $h = $this->h - $this->y - $this->bMargin;
6124 $resth -= $h;
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);
6131 if ($this->rtl) {
6132 $this->x -= $mc_margin['R'];
6133 } else {
6134 $this->x += $mc_margin['L'];
6136 $cborder = $border_middle;
6137 $h = $this->h - $this->y - $this->bMargin;
6138 $resth -= $h;
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;
6151 } else {
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;
6159 } else {
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;
6167 } else {
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();
6185 } else {
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;
6199 if ($ln > 0) {
6200 //Go to the beginning of the next line
6201 $this->setY($currentY + $mc_margin['B']);
6202 if ($ln == 2) {
6203 $this->setX($x + $w + $mc_margin['L'] + $mc_margin['R']);
6205 } else {
6206 // go left or right by case
6207 $this->setPage($startpage);
6208 $this->y = $y;
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;
6216 return $nl;
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
6229 * @public
6230 * @since 4.5.011
6232 public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding=null, $border=0) {
6233 if ($txt === NULL) {
6234 return 0;
6236 if ($txt === '') {
6237 // empty string
6238 return 1;
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)) {
6248 if ($this->rtl) {
6249 $w = $this->x - $this->lMargin;
6250 } else {
6251 $w = $this->w - $this->rMargin - $this->x;
6254 $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
6255 if ($reseth) {
6256 // reset row height
6257 $this->resetLastH();
6259 $lines = 1;
6260 $sum = 0;
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) {
6266 $c = $chars[$i];
6267 $charWidth = $charsWidth[$i];
6268 if (($c != 160)
6269 AND (($c == 173)
6270 OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6271 OR (($c == 45)
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)) {
6281 ++$lines;
6282 if ($c == 10) {
6283 $lastSeparator = -1;
6284 $sum = 0;
6285 } elseif ($lastSeparator != -1) {
6286 $i = $lastSeparator;
6287 $lastSeparator = -1;
6288 $sum = 0;
6289 } else {
6290 $sum = $charWidth;
6292 } else {
6293 $sum += $charWidth;
6296 if ($chars[($length - 1)] == 10) {
6297 --$lines;
6299 $this->cell_padding = $prev_cell_padding;
6300 $this->lasth = $prev_lasth;
6301 return $lines;
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:
6307 * @pre
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 * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
6317 * // get the new Y
6318 * $end_y = $pdf->GetY();
6319 * $end_page = $pdf->getPage();
6320 * // calculate height
6321 * $height = 0;
6322 * if ($end_page == $start_page) {
6323 * $height = $end_y - $start_y;
6324 * } else {
6325 * for ($page=$start_page; $page <= $end_page; ++$page) {
6326 * $this->setPage($page);
6327 * if ($page == $start_page) {
6328 * // first page
6329 * $height += $this->h - $start_y - $this->bMargin;
6330 * } elseif ($page == $end_page) {
6331 * // last page
6332 * $height += $end_y - $this->tMargin;
6333 * } else {
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
6349 * @public
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;
6363 return $height;
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.
6381 * @public
6382 * @since 1.5
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) {
6388 // fix empty text
6389 $txt = ' ';
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)) {
6399 $arabic = true;
6400 } else {
6401 $arabic = false;
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)) {
6405 $rtlmode = true;
6406 } else {
6407 $rtlmode = false;
6409 // get a char width
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);
6426 // page width
6427 $pw = $w = $this->w - $this->lMargin - $this->rMargin;
6428 // calculate remaining line width ($w)
6429 if ($this->rtl) {
6430 $w = $this->x - $this->lMargin;
6431 } else {
6432 $w = $this->w - $this->rMargin - $this->x;
6434 // max column width
6435 $wmax = ($w - $wadj);
6436 if (!$firstline) {
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
6441 return '';
6443 // minimum row height
6444 $row_height = max($h, $this->getCellHeight($this->FontSize));
6445 // max Y
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
6456 $linebreak = false;
6457 $pc = 0; // previous character
6458 // for each character
6459 while ($i < $nb) {
6460 if (($maxh > 0) AND ($this->y > $maxy) ) {
6461 break;
6463 //Get the current character
6464 $c = $chars[$i];
6465 if ($c == 10) { // 10 = "\n" = new line
6466 //Explicit line break
6467 if ($align == 'J') {
6468 if ($this->rtl) {
6469 $talign = 'R';
6470 } else {
6471 $talign = 'L';
6473 } else {
6474 $talign = $align;
6476 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6477 if ($firstline) {
6478 $startx = $this->x;
6479 $tmparr = array_slice($chars, $j, ($i - $j));
6480 if ($rtlmode) {
6481 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6483 $linew = $this->GetArrStringWidth($tmparr);
6484 unset($tmparr);
6485 if ($this->rtl) {
6486 $this->endlinex = $startx - $linew;
6487 } else {
6488 $this->endlinex = $startx + $linew;
6490 $w = $linew;
6491 $tmpcellpadding = $this->cell_padding;
6492 if ($maxh == 0) {
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);
6503 unset($tmpstr);
6504 if ($firstline) {
6505 $this->cell_padding = $tmpcellpadding;
6506 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6508 ++$nl;
6509 $j = $i + 1;
6510 $l = 0;
6511 $sep = -1;
6512 $prevsep = $sep;
6513 $shy = false;
6514 // account for margin changes
6515 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6516 if ($this->AcceptPageBreak())
6518 if ($this->rtl) {
6519 $this->x -= $margin['R'];
6520 } else {
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']);
6529 } else {
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.
6535 if (($c != 160)
6536 AND (($c == 173)
6537 OR preg_match($this->re_spaces, TCPDF_FONTS::unichr($c, $this->isunicode))
6538 OR (($c == 45)
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
6546 $prevsep = $sep;
6547 $sep = $i;
6548 // check if is a SHY
6549 if (($c == 173) OR ($c == 45)) {
6550 $prevshy = $shy;
6551 $shy = true;
6552 if ($pc == 45) {
6553 $tmp_shy_replacement_width = 0;
6554 $tmp_shy_replacement_char = '';
6555 } else {
6556 $tmp_shy_replacement_width = $shy_replacement_width;
6557 $tmp_shy_replacement_char = $shy_replacement_char;
6559 } else {
6560 $shy = false;
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));
6568 } else {
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)) {
6573 $sep = $prevsep;
6574 $shy = $prevshy;
6576 // we have reached the end of column
6577 if ($sep == -1) {
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);
6583 $linebreak = true;
6584 if ($firstline) {
6585 return (TCPDF_FONTS::UniArrSubString($uchars, $j));
6587 } else {
6588 // truncate the word because do not fit on column
6589 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $i);
6590 if ($firstline) {
6591 $startx = $this->x;
6592 $tmparr = array_slice($chars, $j, ($i - $j));
6593 if ($rtlmode) {
6594 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6596 $linew = $this->GetArrStringWidth($tmparr);
6597 unset($tmparr);
6598 if ($this->rtl) {
6599 $this->endlinex = $startx - $linew;
6600 } else {
6601 $this->endlinex = $startx + $linew;
6603 $w = $linew;
6604 $tmpcellpadding = $this->cell_padding;
6605 if ($maxh == 0) {
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);
6613 unset($tmpstr);
6614 if ($firstline) {
6615 $this->cell_padding = $tmpcellpadding;
6616 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6618 $j = $i;
6619 --$i;
6621 } else {
6622 // word wrapping
6623 if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
6624 $endspace = 1;
6625 } else {
6626 $endspace = 0;
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);
6634 if ($firstline) {
6635 $startx = $this->x;
6636 $tmparr = array_slice($chars, $j, ($i - $j));
6637 if ($rtlmode) {
6638 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6640 $linew = $this->GetArrStringWidth($tmparr);
6641 unset($tmparr);
6642 if ($this->rtl) {
6643 $this->endlinex = ($startx - $linew);
6644 } else {
6645 $this->endlinex = ($startx + $linew);
6647 $w = $linew;
6648 $tmpcellpadding = $this->cell_padding;
6649 if ($maxh == 0) {
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);
6657 unset($tmpstr);
6658 if ($firstline) {
6659 $this->cell_padding = $tmpcellpadding;
6660 return (TCPDF_FONTS::UniArrSubString($uchars, $i));
6662 $j = $i;
6663 --$i;
6664 } else {
6665 // word wrapping
6666 if ($shy) {
6667 // add hypen (minus symbol) at the end of the line
6668 $shy_width = $tmp_shy_replacement_width;
6669 if ($this->rtl) {
6670 $shy_char_left = $tmp_shy_replacement_char;
6671 $shy_char_right = '';
6672 } else {
6673 $shy_char_left = '';
6674 $shy_char_right = $tmp_shy_replacement_char;
6676 } else {
6677 $shy_width = 0;
6678 $shy_char_left = '';
6679 $shy_char_right = '';
6681 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, ($sep + $endspace));
6682 if ($firstline) {
6683 $startx = $this->x;
6684 $tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
6685 if ($rtlmode) {
6686 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6688 $linew = $this->GetArrStringWidth($tmparr);
6689 unset($tmparr);
6690 if ($this->rtl) {
6691 $this->endlinex = $startx - $linew - $shy_width;
6692 } else {
6693 $this->endlinex = $startx + $linew + $shy_width;
6695 $w = $linew;
6696 $tmpcellpadding = $this->cell_padding;
6697 if ($maxh == 0) {
6698 $this->setCellPadding(0);
6701 // print the line
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);
6706 unset($tmpstr);
6707 if ($firstline) {
6708 if ($chars[$sep] == 45) {
6709 $endspace += 1;
6711 // return the remaining text
6712 $this->cell_padding = $tmpcellpadding;
6713 return (TCPDF_FONTS::UniArrSubString($uchars, ($sep + $endspace)));
6715 $i = $sep;
6716 $sep = -1;
6717 $shy = false;
6718 $j = ($i + 1);
6721 // account for margin changes
6722 if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND ($this->inPageBody())) {
6723 if ($this->AcceptPageBreak())
6725 if ($this->rtl) {
6726 $this->x -= $margin['R'];
6727 } else {
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'];
6736 if ($linebreak) {
6737 $linebreak = false;
6738 } else {
6739 ++$nl;
6740 $l = 0;
6744 // save last character
6745 $pc = $c;
6746 ++$i;
6747 } // end while i < nb
6748 // print last substring (if any)
6749 if ($l > 0) {
6750 switch ($align) {
6751 case 'J':
6752 case 'C': {
6753 break;
6755 case 'L': {
6756 if (!$this->rtl) {
6757 $w = $l;
6759 break;
6761 case 'R': {
6762 if ($this->rtl) {
6763 $w = $l;
6765 break;
6767 default: {
6768 $w = $l;
6769 break;
6772 $tmpstr = TCPDF_FONTS::UniArrSubString($uchars, $j, $nb);
6773 if ($firstline) {
6774 $startx = $this->x;
6775 $tmparr = array_slice($chars, $j, ($nb - $j));
6776 if ($rtlmode) {
6777 $tmparr = TCPDF_FONTS::utf8Bidi($tmparr, $tmpstr, $this->tmprtl, $this->isunicode, $this->CurrentFont);
6779 $linew = $this->GetArrStringWidth($tmparr);
6780 unset($tmparr);
6781 if ($this->rtl) {
6782 $this->endlinex = $startx - $linew;
6783 } else {
6784 $this->endlinex = $startx + $linew;
6786 $w = $linew;
6787 $tmpcellpadding = $this->cell_padding;
6788 if ($maxh == 0) {
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);
6796 unset($tmpstr);
6797 if ($firstline) {
6798 $this->cell_padding = $tmpcellpadding;
6799 return (TCPDF_FONTS::UniArrSubString($uchars, $nb));
6801 ++$nl;
6803 if ($firstline) {
6804 return '';
6806 return $nl;
6810 * Returns the remaining width between the current position and margins.
6811 * @return float Return the remaining width
6812 * @protected
6814 protected function getRemainingWidth() {
6815 list($this->x, $this->y) = $this->checkPageRegions(0, $this->x, $this->y);
6816 if ($this->rtl) {
6817 return ($this->x - $this->lMargin);
6818 } else {
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)
6831 * @protected
6832 * @since 5.5.009 (2010-07-05)
6834 protected function fitBlock($w, $h, $x, $y, $fitonpage=false) {
6835 if ($w <= 0) {
6836 // set maximum width
6837 $w = ($this->w - $this->lMargin - $this->rMargin);
6838 if ($w <= 0) {
6839 $w = 1;
6842 if ($h <= 0) {
6843 // set maximum height
6844 $h = ($this->PageBreakTrigger - $this->tMargin);
6845 if ($h <= 0) {
6846 $h = 1;
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
6857 if ($fitonpage) {
6858 $maxw = ($this->w - $this->lMargin - $this->rMargin);
6859 if ($w > $maxw) {
6860 $w = $maxw;
6861 $h = ($w / $ratio_wh);
6865 // Check whether we need a new page or new column first as this does not fit
6866 $prev_x = $this->x;
6867 $prev_y = $this->y;
6868 if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
6869 $y = $this->y;
6870 if ($this->rtl) {
6871 $x += ($prev_x - $this->x);
6872 } else {
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
6878 if ($fitonpage) {
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
6928 * @public
6929 * @since 1.1
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) {
6933 return false;
6935 if (TCPDF_STATIC::empty_string($x)) {
6936 $x = $this->x;
6938 if (TCPDF_STATIC::empty_string($y)) {
6939 $y = $this->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
6944 $imsize = FALSE;
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
6948 if (empty($file)) {
6949 return false;
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);
6959 $exurl = $file;
6961 // check if file exist and it is valid
6962 if (!@$this->fileExists($file)) {
6963 return false;
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');
6976 if (!$fp) {
6977 $this->Error('Unable to write file: '.$file);
6979 fwrite($fp, $imgdata);
6980 fclose($fp);
6981 unset($imgdata);
6982 $imsize = @getimagesize($file);
6983 if ($imsize === FALSE) {
6984 unlink($file);
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);
6994 } else {
6995 $this->Error('[Image] Unable to get the size of the image: '.$file);
6998 // file hash
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
7014 $fitbox = '--';
7016 // scale image dimensions proportionally to fit within the ($w, $h) box
7017 if ((($w * $pixh) / ($h * $pixw)) < 1) {
7018 // store current height
7019 $oldh = $h;
7020 // calculate new height
7021 $h = $w * $pixh / $pixw;
7022 // height difference
7023 $hdiff = ($oldh - $h);
7024 // vertical alignment
7025 switch (strtoupper($fitbox[1])) {
7026 case 'T': {
7027 break;
7029 case 'M': {
7030 $y += ($hdiff / 2);
7031 break;
7033 case 'B': {
7034 $y += $hdiff;
7035 break;
7038 } else {
7039 // store current width
7040 $oldw = $w;
7041 // calculate new width
7042 $w = $h * $pixw / $pixh;
7043 // width difference
7044 $wdiff = ($oldw - $w);
7045 // horizontal alignment
7046 switch (strtoupper($fitbox[0])) {
7047 case 'L': {
7048 if ($this->rtl) {
7049 $x -= $wdiff;
7051 break;
7053 case 'C': {
7054 if ($this->rtl) {
7055 $x -= ($wdiff / 2);
7056 } else {
7057 $x += ($wdiff / 2);
7059 break;
7061 case 'R': {
7062 if (!$this->rtl) {
7063 $x += $wdiff;
7065 break;
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) {
7079 $resize = true;
7080 } elseif ($newsize >= $pixsize) {
7081 $resize = false;
7083 // check if image has been already added on document
7084 $newimage = true;
7085 if (in_array($file, $this->imagekeys)) {
7086 $newimage = false;
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))) {
7093 $newimage = true;
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))) {
7108 $newimage = true;
7109 } else {
7110 $newimage = false;
7111 // embed mask image
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);
7118 if ($newimage) {
7119 //First use of image, get info
7120 $type = strtolower($type);
7121 if ($type == '') {
7122 $type = TCPDF_IMAGES::getImageFileType($file, $imsize);
7123 } elseif ($type == 'jpg') {
7124 $type = 'jpeg';
7126 // Specific image handlers (defined on TCPDF_IMAGES CLASS)
7127 $mtd = '_parse'.$type;
7128 // GD image handler function
7129 $gdfunction = 'imagecreatefrom'.$type;
7130 $info = false;
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)) {
7140 try {
7141 // GD library
7142 $img = $gdfunction($file);
7143 if ($img !== false) {
7144 if ($resize) {
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);
7150 $img = $imgr;
7152 if (($type == 'gif') OR ($type == 'png')) {
7153 $info = TCPDF_IMAGES::_toPNG($img, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7154 } else {
7155 $info = TCPDF_IMAGES::_toJPEG($img, $this->jpeg_quality, TCPDF_STATIC::getObjFilename('img', $this->file_id));
7158 } catch(Exception $e) {
7159 $info = false;
7162 if (($info === false) AND extension_loaded('imagick')) {
7163 try {
7164 // ImageMagick library
7165 $img = new Imagick();
7166 if ($type == 'svg') {
7167 if ($file[0] === '@') {
7168 // image from string
7169 $svgimg = substr($file, 1);
7170 } else {
7171 // get SVG file content
7172 $svgimg = $this->getCachedFileContents($file);
7174 if ($svgimg !== FALSE) {
7175 // get width and height
7176 $regs = array();
7177 if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
7178 $svgtag = $regs[1];
7179 $tmp = array();
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);
7184 } else {
7185 $ow = $w;
7187 $tmp = array();
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);
7192 } else {
7193 $oh = $h;
7195 $tmp = array();
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);
7206 } else {
7207 $img->readImage($file);
7209 if ($resize) {
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);
7217 unlink($tempname);
7218 $img->destroy();
7219 } catch(Exception $e) {
7220 $info = false;
7223 if ($info === false) {
7224 // unable to process image
7225 return false;
7227 if ($ismask) {
7228 // force grayscale
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);
7242 // set alignment
7243 $this->img_rb_x = $x + $w;
7244 $this->img_rb_y = $y + $h;
7246 // set alignment
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;
7253 } else {
7254 $ximg = $this->rtl ? $x - $w : $x;
7257 if ($ismask OR $hidden) {
7258 // image is not displayed
7259 return $info['i'];
7261 $xkimg = $ximg * $this->k;
7262 if (!$alt) {
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)) {
7267 $bx = $this->x;
7268 $by = $this->y;
7269 $this->x = $ximg;
7270 if ($this->rtl) {
7271 $this->x += $w;
7273 $this->y = $y;
7274 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
7275 $this->x = $bx;
7276 $this->y = $by;
7278 if ($link) {
7279 $this->Link($ximg, $y, $w, $h, $link, 0);
7281 // set pointer to align the next text/objects
7282 switch($align) {
7283 case 'T': {
7284 $this->y = $y;
7285 $this->x = $this->img_rb_x;
7286 break;
7288 case 'M': {
7289 $this->y = $y + round($h/2);
7290 $this->x = $this->img_rb_x;
7291 break;
7293 case 'B': {
7294 $this->y = $this->img_rb_y;
7295 $this->x = $this->img_rb_x;
7296 break;
7298 case 'N': {
7299 $this->setY($this->img_rb_y);
7300 break;
7302 default:{
7303 break;
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'];
7311 return $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
7331 * @protected
7332 * @since 4.3.007 (2008-12-04)
7333 * @see Image()
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;
7344 $parsed = false;
7345 $parse_error = '';
7346 // ImageMagick extension
7347 if (($parsed === false) AND extension_loaded('imagick')) {
7348 try {
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);
7357 } else {
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);
7366 } else {
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);
7371 $parsed = true;
7372 } catch (Exception $e) {
7373 // Imagemagick fails, try with GD
7374 $parse_error = 'Imagick library error: '.$e->getMessage();
7377 // GD extension
7378 if (($parsed === false) AND function_exists('imagecreatefrompng')) {
7379 try {
7380 // generate images
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);
7403 $parsed = true;
7404 } catch (Exception $e) {
7405 // GD fails
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.');
7412 } else {
7413 $this->Error($parse_error);
7416 // embed mask image
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
7426 * @protected
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);
7434 // correct gamma
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
7450 * @public
7451 * @since 1.0
7452 * @see Cell()
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
7457 return;
7459 if ($cell) {
7460 if ($this->rtl) {
7461 $cellpadding = $this->cell_padding['R'];
7462 } else {
7463 $cellpadding = $this->cell_padding['L'];
7465 } else {
7466 $cellpadding = 0;
7468 if ($this->rtl) {
7469 $this->x = $this->w - $this->rMargin - $cellpadding;
7470 } else {
7471 $this->x = $this->lMargin + $cellpadding;
7473 if (TCPDF_STATIC::empty_string($h)) {
7474 $h = $this->lasth;
7476 $this->y += $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.
7483 * @return float
7484 * @public
7485 * @since 1.2
7486 * @see SetX(), GetY(), SetY()
7488 public function GetX() {
7489 //Get x position
7490 if ($this->rtl) {
7491 return ($this->w - $this->x);
7492 } else {
7493 return $this->x;
7498 * Returns the absolute X value of current position.
7499 * @return float
7500 * @public
7501 * @since 1.2
7502 * @see SetX(), GetY(), SetY()
7504 public function GetAbsX() {
7505 return $this->x;
7509 * Returns the ordinate of the current position.
7510 * @return float
7511 * @public
7512 * @since 1.0
7513 * @see SetY(), GetX(), SetX()
7515 public function GetY() {
7516 return $this->y;
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.
7524 * @public
7525 * @since 1.2
7526 * @see GetX(), GetY(), SetY(), SetXY()
7528 public function setX($x, $rtloff=false) {
7529 $x = floatval($x);
7530 if (!$rtloff AND $this->rtl) {
7531 if ($x >= 0) {
7532 $this->x = $this->w - $x;
7533 } else {
7534 $this->x = abs($x);
7536 } else {
7537 if ($x >= 0) {
7538 $this->x = $x;
7539 } else {
7540 $this->x = $this->w + $x;
7543 if ($this->x < 0) {
7544 $this->x = 0;
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.
7557 * @public
7558 * @since 1.0
7559 * @see GetX(), GetY(), SetY(), SetXY()
7561 public function setY($y, $resetx=true, $rtloff=false) {
7562 $y = floatval($y);
7563 if ($resetx) {
7564 //reset x
7565 if (!$rtloff AND $this->rtl) {
7566 $this->x = $this->w - $this->rMargin;
7567 } else {
7568 $this->x = $this->lMargin;
7571 if ($y >= 0) {
7572 $this->y = $y;
7573 } else {
7574 $this->y = $this->h + $y;
7576 if ($this->y < 0) {
7577 $this->y = 0;
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.
7590 * @public
7591 * @since 1.2
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.
7602 * @public
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.
7613 * @public
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.
7625 * @public
7626 * @since 5.9.186 (2012-09-13)
7627 * @see setAbsX(), setAbsY(), SetAbsXY()
7629 public function setAbsXY($x, $y) {
7630 $this->setAbsX($x);
7631 $this->setAbsY($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>
7640 * @return string
7641 * @public
7642 * @since 1.0
7643 * @see Close()
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) {
7649 $this->Close();
7651 //Normalize parameters
7652 if (is_bool($dest)) {
7653 $dest = $dest ? 'D' : 'F';
7655 $dest = strtoupper($dest);
7657 if ($this->sign) {
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();
7667 $byte_range[0] = 0;
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');
7679 if (!$f) {
7680 $this->Error('Unable to create temporary file: '.$tempdoc);
7682 $pdfdoc_length = strlen($pdfdoc);
7683 fwrite($f, $pdfdoc, $pdfdoc_length);
7684 fclose($f);
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);
7689 } else {
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']);
7692 // read signature
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];
7699 // decode signature
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);
7710 switch($dest) {
7711 case 'I': {
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);
7730 } else {
7731 echo $this->getBuffer();
7733 break;
7735 case 'D': {
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);
7755 } else {
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);
7763 break;
7765 case 'F':
7766 case 'FI':
7767 case 'FD': {
7768 // save PDF to a local file
7769 $f = TCPDF_STATIC::fopenLocal($name, 'wb');
7770 if (!$f) {
7771 $this->Error('Unable to create output file: '.$name);
7773 fwrite($f, $this->getBuffer(), $this->bufferlen);
7774 fclose($f);
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);
7804 } else {
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));
7812 break;
7814 case 'E': {
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");
7822 return $retval;
7824 case 'S': {
7825 // returns PDF as a string
7826 return $this->getBuffer();
7828 default: {
7829 $this->Error('Incorrect output destination: '.$dest);
7832 return '';
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
7840 * @public
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);
7856 closedir($handle);
7858 if (isset($this->imagekeys)) {
7859 foreach($this->imagekeys as $file) {
7860 if (strpos($file, K_PATH_CACHE) === 0 && TCPDF_STATIC::file_exists($file)) {
7861 @unlink($file);
7866 $preserve = array(
7867 'file_id',
7868 'state',
7869 'bufferlen',
7870 'buffer',
7871 'cached_files',
7872 'imagekeys',
7873 'sign',
7874 'signature_data',
7875 'signature_max_length',
7876 'byterange_string',
7877 'tsa_timestamp',
7878 'tsa_data'
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)) {
7883 unset($this->$val);
7890 * Check for locale-related bug
7891 * @protected
7893 protected function _dochecks() {
7894 //Check for locale-related bug
7895 if (1.1 == 1) {
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
7908 * @protected
7910 protected function getInternalPageNumberAliases($a= '') {
7911 $alias = array();
7912 // build array of Unicode + ASCII variants (the order is important)
7913 $alias = array('u' => array(), 'a' => array());
7914 $u = '{'.$a.'}';
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);
7923 return $alias;
7927 * Return an array containing all internal page aliases.
7928 * @return array of page number aliases
7929 * @protected
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);
7933 $pnalias = array();
7934 foreach($basic_alias as $k => $a) {
7935 $pnalias[$k] = $this->getInternalPageNumberAliases($a);
7937 return $pnalias;
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.
7947 * @protected
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) {
7956 // end of alias
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);
7964 if ($type == 'u') {
7965 $chrdiff = floor(($diff + 12) * $ratio);
7966 $shift = str_repeat(' ', $chrdiff);
7967 $shift = TCPDF_FONTS::UTF8ToUTF16BE($shift, false, $this->isunicode, $this->CurrentFont);
7968 } else {
7969 $chrdiff = floor(($diff + 11) * $ratio);
7970 $shift = str_repeat(' ', $chrdiff);
7972 $page = str_replace($aa, $shift, $page);
7976 return $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').
7982 * @protected
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).
7995 * @protected
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);
8005 $pagegroupnum = 0;
8006 $groupnum = 0;
8007 $ptgu = 1;
8008 $ptga = 1;
8009 $ptg_num_chars = 1;
8010 for ($n = 1; $n <= $num_pages; ++$n) {
8011 // get current page
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])) {
8022 $pagegroupnum = 0;
8023 ++$groupnum;
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);
8028 ++$pagegroupnum;
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
8033 $replace = array();
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
8041 $replace = array();
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);
8051 //Page
8052 $this->page_obj_id[$n] = $this->_newobj();
8053 $out = '<<';
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) {
8061 $out .= ' /'.$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'];
8071 $out .= ' /C [';
8072 $out .= sprintf(' %F %F %F', ($color[0] / 255), ($color[1] / 255), ($color[2] / 255));
8073 $out .= ' ]';
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'];
8083 $out .= ' /D [';
8084 foreach ($dashes as $dash) {
8085 $out .= sprintf(' %F', ($dash * $this->k));
8087 $out .= ' ]';
8089 $out .= ' >>';
8092 $out .= ' >>';
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'])) {
8100 // page transitions
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'];
8127 $out .= ' >>';
8129 $out .= $this->_getannotsrefs($n);
8130 $out .= ' /PZ '.$this->pagedim[$n]['PZ'];
8131 $out .= ' >>';
8132 $out .= "\n".'endobj';
8133 $this->_out($out);
8134 //Page content
8135 $p = ($this->compress) ? gzcompress($temppage) : $temppage;
8136 $this->_newobj();
8137 $p = $this->_getrawstream($p);
8138 $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
8140 //Pages root
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';
8148 $this->_out($out);
8152 * Get references to page annotations.
8153 * @param int $n page number
8154 * @return string
8155 * @protected
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'])))) {
8161 return '';
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';
8191 $out .= ' ]';
8192 return $out;
8196 * Output annotations objects for all pages.
8197 * !!! THIS METHOD IS NOT YET COMPLETED !!!
8198 * See section 12.5 of PDF 32000_2008 reference.
8199 * @protected
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'];
8213 $annots = '<<';
8214 $annots .= ' /Type /Annot';
8215 $annots .= ' /Subtype /Widget';
8216 $annots .= ' /Rect [0 0 0 0]';
8217 if ($this->radiobutton_groups[$n][$pl['txt']]['#readonly#']) {
8218 // read only
8219 $annots .= ' /F 68';
8220 $annots .= ' /Ff 49153';
8221 } else {
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 [';
8231 $defval = '';
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'];
8240 $annots .= ' ]';
8241 if (!empty($defval)) {
8242 $annots .= ' /V /'.$defval;
8244 $annots .= ' >>';
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;
8250 $formfield = false;
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'];
8264 $formfield = true;
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'])) {
8273 $fval = 0;
8274 if (is_array($pl['opt']['f'])) {
8275 foreach ($pl['opt']['f'] as $f) {
8276 switch (strtolower($f)) {
8277 case 'invisible': {
8278 $fval += 1 << 0;
8279 break;
8281 case 'hidden': {
8282 $fval += 1 << 1;
8283 break;
8285 case 'print': {
8286 $fval += 1 << 2;
8287 break;
8289 case 'nozoom': {
8290 $fval += 1 << 3;
8291 break;
8293 case 'norotate': {
8294 $fval += 1 << 4;
8295 break;
8297 case 'noview': {
8298 $fval += 1 << 5;
8299 break;
8301 case 'readonly': {
8302 $fval += 1 << 6;
8303 break;
8305 case 'locked': {
8306 $fval += 1 << 8;
8307 break;
8309 case 'togglenoview': {
8310 $fval += 1 << 9;
8311 break;
8313 case 'lockedcontents': {
8314 $fval += 1 << 10;
8315 break;
8317 default: {
8318 break;
8322 } else {
8323 $fval = intval($pl['opt']['f']);
8325 } else {
8326 $fval = 4;
8328 if ($this->pdfa_mode) {
8329 // force print flag for PDF/A mode
8330 $fval |= 4;
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)) {
8344 $annots .= ' <<';
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';
8350 $annots .= ' >>';
8351 } else {
8352 // reference to XObject that define the appearance for this mode
8353 $apsobjid = $this->_putAPXObject($c, $d, $apdef);
8354 $annots .= ' '.$apsobjid.' 0 R';
8357 } else {
8358 $annots .= $pl['opt']['ap'];
8360 $annots .= ' >>';
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']))) {
8373 $annots .= ' /D [';
8374 foreach ($pl['opt']['bs']['d'] as $cord) {
8375 $annots .= ' '.intval($cord);
8377 $annots .= ']';
8379 $annots .= ' >>';
8380 } else {
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])) {
8387 $annots .= ' [';
8388 foreach ($pl['opt']['border'][3] as $dash) {
8389 $annots .= intval($dash).' ';
8391 $annots .= ']';
8393 } else {
8394 $annots .= '0 0 0';
8396 $annots .= ']';
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'];
8403 } else {
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']);
8409 $annots .= '>>';
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');
8439 // Annotation types
8440 switch (strtolower($pl['opt']['subtype'])) {
8441 case 'text': {
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'];
8448 } else {
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) {
8455 break;
8457 if ($hasStateModel AND in_array($pl['opt']['statemodel'], $statemodels)) {
8458 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8459 } else {
8460 $pl['opt']['statemodel'] = 'Marked';
8461 $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
8463 if ($pl['opt']['statemodel'] == 'Marked') {
8464 $states = array('Accepted', 'Unmarked');
8465 } else {
8466 $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
8468 if ($hasState AND in_array($pl['opt']['state'], $states)) {
8469 $annots .= ' /State /'.$pl['opt']['state'];
8470 } else {
8471 if ($pl['opt']['statemodel'] == 'Marked') {
8472 $annots .= ' /State /Unmarked';
8473 } else {
8474 $annots .= ' /State /None';
8477 break;
8479 case 'link': {
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).'>>';
8493 } else {
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>>';
8504 } else {
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']])) {
8510 // internal link ID
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'];
8519 } else {
8520 $annots .= ' /H /I';
8522 //$annots .= ' /PA ';
8523 //$annots .= ' /Quadpoints ';
8524 break;
8526 case 'freetext': {
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);
8544 $annots .= ']';
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'];
8560 break;
8562 case 'line': {
8563 break;
8565 case 'square': {
8566 break;
8568 case 'circle': {
8569 break;
8571 case 'polygon': {
8572 break;
8574 case 'polyline': {
8575 break;
8577 case 'highlight': {
8578 break;
8580 case 'underline': {
8581 break;
8583 case 'squiggly': {
8584 break;
8586 case 'strikeout': {
8587 break;
8589 case 'stamp': {
8590 break;
8592 case 'caret': {
8593 break;
8595 case 'ink': {
8596 break;
8598 case 'popup': {
8599 break;
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
8604 break;
8606 if (!isset($pl['opt']['fs'])) {
8607 break;
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'];
8615 } else {
8616 $annots .= ' /Name /PushPin';
8618 // index (zero-based) of the annotation in the Annots array of this page
8619 $this->embeddedfiles[$filename]['a'] = $key;
8621 break;
8623 case 'sound': {
8624 if (!isset($pl['opt']['fs'])) {
8625 break;
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'];
8635 } else {
8636 $annots .= ' /Name /Speaker';
8639 break;
8641 case 'movie': {
8642 break;
8644 case 'widget': {
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';
8703 $annots .= '>>';
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']);
8708 $annots .= '>>';
8709 } // end MK
8710 // --- Entries for field dictionaries ---
8711 if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
8712 // set parent
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
8727 $flag = 0;
8728 foreach($pl['opt']['ff'] as $val) {
8729 $flag += 1 << ($val - 1);
8731 } else {
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'])) {
8740 $annots .= ' /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;
8748 } else {
8749 $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
8752 if (isset($pl['opt']['dv'])) {
8753 $annots .= ' /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;
8761 } else {
8762 $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
8765 if (isset($pl['opt']['rv'])) {
8766 $annots .= ' /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;
8774 } else {
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).']';
8795 } else {
8796 $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
8799 $annots .= ']';
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'])) {
8805 $annots .= ' /I [';
8806 foreach($pl['opt']['i'] AS $copt) {
8807 $annots .= intval($copt).' ';
8809 $annots .= ']';
8811 break;
8813 case 'screen': {
8814 break;
8816 case 'printermark': {
8817 break;
8819 case 'trapnet': {
8820 break;
8822 case 'watermark': {
8823 break;
8825 case '3d': {
8826 break;
8828 default: {
8829 break;
8832 $annots .= '>>';
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
8850 * @protected
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);
8857 $out .= '<<';
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);
8871 $out .= ' >>';
8872 $out .= ' stream'."\n".$stream."\n".'endstream';
8873 $out .= "\n".'endobj';
8874 $this->_out($out);
8875 return $this->n;
8879 * Output fonts.
8880 * @author Nicola Asuni
8881 * @protected
8883 protected function _putfonts() {
8884 $nf = $this->n;
8885 foreach ($this->diffs as $diff) {
8886 //Encodings
8887 $this->_newobj();
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);
8898 if ($header) {
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')))) {
8907 if ($compressed) {
8908 // uncompress font
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);
8921 if ($compressed) {
8922 // recompress font
8923 $font = gzcompress($font);
8926 $this->_newobj();
8927 $this->FontFiles[$file]['n'] = $this->n;
8928 $stream = $this->_getrawstream($font);
8929 $out = '<< /Length '.strlen($stream);
8930 if ($compressed) {
8931 $out .= ' /Filter /FlateDecode';
8933 $out .= ' /Length1 '.$info['length1'];
8934 if (isset($info['length2'])) {
8935 $out .= ' /Length2 '.$info['length2'].' /Length3 0';
8937 $out .= ' >>';
8938 $out .= ' stream'."\n".$stream."\n".'endstream';
8939 $out .= "\n".'endobj';
8940 $this->_out($out);
8943 foreach ($this->fontkeys as $k) {
8944 //Font objects
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'];
8962 $out .= ' >>';
8963 $out .= "\n".'endobj';
8964 $this->_out($out);
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';
8975 if ($font['enc']) {
8976 if (isset($font['diff'])) {
8977 $out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
8978 } else {
8979 $out .= ' /Encoding /WinAnsiEncoding';
8982 $out .= ' >>';
8983 $out .= "\n".'endobj';
8984 $this->_out($out);
8985 // Widths
8986 $this->_newobj();
8987 $s = '[';
8988 for ($i = 32; $i < 256; ++$i) {
8989 if (isset($font['cw'][$i])) {
8990 $s .= $font['cw'][$i].' ';
8991 } else {
8992 $s .= $font['dw'].' ';
8995 $s .= ']';
8996 $s .= "\n".'endobj';
8997 $this->_out($s);
8998 //Descriptor
8999 $this->_newobj();
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';
9010 $s .= '>>';
9011 $s .= "\n".'endobj';
9012 $this->_out($s);
9013 } else {
9014 // additional types
9015 $mtd = '_put'.strtolower($type);
9016 if (!method_exists($this, $mtd)) {
9017 $this->Error('Unsupported font type: '.$type);
9019 $this->$mtd($font);
9025 * Adds unicode fonts.<br>
9026 * Based on PDF Reference 1.3 (section 5)
9027 * @param array $font font data
9028 * @protected
9029 * @author Nicola Asuni
9030 * @since 1.52.0.TC005 (2005-01-05)
9032 protected function _puttruetypeunicode($font) {
9033 $fontname = '';
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'];
9041 // Type0 Font
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]';
9051 $out .= ' >>';
9052 $out .= "\n".'endobj';
9053 $this->_out($out);
9054 // ToUnicode map for Identity-H
9055 $stream = TCPDF_FONT_DATA::$uni_identity_h;
9056 // ToUnicode Object
9057 $this->_newobj();
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');
9062 // CIDFontType2
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';
9079 $out .= ' >>';
9080 $out .= "\n".'endobj';
9081 $this->_out($out);
9082 // Font descriptor
9083 // A font descriptor describing the CIDFont default metrics other than its glyph widths
9084 $this->_newobj();
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;
9093 $fontdir = false;
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'];
9099 $out .= ' >>';
9100 $out .= "\n".'endobj';
9101 $this->_out($out);
9102 if (isset($font['ctg']) AND (!TCPDF_STATIC::empty_string($font['ctg']))) {
9103 $this->_newobj();
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';
9121 $out .= ' >>';
9122 $out .= ' stream'."\n".$stream."\n".'endstream';
9123 $out .= "\n".'endobj';
9124 $this->_out($out);
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
9132 * @protected
9133 * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
9134 * @since 3.2.000 (2008-06-23)
9136 protected function _putcidfont0($font) {
9137 $cidoffset = 0;
9138 if (!isset($font['cw'][1])) {
9139 $cidoffset = 31;
9141 if (isset($font['cidinfo']['uni2cid'])) {
9142 // convert unicode to cid.
9143 $uni2cid = $font['cidinfo']['uni2cid'];
9144 $cw = array();
9145 foreach ($font['cw'] as $uni => $width) {
9146 if (isset($uni2cid[$uni])) {
9147 $cw[($uni2cid[$uni] + $cidoffset)] = $width;
9148 } elseif ($uni < 256) {
9149 $cw[$uni] = $width;
9150 } // else unknown character
9152 $font = array_merge($font, array('cw' => $cw));
9154 $name = $font['name'];
9155 $enc = $font['enc'];
9156 if ($enc) {
9157 $longname = $name.'-'.$enc;
9158 } else {
9159 $longname = $name;
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'];
9166 if ($enc) {
9167 $out .= ' /Encoding /'.$enc;
9169 $out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
9170 $out .= ' >>';
9171 $out .= "\n".'endobj';
9172 $this->_out($out);
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);
9184 $out .= ' >>';
9185 $out .= "\n".'endobj';
9186 $this->_out($out);
9187 $this->_newobj();
9188 $s = '<</Type /FontDescriptor /FontName /'.$name;
9189 foreach ($font['desc'] as $k => $v) {
9190 if ($k != 'Style') {
9191 if (is_float($v)) {
9192 $v = sprintf('%F', $v);
9194 $s .= ' /'.$k.' '.$v.'';
9197 $s .= '>>';
9198 $s .= "\n".'endobj';
9199 $this->_out($s);
9203 * Output images.
9204 * @protected
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
9211 $altoid = null;
9212 if ((!$this->pdfa_mode) AND isset($info['altimgs']) AND !empty($info['altimgs'])) {
9213 $altoid = $this->_newobj();
9214 $out = '[';
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) {
9220 $out .= ' true';
9221 } else {
9222 $out .= ' false';
9224 $out .= ' >>';
9227 $out .= ' ]';
9228 $out .= "\n".'endobj';
9229 $this->_out($out);
9231 // set image object
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';
9242 // set color space
9243 $icc = false;
9244 if (isset($info['icc']) AND ($info['icc'] !== false)) {
9245 // ICC Colour Space
9246 $icc = true;
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]';
9251 } else {
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'];
9259 if ($altoid > 0) {
9260 // reference to alternate images dictionary
9261 $out .= ' /Alternates '.$altoid.' 0 R';
9263 if (isset($info['exurl']) AND !empty($info['exurl'])) {
9264 // external stream
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'];
9270 $out .= ' >>';
9271 $out .= ' stream'."\n".'endstream';
9272 } else {
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'])) {
9280 $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
9287 $trns = '';
9288 break;
9289 } elseif (empty($trns) AND ($info['trns'][$i] == 0)) {
9290 // store the first fully transparent value
9291 $trns .= $i.' '.$i.' ';
9294 } else {
9295 // grayscale or RGB
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';
9312 $this->_out($out);
9313 if ($icc) {
9314 // ICC colour profile
9315 $this->_newobj();
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') {
9320 // colour palette
9321 $this->_newobj();
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)
9333 * @protected
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";
9341 $out .= '<<';
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) {
9354 // transparency
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'];
9360 } else {
9361 $out .= ' /GS'.$k;
9363 $out .= ' '.$this->extgstates[$k]['n'].' 0 R';
9365 $out .= ' >>';
9367 if (isset($data['gradients']) AND !empty($data['gradients'])) {
9368 $gp = '';
9369 $gs = '';
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.' >>';
9380 // spot colors
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';
9386 $out .= ' >>';
9388 // fonts
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';
9394 $out .= ' >>';
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';
9405 $out .= ' >>';
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');
9422 $out .= ' >>';
9424 $stream = $this->_getrawstream($stream, $data['n']);
9425 $out .= ' /Length '.strlen($stream);
9426 $out .= ' >>';
9427 $out .= ' stream'."\n".$stream."\n".'endstream';
9428 $out .= "\n".'endobj';
9429 $this->_out($out);
9435 * Output Spot Colors Resources.
9436 * @protected
9437 * @since 4.0.024 (2008-09-12)
9439 protected function _putspotcolors() {
9440 foreach ($this->spot_colors as $name => $color) {
9441 $this->_newobj();
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';
9449 $this->_out($out);
9454 * Return XObjects Dictionary.
9455 * @return string XObjects dictionary
9456 * @protected
9457 * @since 5.8.014 (2010-08-23)
9459 protected function _getxobjectdict() {
9460 $out = '';
9461 foreach ($this->xobjects as $id => $objid) {
9462 $out .= ' /'.$id.' '.$objid['n'].' 0 R';
9464 return $out;
9468 * Output Resources Dictionary.
9469 * @protected
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';
9479 $out .= ' >>';
9480 $out .= ' /XObject <<';
9481 $out .= $this->_getxobjectdict();
9482 $out .= ' >>';
9483 // layers
9484 if (!empty($this->pdflayers)) {
9485 $out .= ' /Properties <<';
9486 foreach ($this->pdflayers as $layer) {
9487 $out .= ' /'.$layer['layer'].' '.$layer['objid'].' 0 R';
9489 $out .= ' >>';
9491 if (!$this->pdfa_mode || $this->pdfa_version >= 2) {
9492 // transparency
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'];
9498 } else {
9499 $out .= ' /GS'.$k;
9501 $out .= ' '.$extgstate['n'].' 0 R';
9503 $out .= ' >>';
9505 if (isset($this->gradients) AND !empty($this->gradients)) {
9506 $gp = '';
9507 $gs = '';
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.' >>';
9518 // spot colors
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';
9524 $out .= ' >>';
9526 $out .= ' >>';
9527 $out .= "\n".'endobj';
9528 $this->_out($out);
9532 * Output Resources.
9533 * @protected
9535 protected function _putresources() {
9536 $this->_putextgstates();
9537 $this->_putocg();
9538 $this->_putfonts();
9539 $this->_putimages();
9540 $this->_putspotcolors();
9541 $this->_putshaders();
9542 $this->_putxobjects();
9543 $this->_putresourcedict();
9544 $this->_putdests();
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
9556 * @protected
9558 protected function _putinfo() {
9559 $oid = $this->_newobj();
9560 $out = '<<';
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;
9588 // default producer
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';
9596 $out .= ' >>';
9597 $out .= "\n".'endobj';
9598 $this->_out($out);
9599 return $oid;
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)
9607 * @public
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)
9618 * @public
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)
9628 * @protected
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;
9637 // set XMP data
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;
9770 $this->_out($out);
9771 return $oid;
9775 * Output Catalog.
9776 * @return int object id
9777 * @protected
9779 protected function _putcatalog() {
9780 // put XMP
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');
9786 $filter = '';
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');
9794 // start catalog
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;
9810 $out .= ' ]>>';
9812 $out .= ' >>';
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';
9856 $out .= ' >>]';
9858 //$out .= ' /PieceInfo <<>>';
9859 if (!empty($this->pdflayers)) {
9860 $lyrobjs = '';
9861 $lyrobjs_off = '';
9862 $lyrobjs_lock = '';
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.']';
9874 $out .= ' /D <<';
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';
9881 $out .= ' /AS [';
9882 $out .= ' << /Event /Print /OCGs ['.$lyrobjs.'] /Category [/Print] >>';
9883 $out .= ' << /Event /View /OCGs ['.$lyrobjs.'] /Category [/View] >>';
9884 $out .= ' ]';
9885 $out .= ' /Order ['.$lyrobjs.']';
9886 $out .= ' /ListMode /AllPages';
9887 //$out .= ' /RBGroups ['..']';
9888 //$out .= ' /Locked ['..']';
9889 $out .= ' >>';
9890 $out .= ' >>';
9892 // AcroForm
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 <<';
9897 $objrefs = '';
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';
9921 } else {
9922 $out .= ' /SigFlags 1';
9925 //$out .= ' /CO ';
9926 if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
9927 $out .= ' /DR <<';
9928 $out .= ' /Font <<';
9929 foreach ($this->annotation_fonts as $fontkey => $fontid) {
9930 $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
9932 $out .= ' >> >>';
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');
9937 //$out .= ' /XFA ';
9938 $out .= ' >>';
9939 // signatures
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 >>';
9944 } else {
9945 $out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
9949 //$out .= ' /Legal <<>>';
9950 //$out .= ' /Requirements []';
9951 //$out .= ' /Collection <<>>';
9952 //$out .= ' /NeedsRendering true';
9953 $out .= ' >>';
9954 $out .= "\n".'endobj';
9955 $this->_out($out);
9956 return $oid;
9960 * Output viewer preferences.
9961 * @return string for viewer preferences
9962 * @author Nicola asuni
9963 * @since 3.1.000 (2008-06-09)
9964 * @protected
9966 protected function _putviewerpreferences() {
9967 $vp = $this->viewer_preferences;
9968 $out = ' /ViewerPreferences <<';
9969 if ($this->rtl) {
9970 $out .= ' /Direction /R2L';
9971 } else {
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';
10016 } else {
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']);
10030 $out .= ' >>';
10031 return $out;
10035 * Output PDF File Header (7.5.2).
10036 * @protected
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).
10045 * @protected
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']);
10052 $this->state = 1;
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
10065 $out .= ' /F 4';
10066 $out .= ' /FT /Sig';
10067 $signame = $esa['name'].sprintf(' [%03d]', ($key + 1));
10068 $out .= ' /T '.$this->_textstring($signame, $esa['objid']);
10069 $out .= ' /Ff 0';
10070 $out .= ' >>';
10071 $out .= "\n".'endobj';
10072 $this->_out($out);
10075 // Signature
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
10083 $out .= ' /F 4';
10084 $out .= ' /FT /Sig';
10085 $out .= ' /T '.$this->_textstring($this->signature_appearance['name'], $this->sig_obj_id);
10086 $out .= ' /Ff 0';
10087 $out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
10088 $out .= ' >>';
10089 $out .= "\n".'endobj';
10090 $this->_out($out);
10091 // signature
10092 $this->_putsignature();
10094 // Info
10095 $objid_info = $this->_putinfo();
10096 // Catalog
10097 $objid_catalog = $this->_putcatalog();
10098 // Cross-ref
10099 $o = $this->bufferlen;
10100 // XREF section
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));
10108 ++$freegen;
10109 } else {
10110 $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
10113 // TRAILER
10114 $out = 'trailer'."\n";
10115 $out .= '<<';
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.'> ]';
10123 $out .= ' >>';
10124 $this->_out($out);
10125 $this->_out('startxref');
10126 $this->_out($o);
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().
10135 * @protected
10136 * @see getPageSizeFromFormat(), setPageFormat()
10138 protected function _beginpage($orientation='', $format='') {
10139 ++$this->page;
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();
10144 $this->state = 2;
10145 if (TCPDF_STATIC::empty_string($orientation)) {
10146 if (isset($this->CurOrientation)) {
10147 $orientation = $this->CurOrientation;
10148 } elseif ($this->fwPt > $this->fhPt) {
10149 // landscape
10150 $orientation = 'L';
10151 } else {
10152 // portrait
10153 $orientation = 'P';
10156 if (TCPDF_STATIC::empty_string($format)) {
10157 $this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
10158 $this->setPageOrientation($orientation);
10159 } else {
10160 $this->setPageFormat($format, $orientation);
10162 if ($this->rtl) {
10163 $this->x = $this->w - $this->rMargin;
10164 } else {
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.
10179 * @protected
10181 protected function _endpage() {
10182 $this->setVisibility('all');
10183 $this->state = 1;
10187 * Begin a new object and return the object number.
10188 * @return int object number
10189 * @protected
10191 protected function _newobj() {
10192 $this->_out($this->_getobj());
10193 return $this->n;
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
10200 * @protected
10201 * @since 5.8.009 (2010-08-20)
10203 protected function _getobj($objid=null) {
10204 if (TCPDF_STATIC::empty_string($objid)) {
10205 ++$this->n;
10206 $objid = $this->n;
10208 $this->offsets[$objid] = $this->bufferlen;
10209 $this->pageobjects[$this->page][] = $objid;
10210 return $objid.' 0 obj';
10214 * Underline text.
10215 * @param int $x X coordinate
10216 * @param int $y Y coordinate
10217 * @param string $txt text to underline
10218 * @protected
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
10230 * @protected
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
10243 * @protected
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)
10255 * @protected
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);
10264 * Overline text.
10265 * @param int $x X coordinate
10266 * @param int $y Y coordinate
10267 * @param string $txt text to overline
10268 * @protected
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
10281 * @protected
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.
10295 * @protected
10297 protected function _datastring($s, $n=0) {
10298 if ($n == 0) {
10299 $n = $this->n;
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.
10308 * @public
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.
10321 * @public
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.
10334 * @public
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.
10344 * @public
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.
10356 * @protected
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.
10371 * @protected
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
10385 * @protected
10386 * @author Nicola Asuni
10387 * @since 5.5.000 (2010-06-22)
10389 protected function _getrawstream($s, $n=0) {
10390 if ($n <= 0) {
10391 // default to current object
10392 $n = $this->n;
10394 return $this->_encrypt_data($n, $s);
10398 * Output a string to the document.
10399 * @param string $s string to output.
10400 * @protected
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");
10415 } else {
10416 // set page data
10417 $this->setPageBuffer($this->page, $s."\n", true);
10419 } elseif ($this->state > 0) {
10420 // set general data
10421 $this->setBuffer($s."\n");
10426 * Set header font.
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
10429 * @public
10430 * @since 1.1
10432 public function setHeaderFont($font) {
10433 $this->header_font = $font;
10437 * Get header 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}
10440 * @public
10441 * @since 4.0.012 (2008-07-24)
10443 public function getHeaderFont() {
10444 return $this->header_font;
10448 * Set footer 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
10451 * @public
10452 * @since 1.1
10454 public function setFooterFont($font) {
10455 $this->footer_font = $font;
10459 * Get Footer 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
10462 * @public
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
10472 * @public
10473 * @since 1.1
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;
10479 } else {
10480 $this->rtl = false;
10485 * Returns the PDF data.
10486 * @public
10488 public function getPDFData() {
10489 if ($this->state < 3) {
10490 $this->Close();
10492 return $this->buffer;
10496 * Output anchor link.
10497 * @param string $url link URL or internal link (i.e.: &lt;a href="#23,4.5"&gt;link to page 23 at 4.5 Y position&lt;/a&gt;)
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;
10505 * @public
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]);
10515 } else {
10516 $lnky = 0;
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);
10527 } else {
10528 $this->setTextColorArray($color);
10530 if ($style == -1) {
10531 $this->setFont('', $this->FontStyle.$this->htmlLinkFontStyle);
10532 } else {
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);
10539 return $ret;
10543 * Converts pixels to User's Units.
10544 * @param int $px pixels
10545 * @return float value in user's unit
10546 * @public
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
10558 * @public
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
10571 * @protected
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
10578 // AES padding
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);
10583 return $objkey;
10587 * Encrypt the input string.
10588 * @param int $n object number
10589 * @param string $s data string to encrypt
10590 * @return string encrypted string
10591 * @protected
10592 * @author Nicola Asuni
10593 * @since 5.0.005 (2010-05-11)
10595 protected function _encrypt_data($n, $s) {
10596 if (!$this->encrypted) {
10597 return $s;
10599 switch ($this->encryptdata['mode']) {
10600 case 0: // RC4-40
10601 case 1: { // RC4-128
10602 $s = TCPDF_STATIC::_RC4($this->_objectkey($n), $s, $this->last_enc_key, $this->last_enc_key_c);
10603 break;
10605 case 2: { // AES-128
10606 $s = TCPDF_STATIC::_AES($this->_objectkey($n), $s);
10607 break;
10609 case 3: { // AES-256
10610 $s = TCPDF_STATIC::_AES($this->encryptdata['key'], $s);
10611 break;
10614 return $s;
10618 * Put encryption on PDF document.
10619 * @protected
10620 * @author Nicola Asuni
10621 * @since 2.0.000 (2008-01-02)
10623 protected function _putencryption() {
10624 if (!$this->encrypted) {
10625 return;
10627 $this->encryptdata['objid'] = $this->_newobj();
10628 $out = '<<';
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'];
10644 } else {
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'])) {
10657 $out .= ' /CF <<';
10658 $out .= ' /'.$this->encryptdata['StmF'].' <<';
10659 $out .= ' /Type /CryptFilter';
10660 if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
10661 // The method used
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.'>';
10668 $out .= ' ]';
10669 if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
10670 $out .= ' /EncryptMetadata false';
10671 } else {
10672 $out .= ' /EncryptMetadata true';
10675 } else {
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'];
10681 } else {
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'];
10688 $out .= ' >> >>';
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.'>';
10706 $out .= ' ]';
10708 } else {
10709 $out .= ' /R';
10710 if ($this->encryptdata['V'] == 5) { // AES-256
10711 $out .= ' 5';
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
10716 $out .= ' 4';
10717 } elseif ($this->encryptdata['V'] < 2) { // RC-40
10718 $out .= ' 2';
10719 } else { // RC-128
10720 $out .= ' 3';
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';
10727 } else {
10728 $out .= ' /EncryptMetadata true';
10731 $out .= ' >>';
10732 $out .= "\n".'endobj';
10733 $this->_out($out);
10737 * Compute U value (used for encryption)
10738 * @return string U value
10739 * @protected
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) {
10751 $ek = '';
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);
10763 // User Key Salt
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
10772 * @protected
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
10784 * @protected
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) {
10801 $ek = '';
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);
10808 return $enc;
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);
10813 // Owner Key Salt
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
10822 * @protected
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
10835 * @protected
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
10850 * @protected
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']);
10863 // Compute U value
10864 $this->encryptdata['U'] = $this->_Uvalue();
10865 // Compute UE value
10866 $this->encryptdata['UE'] = $this->_UEvalue();
10867 // Compute O value
10868 $this->encryptdata['O'] = $this->_Ovalue();
10869 // Compute OE value
10870 $this->encryptdata['OE'] = $this->_OEvalue();
10871 // Compute P value
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
10877 $perms .= 'F';
10878 } else {
10879 $perms .= 'T';
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
10885 // Pad passwords
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);
10888 // Compute O value
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);
10900 // Compute U value
10901 $this->encryptdata['U'] = $this->_Uvalue();
10902 // Compute P value
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']);
10913 } else {
10914 $pkprotection = $this->encryptdata['protection'];
10916 // get default permissions (reverse byte order)
10917 $pkpermissions = TCPDF_STATIC::getEncPermissionsString($pkprotection);
10918 // envelope data
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');
10923 if (!$f) {
10924 $this->Error('Unable to create temporary key file: '.$tempkeyfile);
10926 $envelope_length = strlen($envelope);
10927 fwrite($f, $envelope, $envelope_length);
10928 fclose($f);
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]);
10939 unset($tmparr);
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')))
10968 * @public
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
10975 return;
10977 $this->encryptdata['protection'] = TCPDF_STATIC::getUserPermissionCode($permissions, $mode);
10978 if (($pubkeys !== null) AND (is_array($pubkeys))) {
10979 // public-key mode
10980 $this->encryptdata['pubkeys'] = $pubkeys;
10981 if ($mode == 0) {
10982 // public-Key Security requires at least 128 bit
10983 $mode = 1;
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';
10993 } else {
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;
11021 switch ($mode) {
11022 case 0: { // RC4 40 bit
11023 $this->encryptdata['V'] = 1;
11024 $this->encryptdata['Length'] = 40;
11025 $this->encryptdata['CF']['CFM'] = 'V2';
11026 break;
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();
11036 break;
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();
11047 break;
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();
11058 break;
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.
11074 * @public
11075 * @since 2.1.000 (2008-01-07)
11076 * @see StartTransform(), StopTransform()
11078 public function StartTransform() {
11079 if ($this->state != 2) {
11080 return;
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']);
11086 } else {
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.
11097 * @public
11098 * @since 2.1.000 (2008-01-07)
11099 * @see StartTransform(), StopTransform()
11101 public function StopTransform() {
11102 if ($this->state != 2) {
11103 return;
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']);
11113 } else {
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
11122 * @public
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
11135 * @public
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
11148 * @public
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
11162 * @public
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)) {
11168 $x = $this->x;
11170 if (TCPDF_STATIC::empty_string($y)) {
11171 $y = $this->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;
11177 $x *= $this->k;
11178 //calculate elements of transformation matrix
11179 $s_x /= 100;
11180 $s_y /= 100;
11181 $tm = array();
11182 $tm[0] = $s_x;
11183 $tm[1] = 0;
11184 $tm[2] = 0;
11185 $tm[3] = $s_y;
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
11195 * @public
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
11206 * @public
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
11218 * @public
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
11231 * @public
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)
11243 * @public
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
11254 * @public
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
11266 * @public
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
11272 $tm = array();
11273 $tm[0] = 1;
11274 $tm[1] = 0;
11275 $tm[2] = 0;
11276 $tm[3] = 1;
11277 $tm[4] = $t_x * $this->k;
11278 $tm[5] = -$t_y * $this->k;
11279 //translate the coordinate system
11280 $this->Transform($tm);
11284 * Rotate object.
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
11288 * @public
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)) {
11294 $x = $this->x;
11296 if (TCPDF_STATIC::empty_string($y)) {
11297 $y = $this->y;
11299 $y = ($this->h - $y) * $this->k;
11300 $x *= $this->k;
11301 //calculate elements of transformation matrix
11302 $tm = array();
11303 $tm[0] = cos(deg2rad($angle));
11304 $tm[1] = sin(deg2rad($angle));
11305 $tm[2] = -$tm[1];
11306 $tm[3] = $tm[0];
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
11318 * @public
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);
11327 * Skew vertically.
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
11331 * @public
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);
11340 * Skew.
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
11345 * @public
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)) {
11351 $x = $this->x;
11353 if (TCPDF_STATIC::empty_string($y)) {
11354 $y = $this->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.');
11359 $x *= $this->k;
11360 $y = ($this->h - $y) * $this->k;
11361 //calculate elements of transformation matrix
11362 $tm = array();
11363 $tm[0] = 1;
11364 $tm[1] = tan(deg2rad($angle_y));
11365 $tm[2] = tan(deg2rad($angle_x));
11366 $tm[3] = 1;
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
11376 * @protected
11377 * @since 2.1.000 (2008-01-07)
11378 * @see StartTransform(), StopTransform()
11380 protected function Transform($tm) {
11381 if ($this->state != 2) {
11382 return;
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.
11408 * @public
11409 * @since 1.0
11410 * @see Line(), Rect(), Cell(), MultiCell()
11412 public function setLineWidth($width) {
11413 //Set line 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
11424 * @public
11425 * @since 2.1.000 (2008-01-07)
11426 * @see Line(), SetLineWidth()
11428 public function GetLineWidth() {
11429 return $this->LineWidth;
11433 * Set line style.
11434 * @param array $style Line style. Array with keys among the following:
11435 * <ul>
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,
11441 * bevel.</li>
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>
11449 * </ul>
11450 * @param boolean $ret if true do not send the command.
11451 * @return string the PDF command
11452 * @public
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)) {
11458 return $s;
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'])) {
11480 $dash_string = '';
11481 if ($style['dash']) {
11482 if (preg_match('/^.+,/', $style['dash']) > 0) {
11483 $tab = explode(',', $style['dash']);
11484 } else {
11485 $tab = array($style['dash']);
11487 $dash_string = '';
11488 foreach ($tab as $i => $v) {
11489 if ($i) {
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)) {
11505 $this->_out($s);
11507 return $s;
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.
11514 * @protected
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.
11528 * @protected
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
11544 * @protected
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.
11562 * @protected
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.
11578 * @protected
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.
11594 * @protected
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).
11610 * @public
11611 * @since 1.0
11612 * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
11614 public function Line($x1, $y1, $x2, $y2, $style=array()) {
11615 if ($this->state != 2) {
11616 return;
11618 if (is_array($style)) {
11619 $this->setLineStyle($style);
11621 $this->_outPoint($x1, $y1);
11622 $this->_outLine($x2, $y2);
11623 $this->_out('S');
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:
11634 * <ul>
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>
11637 * </ul>
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).
11640 * @public
11641 * @since 1.0
11642 * @see SetLineStyle()
11644 public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
11645 if ($this->state != 2) {
11646 return;
11648 if (empty($style)) {
11649 $style = 'S';
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();
11660 } else {
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).
11711 * @public
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) {
11717 return;
11719 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11720 $this->setFillColorArray($fill_color);
11722 $op = TCPDF_STATIC::getPathPaintOperator($style);
11723 if ($line_style) {
11724 $this->setLineStyle($line_style);
11726 $this->_outPoint($x0, $y0);
11727 $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
11728 $this->_out($op);
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).
11741 * @public
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) {
11747 return;
11749 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11750 $this->setFillColorArray($fill_color);
11752 $op = TCPDF_STATIC::getPathPaintOperator($style);
11753 if ($op == 'f') {
11754 $line_style = array();
11756 if ($line_style) {
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);
11764 $this->_out($op);
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
11782 * @public
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) {
11787 return;
11789 if (TCPDF_STATIC::empty_string($ry) OR ($ry == 0)) {
11790 $ry = $rx;
11792 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
11793 $this->setFillColorArray($fill_color);
11795 $op = TCPDF_STATIC::getPathPaintOperator($style);
11796 if ($op == 'f') {
11797 $line_style = array();
11799 if ($line_style) {
11800 $this->setLineStyle($line_style);
11802 $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc, true, true, false);
11803 $this->_out($op);
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
11823 * @protected
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)) {
11828 return;
11830 $k = $this->k;
11831 if ($nc < 2) {
11832 $nc = 2;
11834 $xmin = 2147483647;
11835 $ymin = 2147483647;
11836 $xmax = 0;
11837 $ymax = 0;
11838 if ($pie) {
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);
11845 if ($svg) {
11846 $as = $angs;
11847 $af = $angf;
11848 } else {
11849 $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
11850 $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
11852 if ($as < 0) {
11853 $as += (2 * M_PI);
11855 if ($af < 0) {
11856 $af += (2 * M_PI);
11858 if ($ccw AND ($as > $af)) {
11859 // reverse rotation
11860 $as -= (2 * M_PI);
11861 } elseif (!$ccw AND ($as < $af)) {
11862 // reverse rotation
11863 $af -= (2 * M_PI);
11865 $total_angle = ($af - $as);
11866 if ($nc < 2) {
11867 $nc = 2;
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
11875 $x0 = $xc;
11876 $y0 = ($this->h - $yc);
11877 // starting angle
11878 $ang = $as;
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);
11884 // first arc point
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)));
11890 if ($pie) {
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);
11897 // draw arcs
11898 for ($i = 1; $i <= $nc; ++$i) {
11899 // starting angle
11900 $ang = $as + ($i * $arcang);
11901 if ($i == $nc) {
11902 $ang = $af;
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)));
11912 // draw arc
11913 $cx1 = ($px1 + $qx1);
11914 $cy1 = ($this->h - ($py1 + $qy1));
11915 $cx2 = ($px2 - $qx2);
11916 $cy2 = ($this->h - ($py2 - $qy2));
11917 $cx3 = $px2;
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
11926 $px1 = $px2;
11927 $py1 = $py2;
11928 $qx1 = $qx2;
11929 $qy1 = $qy2;
11931 if ($pie) {
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);
11943 * Draws a circle.
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.
11954 * @public
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:
11966 * <ul>
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>
11969 * </ul>
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)
11973 * @public
11975 public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
11976 $this->Polygon($p, $style, $line_style, $fill_color, false);
11980 * Draws a polygon.
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:
11984 * <ul>
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>
11987 * </ul>
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
11991 * @public
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) {
11996 return;
11998 $nc = count($p); // number of coordinates
11999 $np = $nc / 2; // number of points
12000 if ($closed) {
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];
12009 $nc += 4;
12011 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
12012 $this->setFillColorArray($fill_color);
12014 $op = TCPDF_STATIC::getPathPaintOperator($style);
12015 if ($op == 'f') {
12016 $line_style = array();
12018 $draw = true;
12019 if ($line_style) {
12020 if (isset($line_style['all'])) {
12021 $this->setLineStyle($line_style['all']);
12022 } else {
12023 $draw = false;
12024 if ($op == 'B') {
12025 // draw fill
12026 $op = 'f';
12027 $this->_outPoint($p[0], $p[1]);
12028 for ($i = 2; $i < $nc; $i = $i + 2) {
12029 $this->_outLine($p[$i], $p[$i + 1]);
12031 $this->_out($op);
12033 // draw outline
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])) {
12040 $this->_out('S');
12041 $this->setLineStyle($line_style[$line_num]);
12042 $this->_outPoint($p[$i - 2], $p[$i - 1]);
12043 $this->_outLine($p[$i], $p[$i + 1]);
12044 $this->_out('S');
12045 $this->_outPoint($p[$i], $p[$i + 1]);
12046 } else {
12047 $this->_outLine($p[$i], $p[$i + 1]);
12050 } else {
12051 $this->_outLine($p[$i], $p[$i + 1]);
12054 $this->_out($op);
12057 if ($draw) {
12058 $this->_outPoint($p[0], $p[1]);
12059 for ($i = 2; $i < $nc; $i = $i + 2) {
12060 $this->_outLine($p[$i], $p[$i + 1]);
12062 $this->_out($op);
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:
12076 * <ul>
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>
12079 * </ul>
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:
12083 * <ul>
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>
12089 * </ul>
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).
12092 * @public
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()) {
12096 if (3 > $ns) {
12097 $ns = 3;
12099 if ($draw_circle) {
12100 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12102 $p = array();
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:
12123 * <ul>
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>
12127 * </ul>
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:
12131 * <ul>
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>
12137 * </ul>
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).
12140 * @public
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()) {
12144 if ($nv < 2) {
12145 $nv = 2;
12147 if ($draw_circle) {
12148 $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
12150 $p2 = array();
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;
12159 $p = array();
12160 $i = 0;
12161 do {
12162 $p[] = $p2[$i * 2];
12163 $p[] = $p2[($i * 2) + 1];
12164 $visited[$i] = true;
12165 $i += $ng;
12166 $i %= $nv;
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).
12182 * @public
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).
12201 * @public
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) {
12206 return;
12208 if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
12209 // Not rounded
12210 $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
12211 return;
12213 // Rounded
12214 if (!(false === strpos($style, 'F')) AND is_array($fill_color)) {
12215 $this->setFillColorArray($fill_color);
12217 $op = TCPDF_STATIC::getPathPaintOperator($style);
12218 if ($op == 'f') {
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;
12227 $yc = $y + $ry;
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);
12231 } else {
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);
12239 } else {
12240 $this->_outLine($x + $w, $y + $h);
12242 $xc = $x + $rx;
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);
12247 } else {
12248 $this->_outLine($x, $y + $h);
12250 $xc = $x + $rx;
12251 $yc = $y + $ry;
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);
12255 } else {
12256 $this->_outLine($x, $y);
12257 $this->_outLine($x + $rx, $y);
12259 $this->_out($op);
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);
12282 $sx1 = $x1;
12283 $sy1 = $y1;
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));
12297 $mode = 'D';
12298 $style = array();
12299 switch ($head_style) {
12300 case 0: {
12301 // draw only arrowhead arms
12302 $mode = 'D';
12303 $style = array(1, 1, 0);
12304 break;
12306 case 1: {
12307 // draw closed arrowhead, but no fill
12308 $mode = 'D';
12309 break;
12311 case 2: {
12312 // closed and filled arrowhead
12313 $mode = 'DF';
12314 break;
12316 case 3: {
12317 // filled arrowhead
12318 $mode = 'F';
12319 break;
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.
12335 * @public
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)) {
12343 return false;
12345 if ($y == -1) {
12346 $y = $this->GetY();
12347 } elseif ($y < 0) {
12348 $y = 0;
12349 } elseif ($y > $this->h) {
12350 $y = $this->h;
12352 if ($x == -1) {
12353 $x = $this->GetX();
12354 } elseif ($x < 0) {
12355 $x = 0;
12356 } elseif ($x > $this->w) {
12357 $x = $this->w;
12359 $fixed = false;
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
12363 $fixed = true;
12365 if (empty($page)) {
12366 $page = $this->PageNo();
12367 if (empty($page)) {
12368 return;
12371 $this->dests[$name] = array('x' => $x, 'y' => $y, 'p' => $page, 'f' => $fixed);
12372 return $name;
12376 * Return the Named Destination array.
12377 * @return array Named Destination array.
12378 * @public
12379 * @author Nicola Asuni
12380 * @since 5.9.097 (2011-06-23)
12382 public function getDestination() {
12383 return $this->dests;
12387 * Insert Named Destinations.
12388 * @protected
12389 * @author Johannes G\FCntert, Nicola Asuni
12390 * @since 5.9.098 (2011-06-23)
12392 protected function _putdests() {
12393 if (empty($this->dests)) {
12394 return;
12396 $this->n_dests = $this->_newobj();
12397 $out = ' <<';
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)));
12401 $out .= ' >>';
12402 $out .= "\n".'endobj';
12403 $this->_out($out);
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).
12416 * @public
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);
12423 * Adds a bookmark.
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).
12432 * @public
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='') {
12436 if ($level < 0) {
12437 $level = 0;
12439 if (isset($this->outlines[0])) {
12440 $lastoutline = end($this->outlines);
12441 $maxlevel = $lastoutline['l'] + 1;
12442 } else {
12443 $maxlevel = 0;
12445 if ($level > $maxlevel) {
12446 $level = $maxlevel;
12448 if ($y == -1) {
12449 $y = $this->GetY();
12450 } elseif ($y < 0) {
12451 $y = 0;
12452 } elseif ($y > $this->h) {
12453 $y = $this->h;
12455 if ($x == -1) {
12456 $x = $this->GetX();
12457 } elseif ($x < 0) {
12458 $x = 0;
12459 } elseif ($x > $this->w) {
12460 $x = $this->w;
12462 $fixed = false;
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
12467 $fixed = true;
12469 if (empty($page)) {
12470 $page = $this->PageNo();
12471 if (empty($page)) {
12472 return;
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.
12480 * @protected
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.
12497 * @protected
12498 * @author Olivier Plathey, Nicola Asuni
12499 * @since 2.1.002 (2008-02-12)
12501 protected function _putbookmarks() {
12502 $nb = count($this->outlines);
12503 if ($nb == 0) {
12504 return;
12506 // sort bookmarks
12507 $this->sortBookmarks();
12508 $lru = array();
12509 $level = 0;
12510 foreach ($this->outlines as $i => $o) {
12511 if ($o['l'] > 0) {
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;
12520 } else {
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;
12530 $level = $o['l'];
12532 //Outline items
12533 $n = $this->n + 1;
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'])) {
12558 // link
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).'>>';
12572 } else {
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'])])) {
12584 // link to a page
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)));
12587 // set font style
12588 $style = 0;
12589 if (!empty($o['s'])) {
12590 // bold
12591 if (strpos($o['s'], 'B') !== false) {
12592 $style |= 2;
12594 // oblique
12595 if (strpos($o['s'], 'I') !== false) {
12596 $style |= 1;
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));
12604 } else {
12605 // black
12606 $out .= ' /C [0.0 0.0 0.0]';
12608 $out .= ' /Count 0'; // normally closed item
12609 $out .= ' >>';
12610 $out .= "\n".'endobj';
12611 $this->_out($out);
12613 //Outline root
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
12623 * @public
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
12636 * @public
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
12643 return false;
12645 ++$this->n;
12646 $this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
12647 return $this->n;
12651 * Create a javascript PDF string.
12652 * @protected
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))) {
12658 return;
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);
12688 $out .= ' >>';
12689 $out .= "\n".'endobj';
12690 $this->_out($out);
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';
12696 $this->_out($out);
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.
12710 * @protected
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) {
12715 if ($this->rtl) {
12716 $x = $x - $w;
12718 // the followind avoid fields duplication after saving the document
12719 $this->javascript .= "if (getField('tcpdfdocsaved').value != 'saved') {";
12720 $k = $this->k;
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);
12726 } else {
12727 $val = "'".$val."'";
12729 $this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
12731 if ($this->rtl) {
12732 $this->x -= $w;
12733 } else {
12734 $this->x += $w;
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.
12746 * @public
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.
12757 * @public
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).
12775 * @public
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)) {
12781 $x = $this->x;
12783 if (TCPDF_STATIC::empty_string($y)) {
12784 $y = $this->y;
12786 // check page for no-write regions and adapt page margins if necessary
12787 list($x, $y) = $this->checkPageRegions($h, $x, $y);
12788 if ($js) {
12789 $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
12790 return;
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.' ';
12803 $text = '';
12804 if (isset($prop['value']) AND !empty($prop['value'])) {
12805 $text = $prop['value'];
12806 } elseif (isset($opt['v']) AND !empty($opt['v'])) {
12807 $text = $opt['v'];
12809 $tmpid = $this->startTemplate($w, $h, false);
12810 $align = '';
12811 if (isset($popt['q'])) {
12812 switch ($popt['q']) {
12813 case 0: {
12814 $align = 'L';
12815 break;
12817 case 1: {
12818 $align = 'C';
12819 break;
12821 case 2: {
12822 $align = 'R';
12823 break;
12825 default: {
12826 $align = '';
12827 break;
12831 $this->MultiCell($w, $h, $text, 0, $align, false, 0, 0, 0, true, 0, false, true, 0, 'T', false);
12832 $this->endTemplate();
12833 --$this->n;
12834 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
12835 unset($this->xobjects[$tmpid]);
12836 $popt['ap']['n'] .= 'Q EMC';
12837 // merge options
12838 $opt = array_merge($popt, $opt);
12839 // remove some conflicting options
12840 unset($opt['bs']);
12841 // set remaining annotation data
12842 $opt['Subtype'] = 'Widget';
12843 $opt['ft'] = 'Tx';
12844 $opt['t'] = $name;
12845 // Additional annotation's parameters (check _putannotsobj() method):
12846 //$opt['f']
12847 //$opt['as']
12848 //$opt['bs']
12849 //$opt['be']
12850 //$opt['c']
12851 //$opt['border']
12852 //$opt['h']
12853 //$opt['mk'];
12854 //$opt['mk']['r']
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']);
12869 //$opt['tu']
12870 //$opt['tm']
12871 //$opt['ff']
12872 //$opt['v']
12873 //$opt['dv']
12874 //$opt['a']
12875 //$opt['aa']
12876 //$opt['q']
12877 $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
12878 if ($this->rtl) {
12879 $this->x -= $w;
12880 } else {
12881 $this->x += $w;
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).
12896 * @public
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)) {
12902 $x = $this->x;
12904 if (TCPDF_STATIC::empty_string($y)) {
12905 $y = $this->y;
12907 // check page for no-write regions and adapt page margins if necessary
12908 list($x, $y) = $this->checkPageRegions($w, $x, $y);
12909 if ($js) {
12910 $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
12911 return;
12913 if (TCPDF_STATIC::empty_string($onvalue)) {
12914 $onvalue = 'On';
12916 if ($checked) {
12917 $defval = $onvalue;
12918 } else {
12919 $defval = 'Off';
12921 // set font
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();
12935 ++$this->n;
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)';
12964 // merge options
12965 $opt = array_merge($popt, $opt);
12966 // set remaining annotation data
12967 $opt['Subtype'] = 'Widget';
12968 $opt['ft'] = 'Btn';
12969 if ($checked) {
12970 $opt['v'] = array('/'.$onvalue);
12971 $opt['as'] = $onvalue;
12972 } else {
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);
12981 if ($this->rtl) {
12982 $this->x -= $w;
12983 } else {
12984 $this->x += $w;
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).
12999 * @public
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)) {
13005 $x = $this->x;
13007 if (TCPDF_STATIC::empty_string($y)) {
13008 $y = $this->y;
13010 // check page for no-write regions and adapt page margins if necessary
13011 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13012 if ($js) {
13013 $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
13014 $s = '';
13015 foreach ($values as $value) {
13016 if (is_array($value)) {
13017 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13018 } else {
13019 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13022 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13023 return;
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.' ';
13036 $text = '';
13037 foreach($values as $item) {
13038 if (is_array($item)) {
13039 $text .= $item[1]."\n";
13040 } else {
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();
13047 --$this->n;
13048 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13049 unset($this->xobjects[$tmpid]);
13050 $popt['ap']['n'] .= 'Q EMC';
13051 // merge options
13052 $opt = array_merge($popt, $opt);
13053 // set remaining annotation data
13054 $opt['Subtype'] = 'Widget';
13055 $opt['ft'] = 'Ch';
13056 $opt['t'] = $name;
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);
13067 if ($this->rtl) {
13068 $this->x -= $w;
13069 } else {
13070 $this->x += $w;
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).
13085 * @public
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)) {
13091 $x = $this->x;
13093 if (TCPDF_STATIC::empty_string($y)) {
13094 $y = $this->y;
13096 // check page for no-write regions and adapt page margins if necessary
13097 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13098 if ($js) {
13099 $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
13100 $s = '';
13101 foreach ($values as $value) {
13102 if (is_array($value)) {
13103 $s .= ',[\''.addslashes($value[1]).'\',\''.addslashes($value[0]).'\']';
13104 } else {
13105 $s .= ',[\''.addslashes($value).'\',\''.addslashes($value).'\']';
13108 $this->javascript .= 'f'.$name.'.setItems('.substr($s, 1).');'."\n";
13109 return;
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.' ';
13123 $text = '';
13124 foreach($values as $item) {
13125 if (is_array($item)) {
13126 $text .= $item[1]."\n";
13127 } else {
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();
13134 --$this->n;
13135 $popt['ap']['n'] .= $this->xobjects[$tmpid]['outdata'];
13136 unset($this->xobjects[$tmpid]);
13137 $popt['ap']['n'] .= 'Q EMC';
13138 // merge options
13139 $opt = array_merge($popt, $opt);
13140 // set remaining annotation data
13141 $opt['Subtype'] = 'Widget';
13142 $opt['ft'] = 'Ch';
13143 $opt['t'] = $name;
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);
13154 if ($this->rtl) {
13155 $this->x -= $w;
13156 } else {
13157 $this->x += $w;
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).
13172 * @public
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)) {
13178 $x = $this->x;
13180 if (TCPDF_STATIC::empty_string($y)) {
13181 $y = $this->y;
13183 // check page for no-write regions and adapt page margins if necessary
13184 list($x, $y) = $this->checkPageRegions($w, $x, $y);
13185 if ($js) {
13186 $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
13187 return;
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);
13215 // merge options
13216 $opt = array_merge($popt, $opt);
13217 // set remaining annotation data
13218 $opt['Subtype'] = 'Widget';
13219 $opt['ft'] = 'Btn';
13220 $opt['t'] = $name;
13221 if (TCPDF_STATIC::empty_string($onvalue)) {
13222 $onvalue = 'Yes';
13224 $opt['opt'] = array($onvalue);
13225 if ($checked) {
13226 $opt['v'] = array('/Yes');
13227 $opt['as'] = 'Yes';
13228 } else {
13229 $opt['v'] = array('/Off');
13230 $opt['as'] = 'Off';
13232 $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
13233 if ($this->rtl) {
13234 $this->x -= $w;
13235 } else {
13236 $this->x += $w;
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).
13252 * @public
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)) {
13258 $x = $this->x;
13260 if (TCPDF_STATIC::empty_string($y)) {
13261 $y = $this->y;
13263 // check page for no-write regions and adapt page margins if necessary
13264 list($x, $y) = $this->checkPageRegions($h, $x, $y);
13265 if ($js) {
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";
13271 return;
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
13288 $border = array(
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();
13296 --$this->n;
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);
13311 // merge options
13312 $opt = array_merge($popt, $opt);
13313 // set remaining annotation data
13314 $opt['Subtype'] = 'Widget';
13315 $opt['ft'] = 'Btn';
13316 $opt['t'] = $caption;
13317 $opt['v'] = $name;
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);
13333 $opt['aa'] .= ']';
13334 } elseif (($key == 'Flags')) {
13335 $ff = 0;
13336 if (is_array($val)) {
13337 foreach ($val AS $flag) {
13338 switch ($flag) {
13339 case 'Include/Exclude': {
13340 $ff += 1 << 0;
13341 break;
13343 case 'IncludeNoValueFields': {
13344 $ff += 1 << 1;
13345 break;
13347 case 'ExportFormat': {
13348 $ff += 1 << 2;
13349 break;
13351 case 'GetMethod': {
13352 $ff += 1 << 3;
13353 break;
13355 case 'SubmitCoordinates': {
13356 $ff += 1 << 4;
13357 break;
13359 case 'XFDF': {
13360 $ff += 1 << 5;
13361 break;
13363 case 'IncludeAppendSaves': {
13364 $ff += 1 << 6;
13365 break;
13367 case 'IncludeAnnotations': {
13368 $ff += 1 << 7;
13369 break;
13371 case 'SubmitPDF': {
13372 $ff += 1 << 8;
13373 break;
13375 case 'CanonicalFormat': {
13376 $ff += 1 << 9;
13377 break;
13379 case 'ExclNonUserAnnots': {
13380 $ff += 1 << 10;
13381 break;
13383 case 'ExclFKey': {
13384 $ff += 1 << 11;
13385 break;
13387 case 'EmbedForm': {
13388 $ff += 1 << 13;
13389 break;
13393 } else {
13394 $ff = intval($val);
13396 $opt['aa'] .= ' /Flags '.$ff;
13399 $opt['aa'] .= ' >>';
13400 } else {
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);
13407 if ($this->rtl) {
13408 $this->x -= $w;
13409 } else {
13410 $this->x += $w;
13414 // --- END FORMS FIELDS ------------------------------------------------
13417 * Add certification signature (DocMDP or UR3)
13418 * You can set only one signature type
13419 * @protected
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']))) {
13425 return;
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';
13443 } else {
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<********************************>';
13473 $out .= ' >>';
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);
13489 $out .= ' >>';
13490 $out .= "\n".'endobj';
13491 $this->_out($out);
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.
13507 * @public
13508 * @author Nicola Asuni
13509 * @since 2.9.000 (2008-03-26)
13511 public function setUserRights(
13512 $enable=true,
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',
13518 $formex='') {
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
13544 * @public
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;
13554 ++$this->n;
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.
13581 * @public
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.
13597 * @public
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='') {
13602 ++$this->n;
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.
13615 * @protected
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='') {
13620 $sigapp = array();
13621 if (($page < 1) OR ($page > $this->numpages)) {
13622 $sigapp['page'] = $this->page;
13623 } else {
13624 $sigapp['page'] = intval($page);
13626 if (empty($name)) {
13627 $sigapp['name'] = 'Signature';
13628 } else {
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));
13636 return $sigapp;
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)
13647 * @public
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;
13662 } else {
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
13675 * @protected
13676 * @author Richard Stockinger
13677 * @since 6.0.090 (2014-06-16)
13679 protected function applyTSA($signature) {
13680 if (!$this->tsa_timestamp) {
13681 return $signature;
13683 //@TODO: implement this feature
13684 return $signature;
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).
13691 * @public
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)
13705 * @public
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.
13714 * @return string
13715 * @since 5.9.099 (2011-06-27)
13716 * @public
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);
13723 if ($wrep > 0) {
13724 $wdiff = max(1, ($this->GetStringWidth($ref) / $wrep));
13725 } else {
13726 $wdiff = 1;
13728 $sdiff = sprintf('%F', $wdiff);
13729 $alias = TCPDF_STATIC::$alias_right_shift.$sdiff.'}';
13730 if ($this->isUnicodeFont()) {
13731 $alias = '{'.$alias;
13733 return $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.
13740 * @return string
13741 * @since 4.0.018 (2008-08-08)
13742 * @public
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.
13755 * @return string
13756 * @since 4.5.000 (2009-01-02)
13757 * @public
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
13771 * @public
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
13786 * @public
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
13799 * @public
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.
13808 * @public
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.
13818 * @public
13819 * @since 4.2.005 (2008-11-06)
13820 * @see PaneNo(), formatPageNumber()
13822 public function PageNoFormatted() {
13823 return TCPDF_STATIC::formatPageNumber($this->PageNo());
13827 * Put pdf layers.
13828 * @protected
13829 * @since 3.0.000 (2008-03-27)
13831 protected function _putocg() {
13832 if (empty($this->pdflayers)) {
13833 return;
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').'>>';
13844 $out .= ' >> >>';
13845 $out .= "\n".'endobj';
13846 $this->_out($out);
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
13856 * @public
13857 * @since 5.9.102 (2011-07-13)
13859 public function startLayer($name='', $print=true, $view=true, $lock=true) {
13860 if ($this->state != 2) {
13861 return;
13863 $layer = sprintf('LYR%03d', (count($this->pdflayers) + 1));
13864 if (empty($name)) {
13865 $name = $layer;
13866 } else {
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.
13876 * @public
13877 * @since 5.9.102 (2011-07-13)
13879 public function endLayer() {
13880 if ($this->state != 2) {
13881 return;
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.
13895 * @public
13896 * @since 3.0.000 (2008-03-27)
13898 public function setVisibility($v) {
13899 if ($this->state != 2) {
13900 return;
13902 $this->endLayer();
13903 switch($v) {
13904 case 'print': {
13905 $this->startLayer('Print', true, false);
13906 break;
13908 case 'view':
13909 case 'screen': {
13910 $this->startLayer('View', false, true);
13911 break;
13913 case 'all': {
13914 $this->_out('');
13915 break;
13917 default: {
13918 $this->Error('Incorrect visibility: '.$v);
13919 break;
13925 * Add transparency parameters to the current extgstate
13926 * @param array $parms parameters
13927 * @return int|void the number of extgstates
13928 * @protected
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
13934 return;
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
13944 return $i;
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];
13953 return $n;
13957 * Add an extgstate
13958 * @param int $gs extgstate
13959 * @protected
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
13965 return;
13967 $this->_out(sprintf('/GS%d gs', $gs));
13971 * Put extgstates for object transparency
13972 * @protected
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) {
13983 $v = 'true';
13984 } elseif ($v === false) {
13985 $v = 'false';
13987 $out .= ' /'.$k.' '.$v;
13989 $out .= ' >>';
13990 $out .= "\n".'endobj';
13991 $this->_out($out);
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).
14001 * @public
14002 * @since 5.9.152 (2012-03-23)
14004 public function setOverprint($stroking=true, $nonstroking=null, $mode=0) {
14005 if ($this->state != 2) {
14006 return;
14008 $stroking = $stroking ? true : false;
14009 if (TCPDF_STATIC::empty_string($nonstroking)) {
14010 // default value if not set
14011 $nonstroking = $stroking;
14012 } else {
14013 $nonstroking = $nonstroking ? true : false;
14015 if (($mode != 0) AND ($mode != 1)) {
14016 $mode = 0;
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>
14027 * @public
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
14040 * @public
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
14046 return;
14048 $stroking = floatval($stroking);
14049 if (TCPDF_STATIC::empty_string($nonstroking)) {
14050 // default value if not set
14051 $nonstroking = $stroking;
14052 } else {
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'))) {
14060 $bm = 'Normal';
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>
14072 * @public
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
14082 * @public
14083 * @since 3.0.000 (2008-03-27)
14085 public function setJPEGQuality($quality) {
14086 if (($quality < 1) OR ($quality > 100)) {
14087 $quality = 75;
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
14095 * @public
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).
14105 * @public
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.
14114 * @public
14115 * @return float
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.
14125 * @public
14126 * @since 3.1.000 (2008-06-09)
14128 public function setPDFVersion($version='1.7') {
14129 if ($this->pdfa_mode && $this->pdfa_version == 1 ) {
14130 // PDF/A-1 mode
14131 $this->PDFVersion = '1.4';
14132 } elseif ($this->pdfa_mode && $this->pdfa_version >= 2 ) {
14133 // PDF/A-2 mode
14134 $this->PDFVersion = '1.7';
14135 } else {
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
14146 * @public
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)
14164 * @public
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
14169 $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);
14176 } else {
14177 $colors = str_replace('ALLSPOT', 'NONE', $colors);
14180 $bars = explode(',', $colors);
14181 $numbars = count($bars); // number of bars to print
14182 if ($numbars <= 0) {
14183 return;
14185 // set bar measures
14186 if ($vertical) {
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
14192 } else {
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
14199 $xb = $x;
14200 $yb = $y;
14201 foreach ($bars as $col) {
14202 switch ($col) {
14203 // set transition colors
14204 case 'A': { // BLACK (GRAYSCALE)
14205 $col_a = array(255);
14206 $col_b = array(0);
14207 break;
14209 case 'W': { // WHITE (GRAYSCALE)
14210 $col_a = array(0);
14211 $col_b = array(255);
14212 break;
14214 case 'R': { // RED (RGB)
14215 $col_a = array(255,255,255);
14216 $col_b = array(255,0,0);
14217 break;
14219 case 'G': { // GREEN (RGB)
14220 $col_a = array(255,255,255);
14221 $col_b = array(0,255,0);
14222 break;
14224 case 'B': { // BLUE (RGB)
14225 $col_a = array(255,255,255);
14226 $col_b = array(0,0,255);
14227 break;
14229 case 'C': { // CYAN (CMYK)
14230 $col_a = array(0,0,0,0);
14231 $col_b = array(100,0,0,0);
14232 break;
14234 case 'M': { // MAGENTA (CMYK)
14235 $col_a = array(0,0,0,0);
14236 $col_b = array(0,100,0,0);
14237 break;
14239 case 'Y': { // YELLOW (CMYK)
14240 $col_a = array(0,0,0,0);
14241 $col_b = array(0,0,100,0);
14242 break;
14244 case 'K': { // KEY - BLACK (CMYK)
14245 $col_a = array(0,0,0,0);
14246 $col_b = array(0,0,0,100);
14247 break;
14249 case 'RGB': { // BLACK REGISTRATION (RGB)
14250 $col_a = array(255,255,255);
14251 $col_b = array(0,0,0);
14252 break;
14254 case 'CMYK': { // BLACK REGISTRATION (CMYK)
14255 $col_a = array(0,0,0,0);
14256 $col_b = array(100,100,100,100);
14257 break;
14259 case 'ALL': { // SPOT COLOR REGISTRATION
14260 $col_a = array(0,0,0,0,'None');
14261 $col_b = array(100,100,100,100,'All');
14262 break;
14264 case 'NONE': { // SKIP THIS COLOR
14265 $col_a = array(0,0,0,0,'None');
14266 $col_b = array(0,0,0,0,'None');
14267 break;
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');
14276 break;
14279 if ($col != 'NONE') {
14280 if ($transition) {
14281 // color gradient
14282 $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
14283 } else {
14284 $this->setFillColorArray($col_b);
14285 // colored rectangle
14286 $this->Rect($xb, $yb, $wb, $hb, 'F', array());
14288 $xb += $xd;
14289 $yb += $yd;
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)
14304 * @public
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) {
14327 switch ($crop) {
14328 case 'T':
14329 case 'TOP': {
14330 $x1 = $x;
14331 $y1 = ($y - $h);
14332 $x2 = $x;
14333 $y2 = ($y - $dh);
14334 break;
14336 case 'F':
14337 case 'BOTTOM': {
14338 $x1 = $x;
14339 $y1 = ($y + $dh);
14340 $x2 = $x;
14341 $y2 = ($y + $h);
14342 break;
14344 case 'L':
14345 case 'LEFT': {
14346 $x1 = ($x - $w);
14347 $y1 = $y;
14348 $x2 = ($x - $dw);
14349 $y2 = $y;
14350 break;
14352 case 'R':
14353 case 'RIGHT': {
14354 $x1 = ($x + $dw);
14355 $y1 = $y;
14356 $x2 = ($x + $w);
14357 $y2 = $y;
14358 break;
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)
14375 * @public
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);
14383 if ($double) {
14384 $ri = $r * 0.5;
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)
14402 * @public
14404 public function registrationMarkCMYK($x, $y, $r) {
14405 // line width
14406 $lw = max((0.5 / $this->k),($r / 8));
14407 // internal radius
14408 $ri = ($r * 0.6);
14409 // external radius
14410 $re = ($r * 1.3);
14411 // Cyan
14412 $this->setFillColorArray(array(100,0,0,0));
14413 $this->PieSector($x, $y, $ri, 270, 360, 'F');
14414 // Magenta
14415 $this->setFillColorArray(array(0,100,0,0));
14416 $this->PieSector($x, $y, $ri, 0, 90, 'F');
14417 // Yellow
14418 $this->setFillColorArray(array(0,0,100,0));
14419 $this->PieSector($x, $y, $ri, 90, 180, 'F');
14420 // Key - black
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'));
14426 // external circle
14427 $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
14428 // cross lines
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)
14446 * @public
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)
14464 * @public
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)
14487 * @public
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)) {
14491 return;
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];
14530 } else {
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)
14579 * @protected
14581 protected function Clip($x, $y, $w, $h) {
14582 if ($this->state != 2) {
14583 return;
14585 if ($this->rtl) {
14586 $x = $this->w - $x - $w;
14588 //save current Graphic State
14589 $s = 'q';
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);
14594 $this->_out($s);
14598 * Output gradient.
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)
14606 * @public
14608 public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
14609 if (($this->pdfa_mode && $this->pdfa_version < 2) OR ($this->state != 2)) {
14610 return;
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;
14619 // color space
14620 $numcolspace = count($stops[0]['color']);
14621 $bcolor = array_values($background);
14622 switch($numcolspace) {
14623 case 5: // SPOT
14624 case 4: { // CMYK
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);
14629 break;
14631 case 3: { // RGB
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);
14636 break;
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);
14643 break;
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'];
14653 } else {
14654 if ($key == 0) {
14655 $this->gradients[$n]['colors'][$key]['offset'] = 0;
14656 } elseif ($key == $last_stop_id) {
14657 $this->gradients[$n]['colors'][$key]['offset'] = 1;
14658 } else {
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;
14668 } else {
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'];
14674 } else {
14675 $this->gradients[$n]['colors'][$key]['exponent'] = 1;
14677 // set colors
14678 $color = array_values($stop['color']);
14679 switch($numcolspace) {
14680 case 5: // SPOT
14681 case 4: { // CMYK
14682 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F %F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
14683 break;
14685 case 3: { // RGB
14686 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F %F %F', $color[0]/255, $color[1]/255, $color[2]/255);
14687 break;
14689 case 1: { // GRAY SCALE
14690 $this->gradients[$n]['colors'][$key]['color'] = sprintf('%F', $color[0]/255);
14691 break;
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)
14713 * @protected
14715 function _putshaders() {
14716 if ($this->pdfa_mode && $this->pdfa_version < 2) {
14717 return;
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();
14723 $out = '<<';
14724 $out .= ' /FunctionType 3';
14725 $out .= ' /Domain [0 1]';
14726 $functions = '';
14727 $bounds = '';
14728 $encode = '';
14729 $i = 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']);
14737 $encode .= '0 1 ';
14739 $out .= ' /Functions ['.trim($functions).']';
14740 $out .= ' /Bounds ['.trim($bounds).']';
14741 $out .= ' /Encode ['.trim($encode).']';
14742 $out .= ' >>';
14743 $out .= "\n".'endobj';
14744 $this->_out($out);
14745 for ($i = 1; $i < $num_cols; ++$i) {
14746 $this->_newobj();
14747 $out = '<<';
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'];
14753 $out .= ' >>';
14754 $out .= "\n".'endobj';
14755 $this->_out($out);
14757 // set transparency functions
14758 if ($grad['transparency']) {
14759 $ft = $this->_newobj();
14760 $out = '<<';
14761 $out .= ' /FunctionType 3';
14762 $out .= ' /Domain [0 1]';
14763 $functions = '';
14764 $i = 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).']';
14772 $out .= ' >>';
14773 $out .= "\n".'endobj';
14774 $this->_out($out);
14775 for ($i = 1; $i < $num_cols; ++$i) {
14776 $this->_newobj();
14777 $out = '<<';
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'];
14783 $out .= ' >>';
14784 $out .= "\n".'endobj';
14785 $this->_out($out);
14789 // set shading object
14790 $this->_newobj();
14791 $out = '<< /ShadingType '.$grad['type'];
14792 if (isset($grad['colspace'])) {
14793 $out .= ' /ColorSpace /'.$grad['colspace'];
14794 } else {
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]';
14808 $out .= ' >>';
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]';
14816 $out .= ' >>';
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);
14824 $out .= ' >>';
14825 $out .= ' stream'."\n".$stream."\n".'endstream';
14827 $out .= "\n".'endobj';
14828 $this->_out($out);
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
14835 $this->_newobj();
14836 $out = '<< /Type /Pattern /PatternType 2';
14837 $out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
14838 $out .= ' >>';
14839 $out .= "\n".'endobj';
14840 $this->_out($out);
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;
14846 $this->_newobj();
14847 $this->_out($shading_transparency);
14848 $this->gradients[$idgs]['id'] = $this->n;
14849 $this->_newobj();
14850 $out = '<< /Type /Pattern /PatternType 2';
14851 $out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
14852 $out .= ' >>';
14853 $out .= "\n".'endobj';
14854 $this->_out($out);
14855 $this->gradients[$idgs]['pattern'] = $this->n;
14856 // luminosity XObject
14857 $oid = $this->_newobj();
14858 $this->xobjects['LX'.$oid] = array('n' => $oid);
14859 $filter = '';
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 >>';
14874 $out .= ' >>';
14875 $out .= ' >> ';
14876 $out .= ' stream'."\n".$stream."\n".'endstream';
14877 $out .= "\n".'endobj';
14878 $this->_out($out);
14879 // SMask
14880 $this->_newobj();
14881 $out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
14882 $this->_out($out);
14883 // ExtGState
14884 $this->_newobj();
14885 $out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
14886 $this->_out($out);
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)
14905 * @public
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)
14926 * @public
14928 public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
14929 if ($this->state != 2) {
14930 return;
14932 if ($this->rtl) {
14933 $xc = ($this->w - $xc);
14935 $op = TCPDF_STATIC::getPathPaintOperator($style);
14936 if ($op == 'f') {
14937 $line_style = array();
14939 if ($cw) {
14940 $d = $b;
14941 $b = (360 - $a + $o);
14942 $a = (360 - $d + $o);
14943 } else {
14944 $b += $o;
14945 $a += $o;
14947 $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
14948 $this->_out($op);
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)
14970 * @public
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) {
14974 return;
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)) {
14981 $x = $this->x;
14983 if (TCPDF_STATIC::empty_string($y)) {
14984 $y = $this->y;
14986 // check page for no-write regions and adapt page margins if necessary
14987 list($x, $y) = $this->checkPageRegions($h, $x, $y);
14988 $k = $this->k;
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);
14997 $regs = array();
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');
15012 if ($start > 0) {
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]));
15019 } else {
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');
15034 if ($end) {
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);
15055 // set alignment
15056 $this->img_rb_y = $y + $h;
15057 // set alignment
15058 if ($this->rtl) {
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;
15065 } else {
15066 $ximg = $x - $w;
15068 $this->img_rb_x = $ximg;
15069 } else {
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;
15076 } else {
15077 $ximg = $x;
15079 $this->img_rb_x = $ximg + $w;
15081 if ($useBoundingBox) {
15082 $dx = $ximg * $k - $x1;
15083 $dy = $y * $k - $y1;
15084 } else {
15085 $dx = $ximg * $k;
15086 $dy = $y * $k;
15088 // save the current graphic state
15089 $this->_out('q'.$this->epsmarker);
15090 // translate
15091 $this->_out(sprintf('%F %F %F %F %F %F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
15092 // scale
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);
15096 $u=0;
15097 $cnt = count($lines);
15098 for ($i=0; $i < $cnt; ++$i) {
15099 $line = $lines[$i];
15100 if (($line == '') OR ($line[0] == '%')) {
15101 continue;
15103 $len = strlen($line);
15104 // check for spot color names
15105 $color_name = '';
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));
15118 // RGB
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!
15124 continue;
15126 $skip = false;
15127 if ($fixoutvals) {
15128 // check for values outside the bounding box
15129 switch ($cmd) {
15130 case 'm':
15131 case 'l':
15132 case 'L': {
15133 // skip values outside bounding box
15134 foreach ($chunks as $key => $val) {
15135 if ((($key % 2) == 0) AND (($val < $x1) OR ($val > $x2))) {
15136 $skip = true;
15137 } elseif ((($key % 2) != 0) AND (($val < $y1) OR ($val > $y2))) {
15138 $skip = true;
15144 switch ($cmd) {
15145 case 'm':
15146 case 'l':
15147 case 'v':
15148 case 'y':
15149 case 'c':
15150 case 'k':
15151 case 'K':
15152 case 'g':
15153 case 'G':
15154 case 's':
15155 case 'S':
15156 case 'J':
15157 case 'j':
15158 case 'w':
15159 case 'M':
15160 case 'd':
15161 case 'n': {
15162 if ($skip) {
15163 break;
15165 $this->_out($line);
15166 break;
15168 case 'x': {// custom fill color
15169 if (empty($color_name)) {
15170 // CMYK color
15171 list($col_c, $col_m, $col_y, $col_k) = $chunks;
15172 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' k');
15173 } else {
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);
15180 break;
15182 case 'X': { // custom stroke color
15183 if (empty($color_name)) {
15184 // CMYK color
15185 list($col_c, $col_m, $col_y, $col_k) = $chunks;
15186 $this->_out(''.$col_c.' '.$col_m.' '.$col_y.' '.$col_k.' K');
15187 } else {
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);
15194 break;
15196 case 'Y':
15197 case 'N':
15198 case 'V':
15199 case 'L':
15200 case 'C': {
15201 if ($skip) {
15202 break;
15204 $line[($len - 1)] = strtolower($cmd);
15205 $this->_out($line);
15206 break;
15208 case 'b':
15209 case 'B': {
15210 $this->_out($cmd . '*');
15211 break;
15213 case 'f':
15214 case 'F': {
15215 if ($u > 0) {
15216 $isU = false;
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')));
15221 if ($isU) {
15222 $this->_out('f*');
15224 } else {
15225 $this->_out('f*');
15227 break;
15229 case '*u': {
15230 ++$u;
15231 break;
15233 case '*U': {
15234 --$u;
15235 break;
15239 // restore previous graphic state
15240 $this->_out($this->epsmarker.'Q');
15241 if (!empty($border)) {
15242 $bx = $this->x;
15243 $by = $this->y;
15244 $this->x = $ximg;
15245 if ($this->rtl) {
15246 $this->x += $w;
15248 $this->y = $y;
15249 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
15250 $this->x = $bx;
15251 $this->y = $by;
15253 if ($link) {
15254 $this->Link($ximg, $y, $w, $h, $link, 0);
15256 // set pointer to align the next text/objects
15257 switch($align) {
15258 case 'T':{
15259 $this->y = $y;
15260 $this->x = $this->img_rb_x;
15261 break;
15263 case 'M':{
15264 $this->y = $y + round($h/2);
15265 $this->x = $this->img_rb_x;
15266 break;
15268 case 'B':{
15269 $this->y = $this->img_rb_y;
15270 $this->x = $this->img_rb_x;
15271 break;
15273 case 'N':{
15274 $this->setY($this->img_rb_y);
15275 break;
15277 default:{
15278 break;
15281 $this->endlinex = $this->img_rb_x;
15285 * Set document barcode.
15286 * @param string $bc barcode
15287 * @public
15289 public function setBarcode($bc='') {
15290 $this->barcode = $bc;
15294 * Get current barcode.
15295 * @return string
15296 * @public
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)
15331 * @public
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))) {
15335 return;
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;
15360 } else {
15361 $style['fitwidth'] = false;
15364 if ($style['fitwidth']) {
15365 // disable stretch
15366 $style['stretch'] = false;
15368 if (!isset($style['stretch'])) {
15369 if (($w === '') OR ($w <= 0)) {
15370 $style['stretch'] = false;
15371 } else {
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;
15384 $fontsize = 0;
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)) {
15398 $x = $this->x;
15400 if (TCPDF_STATIC::empty_string($y)) {
15401 $y = $this->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)) {
15406 if ($this->rtl) {
15407 $w = $x - $this->lMargin;
15408 } else {
15409 $w = $this->w - $this->rMargin - $x;
15412 // padding
15413 if (!isset($style['padding'])) {
15414 $padding = 0;
15415 } elseif ($style['padding'] === 'auto') {
15416 $padding = 10 * ($w / ($arrcode['maxw'] + 20));
15417 } else {
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));
15425 } else {
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);
15433 } else {
15434 $vpadding = floatval($style['vpadding']);
15436 // calculate xres (single bar width)
15437 $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
15438 if ($style['stretch']) {
15439 $xres = $max_xres;
15440 } else {
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
15446 $xres = $max_xres;
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']) {
15457 $wold = $w;
15458 $w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
15459 if (isset($style['cellfitalign'])) {
15460 switch ($style['cellfitalign']) {
15461 case 'L': {
15462 if ($this->rtl) {
15463 $x -= ($wold - $w);
15465 break;
15467 case 'R': {
15468 if (!$this->rtl) {
15469 $x += ($wold - $w);
15471 break;
15473 case 'C': {
15474 if ($this->rtl) {
15475 $x -= (($wold - $w) / 2);
15476 } else {
15477 $x += (($wold - $w) / 2);
15479 break;
15481 default : {
15482 break;
15487 $text_height = $this->getCellHeight($fontsize / $this->k);
15488 // height
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);
15494 if ($barh <=0) {
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);
15508 // set alignment
15509 $this->img_rb_y = $y + $h;
15510 // set alignment
15511 if ($this->rtl) {
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;
15518 } else {
15519 $xpos = $x - $w;
15521 $this->img_rb_x = $xpos;
15522 } else {
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;
15529 } else {
15530 $xpos = $x;
15532 $this->img_rb_x = $xpos + $w;
15534 $xpos_rect = $xpos;
15535 if (!isset($style['align'])) {
15536 $style['align'] = 'C';
15538 switch ($style['align']) {
15539 case 'L': {
15540 $xpos = $xpos_rect + $hpadding;
15541 break;
15543 case 'R': {
15544 $xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
15545 break;
15547 case 'C':
15548 default : {
15549 $xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
15550 break;
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']);
15566 // print bars
15567 foreach ($arrcode['bcode'] as $k => $v) {
15568 $bw = ($v['w'] * $xres);
15569 if ($v['t']) {
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']);
15574 $xpos += $bw;
15576 // print text
15577 if ($style['text']) {
15578 if (isset($style['label']) AND !TCPDF_STATIC::empty_string($style['label'])) {
15579 $label = $style['label'];
15580 } else {
15581 $label = $code;
15583 $txtwidth = ($arrcode['maxw'] * $xres);
15584 if ($this->GetStringWidth($label) > $txtwidth) {
15585 $style['stretchtext'] = 2;
15587 // print text
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
15600 switch($align) {
15601 case 'T':{
15602 $this->y = $y;
15603 $this->x = $this->img_rb_x;
15604 break;
15606 case 'M':{
15607 $this->y = $y + round($h / 2);
15608 $this->x = $this->img_rb_x;
15609 break;
15611 case 'B':{
15612 $this->y = $this->img_rb_y;
15613 $this->x = $this->img_rb_x;
15614 break;
15616 case 'N':{
15617 $this->setY($this->img_rb_y);
15618 break;
15620 default:{
15621 break;
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)
15649 * @public
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))) {
15653 return;
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;
15677 // padding
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)) {
15703 $x = $this->x;
15705 if (TCPDF_STATIC::empty_string($y)) {
15706 $y = $this->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
15723 if ($this->rtl) {
15724 $maxw = $x - $this->lMargin;
15725 } else {
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));
15731 if (!$distort) {
15732 if (($maxw * $ratioHW) > $maxh) {
15733 $maxw = $maxh * $ratioWH;
15735 if (($maxh * $ratioWH) > $maxw) {
15736 $maxh = $maxw * $ratioHW;
15739 // set maximum dimensions
15740 if ($w > $maxw) {
15741 $w = $maxw;
15743 if ($h > $maxh) {
15744 $h = $maxh;
15746 // set 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
15759 $cw = $bw / $cols;
15760 $ch = $bh / $rows;
15761 if (!$distort) {
15762 if (($cw / $ch) > ($mw / $mh)) {
15763 // correct horizontal distortion
15764 $cw = $ch * $mw / $mh;
15765 $bw = $cw * $cols;
15766 $style['hpadding'] = ($w - $bw) / (2 * $cw);
15767 } else {
15768 // correct vertical distortion
15769 $ch = $cw * $mh / $mw;
15770 $bh = $ch * $rows;
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);
15776 // set alignment
15777 $this->img_rb_y = $y + $h;
15778 // set alignment
15779 if ($this->rtl) {
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;
15786 } else {
15787 $xpos = $x - $w;
15789 $this->img_rb_x = $xpos;
15790 } else {
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;
15797 } else {
15798 $xpos = $x;
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
15816 // for each row
15817 for ($r = 0; $r < $rows; ++$r) {
15818 $xr = $xstart;
15819 // for each column
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']);
15825 $xr += $cw;
15827 $ystart += $ch;
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
15834 switch($align) {
15835 case 'T':{
15836 $this->y = $y;
15837 $this->x = $this->img_rb_x;
15838 break;
15840 case 'M':{
15841 $this->y = $y + round($h/2);
15842 $this->x = $this->img_rb_x;
15843 break;
15845 case 'B':{
15846 $this->y = $this->img_rb_y;
15847 $this->x = $this->img_rb_x;
15848 break;
15850 case 'N':{
15851 $this->setY($this->img_rb_y);
15852 break;
15854 default:{
15855 break;
15858 $this->endlinex = $this->img_rb_x;
15862 * Returns an array containing current margins:
15863 * <ul>
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>
15875 * </ul>
15876 * @return array containing all margins measures
15877 * @public
15878 * @since 3.2.000 (2008-06-23)
15880 public function getMargins() {
15881 $ret = array(
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']
15894 return $ret;
15898 * Returns an array containing original margins:
15899 * <ul>
15900 <li>$ret['left'] = left margin</li>
15901 <li>$ret['right'] = right margin</li>
15902 * </ul>
15903 * @return array containing all margins measures
15904 * @public
15905 * @since 4.0.012 (2008-07-24)
15907 public function getOriginalMargins() {
15908 $ret = array(
15909 'left' => $this->original_lMargin,
15910 'right' => $this->original_rMargin
15912 return $ret;
15916 * Returns the current font size.
15917 * @return float current font size
15918 * @public
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
15928 * @public
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
15938 * @public
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
15948 * @public
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
15963 * @public
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
15975 * @protected
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);
15985 } else {
15986 $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
15988 return $width;
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)
15995 * @protected
15996 * @since 5.7.000 (2010-08-02)
15998 protected function getCSSBorderDashStyle($style) {
15999 switch (strtolower($style)) {
16000 case 'none':
16001 case 'hidden': {
16002 $dash = -1;
16003 break;
16005 case 'dotted': {
16006 $dash = 1;
16007 break;
16009 case 'dashed': {
16010 $dash = 3;
16011 break;
16013 case 'double':
16014 case 'groove':
16015 case 'ridge':
16016 case 'inset':
16017 case 'outset':
16018 case 'solid':
16019 default: {
16020 $dash = 0;
16021 break;
16024 return $dash;
16028 * Returns the border style array from CSS border properties
16029 * @param string $cssborder border properties
16030 * @return array containing border properties
16031 * @protected
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]);
16039 --$count;
16042 $border = array(); // value to be returned
16043 switch ($count) {
16044 case 2: {
16045 $width = 'medium';
16046 $style = $bprop[0];
16047 $color = $bprop[1];
16048 break;
16050 case 1: {
16051 $width = 'medium';
16052 $style = $bprop[0];
16053 $color = 'black';
16054 break;
16056 case 0: {
16057 $width = 'medium';
16058 $style = 'solid';
16059 $color = 'black';
16060 break;
16062 default: {
16063 $width = $bprop[0];
16064 $style = $bprop[1];
16065 $color = $bprop[2];
16066 break;
16069 if ($style == 'none') {
16070 return array();
16072 $border['cap'] = 'square';
16073 $border['join'] = 'miter';
16074 $border['dash'] = $this->getCSSBorderDashStyle($style);
16075 if ($border['dash'] < 0) {
16076 return array();
16078 $border['width'] = $this->getCSSBorderWidth($width);
16079 $border['color'] = TCPDF_COLORS::convertHTMLColorToDec($color, $this->spot_colors);
16080 return $border;
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
16088 * @public
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)) {
16095 case 4: {
16096 $cell_padding['T'] = $padding[0];
16097 $cell_padding['R'] = $padding[1];
16098 $cell_padding['B'] = $padding[2];
16099 $cell_padding['L'] = $padding[3];
16100 break;
16102 case 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];
16107 break;
16109 case 2: {
16110 $cell_padding['T'] = $padding[0];
16111 $cell_padding['R'] = $padding[1];
16112 $cell_padding['B'] = $padding[0];
16113 $cell_padding['L'] = $padding[1];
16114 break;
16116 case 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];
16121 break;
16123 default: {
16124 return $this->cell_padding;
16127 if ($width == 0) {
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
16142 * @public
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)) {
16149 case 4: {
16150 $cell_margin['T'] = $margin[0];
16151 $cell_margin['R'] = $margin[1];
16152 $cell_margin['B'] = $margin[2];
16153 $cell_margin['L'] = $margin[3];
16154 break;
16156 case 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];
16161 break;
16163 case 2: {
16164 $cell_margin['T'] = $margin[0];
16165 $cell_margin['R'] = $margin[1];
16166 $cell_margin['B'] = $margin[0];
16167 $cell_margin['L'] = $margin[1];
16168 break;
16170 case 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];
16175 break;
16177 default: {
16178 return $this->cell_margin;
16181 if ($width == 0) {
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
16196 * @public
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)) {
16203 case 2: {
16204 $border_spacing['H'] = $space[0];
16205 $border_spacing['V'] = $space[1];
16206 break;
16208 case 1: {
16209 $border_spacing['H'] = $space[0];
16210 $border_spacing['V'] = $space[0];
16211 break;
16213 default: {
16214 return array('H' => 0, 'V' => 0);
16217 if ($width == 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.
16230 * @protected
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) {
16237 case 'normal': {
16238 $val = 0;
16239 break;
16241 case 'inherit': {
16242 if ($parent == 'normal') {
16243 $val = 0;
16244 } else {
16245 $val = $parent;
16247 break;
16249 default: {
16250 $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
16253 return $val;
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
16261 * @protected
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': {
16269 $val = 40;
16270 break;
16272 case 'extra-condensed': {
16273 $val = 55;
16274 break;
16276 case 'condensed': {
16277 $val = 70;
16278 break;
16280 case 'semi-condensed': {
16281 $val = 85;
16282 break;
16284 case 'normal': {
16285 $val = 100;
16286 break;
16288 case 'semi-expanded': {
16289 $val = 115;
16290 break;
16292 case 'expanded': {
16293 $val = 130;
16294 break;
16296 case 'extra-expanded': {
16297 $val = 145;
16298 break;
16300 case 'ultra-expanded': {
16301 $val = 160;
16302 break;
16304 case 'wider': {
16305 $val = ($parent + 10);
16306 break;
16308 case 'narrower': {
16309 $val = ($parent - 10);
16310 break;
16312 case 'inherit': {
16313 if ($parent == 'normal') {
16314 $val = 100;
16315 } else {
16316 $val = $parent;
16318 break;
16320 default: {
16321 $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
16324 return $val;
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
16334 * @public
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);
16339 switch ($val) {
16340 case 'xx-small': {
16341 $size = ($refsize - 4);
16342 break;
16344 case 'x-small': {
16345 $size = ($refsize - 3);
16346 break;
16348 case 'small': {
16349 $size = ($refsize - 2);
16350 break;
16352 case 'medium': {
16353 $size = $refsize;
16354 break;
16356 case 'large': {
16357 $size = ($refsize + 2);
16358 break;
16360 case 'x-large': {
16361 $size = ($refsize + 4);
16362 break;
16364 case 'xx-large': {
16365 $size = ($refsize + 6);
16366 break;
16368 case 'smaller': {
16369 $size = ($parent_size - 3);
16370 break;
16372 case 'larger': {
16373 $size = ($parent_size + 3);
16374 break;
16376 default: {
16377 $parentSize = $this->getHTMLUnitToUnits($parent_size, $refsize, $defaultunit, true);
16378 $size = $this->getHTMLUnitToUnits($val, $parent_size, $defaultunit, true);
16381 return $size;
16385 * Returns the HTML DOM array.
16386 * @param string $html html code
16387 * @return array
16388 * @protected
16389 * @since 3.2.000 (2008-06-20)
16391 protected function getHtmlDomArray($html) {
16392 // array of CSS styles ( selector => properties).
16393 $css = array();
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) {
16406 $type = array();
16407 if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
16408 $type = array();
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')))) {
16413 $type = array();
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) {
16429 $type = array();
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);
16456 $offset = 0;
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&nbsp;\\3</pre>", $html_b);
16468 $html = $html_a.$html_b.substr($html, $pos + 6);
16469 $offset = strlen($html_a.$html_b);
16471 $offset = 0;
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);
16485 $offset = 0;
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>&nbsp;\\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>&nbsp;</li>', $html);
16517 $html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
16518 $html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
16519 $html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\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
16523 // trim string
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);
16533 // count elements
16534 $maxel = count($a);
16535 $elkey = 0;
16536 $key = 0;
16537 // create an array of elements
16538 $dom = array();
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
16566 ++$key;
16567 $level = array();
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)) {
16574 // html tag
16575 $element = substr($element, 1, -1);
16576 // get tag name
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] == '/') {
16582 $thead = false;
16583 } else {
16584 $thead = true;
16586 ++$elkey;
16587 continue;
16589 $dom[$key]['tag'] = true;
16590 $dom[$key]['value'] = $tagname;
16591 if (in_array($dom[$key]['value'], $blocktags)) {
16592 $dom[$key]['block'] = true;
16593 } else {
16594 $dom[$key]['block'] = false;
16596 if ($element[0] == '/') {
16597 // *** closing html tag
16598 $dom[$key]['opening'] = false;
16599 $dom[$key]['parent'] = end($level);
16600 array_pop($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']]);
16629 $key = $i;
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
16637 if (
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>';
16659 } else {
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;
16666 } else {
16667 // opening tag
16668 array_push($level, $key);
16669 $dom[$key]['self'] = false;
16671 // copy some values from parent
16672 $parentkey = 0;
16673 if ($key > 0) {
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'];
16695 // get attributes
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 ---
16716 // text direction
16717 if (isset($dom[$key]['style']['direction'])) {
16718 $dom[$key]['dir'] = $dom[$key]['style']['direction'];
16720 // display
16721 if (isset($dom[$key]['style']['display'])) {
16722 $dom[$key]['hide'] = (trim(strtolower($dom[$key]['style']['display'])) == 'none');
16724 // font family
16725 if (isset($dom[$key]['style']['font-family'])) {
16726 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
16728 // list-style-type
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'];
16735 // text-indent
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'];
16742 // text-transform
16743 if (isset($dom[$key]['style']['text-transform'])) {
16744 $dom[$key]['text-transform'] = $dom[$key]['style']['text-transform'];
16746 // font size
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');
16751 // font-stretch
16752 if (isset($dom[$key]['style']['font-stretch'])) {
16753 $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
16755 // letter-spacing
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
16764 case 'normal': {
16765 $dom[$key]['line-height'] = $dom[0]['line-height'];
16766 break;
16768 case 'inherit': {
16769 $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
16771 default: {
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;
16780 } else {
16781 $dom[$key]['line-height'] = (($dom[$key]['line-height'] - $this->cell_padding['T'] - $this->cell_padding['B']) / $dom[$key]['fontsize']);
16787 // font style
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';
16800 // font color
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);
16810 // text-decoration
16811 if (isset($dom[$key]['style']['text-decoration'])) {
16812 $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
16813 foreach ($decors as $dec) {
16814 $dec = trim($dec);
16815 if (!TCPDF_STATIC::empty_string($dec)) {
16816 if ($dec[0] == 'u') {
16817 // underline
16818 $dom[$key]['fontstyle'] .= 'U';
16819 } elseif ($dec[0] == 'l') {
16820 // line-through
16821 $dom[$key]['fontstyle'] .= 'D';
16822 } elseif ($dec[0] == 'o') {
16823 // overline
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']);
16939 } else {
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']);
16950 } else {
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') {
16998 // font family
16999 if (isset($dom[$key]['attribute']['face'])) {
17000 $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
17002 // font size
17003 if (isset($dom[$key]['attribute']['size'])) {
17004 if ($key > 0) {
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));
17009 } else {
17010 $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
17012 } else {
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'))) {
17020 if ($this->rtl) {
17021 $dom[$key]['align'] = 'R';
17022 } else {
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;
17066 if ($thead) {
17067 $dom[$key]['thead'] = true;
17068 // rows on thead block are printed as a separate table
17069 } else {
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']);
17090 } else {
17091 $colspan = 1;
17093 $dom[$key]['attribute']['colspan'] = $colspan;
17094 $dom[($dom[$key]['parent'])]['cols'] += $colspan;
17096 // text direction
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'])) {
17132 // font fill
17133 if ($dom[$key]['attribute']['fill'] == 'true') {
17134 $dom[$key]['fill'] = true;
17135 } else {
17136 $dom[$key]['fill'] = false;
17139 if (isset($dom[$key]['attribute']['clip'])) {
17140 // clipping mode
17141 if ($dom[$key]['attribute']['clip'] == 'true') {
17142 $dom[$key]['clip'] = true;
17143 } else {
17144 $dom[$key]['clip'] = false;
17147 } // end opening tag
17148 } else {
17149 // text
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));
17165 break;
17167 case 'uppercase': {
17168 $element = strtoupper($element);
17169 break;
17171 case 'lowercase': {
17172 $element = strtolower($element);
17173 break;
17177 $element = preg_replace("/&NBSP;/i", "&nbsp;", $element);
17179 $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
17181 ++$elkey;
17182 ++$key;
17184 return $dom;
17188 * Returns the string used to find spaces
17189 * @return string
17190 * @protected
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);
17199 return $spacestr;
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
17205 * @return string
17206 * @public static
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
17216 * @public static
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()
17258 * @public
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>
17275 * @public
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;
17293 $newline = true;
17294 $startlinepage = $this->page;
17295 $minstartliney = $this->y;
17296 $maxbottomliney = 0;
17297 $startlinex = $this->x;
17298 $startliney = $this->y;
17299 $yshift = 0;
17300 $loop = 0;
17301 $curpos = 0;
17302 $this_method_vars = array();
17303 $undo = false;
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]);
17312 } else {
17313 $pask = 0;
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];
17321 } else {
17322 $this->footerpos[$this->page] = $this->pagelen[$this->page];
17324 $startlinepos = $this->footerpos[$this->page];
17325 } else {
17326 // we are inside the footer
17327 $startlinepos = $this->pagelen[$this->page];
17329 $lalign = $align;
17330 $plalign = $align;
17331 if ($this->rtl) {
17332 $w = $this->x - $this->lMargin;
17333 } else {
17334 $w = $this->w - $this->rMargin - $this->x;
17336 $w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
17337 if ($cell) {
17338 if ($this->rtl) {
17339 $this->x -= $this->cell_padding['R'];
17340 $this->lMargin += $this->cell_padding['L'];
17341 } else {
17342 $this->x += $this->cell_padding['L'];
17343 $this->rMargin += $this->cell_padding['R'];
17346 if ($this->customlistindent >= 0) {
17347 $this->listindent = $this->customlistindent;
17348 } else {
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);
17368 $key = 0;
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
17375 ++$key;
17376 } else {
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
17380 ++$key;
17382 ++$key;
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;
17403 } else {
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) {
17463 $$vkey = $vval;
17465 // disable table header
17466 $tmp_thead = $this->thead;
17467 $this->thead = '';
17468 // add a page (or trig AcceptPageBreak() for multicolumn mode)
17469 $pre_y = $this->y;
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'];
17483 } else {
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'))) )) {
17500 ++$key;
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
17522 --$key;
17526 if (!$autolinebreak) {
17527 if ($this->inPageBody()) {
17528 $pre_y = $this->y;
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]);
17560 } else {
17561 $next_pask = 0;
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)))
17602 )) {
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]);
17625 } else {
17626 $next_pask = 0;
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]);
17655 } else {
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']) {
17687 $plalign = '';
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']);
17693 $wfill = true;
17694 } else {
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)) {
17707 $lalign = $align;
17710 // align lines
17711 if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
17712 $newline = true;
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)) {
17718 $yshift = 0;
17720 $t_x = 0;
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;
17728 } else {
17729 $midpos = 0;
17731 if ($midpos > 0) {
17732 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
17733 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
17734 } else {
17735 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
17736 $pend = '';
17738 } else {
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];
17748 } else {
17749 $midpos = 0;
17751 if ($midpos > 0) {
17752 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
17753 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
17754 } else {
17755 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
17756 $pend = '';
17759 if ((((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
17760 // calculate shifting amount
17761 $tw = $w;
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, '[(');
17776 if ($pos1 > 0) {
17777 $pos1 = intval($pos1);
17778 if ($this->isUnicodeFont()) {
17779 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
17780 $spacelen = 2;
17781 } else {
17782 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
17783 $spacelen = 1;
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, '[(')) {
17790 $no = 1;
17794 } else { // LTR
17795 // remove right space if exist
17796 $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
17797 if ($pos1 > 0) {
17798 $pos1 = intval($pos1);
17799 if ($this->isUnicodeFont()) {
17800 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
17801 $spacelen = 2;
17802 } else {
17803 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
17804 $spacelen = 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') {
17814 if ($this->rtl) {
17815 $t_x = -($mdiff / 2);
17816 } else {
17817 $t_x = ($mdiff / 2);
17819 } elseif ($plalign == 'R') {
17820 // right alignment on LTR document
17821 $t_x = $mdiff;
17822 } elseif ($plalign == 'L') {
17823 // left alignment on RTL document
17824 $t_x = -$mdiff;
17825 } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
17826 // Justification
17827 if ($this->isRTLTextDir()) {
17828 // align text on the left
17829 $t_x = -$mdiff;
17831 $ns = 0; // number of spaces
17832 $pmidtemp = $pmid;
17833 // escape special characters
17834 $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
17835 $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
17836 // search spaces
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;
17850 if ($ns == 0) {
17851 $ns = 1;
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;
17864 $nsmax = $ns;
17865 $ns = 0;
17866 reset($lnstring);
17867 $offset = 0;
17868 $strcount = 0;
17869 $prev_epsposbeg = 0;
17870 $textpos = 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.');
17886 continue;
17888 if ($this->isRTLTextDir()) {
17889 $spacew = ($spacewidth * ($nsmax - $ns));
17890 } else {
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;
17910 continue;
17913 $currentxpos = 0;
17914 // shift blocks of code
17915 switch ($strpiece[2][0]) {
17916 case 'Td':
17917 case 'cm':
17918 case 'm':
17919 case 'l': {
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])) {
17923 break;
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));
17932 ++$strcount;
17934 // justify block
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);
17940 break;
17942 case 're': {
17943 // justify block
17944 if (!TCPDF_STATIC::empty_string($this->lispacer)) {
17945 $this->lispacer = '';
17946 break;
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])) {
17950 break;
17952 $currentxpos = $xmatches[1];
17953 $x_diff = 0;
17954 $w_diff = 0;
17955 if ($this->isRTLTextDir()) { // RTL
17956 if ($currentxpos < $textpos) {
17957 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
17958 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17959 } else {
17960 if ($strcount > 0) {
17961 $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
17962 $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
17965 } else { // LTR
17966 if ($currentxpos > $textpos) {
17967 if ($strcount > 0) {
17968 $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
17970 $w_diff = ($spacewidth * $lnstring[2][$strcount]);
17971 } else {
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);
17987 break;
17989 case 'c': {
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])) {
17993 break;
17995 $currentxpos = $xmatches[1];
17996 // justify block
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);
18005 break;
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);
18018 } else {
18019 $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18021 break;
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);
18030 } else {
18031 $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
18033 break;
18037 } // end of while
18038 // remove markers
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
18048 $pos = 0;
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));
18061 ++$pos;
18063 unset($pamatch);
18065 if ($this->inxobj) {
18066 // we are inside an XObject template
18067 $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
18068 } else {
18069 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
18071 $endlinepos = strlen($pstart."\n".$pmid."\n");
18072 } else {
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;
18083 } else {
18084 $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
18086 $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
18089 } // end of J
18090 } // end if $startlinex
18091 if (($t_x != 0) OR ($yshift < 0)) {
18092 // shift the line
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;
18105 } else {
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);
18128 } else {
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;
18135 } else {
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];
18142 } else {
18143 $this->footerpos[$this->page] = $this->pagelen[$this->page];
18145 $startlinepos = $this->footerpos[$this->page];
18146 } else {
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]);
18154 } else {
18155 $pask = 0;
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);
18161 if ($wfill) {
18162 $this->setFillColorArray($this->bgcolor);
18165 } // end newline
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;
18176 // table
18177 if (($dom[$key]['value'] == 'table') AND isset($dom[$key]['cols']) AND ($dom[$key]['cols'] > 0)) {
18178 // available page width
18179 if ($this->rtl) {
18180 $wtmp = $this->x - $this->lMargin;
18181 } else {
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'];
18190 } else {
18191 $cellspacing = array('H' => 0, 'V' => 0);
18193 // table width
18194 if (isset($dom[$key]['width'])) {
18195 $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
18196 } else {
18197 $table_width = $wtmp;
18199 $table_width -= (2 * $cellspacing['H']);
18200 if (!$this->inthead) {
18201 $this->y += $cellspacing['V'];
18203 if ($this->rtl) {
18204 $cellspacingx = -$cellspacing['H'];
18205 } else {
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);
18215 // table row
18216 if ($dom[$key]['value'] == 'tr') {
18217 // reset column counter
18218 $colid = 0;
18220 // table cell
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
18228 $tdborder = 0;
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) {
18234 $colspan = 1;
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'];
18242 } else {
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');
18249 } else {
18250 $cellh = 0;
18252 if (isset($dom[$key]['content'])) {
18253 $cell_content = $dom[$key]['content'];
18254 } else {
18255 $cell_content = '&nbsp;';
18257 $tagtype = $dom[$key]['value'];
18258 $parentid = $key;
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
18261 ++$key;
18263 if (!isset($dom[$trid]['startpage'])) {
18264 $dom[$trid]['startpage'] = $this->page;
18265 } else {
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']) {
18271 $tmpx = $this->x;
18272 $this->selectColumn($dom[$trid]['startcolumn']);
18273 $this->x = $tmpx;
18275 if (!isset($dom[$trid]['starty'])) {
18276 $dom[$trid]['starty'] = $this->y;
18277 } else {
18278 $this->y = $dom[$trid]['starty'];
18280 if (!isset($dom[$trid]['startx'])) {
18281 $dom[$trid]['startx'] = $this->x;
18282 $this->x += $cellspacingx;
18283 } else {
18284 $this->x += ($cellspacingx / 2);
18286 if (isset($dom[$parentid]['attribute']['rowspan'])) {
18287 $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
18288 } else {
18289 $rowspan = 1;
18291 // skip row-spanned cells started on the previous rows
18292 if (isset($dom[$table_el]['rowspans'])) {
18293 $rsk = 0;
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']);
18303 $rsstartx -= $dl;
18304 $rsendx -= $dl;
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']);
18307 $rsstartx += $dl;
18308 $rsendx += $dl;
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'];
18329 $rsk = 0;
18330 } else {
18331 ++$rsk;
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;
18342 } else {
18343 // inherit column width
18344 $cellw = 0;
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
18370 if ($this->rtl) {
18371 $this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
18372 } else {
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;
18392 } else {
18393 $dom[$trid]['endy'] = $this->y;
18395 if (isset($dom[$trid]['endpage'])) {
18396 $dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
18397 } else {
18398 $dom[$trid]['endpage'] = $this->page;
18400 if (isset($dom[$trid]['endcolumn'])) {
18401 $dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
18402 } else {
18403 $dom[$trid]['endcolumn'] = $this->current_column;
18405 } else {
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'];
18423 } else {
18424 $dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
18430 $this->x += ($cellspacingx / 2);
18431 } else {
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];
18440 } else {
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) {
18460 // print list-item
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);
18479 // text
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;
18494 if ($newline) {
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']);
18500 } else {
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;
18509 $newline = false;
18510 $firstblock = true;
18511 } else {
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']);
18516 $strrest = '';
18517 if ($this->rtl) {
18518 $this->x -= $this->textindent;
18519 } else {
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']))) {
18525 // HTML <a> Link
18526 $hrefcolor = '';
18527 if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
18528 $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
18530 $hrefstyle = -1;
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);
18535 } else {
18536 $wadj = 0; // space to leave for block continuity
18537 if ($this->rtl) {
18538 $cwa = ($this->x - $this->lMargin);
18539 } else {
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']) {
18553 // end of 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']);
18560 } else {
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;
18569 ++$nkey;
18572 if (($wadj > 0) AND (($strlinelen + $wadj) >= $cwa)) {
18573 $wadj = 0;
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)]));
18579 } else {
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
18590 if ($this->rtl) {
18591 $this->x += $revshift;
18592 } else {
18593 $this->x -= $revshift;
18595 $xws = $this->x;
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
18613 ++$loop;
18614 } else {
18615 $loop = 0;
18617 $dom[$key]['value'] = $strrest;
18618 if ($cell) {
18619 if ($this->rtl) {
18620 $this->x -= $this->cell_padding['R'];
18621 } else {
18622 $this->x += $this->cell_padding['L'];
18625 if ($loop < 3) {
18626 --$key;
18628 } else {
18629 $loop = 0;
18630 // add the positive font spacing of the last character (if any)
18631 if ($this->font_spacing > 0) {
18632 if ($this->rtl) {
18633 $this->x -= $this->font_spacing;
18634 } else {
18635 $this->x += $this->font_spacing;
18640 ++$key;
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) {
18649 $$vkey = $vval;
18651 if (!empty($dom[$key]['thead'])) {
18652 $this->inthead = true;
18654 // add a page (or trig AcceptPageBreak() for multicolumn mode)
18655 $pre_y = $this->y;
18656 if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
18657 $startliney = $this->y;
18659 $undo = true; // avoid infinite loop
18660 } else {
18661 $undo = false;
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)) {
18669 $yshift = 0;
18671 $t_x = 0;
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;
18679 } else {
18680 $midpos = 0;
18682 if ($midpos > 0) {
18683 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
18684 $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
18685 } else {
18686 $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
18687 $pend = '';
18689 } else {
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];
18699 } else {
18700 $midpos = 0;
18702 if ($midpos > 0) {
18703 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
18704 $pend = substr($this->getPageBuffer($startlinepage), $midpos);
18705 } else {
18706 $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
18707 $pend = '';
18710 if ((((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
18711 // calculate shifting amount
18712 $tw = $w;
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, '[(');
18724 if ($pos1 > 0) {
18725 $pos1 = intval($pos1);
18726 if ($this->isUnicodeFont()) {
18727 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(0).chr(32)));
18728 $spacelen = 2;
18729 } else {
18730 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, '[('.chr(32)));
18731 $spacelen = 1;
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, '[(')) {
18738 $no = 1;
18742 } else { // LTR
18743 // remove right space if exist
18744 $pos1 = TCPDF_STATIC::revstrpos($pmid, ')]');
18745 if ($pos1 > 0) {
18746 $pos1 = intval($pos1);
18747 if ($this->isUnicodeFont()) {
18748 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(0).chr(32).')]')) + 2;
18749 $spacelen = 2;
18750 } else {
18751 $pos2 = intval(TCPDF_STATIC::revstrpos($pmid, chr(32).')]')) + 1;
18752 $spacelen = 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') {
18762 if ($this->rtl) {
18763 $t_x = -($mdiff / 2);
18764 } else {
18765 $t_x = ($mdiff / 2);
18767 } elseif ($plalign == 'R') {
18768 // right alignment on LTR document
18769 $t_x = $mdiff;
18770 } elseif ($plalign == 'L') {
18771 // left alignment on RTL document
18772 $t_x = -$mdiff;
18774 } // end if startlinex
18775 if (($t_x != 0) OR ($yshift < 0)) {
18776 // shift the line
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;
18789 } else {
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;
18802 $yshift = 0;
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;
18825 unset($dom);
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
18834 * @protected
18836 protected function openHTMLTagHandler($dom, $key, $cell) {
18837 $tag = $dom[$key];
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']);
18843 } else {
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);
18854 } else {
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) {
18860 $on = 0.6;
18861 } else {
18862 $on = 1;
18864 if ((!isset($this->tagvspaces[$tag['value']])) AND (in_array($tag['value'], array('div', 'dt', 'dd', 'li', 'br', 'hr')))) {
18865 $hb = 0;
18866 } else {
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);
18872 } else {
18873 $hbz = $this->getCellHeight($this->FontSize);
18876 if (isset($dom[($key - 1)]) AND ($dom[($key - 1)]['value'] == 'table')) {
18877 // fix vertical space after table
18878 $hbz = 0;
18880 // closing vertical space
18881 $hbc = 0;
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);
18886 } else {
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) {
18892 $cn = 0.6;
18893 } else {
18894 $cn = 1;
18896 if (isset($this->tagvspaces[$tag['value']][1])) {
18897 $hbc = ($cn * $pre_h);
18900 // Opening tag
18901 switch($tag['value']) {
18902 case 'table': {
18903 $cp = 0;
18904 $cs = 0;
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);
18942 break;
18944 case 'tr': {
18945 // array of columns positions
18946 $dom[$key]['cellpos'] = array();
18947 break;
18949 case 'hr': {
18950 if ((isset($tag['height'])) AND ($tag['height'] != '')) {
18951 $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
18952 } else {
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;
18959 if ($cell) {
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');
18964 } else {
18965 $hrWidth = $wtmp;
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)]));
19000 break;
19002 case 'a': {
19003 if (array_key_exists('href', $tag['attribute'])) {
19004 $this->HREF['url'] = $tag['attribute']['href'];
19006 break;
19008 case 'img': {
19009 if (empty($tag['attribute']['src'])) {
19010 break;
19012 $imgsrc = $tag['attribute']['src'];
19013 if ($imgsrc[0] === '@') {
19014 // data stream
19015 $imgsrc = '@'.base64_decode(substr($imgsrc, 1));
19016 $type = '';
19017 } else if (preg_match('@^data:image/([^;]*);base64,(.*)@', $imgsrc, $reg)) {
19018 $imgsrc = '@'.base64_decode($reg[2]);
19019 $type = $reg[1];
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);
19024 } else {
19025 if (($imgsrc[0] === '/') AND !empty($_SERVER['DOCUMENT_ROOT']) AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
19026 // fix image path
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;
19031 } else {
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);
19045 // get image type
19046 $type = TCPDF_IMAGES::getImageFileType($imgsrc);
19048 if (!isset($tag['width'])) {
19049 $tag['width'] = 0;
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']) {
19060 case 'top': {
19061 $align = 'T';
19062 break;
19064 case 'middle': {
19065 $align = 'M';
19066 break;
19068 case 'bottom': {
19069 $align = 'B';
19070 break;
19072 default: {
19073 $align = 'B';
19074 break;
19077 $prevy = $this->y;
19078 $xpos = $this->x;
19079 $imglink = '';
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]);
19092 } else {
19093 $lnky = 0;
19095 $imglink = $this->AddLink();
19096 $this->setLink($imglink, $lnky, $page);
19100 $border = 0;
19101 if (isset($tag['border']) AND !empty($tag['border'])) {
19102 // currently only support 1 (frame) or a combination of 'LTRB'
19103 $border = $tag['border'];
19105 $iw = '';
19106 if (isset($tag['width'])) {
19107 $iw = $this->getHTMLUnitToUnits($tag['width'], ($tag['fontsize'] / $this->k), 'px', false);
19109 $ih = '';
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);
19117 } else {
19118 $this->Image($imgsrc, $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
19120 switch($align) {
19121 case 'T': {
19122 $this->y = $prevy;
19123 break;
19125 case 'M': {
19126 $this->y = (($this->img_rb_y + $prevy - ($this->getCellHeight($tag['fontsize'] / $this->k))) / 2);
19127 break;
19129 case 'B': {
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));
19131 break;
19134 break;
19136 case 'dl': {
19137 ++$this->listnum;
19138 if ($this->listnum == 1) {
19139 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19140 } else {
19141 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19143 break;
19145 case 'dt': {
19146 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19147 break;
19149 case 'dd': {
19150 if ($this->rtl) {
19151 $this->rMargin += $this->listindent;
19152 } else {
19153 $this->lMargin += $this->listindent;
19155 ++$this->listindentlevel;
19156 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19157 break;
19159 case 'ul':
19160 case 'ol': {
19161 ++$this->listnum;
19162 if ($tag['value'] == 'ol') {
19163 $this->listordered[$this->listnum] = true;
19164 } else {
19165 $this->listordered[$this->listnum] = false;
19167 if (isset($tag['attribute']['start'])) {
19168 $this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
19169 } else {
19170 $this->listcount[$this->listnum] = 0;
19172 if ($this->rtl) {
19173 $this->rMargin += $this->listindent;
19174 $this->x -= $this->listindent;
19175 } else {
19176 $this->lMargin += $this->listindent;
19177 $this->x += $this->listindent;
19179 ++$this->listindentlevel;
19180 if ($this->listnum == 1) {
19181 if ($key > 1) {
19182 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19184 } else {
19185 $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
19187 break;
19189 case 'li': {
19190 if ($key > 2) {
19191 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19193 if ($this->listordered[$this->listnum]) {
19194 // ordered item
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;
19201 } else {
19202 $this->lispacer = '#';
19204 ++$this->listcount[$this->listnum];
19205 if (isset($tag['attribute']['value'])) {
19206 $this->listcount[$this->listnum] = intval($tag['attribute']['value']);
19208 } else {
19209 // unordered item
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;
19216 } else {
19217 $this->lispacer = '!';
19220 break;
19222 case 'blockquote': {
19223 if ($this->rtl) {
19224 $this->rMargin += $this->listindent;
19225 } else {
19226 $this->lMargin += $this->listindent;
19228 ++$this->listindentlevel;
19229 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19230 break;
19232 case 'br': {
19233 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19234 break;
19236 case 'div': {
19237 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19238 break;
19240 case 'p': {
19241 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19242 break;
19244 case 'pre': {
19245 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19246 $this->premode = true;
19247 break;
19249 case 'sup': {
19250 $this->setXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
19251 break;
19253 case 'sub': {
19254 $this->setXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
19255 break;
19257 case 'h1':
19258 case 'h2':
19259 case 'h3':
19260 case 'h4':
19261 case 'h5':
19262 case 'h6': {
19263 $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
19264 break;
19266 // Form fields (since 4.8.000 - 2009-09-07)
19267 case 'form': {
19268 if (isset($tag['attribute']['action'])) {
19269 $this->form_action = $tag['attribute']['action'];
19270 } else {
19271 $this->Error('Please explicitly set action attribute path!');
19273 if (isset($tag['attribute']['enctype'])) {
19274 $this->form_enctype = $tag['attribute']['enctype'];
19275 } else {
19276 $this->form_enctype = 'application/x-www-form-urlencoded';
19278 if (isset($tag['attribute']['method'])) {
19279 $this->form_mode = $tag['attribute']['method'];
19280 } else {
19281 $this->form_mode = 'post';
19283 break;
19285 case 'input': {
19286 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19287 $name = $tag['attribute']['name'];
19288 } else {
19289 break;
19291 $prop = array();
19292 $opt = array();
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;
19305 } else {
19306 $w = $h;
19308 if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
19309 $checked = true;
19310 } else {
19311 $checked = false;
19313 if (isset($tag['align'])) {
19314 switch ($tag['align']) {
19315 case 'C': {
19316 $opt['q'] = 1;
19317 break;
19319 case 'R': {
19320 $opt['q'] = 2;
19321 break;
19323 case 'L':
19324 default: {
19325 break;
19329 switch ($tag['attribute']['type']) {
19330 case 'text': {
19331 if (isset($value)) {
19332 $opt['v'] = $value;
19334 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19335 break;
19337 case 'password': {
19338 if (isset($value)) {
19339 $opt['v'] = $value;
19341 $prop['password'] = 'true';
19342 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19343 break;
19345 case 'checkbox': {
19346 if (!isset($value)) {
19347 break;
19349 $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
19350 break;
19352 case 'radio': {
19353 if (!isset($value)) {
19354 break;
19356 $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
19357 break;
19359 case 'submit': {
19360 if (!isset($value)) {
19361 $value = 'submit';
19363 $w = $this->GetStringWidth($value) * 1.5;
19364 $h *= 1.6;
19365 $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
19366 $action = array();
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);
19376 break;
19378 case 'reset': {
19379 if (!isset($value)) {
19380 $value = 'reset';
19382 $w = $this->GetStringWidth($value) * 1.5;
19383 $h *= 1.6;
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);
19386 break;
19388 case 'file': {
19389 $prop['fileSelect'] = 'true';
19390 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19391 if (!isset($value)) {
19392 $value = '*';
19394 $w = $this->GetStringWidth($value) * 2;
19395 $h *= 1.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);
19399 break;
19401 case 'hidden': {
19402 if (isset($value)) {
19403 $opt['v'] = $value;
19405 $opt['f'] = array('invisible', 'hidden');
19406 $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
19407 break;
19409 case 'image': {
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'];
19413 } else {
19414 break;
19416 $value = 'img';
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'];
19420 } else {
19421 $jsaction = '';
19423 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19424 break;
19426 case 'button': {
19427 if (!isset($value)) {
19428 $value = ' ';
19430 $w = $this->GetStringWidth($value) * 1.5;
19431 $h *= 1.6;
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'];
19435 } else {
19436 $jsaction = '';
19438 $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
19439 break;
19442 break;
19444 case 'textarea': {
19445 $prop = array();
19446 $opt = array();
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'];
19452 } else {
19453 break;
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;
19460 } else {
19461 $w = 40;
19463 if (isset($tag['attribute']['rows']) AND !TCPDF_STATIC::empty_string($tag['attribute']['rows'])) {
19464 $h = intval($tag['attribute']['rows']) * $this->getCellHeight($this->FontSize);
19465 } else {
19466 $h = 10;
19468 $prop['multiline'] = 'true';
19469 $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
19470 break;
19472 case 'select': {
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);
19477 $prop = array();
19478 $opt = array();
19479 if (isset($tag['attribute']['name']) AND !TCPDF_STATIC::empty_string($tag['attribute']['name'])) {
19480 $name = $tag['attribute']['name'];
19481 } else {
19482 break;
19484 $w = 0;
19485 if (isset($tag['attribute']['opt']) AND !TCPDF_STATIC::empty_string($tag['attribute']['opt'])) {
19486 $options = explode('#!NwL!#', $tag['attribute']['opt']);
19487 $values = array();
19488 foreach ($options as $val) {
19489 if (strpos($val, '#!TaB!#') !== false) {
19490 $opts = explode('#!TaB!#', $val);
19491 $values[] = $opts;
19492 $w = max($w, $this->GetStringWidth($opts[1]));
19493 } else {
19494 $values[] = $val;
19495 $w = max($w, $this->GetStringWidth($val));
19498 } else {
19499 break;
19501 $w *= 2;
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);
19505 } else {
19506 $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
19508 break;
19510 case 'tcpdf': {
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);
19519 } else {
19520 $this->$tcpdf_method();
19522 $this->newline = true;
19526 break;
19528 default: {
19529 break;
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)) {
19535 // set border
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);
19551 return $dom;
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
19561 * @protected
19563 protected function closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney=0) {
19564 $tag = $dom[$key];
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)
19569 if ($this->rtl) {
19570 $xmax = $this->w;
19571 } else {
19572 $xmax = 0;
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);
19582 } else {
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) {
19588 $cn = 0.6;
19589 } else {
19590 $cn = 1;
19592 if ((!isset($this->tagvspaces[$tag['value']])) AND ($tag['value'] == 'div')) {
19593 $hb = 0;
19594 } else {
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);
19603 // Closing tag
19604 switch($tag['value']) {
19605 case 'tr': {
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']))) {
19659 // page jump
19660 $this->selectColumn(0);
19661 $dom[($dom[$key]['parent'])]['endcolumn'] = 0;
19662 $dom[($dom[$key]['parent'])]['endy'] = $this->y;
19663 } else {
19664 $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
19665 $this->y = $dom[($dom[$key]['parent'])]['endy'];
19667 } else {
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']);
19687 break;
19689 case 'tablehead':
19690 // closing tag used for the thead part
19691 $in_table_head = true;
19692 $this->inthead = false;
19693 case 'table': {
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)));
19699 } else {
19700 $border = 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'])];
19732 // for each row
19733 if (!empty($table_el['trids'])) {
19734 unset($xmax);
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'];
19751 } else {
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']);
19766 $fill = true;
19767 } else {
19768 $fill = false;
19770 $x = $cellpos['startx'];
19771 $y = $parent['starty'];
19772 $starty = $y;
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
19780 $ccode = '';
19781 $this->setPage($page);
19782 if ($this->num_columns < 2) {
19783 // single-column mode
19784 $this->x = $x;
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
19796 $deltacol = 0;
19797 $deltath = 0;
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'];
19803 $this->y = $y;
19804 $this->x = $x;
19805 } elseif ($column == $startcolumn) { // first column
19806 $cborder = $border_start;
19807 $this->y = $starty;
19808 $this->x = $x;
19809 $h = $this->h - $this->y - $this->bMargin;
19810 if ($this->rtl) {
19811 $deltacol = $this->x + $this->rMargin - $this->w;
19812 } else {
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
19833 $deltacol = 0;
19834 $deltath = 0;
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;
19840 $this->x = $x;
19841 $h = $this->h - $this->y - $this->bMargin;
19842 if ($this->rtl) {
19843 $deltacol = $this->x + $this->rMargin - $this->w;
19844 } else {
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
19858 $deltacol = 0;
19859 $deltath = 0;
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
19880 $deltacol = 0;
19881 $deltath = 0;
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;
19902 } else {
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;
19910 } else {
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];
19917 } else {
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
19955 $plendiff = 0;
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
19969 $this->thead = '';
19970 $this->theadMargins = array();
19971 $this->pagedim[$this->page]['tm'] = $this->tMargin;
19974 $parent = $table_el;
19975 break;
19977 case 'a': {
19978 $this->HREF = array();
19979 break;
19981 case 'sup': {
19982 $this->setXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
19983 break;
19985 case 'sub': {
19986 $this->setXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize']) / $this->k));
19987 break;
19989 case 'div': {
19990 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
19991 break;
19993 case 'blockquote': {
19994 if ($this->rtl) {
19995 $this->rMargin -= $this->listindent;
19996 } else {
19997 $this->lMargin -= $this->listindent;
19999 --$this->listindentlevel;
20000 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20001 break;
20003 case 'p': {
20004 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20005 break;
20007 case 'pre': {
20008 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20009 $this->premode = false;
20010 break;
20012 case 'dl': {
20013 --$this->listnum;
20014 if ($this->listnum <= 0) {
20015 $this->listnum = 0;
20016 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20017 } else {
20018 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20020 $this->resetLastH();
20021 break;
20023 case 'dt': {
20024 $this->lispacer = '';
20025 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20026 break;
20028 case 'dd': {
20029 $this->lispacer = '';
20030 if ($this->rtl) {
20031 $this->rMargin -= $this->listindent;
20032 } else {
20033 $this->lMargin -= $this->listindent;
20035 --$this->listindentlevel;
20036 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20037 break;
20039 case 'ul':
20040 case 'ol': {
20041 --$this->listnum;
20042 $this->lispacer = '';
20043 if ($this->rtl) {
20044 $this->rMargin -= $this->listindent;
20045 } else {
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);
20052 } else {
20053 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20055 $this->resetLastH();
20056 break;
20058 case 'li': {
20059 $this->lispacer = '';
20060 $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
20061 break;
20063 case 'h1':
20064 case 'h2':
20065 case 'h3':
20066 case 'h4':
20067 case 'h5':
20068 case 'h6': {
20069 $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
20070 break;
20072 // Form fields (since 4.8.000 - 2009-09-07)
20073 case 'form': {
20074 $this->form_action = '';
20075 $this->form_enctype = 'application/x-www-form-urlencoded';
20076 break;
20078 default : {
20079 break;
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;
20098 return $dom;
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.
20108 * @protected
20110 protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
20111 if ($firsttag) {
20112 $this->Ln(0, $cell);
20113 $this->htmlvspace = 0;
20114 return;
20116 if ($lasttag) {
20117 $this->Ln($hbz, $cell);
20118 $this->htmlvspace = 0;
20119 return;
20121 if ($hb < $this->htmlvspace) {
20122 $hd = 0;
20123 } else {
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
20133 * @protected
20134 * @since 5.7.000 (2010-08-03)
20136 protected function getBorderStartPosition() {
20137 if ($this->rtl) {
20138 $xmax = $this->lMargin;
20139 } else {
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.
20149 * @protected
20150 * @since 5.7.000 (2010-08-03)
20152 protected function drawHTMLTagBorder($tag, $xmax) {
20153 if (!isset($tag['borderposition'])) {
20154 // nothing to draw
20155 return;
20157 $prev_x = $this->x;
20158 $prev_y = $this->y;
20159 $prev_lasth = $this->lasth;
20160 $border = 0;
20161 $fill = false;
20162 $this->lasth = 0;
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']);
20175 $fill = true;
20177 if (!$border AND !$fill) {
20178 // nothing to draw
20179 return;
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'];
20186 } else {
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';
20193 if ($this->rtl) {
20194 if ($xmax >= $tag['borderposition']['x']) {
20195 $xmax = $tag['borderposition']['xmax'];
20197 $w = ($tag['borderposition']['x'] - $xmax);
20198 } else {
20199 if ($xmax <= $tag['borderposition']['x']) {
20200 $xmax = $tag['borderposition']['xmax'];
20202 $w = ($xmax - $tag['borderposition']['x']);
20204 if ($w <= 0) {
20205 return;
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;
20215 $this->x = $x;
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
20230 $ccode = '';
20231 $this->setPage($page);
20232 if ($this->num_columns < 2) {
20233 // single-column mode
20234 $this->x = $x;
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) {
20246 // single page
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) {
20283 // end column
20284 $cborder = $border_end;
20285 $h = $currentY - $this->y;
20286 } else {
20287 // middle column
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;
20310 } else {
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;
20318 } else {
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];
20324 } else {
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')
20351 * @public
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;
20358 return;
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;
20364 } else {
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.
20374 * @public
20375 * @since 4.2.000 (2008-10-29)
20377 public function setBooklet($booklet=true, $inner=-1, $outer=-1) {
20378 $this->booklet = $booklet;
20379 if ($inner >= 0) {
20380 $this->lMargin = $inner;
20382 if ($outer >= 0) {
20383 $this->rMargin = $outer;
20388 * Swap the left and right margins.
20389 * @param boolean $reverse if true swap left and right margins.
20390 * @protected
20391 * @since 4.2.000 (2008-10-29)
20393 protected function swapMargins($reverse=true) {
20394 if ($reverse) {
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.
20414 * @public
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.
20424 * @public
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.
20434 * @public
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
20445 * @public
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
20460 * @public
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');
20465 $retval = 0;
20466 $value = 0;
20467 $unit = 'px';
20468 if ($points) {
20469 $k = 1;
20470 } else {
20471 $k = $this->k;
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)) {
20482 $unit = $munit[1];
20486 switch ($unit) {
20487 // percentage
20488 case '%': {
20489 $retval = (($value * $refsize) / 100);
20490 break;
20492 // relative-size
20493 case 'em': {
20494 $retval = ($value * $refsize);
20495 break;
20497 // height of lower case 'x' (about half the font-size)
20498 case 'ex': {
20499 $retval = ($value * ($refsize / 2));
20500 break;
20502 // absolute-size
20503 case 'in': {
20504 $retval = (($value * $this->dpi) / $k);
20505 break;
20507 // centimeters
20508 case 'cm': {
20509 $retval = (($value / 2.54 * $this->dpi) / $k);
20510 break;
20512 // millimeters
20513 case 'mm': {
20514 $retval = (($value / 25.4 * $this->dpi) / $k);
20515 break;
20517 // one pica is 12 points
20518 case 'pc': {
20519 $retval = (($value * 12) / $k);
20520 break;
20522 // points
20523 case 'pt': {
20524 $retval = ($value / $k);
20525 break;
20527 // pixels
20528 case 'px': {
20529 $retval = $this->pixelsToUnits($value);
20530 if ($points) {
20531 $retval *= $this->k;
20533 break;
20536 return $retval;
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
20544 * @protected
20545 * @since 4.4.004 (2008-12-10)
20547 protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
20548 if ($this->state != 2) {
20549 return;
20551 $size /= $this->k;
20552 $fill = '';
20553 $bgcolor = $this->bgcolor;
20554 $color = $this->fgcolor;
20555 $strokecolor = $this->strokecolor;
20556 $width = 0;
20557 $textitem = '';
20558 $tmpx = $this->x;
20559 $lspace = $this->GetStringWidth(' ');
20560 if ($listtype == '^') {
20561 // special symbol used for avoid justification of rect bullet
20562 $this->lispacer = '';
20563 return;
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);
20574 $listtype = 'img';
20576 switch ($listtype) {
20577 // unordered types
20578 case 'none': {
20579 break;
20581 case 'disc': {
20582 $r = $size / 6;
20583 $lspace += (2 * $r);
20584 if ($this->rtl) {
20585 $this->x += $lspace;
20586 } else {
20587 $this->x -= $lspace;
20589 $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, 'F', array(), $color, 8);
20590 break;
20592 case 'circle': {
20593 $r = $size / 6;
20594 $lspace += (2 * $r);
20595 if ($this->rtl) {
20596 $this->x += $lspace;
20597 } else {
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
20604 break;
20606 case 'square': {
20607 $l = $size / 3;
20608 $lspace += $l;
20609 if ($this->rtl) {;
20610 $this->x += $lspace;
20611 } else {
20612 $this->x -= $lspace;
20614 $this->Rect($this->x, ($this->y + (($this->lasth - $l) / 2)), $l, $l, 'F', array(), $color);
20615 break;
20617 case 'img': {
20618 // 1=>type, 2=>width, 3=>height, 4=>image.ext
20619 $lspace += $img[2];
20620 if ($this->rtl) {;
20621 $this->x += $lspace;
20622 } else {
20623 $this->x -= $lspace;
20625 $imgtype = strtolower($img[1]);
20626 $prev_y = $this->y;
20627 switch ($imgtype) {
20628 case 'svg': {
20629 $this->ImageSVG($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', 'T', '', 0, false);
20630 break;
20632 case 'ai':
20633 case 'eps': {
20634 $this->ImageEps($img[4], $this->x, ($this->y + (($this->lasth - $img[3]) / 2)), $img[2], $img[3], '', true, 'T', '', 0, false);
20635 break;
20637 default: {
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);
20639 break;
20642 $this->y = $prev_y;
20643 break;
20645 // ordered types
20646 // $this->listcount[$this->listnum];
20647 // $textitem
20648 case '1':
20649 case 'decimal': {
20650 $textitem = $this->listcount[$this->listnum];
20651 break;
20653 case 'decimal-leading-zero': {
20654 $textitem = sprintf('%02d', $this->listcount[$this->listnum]);
20655 break;
20657 case 'i':
20658 case 'lower-roman': {
20659 $textitem = strtolower(TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]));
20660 break;
20662 case 'I':
20663 case 'upper-roman': {
20664 $textitem = TCPDF_STATIC::intToRoman($this->listcount[$this->listnum]);
20665 break;
20667 case 'a':
20668 case 'lower-alpha':
20669 case 'lower-latin': {
20670 $textitem = chr(97 + $this->listcount[$this->listnum] - 1);
20671 break;
20673 case 'A':
20674 case 'upper-alpha':
20675 case 'upper-latin': {
20676 $textitem = chr(65 + $this->listcount[$this->listnum] - 1);
20677 break;
20679 case 'lower-greek': {
20680 $textitem = TCPDF_FONTS::unichr((945 + $this->listcount[$this->listnum] - 1), $this->isunicode);
20681 break;
20684 // Types to be implemented (special handling)
20685 case 'hebrew': {
20686 break;
20688 case 'armenian': {
20689 break;
20691 case 'georgian': {
20692 break;
20694 case 'cjk-ideographic': {
20695 break;
20697 case 'hiragana': {
20698 break;
20700 case 'katakana': {
20701 break;
20703 case 'hiragana-iroha': {
20704 break;
20706 case 'katakana-iroha': {
20707 break;
20710 default: {
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)) {
20719 $tmpx = $this->x;
20721 // print ordered item
20722 if ($this->rtl) {
20723 $textitem = '.'.$textitem;
20724 } else {
20725 $textitem = $textitem.'.';
20727 $lspace += $this->GetStringWidth($textitem);
20728 if ($this->rtl) {
20729 $this->x += $lspace;
20730 } else {
20731 $this->x -= $lspace;
20733 $this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
20735 $this->x = $tmpx;
20736 $this->lispacer = '^';
20737 // restore colors
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
20746 * @protected
20747 * @since 4.2.010 (2008-11-14)
20749 protected function getGraphicVars() {
20750 $grapvars = array(
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,
20782 // extended
20783 'lasth' => $this->lasth,
20784 'tMargin' => $this->tMargin,
20785 'bMargin' => $this->bMargin,
20786 'AutoPageBreak' => $this->AutoPageBreak,
20787 'PageBreakTrigger' => $this->PageBreakTrigger,
20788 'x' => $this->x,
20789 'y' => $this->y,
20790 'w' => $this->w,
20791 'h' => $this->h,
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
20800 return $grapvars;
20804 * Set graphic variables.
20805 * @param array $gvars array of graphic variablesto restore
20806 * @param boolean $extended if true restore extended graphic variables
20807 * @protected
20808 * @since 4.2.010 (2008-11-14)
20810 protected function setGraphicVars($gvars, $extended=false) {
20811 if ($this->state != 2) {
20812 return;
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'];
20845 if ($extended) {
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'
20872 * @protected
20874 protected function _outSaveGraphicsState() {
20875 $this->_out('q');
20879 * Outputs the "restore graphics state" operator 'Q'
20880 * @protected
20882 protected function _outRestoreGraphicsState() {
20883 $this->_out('Q');
20887 * Set buffer content (always append data).
20888 * @param string $data data
20889 * @protected
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
20900 * @protected
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
20911 * @protected
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.
20923 * @protected
20924 * @since 4.5.000 (2008-12-31)
20926 protected function setPageBuffer($page, $data, $append=false) {
20927 if ($append) {
20928 $this->pages[$page] .= $data;
20929 } else {
20930 $this->pages[$page] = $data;
20932 if ($append AND isset($this->pagelen[$page])) {
20933 $this->pagelen[$page] += strlen($data);
20934 } else {
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
20943 * @protected
20944 * @since 4.5.000 (2008-12-31)
20946 protected function getPageBuffer($page) {
20947 if (isset($this->pages[$page])) {
20948 return $this->pages[$page];
20950 return false;
20954 * Set image buffer content.
20955 * @param string $image image key
20956 * @param array $data image data
20957 * @return int image index number
20958 * @protected
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;
20968 return $data['i'];
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
20976 * @protected
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
20990 * @protected
20991 * @since 4.5.000 (2008-12-31)
20993 protected function getImageBuffer($image) {
20994 if (isset($this->images[$image])) {
20995 return $this->images[$image];
20997 return false;
21001 * Set font buffer content.
21002 * @param string $font font key
21003 * @param array $data font data
21004 * @protected
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
21012 ++$this->n;
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
21023 * @protected
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
21037 * @protected
21038 * @since 4.5.000 (2009-01-02)
21040 protected function getFontBuffer($font) {
21041 if (isset($this->fonts[$font])) {
21042 return $this->fonts[$font];
21044 return false;
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.
21052 * @public
21053 * @since 4.5.000 (2009-01-02)
21055 public function movePage($frompage, $topage) {
21056 if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
21057 return false;
21059 if ($frompage == $this->page) {
21060 // close the page before moving it
21061 $this->endPage();
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]];
21087 break;
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]];
21093 break;
21097 for ($i = $frompage; $i > $topage; --$i) {
21098 $j = $i - 1;
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]);
21162 // adjust outlines
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;
21173 // adjust dests
21174 $tmpdests = $this->dests;
21175 foreach ($tmpdests as $key => $dest) {
21176 if (!$dest['f']) {
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;
21184 // adjust links
21185 $tmplinks = $this->links;
21186 foreach ($tmplinks as $key => $link) {
21187 if (!$link['f']) {
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;
21205 } else {
21206 $newpage = $pagenum;
21208 --$newpage;
21209 $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21210 $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21212 unset($pamatch);
21214 // return to last page
21215 $this->lastPage(true);
21216 return 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.
21223 * @public
21224 * @since 4.6.004 (2009-04-23)
21226 public function deletePage($page) {
21227 if (($page < 1) OR ($page > $this->numpages)) {
21228 return false;
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]];
21259 break;
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) {
21269 $j = $i + 1;
21270 // shift pages
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]);
21349 --$this->numpages;
21350 $this->page = $this->numpages;
21351 // adjust outlines
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]);
21362 // adjust dests
21363 $tmpdests = $this->dests;
21364 foreach ($tmpdests as $key => $dest) {
21365 if (!$dest['f']) {
21366 if ($dest['p'] > $page) {
21367 $this->dests[$key]['p'] = $dest['p'] - 1;
21368 } elseif ($dest['p'] == $page) {
21369 unset($this->dests[$key]);
21373 // adjust links
21374 $tmplinks = $this->links;
21375 foreach ($tmplinks as $key => $link) {
21376 if (!$link['f']) {
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
21385 $jpage = $page;
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) {
21392 $newpage = 1;
21393 } else {
21394 $newpage = $pagenum;
21396 --$newpage;
21397 $newjs = "this.addField(\'".$pamatch[1][$pk]."\',\'".$pamatch[2][$pk]."\',".$newpage;
21398 $this->javascript = str_replace($pmatch, $newjs, $this->javascript);
21400 unset($pamatch);
21402 // return to last page
21403 if ($this->numpages > 0) {
21404 $this->lastPage(true);
21406 return 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.
21413 * @public
21414 * @since 4.9.015 (2010-04-20)
21416 public function copyPage($page=0) {
21417 if ($page == 0) {
21418 // default value
21419 $page = $this->page;
21421 if (($page < 1) OR ($page > $this->numpages)) {
21422 return false;
21424 // close the last page
21425 $this->endPage();
21426 // copy all page-related states
21427 ++$this->numpages;
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];
21457 // copy outlines
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']);
21464 // copy links
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);
21473 return 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).
21488 * @public
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)) {
21511 $filler = ' ';
21513 if (TCPDF_STATIC::empty_string($page)) {
21514 $gap = ' ';
21515 } else {
21516 $gap = '';
21517 if ($page < 1) {
21518 $page = 1;
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);
21529 if ($this->rtl) {
21530 $aligntext = 'R';
21531 $alignnum = 'L';
21532 } else {
21533 $aligntext = 'L';
21534 $alignnum = 'R';
21536 if ($outline['l'] == 0) {
21537 $this->setFont($fontfamily, $outline['s'].'B', $fontsize);
21538 } else {
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;
21548 } else {
21549 if ($this->current_column != $current_column) {
21550 if ($this->rtl) {
21551 $x_start = $this->w - $this->columns[$this->current_column]['x'];
21552 } else {
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']);
21563 if ($this->rtl) {
21564 $this->x -= $indent;
21565 $this->rMargin = $this->w - $this->x;
21566 } else {
21567 $this->x += $indent;
21568 $this->lMargin = $this->x;
21570 $link = $this->AddLink();
21571 $this->setLink($link, $outline['y'], $outline['p']);
21572 // write the text
21573 if ($this->rtl) {
21574 $txt = ' '.$outline['t'];
21575 } else {
21576 $txt = $outline['t'].' ';
21578 $this->Write(0, $txt, $link, false, $aligntext, false, 0, false, false, 0, $numwidth, '');
21579 if ($this->rtl) {
21580 $tw = $this->x - $this->lMargin;
21581 } else {
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'];
21587 } else {
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);
21599 } else {
21600 $numfills = 0;
21602 if ($numfills > 0) {
21603 $rowfill = str_repeat($filler, $numfills);
21604 } else {
21605 $rowfill = '';
21607 if ($this->rtl) {
21608 $pagenum = $pagenum.$gap.$rowfill;
21609 } else {
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)
21624 $this->addPage();
21625 ++$page_last;
21626 ++$numpages;
21628 if ($page_fill_end) {
21629 // add a page at the end
21630 $this->addPage();
21631 ++$page_last;
21632 ++$numpages;
21635 $maxpage = max($maxpage, $page_last);
21636 if (!TCPDF_STATIC::empty_string($page)) {
21637 for ($p = $page_first; $p <= $page_last; ++$p) {
21638 // get page data
21639 $temppage = $this->getPageBuffer($p);
21640 for ($n = 1; $n <= $maxpage; ++$n) {
21641 // update page numbers
21642 $a = '{#'.$n.'}';
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;
21648 } else {
21649 $np = $n;
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.' '))));
21656 if ($this->rtl) {
21657 $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21658 } else {
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.' '))));
21665 if ($this->rtl) {
21666 $nr = $na.' '.$sfill;
21667 } else {
21668 $nr = $sfill.' '.$na;
21670 $temppage = str_replace($a, $nr, $temppage);
21673 // save changes
21674 $this->setPageBuffer($p, $temppage);
21676 // move pages
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).
21698 * @public
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)) {
21704 $filler = ' ';
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'];
21731 } else {
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)
21760 $this->addPage();
21761 ++$page_last;
21762 ++$numpages;
21764 if ($page_fill_end) {
21765 // add a page at the end
21766 $this->addPage();
21767 ++$page_last;
21768 ++$numpages;
21771 $maxpage = max($maxpage, $page_last);
21772 if (!TCPDF_STATIC::empty_string($page)) {
21773 for ($p = $page_first; $p <= $page_last; ++$p) {
21774 // get page data
21775 $temppage = $this->getPageBuffer($p);
21776 for ($n = 1; $n <= $maxpage; ++$n) {
21777 // update page numbers
21778 $a = '{#'.$n.'}';
21779 // get page number aliases
21780 $pnalias = $this->getInternalPageNumberAliases($a);
21781 // calculate replacement number
21782 if ($n >= $page) {
21783 $np = $n + $numpages;
21784 } else {
21785 $np = $n;
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.' ')));
21793 if ($this->rtl) {
21794 $nr = $nu.TCPDF_FONTS::UTF8ToUTF16BE(' '.$sfill, false, $this->isunicode, $this->CurrentFont);
21795 } else {
21796 $nr = TCPDF_FONTS::UTF8ToUTF16BE($sfill.' ', false, $this->isunicode, $this->CurrentFont).$nu;
21798 } else {
21799 $nr = $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.' ')));
21806 if ($this->rtl) {
21807 $nr = $na.' '.$sfill;
21808 } else {
21809 $nr = $sfill.' '.$na;
21811 } else {
21812 $nr = $na;
21814 $temppage = str_replace($a, $nr, $temppage);
21817 // save changes
21818 $this->setPageBuffer($p, $temppage);
21820 // move pages
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.
21833 * @public
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.
21850 * @public
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.
21866 * @public
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);
21873 if ($self) {
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;
21881 unset($objcopy);
21882 return $this;
21884 /* The unique file_id should not be used during cleanup again */
21885 $this->file_id = NULL;
21886 return $objcopy;
21888 return $this;
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)
21898 * @public
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) {
21904 $numcols = 0;
21905 $this->columns = array();
21906 } else {
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)) {
21913 $y = $this->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.
21930 * @public
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
21943 * @public
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.
21957 * @public
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) {
21964 $col = 0;
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'];
21973 } else {
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
21987 $colpos = 0;
21988 for ($i = 0; $i < $col; ++$i) {
21989 $colpos += ($this->columns[$i]['w'] + $this->columns[$i]['s']);
21991 if ($this->rtl) {
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;
21996 } else {
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;
22018 $this->lasth = 0;
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
22024 if ($this->rtl) {
22025 $this->rMargin += $xshift['x'];
22026 $this->x -= ($xshift['x'] + $xshift['p']['R']);
22027 } else {
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
22036 * @public
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
22046 * @public
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
22058 * @public
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)) {
22065 $stroke = 0;
22067 if ($fill === true) {
22068 if ($stroke > 0) {
22069 if ($clip === true) {
22070 // Fill, then stroke text and add to path for clipping
22071 $textrendermode = 6;
22072 } else {
22073 // Fill, then stroke text
22074 $textrendermode = 2;
22076 $textstrokewidth = $stroke;
22077 } else {
22078 if ($clip === true) {
22079 // Fill text and add to path for clipping
22080 $textrendermode = 4;
22081 } else {
22082 // Fill text
22083 $textrendermode = 0;
22086 } else {
22087 if ($stroke > 0) {
22088 if ($clip === true) {
22089 // Stroke text and add to path for clipping
22090 $textrendermode = 5;
22091 } else {
22092 // Stroke text
22093 $textrendermode = 1;
22095 $textstrokewidth = $stroke;
22096 } else {
22097 if ($clip === true) {
22098 // Add text to path for clipping
22099 $textrendermode = 7;
22100 } else {
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)
22114 * @public
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;
22119 } else {
22120 $this->txtshadow['enabled'] = false;
22122 if (isset($params['depth_w'])) {
22123 $this->txtshadow['depth_w'] = floatval($params['depth_w']);
22124 } else {
22125 $this->txtshadow['depth_w'] = 0;
22127 if (isset($params['depth_h'])) {
22128 $this->txtshadow['depth_h'] = floatval($params['depth_h']);
22129 } else {
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'];
22134 } else {
22135 $this->txtshadow['color'] = $this->strokecolor;
22137 if (isset($params['opacity'])) {
22138 $this->txtshadow['opacity'] = min(1, max(0, floatval($params['opacity'])));
22139 } else {
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'];
22144 } else {
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)
22156 * @public
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)
22174 * @protected
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) {
22180 return $word;
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) {
22186 // email
22187 return $word;
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) {
22191 // URL
22192 return $word;
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);
22208 $digits = 1;
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)) {
22212 if ($j == 0) {
22213 $zero = $pos - 1;
22214 } else {
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;
22223 ++$digits;
22229 $inserted = 0;
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);
22236 ++$inserted;
22239 return $word;
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)
22254 * @public
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);
22267 // for each char
22268 foreach ($unichars as $char) {
22269 if ((!$intag) AND (!$skip) AND TCPDF_FONT_DATA::$uni_type[$char] == 'L') {
22270 // letter character
22271 $word[] = $char;
22272 } else {
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));
22277 $word = array();
22279 $txtarr[] = $char;
22280 if (chr($char) == '<') {
22281 // we are inside an HTML tag
22282 $intag = true;
22283 } elseif ($intag AND (chr($char) == '>')) {
22284 // end of HTML tag
22285 $intag = false;
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
22297 $skip = false;
22298 } else {
22299 // opening style tag
22300 $skip = true;
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.
22317 * @public
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
22328 * @public
22329 * @since 5.3.002 (2010-06-07)
22331 public function setFontSubsetting($enable=true) {
22332 if ($this->pdfa_mode) {
22333 $this->font_subsetting = false;
22334 } else {
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
22343 * @public
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
22356 * @public
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
22369 * @public
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
22382 * @public
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);
22388 return $str;
22392 * Return true if the current font is unicode type.
22393 * @return bool true for unicode font, false otherwise.
22394 * @author Nicola Asuni
22395 * @public
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
22407 * @public
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)) {
22433 return $font;
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
22450 * @public
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
22457 return false;
22459 $this->inxobj = true;
22460 ++$this->n;
22461 // XObject ID
22462 $this->xobjid = 'XT'.$this->n;
22463 // object ID
22464 $this->xobjects[$this->xobjid] = array('n' => $this->n);
22465 // store current graphic state
22466 $this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
22467 // initialize data
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;
22492 $this->w = $w;
22493 $this->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;
22498 $this->x = 0;
22499 $this->y = 0;
22500 $this->lMargin = 0;
22501 $this->rMargin = 0;
22502 $this->tMargin = 0;
22503 $this->bMargin = 0;
22504 // set group mode
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
22515 * @public
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
22522 return false;
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
22544 * @public
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) {
22550 return;
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();
22559 } else {
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)) {
22566 $x = $this->x;
22568 if (TCPDF_STATIC::empty_string($y)) {
22569 $y = $this->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'];
22574 if ($ow <= 0) {
22575 $ow = 1;
22577 $oh = $this->xobjects[$id]['h'];
22578 if ($oh <= 0) {
22579 $oh = 1;
22581 // calculate template width and height on document
22582 if (($w <= 0) AND ($h <= 0)) {
22583 $w = $ow;
22584 $h = $oh;
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
22593 $rb_y = $y + $h;
22594 // set alignment
22595 if ($this->rtl) {
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;
22602 } else {
22603 $xt = $x - $w;
22605 $rb_x = $xt;
22606 } else {
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;
22613 } else {
22614 $xt = $x;
22616 $rb_x = $xt + $w;
22618 // print XObject Template + Transformation matrix
22619 $this->StartTransform();
22620 // translate and scale
22621 $sx = ($w / $ow);
22622 $sy = ($h / $oh);
22623 $tm = array();
22624 $tm[0] = $sx;
22625 $tm[1] = 0;
22626 $tm[2] = 0;
22627 $tm[3] = $sy;
22628 $tm[4] = $xt * $this->k;
22629 $tm[5] = ($this->h - $h - $y) * $this->k;
22630 $this->Transform($tm);
22631 // set object
22632 $this->_out('/'.$id.' Do');
22633 $this->StopTransform();
22634 // add annotations
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
22648 switch($align) {
22649 case 'T': {
22650 $this->y = $y;
22651 $this->x = $rb_x;
22652 break;
22654 case 'M': {
22655 $this->y = $y + round($h/2);
22656 $this->x = $rb_x;
22657 break;
22659 case 'B': {
22660 $this->y = $rb_y;
22661 $this->x = $rb_x;
22662 break;
22664 case 'N': {
22665 $this->setY($rb_y);
22666 break;
22668 default:{
22669 break;
22675 * Set the percentage of character stretching.
22676 * @param int $perc percentage of stretching (100 = no stretching)
22677 * @author Nicola Asuni
22678 * @public
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
22689 * @public
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
22700 * @public
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
22711 * @public
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
22722 * @public
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
22737 * @public
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();
22744 // add regions
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
22757 * @public
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
22776 * @public
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
22795 * @protected
22796 * @since 5.9.003 (2010-10-13)
22798 protected function checkPageRegions($h, $x, $y) {
22799 // set default values
22800 if ($x === '') {
22801 $x = $this->x;
22803 if ($y === '') {
22804 $y = $this->y;
22806 if (!$this->check_page_regions OR empty($this->page_regions)) {
22807 // no page regions defined
22808 return array($x, $y);
22810 if (empty($h)) {
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
22816 $x = $this->x;
22817 $y = $this->y;
22819 if ($this->num_columns > 1) {
22820 if ($this->rtl) {
22821 $this->lMargin = ($this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22822 } else {
22823 $this->rMargin = ($this->w - $this->columns[$this->current_column]['x'] - $this->columns[$this->current_column]['w']);
22825 } else {
22826 if ($this->rtl) {
22827 $this->lMargin = max($this->clMargin, $this->original_lMargin);
22828 } else {
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) {
22846 if ($this->rtl) {
22847 // adjust left page margin
22848 $this->lMargin = max(0, $new_margin);
22850 if ($x < $new_margin) {
22851 // adjust x position
22852 $x = $new_margin;
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) {
22862 if (!$this->rtl) {
22863 // adjust right page margin
22864 $this->rMargin = max(0, ($this->w - $new_margin));
22866 if ($x > $new_margin) {
22867 // adjust x position
22868 $x = $new_margin;
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)
22899 * @public
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) {
22903 return;
22905 // reset SVG vars
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)) {
22931 $x = $this->x;
22933 if (TCPDF_STATIC::empty_string($y)) {
22934 $y = $this->y;
22936 // check page for no-write regions and adapt page margins if necessary
22937 list($x, $y) = $this->checkPageRegions($h, $x, $y);
22938 $k = $this->k;
22939 $ox = 0;
22940 $oy = 0;
22941 $ow = $w;
22942 $oh = $h;
22943 $aspect_ratio_align = 'xMidYMid';
22944 $aspect_ratio_ms = 'meet';
22945 $regs = array();
22946 // get original image width and height
22947 preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
22948 if (isset($regs[1]) AND !empty($regs[1])) {
22949 $tmp = array();
22950 if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22951 $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22953 $tmp = array();
22954 if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22955 $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
22957 $tmp = array();
22958 if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22959 $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22961 $tmp = array();
22962 if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
22963 $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
22965 $tmp = array();
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) {
22969 array_shift($tmp);
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
22977 $tmp = array();
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)) {
22981 case 3: {
22982 $aspect_ratio_align = $aspect_ratio[1];
22983 $aspect_ratio_ms = $aspect_ratio[2];
22984 break;
22986 case 2: {
22987 $aspect_ratio_align = $aspect_ratio[0];
22988 $aspect_ratio_ms = $aspect_ratio[1];
22989 break;
22991 case 1: {
22992 $aspect_ratio_align = $aspect_ratio[0];
22993 $aspect_ratio_ms = 'meet';
22994 break;
23000 if ($ow <= 0) {
23001 $ow = 1;
23003 if ($oh <= 0) {
23004 $oh = 1;
23006 // calculate image width and height on document
23007 if (($w <= 0) AND ($h <= 0)) {
23008 // convert image size to document unit
23009 $w = $ow;
23010 $h = $oh;
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);
23022 // set alignment
23023 $this->img_rb_y = $y + $h;
23024 // set alignment
23025 if ($this->rtl) {
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;
23032 } else {
23033 $ximg = $x - $w;
23035 $this->img_rb_x = $ximg;
23036 } else {
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;
23043 } else {
23044 $ximg = $x;
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];
23056 } else {
23057 if ($ow <= 0) {
23058 $ow = $w;
23060 if ($oh <= 0) {
23061 $oh = $h;
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;
23079 } else { // meet
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)) {
23089 case 'Min': {
23090 // do nothing
23091 break;
23093 case 'Max': {
23094 $svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
23095 break;
23097 default:
23098 case 'Mid': {
23099 $svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
23100 break;
23103 // correct Y alignment
23104 switch (substr($aspect_ratio_align, 5)) {
23105 case 'Min': {
23106 // do nothing
23107 break;
23109 case 'Max': {
23110 $svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
23111 break;
23113 default:
23114 case 'Mid': {
23115 $svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
23116 break;
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"
23153 unset($parser);
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)) {
23161 $bx = $this->x;
23162 $by = $this->y;
23163 $this->x = $ximg;
23164 if ($this->rtl) {
23165 $this->x += $w;
23167 $this->y = $y;
23168 $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
23169 $this->x = $bx;
23170 $this->y = $by;
23172 if ($link) {
23173 $this->Link($ximg, $y, $w, $h, $link, 0);
23175 // set pointer to align the next text/objects
23176 switch($align) {
23177 case 'T':{
23178 $this->y = $y;
23179 $this->x = $this->img_rb_x;
23180 break;
23182 case 'M':{
23183 $this->y = $y + round($h/2);
23184 $this->x = $this->img_rb_x;
23185 break;
23187 case 'B':{
23188 $this->y = $this->img_rb_y;
23189 $this->x = $this->img_rb_x;
23190 break;
23192 case 'N':{
23193 $this->setY($this->img_rb_y);
23194 break;
23196 default:{
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'];
23210 break;
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
23223 * @protected
23224 * @since 5.0.000 (2010-05-02)
23226 protected function convertSVGtMatrix($tm) {
23227 $a = $tm[0];
23228 $b = -$tm[1];
23229 $c = -$tm[2];
23230 $d = $tm[3];
23231 $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
23232 $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
23233 $x = 0;
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
23243 * @protected
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)
23263 * @protected
23265 protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
23266 if ($this->state != 2) {
23267 return;
23269 $objstyle = '';
23270 $minlen = (0.01 / $this->k); // minimum acceptable length
23271 if (!isset($svgstyle['opacity'])) {
23272 return $objstyle;
23274 // clip-path
23275 $regs = array();
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']);
23282 // opacity
23283 if ($svgstyle['opacity'] != 1) {
23284 $this->setAlpha($svgstyle['opacity'], 'Normal', $svgstyle['opacity'], false);
23286 // color
23287 $fill_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['color'], $this->spot_colors);
23288 $this->setFillColorArray($fill_color);
23289 // text color
23290 $text_color = TCPDF_COLORS::convertHTMLColorToDec($svgstyle['text-color'], $this->spot_colors);
23291 $this->setTextColorArray($text_color);
23292 // clip
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);
23298 $cx = $x + $left;
23299 $cy = $y + $top;
23300 $cw = $w - $left - $right;
23301 $ch = $h - $top - $bottom;
23302 if ($svgstyle['clip-rule'] == 'evenodd') {
23303 $clip_rule = 'CNZ';
23304 } else {
23305 $clip_rule = 'CEO';
23307 $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
23309 // fill
23310 $regs = array();
23311 if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
23312 // gradient
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) {
23360 $w = $minlen;
23362 if ($h <= $minlen) {
23363 $h = $minlen;
23365 // shift units
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);
23382 if ($val < 0) {
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);
23406 } else {
23407 $cy -= $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') {
23420 $objstyle .= 'F*';
23421 } else {
23422 $objstyle .= 'F';
23425 // stroke
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);
23442 $objstyle .= 'D';
23444 // font
23445 $regs = array();
23446 if (!empty($svgstyle['font'])) {
23447 if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
23448 $font_family = $this->getFontFamilyName($regs[1]);
23449 } else {
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]);
23454 } else {
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]);
23459 } else {
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]);
23464 } else {
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]);
23469 } else {
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]);
23474 } else {
23475 $font_spacing = $svgstyle['letter-spacing'];
23477 } else {
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) {
23489 case 'italic': {
23490 $font_style = 'I';
23491 break;
23493 case 'oblique': {
23494 $font_style = 'I';
23495 break;
23497 default:
23498 case 'normal': {
23499 $font_style = '';
23500 break;
23503 switch ($font_weight) {
23504 case 'bold':
23505 case 'bolder': {
23506 $font_style .= 'B';
23507 break;
23509 case 'normal': {
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);
23515 break;
23518 switch ($svgstyle['text-decoration']) {
23519 case 'underline': {
23520 $font_style .= 'U';
23521 break;
23523 case 'overline': {
23524 $font_style .= 'O';
23525 break;
23527 case 'line-through': {
23528 $font_style .= 'D';
23529 break;
23531 default:
23532 case 'none': {
23533 break;
23536 $this->setFont($font_family, $font_style, $font_size);
23537 $this->setFontStretching($font_stretch);
23538 $this->setFontSpacing($font_spacing);
23539 return $objstyle;
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:
23546 * <ul>
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>
23554 * </ul>
23555 * @return array of container box measures (x, y, w, h)
23556 * @author Nicola Asuni
23557 * @since 5.0.000 (2010-05-02)
23558 * @protected
23560 protected function SVGPath($d, $style='') {
23561 if ($this->state != 2) {
23562 return;
23564 // set fill/stroke style
23565 $op = TCPDF_STATIC::getPathPaintOperator($style, '');
23566 if (empty($op)) {
23567 return;
23569 $paths = array();
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);
23573 $x = 0;
23574 $y = 0;
23575 $x1 = 0;
23576 $y1 = 0;
23577 $x2 = 0;
23578 $y2 = 0;
23579 $xmin = 2147483647;
23580 $xmax = 0;
23581 $ymin = 2147483647;
23582 $ymax = 0;
23583 $xinitial = 0;
23584 $yinitial = 0;
23585 $relcoord = false;
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) {
23590 // get curve type
23591 $cmd = trim($val[1]);
23592 if (strtolower($cmd) == $cmd) {
23593 // use relative coordinated instead of absolute
23594 $relcoord = true;
23595 $xoffset = $x;
23596 $yoffset = $y;
23597 } else {
23598 $relcoord = false;
23599 $xoffset = 0;
23600 $yoffset = 0;
23602 $params = array();
23603 if (isset($val[2])) {
23604 // get curve parameters
23605 $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
23606 $params = array();
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
23611 $params[$ck] = 0;
23615 // store current origin point
23616 $x0 = $x;
23617 $y0 = $y;
23618 switch (strtoupper($cmd)) {
23619 case 'M': { // moveto
23620 foreach ($params as $ck => $cp) {
23621 if (($ck % 2) == 0) {
23622 $x = $cp + $xoffset;
23623 } else {
23624 $y = $cp + $yoffset;
23625 if ($firstcmd OR (abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23626 if ($ck == 1) {
23627 $this->_outPoint($x, $y);
23628 $firstcmd = false;
23629 $xinitial = $x;
23630 $yinitial = $y;
23631 } else {
23632 $this->_outLine($x, $y);
23634 $x0 = $x;
23635 $y0 = $y;
23637 $xmin = min($xmin, $x);
23638 $ymin = min($ymin, $y);
23639 $xmax = max($xmax, $x);
23640 $ymax = max($ymax, $y);
23641 if ($relcoord) {
23642 $xoffset = $x;
23643 $yoffset = $y;
23647 break;
23649 case 'L': { // lineto
23650 foreach ($params as $ck => $cp) {
23651 if (($ck % 2) == 0) {
23652 $x = $cp + $xoffset;
23653 } else {
23654 $y = $cp + $yoffset;
23655 if ((abs($x0 - $x) >= $minlen) OR (abs($y0 - $y) >= $minlen)) {
23656 $this->_outLine($x, $y);
23657 $x0 = $x;
23658 $y0 = $y;
23660 $xmin = min($xmin, $x);
23661 $ymin = min($ymin, $y);
23662 $xmax = max($xmax, $x);
23663 $ymax = max($ymax, $y);
23664 if ($relcoord) {
23665 $xoffset = $x;
23666 $yoffset = $y;
23670 break;
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);
23677 $x0 = $x;
23678 $y0 = $y;
23680 $xmin = min($xmin, $x);
23681 $xmax = max($xmax, $x);
23682 if ($relcoord) {
23683 $xoffset = $x;
23686 break;
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);
23693 $x0 = $x;
23694 $y0 = $y;
23696 $ymin = min($ymin, $y);
23697 $ymax = max($ymax, $y);
23698 if ($relcoord) {
23699 $yoffset = $y;
23702 break;
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);
23719 if ($relcoord) {
23720 $xoffset = $x;
23721 $yoffset = $y;
23725 break;
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;
23734 } else {
23735 $x1 = $x;
23736 $y1 = $y;
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);
23747 if ($relcoord) {
23748 $xoffset = $x;
23749 $yoffset = $y;
23753 break;
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);
23773 if ($relcoord) {
23774 $xoffset = $x;
23775 $yoffset = $y;
23779 break;
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;
23788 } else {
23789 $x1 = $x;
23790 $y1 = $y;
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);
23804 if ($relcoord) {
23805 $xoffset = $x;
23806 $yoffset = $y;
23810 break;
23812 case 'A': { // elliptical arc
23813 foreach ($params as $ck => $cp) {
23814 $params[$ck] = $cp;
23815 if ((($ck + 1) % 7) == 0) {
23816 $x0 = $x;
23817 $y0 = $y;
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);
23832 } else {
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);
23839 $rx2 = $rx * $rx;
23840 $ry2 = $ry * $ry;
23841 $xa2 = $xa * $xa;
23842 $ya2 = $ya * $ya;
23843 $delta = ($xa2 / $rx2) + ($ya2 / $ry2);
23844 if ($delta > 1) {
23845 $rx *= sqrt($delta);
23846 $ry *= sqrt($delta);
23847 $rx2 = $rx * $rx;
23848 $ry2 = $ry * $ry;
23850 $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
23851 if ($numerator < 0) {
23852 $root = 0;
23853 } else {
23854 $root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
23856 if ($fa == $fs){
23857 $root *= -1;
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);
23864 // get angles
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))) {
23874 // reverse angles
23875 $tmp = $angs;
23876 $angs = $angf;
23877 $angf = $tmp;
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)) {
23883 $angs += 360;
23884 $angf += 360;
23886 $pie = false;
23887 if (($key == 0) AND (isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
23888 $pie = true;
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);
23896 if ($relcoord) {
23897 $xoffset = $x;
23898 $yoffset = $y;
23902 break;
23904 case 'Z': {
23905 $this->_out('h');
23906 $x = $x0 = $xinitial;
23907 $y = $y0 = $yinitial;
23908 break;
23911 $firstcmd = false;
23912 } // end foreach
23913 $this->_out($op);
23914 return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
23918 * Return the tag name without the namespace
23919 * @param string $name Tag name
23920 * @protected
23922 protected function removeTagNamespace($name) {
23923 if(strpos($name, ':') !== false) {
23924 $parts = explode(':', $name);
23925 return $parts[(sizeof($parts) - 1)];
23927 return $name;
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)
23938 * @protected
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]);
23945 return;
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);
23951 return;
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);
23958 return;
23961 return;
23963 $clipping = false;
23964 if ($parser == 'clip-path') {
23965 // set clipping mode
23966 $clipping = true;
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;
23988 } else {
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;
23997 } else {
23998 $svgstyle[$key] = $attrval[1];
24003 // transformation matrix
24004 if (!empty($ctm)) {
24005 $tm = $ctm;
24006 } else {
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)
24016 $invisible = true;
24018 // process tag
24019 switch($name) {
24020 case 'defs': {
24021 $this->svgdefsmode = true;
24022 break;
24024 // clipPath
24025 case 'clipPath': {
24026 if ($invisible) {
24027 break;
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;
24036 break;
24038 case 'svg': {
24039 // start of SVG object
24040 if(++$this->svg_tag_depth <= 1) {
24041 break;
24043 // inner SVG
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
24054 $x = 0;
24055 $y = 0;
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'])) {
24062 $tmp = array();
24063 preg_match_all("/[0-9]+/", $attribs['viewBox'], $tmp);
24064 $tmp = $tmp[0];
24065 if (sizeof($tmp) == 4) {
24066 $vx = $tmp[0];
24067 $vy = $tmp[1];
24068 $vw = $tmp[2];
24069 $vh = $tmp[3];
24070 // get aspect ratio
24071 $tmp = array();
24072 $aspectX = 'xMid';
24073 $aspectY = 'YMid';
24074 $fit = 'meet';
24075 if (isset($attribs['preserveAspectRatio'])) {
24076 if($attribs['preserveAspectRatio'] == 'none') {
24077 $fit = 'none';
24078 } else {
24079 preg_match_all('/[a-zA-Z]+/', $attribs['preserveAspectRatio'], $tmp);
24080 $tmp = $tmp[0];
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);
24084 $fit = $tmp[1];
24088 $wr = ($svgW / $vw);
24089 $hr = ($svgH / $vh);
24090 $ax = $ay = 0;
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);
24098 $wr = $hr;
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);
24106 $hr = $wr;
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);
24114 break;
24116 case 'g': {
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);
24127 break;
24129 case 'linearGradient': {
24130 if ($this->pdfa_mode && $this->pdfa_version < 2) {
24131 break;
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'];
24142 } else {
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';
24152 } else {
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);
24167 break;
24169 case 'radialGradient': {
24170 if ($this->pdfa_mode && $this->pdfa_version < 2) {
24171 break;
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'];
24182 } else {
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';
24192 } else {
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);
24208 break;
24210 case 'stop': {
24211 // gradient stops
24212 if (substr($attribs['offset'], -1) == '%') {
24213 $offset = floatval(substr($attribs['offset'], 0, -1)) / 100;
24214 } else {
24215 $offset = floatval($attribs['offset']);
24216 if ($offset > 1) {
24217 $offset /= 100;
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);
24223 break;
24225 // paths
24226 case 'path': {
24227 if ($invisible) {
24228 break;
24230 if (isset($attribs['d'])) {
24231 $d = trim($attribs['d']);
24232 if (!empty($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));
24238 if ($clipping) {
24239 $this->SVGTransform($tm);
24240 $this->SVGPath($d, 'CNZ');
24241 } else {
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();
24252 break;
24254 // shapes
24255 case 'rect': {
24256 if ($invisible) {
24257 break;
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);
24265 if ($clipping) {
24266 $this->SVGTransform($tm);
24267 $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
24268 } else {
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();
24277 break;
24279 case 'circle': {
24280 if ($invisible) {
24281 break;
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));
24286 $x = ($cx - $r);
24287 $y = ($cy - $r);
24288 $w = (2 * $r);
24289 $h = $w;
24290 if ($clipping) {
24291 $this->SVGTransform($tm);
24292 $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
24293 } else {
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();
24302 break;
24304 case 'ellipse': {
24305 if ($invisible) {
24306 break;
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));
24312 $x = ($cx - $rx);
24313 $y = ($cy - $ry);
24314 $w = (2 * $rx);
24315 $h = (2 * $ry);
24316 if ($clipping) {
24317 $this->SVGTransform($tm);
24318 $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
24319 } else {
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();
24328 break;
24330 case 'line': {
24331 if ($invisible) {
24332 break;
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);
24338 $x = $x1;
24339 $y = $y1;
24340 $w = abs($x2 - $x1);
24341 $h = abs($y2 - $y1);
24342 if (!$clipping) {
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();
24349 break;
24351 case 'polyline':
24352 case 'polygon': {
24353 if ($invisible) {
24354 break;
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) {
24361 break;
24363 $p = array();
24364 $xmin = 2147483647;
24365 $xmax = 0;
24366 $ymin = 2147483647;
24367 $ymax = 0;
24368 foreach ($points as $key => $val) {
24369 $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
24370 if (($key % 2) == 0) {
24371 // X coordinate
24372 $xmin = min($xmin, $p[$key]);
24373 $xmax = max($xmax, $p[$key]);
24374 } else {
24375 // Y coordinate
24376 $ymin = min($ymin, $p[$key]);
24377 $ymax = max($ymax, $p[$key]);
24380 $x = $xmin;
24381 $y = $ymin;
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
24393 if ($clipping) {
24394 $this->SVGTransform($tm);
24395 $this->Polygon($p, 'CNZ', array(), array(), true);
24396 } else {
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();
24406 break;
24408 // image
24409 case 'image': {
24410 if ($invisible) {
24411 break;
24413 if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
24414 break;
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'];
24421 if (!$clipping) {
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])));
24428 } else {
24429 // fix image path
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;
24439 } else {
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);
24454 // get image type
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') {
24459 // store SVG vars
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;
24482 } else {
24483 $this->Image($img, $x, $y, $w, $h);
24485 $this->StopTransform();
24487 break;
24489 // text
24490 case 'text':
24491 case 'tspan': {
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;
24497 if ($invisible) {
24498 break;
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') {
24504 $x = $this->x;
24505 } else {
24506 $x = 0;
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') {
24514 $y = $this->y;
24515 } else {
24516 $y = 0;
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'];
24525 } else {
24526 $this->svgtextmode['text-anchor'] = 'start';
24528 if (isset($svgstyle['direction'])) {
24529 if ($svgstyle['direction'] == 'rtl') {
24530 $this->svgtextmode['rtl'] = true;
24531 } else {
24532 $this->svgtextmode['rtl'] = false;
24534 } else {
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);
24539 } else {
24540 $this->svgtextmode['stroke'] = false;
24542 $this->StartTransform();
24543 $this->SVGTransform($tm);
24544 $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
24545 $this->x = $x;
24546 $this->y = $y;
24547 break;
24549 // use
24550 case 'use': {
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'])) {
24571 // merge styles
24572 $attribs['style'] = str_replace(';;',';',';'.$use['attribs']['style'].$attribs['style']);
24574 $attribs = array_merge($use['attribs'], $attribs);
24575 $this->startSVGElementHandler($parser, $use['name'], $attribs);
24576 return;
24579 break;
24581 default: {
24582 break;
24584 } // end of switch
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']);
24592 } else {
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)
24608 * @protected
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));
24619 return;
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));
24624 return;
24628 return;
24630 switch($name) {
24631 case 'defs': {
24632 $this->svgdefsmode = false;
24633 break;
24635 // clipPath
24636 case 'clipPath': {
24637 $this->svgclipmode = false;
24638 break;
24640 case 'svg': {
24641 if (--$this->svg_tag_depth <= 0) {
24642 break;
24645 case 'g': {
24646 // ungroup: remove last style from array
24647 array_pop($this->svgstyles);
24648 $this->StopTransform();
24649 break;
24651 case 'text':
24652 case 'tspan': {
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.
24656 break;
24658 // print text
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;
24667 } else {
24668 $this->x -= $textlen;
24670 } elseif ($this->svgtextmode['text-anchor'] == 'middle') {
24671 if ($this->svgtextmode['rtl']) {
24672 $this->x += ($textlen / 2);
24673 } else {
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
24683 $tmpx = $this->x;
24684 $tmpy = $this->y;
24686 // print the text
24687 $this->Cell($textlen, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
24688 if ($name == 'text') {
24689 // restore coordinates
24690 $this->x = $tmpx;
24691 $this->y = $tmpy;
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);
24701 break;
24703 default: {
24704 break;
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)
24715 * @protected
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
24726 * @return string
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
24739 * @return bool
24741 protected function fileExists($file)
24743 if (isset($this->fileContentCache[$file]) || false !== $this->getImageBuffer($file)) {
24744 return true;
24747 return TCPDF_STATIC::file_exists($file);
24750 } // END OF TCPDF CLASS
24752 //============================================================+
24753 // END OF FILE
24754 //============================================================+